--- /dev/null
+The following is a list of the people (in roughly chronological order)
+who've helped out. If anyone's name has been left out (probably), or if
+something has been incorrectly attributed to you (ditto), please let us
+know.
+
+Rich Salz:
+ Designed and wrote most of it.
+
+Bob Halley:
+ Did the TCL extension.
+
+Christophe Wolfhugel:
+ Did the Perl extension and provided several other fixes.
+
+Doug Needham:
+ Made nnrpd spool if innd is unavailable. Made nnrpd handle the
+ LIST SUBSCRIPTIONS command. Added the rebuilding of control
+ connections to innd (SIGUSR1). Got inews to ask the nntp peer for
+ moderator info instead of digging it out of a local file.
+
+David Lawrence:
+ Did the hooks for PGP verificiation of control messages, added
+ actived support for syncing against an active file obtained via
+ ftp.
+
+John Stapleton:
+ Wrote the poison newsgroup code ('@') for newsfeeds(5). Wrote the
+ too-many-connects support ('-X -H -T' flags to innd).
+
+Landon Curt Noll:
+ Wrote or co-wrote actsync, nntpsend, shrinkfile, innstat,
+ news.daily, tally.control and various man pages. He also was the
+ person originally behind the site directory
+ configuration/installation process.
+
+John Levine:
+ Wrote the '-e' support for expire (expire on shortest time).
+
+Matthias Urlichs:
+ Made rnews recognise gzip compression. Made newsfeeds(5) take the
+ 'Wp' flag.
+
+Stefan Petri:
+ Did the original XBATCH support
+
+Russel Street:
+ Did more XBATCH support.
+
+Alan Barrett:
+ Did the work-limiter in the select loop to stop streaming from
+ killing performance.
+
+Greg Patten:
+ Wrote the perl innlog.
+
+Clayton O'Neill:
+ Wrote the articles storage API and implemented the timehash
+ and regular storage mechanisms with it. He made significant
+ modifications to dbz. Integrating innfeed, adding Xref slaving,
+ the history cache, the WIP rewrite and various speedups were
+ also his doing. Provided the tradindexed overview mechanism.
+ Implemented the O flag in newsfeeds. Did a bunch of early work on
+ the CVS repository, reorganization of the code, and committing
+ patches from others.
+
+Vincent Archer:
+ Wrote the initial autoconf scripts.
+
+Forrest J. Cavalier III:
+ Provided a lot of bug fixes to 1.5.2. He extended the autoconf
+ setup a lot to work with version 2.0, and has provided a lot of
+ valuable design input and testing.
+
+Scott Fritchie:
+ Wrote the CNFS storage back end.
+
+Fabien Tassin:
+ Wrote the innreport package. Implemented the new incoming.conf
+ configuration file. Added support for nested profile timers.
+
+Jeremy Nixon:
+ Wrote the initial patch for Perl filtering of message IDs on IHAVE
+ or CHECK and other patches related to the filtering code.
+
+Karl Kleinpaste:
+ Wrote the experimental code for automatically generating keywords
+ from incoming articles and putting those keywords in the overview
+ for the use of readers.
+
+Dave Hayes:
+ Along with some bugfixes, Dave wrote the posting-backoff code for
+ nnrpd and the patches to the perl hooks to make the headers
+ modifiable.
+
+Joe Greco:
+ Wrote the code for measuring the timing of various parts of innd
+ and the original actived code.
+
+Sang-yong Suh:
+ Provided the fuzzy offset technique to dbz.
+
+Katsuhiro Kondou:
+ Provided unified overview, the buffindexed overview method, trash
+ storage method, spool translation method, traditional expire
+ policy for articles stored through storage API and expireindex, as
+ well as hundreds of fixes to clean up defects as changes were
+ made. Did a large amount of man page documentation and clean up.
+ Has also been a major force in the CVS pool maintenace.
+
+Russell Vincent:
+ Expanded inn.conf to make many of the old compile time options
+ into run time variables. Numerous bug fixes, small feature
+ enhancements and man updates.
+
+Darrell Fuhriman:
+ Provided various bug fixes and contributed to the pre-SM CNFS
+ development.
+
+Steve Carrie:
+ Modified nnrpd to allow detailed client tracking, added the -R
+ flag to nnrpd.
+
+Ed Mooring:
+ Wrote the first Perl filter callbacks into INN.
+
+Aidan Cully:
+ Provided the patches to support the new readers.conf file, and
+ wrote the initial user authenticators and resolvers for the
+ readers.conf. Provided the patches to support the new
+ storage.conf format. Added the option to store articles based on
+ the Expires header. Also added the '@' article exclusion code to
+ incoming.conf.
+
+Andrew Gierth:
+ Contributed improvements to the nnrpd Perl filtering support to
+ give access to message bodies and support the DROP and SPOOL
+ keywords in filter returns.
+
+Russ Allbery:
+ Has done large amounts of clean-up on various pieces of the system
+ (especially the documentation and build system), and has helped
+ with the CVS pool maintenance. Improved the speed and portability
+ of the Perl filter. Rewrote the tradindexed overview method for
+ additional robustness. Has done extensive work on libinn,
+ breaking out common code from other parts of INN. Lots of other
+ fixes to various parts of INN.
+
+Kai Henningsen:
+ Implemented the C and U flags in newsfeeds.
+
+Julio Sanchez:
+ Wrote the initial libtool support for INN.
+
+Igor Timkin:
+ Added min-queue-connection support to innfeed, added outgoing
+ volume logging and reporting, and provided a variety of bug
+ fixes.
+
+Heath Kehoe:
+ Various portability and bug fixes, wrote the ovdb overview
+ mechanism that uses Berkeley DB.
+
+Richard Todd:
+ Implemented the timecaf and tradspool storage mechanisms, as well
+ as many bug fixes and other contributions.
+
+Brian Kantor:
+ Wrote the news2mail gateway.
+
+Ilya Etingof:
+ Added Python authentication support for nnrpd.
+
+Kenichi OKADA:
+ Added preliminary SSL and SASL support for nnrpd.
+
+Olaf Titz:
+ Implemented MODE CANCEL support, as well as other patches and bug
+ fixes.
+
+Sven Paulus:
+ Wrote the support for variables in newsfeeds, contributed various
+ other patches and bug fixes.
+
+Krischan Jodies:
+ Wrote the SMB authenticator.
+
+Alex Kiernan:
+ Wrote the history API, generalized the timer code in innd and
+ innfeed into a generic timer library, reworked the NEWNEWS code
+ and added a history cache, and contributed various other bug fixes.
+
+Marco d'Itri:
+ Wrote gpgverify and overhauled controlchan and its modules. Added
+ IPv6 support to innd and inndstart. Contributed a rewritten
+ send-uucp. Has also contributed a variety of bug fixes and helped
+ with testing.
+
+Jeffrey M. Vinocur:
+ Broke parts of the interface with nnrpd for authentication programs
+ into a separate library, added various features to readers.conf,
+ and wrote various other fixes and feature improvements,
+ particularly to nnrpd.
+
+Erik Klavon:
+ Significantly reworked nnrpd Perl and Python hooks to be more useful
+ in combination with the readers.conf mechanism.
+
+Nathan Lutchansky:
+ Added IPv6 support to innfeed, nnrpd, and supporting programs.
+
+Also:
+
+Dave Barr:
+ Kept INN alive after Rich Salz didn't have the time any more but
+ before the ISC took over. He released 4 unofficial versions that
+ provided a good boost to what the ISC started with. Minor work
+ on 2.0, mostly with example files and minor code tweaks.
+
+James Brister:
+ The chief maintainer of INN from when the ISC took over
+ maintenance through the 2.2 release, James is also the original
+ author of innfeed and has made fixes, improvements, and feature
+ additions all over the code.
+
+Marc Fournier:
+ Provided various bug fixes and did a lot of work integrating other
+ peoples patches and looking after the CVS pool. Helped
+ significantly with the conversion to autoconf. Added the ability
+ to set connection limits on a per-host basis.
+
+Joshua M. Thompson
+ Wrote the original INSTALL documentation.
+
+The following people helped above and beyond the call of duty with testing
+(provided patches, bug reports, suggestions, documentation improvements,
+and lobbying):
+
+Paul Vixie, Robert Elz, Evan Champion, Robert Keller, Barry Bouwsma,
+markd@mira.net.au, Ollivier Robert, Kevin Jameson, Heiko W. Rupp,
+Fletcher Mattox, Matus Uhlar, Gabor Kiss, Matthias Scheler,
+Richard Michael Todd, Trevor Riley, Alex Bligh, J. Porter Clark,
+Alan Brown, Bert Hyman, Petter Nilsen, Gary E. Miller, Kim Culhan,
+Marc Baudoin, Neal Becker, Bjorn Knutsson, Stephen Marquard,
+Frederick Korz, Benedict Lofstedt, Dan Ellis, Joe Ramey,
+Odd Einar Aurbakken, Jon Lewis, Dan Riley, Peter Eriksson, Ken Lalonde,
+Koichi Mouri, J. Richard Sladkey, Trine Krogstad, Holger Burbach,
+Per Hedeland, Larry Rosenman, Andrew Burgess, Michael Brunnbauer,
+Mohan Kokal, Robert R. Collier, Mark Hittinger, Miquel van Smoorenburg,
+Boyd Lynn Gerber, Yury B. Razbegin, Joe St. Sauver, Heiko Schlichting,
+John P. Speno, Scott Gifford, Steve Parr, Robert Kiessling,
+Francis Swasey, Paul Tomblin, Florian La Roche, Curt Welch,
+Thomas Mike Michlmayr, KIZU Takashi, Michael Hall, Jeff King,
+Edward S. Marshall, Michael Schroeder, George Lindholm, Don Lewis,
+Christopher Masto, Hiroaki Sengoku, Yury July, Yar Tikhiy, Kees Bakker,
+Peter da Silva, Matt McLeod, Ed Korthof, Jan Rychter, Winfried Magerl,
+Andreas Lamrecht, Duane Currie, Ian Dickinson, Bettina Fink,
+Jochen Erwied, Rebecca Ore, Felicia Neff, Antonio Querubin, Bear Giles,
+Christopher P. Lindsey, Winfried Szukalski, Edvard Tuinder,
+Frank McConnell, Ilya Kovalenko, Steve Youngs, Jacek Konieczny,
+Ilya Voronin, Sergey Babitch, WATANABE Katsuhiro, Chris Caputo,
+Thomas Parmelan
--- /dev/null
+2008-06-29 iulius
+
+ * lib/perl.c: Use snprintf instead of asprintf.
+
+ * doc/hook-python, doc/pod/hook-python.pod: Use initial capital
+ letters for head titles.
+
+ * NEWS, doc/pod/news.pod, lib/perl.c: Fixed a hang in Perl hooks on
+ (at least) HP/PA since Perl 5.10. On such architectures,
+ pthread_mutex_lock() hangs inside perl_parse() if
+ PERL_SYS_INIT3() hasn't been called.
+
+ Also rewrite "do" and "eval" calls to use perl_eval_pv().
+
+2008-06-25 iulius
+
+ * samples/innreport.conf.in: For two sections in innreport.conf
+ there is a mismatch between sort function and sorted hash.
+
+ Thanks to Alexander Bartolich for this patch.
+
+2008-06-24 iulius
+
+ * NEWS, doc/pod/news.pod, scripts/innreport_inn.pm: Fix another
+ long-standing bug in innreport which prevented it from correctly
+ reporting innfeed log messages.
+
+ * scripts/innreport_inn.pm: Suppress a few other nnrpd and
+ controlchan notices in innreport.
+
+2008-06-23 iulius
+
+ * NEWS, doc/pod/news.pod: Add changelog for innreport.
+
+ * NEWS, doc/pod/news.pod: Changelog for INN 2.4.5 :-)
+
+ * doc/hook-python: Update the auto-generated documentation for INN
+ 2.4.5.
+
+ * scripts/innreport_inn.pm: Fix a long-standing bug in innreport
+ which prevented it from correctly reporting nnrpd log messages.
+
+ * scripts/innreport_inn.pm: Suppress a few warnings in innreport
+ (especially from Python hooks and nnrpd). Also backport some
+ other improvements made in TRUNK.
+
+ * site, site/.cvsignore, site/Makefile: Install nnrpd.py which
+ previously was not.
+
+ * MANIFEST, samples/nnrpd_access.py, samples/nnrpd_auth.py,
+ samples/nnrpd_dynamic.py, site, site/.cvsignore, site/Makefile:
+ Update the Python nnrpd filter. New samples for access and
+ dynamic hooks.
+
+2008-06-22 iulius
+
+ * samples/filter_innd.py: Update the Python innd filter.
+
+ * doc/pod/hook-python.pod: Typo (canceled -> cancelled).
+
+ * samples/nnrpd_access_wrapper.py, samples/nnrpd_auth_wrapper.py,
+ samples/nnrpd_dynamic_wrapper.py: Update old Python wrappers.
+
+ * samples/INN.py, samples/nnrpd.py: Update stub Python scripts. Fix
+ a compilation problem with INN.py (undefined variable) and add
+ missing methods.
+
+ * doc/hook-python, doc/man/readers.conf.5, doc/pod/hook-python.pod,
+ doc/pod/readers.conf.pod: Update POD documentation for Python
+ hooks. It is a complete proof-reading.
+
+ * nnrpd/python.c: No need to check the existence of methods not
+ used by the hooked script.
+
+ * innd/python.c, nnrpd/python.c: Fix an issue with Python exception
+ handling.
+
+ * nnrpd/python.c: Fix typos.
+
+ * nnrpd/python.c: Fix a segfault when one closes and then reopens
+ Python in the same process. files and dynamic_file are still
+ pointing to the old freed memory and INN blithely tries to write
+ to it. Thanks to Russ Allbery for the patch.
+
+2008-06-21 iulius
+
+ * innd/python.c: Better be more careful when decrementing the
+ reference count for these objects.
+
+2008-06-16 iulius
+
+ * doc/external-auth, doc/hook-perl, doc/hook-python,
+ doc/man/active.5, doc/man/active.times.5, doc/man/auth_krb5.8,
+ doc/man/auth_smb.8, doc/man/ckpasswd.8, doc/man/control.ctl.5,
+ doc/man/convdate.1, doc/man/cycbuff.conf.5,
+ doc/man/distrib.pats.5, doc/man/domain.8, doc/man/expire.ctl.5,
+ doc/man/expireover.8, doc/man/fastrm.1, doc/man/grephistory.1,
+ doc/man/ident.8, doc/man/inews.1, doc/man/inn.conf.5,
+ doc/man/innconfval.1, doc/man/innd.8, doc/man/inndf.8,
+ doc/man/inndstart.8, doc/man/innmail.1, doc/man/innupgrade.8,
+ doc/man/libauth.3, doc/man/libinnhist.3, doc/man/list.3,
+ doc/man/mailpost.8, doc/man/makehistory.8, doc/man/motd.news.5,
+ doc/man/newsfeeds.5, doc/man/ninpaths.8, doc/man/nnrpd.8,
+ doc/man/ovdb.5, doc/man/ovdb_init.8, doc/man/ovdb_monitor.8,
+ doc/man/ovdb_server.8, doc/man/ovdb_stat.8,
+ doc/man/passwd.nntp.5, doc/man/qio.3, doc/man/radius.8,
+ doc/man/radius.conf.5, doc/man/rc.news.8, doc/man/readers.conf.5,
+ doc/man/sasl.conf.5, doc/man/sendinpaths.8, doc/man/simpleftp.1,
+ doc/man/sm.1, doc/man/subscriptions.5, doc/man/tdx-util.8,
+ doc/man/tst.3, doc/man/uwildmat.3: Update version number for INN
+ 2.4.5 documentation.
+
+2008-06-11 iulius
+
+ * support/config.guess, support/config.sub: Update support files
+ for autoconf to their last stable version.
+
+2008-06-10 iulius
+
+ * innd/python.c: Fix the name of a variable used in Python filters.
+
+2008-06-09 iulius
+
+ * innd/python.c: Fix a bug when reloading Python filters. They
+ might not be correctly reloaded. They must be reimported before
+ being reloaded.
+
+ * nnrpd/python.c: Fix a segfault when generating access groups with
+ embedded Python filters for nnrpd. Thanks to David Hlacik for the
+ bug report.
+
+2008-06-08 iulius
+
+ * frontends/pullnews.in: Two minor issues resolved with this patch
+ by Geraint Edwards: * an off-by-one error on the limit to the
+ amount of articles to get; * when an article is not available, we
+ may have redundantly retried that article.
+
+2008-06-07 iulius
+
+ * doc/pod/cycbuff.conf.pod, doc/pod/hook-perl.pod,
+ doc/pod/hook-python.pod, innd/python.c, samples/filter_innd.pl:
+ Fix the use of "ctlinnd reload something 'reason'" in
+ documentation.
+
+2008-06-05 iulius
+
+ * doc/hook-perl, doc/hook-python, doc/pod/hook-perl.pod,
+ doc/pod/hook-python.pod, innd/innd.c, innd/innd.h,
+ samples/filter_innd.py: Add access to several new headers within
+ Perl and Python hooks for innd. Thanks to Matija Nalis for the
+ patch.
+
+ Also update the POD documentation and the Python sample.
+
+ * doc/man/pullnews.1, doc/pod/pullnews.pod, frontends/pullnews.in:
+ A new improved version of pullnews. Great thanks to Geraint A.
+ Edwards for all his work. He added no more than 16 flags, fixed
+ some bugs and integrated the backupfeed contrib script by Kai
+ Henningsen, adding again 6 other flags.
+
+ A long-standing but very minor bug in the -g option was
+ especially fixed and items from the to-do list implemented.
+
+ From TODO:
+
+ + reset highwater mark to match server (-w) + reset highwater
+ mark to zero (also -w) + add group to config (-G) + drop articles
+ with headers matching (or not matching) regexp (-m)
+
+ From backupfeed:
+
+ + pull only a proportion (factor) of articles (-f) + sleeps
+ between articles/groups (-z/-Z) + Path: fake hop insert (-F) +
+ NNTP connection timeout (-N) + overall session timeout (-S)
+
+ Other new flags/features:
+
+ -l logfile log to logfile (rather than /dev/null when rnews'ing!)
+ -s host:port add local port option (can use -p already) -t
+ retries attempt connect to upstream retries times -T retry_pause
+ wait between retries -k checkpt checkpoint the config file every
+ checkpt arts -C width when writing the progress bar - use width
+ columns -d debug_level self-explanatory -M max_arts only process
+ max_arts articles per run -H headers remove these headers from
+ articles -Q quietness set how quiet we are -R be a reader -n
+ no-op -P paths feed articles depending on number of hops in Path:
+
+2008-05-25 iulius
+
+ * control/modules/newgroup.pl: Fix a Perl warning.
+
+2008-05-24 iulius
+
+ * nnrpd/tls.c: When an article of a size greater than remaining
+ stack is retrieved via SSL, a segmentation fault will occur due
+ to the use of alloca(). The below patch uses heap based realloc()
+ instead of stack based alloca(), with a static buffer growing as
+ needed. It uses realloc() instead of malloc() for performance
+ reasons since this function is called frequently. The caveat is
+ that the memory is never free()'ed, so if more correct code is
+ desired, it should be adjusted.
+
+ Thanks to Chris Caputo for this patch.
+
+2008-05-19 iulius
+
+ * innd/Makefile, nnrpd/line.c: Implementation of the "alarm signal"
+ around SSL_read so that to prevent dead connections from leading
+ nnrpd processes to wait forever in SSL_read(). "clienttimeout"
+ now also works on SSL connections.
+
+ Thanks to Matija Nalis for the patch.
+
+ * nnrpd/tls.c: Implementation on systems that support it of
+ SO_KEEPALIVE in SSL TCP connections, allowing system detection
+ and closing the dead TCP SSL connections automatically after
+ system-specified time (usually at least 2 hours as recommended by
+ RFC (on Linux, see /proc/sys/net/ipv4/tcp_keepalive_*).
+
+ Thanks to Matija Nalis for the patch.
+
+2008-05-18 iulius
+
+ * innfeed/host.c: Fix a problem of undefined constant.
+
+2008-05-14 iulius
+
+ * innfeed/host.c: Fix a bug in ipAddrs which contained thrice the
+ same IPs. Rotating the peer IP addresses was a bit slower than it
+ could be.
+
+ Thanks, D. Stussy, for having seen that. Miquel van Smoorenburg
+ provided the patch.
+
+ * Makefile.global.in: Bump the revision number to 2.4.5 (in case it
+ is released one day).
+
--- /dev/null
+Hacking INN
+
+ This file is for people who are interested in making modifications to
+ INN. Normal users can safely skip reading it. It is intended primarily
+ as a guide, resource, and accumulation of tips for maintainers and
+ contributors, and secondarily as documentation of some of INN's
+ internals.
+
+ This is $Revision: 7736 $ dated $Date: 2006-04-15 04:52:06 +0200 (Sat,
+ 15 Apr 2006) $.
+
+ First of all, if you plan on working on INN source, please start from
+ the current development tree. There may be significant changes from the
+ previous full release, so starting from development sources will make it
+ considerably easier to integrate your work. You can get nightly
+ snapshots of the current development source from ftp.isc.org in
+ /isc/inn/snapshots (the snapshots named inn-CURRENT-*.tar.gz), or you
+ can get the current CVS tree by using CVSup (see "Using CVSup").
+
+Configuring and Portability
+
+ All INN code should be written expecting ANSI C and POSIX. There is no
+ need to attempt to support pre-ANSI compilers, and ANSI-only features
+ such as <stdarg.h>, string concatenation, #elif, and token pasting may
+ be used freely. So far as possible, INN is written to attempt to be
+ portable to any system new enough that someone is likely to want to run
+ a news server on it, but whenever possible this portability should be
+ provided by checking for standard behavior in configure and supplying
+ replacements for standard functions that are missing.
+
+ When there is a conflict between ANSI C and C99, INN code should be
+ written expecting C99 and autoconf used to patch up the differences.
+
+ Try to avoid using #ifdef and the like in the middle of code as much as
+ possible. Instead, try to isolate the necessary portability bits and
+ include them in libinn or at least in conditional macros separate from
+ the code. Trying to read code littered with conditional compilation
+ directives is much more difficult.
+
+ The shell script configure at the top level of the source tree is
+ generated by autoconf from configure.in, and include/config.h.in is
+ generated by autoheader from configure.in and include/acconfig.h. At
+ configure time, configure generates include/config.h and several other
+ files based on options it was given and what it discovers about the
+ target system.
+
+ All modifications to configure should instead be made to configure.in.
+ Similarly, modifications to include/config.h.in should instead be made
+ to include/acconfig.h. The autoconf manual (available using info
+ autoconf if you have autoconf and the GNU info utilities installed on
+ your system) is a valuable reference when making any modifications.
+
+ To regenerate configure, just run "autoconf". To regenerate
+ include/config.h.in, run:
+
+ autoheader -l include
+
+ to tell it where to find acconfig.h. Please don't include patches to
+ either configure or include/config.h.in when sending patches to INN;
+ instead, note in your patch that those files must be regenerated.
+
+ The generated files are checked into the CVS repository so that people
+ working on INN don't have to have autoconf on their system, and to make
+ packaging easier.
+
+ At the time of this writing, autoconf 2.13 is required.
+
+ The supporting files for autoconf are in the support subdirectory,
+ including the files config.guess and config.sub to determine the system
+ name and and ltmain.sh for libtool support. The latter file comes from
+ the libtool distribution; the canonical version of the former two are
+ available from ftp.gnu.org in /gnu/config. In addition, m4/libtool.m4
+ is just a copy of libtool.m4 from the libtool distribution. (Using
+ libtool without using automake requires a few odd hacks.) These files
+ used to be on a separate vendor branch so that we could make local
+ modifications, but local modifications have not been necessary for some
+ time. Now, new versions can just be checked in like any other file
+ modifications.
+
+ INN should not compile with libtool by default, only when requested,
+ since otherwise normal compilations are quite slow. (Using libtool is
+ not without some cost.) Basic compilation with libtool works fine as of
+ this writing, with both static and shared compiles, but the dependencies
+ aren't quite right for make -j using libtool.
+
+Documentation
+
+ INN's documentation is currently somewhat in a state of flux. The vast
+ majority is still in the form of man pages written directly in nroff.
+ Some parts of the documentation have been rewritten in POD; that
+ documentation can be found in doc/pod. The canonical source for README,
+ INSTALL, NEWS, doc/hook-perl, doc/hook-python, and this file are also in
+ POD.
+
+ If you're modifying some part of INN's documentation and see that it has
+ a POD version in doc/pod, it's preferred if you can make the
+ modifications to the POD source and then regenerate the derived files.
+ For a quick introduction to POD, see the perlpod(1) man page on your
+ system (it should be installed if you have Perl installed).
+
+ When writing new documentation, write in whatever format you care to; if
+ necessary, we can always convert it to POD or whatever else we want to
+ use. Having the documentation exist in *some* form is more important
+ than what language you write it in. If you really don't have any
+ particular preference, there's a slight preference currently for POD.
+
+ If you use POD or regenerate POD documentation, please install something
+ close to the latest versions of the POD processing utilities to avoid
+ changes to the documentation depending on who generated it last. You
+ can find the latest version on CPAN (ftp.perl.org or another mirror) in
+ modules/by-module/Pod. You'll need PodParser (for versions of Perl
+ before 5.6.1; 5.6.1 and later come with a recent enough version) and the
+ latest version of podlators. For versions of Perl earlier than 5.005,
+ you'll also need File::Spec in modules/by-module/File.
+
+ podlators 1.25 or later will build INN's documentation without
+ significant changes from the versions that are checked into the
+ repository.
+
+ There are Makefile rules in doc/pod/Makefile to build all of the
+ documentation whose master form is POD; if you add additional
+ documentation, please add a rule there as well. Documentation should be
+ generated by cd'ing to doc/pod and typing "make file" where "file" is
+ the relative path to the documentation file. This will get all of the
+ various flags right for pod2text or pod2man.
+
+Error Handling
+
+ INN has a set of generic error handling routines that should be used as
+ much as possible so that the same syntax can be used for reporting
+ errors everywhere in INN. The four basic functions are warn, syswarn,
+ die, and sysdie; warn prints or logs a warning, and die does the same
+ and then exits the current program. The sys* versions add a colon, a
+ space, and the value of strerror(errno) to the end of the message, and
+ should be used to report failing system calls.
+
+ All of the actual error reporting is done via error handlers, and a
+ program can register its own handlers in addition to or instead of the
+ default one. The default error handler (error_log_stderr) prints to
+ stderr, prepending the value of error_program_name if it's set to
+ something other than NULL. Three other error handlers are available,
+ error_log_syslog_crit, error_log_syslog_err, and
+ error_log_syslog_warning, which log the message to syslog at LOG_CRIT,
+ LOG_ERR, or LOG_WARNING priority, respectively.
+
+ There is a different set of error handlers for warn/syswarn and
+ die/sysdie. To set them, make calls like:
+
+ warn_set_handlers(2, error_log_stderr, error_log_syslog_warning);
+ die_set_handlers(2, error_log_stderr, error_log_syslog_err);
+
+ The first argument is the number of handlers, and the remaining
+ arguments are pointers to functions taking an int (the length of the
+ formatted message), a const char * (the format), a va_list (the
+ arguments), and an int that's 0 if warn or die was called and equal to
+ the value of errno if syswarn or sysdie was called. The length of the
+ formatted message is obtained by calling vsnprintf with the provided
+ format and arguments, and therefore is reliable to use as the size of a
+ buffer to malloc to hold the result of formatting the message provided
+ that vsnprintf is used to format it (warning: the system vsprintf may
+ produce more output under some circumstances, so always use vsnprintf).
+
+ The error handler can do anything it wishes; each error handler is
+ called in the sequence given. Error handlers shouldn't call warn or die
+ unless great caution is taken to prevent infinite recursion. Also be
+ aware that sysdie is called if malloc fails in xmalloc, so if the error
+ handler needs to allocate memory, it must not use xmalloc or a related
+ function to do so and it shouldn't call die to report failure. The
+ default syslog handlers report memory allocation failure to stderr and
+ exit.
+
+ Finally, die and sysdie support an additional handler that's called
+ immediate before exiting, takes no arguments, and returns an int which
+ is used as the argument for exit. It can do any necessary global
+ cleanup, call abort instead to generate a core dump or the like.
+
+ The advantage of using this system everywhere in INN is that library
+ code can use warn and die to report errors and each calling program can
+ set up the error handlers as appropriate to make sure the errors go to
+ the right place. The default handler is fine for interactive programs;
+ for programs that run from interactive scripts, adding something like:
+
+ error_program_name = "program";
+
+ to the beginning of main (where program is the name of the program) will
+ make it easier to figure out which program the script calls is failing.
+ For programs that may also be called non-interactively, like inndstart,
+ one may want to set up handlers like:
+
+ warn_set_handlers(2, error_log_stderr, error_log_syslog_warning);
+ die_set_handlers(2, error_log_stderr, error_log_syslog_err);
+
+ Finally, for daemons and other non-interactive programs, one may want to
+ do:
+
+ warn_set_handlers(1, error_log_syslog_warning);
+ die_set_handlers(1, error_log_syslog_err);
+
+ to report errors only via syslog. (Note that if you use syslog error
+ handlers, the program should call openlog first thing to make sure they
+ are logged with the right facility.)
+
+ For historical reasons, error messages that are fatal to the news
+ subsystem are logged at the LOG_CRIT priority, and therefore die in innd
+ should use error_log_syslog_crit.
+
+Test Suite
+
+ The test suite for INN is located in the tests directory and is just
+ getting started. The test suite consists of a set of programs listed in
+ tests/TESTS and the scaffolding in the runtests program.
+
+ Adding new tests is very straightforward and very flexible. Just write
+ a program that tests some part of INN, put it in a directory under tests
+ named after the part of INN it's testing (all the tests so far are in
+ lib because they're testing libinn routines), and have it output first a
+ line containing the count of test cases in that file, and then for each
+ test a line saying "ok n" or "not ok n" where n is the test case number.
+ (If a test is skipped for some reason, such as a test of an optional
+ feature that wasn't compiled into INN, the test program should output
+ "ok n # skip".) Add any rules necessary to build the test to
+ tests/Makefile (note that for simplicity it doesn't recurse into
+ subdirectories) and make sure it creates an executable ending in .t.
+ Then add the name of the test to tests/TESTS, without the .t ending.
+
+ One naming convention: to distinguish more easily between e.g.
+ lib/error.c (the implementation) and tests/lib/error-t.c (the test
+ suite), we add -t to the end of the test file names. So
+ tests/lib/error-t.c is the source that compiles into an executable
+ tests/lib/error.t which is run by putting a line in tests/TESTS of just
+ "lib/error".
+
+ Note that tests don't have to be written in C; in fact, lib/xmalloc.t is
+ just a shell script (that calls a supporting C program). Tests can be
+ written in shell or Perl (but other languages should be avoided because
+ someone who wants to run the test suite may not have it) and just have
+ to follow the above output conventions.
+
+ Additions to the test suite, no matter how simple, are very welcome.
+
+Makefiles
+
+ All INN makefiles include Makefile.global at the top level, and only
+ that makefile is a configure substitution target. This has the
+ disadvantage that configure's normal support for building in a tree
+ outside of the source tree doesn't work, but it has the significant
+ advantage of making configure run much faster and allowing one to run
+ make in any subdirectory and pick up all the definitions and settings
+ from the top level configuration.
+
+ All INN makefiles should also set $(top) to be the path to the top of
+ the build directory (usually relative). This path is used to find
+ various programs like fixscript and libtool so that the same macros (set
+ in Makefile.global) can be used all over INN.
+
+ The format of INN's makefiles is mostly standardized; the best examples
+ of the format are probably frontends/Makefile and backends/Makefile, at
+ least for directories with lots of separate programs. The ALL variable
+ holds all the files that should be generated, EXTRA those additional
+ files that were generated by configure, and SOURCES the C source files
+ for generating tag information.
+
+ There are a set of standard installation commands defined in make
+ variables by Makefile.global, and these should be used for all file
+ installations. See the comment blocks in Makefile.global.in for
+ information on what commands are available and when they should be used.
+ There are also variables set for each of the installation directories
+ that INN uses, for use in building the list of installed paths to files.
+
+ Each subdirectory makefile should have the targets all (the default),
+ clean, clobber, install, tags, and profiled. The tags target generates
+ vi tags files, and the profiled target generates a profiling version of
+ the programs (although this hasn't been tested much recently). These
+ rules should be present and empty in those directories where they don't
+ apply.
+
+ Be sure to test compiling with both static and dynamic libraries and
+ make sure that all the libtool support works correctly. All linking
+ steps, and the compile steps for all library source, should be done
+ through $(LIBTOOL) (which will be set to empty in Makefile.global if
+ libtool support isn't desired).
+
+Scripts
+
+ INN comes with and installs a large number of different scripts, both
+ Bourne shell and Perl, and also comes with support for Tcl scripts
+ (although it doesn't come with any). Shell variables containing both
+ configure-time information and configuration information from inn.conf
+ are set by the innshellvars support libraries, so the only
+ system-specific configuration that should have to be done is fixing the
+ right path to the interpretor and adding a line to load the appropriate
+ innshellvars.
+
+ support/fixscript, built by configure, does this. It takes a .in file
+ and generates the final script (removing the .in) by fixing the path to
+ the interpretor on the first line and replacing the second line,
+ whatever it is, with code to load the innshellvars appropriate for that
+ interpretor. (If invoked with -i, it just fixes the interpretor path.)
+
+ Scripts should use innshellvars (via fixscript) to get the right path
+ and the right variables whenever possible, rather than having configure
+ substitute values in them. Any values needed at run-time should instead
+ be available from all of the different innshellvars.
+
+ See the existing scripts for examples of how this is done.
+
+Include Files
+
+ Include files relevant to all of INN, or relevant to the two libraries
+ built as part of INN (the utility libinn library and the libstorage
+ library that contains all storage and overview functions) are found in
+ the include directory; other include files relevant only to a portion of
+ INN are found in the relevant directory.
+
+ Practically all INN source files will start with:
+
+ #include "config.h"
+ #include "clibrary.h"
+
+ The first picks up all defines generated by autoconf and is necessary
+ for types that may not be present on all systems (uid_t, pid_t, size_t,
+ int32_t, and the like). It therefore should be included before any
+ other headers that use those types, as well as to get general
+ configuration information.
+
+ The second is portably equivalent to:
+
+ #include <sys/types.h>
+ #include <stdarg.h>
+ #include <stdio.h>
+ #include <stdlib.h>
+ #include <stddef.h>
+ #include <stdint.h>
+ #include <string.h>
+ #include <unistd.h>
+
+ except that it doesn't include headers that are missing on a given
+ system, replaces functions not found on the system with the INN
+ equivalents, provides macros that INN assumes are available but which
+ weren't found, and defines some additional portability things. Even if
+ this is more headers than the source file actually needs, it's generally
+ better to just include clibrary.h rather than trying to duplicate the
+ autoconf-driven hackery that it does to do things portably. The primary
+ exception is for source files in lib that only define a single function
+ and are used for portability; those may want to include only config.h so
+ that they can be easily used in other projects that use autoconf.
+ config.h is a fairly standard header name for this purpose.
+
+ clibrary.h does also include config.h, but it's somewhat poor form to
+ rely on this; it's better to explicitly list the header dependencies for
+ the benefit of someone else reading the code.
+
+ There are portable wrappers around several header files that have known
+ portability traps or that need some fixing up on some platforms. Look
+ in include/portable and familiarize yourself with them and use them
+ where appropriate.
+
+ Another frequently included header file is libinn.h, which among other
+ things defines xmalloc(), xrealloc(), xstrdup(), and xcalloc(), which
+ are checked versions of the standard memory allocation routines that
+ terminate the program if the memory allocation fails. These should
+ generally always be used instead of the regular C versions. libinn.h
+ also provides various other utility functions that are frequently used.
+
+ paths.h includes a wide variety of paths determined at configure time,
+ both default paths to various parts of INN and paths to programs. Don't
+ just use the default paths, though, if they're also configurable in
+ inn.conf; instead, call ReadInnConf() and use the global innconf
+ structure.
+
+ Other files in include are interfaces to particular bits of INN library
+ functionality or are used for other purposes; see the comments in each
+ file.
+
+ Eventually, the header files will be separated into installed header
+ files and uninstalled header files; the latter are those headers that
+ are used only for compiling INN and aren't useful for users of INN's
+ libraries (such as clibrary.h). All of the installed headers will live
+ in include/inn and be installed in a subdirectory named inn in the
+ configured include directory. This conversion is still in progress.
+
+ When writing header files, remember that C reserves all identifiers
+ beginning with two underscores and all identifiers beginning with an
+ underscore and a capital letter for the use of the implementation; don't
+ use any identifiers with names like that. Additionally, any identifier
+ beginning with an underscore and a lower-case letter is reserved in file
+ scope, which means that such identifiers can only be used by INN for the
+ name of structure members or function arguments in function prototypes.
+
+ Try to pay attention to the impact of a header file on the program
+ namespace, particularly for installed header files in include/inn. All
+ symbols defined by a header file should ideally begin with INN_, inn_,
+ or some other unique prefix indicating the subsystem that symbol is part
+ of, to avoid accidental conflicts with symbols defined by the program
+ that uses that header file.
+
+Coding Style
+
+ INN has quite a variety of coding styles intermixed. As with all
+ programs, it's preferrable when making minor modifications to keep the
+ coding style of the code you're modifying. In INN, that will vary by
+ file. (Over time we're trying to standardize on one coding style, so
+ changing the region you worked on to fit the general coding style is
+ also acceptable).
+
+ If you're writing a substantial new piece of code, the prevailing
+ "standard" INN coding style appears to be something like the following:
+
+ * Write in regular ANSI C whenever possible. Use the normal ANSI and
+ POSIX constructs and use autoconf or portability wrappers to fix
+ things up beforehand so that the code itself can read like regular
+ ANSI or POSIX code. Code should be written so that it works as
+ expected on a modern platform and is fixed up with portability tricks
+ for older platforms, not the other way around. You may assume an
+ ANSI C compiler.
+
+ Try to use const wherever appropriate. Don't use register; modern
+ compilers will do as good of a job as you will in choosing what to
+ put into a register. Don't bother with restrict (at least yet).
+
+ * Use string handling functions that take counts for the size of the
+ buffer whenever possible. This means using snprintf in preference to
+ sprintf and using strlcpy and strlcat in preference to strcpy and
+ strcat. Also, use strlcpy and strlcat instead of strncpy and strncat
+ unless the behavior of the latter is specifically required, as it is
+ much easier to audit uses of the former than the latter. (strlcpy is
+ like strncpy except that it always nul-terminates and doesn't fill
+ the rest of the buffer with nuls, making it more efficient. strlcat
+ is like strncat except that it always nul-terminates and it takes the
+ total size of the buffer as its third argument rather than just the
+ amount of space left.) All of these functions are guaranteed to be
+ available; there are replacements in lib for systems that don't have
+ them.
+
+ * Avoid #ifdef and friends whenever possible. Particularly avoid using
+ them in the middle of code blocks. Try to hide all portability
+ preprocessor magic in header files or in portability code in lib.
+ When something just has to be done two completely different ways
+ depending on the platform or compile options or the like, try to
+ abstract that functionality out into a generic function and provide
+ two separate implementations using #ifdef; then the main code can
+ just call that function.
+
+ If you do have to use preprocessor defines, note that if you always
+ define them to either 0 or 1 (never use #define without a second
+ argument), you can use the preprocessor define in a regular if
+ statement rather than using #if or #ifdef. Make use of this instead
+ of #ifdef when possible, since that way the compiler will still
+ syntax-check the other branch for you and it makes it far easier to
+ convert the code to use a run-time check if necessary.
+ (Unfortunately, this trick can't be used if one branch may call
+ functions unavailable on a particular platform.)
+
+ * Avoid uses of fixed-width buffers except in performance-critical
+ code, as it's harder to be sure that such code is correct and it
+ tends to be less flexible later on. If you need a reusable,
+ resizable memory buffer, one is provided in lib/buffer.c.
+
+ * Avoid uses of static variables whenever possible, particularly in
+ libraries, because it interferes with making the code re-entrant down
+ the road and makes it harder to follow what's going on. Similarly,
+ avoid using global variables whenever possible, and if they are
+ required, try to wrap them into structures that could later be
+ changed into arguments to the affected functions.
+
+ * Roughly BSD style but with four-space indents. This means no space
+ before the parens around function arguments, open brace on the same
+ line as if/while/for, and close and open brace on the same line as
+ else).
+
+ * Introductory comments for functions or files are generally written
+ as:
+
+ /*
+ ** Introductory comment.
+ */
+
+ Other multiline comments in the source are generally written as:
+
+ /* This is a
+ multiline comment. */
+
+ Comments before functions saying what they do are nice to have. In
+ general, the RCS/CVS Id tag is on the first line of each source file
+ since it's useful to know when a file was last modified.
+
+ * Checks for NULL pointers are preferrably written out explicitly; in
+ other words, use:
+
+ if (p != NULL)
+
+ rather than:
+
+ if (p)
+
+ to make it clearer what the code is assuming.
+
+ * It's better to always put the body of an if statement on a separate
+ line, even if it's only a single line. In other words, write:
+
+ if (p != NULL)
+ return p;
+
+ and not:
+
+ if (p != NULL) return p;
+
+ This is in part for a practical reason: some code coverage analysis
+ tools like purecov will count the second example above as a single
+ line and won't notice if the condition always evaluates the same way.
+
+ * Plain structs make perfectly reasonable abstract data types; it's not
+ necessary to typedef the struct to something else. Structs are
+ actually very useful for opaque data structures, since you can
+ predeclare them and then manipulate pointers to them without ever
+ having to know what the contents look like. Please try to avoid
+ typedefs except for function pointers or other extremely confusing
+ data types, or for data types where we really gain some significant
+ data abstraction from hiding the underlying data type. Also avoid
+ using the _t suffix for any type; all types ending in _t are reserved
+ by POSIX. For typedefs of function pointer types, a suffix of _func
+ usually works.
+
+ This style point is currently widely violated inside of INN itself;
+ INN originally made extensive use of typedefs.
+
+ * When noting something that should be improved later, add a comment
+ containing "FIXME:" so that one can easily grep for such comments.
+
+ INN's indentation style roughly corresponds to that produced by GNU
+ indent 2.2.6 with the following options:
+
+ -bad -bap -nsob -fca -lc78 -cd41 -cp1 -br -ce -cdw -cli0 -ss -npcs
+ -ncs -di1 -nbc -psl -brs -i4 -ci4 -lp -ts8 -nut -ip5 -lps -l78 -bbo
+ -hnl
+
+ Unfortunately, indent currently doesn't get everything right (it has
+ problems with spacing around struct pointer arguments in functions,
+ wants to put in a space between a dereference of a function pointer and
+ the arguments to the called function, misidentifies some macro calls as
+ being type declarations, and fouls up long but simple case statements).
+ It would be excellent if someday we could just run all of INN's code
+ through indent routinely to enforce a consistant coding style, but
+ indent isn't quite ready for that.
+
+ For users of emacs cc-mode, use the "bsd" style but with:
+
+ (setq c-basic-offset 4)
+
+ Finally, if possible, please don't use tabs in source files, since they
+ can expand differently in different environments. In particular, please
+ try not to use the mix of tabs and spaces that is the default in emacs.
+ If you use emacs to edit INN code, you may want to put:
+
+ ; Use only spaces when indenting or centering, no tabs.
+ (setq-default indent-tabs-mode nil)
+
+ in your ~/.emacs file.
+
+ Note that this is only a rough guideline and the maintainers aren't
+ style nazis; we're more interested in your code contribution than in how
+ you write it.
+
+Using CVSup
+
+ If you want to get updated INN source more easily or more quickly than
+ by downloading nightly snapshots, or if you want to see the full CVS
+ history, you may want to use CVSup to download the source. CVSup is a
+ client and server designed for replicating CVS repositories between
+ sites.
+
+ Unfortunately, CVSup is written in Modula-3, so getting a working binary
+ can be somewhat difficult. Binaries are available in the *BSD ports
+ collection or (for a wide variety of different platforms) available from
+ <ftp://ftp.freebsd.org/pub/FreeBSD/CVSup/binaries/> and its mirrors.
+ Alternately, you can get a compiler from <http://m3.polymtl.ca/m3/>
+ (this is more actively maintained than the DEC Modula-3 compiler) and
+ the source from <ftp://ftp.freebsd.org/pub/FreeBSD/CVSup/>.
+
+ After you have the CVSup client, you need to have space to download the
+ INN repository and space for CVSup to store its data files. You also
+ need to write a configuration file (a supfile) for CVSup. The following
+ supfile will download the latest versions from the mainline source:
+
+ *default host=inn-cvs.isc.org
+ *default base=<data-location>
+ *default prefix=<download-location>
+ *default release=cvs
+ *default tag=.
+ *default delete use-rel-suffix
+ inn
+
+ where <data-location> should be a directory where CVSup can put its data
+ files and <download-location> is where the downloaded source will go (it
+ will be put into a subdirectory named inn). If you want to pull down
+ the entire CVS repository instead (warning: this is much larger than
+ just the latest versions of the source), delete the "*default tag=."
+ line. The best way to download the CVS repository is to download it
+ into a portion of a locally-created CVS repository, so that then you can
+ perform standard CVS operations (like cvs log) against the downloaded
+ repository. Creating your own local CVS repository is outside the scope
+ of this document.
+
+ Note that only multiplexed mode is supported (this mode should be the
+ default).
+
+ For more general information on using CVSup, see the FreeBSD page on it
+ at <http://www.freebsd.org/handbook/mirrors-cvsup.html>.
+
+Making a Release
+
+ This is a checklist that INN maintainers should go through when
+ preparing a new release of INN.
+
+ 1. If making a major release, branch the source tree and create a new
+ STABLE branch tag. This branch will be used for minor releases
+ based on that major release and can be done a little while before
+ the .0 release of that major release. At the same time as the
+ branch is cut, tag the trunk with a STABLE-<version>-branch marker
+ tag so that it's easy to refer to the trunk at the time of the
+ branch.
+
+ 2. Update doc/pod/news.pod and regenerate NEWS. Be more detailed for a
+ minor release than for a major release. For a major release, also
+ add information on how to upgrade from the last major release,
+ including anything special to be aware of. (Minor releases
+ shouldn't require any special care when upgrading.)
+
+ 3. Make sure that support/config.sub and support/config.guess are the
+ latest versions (from <ftp://ftp.gnu.org/gnu/config/>). See the
+ instructions in "Configuring and Portability" for details on how to
+ update these files.
+
+ 4. Make sure that samples/control.ctl is in sync with the master
+ version at <ftp://ftp.isc.org/pub/usenet/CONFIG/control.ctl>.
+
+ 5. Check out a copy of the release branch. It's currently necessary to
+ run configure to generate Makefile.global. Then run "make
+ check-manifest". The only differences should be files that are
+ generated by configure; if there are any other differences, fix the
+ MANIFEST.
+
+ 6. Run "make release". Note that you need to have a copy of svn2cl
+ from <http://ch.tudelft.nl/~arthur/svn2cl/> to do this; at least
+ version 0.7 is required. Start the ChangeLog at the time of the
+ previous release. (Eventually, the script will be smart enough to
+ do this for you.)
+
+ 7. Make the resulting tar file available for testing in a non-listable
+ directory on ftp.isc.org and announce its availability on
+ inn-workers. Install it on at least one system and make sure that
+ system runs fine for at least a few days. This is also a good time
+ to send out a draft of the release announcement to inn-workers for
+ proof-reading.
+
+ 8. Generate a diff between this release and the previous release if
+ feasible (always for minor releases, possibly not a good idea due to
+ the length of the diff for major releases).
+
+ 9. Move the release into the public area of the ftp site and update the
+ inn.tar.gz link. Make an MD5 checksum of the release tarball and
+ put it on the ftp site as well, and update the inn.tar.gz.md5 link.
+ Put the diff up on the ftp site as well. Contact the ISC folks to
+ get the release PGP-signed. Possibly move older releases off into
+ the OLD directory.
+
+ 10. Announce the new release on inn-announce and in news.software.nntp.
+
+ 11. Tag the checked-out tree that was used for generating the release
+ with a release tag (INN-<version>).
+
+ 12. Bump the revision number in Makefile.global.in.
+
+References
+
+ Some additional references that may be hard to find and may be of use to
+ people working on INN:
+
+ <http://www.eyrie.org/~eagle/nntp/>
+ The home page for the IETF NNTP standardization effort, including
+ links to the IETF NNTP working group archives and copies of the
+ latest drafts of the new NNTP standard. The old archived mailing
+ list traffic contains a lot of interesting discussion of why NNTP is
+ the way it is.
+
+ <http://www.imc.org/ietf-usefor/>
+ The archives for the USEFOR IETF working group, the working group
+ for the RFC 1036 replacement (the format of Usenet articles). Also
+ contains a lot of references to other relevant work, such as the RFC
+ 822 replacement work.
+
+ <http://www.mibsoftware.com/userkt/inn/dev/>
+ Forrest Cavalier provides several tools for following INN
+ development at this page and elsewhere in the Usenet RKT. Under
+ here is a web-accessible checked-out copy of the current INN source
+ tree and pointers to how to use CVSup.
+
+ <http://www.sas.com/standards/large.file/>
+ The standards for large file support on Unix that are being
+ generally implemented by vendors. INN sort of partially uses these,
+ but a good full audit of the code to check them should really be
+ done and there are occasional problems.
+
+ <http://v6web.litech.org/ipv6primer/>
+ A primer on IPv6 with pointers to the appropriate places for more
+ technical details as needed, useful when working on IPv6 support in
+ INN.
+
--- /dev/null
+Welcome to INN 2.4!
+
+ Please read this document thoroughly before trying to install INN.
+ You'll be glad you did.
+
+ If you are upgrading from a major release of INN prior to 2.3, it is
+ recommended that you make copies of your old configuration files and use
+ them as guides for doing a clean installation and configuration of 2.4.
+ Many config files have changed, some have been added, and some have been
+ removed. You'll find it much easier to start with a fresh install than
+ to try to update your old installation. This is particularly true if
+ you're upgrading from a version of INN prior to 2.0.
+
+ If you are upgrading from INN 2.3 or later, you may be able to just
+ update the binaries, scripts, and man pages by running:
+
+ make update
+
+ after building INN and then comparing the new sample configuration files
+ with your current ones to see if anything has changed. If you take this
+ route, the old binaries, scripts, and man pages will be saved with an
+ extension of ".OLD" so that you can easily back out. Be sure to
+ configure INN with the same options that you used previously if you take
+ this approach (in particular, INN compiled with --enable-largefiles
+ can't read the data structures written by INN compiled without that
+ flag, and vice versa). If you don't remember what options you used but
+ you have your old build tree, look at the comments at the beginning of
+ config.status.
+
+ If you made ckpasswd setuid root so that you could use system passwords,
+ you'll have to do that again after make update. (It's much better to
+ use PAM instead if you can.)
+
+ If you use "make update" to upgrade from INN 2.3, also look at the new
+ sample configuration files in samples to see if there are new options of
+ interest to you. In particular, control.ctl has been updated and
+ inn.conf has various new options.
+
+ For more information about recent changes, see NEWS.
+
+Supported Systems
+
+ As much as possible, INN is written in portable C and should work on any
+ Unix platform. It does, however, make extensive use of mmap(2) and
+ certain other constructs that may be poorly or incompletely implemented,
+ particularly on very old operating systems.
+
+ INN has been confirmed to work on the following operating systems:
+
+ AIX 4.3
+ FreeBSD 2.2.x and up
+ HP-UX 10.20 and up
+ Linux 2.x (tested with libc 5.4, glibc 2.0 and up)
+ Mac OS X 10.2 and up
+ NetBSD 1.6 and up
+ OpenBSD 2.8 and up
+ SCO 5.0.4 (tested with gcc 2.8.1, cc)
+ Solaris 2.5.x and up
+ UnixWare 7.1
+ UX/4800 R11 and up
+
+ If you have gotten INN working on an operating system other than the
+ ones listed above, please let us know at <inn-bugs@isc.org>.
+
+Before You Begin
+
+ INN requires several other packages be installed in order to be fully
+ functional (or in some cases, to work at all):
+
+ * In order to build INN, you will need a C compiler that understands
+ ANSI C. If you are trying to install INN on an operating system that
+ doesn't have an ANSI C compiler (such as SunOS), installing gcc is
+ recommended. You can get it from <ftp://ftp.gnu.org/gnu/gcc/> or its
+ mirrors. INN is tested with gcc more thoroughly than with any other
+ compiler, so even if you have another compiler available, you may wish
+ to use gcc instead.
+
+ * Currently, in order to build INN, you will need an implementation of
+ yacc. GNU bison (from <ftp://ftp.gnu.org/gnu/bison/> or its mirrors)
+ will work fine. We hope to remove this requirement in the future.
+
+ * INN requires at least Perl 5.004_03 to build and to run several
+ subsystems. INN is tested primarily with newer versions of Perl, so
+ it's generally recommended that you install the latest stable
+ distribution of Perl before compiling INN. For instructions on
+ obtaining and installing Perl, see
+ <http://www.perl.com/pub/language/info/software.html>. Note that you
+ may need to use the same compiler and options (particularly largefile
+ support) for Perl and INN.
+
+ If you're using a version of Perl prior to 5.6.0, you may need to make
+ sure that the Perl versions of your system header files have been
+ generated in order for Sys::Syslog to work properly (used by various
+ utility programs, including controlchan). To do this, run the
+ following two commands:
+
+ # cd /usr/include
+ # h2ph * sys/*
+
+ An even better approach is to install Perl 5.6.1 or later, which have
+ a fixed version of Sys::Syslog that doesn't require this (as well as
+ many other improvements over earlier versions of Perl).
+
+ * The INN Makefiles use the syntax "include FILE", rather than the
+ syntax expected by some BSDish systems of ".include <FILE>". If your
+ system expects the latter syntax, the recommended solution is to
+ install GNU make from <ftp://ftp.gnu.org/make/>. You may have GNU
+ make already installed as gmake, in which case using gmake rather than
+ make to build INN should be sufficient.
+
+ * If you want to enable support for authenticated control messages (this
+ is not required, but is highly recommended for systems carrying public
+ Usenet hierarchies) then you will need to install some version of PGP.
+ The recommended version is GnuPG, since it's actively developed,
+ supports OpenPGP, is freely available and free to use for any purpose
+ (in the US and elsewhere), and (as of version 1.0.4 at least) supports
+ the RSA signatures used by most current control message senders.
+
+ Alternately, you can install PGP from <http://www.pgp.com/> or one of
+ the international versions of it. Be warned, however, that the
+ licensing restrictions on PGP inside the United States are extremely
+ unclear; it's possible that if you are installing INN for a company in
+ the U.S., even if the news server is not part of the business of that
+ company, you would need to purchase a commercial license for PGP. For
+ an educational or non-profit organization, this shouldn't be a
+ problem.
+
+ * If you want to use either the Python embedded hooks, you'll need to
+ have a suitable versions of Python installed. See doc/hook-python for
+ more information.
+
+ * Many of INN's optional features require other packages (primarily
+ libraries) be installed. If you wish to use any of these optional
+ features, you will need to install those packages first. Here is a
+ table of configure options enabling optional features and the software
+ and versions you'll need:
+
+ --with-perl Perl 5.004_03 or higher, 5.6.1+ recommended
+ --with-python Python 1.5.2 or higher
+ --with-berkeleydb BerkeleyDB 2.0 or higher, 4.2+ recommended
+ --with-openssl OpenSSL 0.9.6 or higher
+ --with-sasl SASL 2.x or higher
+ --with-kerberos MIT Kerberos v5 1.2.x or higher
+
+ If any of these libraries (other than Perl or Python) are built shared
+ and installed in locations where your system doesn't search for shared
+ libraries by default, you may need to encode the paths to those shared
+ libraries in the INN binaries. For more information on shared library
+ paths, see:
+
+ <http://www.eyrie.org/~eagle/notes/rpath.html>
+
+ For most systems, setting the environment variable LD_RUN_PATH to a
+ colon-separated list of additional directories in which to look for
+ shared libraries before building INN will be sufficient.
+
+Unpacking the Distribution
+
+ Released versions of INN are available from ftp.isc.org in /isc/inn.
+ New major releases will be announed on <inn-announce@isc.org> (see
+ README) when they're made.
+
+ If you want more a more cutting-edge version, you can obtain current
+ snapshots from from ftp.isc.org in directory /isc/inn/snapshots. These
+ are snapshots of the INN CVS tree taken daily; there are two snapshots
+ made each night (one of the current development branch, and one of the
+ stable branch consisting of bug fixes to the previous major release).
+ They are stored in date format; in other words the snapshots from April
+ 6th, 2000, would be named inn-CURRENT-20000406.tar.gz and
+ inn-STABLE-20000406.tar.gz. Choose the newest file of whichever branch
+ you prefer. (Note that the downloading, configuring, and compiling
+ steps can be done while logged in as any user.)
+
+ The distribution is in gzip compressed tar archive format. To extract
+ it, execute:
+
+ gunzip -c <inn-src-file> | tar -xf -
+
+ Extracting the source distribution will create a directory named
+ inn-<version> or inn-<BRANCH>-<date> where the source resides.
+
+Installing INN
+
+ Before beginning installation, you should make sure that there is a user
+ on your system named "news", and that this user's primary group is set
+ to a group called "news". You can change these with the
+ --with-news-user and --with-news-group options to configure (see below).
+ The home directory of this user should be set to the directory under
+ which you wish to install INN (/usr/local/news is the default and is a
+ good choice). INN will install itself as this user and group. You can
+ change these if you want, but these are the defaults and it's easier to
+ stick with them on a new installation.
+
+ By default, INN sends reports to the user "usenet". This account isn't
+ used for any other purposes. You can change it with the
+ --with-news-master option to configure (see below).
+
+ WARNING: By default, INN installs various configuration files as
+ group-writeable, and in general INN is not hardened from a security
+ standpoint against an attack by someone who is already in the news
+ group. In general, you should consider membership in the news group as
+ equivalent to access to the news account. You should not rely on being
+ able to keep anyone with access to the news GID from converting that
+ into access to the news UID. The recommended configuration is to have
+ the only member of the group "news" be the user "news".
+
+ Installing INN so that all of its files are under a single directory
+ tree, rather than scattering binaries, libraries, and man pages
+ throughout the file system, is strongly recommended. It helps keep
+ everything involved in the operation of INN together as a unit and will
+ make the installation instructions easier to follow.
+
+ As a side note, whenever doing anything with a running news server,
+ first log in as this user. That way, you can ensure that all files
+ created by any commands you run are created with the right ownership to
+ be readable by the server. Particularly avoid doing anything in the
+ news spool itself as root, and make sure you fix the ownership of any
+ created files if you have to. INN doesn't like files in the news spool
+ owned by a user other than the news user. However, since certain
+ binaries need to be setuid root, indiscriminate use of "chown news" is
+ not the solution. (If you don't like to log in to system accounts,
+ careful use of "chmod g+s" on directories and a umask of 002 or 007 may
+ suffice.)
+
+ INN uses GNU autoconf and a generated configure script to make
+ configuration rather painless. Unless you have a rather abnormal setup,
+ configure should be able to completely configure INN for your system.
+ If you want to change the defaults, you can invoke the configure script
+ with one or more command line options. Type:
+
+ ./configure --help
+
+ for a full list of supported options. Some of the most commonly used
+ options are:
+
+ --prefix=PATH
+ Sets the installation prefix for INN. The default is
+ /usr/local/news. All of INN's programs and support files will be
+ installed under this directory. This should match the home
+ directory of your news user (it will make installation and
+ maintenance easier). It is not recommended to set this to /usr; if
+ you decide to do that anyway, make sure to point INN's temporary
+ directory at a directory that isn't world-writeable (see
+ --with-tmp-dir below).
+
+ --with-db-dir=PATH
+ Sets the prefix for INN database files. The default is PREFIX/db,
+ where PREFIX is /usr/local/news unless overridden with the option
+ above. The history and active files will be stored in this
+ directory, and writes to those files are an appreciable percentage
+ of INN's disk activity. The history file can also be quite large
+ (requiring up to 2 GB or more during nightly expire), so this is a
+ common portion of INN to move to a different file system.
+
+ --with-spool-dir=PATH
+ Sets the prefix for the news spool (when using any storage method
+ other than CNFS) and the overview spool. The default is
+ PREFIX/spool. This is another common portion of INN to move to a
+ different file system (often /news).
+
+ --with-tmp-dir=PATH
+ Sets the directory in which INN will create temporary files. This
+ should under no circumstances be the same as the system temporary
+ directory or otherwise be set to a world-writeable directory, since
+ INN doesn't take care to avoid symlink attacks and other security
+ problems possible with a world-writeable directory. This directory
+ should be reserved for the exclusive use of INN and only writeable
+ by the news user. Usage is generally light, so this is unlikely to
+ need a separate partition.
+
+ It's also possible to set the paths for most other sections of the
+ INN installation independently; see "./configure --help" and look
+ for the --with-*-dir=PATH options.
+
+ --enable-largefiles
+ Enables large file support. This is not enabled by default, even on
+ platforms that support it, because it changes the format of INN's
+ on-disk databases (making it difficult to upgrade an earlier INN
+ installation) and can significantly increase the size of some of the
+ history database files. Large file support is not necessary unless
+ your history database is so large that it exceeds 2 GB or you want
+ to use CNFS buffers larger than 2 GB.
+
+ The history, tradindexed and buffindexed overview, CNFS, and timecaf
+ databases written by an INN built with this option are incompatible
+ with those written by an INN without this option.
+
+ --enable-tagged-hash
+ Use tagged hash table for the history database. The tagged hash
+ format uses much less memory but is somewhat slower. This option is
+ recommended if you have less than 256 MB of RAM on your news server.
+ If you install INN without tagged hash (the default) and expire
+ takes an excessive amount of time, you should make sure the RAM in
+ your system satisfies the following formula:
+
+ ram > 10 * tablesize
+
+ ram: Amount of system RAM (in bytes)
+ tablesize: 3rd field on the 1st line of history.dir (bytes)
+
+ If you don't have at least that much RAM, try rebuilding INN with
+ tagged hash enabled.
+
+ NOTE: --enable-largefiles cannot be used with --enable-tagged-hash.
+
+ --with-perl
+ Enables support for embedded Perl, allowing you to install filter
+ scripts written in Perl. Highly recommended, because many really
+ good spam filters are written in Perl. See doc/hook-perl for all
+ the details.
+
+ Even if you do not use this option, INN still requires Perl as
+ mentioned above.
+
+ --with-python
+ Enables support for Python, allowing you to install filter and
+ authentication scripts written in Python. You will need Python
+ 1.5.2 or later installed on your system to enable this option. See
+ doc/hook-python for all the details. Note that there is an
+ incompatibility between INN and Python 2.0 when Python is compiled
+ with cycle garbage collection; this problem was reported fixed in
+ Python 2.1a1.
+
+ --with-innd-port=PORT
+ By default, inndstart(8) refuses to bind to any port under 1024
+ other than 119 and 433 for security reasons (to prevent attacks on
+ rsh(1)-based commands and replacing standard system daemons). If
+ you want to run innd on a different port under 1024, you'll need to
+ tell configure what port you intend to use. (You'll also still need
+ to set the port number in inn.conf or give it to inndstart on the
+ command line.)
+
+ --with-syslog-facility=FACILITY
+ Specifies the syslog facility that INN programs should log to. The
+ default is LOG_NEWS unless configure detects that your system
+ doesn't understand that facility, in which case it uses LOG_LOCAL1.
+ This flag overrides the automatic detection. Be sure to specify a
+ facility not used by anything else on your system (one of LOG_LOCAL0
+ through LOG_LOCAL7, for example).
+
+ --enable-libtool
+ INN has optional support for libtool to generate shared versions of
+ INN's libraries. This can significantly decrease the size of the
+ various binaries that come with a complete INN installation. You
+ can also choose to use libtool even when only building static
+ libraries; a libtool build may be somewhat more portable on weird
+ systems. libtool builds aren't the default because they take
+ somewhat longer. See "./configure --help" for the various available
+ options related to libtool builds.
+
+ Please note that INN's shared library interface is not stable and
+ may change drastically in future releases. For this reason, it's
+ also not properly versioned and won't be until some degree of
+ stability is guaranteed, and the relevant header files are not
+ installed. Only INN should use INN's shared libraries, and you
+ should only use the shared libraries corresponding to the version of
+ INN that you're installing.
+
+ Also, when updating an existing version of INN, INN tries to save
+ backup copies of all files so that you can revert to the previous
+ installed version. Unfortunately, when using shared libraries, this
+ confuses ldconfig on some systems (such as Linux) and the symbolic
+ links for the libraries may point to the .OLD versions. If this
+ happens, you can either fix the links by hand or remove the .OLD
+ versions and re-run ldconfig.
+
+ --enable-uucp-rnews
+ If this option is given to configure, rnews will be installed setuid
+ news, owned by group uucp, and mode 4550. This will allow the UUCP
+ subsystem to run rnews to process UUCP batches of news articles.
+ Prior to INN 2.3, installing rnews setuid news was standard; since
+ most sites no longer use UUCP, it is no longer the default as of INN
+ 2.3 and must be requested at configure time. You probably don't
+ want to use this option unless your server accepts UUCP news
+ batches.
+
+ --enable-setgid-inews
+ If this option is given to configure, inews will be installed setgid
+ news and world-executable so that non-privileged users on the news
+ server machine can use inews to post articles locally (somewhat
+ faster than opening a new network connection). For standalone news
+ servers, by far the most common configuration now, there's no need
+ to use this option; even if you have regular login accounts on your
+ news server, INN's inews can post fine via a network connection to
+ your running news server and doesn't need to use the local socket
+ (which is what setgid enables it to do). Installing inews setgid
+ was the default prior to INN 2.3.
+
+ --with-berkeleydb=PATH
+ Enables support for Berkeley DB (2.x or 3.x), which means that it
+ will then be possible to use the ovdb overview method if you wish.
+ Enabling this configure option doesn't mean you'll be required to
+ use ovdb, but it does require that Berkeley DB be installed on your
+ system (including the header files, not just the runtime libraries).
+ If a path is given, it sets the installed directory of Berkeley DB
+ (configure will search for it in some standard locations, but if you
+ have it installed elsewhere, you may need this option).
+
+ --with-openssl=PATH
+ Enables support for SSL for news reading, which means it will be
+ possible to have SSL or TLS encrypted NNTP connections between your
+ server and newsreaders. This option requires OpenSSL be installed
+ on your system (including the header files, not just the runtime
+ libraries). If a path is given, it sets the installed directory of
+ OpenSSL. After compiling and installing INN with this option,
+ you'll still need to make a certificate and private key to use SSL.
+ See below for details on how to do that.
+
+ --enable-ipv6
+ Enables support for IPv6 in innd, innfeed, nnrpd, and several of the
+ supporting programs. This option should be considered developmental
+ at present. For more information see doc/IPv6-info (and if you have
+ any particularly good or bad news to report, please let us know at
+ <inn-bugs@isc.org>).
+
+ For the most common installation, a standalone news server, a suggested
+ set of options is:
+
+ ./configure --with-perl
+
+ provided that you have the necessary version of Perl installed.
+ (Compiling with an embedded Perl interpretor will allow you to use one
+ of the available excellent spam filters if you so choose.)
+
+ If the configure program runs successfully, then you are ready to build
+ the distribution. From the root of the INN source tree, type:
+
+ make
+
+ At this point you can step away from the computer for a little while and
+ have a quick snack while INN compiles. On a decently fast system it
+ should only take five or ten minutes at the most to build.
+
+ Once the build has completed successfully, you are ready to install INN
+ into its final home. Type:
+
+ make install
+
+ You will need to run this command as root so that INN can create the
+ directories it needs, change ownerships (if you did not compile as the
+ news user) and install a couple of setuid wrapper programs needed to
+ raise resource limits and allow innd to bind to ports under 1024. This
+ step will install INN under the install directory (/usr/local/news,
+ unless you specified something else to the configure script).
+
+ If you are configuring SSL support for newsreaders, you must make a
+ certificate and private key at least once. Type:
+
+ make cert
+
+ as root in order to do this.
+
+ You are now ready for the really fun part: configuring your copy of
+ INN!
+
+Choosing an Article Storage Format
+
+ The first thing to decide is how INN should store articles on your
+ system. There are four different methods for you to choose from, each
+ of which has its own advantages and disadvantages. INN can support all
+ four at the same time, so you can store certain newsgroups in one method
+ and other newsgroups in another method.
+
+ The supported storage formats are:
+
+ tradspool
+ This is the storage method used by all versions of INN previous to
+ 2.0. Articles are stored as individual text files whose names are
+ the same as the article number. The articles are divided up into
+ directories based on the newsgroup name. For example, article 12345
+ in news.software.nntp would be stored as news/software/nntp/12345
+ relative to the root of the article spool.
+
+ Advantages: Widely used and well-understood storage mechanism, can
+ read article spools written by older versions of INN, compatible
+ with all third-party INN add-ons, provides easy and direct access to
+ the articles stored on your server and makes writing programs that
+ fiddle with the news spool very easy, and gives you fine control
+ over article retention times.
+
+ Disadvantages: Takes a very fast file system and I/O system to keep
+ up with current Usenet traffic volumes due to file system overhead.
+ Groups with heavy traffic tend to create a bottleneck because of
+ inefficiencies in storing large numbers of article files in a single
+ directory. Requires a nightly expire program to delete old articles
+ out of the news spool, a process that can slow down the server for
+ several hours or more.
+
+ timehash
+ Articles are stored as individual files as in tradspool, but are
+ divided into directories based on the arrival time to ensure that no
+ single directory contains so many files as to cause a bottleneck.
+
+ Advantages: Heavy traffic groups do not cause bottlenecks, and fine
+ control of article retention time is still possible.
+
+ Disadvantages: The ability to easily find all articles in a given
+ newsgroup and manually fiddle with the article spool is lost, and
+ INN still suffers from speed degredation due to file system overhead
+ (creating and deleting individual files is a slow operation).
+
+ timecaf
+ Similar to timehash, articles are stored by arrival time, but
+ instead of writing a separate file for each article, multiple
+ articles are put in the same file.
+
+ Advantages: Roughly four times faster than timehash for article
+ writes, since much of the file system overhead is bypassed, while
+ still retaining the same fine control over article retention time.
+
+ Disadvantages: Even worse than timehash, and similar to cnfs
+ (below), using this method means giving up all but the most careful
+ manually fiddling with your article spool. As one of the newer and
+ least widely used storage types, timecaf has not been as thoroughly
+ tested as the other methods.
+
+ cnfs
+ CNFS stores articles sequentially in pre-configured buffer files.
+ When the end of the buffer is reached, new articles are stored from
+ the beginning of the buffer, overwriting older articles.
+
+ Advantages: Blazingly fast because no file creations or deletions
+ are necessary to store an article. Unlike all other storage
+ methods, does not require manual article expiration; old articles
+ are deleted to make room for new ones when the buffers get too full.
+ Also, with CNFS your server will never throttle itself due to a full
+ spool disk, and groups are restricted to just the buffer files you
+ give them so that they can never use more than the amount of disk
+ space you allocate to them.
+
+ Disadvantages: Article retention times are more difficult to
+ control because old articles are overwritten automatically. Attacks
+ on Usenet, such as flooding or massive amounts of spam, can result
+ in wanted articles expiring much faster than you intended (with no
+ warning).
+
+ Some general recommendations: If you are installing a transit news
+ server (one that just accepts news and sends it out again to other
+ servers and doesn't support any readers), use CNFS exclusively and don't
+ worry about any of the other storage methods. Otherwise, put
+ high-volume groups and groups whose articles you don't need to keep
+ around very long (binaries groups, *.jobs*, news.lists.filters, etc.) in
+ CNFS buffers, and use timehash, timecaf, or tradspool (if you have a
+ fast I/O subsystem or need to be able to go through the spool manually)
+ for everything else. You'll probably find it most convenient to keep
+ special hierarchies like local hierarchies and hierarchies that should
+ never expire in tradspool.
+
+ If your news server will be supporting readers, you'll also need to
+ choose an overview storage mechanism (by setting *ovmethod* in
+ inn.conf). There are three overview mechanisms to choose from:
+ tradindexed, buffindexed, and ovdb. tradindexed is very fast for
+ readers, but it has to update two files for each incoming article and
+ can be quite slow to write. buffindexed can keep up with a large feed
+ more easily, since it uses large buffers to store all overview
+ information, but it's somewhat slower for readers (although not as slow
+ as the unified overview in INN 2.2). ovdb stores overview data in a
+ Berkeley DB database; it's fast and very robust, but requires more disk
+ space. See the ovdb(5) man page for more information on it.
+
+ Note that ovdb has not been as widely tested as the other overview
+ mechanisms and should be considered experimental. tradindexed is the
+ best tested and most widely used of the overview implementations.
+
+ If buffindexed is chosen, you will need to create the buffers for it to
+ use (very similar to creating CNFS buffers) and list the available
+ buffers in buffindexed.conf. See buffindexed.conf(5) for more
+ information.
+
+Configuring INN
+
+ All documentation from this point on assumes that you have set up the
+ news user on your system as suggested in "Installing INN" so that the
+ root of your INN installation is ~news/. If you've moved things around
+ by using options with "configure", you'll need to adjust the
+ instructions to account for that.
+
+ All of INN's configuration files are located in ~news/etc. Unless noted
+ otherwise, any files referred to below are in this directory. When you
+ first install INN, a sample of each file (containing lots of comments)
+ is installed in ~news/etc; refer to these for concrete examples of
+ everything discussed in this section.
+
+ All of INN's configuration files, all of the programs that come with it,
+ and some of its library routines have documentation in the form of man
+ pages. These man pages were installed in ~news/man as part of the INN
+ installation process and are the most complete reference to how INN
+ works. You're strongly encouraged to refer to the man pages frequently
+ while configuring INN, and for quick reference afterwards. Any detailed
+ questions about individual configuration files or the behavior of
+ specific programs should be answered in them. You may want to add
+ ~news/man to your MANPATH environment variable; otherwise, you may have
+ to use a command like:
+
+ man -M ~news/man inn.conf
+
+ to see the inn.conf(5) man page (for example).
+
+ Before we begin, it is worth mentioning the wildmat pattern matching
+ syntax used in many configuration files. These are simple wildcard
+ matches using the asterisk ("*") as the wildcard character, much like
+ the simple wildcard expansion used by Unix shells.
+
+ In many cases, wildmat patterns can be specified in a comma-separated
+ list to indicate a list of newsgroups. When used in this fashion, each
+ pattern is checked in turn to see if it matches, and the last pattern in
+ the line that matches the group name is used. Patterns beginning with
+ "!" mean to exclude groups matching that pattern. For example:
+
+ *, !comp.*, comp.os.*
+
+ In this case, we're saying we match everything ("*"), except that we
+ don't match anything under comp ("!comp.*"), unless it is actually under
+ the comp.os hierarchy ("comp.os.*"). This is because non-comp groups
+ will match only the first pattern (so we want them), comp.os groups will
+ match all three patterns (so we want them too, because the third pattern
+ counts in this case), and all other comp groups will match the first and
+ second patterns and will be excluded by the second pattern.
+
+ Some uses of wildmat patterns also support "poison" patterns (patterns
+ starting with "@"). These patterns behave just like "!" patterns when
+ checked against a single newsgroup name. Where they become special is
+ for articles crossposted to multiple newsgroups; normally, such an
+ article will be considered to match a pattern if any of the newsgroups
+ it is posted to matches the pattern. If any newsgroup the article is
+ posted to matches an expression beginning with "@", however, that
+ article will not match the pattern even if other newsgroups to which it
+ was posted match other expressions.
+
+ See uwildmat(3) for full details on wildmat patterns.
+
+ In all INN configuration files, blank lines and lines beginning with a
+ "#" symbol are considered comments and are ignored. Be careful, not all
+ files permit comments to begin in the middle of the line.
+
+ inn.conf
+
+ The first, and most important file is inn.conf. This file is organized
+ as a series of parameter-value pairs, one per line. The parameter is
+ first, followed by a colon and one or more whitespace characters, and
+ then the value itself. For some parameters the value is a string or a
+ number; for others it is true or false. (True values can be written as
+ "yes", "true", or "on", whichever you prefer. Similarly, false values
+ can be written as "no", "false", or "off".)
+
+ inn.conf contains dozens of changeable parameters (see inn.conf(5) for
+ full details), but only a few really need to be edited during normal
+ operation:
+
+ allownewnews
+ If set to true then INN will support the NEWNEWS command for news
+ readers. While this can be an expensive operation, its speed has
+ been improved considerably as of INN 2.3 and it's probably safe to
+ turn on without risking excessive server load. The default is true.
+ (Note that the *access:* setting in readers.conf overrides this
+ value; see readers.conf(5) for more details.)
+
+ complaints
+ Used to set the value of the X-Complaints-To: header, which is added
+ to all articles posted locally. The usual value would be something
+ like "abuse@example.com" or "postmaster@example.com". If not
+ specified, the newsmaster email address will be used.
+
+ hiscachesize
+ The amount of memory (in kilobytes) to allocate for a cache of
+ recently used history file entries. Setting this to 0 disables
+ history caching. History caching can greatly increase the number of
+ articles per second that your server is capable of processing. A
+ value of 256 is a good default choice.
+
+ logipaddr
+ If set to true (the default), INN will log the IP address (or
+ hostname, if the host is listed in incoming.conf with a hostname) of
+ the remote host from which it received an article. If set to false,
+ the trailing Path: header entry is logged instead. If you are using
+ controlchan (see below) and need to process ihave/sendme control
+ messages (this is very, very unlikely, so if you don't know what
+ this means, don't worry about it), make sure you set this to false,
+ since controlchan needs a site name, not an IP address.
+
+ organization
+ Set this to the name of your organization as you want it to appear
+ in the Organization: header of all articles posted locally and not
+ already containing that header. This will be overridden by the
+ value of the ORGANIZATION environment variable (if it exists). If
+ neither this parameter nor the environment variable or set, no
+ Organization: header will be added to posts which lack one.
+
+ pathhost
+ This is the name of your news server as you wish it to appear in the
+ Path: header of all postings which travel through your server (this
+ includes local posts and incoming posts that you forward out to
+ other sites). If this parameter is unspecified, the fully-qualified
+ domain name (FQDN) of the machine will be used instead. Please use
+ the FQDN of your server or an alias for your server unless you have
+ a very good reason not to; a future version of the news RFCs may
+ require this.
+
+ rlimitnofile
+ If set to a non-negative value (the default is -1), INN (both innd
+ and innfeed) will try to raise the maximum number of open file
+ descriptors to this value when it starts. This may be needed if you
+ have lots of incoming and outgoing feeds. Note that the maximum
+ value for this setting is very operating-system-dependent, and you
+ may have to reconfigure your system (possibly even recompile your
+ kernel) to increase it. See "File Descriptor Limits" for complete
+ details.
+
+ There are tons of other possible settings; you may want to read through
+ inn.conf(5) to get a feel for your options. Don't worry if you don't
+ understand the purpose of most of them right now. Some of the settings
+ are only needed for very obscure things, and with more experience
+ running your news server the rest will make more sense.
+
+ newsfeeds
+
+ newsfeeds determines how incoming articles are redistributed to your
+ peers and to other INN processes. newsfeeds is very versatile and
+ contains dozens of options; we will touch on just the basics here. The
+ manpage contains more detailed information.
+
+ newsfeeds is organized as a series of feed entries. Each feed entry is
+ composed of four fields separated by colons. Entries may span multiple
+ lines by using a backslash ("\") to indicate that the next line is a
+ continuation of the current line. (Note that comments don't interact
+ with backslashes in the way you might expect. A commented-out line
+ ending in a backslash will still be considered continued on the next
+ line, possibly resulting in more commented out than you intended or
+ bizarre syntax errors. In general, it's best to avoid commenting out
+ lines in the middle of continuation lines.)
+
+ The first field in an entry is the name of the feed. It must be unique,
+ and for feeds to other news servers it is usually set to the actual
+ hostname of the remote server (this makes things easier). The name can
+ optionally be followed by a slash and a comma-separated exclude list.
+ If the feed name or any of the names in the exclude list appear in the
+ Path line of an article, then that article will not be forwarded to the
+ feed as it is assumed that it has passed through that site once already.
+ The exclude list is useful when a news server's hostname is not the same
+ as what it puts in the Path header of its articles, or when you don't
+ want a feed to receive articles from a certain source.
+
+ The second field specifies a set of desired newsgroups and distribution
+ lists, given as newsgroup-pattern/distribution-list. The distribution
+ list is not described here; see newsfeeds(5) for information (it's not
+ used that frequently in practice). The newsgroup pattern is a
+ wildmat-style pattern list as described above (supporting "@").
+
+ The third field is a comma-separated list of flags that determine both
+ the type of feed entry and sets certain parameters for the entry. See
+ newsfeeds(5) for information on the flag settings; you can do a
+ surprising amount with them. The three most common patterns, and the
+ ones mainly used for outgoing news feeds to other sites, are "Tf,Wnm"
+ (to write out a batch file of articles to be sent, suitable for
+ processing by nntpsend and innxmit), "Tm" (to send the article to a
+ funnel feed, used with innfeed), and "Tc,Wnm*" (to collect a funnel feed
+ and send it via a channel feed to an external program, used to send
+ articles to innfeed).
+
+ The fourth field is a multi-purpose parameter whose meaning depends on
+ the settings of the flags in the third field. To get a feel for it
+ using the examples above, for file feeds ("Tf") it's the name of the
+ file to write, for funnel feeds ("Tm") it's the name of the feed entry
+ to funnel into, and for channel feeds ("Tc") it's the name of the
+ program to run and feed references to articles.
+
+ Now that you have a rough idea of the file layout, we'll begin to add
+ the actual feed entries. First, we'll set up the special ME entry.
+ This entry is required and serves two purposes: the newsgroup pattern
+ specified here is prepended to the newsgroup list of all other feeds,
+ and the distribution pattern for this entry determines what
+ distributions (from the Distribution: header of incoming articles) are
+ accepted from remote sites by your server. The example in the sample
+ newsfeeds file is a good starting point. If you are going to create a
+ local hierarchy that should not be distributed off of your system, it
+ may be useful to exclude it from the default subscription pattern, but
+ default subscription patterns are somewhat difficult to use right so you
+ may want to just exclude it specifically from every feed instead.
+
+ The ME entry tends to confuse a lot of people, so this point is worth
+ repeating: the newsgroup patterns set the default subscription for
+ *outgoing* feeds, and the distribution patterns set the acceptable
+ Distribution: header entries for *incoming* articles. This is confusing
+ enough that it may change in later versions of INN.
+
+ There are two basic ways to feed articles to remote sites. The most
+ common for large sites and particularly for transit news servers is
+ innfeed(8), which sends articles to remote sites in real time (the
+ article goes out to all peers that are supposed to receive it
+ immediately after your server accepts it). For smaller sites,
+ particularly sites where the only outgoing messages will be locally
+ posted articles, it's more common to batch outgoing articles and send
+ them every ten minutes or so from cron using nntpsend(8) and innxmit(8).
+ Batching gives you more control and tends to be extremely stable and
+ reliable, but it's much slower and can't handle high volume very well.
+
+ Batching outgoing posts is easy to set up; for each peer, add an entry
+ to newsfeeds that looks like:
+
+ remote.example.com/news.example.com\
+ :<newsgroups>\
+ :Tf,Wnm:
+
+ where <newsgroups> is the wildmat pattern for the newsgroups that site
+ wants. In this example, the actual name of the remote site is
+ "remote.example.com", but it puts "news.example.com" in the Path:
+ header. If the remote site puts its actual hostname in the Path:
+ header, you won't need the "/news.example.com" part.
+
+ This entry will cause innd to write out a file in ~news/spool/outgoing
+ named remote.example.com and containing the Message-ID and storage token
+ of each message to send to that site. (The storage token is INN's
+ internal pointer to where an article is stored; to retrieve an article
+ given its storage token, use sm(8)). innxmit knows how to read files of
+ this format and send those articles to the remote site. For information
+ on setting it up to run periodically, see "Setting Up the Cron Jobs"
+ below. You will also need to set up a config file for nntpsend; see the
+ man page for nntpsend.ctl(5) for more information.
+
+ If instead you want to use innfeed to send outgoing messages
+ (recommended for sites with more than a couple of peers), you need some
+ slightly more complex magic. You still set up a separate entry for each
+ of your peers, but rather than writing out batch files, they all
+ "funnel" into a special innfeed entry. That special entry collects all
+ of the separate funnel feeds and sends the data through a special sort
+ of feed to an external program (innfeed in this case); this is a
+ "channel" feed.
+
+ First, the special channel feed entry for innfeed that will collect all
+ the funnel feeds:
+
+ innfeed!\
+ :!*\
+ :Tc,Wnm*:/usr/local/news/bin/startinnfeed -y
+
+ (adjust the path to startinnfeed(1) if you installed it elsewhere).
+ Note that we don't feed this entry any articles directly (its newsgroup
+ pattern is "!*"). Note also that the name of this entry ends in an
+ exclamation point. This is a standard convention for all special feeds;
+ since the delimiter for the Path: header is "!", no site name containing
+ that character can ever match the name of a real site.
+
+ Next, set up entries for each remote site to which you will be feeding
+ articles. All of these entries should be of the form:
+
+ remote.example.com/news.example.com\
+ :<newsgroups>\
+ :Tm:innfeed!
+
+ specifying that they funnel into the "innfeed!" feed. As in the
+ previous example for batching, "remote.example.com" is the actual name
+ of the remote peer, "news.example.com" is what it puts in the Path:
+ header (if different than the actual name of the server), and
+ <newsgroups> is the wildmat pattern of newsgroups to be sent.
+
+ As an alternative to NNTP, INN may also feed news out to an IMAP server,
+ by using imapfeed(8), which is almost identical to innfeed. The
+ startinnfeed process can be told to start imapfeed instead of innfeed.
+ The feed entry for this is as follows:
+
+ imapfeed!\
+ :!*\
+ :Tc,Wnm*,S16384:/usr/local/news/bin/startinnfeed imapfeed
+
+ And set up entries for each remote site like:
+
+ remote.example.com/news.example.com\
+ :<newsgroups>\
+ :Tm:imapfeed!
+
+ For more information on imapfeed, look at the innfeed/imap_connection.c.
+ For more information on IMAP in general, see RFC 2060.
+
+ Finally, there is a special entry for controlchan(8), which processes
+ newsgroup control messages, that should always be in newsfeeds unless
+ you never want to honor any control messages. This entry should look
+ like:
+
+ controlchan!\
+ :!*,control,control.*,!control.cancel\
+ :Tc,Wnsm:/usr/local/news/bin/controlchan
+
+ (modified for the actual path to controlchan if you put it somewhere
+ else). See "Processing Control Messages" for more details.
+
+ For those of you upgrading from earlier versions of INN, note that the
+ functionality of overchan(8) and crosspost is now incorporated into INN
+ and neither of those programs is necessary. Unfortunately, crosspost
+ currently will not work even with the tradspool storage method. You can
+ still use overchan if you make sure to set *useoverchan* to true in
+ inn.conf so that innd doesn't write overview data itself, but be
+ careful: innd may accept articles faster than overchan can process the
+ data.
+
+ incoming.conf
+
+ incoming.conf file specifies which machines are permitted to connect to
+ your host and feed it articles. Remote servers you peer with should be
+ listed here. Connections from hosts not listed in this file will (if
+ you don't allow readers) be rejected or (if you allow readers) be handed
+ off to nnrpd and checked against the access restrictions in
+ readers.conf.
+
+ Start with the sample incoming.conf and, for each remote peer, add an
+ entry like:
+
+ peer remote.example.com { }
+
+ This uses the default parameters for that feed and allows incoming
+ connections from a machine named "remote.example.com". If that peer
+ could be connecting from several different machines, instead use an
+ entry like:
+
+ peer remote.example.com {
+ hostname: "remote.example.com, news.example.com"
+ }
+
+ This will allow either "remote.example.com" or "news.example.com" to
+ feed articles to you. (In general, you should add new peer lines for
+ each separate remote site you peer with, and list multiple host names
+ using the *hostname* key if one particular remote site uses multiple
+ servers.)
+
+ You can restrict the newsgroups a remote site is allowed to send you,
+ using the same sort of pattern that newsfeeds(5) uses. For example, if
+ you want to prevent "example.com" hosts from sending you any articles in
+ the "local.*" hierarchy (even if they're crossposted to other groups),
+ change the above to:
+
+ peer remote.example.com {
+ patterns: "*, @local.*"
+ hostname: "remote.example.com, news.example.com"
+ }
+
+ Note, however, that restricting what a remote side can send you will
+ *not* reduce your incoming bandwidth usage. The remote site will still
+ send you the entire article; INN will just reject it rather than saving
+ it to disk. To reduce bandwidth, you have to contact your peers and ask
+ them not to send you the traffic you don't want.
+
+ There are various other things you can set, including the maximum number
+ of connections the remote host will be allowed. See incoming.conf(5)
+ for all the details.
+
+ Note for those familiar with older versions of INN: this file replaces
+ the old hosts.nntp configuration file.
+
+ cycbuff.conf
+
+ cycbuff.conf is only required if CNFS is used. If you aren't using
+ CNFS, skip this section.
+
+ CNFS stores articles in logical objects called *metacycbuffs*. Each
+ metacycbuff is in turn composed of one or more physical buffers called
+ *cycbuffs*. As articles are written to the metacycbuff, each article is
+ written to the next cycbuff in the list in a round-robin fashion (unless
+ "sequential" mode is specified, in which case each cycbuff is filled
+ before moving on to the next). This is so that you can distribute the
+ individual cycbuffs across multiple physical disks and balance the load
+ between them.
+
+ There are two ways to create your cycbuffs:
+
+ 1. Use a block device directly. This will probably give you the most
+ speed since it avoids the file system overhead of large files, but
+ it requires your OS support mmap(2) on a block device. Solaris
+ supports this, as do late Linux 2.4 kernels. FreeBSD does not at
+ last report. Also on many PC-based Unixes it is difficult to create
+ more than eight partitions, which may limit your options.
+
+ 2. Use a real file on a filesystem. This will probably be a bit slower
+ than using a block device directly, but it should work on any Unix
+ system.
+
+ If you're having doubts, use option #2; it's easier to set up and should
+ work regardless of your operating system.
+
+ Now you need to decide on the sizes of your cycbuffs and metacycbuffs.
+ You'll probably want to separate the heavy-traffic groups
+ ("alt.binaries.*" and maybe a few other things like "*.jobs*" and
+ "news.lists.filters") into their own metacycbuff so that they don't
+ overrun the server and push out articles on the more useful groups. If
+ you have any local groups that you want to stay around for a while then
+ you should put them in their own metacycbuff as well, so that they don't
+ get pushed out by other traffic. (Or you might store them in one of the
+ other storage methods, such as tradspool.)
+
+ For each metacycbuff, you now need to determine how many cycbuffs will
+ make up the metacycbuff, the size of those cycbuffs, and where they will
+ be stored. Some OSes do not support files larger than 2 GB, which will
+ limit the size you can make a single cycbuff, but you can still combine
+ many cycbuffs into each metacycbuff. Older versions of Linux are known
+ to have this limitation; FreeBSD does not. Some OSes that support large
+ files don't support direct access to block devices for large partitions
+ (Solaris prior to Solaris 7, or not running in 64-bit mode, is in this
+ category); on those OSes, if you want cycbuffs over 2 GB, you'll have to
+ use regular files. If in doubt, keep your cycbuffs smaller than 2 GB.
+ Also, when laying out your cycbuffs, you will want to try to arrange
+ them across as many physical disks as possible (or use a striped disk
+ array and put them all on that).
+
+ In order to use any cycbuff larger than 2 GB, you need to build INN with
+ the --enable-largefiles option. See "Installing INN" for more
+ information and some caveats.
+
+ For each cycbuff you will be creating, add a line to cycbuff.conf like
+ the following:
+
+ cycbuff:NAME:/path/to/buffer:SIZE
+
+ NAME must be unique and must be at most seven characters long.
+ Something simple like "BUFF00", "BUFF01", etc. is a decent choice, or
+ you may want to use something that includes the SCSI target and slice
+ number of the partition. SIZE is the buffer size in kilobytes (if
+ you're trying to stay under 2 GB, keep your sizes below 2097152).
+
+ Now, you need to tell INN how to group your cycbuffs into metacycbuffs.
+ This is similar to creating cycbuff entries:
+
+ metacycbuff:BUFFNAME:CYCBUFF,CYCBUFF,CYCBUFF
+
+ BUFFNAME is the name of the metacycbuff and must be unique and at most
+ eight characters long. These should be a bit more meaningful than the
+ cycbuff names since they will be used in other config files as well.
+ Try to name them after what will be stored in them; for example, if this
+ metacycbuff will hold alt.binaries postings, "BINARIES" would be a good
+ choice. The last part of the entry is a comma-separated list of all of
+ the cycbuffs that should be used to build this metacycbuff. Each
+ cycbuff should only appear in one metacycbuff line, and all metacycbuff
+ lines must occur after all cycbuff lines in the file.
+
+ If you want INN to fill each cycbuff before moving on to the next one
+ rather than writing to them round-robin, add ":SEQUENTIAL" to the end of
+ the metacycbuff line. This may give noticeably better performance when
+ using multiple cycbuffs on the same spindle (such as partitions or
+ slices of a larger disk), but will probably give worse performance if
+ your cycbuffs are spread out across a lot of spindles.
+
+ By default, CNFS data is flushed to disk every 25 articles. If you're
+ running a small server with a light article load, this could mean losing
+ quite a few articles in a crash. You can change this interval by adding
+ a cycbuffupdate line to your cycbuff.conf file; see cycbuff.conf(5) for
+ more details.
+
+ Finally, you have to create the cycbuffs. See "Creating the Article
+ Spool" for more information on how to do that.
+
+ storage.conf
+
+ storage.conf determines where incoming articles will be stored (what
+ storage method, and in the case of CNFS, what metacycbuff). Each entry
+ in the file defines a storage class for articles. The first matching
+ storage class is used to store the article; if no storage class matches,
+ INN will reject that article. (This is almost never what you want, so
+ make sure this file ends in a catch-all entry that will match
+ everything.)
+
+ A storage class definition looks like this:
+
+ method <methodname> {
+ newsgroups: <wildmat>
+ class: <storage_class>
+ size: <minsize>[,<maxsize>]
+ expires: <mintime>[,<maxtime>]
+ options: <options>
+ }
+
+ <methodname> is the name of the storage method to use to store articles
+ in this class ("cnfs", "timehash", "timecaf", "tradspool", or the
+ special method "trash" that accepts the article and throws it away).
+
+ The first parameter is a wildmat pattern in the same format used by the
+ newsfeeds(5) file, and determines what newsgroups are accepted by this
+ storage class.
+
+ The second parameter is a unique number identifying this storage class
+ and should be between 0 and 255. It can be used to control article
+ expiration, and for timehash and timecaf will set the top-level
+ directory in which articles accepted by this storage class are stored.
+ The easiest way to deal with this parameter is to just number all
+ storage classes in storage.conf sequentially. The assignment of a
+ particular number to a storage class is arbitrary but *permanent* (since
+ it is used in storage tokens).
+
+ The third parameter can be used to accept only articles in a certain
+ size range into this storage class. A <maxsize> of 0 (or a missing
+ <maxsize>) means no upper limit (and of course a <minsize> of 0 would
+ mean no lower limit, because all articles are more than zero bytes
+ long). If you don't want to limit the size of articles accepted by this
+ storage class, leave this parameter out entirely.
+
+ The fourth parameter you probably don't want to use; it lets you assign
+ storage classes based on the Expires: header of incoming articles. The
+ exact details are in storage.conf(5). It's very easy to use this
+ parameter incorrectly; leave it out entirely unless you've read the man
+ page and know what you're doing.
+
+ The fifth parameter is the options parameter. Currently only CNFS uses
+ this field; it should contain the name of the metacycbuff used to store
+ articles in this storage class.
+
+ If you're using CNFS exclusively, just create one storage class for each
+ metacycbuff that you have defined in cycbuff.conf and set the newsgroups
+ pattern according to what newsgroups should be stored in that buffer.
+
+ If you're using timehash or timecaf, the storage class IDs are used to
+ store articles in separate directory trees, which you can take advantage
+ of to put particular storage classes on different disks. Also,
+ currently storage class is the only way to specify expiration time, so
+ you will need to divide up your newsgroups based on how long you want to
+ retain articles in those groups and create a storage class for each such
+ collection of newsgroups. Make note of the storage class IDs you assign
+ as they will be needed when you edit expire.ctl a bit later.
+
+ expire.ctl
+
+ expire.ctl sets the expiration policy for articles stored on the server.
+ Be careful, since the default configuration will expire most articles
+ after 10 days; in most circumstances this deletion is *permanent*, so
+ read this whole section carefully if you want to keep local hierarchies
+ forever. (See archive(8) for a way to automate backups of important
+ articles.)
+
+ Only one entry is required for all storage classes; it looks like:
+
+ /remember/:10
+
+ This entry says how long to keep the Message-IDs for articles that have
+ already expired in the history file so that the server doesn't accept
+ them again. Occasionally, fairly old articles will get regurgitated
+ somewhere and offered to you again, so even after you've expired
+ articles from your spool, you want to keep them around in your history
+ file for a little while to ensure you don't get duplicates.
+
+ INN will reject any articles more than a certain number of days old (the
+ *artcutoff* parameter in inn.conf, defaulting to 10); the number on the
+ "/remember/" line should match that.
+
+ CNFS makes no further use of expire.ctl, since articles stored in CNFS
+ buffers expire automatically when the buffer runs out of free space (but
+ see the "-N" option in expireover(8) if you really want to expire them
+ earlier). For other storage methods, there are two different syntaxes
+ of this file, depending on *groupbaseexpiry* in inn.conf. If it is set
+ to false, expire.ctl takes entries of the form:
+
+ <storage_class>:<keep>:<default>:<purge>
+
+ <storage_class> is the number assigned to a storage class in
+ storage.conf. <default> is the number of days to keep normal articles
+ in that storage class (decimal values are allowed). For articles that
+ don't have an Expires: header, those are the only two values that
+ matter. For articles with an Expires: header, the other two values come
+ into play; the date given in the Expires: header of an article will be
+ honored, subject to the contraints set by <keep> and <purge>. All
+ articles in this storage class will be kept for at least <keep> days,
+ regardless of their Expires: headers, and all articles in this storage
+ class will be expired after <purge> days, even if their Expires: headers
+ specify a longer life.
+
+ All three of these fields can also contain the special keyword "never".
+ If <default> is "never", only articles with explicit Expires: headers
+ will ever be expired. If <keep> is "never", articles with explicit
+ Expires: headers will be kept forever. Setting <purge> to "never" says
+ to honor Expires: headers even if they specify dates far into the
+ future. (Note that if <keep> is set to "never", all articles with
+ Expires: headers are kept forever and the value of <purge> is not used.)
+
+ If the value of "groupbaseexpiry" is true, expire.ctl takes entries of
+ the form:
+
+ <wildmat>:<flag>:<keep>:<default>:<purge>
+
+ <wildmat> is a wildmat expression ("!" and "@" not permitted, and only a
+ single expression, not a comma-separated set of them). Each expiration
+ line applies to groups matching the wildmat expression. <flag> is "M"
+ for moderated groups, "U" for unmoderated groups, and "A" for groups
+ with any moderation status; the line only matches groups with the
+ indicated expiration status. All of the other fields have the same
+ meaning as above.
+
+ readers.conf
+
+ Provided that *noreader* is set to false in inn.conf, any connection
+ from a host that doesn't match an entry in incoming.conf (as well as any
+ connection from a host that does match such an entry, but has issued a
+ MODE READER command) will be handed off to nnrpd(8), the part of INN
+ that supports newsreading clients. nnrpd uses readers.conf to determine
+ whether a given connection is allowed to read news, and if so what
+ newsgroups the client can read and post to.
+
+ There are a variety of fairly complicated things that one can do with
+ readers.conf, things like run external authentication programs that can
+ query RADIUS servers. See readers.conf(5) and the example file for all
+ the gory details. Here's an example of probably the simplest reasonable
+ configuration, one that only allows clients in the example.com domain to
+ read from the server and allows any host in that domain to read and post
+ to all groups:
+
+ auth "example.com" {
+ hosts: "example.com, *.example.com"
+ default: "<user>"
+ default-domain: "example.com"
+ }
+
+ access "all" {
+ users: "*@example.com"
+ newsgroups: "*"
+ }
+
+ If you're running a server for one particular domain, want to allow all
+ hosts within that domain to read and post to any group on the server,
+ and want to deny access to anyone outside that domain, just use the
+ above and change "example.com" in the above to your domain and you're
+ all set. Lots of examples of more complicated things are in the sample
+ file.
+
+Creating the Article Spool (CNFS only)
+
+ If you are using actual files as your CNFS buffers, you will need to
+ pre-create those files, ensuring they're the right size. The easiest
+ way to do this is with dd. For each cycbuff in cycbuff.conf, create the
+ buffer with the following commands (as the news user):
+
+ dd if=/dev/zero of=/path/to/buffer bs=1k count=BUFFERSIZE
+ chmod 664 /path/to/buffer
+
+ Substitute the correct path to the buffer and the size of the buffer as
+ specified in cycbuff.conf. This will create a zero-filled file of the
+ correct size; it may take a while, so be prepared to wait.
+
+ Here's a command that will print out the dd(1) commands that you should
+ run:
+
+ awk -F: \
+ '/^cy/ { printf "dd if=/dev/zero of=%s bs=1k count=%s\n", $3, $4 }' \
+ ~news/etc/cycbuff.conf
+
+ If you are using block devices, you don't technically have to do
+ anything at all (since INN is capable of using the devices in /dev), but
+ you probably want to create special device files for those devices
+ somewhere for INN's private use. It s more convenient to keep all of
+ INN's stuff together, but more importantly, the device files used by INN
+ really should be owned by the news user and group, and you may not want
+ to do that with the files in /dev.
+
+ To create the device files for INN, use mknod(8) with a type of "b",
+ getting the major and minor device numbers from the existing devices in
+ /dev. There's a small shell script in cycbuff.conf(5) that may help
+ with this. Make sure to create the device files in the location INN
+ expects them (specified in cycbuff.conf).
+
+ Solaris users please note: on Solaris, do not use block devices that
+ include the first cylinder of the disk. Solaris doesn't protect the
+ superblock from being overwritten by an application writing to block
+ devices and includes it in the first cylinder of the disk, so unless you
+ use a slice that starts with cylinder 1 instead of 0, INN will
+ invalidate the partition table when it tries to initialize the cycbuff
+ and all further accesses will fail until you repartition.
+
+Creating the Database Files
+
+ At this point, you need to set up the news database directory
+ (~news/db). This directory will hold the active(5) file (the list of
+ newsgroups you carry), the active.times(5) file (the creator and
+ creation time of newsgroups created since the server was initialized),
+ the newsgroups(5) file (descriptions for all the newsgroups you carry),
+ and the history(5) file (a record of every article the server currently
+ has or has seen in the past few days, used to decide whether to accept
+ or refuse new incoming messages).
+
+ Before starting to work on this, make sure you're logged on as the news
+ user, since all of these files need to be owned by that user. This is a
+ good policy to always follow; if you are doing any maintenance work on
+ your news server, log on as the news user. Don't do maintenance work as
+ root. Also make sure that ~news/bin is in the default path of the news
+ user (and while you're at it, make sure ~news/man is in the default
+ MANPATH) so that you can run INN maintenance commands without having to
+ type the full path.
+
+ If you already have a server set up (if you're upgrading, or setting up
+ a new server based on an existing server), copy active and newsgroups
+ from that server into ~news/db. Otherwise, you'll need to figure out
+ what newsgroups you want to carry and create new active and newsgroups
+ files for them. If you plan to carry a full feed, or something close to
+ that, go to <ftp://ftp.isc.org/pub/usenet/CONFIG/> and download active
+ and newsgroups from there; that will start you off with reasonably
+ complete files. If you plan to only carry a small set of groups, the
+ default minimal active file installed by INN is a good place to start;
+ you can create additional groups after the server is running by using
+ "ctlinnd newgroup". (Another option is to use actsync(8) to synchronize
+ your newsgroup list to that of another server.)
+
+ "control" and "junk" must exist as newsgroups in your active file for
+ INN to start, and creating pseudogroups for the major types of control
+ messages is strongly encouraged for all servers that aren't standalone.
+ If you don't want these groups to be visible to clients, do *not* delete
+ them; simply hide them in readers.conf. "to" must also exist as a
+ newsgroup if you have mergetogroups set in inn.conf.
+
+ Next, you need to create an empty history database. To do this, type:
+
+ cd ~news/db
+ touch history
+ makedbz -i
+
+ When it finishes, rename the files it created to remove the ".n" in the
+ file names and then make sure the file permissions are correct on all
+ the files you've just created:
+
+ chmod 644 *
+
+ Your news database files are now ready to go.
+
+Configuring syslog
+
+ While some logs are handled internally, INN also logs a wide variety of
+ information via syslog. INN's nightly report programs know how to roll
+ and summarize those syslog log files, but when you first install INN you
+ need to set them up.
+
+ If your system understands the "news" syslog facility, INN will use it;
+ otherwise, it will log to "local1". Nearly every modern system has a
+ "news" syslog facility so you can safely assume that yours does, but if
+ in doubt take a look at the output from running "configure". You should
+ see a line that looks like:
+
+ checking log level for news... LOG_NEWS
+
+ If that says LOG_LOCAL1 instead, change the below instructions to use
+ "local1" instead of "news".
+
+ Edit /etc/syslog.conf on your system and add lines that look like the
+ following:
+
+ news.crit /usr/local/news/log/news.crit
+ news.err /usr/local/news/log/news.err
+ news.notice /usr/local/news/log/news.notice
+
+ (Change the path names as necessary if you installed INN in a different
+ location than /usr/local/news.) These lines *must* be tab-delimited, so
+ don't copy and paste from these instructions. Type it in by hand and
+ make sure you use a tab, or you'll get mysterious failures. You'll also
+ want to make sure that news log messages don't fill your other log files
+ (INN generates a lot of log traffic); so for every entry in
+ /etc/syslog.conf that starts with "*", add ";news.none" to the end of
+ the first column. For example, if you have a line like:
+
+ *.err /dev/console
+
+ change it to:
+
+ *.err;news.none /dev/console
+
+ (You can choose not to do this for the higher priority log messages, if
+ you want to make sure they go to your normal high-priority log files as
+ well as INN's. Don't bother with anything lower priority than "crit",
+ though. "news.err" isn't interesting enough to want to see all the
+ time.) Now, make sure that the news log files exist; syslog generally
+ won't create files automatically. Enter the following commands:
+
+ touch /usr/local/news/log/news.crit
+ touch /usr/local/news/log/news.err
+ touch /usr/local/news/log/news.notice
+ chown news /usr/local/news/log/news.*
+ chgrp news /usr/local/news/log/news.*
+
+ (again adjusting the paths if necessary for your installation).
+ Finally, send a HUP signal to syslogd to make it re-read its
+ configuration file.
+
+Setting Up the Cron Jobs
+
+ INN requires a special cron job to be set up on your system to run
+ news.daily(8) which performs daily server maintenance tasks such as
+ article expiration and the processing and rotation of the server logs.
+ Since it will slow the server down while it is running, it should be run
+ during periods of low server usage, such as in the middle of the night.
+ To run it at 3am, for example, add the following entry to the news
+ user's crontab file:
+
+ 0 3 * * * /usr/local/news/bin/news.daily expireover lowmark
+
+ or, if your system does not have per-user crontabs, put the following
+ line into your system crontab instead:
+
+ 0 3 * * * su -c "/usr/local/news/bin/news.daily expireover lowmark" news
+
+ If you're using any non-CNFS storage methods, add "delayrm" to the above
+ option list for news.daily.
+
+ The news user obviously must be able to run cron jobs. On Solaris, this
+ means that it must have a valid /etc/shadow entry and must not be locked
+ (although it may be a non-login account). There may be similar
+ restrictions with other operating systems.
+
+ If you use the batching method to send news, also set up a cron job to
+ run nntpsend(8) every ten minutes. nntpsend will run innxmit for all
+ non-empty pending batch files to send pending news to your peers. That
+ cron entry should look something like:
+
+ 0,10,20,30,40,50 * * * * /usr/local/news/bin/nntpsend
+
+ The pathnames and user ID used above are the installation defaults;
+ change them to match your installation if you used something other than
+ the defaults.
+
+ The parameters passed to news.daily in the above example are the most
+ common (and usually the most efficient) ones to use. More information
+ on what these parameters do can be found in the news.daily(8) man page.
+
+File Descriptor Limits
+
+ INN likes to use a lot of file descriptors, particularly if you have a
+ lot of peers. Depending on what your system defaults are, you may need
+ to make sure the default limit is increased for INN (particularly for
+ innd and innfeed). This is vital on Solaris, which defaults (at least
+ as of 2.6) to an absurdly low limit of 64 file descriptors per process.
+
+ One way to increase the number of file descriptors is to set
+ *rlimitnofile* in inn.conf to a higher value. This will cause both
+ startinnfeed and inndstart (the setuid root wrapper scripts that start
+ innfeed and innd, respectively) to increase the file descriptor limits
+ before they run the regular INN programs. Note, however, that INN won't
+ be able to increase the limits above the hard limits set by your
+ operating system; on some systems, that hard limit is normally 256 file
+ descriptors (Linux, for example). On others, like Solaris, it's 1024.
+ Increasing the limit beyond that value may require serious system
+ configuration work. (On some operating systems, it requires patching
+ and recompiling the kernel. On Solaris it can be changed in
+ /etc/system, but for 2.6 or earlier the limit cannot be increased beyond
+ 1024 without breaking select(2) and thereby breaking all of INN. For
+ current versions of Linux, you may be able to change the maximum by
+ writing to /proc/sys/fs/file-max.)
+
+ 256 file descriptors will probably be enough for all but the largest
+ sites. There is no harm in setting the limits higher than you actually
+ need (provided they're set to something lower than or equal to your
+ system hard limit). 256 is therefore a reasonable value to try.
+
+ If you're installing INN on a Solaris system, particularly if you're
+ installing it on a dedicated news server machine, it may be easier to
+ just increase the default file descriptor limit across the board for all
+ processes. You can do that by putting the line:
+
+ set rlim_fd_cur = 256
+
+ in /etc/system and rebooting. You can increase it all the way to 1024
+ (and may need to if you have a particularly large site), but that can
+ cause RPC and some stdio applications to break. It therefore probably
+ isn't a good idea on a machine that isn't dedicated to INN.
+
+Starting and Stopping the System
+
+ INN is started via the shell script rc.news. This must be run as the
+ news user and not as root. To start INN on system boot, you therefore
+ want to put something like:
+
+ su - news -c /usr/local/news/bin/rc.news
+
+ in the system boot scripts. If innd is stopped or killed, you can
+ restart it by running rc.news by hand as the news user.
+
+ The rc.news script may also be used to shut down INN, with the "stop"
+ option:
+
+ su - news -c '/usr/local/news/bin/rc.news stop'
+
+ In the contrib directory of this source tree is a sample init script for
+ people using System V-style init.d directories.
+
+Processing Newsgroup Control Messages
+
+ Control messages are specially-formatted messages that tell news servers
+ to take various actions. Cancels (commands to delete messages) are
+ handled internally by INN, and all other control messages are processed
+ by controlchan. controlchan should be run out of newsfeeds if you want
+ your news server to process any control messages; see "Configuring INN"
+ for specific instructions.
+
+ The actions of controlchan are determined by control.ctl, which lists
+ who can perform what actions. The primary control messages to be
+ concerned with are "newgroup" (to create a newsgroup), "rmgroup" (to
+ remove a newsgroup), and "checkgroups" (to compare the list of groups
+ carried in a hierarchy to a canonical list). INN comes with a
+ control.ctl file that processes control messages in most major public
+ hierarchies; if you don't want to act on all those control messages, you
+ should remove from that file all entries for hierarchies you don't want
+ to carry.
+
+ You can tell INN to just authenticate control messages based on the From
+ header of the message, but this is obviously perilous and control
+ messages are widely forged. Many hierarchies sign all of their control
+ messages with PGP, allowing news servers to verify their authenticity,
+ and checking those signatures for hierarchies that use them is highly
+ recommended. controlchan knows how to do this (using pgpverify) without
+ additional configuration, but you do have to provide it with a public
+ key ring containing the public keys of all of the hierarchy
+ administrators whose control messages you want to check.
+
+ INN expects the public key ring to either be in the default location for
+ a PGP public key ring for the news user (generally ~news/.gnupg for
+ GnuPG and ~news/.pgp for old PGP implementations), or in pathetc/pgp
+ (/usr/local/news/etc/pgp by default). The latter is the recommended
+ path. To add a key to that key ring, use:
+
+ gpg --import --homedir=/usr/local/news/etc/pgp <file>
+
+ where <file> is a file containing the hierarchy key. Change the homedir
+ setting to point to pathetc/pgp if you have INN installed in a
+ non-default location. If you're using the old-style PGP program, an
+ equivalent command is:
+
+ env PGPPATH=/usr/local/news/etc/pgp pgp <file>
+
+ You can safely answer "no" to questions about whether you want to sign,
+ trust, or certify keys.
+
+ The URLs from which you can get hierarchy keys are noted in comments in
+ control.ctl. <ftp://ftp.isc.org/pub/pgpcontrol/PGPKEYS> tries to
+ collect the major hierarchy keys.
+
+ If you are using GnuPG, please note that the first user ID on the key
+ will be the one that's used by INN for verification and must match the
+ key listed in control.ctl. If a hierarchy key has multiple user IDs,
+ you may have to remove all the user IDs except the one that matches the
+ control.ctl entry using "gpg --edit-key" and the "delkey" command.
+
--- /dev/null
+INN as a whole and all code contained in it not otherwise marked with
+different licenses and/or copyrights is covered by the following copyright
+and license:
+
+ Copyright (c) 2004, 2005, 2006, 2007, 2008
+ by Internet Systems Consortium, Inc. ("ISC")
+ Copyright (c) 1991, 1994, 1995, 1996, 1997, 1998, 1999, 2000, 2001,
+ 2002, 2003 by The Internet Software Consortium and Rich Salz
+
+ This code is derived from software contributed to the Internet Software
+ Consortium by Rich Salz.
+
+ Permission to use, copy, modify, and distribute this software for any
+ purpose with or without fee is hereby granted, provided that the above
+ copyright notice and this permission notice appear in all copies.
+
+ THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+ REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY
+ SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+
+Some specific portions of INN are covered by different licenses. Those
+licenses, if present, will be noted prominantly at the top of those source
+files. Specifically (but possibly not comprehensively):
+
+ authprogs/smbval/*, backends/send-uucp.in, and control/perl-nocem.in
+ are under the GNU General Public License. See doc/GPL for a copy of
+ this license.
+
+ backends/shrinkfile.c, frontends/scanspool.in, lib/concat.c,
+ lib/hstrerror.c, lib/inet_aton.c, lib/inet_ntoa.c, lib/memcmp.c,
+ lib/parsedate.y, lib/pread.c, lib/pwrite.c, lib/setenv.c, lib/seteuid.c,
+ lib/strerror.c, lib/strlcat.c and lib/strlcpy.c are in the public
+ domain.
+
+ lib/snprintf.c may be used for any purpose as long as the author's
+ notice remains intact in all source code distributions.
+
+ control/gpgverify.in, control/pgpverify.in and control/signcontrol.in
+ are under a BSD-style license (with the advertising clause) with UUNET
+ Technologies, Inc. as the copyright holder. See the end of those files
+ for details.
+
+ control/controlchan.in and control/modules/*.pl are covered by a
+ two-clause BSD-style license (no advertising clause). See the
+ beginning of those files for details.
+
+ lib/strcasecmp.c, lib/strspn.c, and lib/strtok.c are taken from BSD
+ sources and are covered by the standard BSD license. See those files
+ for more details.
+
+ lib/md5.c is covered under the standard free MD5 license from RSA Data
+ Security. See the file for more details. A clarification is also
+ provided here: <http://www.ietf.org/ietf/IPR/RSA-MD-all>.
+
+ "Implementations of these message-digest algorithms, including
+ implementations derived from the reference C code in RFC-1319,
+ RFC-1320, and RFC-1321, may be made, used, and sold without
+ license from RSA for any purpose."
+
+ history/his.c and history/hisv6/hisv6.c are under a license very
+ similar to the new BSD license (no advertising clause) but with Thus
+ plc as the copyright holder. See those files for details.
+
+ lib/tst.c, include/inn/tst.h and doc/pod/tst.pod are derived from
+ <http://www.octavian.org/cs/tst1.3.tar.gz> and are under the new BSD
+ license (no advertising clause), but with Peter A. Friend as the
+ copyright holder.
+
+ tests/runtests.c is covered under a license very similar to the MIT/X
+ Consortium license. See the beginning of the file for details.
+
+Note that all portions of INN that link with core INN code have to be
+covered by licenses compatible with the license at the top of this file,
+and since INN links with several external libraries if so configured (such
+as OpenSSL), should also be compatible with the licenses of those external
+libraries to be safe. Some portions of this distribution are covered by
+more restrictive licenses, but all of that code is completely stand-alone,
+either as a standalone script or as code that compiles into a separate
+executable.
+
+Please note that the files in the contrib directory are not properly part
+of INN and may be under widely varying licenses. Please see each file
+and/or its documentation for license information.
--- /dev/null
+File Name Description
+-------------------------------------------------------------------------------
+CONTRIBUTORS List of contributors
+HACKING Docs for INN coders and maintainers
+INSTALL INN installation instructions
+LICENSE Legal mumbo-jumbo
+MANIFEST This shipping list
+Makefile Top-level makefile
+Makefile.global.in Make variables for all Makefiles
+NEWS Changes since last version
+README Introduction to the INN package
+TODO The list of pending INN projects
+aclocal.m4 M4 macro for libtool
+authprogs The authentication programs (Directory)
+authprogs/Makefile Makefile for auth programs
+authprogs/auth_krb5.c Authenticator against Kerberos v5
+authprogs/auth_smb.c Authenticator against Samba servers
+authprogs/ckpasswd.c Check password
+authprogs/domain.c Get username from remote user's hostname
+authprogs/ident.c Get username from ident
+authprogs/libauth.c Library for talking to nnrpd
+authprogs/libauth.h Interface for libauth
+authprogs/radius.c Authenticator against RADIUS servers
+authprogs/smbval The smb auth libraries (Directory)
+authprogs/smbval/Makefile Libraries for smb auth
+authprogs/smbval/byteorder.h Libraries for smb auth
+authprogs/smbval/rfcnb-common.h Libraries for smb auth
+authprogs/smbval/rfcnb-error.h Libraries for smb auth
+authprogs/smbval/rfcnb-io.c Libraries for smb auth
+authprogs/smbval/rfcnb-io.h Libraries for smb auth
+authprogs/smbval/rfcnb-priv.h Libraries for smb auth
+authprogs/smbval/rfcnb-util.c Libraries for smb auth
+authprogs/smbval/rfcnb-util.h Libraries for smb auth
+authprogs/smbval/rfcnb.h Libraries for smb auth
+authprogs/smbval/session.c Libraries for smb auth
+authprogs/smbval/smbdes.c Libraries for smb auth
+authprogs/smbval/smbencrypt.c Libraries for smb auth
+authprogs/smbval/smblib-common.h Libraries for smb auth
+authprogs/smbval/smblib-priv.h Libraries for smb auth
+authprogs/smbval/smblib-util.c Libraries for smb auth
+authprogs/smbval/smblib.c Libraries for smb auth
+authprogs/smbval/smblib.h Libraries for smb auth
+authprogs/smbval/valid.c Libraries for smb auth
+authprogs/smbval/valid.h Libraries for smb auth
+backends Outgoing feed programs (Directory)
+backends/Makefile Makefile for outgoing feed programs
+backends/actmerge.in Merge two active files to stdout
+backends/actsync.c Poll remote(s) for active file & merge
+backends/actsyncd.in Daemon to run actsync periodically
+backends/archive.c Simple article archiver
+backends/batcher.c Make news batches
+backends/buffchan.c Buffered funnel to file splitter
+backends/crosspost.c Create links for crossposts (obselete)
+backends/cvtbatch.c Add fields to simple batchfile
+backends/filechan.c Split a funnel into separate files
+backends/inndf.c df used for innwatch
+backends/innxbatch.c Send batches using XBATCH to remote
+backends/innxmit.c Send articles to remote site
+backends/map.c Site name to filename mapping routines
+backends/map.h Headers for backends/map.c
+backends/mod-active.in Batch do active file modifications
+backends/news2mail.in News to mail gateway
+backends/ninpaths.c Path statistics accumulation program
+backends/nntpget.c Get articles from remote site
+backends/nntpsend.in Invoke all innxmit's at once
+backends/overchan.c Update news overview database
+backends/send-ihave.in Script to post ihave messages
+backends/send-nntp.in Shell script to call innxmit
+backends/send-uucp.in Script to call batcher
+backends/sendinpaths.in Send accumulated Path statistics
+backends/sendxbatches.in Shell wrapper around innxbatch
+backends/shlock.c Program to make lockfiles in scripts
+backends/shrinkfile.c Shrink file from beginning
+configure Script to configure INN
+configure.in Source file for configure
+contrib External contributions (Directory)
+contrib/Makefile Makefile for contrib programs
+contrib/README Contents of the contrib directory
+contrib/archivegz.in Compressing version of archive
+contrib/auth_pass.README README corresponding to auth_pass.c
+contrib/auth_pass.c Sample for use with AUTHINFO GENERIC
+contrib/backlogstat.in Analyze innfeed's backlog status
+contrib/backupfeed.in Suck down news via a reading connection
+contrib/cleannewsgroups.in Script to clean newsgroups file
+contrib/count_overview.pl Count overview entries
+contrib/delayer.in Delay data in a pipe, for innfeed
+contrib/expirectl.c Generate expire.ctl from template
+contrib/findreadgroups.in Track which groups are being read
+contrib/fixhist Script to clean history
+contrib/innconfcheck Merge inn.conf with its man page
+contrib/makeexpctl.in Create expire.ctl from read groups
+contrib/makestorconf.in Create storage.conf from read groups
+contrib/mkbuf Create cycbuff for HP-UX
+contrib/mlockfile.c Lock files into memory using mlock
+contrib/newsresp.c Measure responsiveness of remote server
+contrib/pullart.c Recover articles from cyclic buffers
+contrib/reset-cnfs.c Reset the state parts of a CNFS buffer
+contrib/respool.c Respool articles in the storage manager
+contrib/sample.init.script Example SysV-style init.d script
+contrib/showtoken.in Decode storage API tokens
+contrib/stathist.in Parse history statistics
+contrib/thdexpire.in Dynamic expire for timehash and timecaf
+contrib/tunefeed.in Tune a feed by comparing active files
+control Control message handling (Directory)
+control/Makefile Makefile for control programs
+control/controlbatch.in Batch program for controlchan
+control/controlchan.in Channel program for control messages
+control/docheckgroups.in Script to execute checkgroups
+control/gpgverify.in Verify control messages with GnuPG
+control/modules Modules for controlchan (Directory)
+control/modules/checkgroups.pl checkgroups controlchan handler
+control/modules/ihave.pl ihave controlchan handler
+control/modules/newgroup.pl newgroup controlchan handler
+control/modules/rmgroup.pl rmgroup controlchan handler
+control/modules/sendme.pl sendme controlchan handler
+control/modules/sendsys.pl sendsys controlchan handler
+control/modules/senduuname.pl senduuname controlchan handler
+control/modules/version.pl version controlchan handler
+control/perl-nocem.in NoCeM on spool implementation
+control/pgpverify.in Verify control messages with PGP
+control/signcontrol.in PGP control message signing program
+doc Documentation (Directory)
+doc/GPL The GNU General Public License 2.0
+doc/IPv6-info Nathan Lutchansky's IPv6 notes
+doc/Makefile Makefile for documentation
+doc/checklist Checklist for installing INN
+doc/compliance-nntp INN compliance with the NNTP standard
+doc/config-design Configuration parser design principles
+doc/config-semantics Configuration file semantics
+doc/config-syntax Configuration file syntax
+doc/external-auth readers.conf external interface notes
+doc/history Messages of historical significance
+doc/hook-perl Christophe Wolfhugel's Perl hook notes
+doc/hook-python Python hook notes
+doc/hook-tcl Bob Halley's TCL hook notes
+doc/man nroff documentation (Directory)
+doc/man/Makefile Makefile for nroff documentation
+doc/man/active.5 Manpage for active database
+doc/man/active.times.5 Manpage for active.times file
+doc/man/actsync.8 Manpage for active file synch program
+doc/man/actsyncd.8 Manpage for active synch daemon
+doc/man/archive.8 Manpage for archive backend
+doc/man/auth_krb5.8 Manpage for auth_krb5 authenticator
+doc/man/auth_smb.8 Manpage for auth_smb authenticator
+doc/man/batcher.8 Manpage for batcher
+doc/man/buffchan.8 Manpage for buffchan backend
+doc/man/buffindexed.conf.5 Manpage for buffindexed.conf config file
+doc/man/ckpasswd.8 Manpage for ckpasswd authenticator
+doc/man/clientlib.3 Manpage for C News library interface
+doc/man/cnfsheadconf.8 Manpage for cnfsheadconf
+doc/man/cnfsstat.8 Manpage for cnfsstat
+doc/man/control.ctl.5 Manpage for control.ctl config file
+doc/man/controlchan.8 Manpage for controlchan backend
+doc/man/convdate.1 Manpage for convdate utility
+doc/man/ctlinnd.8 Manpage for ctlinnd frontend
+doc/man/cvtbatch.8 Manpage for cvtbatch utility
+doc/man/cycbuff.conf.5 Manpage for cycbuff.conf config file
+doc/man/dbz.3 Manpage for DBZ database interface
+doc/man/distrib.pats.5 Manpage for distrib.pats config file
+doc/man/domain.8 Manpage for domain resolver
+doc/man/expire.8 Manpage for expire
+doc/man/expire.ctl.5 Manpage for expire.ctl config file
+doc/man/expireover.8 Manpage for expireover
+doc/man/expirerm.8 Manpage for expirerm
+doc/man/fastrm.1 Manpage for fastrm utility
+doc/man/filechan.8 Manpage for filechan backend
+doc/man/getlist.1 Manpage for getlist frontend
+doc/man/grephistory.1 Manpage for grephistory
+doc/man/history.5 Manpage for history database
+doc/man/ident.8 Manpage for ident resolver
+doc/man/incoming.conf.5 Manpage for incoming.conf config file
+doc/man/inews.1 Manpage for inews frontend
+doc/man/inn.conf.5 Manpage for inn.conf config file
+doc/man/inncheck.8 Manpage for inncheck utility
+doc/man/innconfval.1 Manpage for innconfval
+doc/man/innd.8 Manpage for innd server
+doc/man/inndcomm.3 Manpage for part of INN library
+doc/man/inndf.8 Manpage for inndf utility
+doc/man/inndstart.8 Manpage for inndstart
+doc/man/innfeed.1 Manpage for innfeed backend
+doc/man/innfeed.conf.5 Manpage for innfeed.conf config file
+doc/man/innmail.1 Manpage for innmail utility
+doc/man/innreport.8 Manpage for innreport
+doc/man/innstat.8 Manpage for innstat utility
+doc/man/innupgrade.8 Manpage for innupgrade utility
+doc/man/innwatch.8 Manpage for innwatch
+doc/man/innwatch.ctl.5 Manpage for innwatch.ctl config file
+doc/man/innxbatch.8 Manpage for innxbatch
+doc/man/innxmit.8 Manpage for innxmit
+doc/man/libauth.3 Manpage for authprogs utilty routines
+doc/man/libinn.3 Manpage for INN library routines
+doc/man/libinnhist.3 Manpage for history API library routines
+doc/man/libstorage.3 Manpage for storage API library routines
+doc/man/list.3 Manpage for list routines
+doc/man/mailpost.8 Manpage for mailpost frontend
+doc/man/makeactive.8 Manpage for makeactive
+doc/man/makedbz.8 Manpage for makedbz
+doc/man/makehistory.8 Manpage for makehistory
+doc/man/mod-active.8 Manpage for mod-active
+doc/man/moderators.5 Manpage for moderators config file
+doc/man/motd.news.5 Manpage for motd.news config file
+doc/man/news.daily.8 Manpage for news.daily
+doc/man/news2mail.8 Manpage for news2mail
+doc/man/newsfeeds.5 Manpage for newsfeeds config file
+doc/man/newslog.5 Manpage for log files
+doc/man/ninpaths.8 Manpage for ninpaths
+doc/man/nnrpd.8 Manpage for nnrpd daemon
+doc/man/nnrpd.track.5 Manpage for nnrpd.track config file
+doc/man/nntpget.1 Manpage for nntpget frontend
+doc/man/nntpsend.8 Manpage for nntpsend
+doc/man/nntpsend.ctl.5 Manpage for nntpsend.ctl config file
+doc/man/ovdb.5 Manpage for the ovdb storage module
+doc/man/ovdb_init.8 Manpage for ovdb_init
+doc/man/ovdb_monitor.8 Manpage for ovdb_monitor
+doc/man/ovdb_server.8 Manpage for ovdb_server
+doc/man/ovdb_stat.8 Manpage for ovdb_stat
+doc/man/overchan.8 Manpage for overchan backend
+doc/man/overview.fmt.5 Manpage for overview.fmt config file
+doc/man/parsedate.3 Manpage for parsedate library routine
+doc/man/passwd.nntp.5 Manpage for passwd.nntp config file
+doc/man/perl-nocem.8 Manpage for perl-nocem
+doc/man/pgpverify.1 Manpage for pgpverify
+doc/man/prunehistory.8 Manpage for prunehistory
+doc/man/pullnews.1 Manpage for pullnews
+doc/man/putman.sh Install a manpage
+doc/man/qio.3 Manpage for fast I/O file routines
+doc/man/radius.8 Manpage for radius authenticator
+doc/man/radius.conf.5 Manpage for radius.conf config file
+doc/man/rc.news.8 Manpage for rc.news
+doc/man/readers.conf.5 Manpage for readers.conf config file
+doc/man/rnews.1 Manpage for rnews frontend
+doc/man/sasl.conf.5 Manpage for sasl.conf config file
+doc/man/scanlogs.8 Manpage for scanlogs
+doc/man/send-nntp.8 Manpage for send-nntp and send-ihave
+doc/man/send-uucp.8 Manpage for send-uucp
+doc/man/sendinpaths.8 Manpage for sendinpaths
+doc/man/shlock.1 Manpage for shlock backend utility
+doc/man/shrinkfile.1 Manpage for shrinkfile utility
+doc/man/simpleftp.1 Manpage for simpleftp utility
+doc/man/sm.1 Manpage for sm
+doc/man/startinnfeed.1 Manpage for startinnfeed
+doc/man/storage.conf.5 Manpage for storage.conf config file
+doc/man/subscriptions.5 Manpage for subscriptions list
+doc/man/tally.control.8 Manpage for tally.control
+doc/man/tdx-util.8 Manpage for tdx-util
+doc/man/tst.3 Manpage for ternary search tree routines
+doc/man/uwildmat.3 Manpage for uwildmat library routine
+doc/man/writelog.8 Manpage for writelog
+doc/pod POD documentation (Directory)
+doc/pod/Makefile Maintainer rules for derived files
+doc/pod/active.pod Master file for active.5
+doc/pod/active.times.pod Master file for active.times.5
+doc/pod/auth_krb5.pod Master file for auth_krb5.8
+doc/pod/auth_smb.pod Master file for auth_smb.8
+doc/pod/checklist.pod Master file for doc/checklist
+doc/pod/ckpasswd.pod Master file for ckpasswd.8
+doc/pod/control.ctl.pod Master file for control.ctl.5
+doc/pod/convdate.pod Master file for convdate.1
+doc/pod/cycbuff.conf.pod Master file for cycbuff.conf.5
+doc/pod/distrib.pats.pod Master file for distrib.pats.5
+doc/pod/domain.pod Master file for domain.8
+doc/pod/expire.ctl.pod Master file for expire.ctl.5
+doc/pod/expireover.pod Master file for expireover.8
+doc/pod/external-auth.pod Master file for doc/external-auth
+doc/pod/fastrm.pod Master file for fastrm.1
+doc/pod/grephistory.pod Master file for grephistory.1
+doc/pod/hacking.pod Master file for HACKING
+doc/pod/hook-perl.pod Master file for doc/hook-perl
+doc/pod/hook-python.pod Master file for doc/hook-python
+doc/pod/ident.pod Master file for ident.8
+doc/pod/inews.pod Master file for inews.1
+doc/pod/inn.conf.pod Master file for inn.conf.5
+doc/pod/innconfval.pod Master file for innconfval.1
+doc/pod/innd.pod Master file for innd.8
+doc/pod/inndf.pod Master file for inndf.8
+doc/pod/inndstart.pod Master file for inndstart.8
+doc/pod/innmail.pod Master file for innmail.1
+doc/pod/innupgrade.pod Master file for innupgrade.8
+doc/pod/install.pod Master file for INSTALL
+doc/pod/libauth.pod Master file for libauth.3
+doc/pod/libinnhist.pod Master file for libinnhist.3
+doc/pod/list.pod Master file for list.3
+doc/pod/mailpost.pod Master file for mailpost.8
+doc/pod/makehistory.pod Master file for makehistory.8
+doc/pod/motd.news.pod Master file for motd.news.5
+doc/pod/news.pod Master file for NEWS
+doc/pod/newsfeeds.pod Master file for newsfeeds.5
+doc/pod/ninpaths.pod Master file for ninpaths.8
+doc/pod/nnrpd.pod Master file for nnrpd.8
+doc/pod/ovdb.pod Master file for ovdb.5
+doc/pod/ovdb_init.pod Master file for ovdb_init.8
+doc/pod/ovdb_monitor.pod Master file for ovdb_monitor.8
+doc/pod/ovdb_server.pod Master file for ovdb_server.8
+doc/pod/ovdb_stat.pod Master file for ovdb_stat.8
+doc/pod/passwd.nntp.pod Master file for passwd.nntp.5
+doc/pod/pullnews.pod Master file for pullnews.1
+doc/pod/qio.pod Master file for qio.3
+doc/pod/radius.conf.pod Master file for radius.conf.5
+doc/pod/radius.pod Master file for radius.8
+doc/pod/rc.news.pod Master file for rc.news.8
+doc/pod/readers.conf.pod Master file for readers.conf.5
+doc/pod/readme.pod Master file for README
+doc/pod/sasl.conf.pod Master file for sasl.conf.5
+doc/pod/sendinpaths.pod Master file for sendinpaths.8
+doc/pod/simpleftp.pod Master file for simpleftp.1
+doc/pod/sm.pod Master file for sm.1
+doc/pod/subscriptions.pod Master file for subscriptions.5
+doc/pod/tdx-util.pod Master file for tdx-util.8
+doc/pod/tst.pod Master file for tst.3
+doc/pod/uwildmat.pod Master file for uwildmat.3
+doc/sample-control Sample PGP-signed control message
+expire Expiration and recovery (Directory)
+expire/Makefile Makefile for expiration
+expire/convdate.c Date string conversions
+expire/expire.c Expire old articles and history lines
+expire/expireover.c Expire news overview data
+expire/expirerm.in Remove articles from expire -z
+expire/fastrm.c Remove list of files
+expire/grephistory.c Find entries in history database
+expire/makedbz.c Recover dbz
+expire/makehistory.c Recover the history database
+expire/prunehistory.c Prune file names from history file
+frontends inews, rnews, ctlinnd (Directory)
+frontends/Makefile Makefile for frontends
+frontends/cnfsheadconf.in Setup cycbuff header
+frontends/cnfsstat.in Show cycbuff status
+frontends/ctlinnd.c Send control request to innd
+frontends/decode.c Decode 7-bit data into binary file
+frontends/encode.c Encode binary file into 7-bit data
+frontends/feedone.c Test rig to feed a single NNTP article
+frontends/getlist.c Get active or other list from server
+frontends/inews.c Send article to local NNTP server
+frontends/innconfval.c Get an INN configuration parameter
+frontends/mailpost.in Mail to news gateway
+frontends/ovdb_init.c Prepare ovdb database for use
+frontends/ovdb_monitor.c Database maintainance for ovdb
+frontends/ovdb_server.c Helper server for ovdb
+frontends/ovdb_stat.c Display information from ovdb database
+frontends/pullnews.in Sucking news feeder
+frontends/rnews.c UUCP unbatcher
+frontends/scanspool.in Scan spool directory for trash
+frontends/sm.c Get article or overview data from token
+frontends/sys2nf.c Sys file to newsfeeds conversion aid
+history History library routines (Directory)
+history/Make.methods Makefile for history methods
+history/Makefile Makefile for history library
+history/buildconfig.in Construct history interface
+history/his.c History API glue implementation
+history/hisinterface.h History API interface
+history/hisv6 History v6 method (Directory)
+history/hisv6/hismethod.config hisbuildconfig definition
+history/hisv6/hisv6-private.h Private header file for hisv6
+history/hisv6/hisv6.c hisv6 history method
+history/hisv6/hisv6.h Header for hisv6 history
+include Header files (Directory)
+include/Makefile Makefile for header files
+include/acconfig.h Master file for config.h.in
+include/clibrary.h C library portability
+include/conffile.h Header file for reading *.conf files
+include/config.h.in Template configuration data
+include/dbz.h Header file for DBZ
+include/inn Installed header files (Directory)
+include/inn/buffer.h Header file for reusable counted buffers
+include/inn/confparse.h Header file for configuration parser
+include/inn/defines.h Portable defs for installed headers
+include/inn/hashtab.h Header file for generic hash table
+include/inn/history.h Header file for the history API
+include/inn/innconf.h Header file for the innconf struct
+include/inn/list.h Header file for list routines
+include/inn/md5.h Header file for MD5 digests
+include/inn/messages.h Header file for message functions
+include/inn/mmap.h Header file for mmap() functions
+include/inn/qio.h Header file for quick I/O package
+include/inn/sequence.h Header file for sequence space arithmetic
+include/inn/timer.h Header file for generic timers
+include/inn/tst.h Header file for ternary search tries
+include/inn/vector.h Header file for vectors of strings
+include/inn/wire.h Header file for wire-format functions
+include/inndcomm.h innd control channel commands
+include/innperl.h Header file for embedded Perl
+include/libinn.h INN library declarations
+include/nntp.h NNTP command and reply codes
+include/ov.h Header file for overview
+include/paths.h.in Paths to common programs and files
+include/portable Portability wrappers (Directory)
+include/portable/mmap.h Wrapper for <sys/mman.h>
+include/portable/setproctitle.h Portable setup for setproctitle
+include/portable/socket.h Wrapper for <sys/socket.h> and friends
+include/portable/time.h Wrapper for <time.h> and <sys/time.h>
+include/portable/wait.h Wrapper for <sys/wait.h>
+include/ppport.h Header file for Perl support
+include/storage.h Header file for storage API
+innd Server (Directory)
+innd/Makefile Makefile for server
+innd/art.c Process a received article
+innd/cc.c Control channel routines
+innd/chan.c I/O channel routines
+innd/icd.c Read and write the active file
+innd/innd.c Main and utility routines
+innd/innd.h Header file for server
+innd/inndstart.c Open the NNTP port, then exec innd
+innd/keywords.c Generate article keywords
+innd/lc.c Local NNTP channel routines
+innd/nc.c NNTP channel routines
+innd/newsfeeds.c Routines to parse the newsfeeds file
+innd/ng.c Newsgroup routines
+innd/perl.c Perl routines for innd
+innd/proc.c Process routines
+innd/python.c Python routines for innd
+innd/rc.c Remote channel accepting routines
+innd/site.c Site feeding routines
+innd/status.c Status routines for innd
+innd/tcl.c Bob Halley's Tcl hook
+innd/util.c Utility functions for innd
+innd/wip.c Work-in-progress routines for innd
+innfeed innfeed (Directory)
+innfeed/Makefile Makefile for innfeed
+innfeed/README Assorted notes
+innfeed/article.c Implementation of the Article class
+innfeed/article.h Public interface to Articles
+innfeed/buffer.c Implementation of the Buffer class
+innfeed/buffer.h Public interface to the Buffer class
+innfeed/config_l.c Lexer for the innfeed config file
+innfeed/configfile.h Header file for configfile.y
+innfeed/configfile.l Master file for config_l.c
+innfeed/configfile.y Parser for innfeed config file
+innfeed/connection.c Implementation of the Connection class
+innfeed/connection.h Public interface to the Connection class
+innfeed/endpoint.c Implementation of the EndPoint class
+innfeed/endpoint.h Public interface to the EndPoint class
+innfeed/host.c Implementation of the Host class
+innfeed/host.h Public interface to the Host class
+innfeed/imap_connection.c Implementation of IMAP Connection class
+innfeed/innfeed-convcfg.in Script to convert old innfeed.conf
+innfeed/innfeed.h Application configuration values
+innfeed/innlistener.c Implementation of the InnListener class
+innfeed/innlistener.h Public interface of InnListener class
+innfeed/main.c Main routines for the innfeed program
+innfeed/misc.c Miscelloneous routines for innfeed
+innfeed/misc.h Header file for misc.c
+innfeed/procbatch.in Script to process dropped articles
+innfeed/startinnfeed.c Start innfeed
+innfeed/tape.c Implementation of the Tape class
+innfeed/tape.h Public interface to the Tape class
+innfeed/testListener.pl Script to hand articles to innfeed
+lib INN library routines (Directory)
+lib/Makefile Makefile for library
+lib/buffer.c Reusable counted buffer
+lib/cleanfrom.c Clean out a From line
+lib/clientactive.c Client access to the active file
+lib/clientlib.c Replacement for C News library routine
+lib/concat.c Concatenate strings with dynamic memory
+lib/conffile.c Routines for reading *.conf files
+lib/confparse.c Generic configuration file parser
+lib/daemonize.c Code necessary to become a daemon
+lib/date.c Date parsing and conversion routines
+lib/dbz.c DBZ database library
+lib/defdist.c Determine default Distribution header
+lib/fdflags.c Set or clear file descriptor flags
+lib/fdlimit.c File descriptor limits
+lib/fseeko.c fseeko replacement
+lib/ftello.c ftello replacement
+lib/genid.c Generate a message ID
+lib/getfqdn.c Get FQDN of local host
+lib/getmodaddr.c Get a moderator's address
+lib/getpagesize.c getpagesize replacement
+lib/gettime.c Get time and timezone info
+lib/hash.c Create hash from message ID
+lib/hashtab.c Generic hash table
+lib/hstrerror.c Error reporting for resolver
+lib/inet_aton.c Extra source for inet_aton routine
+lib/inet_ntoa.c Convert inaddr to string (BSD)
+lib/innconf.c Parsing and manipulation of inn.conf
+lib/inndcomm.c Library routines to talk to innd
+lib/list.c List routines
+lib/localopen.c Open a local NNTP connection
+lib/lockfile.c Try to lock a file descriptor
+lib/makedir.c Make directory recursively
+lib/md5.c MD5 checksum calculation
+lib/memcmp.c memcmp replacement
+lib/messages.c Error reporting and debug output
+lib/mkstemp.c mkstemp replacement
+lib/mmap.c mmap manipulation routines
+lib/parsedate.y Date parsing
+lib/perl.c Perl hook support for nnrpd and innd
+lib/pread.c pread replacement
+lib/pwrite.c pwrite replacement
+lib/qio.c Quick I/O package
+lib/radix32.c Encode a number as a radix-32 string
+lib/readin.c Read file into memory
+lib/remopen.c Open a remote NNTP connection
+lib/reservedfd.c File descriptor reservation
+lib/resource.c Get process CPU usage
+lib/sendarticle.c Send an article, NNTP style
+lib/sendpass.c Send NNTP authentication
+lib/sequence.c Sequence space arithmetic routines
+lib/setenv.c setenv replacement
+lib/seteuid.c seteuid replacement
+lib/setproctitle.c setproctitle replacement
+lib/snprintf.c snprintf and vsnprintf replacement
+lib/sockaddr.c Manipulation of sockaddr structs
+lib/strcasecmp.c Case-insenstive string comparison (BSD)
+lib/strerror.c String representation of errno
+lib/strlcat.c strlcat replacement
+lib/strlcpy.c strlcpy replacement
+lib/strspn.c Skip bytes in a string (BSD)
+lib/strtok.c Split a string into tokens (BSD)
+lib/timer.c Generic profile timer
+lib/tst.c Ternary search trie implementation
+lib/uwildmat.c Pattern match routine
+lib/vector.c Manipulate vectors of strings
+lib/version.c INN version constants
+lib/wire.c Manipulate wire-format articles
+lib/xfopena.c Open a FILE in append mode
+lib/xmalloc.c Failsafe memory allocation wrapper
+lib/xsignal.c signal() wrapper using sigaction
+lib/xwrite.c write that handles partial transfers
+nnrpd Reader server (Directory)
+nnrpd/Makefile Makefile for nnrpd
+nnrpd/article.c Article-related routines
+nnrpd/cache.c MessageID cache routines
+nnrpd/cache.h MessageID cache interfaces
+nnrpd/commands.c Assorted server commands
+nnrpd/group.c Group-related routines
+nnrpd/line.c Long line-by-line reading routines
+nnrpd/list.c The LIST commands
+nnrpd/misc.c Miscellaneous support routines
+nnrpd/newnews.c The NEWNEWS command
+nnrpd/nnrpd.c Main and some utility routines
+nnrpd/nnrpd.h Header file for nnrpd
+nnrpd/perl.c Perl routines for nnrpd
+nnrpd/perm.c Reading readers.conf
+nnrpd/post.c Article processing and posting
+nnrpd/post.h Article data types
+nnrpd/python.c Python routines for nnrpd
+nnrpd/sasl_config.c Configuration for SASL
+nnrpd/sasl_config.h SASL data types
+nnrpd/tls.c Transport layer security
+nnrpd/tls.h Transport layer security data types
+nnrpd/track.c Track client behavior
+samples Prototype INN config files (Directory)
+samples/INN.py Stub Python functions
+samples/Makefile Makefile for samples
+samples/active.minimal Minimal starting active file
+samples/actsync.cfg Config file for actsync
+samples/actsync.ign Ignore file for actsync
+samples/buffindexed.conf Buffindexed overview config file
+samples/control.ctl Access control for control messages
+samples/cycbuff.conf Sample cycbuff.conf file
+samples/distrib.pats Default values for Distribution header
+samples/expire.ctl Expiration config file
+samples/filter.tcl Sample Tcl filter for innd
+samples/filter_innd.pl Sample Perl filter for innd
+samples/filter_innd.py Sample Python filter for innd
+samples/filter_nnrpd.pl Sample Perl filter for nnrpd
+samples/incoming.conf Permissions for incoming feeds
+samples/inn.conf.in General INN configuration
+samples/innfeed.conf Outgoing feed configuration
+samples/innreport.conf.in Log summary configuration
+samples/innwatch.ctl INN monitoring configuration
+samples/moderators Moderation submission addresses
+samples/motd.news Sample MOTD file
+samples/news2mail.cf news2mail config file
+samples/newsfeeds.in innd feed configuration
+samples/newsgroups.minimal Minimal starting newsgroups file
+samples/nnrpd.py Python hooks for nnrpd
+samples/nnrpd.track Reader tracking configuration
+samples/nnrpd_access.pl.in Sample nnrpd Perl access hooks
+samples/nnrpd_access.py Sample nnrpd Python access hooks
+samples/nnrpd_access_wrapper.pl.in Wrapper around old Perl access hooks
+samples/nnrpd_access_wrapper.py Wrapper around old Python access hooks
+samples/nnrpd_auth.pl.in Sample nnrpd Perl authorization hooks
+samples/nnrpd_auth.py Sample nnrpd Python authorization hooks
+samples/nnrpd_auth_wrapper.pl.in Wrapper around old Perl auth hooks
+samples/nnrpd_auth_wrapper.py Wrapper around old Python auth hooks
+samples/nnrpd_dynamic.py Sample nnrpd Python dynamic access hooks
+samples/nnrpd_dynamic_wrapper.py Wrapper around old Python dynamic hooks
+samples/nntpsend.ctl Outgoing nntpsend feed configuration
+samples/ovdb.conf Berkeley DB overview configuration
+samples/overview.fmt Format of news overview database
+samples/passwd.nntp Passwords for remote connections
+samples/radius.conf Sample config for RADIUS authentication
+samples/readers.conf Reader connection configuration
+samples/sasl.conf.in SASL configuration
+samples/startup.tcl Tcl startup code for innd
+samples/startup_innd.pl Perl startup code for innd
+samples/storage.conf Sample storage configuration
+samples/subscriptions Sample default subscriptions list
+scripts Various utilities (Directory)
+scripts/Makefile Makefile for script files
+scripts/inncheck.in Syntax-check INN config files
+scripts/innmail.in Perl front-end to sendmail
+scripts/innreport.in Script to analyze INN logs
+scripts/innreport_inn.pm Config file for innreport
+scripts/innshellvars.in Config parameters for shell scripts
+scripts/innshellvars.pl.in Config parameters for Perl scripts
+scripts/innshellvars.tcl.in Config parameters for Tcl scripts
+scripts/innstat.in Display INN status snapshot
+scripts/innupgrade.in Upgrade INN configuration files
+scripts/innwatch.in Throttle innd based on load and space
+scripts/news.daily.in Front-end script to run expire, etc.
+scripts/rc.news.in News boot script
+scripts/scanlogs.in Summarize log files
+scripts/simpleftp.in Rudimentary ftp client
+scripts/tally.control.in Count newgroup/rmgroup messages
+scripts/writelog.in Write a log entry or mail it
+site Site-local files (Directory)
+site/Makefile Makefile for site-local files
+site/getsafe.sh Safely get config file from samples
+storage Storage library (Directory)
+storage/Make.methods Makefile for storage methods
+storage/Makefile Makefile for storage library
+storage/buffindexed buffindexed overview method (Directory)
+storage/buffindexed/buffindexed.c buffindexed overview routines
+storage/buffindexed/buffindexed.h Header file for buffindexed overview
+storage/buffindexed/ovmethod.config buildconfig definition
+storage/buffindexed/ovmethod.mk Make rules for buffindexed overview
+storage/buildconfig.in Construct method interface
+storage/cnfs CNFS storage method (Directory)
+storage/cnfs/cnfs-private.h Private header file for CNFS
+storage/cnfs/cnfs.c CNFS storage routines
+storage/cnfs/cnfs.h Header file for CNFS
+storage/cnfs/method.config buildconfig definition
+storage/expire.c Overview-drive expire implementation
+storage/interface.c Storage API glue implementation
+storage/interface.h Storage API interface
+storage/ov.c Overview API glue implementation
+storage/ovdb ovdb overview method (Directory)
+storage/ovdb/ovdb-private.h Private header file for ovdb
+storage/ovdb/ovdb.c ovdb (Berkeley DB) overview method
+storage/ovdb/ovdb.h Header for ovdb (Berkeley DB) overview
+storage/ovdb/ovmethod.config buildconfig definition
+storage/overdata.c Overview data manipulation
+storage/ovinterface.h Overview API interface
+storage/timecaf timecaf storage method (Directory)
+storage/timecaf/README.CAF README the CAF file format
+storage/timecaf/caf.c CAF file implementation
+storage/timecaf/caf.h Header for CAF files
+storage/timecaf/method.config buildconfig definition
+storage/timecaf/timecaf.c timecaf storage routines
+storage/timecaf/timecaf.h Header file for timecaf
+storage/timehash timehash storage method (Directory)
+storage/timehash/method.config buildconfig definition
+storage/timehash/timehash.c timehash storage routines
+storage/timehash/timehash.h Header for timehash
+storage/tradindexed tradindexed overview method (Directory)
+storage/tradindexed/ovmethod.config buildconfig definition
+storage/tradindexed/ovmethod.mk Make rules for tradindexed overview
+storage/tradindexed/tdx-cache.c Data file cache handling for tradindexed
+storage/tradindexed/tdx-data.c Data file handling for tradindexed
+storage/tradindexed/tdx-group.c Group index handling for tradindexed
+storage/tradindexed/tdx-private.h Private header file for tradindexed
+storage/tradindexed/tdx-structure.h On disk layout of tradindexed files
+storage/tradindexed/tdx-util.c Utility program for tradindexed
+storage/tradindexed/tradindexed.c Interface code for the overview API
+storage/tradindexed/tradindexed.h Interface for tradindexed
+storage/tradspool tradspool storage method (Directory)
+storage/tradspool/README.tradspool Docs for tradspool storage method
+storage/tradspool/method.config buildconfig definition
+storage/tradspool/tradspool.c tradspool storage routines
+storage/tradspool/tradspool.h Header for tradspool
+storage/trash Trash storage method (Directory)
+storage/trash/method.config buildconfig definition
+storage/trash/trash.c Trash storage routines
+storage/trash/trash.h Header file for trash storage
+support Tools for building INN (Directory)
+support/config.guess Determine system type for libtool
+support/config.sub Canonicalize system type for libtool
+support/fixscript.in Interpreter path fixup script
+support/indent A mostly working wrapper around indent
+support/install-sh Installation utility
+support/ltmain.sh Source for libtool utility
+support/makedepend Generate dependencies for C files
+support/mkchangelog Generate ChangeLog from CVS
+support/mkmanifest Generate a list of files for the manifest
+support/mksnapshot Generate a snapshot of the tree
+support/mksystem Generate <inn/system.h> from config.h
+support/mkversion Generate <inn/version.h> with INN version
+tests Test suite for INN (Directory)
+tests/Makefile Makefile for test suite
+tests/TESTS List of tests to run
+tests/authprogs Test suite for auth programs (Directory)
+tests/authprogs/ckpasswd.t Tests for authprogs/ckpasswd
+tests/authprogs/domain.t Tests for authprogs/domain
+tests/authprogs/passwd Password data for ckpasswd tests
+tests/lib Test suite for libinn (Directory)
+tests/lib/articles Testing news articles (Directory)
+tests/lib/articles/no-body An article without a body
+tests/lib/articles/strange An article with CR and LF in headers
+tests/lib/articles/truncated An article truncated in the headers
+tests/lib/buffer-t.c Tests for lib/buffer.c
+tests/lib/concat-t.c Tests for lib/concat.c
+tests/lib/config Testing config files (Directory)
+tests/lib/config/errors Various config files with errors
+tests/lib/config/line-endings A config file with varied line endings
+tests/lib/config/no-newline A config file without an ending newline
+tests/lib/config/null A config file containing a nul character
+tests/lib/config/simple A simple config file
+tests/lib/config/valid Various valid config parameters
+tests/lib/config/warn-bool Invalid boolean parameters
+tests/lib/config/warn-int Invalid integer parameters
+tests/lib/config/warnings Various config files with warnings
+tests/lib/confparse-t.c Tests for lib/confparse.c
+tests/lib/date-t.c Tests for lib/date.c
+tests/lib/fakewrite.c Helper functions for xwrite tests
+tests/lib/hash-t.c Tests for lib/hash.c
+tests/lib/hashtab-t.c Tests for lib/hashtab.c
+tests/lib/hstrerror-t.c Tests for lib/hstrerror.c
+tests/lib/inet_aton-t.c Tests for lib/inet_aton.c
+tests/lib/inet_ntoa-t.c Tests for lib/inet_ntoa.c
+tests/lib/innconf-t.c Tests for lib/innconf.c
+tests/lib/list-t.c Tests for lib/list.c
+tests/lib/md5-t.c Tests for lib/md5.c
+tests/lib/memcmp-t.c Tests for lib/memcmp.c
+tests/lib/messages-t.c Tests for lib/messages.c
+tests/lib/mkstemp-t.c Tests for lib/mkstemp.c
+tests/lib/pread-t.c Tests for lib/pread.c
+tests/lib/pwrite-t.c Tests for lib/pwrite.c
+tests/lib/qio-t.c Tests for lib/qio.c
+tests/lib/setenv-t.c Tests for lib/setenv.c
+tests/lib/setenv.t Wrapper for setenv tests
+tests/lib/snprintf-t.c Tests for lib/snprintf.c
+tests/lib/strerror-t.c Tests for lib/strerror.c
+tests/lib/strlcat-t.c Tests for lib/strlcat.c
+tests/lib/strlcpy-t.c Tests for lib/strlcpy.c
+tests/lib/tst-t.c Tests for lib/tst.c
+tests/lib/uwildmat-t.c Tests for lib/uwildmat.c
+tests/lib/vector-t.c Tests for lib/vector.c
+tests/lib/wire-t.c Tests for lib/wire.c
+tests/lib/xmalloc.c Helper program for xmalloc tests
+tests/lib/xmalloc.t Tests for lib/xmalloc.c
+tests/lib/xwrite-t.c Tests for lib/xwrite.c
+tests/libtest.c Helper library for writing tests
+tests/libtest.h Interface to libtest
+tests/overview Test suite for overview (Directory)
+tests/overview/data Test overview data (Directory)
+tests/overview/data/basic Basic set of overview test data
+tests/overview/data/bogus Bad newsgroup name test data
+tests/overview/data/high-numbered High-numbered article test data
+tests/overview/data/reversed Same as basic, but in reverse order
+tests/overview/munge-data Support script to generate test data
+tests/overview/tradindexed-t.c Tests for storage/tradindexed/*
+tests/runtests.c The test suite driver program
+tests/util Test suite for utilities (Directory)
+tests/util/convdate.t Tests for expire/convdate
--- /dev/null
+## $Id: Makefile 7488 2005-12-25 00:26:08Z eagle $
+
+include Makefile.global
+
+## All installation directories except for $(PATHRUN), which has a
+## different mode than the rest.
+INSTDIRS = $(PATHNEWS) $(PATHBIN) $(PATHAUTH) $(PATHAUTHRESOLV) \
+ $(PATHAUTHPASSWD) $(PATHCONTROL) $(PATHFILTER) \
+ $(PATHRNEWS) $(PATHDB) $(PATHDOC) $(PATHETC) $(PATHLIB) \
+ $(PATHMAN) $(MAN1) $(MAN3) $(MAN5) $(MAN8) $(PATHSPOOL) \
+ $(PATHTMP) $(PATHARCHIVE) $(PATHARTICLES) $(PATHINCOMING) \
+ $(PATHINBAD) $(PATHTAPE) $(PATHOVERVIEW) $(PATHOUTGOING) \
+ $(PATHLOG) $(PATHLOG)/OLD $(PATHINCLUDE)
+
+## LIBDIRS are built before PROGDIRS, make update runs in all UPDATEDIRS,
+## and make install runs in all ALLDIRS. Nothing runs in test except the
+## test target itself and the clean targets. Currently, include is built
+## before anything else but nothing else runs in it except clean targets.
+LIBDIRS = include lib storage history
+PROGDIRS = innd nnrpd innfeed control expire frontends backends authprogs \
+ scripts
+UPDATEDIRS = $(LIBDIRS) $(PROGDIRS) doc
+ALLDIRS = $(UPDATEDIRS) samples site
+CLEANDIRS = $(ALLDIRS) include tests
+
+## The directory name and tar file to use when building a release.
+TARDIR = inn-$(VERSION)
+TARFILE = $(TARDIR).tar
+
+## The directory to use when building a snapshot.
+SNAPDIR = inn-$(SNAPSHOT)-$(SNAPDATE)
+
+## DISTDIRS gets all directories from the MANIFEST, and DISTFILES gets all
+## regular files. Anything not listed in the MANIFEST will not be included
+## in a distribution. These are arguments to sed.
+DISTDIRS = -e 1,2d -e '/(Directory)/!d' -e 's/ .*//' -e 's;^;$(TARDIR)/;'
+SNAPDIRS = -e 1,2d -e '/(Directory)/!d' -e 's/ .*//' -e 's;^;$(SNAPDIR)/;'
+DISTFILES = -e 1,2d -e '/(Directory)/d' -e 's/ .*//'
+
+
+## Major target -- build everything. Rather than just looping through
+## all the directories, use a set of parallel rules so that make -j can
+## work on more than one directory at a time.
+all: all-include all-libraries all-programs
+ cd doc && $(MAKE) all
+ cd samples && $(MAKE) all
+ cd site && $(MAKE) all
+
+all-libraries: all-lib all-storage all-history
+
+all-include: ; cd include && $(MAKE) all
+all-lib: all-include ; cd lib && $(MAKE) all
+all-storage: all-lib ; cd storage && $(MAKE) library
+all-history: all-storage ; cd history && $(MAKE) all
+
+all-programs: all-innd all-nnrpd all-innfeed all-control all-expire \
+ all-frontends all-backends all-authprogs all-scripts \
+ all-store-util
+
+all-authprogs: all-lib ; cd authprogs && $(MAKE) all
+all-backends: all-libraries ; cd backends && $(MAKE) all
+all-control: ; cd control && $(MAKE) all
+all-expire: all-libraries ; cd expire && $(MAKE) all
+all-frontends: all-libraries ; cd frontends && $(MAKE) all
+all-innd: all-libraries ; cd innd && $(MAKE) all
+all-innfeed: all-libraries ; cd innfeed && $(MAKE) all
+all-nnrpd: all-libraries ; cd nnrpd && $(MAKE) all
+all-scripts: ; cd scripts && $(MAKE) all
+all-store-util: all-libraries ; cd storage && $(MAKE) programs
+
+
+## If someone tries to run make before running configure, tell them to run
+## configure first.
+Makefile.global:
+ @echo 'Run ./configure before running make. See INSTALL for details.'
+ @exit 1
+
+
+## Installation rules. make install installs everything; make update only
+## updates the binaries, scripts, and documentation and leaves config
+## files alone.
+install: directories
+ @for D in $(ALLDIRS) ; do \
+ echo '' ; \
+ cd $$D && $(MAKE) install || exit 1 ; cd .. ; \
+ done
+ @echo ''
+ @echo 'If this is a first-time installation, a minimal active file and'
+ @echo 'history database have been installed. Do not forget to update'
+ @echo 'your cron entries and configure INN. See INSTALL for more'
+ @echo 'information.'
+ @echo ''
+
+directories:
+ @chmod +x support/install-sh
+ for D in $(INSTDIRS) ; do \
+ support/install-sh $(OWNER) -m 0755 -d $(D)$$D ; \
+ done
+ support/install-sh $(OWNER) -m 0750 -d $(D)$(PATHRUN)
+
+update:
+ @chmod +x support/install-sh
+ @for D in $(UPDATEDIRS) ; do \
+ echo '' ; \
+ cd $$D && $(MAKE) install || exit 1 ; cd .. ; \
+ done
+ $(PATHBIN)/innupgrade $(PATHETC)
+
+## Install a certificate for TLS/SSL support.
+cert:
+ $(SSLBIN)/openssl req -new -x509 -nodes \
+ -out $(PATHLIB)/cert.pem -days 366 \
+ -keyout $(PATHLIB)/key.pem
+ chown $(NEWSUSER) $(PATHLIB)/cert.pem
+ chgrp $(NEWSGROUP) $(PATHLIB)/cert.pem
+ chmod 640 $(PATHLIB)/cert.pem
+ chown $(NEWSUSER) $(PATHLIB)/key.pem
+ chgrp $(NEWSGROUP) $(PATHLIB)/key.pem
+ chmod 600 $(PATHLIB)/key.pem
+
+
+## Cleanup targets. clean deletes all compilation results but leaves the
+## configure results. distclean or clobber removes everything not part of
+## the distribution tarball. maintclean removes some additional files
+## created as part of the release process.
+clean:
+ @for D in $(CLEANDIRS) ; do \
+ echo '' ; \
+ cd $$D && $(MAKE) clean || exit 1 ; cd .. ; \
+ done
+
+clobber realclean distclean:
+ @for D in $(CLEANDIRS) ; do \
+ echo '' ; \
+ cd $$D && $(MAKE) $(FLAGS) clobber && cd .. ; \
+ done
+ @echo ''
+ rm -f LIST.* Makefile.global TAGS tags config.cache config.log
+ rm -f config.status libtool support/fixscript
+
+maintclean: distclean
+ rm -rf $(TARDIR)
+ rm -f CHANGES ChangeLog inn*.tar.gz
+
+
+## Other generic targets.
+depend tags ctags profiled:
+ @for D in $(ALLDIRS) ; do \
+ echo '' ; \
+ cd $$D && $(MAKE) $@ || exit 1 ; cd .. ; \
+ done
+
+TAGS etags:
+ etags */*.c */*.h */*/*.c */*/*.h
+
+
+## Run the test suite.
+check test tests:
+ cd tests && $(MAKE) test
+
+
+## For maintainers, build the entire source base with warnings enabled.
+warnings:
+ $(MAKE) COPT="$(WARNINGS) $(COPT)" all
+
+
+## Make a release. We create a release by recreating the directory
+## structure and then copying over all files listed in the MANIFEST. If it
+## isn't in the MANIFEST, it doesn't go into the release. We also update
+## the version information in Makefile.global.in to remove the prerelease
+## designation and update all timestamps to the date the release is made.
+release: ChangeLog
+ rm -rf $(TARDIR)
+ rm -f inn*.tar.gz
+ mkdir $(TARDIR)
+ for d in `sed $(DISTDIRS) MANIFEST` ; do mkdir -p $$d ; done
+ for f in `sed $(DISTFILES) MANIFEST` ; do \
+ cp $$f $(TARDIR)/$$f || exit 1 ; \
+ done
+ sed 's/= prerelease/=/' < Makefile.global.in \
+ > $(TARDIR)/Makefile.global.in
+ cp ChangeLog $(TARDIR)
+ find $(TARDIR) -type f -print | xargs touch -t `date +%m%d%H%M.%S`
+ tar cf $(TARFILE) $(TARDIR)
+ $(GZIP) -9 $(TARFILE)
+
+## Generate the ChangeLog using support/mkchangelog. This should only be
+## run by a maintainer since it depends on cvs log working and also
+## requires cvs2cl be available somewhere.
+ChangeLog:
+ $(PERL) support/mkchangelog
+
+
+## Check the MANIFEST against the files present in the current tree,
+## building a list with find and running diff between the lists.
+check-manifest:
+ sed -e 1,2d -e 's/ .*//' MANIFEST > LIST.manifest
+ $(PERL) support/mkmanifest > LIST.real
+ diff -u LIST.manifest LIST.real
+
+
+## Make a snapshot. This is like making a release, except that we don't do
+## the ChangeLog thing and we don't change the version number. We also
+## assume that SNAPSHOT has been set to the appropriate current branch.
+snapshot:
+ rm -rf $(SNAPDIR)
+ rm -f inn*.tar.gz
+ mkdir $(SNAPDIR)
+ set -e ; for d in `sed $(SNAPDIRS) MANIFEST` ; do mkdir -p $$d ; done
+ set -e ; for f in `sed $(DISTFILES) MANIFEST` ; do \
+ cp $$f $(SNAPDIR)/$$f ; \
+ done
+ cp README.snapshot $(SNAPDIR)/
+ sed 's/= prerelease/= $(SNAPDATE) snapshot/' \
+ Makefile.global.in > $(SNAPDIR)/Makefile.global.in
+ find $(SNAPDIR) -type f -print | xargs touch -t `date +%m%d%H%M.%S`
+ tar cf $(SNAPDIR).tar $(SNAPDIR)
+ $(GZIP) -9 $(SNAPDIR).tar
--- /dev/null
+## $Id: Makefile.global.in 7830 2008-05-14 18:57:39Z iulius $
+##
+## This file is meant to be the central Makefile that configure works with
+## and that all other Makefiles include. No Makefile other than this one
+## should have to be a configure substitution target.
+##
+## For installation paths, see the bottom of this file.
+
+## This version information is used to generate lib/version.c and is used
+## by INN for banner and local version identification. The version
+## identification string will be "$VERSION ($VERSION_EXTRA)", with the
+## parentheses omitted if $VERSION_EXTRA is empty (as it is for major
+## releases). If you make extensive local modifications to INN, you can
+## put your own version information in $VERSION_EXTRA. If it's set to
+## "CVS prerelease", the build time will be automatically included.
+
+VERSION = 2.4.5
+VERSION_EXTRA =
+
+## If you want to install INN relative to a root directory other than /,
+## set DESTDIR to the path to the root directory of the file system. This
+## won't affect any of the paths compiled into INN; it's used primarily
+## when building a software distribution, where software has to be
+## installed into some file system that will later be mounted as / on the
+## final system. DESTDIR should have a trailing slash, as the trailing
+## slash is not added automatically (in case someone wants to add a prefix
+## that isn't just a parent directory).
+
+DESTDIR =
+D = $(DESTDIR)
+
+## The absolute path to the top of the build directory, used to find the
+## libraries built as part of INN. Using relative paths confuses libtool
+## when linking the test suite.
+
+builddir = @builddir@
+
+## Basic compiler settings. COPT is the variable to override on the make
+## command line to change the optimization or add warning flags (such as
+## -Wall). LFS_* is for large file support. All of INN is built with the
+## large file support flags if provided.
+
+CC = @CC@
+COPT = @CFLAGS@
+GCFLAGS = $(COPT) -I$(top)/include @CPPFLAGS@ $(LFS_CFLAGS)
+
+BERKELEY_DB_CFLAGS = @BERKELEY_DB_CFLAGS@
+
+LDFLAGS = @LDFLAGS@ $(LFS_LDFLAGS) @BERKELEY_DB_LDFLAGS@
+LIBS = @LIBS@ $(LFS_LIBS)
+
+LFS_CFLAGS = @LFS_CFLAGS@
+LFS_LDFLAGS = @LFS_LDFLAGS@
+LFS_LIBS = @LFS_LIBS@
+
+PROF = -pg
+PROFSUFFIX = _p
+MAKEPROFILING = $(MAKE) COPT="$(COPT) $(PROF)" \
+ LDFLAGS="$(LDFLAGS) $(PROF)" \
+ LIBSUFFIX=$(PROFSUFFIX)
+
+## Used to support non-recursive make. This variable is set to the necessary
+## options to the compiler to create an object file in a subdirectory. It
+## should be used instead of -c -o $@ $< and may be replaced with code that
+## calls mv, if the compiler doesn't support -c with -o.
+
+CCOUTPUT = @CCOUTPUT@
+
+## Warnings to use with gcc. Default to including all of the generally
+## useful warnings unless there's something that makes them unsuitable. In
+## particular, the following warnings are *not* included:
+##
+## -ansi Requires messing with feature test macros.
+## -pedantic Too much noise from embedded Perl.
+## -Wtraditional We assume ANSI C, so these aren't interesting.
+## -Wshadow Names like log or index are too convenient.
+## -Wcast-qual Used for a while, but some casts are unavoidable.
+## -Wconversion Too much unsigned to signed noise.
+## -Wredundant-decls Too much noise from system headers.
+##
+## Some may be worth looking at again once a released version of gcc doesn't
+## warn on system headers. The warnings below are in the same order as
+## they're listed in the gcc manual.
+##
+## Add -g because when building with warnings one generally also wants the
+## debugging information, and add -O because gcc won't find some warnings
+## without optimization turned on. Add -DDEBUG=1 so that we'll also
+## compile all debugging code and check it as well.
+
+WARNINGS = -g -O -DDEBUG=1 -Wall -W -Wendif-labels -Wpointer-arith \
+ -Wbad-function-cast -Wcast-align -Wwrite-strings \
+ -Wstrict-prototypes -Wmissing-prototypes -Wnested-externs
+
+## libtool support. Note that INN does not use Automake (and that
+## retrofitting Automake is likely more work than it's worth), so
+## libtool-aware rules have to be written by hand.
+
+LIBTOOL = @LIBTOOL@
+LIBTOOLCC = @LIBTOOLCC@
+LIBTOOLLD = @LIBTOOLLD@
+EXTOBJ = @EXTOBJ@
+EXTLIB = @EXTLIB@
+
+LIBCC = $(LIBTOOLCC) $(CC)
+LIBLD = $(LIBTOOLLD) $(CC)
+
+## INN libraries. Nearly all INN programs are linked with libinn, and any
+## INN program that reads from or writes to article storage or overview is
+## linked against libstorage. EXTSTORAGELIBS is for external libraries
+## needed by libstorage.
+
+LIBINN = $(builddir)/lib/libinn$(LIBSUFFIX).$(EXTLIB)
+LIBHIST = $(builddir)/history/libinnhist$(LIBSUFFIX).$(EXTLIB)
+LIBSTORAGE = $(builddir)/storage/libstorage$(LIBSUFFIX).$(EXTLIB)
+EXTSTORAGELIBS = @BERKELEY_DB_LIB@
+
+DBMINC = @DBM_INC@
+DBMLIB = @DBM_LIB@
+
+CRYPTLIB = @CRYPT_LIB@
+PAMLIB = @PAM_LIB@
+REGEXLIB = @REGEX_LIB@
+SHADOWLIB = @SHADOW_LIB@
+
+## Embedding support. Additional flags and libraries used when compiling
+## or linking portions of INN that support embedded interpretors, set by
+## configure based on what interpretor embeddings are selected.
+
+PERLLIB = $(builddir)/lib/perl$(LIBSUFFIX).o @PERL_LIB@
+PERLINC = @PERL_INC@
+
+PYTHONLIB = @PYTHON_LIB@
+PYTHONINC = @PYTHON_INC@
+
+## OpenSSL support. Additional flags and libraries used when compiling or
+## linking code that contains OpenSSL support, and the path to the OpenSSL
+## binaries.
+
+SSLLIB = @SSL_LIB@
+SSLINC = @SSL_INC@
+SSLBIN = @SSL_BIN@
+
+## SASL support. Additional flags and libraries used when compiling or
+## linking code that contains SASL support.
+
+SASLLIB = @SASL_LIB@
+SASLINC = @SASL_INC@
+
+## Kerberos support. Additional flags and libraries used when compiling or
+## linking code that contains Kerberos support. If Kerberos libraries were
+## compiled, KRB5_AUTH is also set to the name of the Kerberos v5
+## authenticator that should be compiled and installed.
+KRB5LIB = @KRB5_LIB@
+KRB5INC = @KRB5_INC@
+KRB5_AUTH = @KRB5_AUTH@
+
+## Missing functions. If non-empty, configure detected that your system
+## was missing some standard functions, and INN will be providing its own
+## replacements from the lib directory.
+
+LIBOBJS = @LIBOBJS@
+
+## Paths to various standard programs used during the build process.
+## Changes to this file will *not* be reflected in the paths compiled into
+## programs; these paths are only used during the build process and for
+## some autogenerated scripts. To change the compiled paths, see
+## include/paths.h. You may also need to modify scripts/innshellvars*.
+
+AWK = @_PATH_AWK@
+COMPRESS = @COMPRESS@
+CTAGS = @CTAGS@
+GZIP = @GZIP@
+LEX = @LEX@
+PERL = @_PATH_PERL@
+RANLIB = @RANLIB@
+YACC = @YACC@
+UNCOMPRESS = @UNCOMPRESS@
+
+FIXSCRIPT = $(top)/support/fixscript
+
+PERLWHOAMI = $(PERL) -e 'print scalar getpwuid($$>), "\n"'
+WHOAMI = (whoami || /usr/ucb/whoami || $(PERLWHOAMI)) 2> /dev/null
+
+## Paths and command lines for programs used only by the maintainers to
+## regenerate dependencies, documentation, and the like.
+
+MAKEDEPEND = $(top)/support/makedepend
+
+POD2MAN = pod2man -c 'InterNetNews Documentation' -r 'INN $(VERSION)'
+POD2TEXT = pod2text -s -l
+
+## Installation directories. If any of the below are incorrect, don't just
+## edit this file; these directories are substituted in all over the source
+## tree by configure. Instead, re-run configure with the correct
+## command-line flags to set the directories. Run configure --help for a
+## list of supported flags.
+
+prefix = @prefix@
+
+PATHNEWS = $(prefix)
+PATHBIN = $(PATHNEWS)/bin
+PATHDOC = @DOCDIR@
+PATHETC = @ETCDIR@
+PATHMAN = @mandir@
+PATHINCLUDE = @includedir@
+PATHLIB = @LIBDIR@
+PATHCONTROL = @CONTROLDIR@
+PATHFILTER = @FILTERDIR@
+PATHRUN = @RUNDIR@
+PATHLOG = @LOGDIR@
+PATHLOGOLD = $(PATHLOG)/OLD
+PATHDB = @DBDIR@
+PATHSPOOL = @SPOOLDIR@
+PATHTMP = @tmpdir@
+PATHAUTH = $(PATHBIN)/auth
+PATHAUTHRESOLV = $(PATHAUTH)/resolv
+PATHAUTHPASSWD = $(PATHAUTH)/passwd
+PATHRNEWS = $(PATHBIN)/rnews.libexec
+PATHARCHIVE = $(PATHSPOOL)/archive
+PATHARTICLES = $(PATHSPOOL)/articles
+PATHINCOMING = $(PATHSPOOL)/incoming
+PATHTAPE = $(PATHSPOOL)/innfeed
+PATHINBAD = $(PATHINCOMING)/bad
+PATHOVERVIEW = $(PATHSPOOL)/overview
+PATHOUTGOING = $(PATHSPOOL)/outgoing
+
+MAN1 = @mandir@/man1
+MAN3 = @mandir@/man3
+MAN5 = @mandir@/man5
+MAN8 = @mandir@/man8
+
+## Installation settings. The file installation modes are determined by
+## configure; inews and rnews are special and have configure flags to
+## control how they're installed. See INSTALL for more information.
+
+NEWSUSER = @NEWSUSER@
+NEWSGROUP = @NEWSGRP@
+
+INEWSMODE = @INEWSMODE@
+RNEWSMODE = @RNEWSMODE@
+FILEMODE = @FILEMODE@
+
+OWNER = -o $(NEWSUSER) -g $(NEWSGROUP)
+ROWNER = -o $(NEWSUSER) -g @RNEWSGRP@
+
+INSTALL = $(top)/support/install-sh -c
+
+## Installation commands. These commands are used by the installation rules
+## of each separate subdirectory. The naming scheme is as follows: the first
+## two characters are CP (indicating a plain copy) or LI (indicating an
+## installation that goes through libtool). After an underscore is a
+## one-character indicator of the file type (R for a regular file, X for an
+## executable, S for a setuid root executable) and then PUB for a
+## world-readable/world-executable file or PRI for a group-readable/
+## group-executable file (only the news group).
+##
+## inews and rnews have their own special installation rules, as do database
+## files like active and newsgroups that should have the same permissions as
+## article files.
+
+LI_SPRI = $(LIBTOOL) $(INSTALL) -o root -g $(NEWSGROUP) -m 4550 -B .OLD
+LI_XPRI = $(LIBTOOL) $(INSTALL) $(OWNER) -m 0550 -B .OLD
+LI_XPUB = $(LIBTOOL) $(INSTALL) $(OWNER) -m 0555 -B .OLD
+
+LI_INEWS = $(LIBTOOL) $(INSTALL) $(OWNER) -m $(INEWSMODE) -B .OLD
+LI_RNEWS = $(LIBTOOL) $(INSTALL) $(ROWNER) -m $(RNEWSMODE) -B .OLD
+
+CP_RPRI = $(INSTALL) $(OWNER) -m 0640 -B .OLD
+CP_RPUB = $(INSTALL) $(OWNER) -m 0644 -B .OLD
+CP_XPRI = $(INSTALL) $(OWNER) -m 0550 -B .OLD
+CP_XPUB = $(INSTALL) $(OWNER) -m 0555 -B .OLD
+
+CP_DATA = $(INSTALL) $(OWNER) -m $(FILEMODE) -B .OLD
+
+## How to install man pages. Pick one of SOURCE, BSD4.4, NROFF-PACK, or
+## NROFF-PACK-SCO. Used by doc/man/putman.sh; read that script for more
+## information on what it does.
+
+MANPAGESTYLE = SOURCE
+
+## Some additional definitions needed by some versions of make, to ensure a
+## consistant set of variables are available.
+
+SHELL = /bin/sh
+
+@SET_MAKE@
--- /dev/null
+Changes in 2.4.5
+
+ * Fixed the "alarm signal" around "SSL_read" in nnrpd: it allows a
+ proper disconnection of news clients which were previously hanging
+ when posting an article through a SSL connection. Moreover, the
+ *clienttimeout* parameter now works on SSL connections. Thanks to
+ Matija Nalis for the patch.
+
+ * SO_KEEPALIVE is now implemented for SSL TCP connections on systems
+ which support it, allowing system detection and closing the dead TCP
+ SSL connections automatically after system-specified time. Thanks to
+ Matija Nalis for the patch.
+
+ * Fixed a segmentation fault when an article of a size greater than
+ remaining stack is retrieved via SSL. Thanks to Chris Caputo for this
+ patch.
+
+ * Fixed a few segfaults and bugs which affected both Python innd and
+ nnrpd hooks. They no longer check the existence of methods not used
+ by the hooked script. An issue with Python exception handling was
+ also fixed, as well as a segfault fixed by Russ Allbery which happened
+ whenever one closes and then reopens Python in the same process.
+ Julien Elie also fixed a bug when reloading Python filters (they were
+ not always correctly reloaded) and a segfault when generating access
+ groups with embedded Python filters for nnrpd. Many thanks to David
+ Hlacik for its bug reports.
+
+ * The nnrpd.py stub file in order to test Python nnrpd hooks, as
+ mentioned in their documentation, is now installed; only INN.py was
+ previously installed in *pathfilter*. Also fixed a bug in INN.py and
+ add missing methods to it.
+
+ * Fixed a long-standing bug in innreport which prevented it from
+ correctly reporting nnrpd and innfeed log messages.
+
+ * Fixed a hang in Perl hooks on (at least) HP/PA since Perl 5.10.
+
+ * Fixed a compilation problem on some platforms because of AF_INET6
+ which was not inside a HAVE_INET6 block in innfeed.
+
+ * Fixed a bug in innfeed which contained thrice the same IPs for each
+ peer; it unnecessarily slowed the peer IP rotation for innfeed.
+ Thanks, D. Stussy, for having seen that. Miquel van Smoorenburg
+ provided the patch.
+
+ * A new *heavily* improved version of pullnews is shipped with this INN
+ release. This new version is provided by Geraint Edwards. He added
+ no more than 16 flags, fixed some bugs and integrated the backupfeed
+ contrib script by Kai Henningsen, adding again 6 other flags. A
+ long-standing but very minor bug in the -g option was especially fixed
+ and items from the to-do list implemented. Many thanks again to
+ Geraint Edwards.
+
+ * New headers are accessible through Perl and Python innd filtering
+ hooks. You will find the exact list in the INN Python Filtering and
+ Authentication Hooks documentation (doc/hook-python) and in Python
+ samples. Thanks to Matija Nalis for this addition of new useful
+ headers.
+
+ * New samples for Python nnrpd hooks are shipped with INN:
+ nnrpd_access.py for access control and nnrpd_dynamic.py for dynamic
+ access control. The nnrpd_auth.py script is now only used for
+ authorization control. See the readers.conf man page for more
+ information (especially the *python_auth*, *python_access* and
+ *python_dynamic* parameters). The documention about INN Python
+ Filtering and Authentication Hooks has also been improved by Julien
+ Elie.
+
+Changes in 2.4.4
+
+ * Fixed incomplete checking of packet sizes in the ctlinnd interface in
+ the no-Unix-domain-sockets case. This is a potential buffer overflow
+ in dead code since basically all systems INN builds on support Unix
+ domain sockets these days. Also track the buffer size more correctly
+ in the client side of this interface for the Unix domain socket case.
+
+ * Group blocks in incoming.conf are now correctly parsed and no longer
+ cause segfaults when loading this file.
+
+ * Fixed a problem with innfeed continuously segfaulting on amd64
+ hardware (and possibly on lots of 64-bit platforms). Many thanks to
+ Ollivier Robert for his patch and also to Kai Gallasch for having
+ reported the problem and provided the FreeBSD server to debug it.
+
+ * scanlogs now rotates innfeed's log file, which prevents innfeed from
+ silently dying when its log file reaches 2 GB.
+
+ * Perl 5.10 support has been added to INN thanks to Jakub Bogusz.
+
+ * Some news clients hang when posting an article through a SSL
+ connection: it seems that nnrpd's SSL routines make it wrongly wait
+ for data completion. In order to fix the problem, the select() wait
+ is now just bypassed. However, the IDLE timer stat is currently not
+ collected for such connections. Thanks to Kachun Lee for this
+ workaround.
+
+ * Fixed a bug in the display of the used compressor ("cunbatch" was used
+ if arguments were passed to gzip or bzip2).
+
+ * Fixed a bug in mailpost and pullnews which prevented useful error
+ messages to be seen. Also add the -x flag to pullnews in order to
+ insert Xref: headers in articles which lack one.
+
+ * If compiling with Berkeley DB, use its ndbm compatibility layer for
+ ckpasswd in preference to searching for a traditional dbm library.
+ INN also supports Berkeley DB 4.4, 4.5 and 4.6 thanks to Marco d'Itri.
+
+ * ovdb_init now properly closes stdin/out/err when it becomes a daemon.
+ The issue was reported by Viktor Pilpenok and fixed by Marco d'Itri.
+
+ * Added support for Diablo quickhash and hashfeed algorithms. It allows
+ to distribute the messages among several peers (new Q flag for
+ newsfeeds). Thanks to Miquel van Smoorenburg for this implementation
+ in INN.
+
+ * innd now listen on separate sockets for IPv4 and IPv6 connections if
+ the IPV6_V6ONLY socket option is available. There might also be
+ operating systems that still have separate IPv4 and IPv6 TCP
+ implementations, and advanced features like TCP SACK might not be
+ available on v6 sockets. Thanks to Miquel van Smoorenburg for this
+ patch.
+
+ * The two configuration options *bindaddress* and *bindaddress6* can now
+ be set on a per-peer basis for innfeed. Setting *bindaddress6* to
+ "none" tells innfeed to never attempt an IPv6 connection to that host.
+ Thanks to Miquel van Smoorenburg for this patch.
+
+ * Added a *nnrpdflags* parameter to inn.conf (modeled on the concept of
+ *innflags*) to permit passing of command line arguments to instances
+ of nnrpd spawned from innd.
+
+ * A new inn.conf parameter called *pathcluster* has been added: it
+ allows to append a common name to the Path: header on all incoming
+ articles. *pathhost* and *pathalias* (if set) are still appended to
+ the path as usual, but *pathcluster* is always appended as the last
+ element (e.g. on the leftmost side of the Path: header). Thanks to
+ Miquel van Smoorenburg for this feature.
+
+ * simpleftp has been rewritten to use "Net::FTP". Indeed, ftp.pl is no
+ longer shipped with Perl 5 and the script did not work.
+
+ * perl-nocem will now check for a timeout and re-open the socket if
+ required. Additionally, perl-nocem will switch to cancel_ctlinnd in
+ case cancel_nntp fails after sending the Message-ID. Thanks to
+ Christoph Biedl for the patch. A more detailed documentation has also
+ been written for perl-nocem(8).
+
+ * The RADIUS configuration is now wrapped in a "server {}" block in
+ radius.conf.
+
+ * Checkgroups when there is nothing to change no longer result in
+ sending a blank mail to administrators. Besides, no mail is sent by
+ controlchan for the creation of a newsgroup when the action is "no
+ change".
+
+ * Checkgroups are now properly propagated even though the news server
+ does not carry the groups they are posted to.
+
+ * controlchan and docheckgroups now handle wire format messages so that
+ articles from the spool can be directly fed to them.
+
+ * Newgroup control messages for existing groups now change their
+ description. If a mail is sent to administrators, it reminds them to
+ update their newsgroups file. It also warns when there are missing or
+ obsolete descriptions. Furthermore, the newsgroups file is now
+ written prettier (from one to three tabulations between the name of
+ the group and its short description) and to.* groups cannot be
+ created.
+
+ * The sample control.ctl file has been extensively updated.
+
+ * Fixed empty LISTGROUP replies which were not terminated. Thanks to
+ David Canzi for the patch.
+
+ * In response to a LIST [file] command, if the file does not exist, we
+ assume it is not maintained and return 503 instead of 215 and an empty
+ file. Moreover, capability to LIST ACTIVE.TIMES for a wildmat pattern
+ as its third argument has been added in order to select wanted
+ newsgroups.
+
+ * inews now tries to authenticate if it does not receive a 200 return
+ code after MODE READER. Indeed, it might be able to post even with a
+ 201 return code and also with another codes like 440 or 480.
+
+ * If creating a new history file, set the ownership and mode
+ appropriately. inncheck also expects fewer things to be private to
+ the news user. Most of the configuration files will never contain
+ private information like passwords.
+
+ * Other minor bug fixes and documentation improvements.
+
+Changes in 2.4.3
+
+ * Previous versions of INN had an optimization for handling XHDR
+ Newsgroups that used the Xref: header from overview. While this does
+ make the command much faster, it doesn't produce accurate results and
+ breaks the NNTP protocol, so this optimization has been removed.
+
+ * Fixed a bug in innd that allowed it to accept articles with duplicated
+ headers if the header occurred an odd number of times. Modified the
+ programs for rebuilding overview to use the last Xref: header if there
+ are multiple ones to avoid problems with spools that contain such
+ invalid articles.
+
+ * Fixed yet another problem with verifying that a user has permissions
+ to approve posts to a moderated group. Thanks, Jens Schlegel.
+
+ * Increase the send and receive buffer on the Unix domain socket used by
+ ctlinnd. This should allow longer replies (particularly for innstat)
+ on platforms with very low default Unix domain socket buffer sizes.
+
+ * rnews's handling of articles with nul characters, NNTP errors, header
+ problems, and deferrals has been significantly improved.
+
+ * Thomas Parmelan added support to send-uucp for specifying the funnel
+ or exploder site to flush for feeds managed through one and fixed a
+ problem with picking up old stranded work files.
+
+ * Many other more minor bug fixes, optimization improvements, and
+ documentation fixes.
+
+Changes in 2.4.2
+
+ * INN is now licensed under a less restrictive license (about as
+ minimally restrictive as possible shy of public domain), and the
+ clause similar to the old BSD advertising clause has been dropped.
+
+ * "make install" and "make update" now always install the newly built
+ binaries, rather than only installing them if the modification times
+ are newer. This is the behavior that people expect. "make install"
+ now also automatically builds a new (empty) history database if one
+ doesn't already exist.
+
+ * The embedded Tcl filter code has been disabled (and will be removed
+ entirely in the next major release of INN). It hasn't worked for some
+ time and causes innd crashes if compiled in (even if not used). If
+ someone wants to step forward and maintain it, I recommend starting
+ from scratch and emulating the Perl and Python filters.
+
+ * ctlinnd should now successfully handle messages from INN up to the
+ maximum allowable packet size in the protocol, fixing problems sites
+ with many active peers were having with innstat output.
+
+ * Overview generation has been fixed in both makehistory and innd to
+ follow the rules in the latest NNTP draft rather than just replacing
+ special characters with spaces. This means that the unfolding of
+ folded header lines will not introduce additional, incorrect
+ whitespace in the overview data.
+
+ * nnrpd now uniformly responds with a 480 or 502 status code to attempts
+ to read a newsgroup to which the user does not have access, depending
+ on whether the user has authenticated. Previously, it returned a 411
+ status code, claiming the group didn't exist, which confuses the
+ reactive authentication capability of news readers.
+
+ * If a user is not authorized to approve articles (using the "A"
+ *access* control in readers.conf), articles that include Approved:
+ headers will be rejected even if posted to unmoderated groups. Some
+ other site may consider that group to be moderated.
+
+ * The configuration parser used for readers.conf and others now
+ correctly handles "#" inside quoted strings and is more robust against
+ unmatched double quotes.
+
+ * Messages mailed to moderators had two spaces after the colons in the
+ headers, rather than one. This bug has been fixed.
+
+ * A bug that could cause heap corruption and random crashes in innd if
+ INN were compiled with Python support has been fixed.
+
+ * Some problems with innd's tracking of article size and enforcement of
+ the configured maximum article size have been fixed.
+
+ * pgpverify will now correctly verify signatures generated by GnuPG and
+ better supports GnuPG as the PGP implementation.
+
+ * INN's code should now be more 64-bit clean in its handling of size_t,
+ pointer differences, and casting of pointers, correcting problems that
+ showed up on 64-bit platforms like AMD64.
+
+ * Improved the error reporting in the history database code, in inews,
+ in controlchan, and in expire.
+
+ * Many other, more minor bugs have also been fixed.
+
+Changes in 2.4.1
+
+ * SECURITY: Handle the special filing of control messages into per-type
+ newsgroups more robustly. This closes a potentially exploitable
+ buffer overflow. Thanks to Dan Riley for his excellent bug report.
+
+ * Fixed article handling in innd so that articles without a Path: header
+ (arising from peers sending malformatted articles or injecting
+ malformatted articles through rnews) would not cause innd to crash.
+ (This was not exploitable.)
+
+ * Fixed a serious bug in XPAT handling, thanks to Tommy van Leeuwen.
+
+ * "configure" now looks for sendmail only in /usr/sbin and /usr/lib, not
+ on the user's path. This should reduce the need for --with-sendmail
+ if your preferred sendmail is in a standard location.
+
+ * The robustness of the tradindexed overview method has been further
+ increased, handling more edge cases arising from corrupted databases
+ and oddly-named newsgroups.
+
+ * innd now never decreases the high water mark of a newsgroup when
+ renumbering, which should help ameliorate overview and active file
+ synchronization problems.
+
+ * Do not close and reopen the history file on ctlinnd reload when the
+ server is paused or throttled. This was breaking ctlinnd reload all
+ during a server pause.
+
+ * Various minor portability and compilation issues fixed. Substantial
+ numbers of compiler warnings have been cleaned up, thanks largely to
+ work by Ilya Kovalenko.
+
+ * Multiple other more minor bugs have been fixed.
+
+ * Documentation and man pages have been clarified and updated.
+
+Upgrading from 2.3 to 2.4
+
+ The inn.conf parser has changed between INN 2.3 and 2.4. Due to that
+ change, options in inn.conf that contain whitespace or a few other
+ special characters must be quoted with double quotes, and empty
+ parameters (parameters with no value) are not allowed. INN 2.4 comes
+ with a script, innupgrade, run automatically during "make update", that
+ will attempt to fix any problems that it finds with your inn.conf file,
+ saving the original as inn.conf.OLD.
+
+ This change is the beginning of standardization of parsing and syntax
+ across all of INN's configuration files.
+
+ The history subsystem now has a standard API that allows other backends
+ to be used. Because of this, you now need to specify the history method
+ in inn.conf. Adding:
+
+ hismethod: hisv6
+
+ will tell INN to use the same history backend as was used in previous
+ versions. innupgrade should take care of this for you.
+
+ ovdb is known to have some locking and timing issues related to how
+ nnrpd shuts down (or fails to shut down) the overview databases. If you
+ have stability problems with ovdb, try setting *readserver* to "true" in
+ ovdb.conf. This will funnel all ovdb reads through a single process
+ with a cleaner interface to the underlying Berkeley DB database.
+
+ If you use Perl authentication for nnrpd (if *nnrpdperlauth* in inn.conf
+ is "true"), there have been major changes. See "Changes to Perl
+ Authentication Support for nnrpd" in doc/hook-perl for details.
+
+ Similarly, if you use Python authentication for nnrpd (if
+ *nnrpdpythonauth* in inn.conf is "true"), there have been major changes.
+ See "Changes to Python Authentication and Access Control Support for
+ nnrpd" in doc/hook-python for details.
+
+ If you use send-uucp, it has been completely rewritten and now takes a
+ configuration file to specify its behavior. See its man page for more
+ information. If you use sendbatch, it is no longer included in INN
+ since the new send-uucp can handle all of the same functionality.
+
+ The wildmat API has been renamed (to uwildmat and friends; see
+ uwildmat(3) for the interfaces) to distinguish it from Rich $alz's
+ original version, since it now supports UTF-8. This may require changes
+ in other software packages that link against INN's libraries.
+
+ If you are upgrading from a version prior to INN 2.3, see "Upgrading
+ from 2.2 to 2.3".
+
+Changes in 2.4.0
+
+ * IPv6 support has been added, disabled by default. If you have IPv6
+ connectivity, build with --enable-ipv6 to try it. There are no known
+ bugs, but please report any problems you find (or even successes, if
+ you use an unusual platform). There are a few changes of interest;
+ further information is available in doc/IPv6-info.
+
+ * The tradindexed overview method has been completely rewritten and
+ should be considerably more robust in the face of system crashes. A
+ new utility, tdx-util, is provided to examine the contents of the
+ overview database, repair inconsistencies, and rebuild the overview
+ for particular groups from a tradspool news spool. See tdx-util(8)
+ for more details.
+
+ * The Perl and Python authentication hooks for readers have been
+ extensively overhauled and integrated better with readers.conf. See
+ the Changes sections in doc/hook-perl and doc/hook-python for more
+ details.
+
+ * nnrpd now optionally supports article injection via IHAVE, see
+ readers.conf(5). Any articles injected this way must have Date, From,
+ Message-ID, Newsgroups, Path, and Subject headers. X-Trace and
+ X-Complaints-To headers will be added if the appropriate options are
+ set in readers.conf, but other headers will not be modified/inserted
+ (e.g. NNTP-Posting-Host, NNTP-Posting-Date, Organization, Lines, Cc,
+ Bcc, and To headers).
+
+ * nnrpd now handles arbitrarily long lines in POST and IHAVE;
+ administrators who want to limit the length of lines in locally posted
+ articles will need to add this to their local filters instead.
+
+ * nnrpd no longer handles the poorly-specified RFC 977 optional fourth
+ argument to the NEWGROUPS command specifying the "distributions" that
+ the command was supposed to apply to.
+
+ Clients that use that argument will break. There are not believed to
+ be any such clients, and it's easy enough to just filter the returned
+ list of newsgroups (which is generally fairly short) to achieve the
+ same results.
+
+ * nnrpd no longer accepts UTC as a synonym for GMT for NEWGROUPS or
+ NEWNEWS. This usage was never portable, and was rejected by the NNTP
+ working group. It is being removed now in the hope that it will be
+ caught before anyone starts to rely on it.
+
+ * innfeed supports a new peer parameter, *backlog-feed-first*, that if
+ set to "true" feeds any backlog to a peer before new articles, see
+ innfeed.conf(5). When used in combination with *max-connections* set
+ to 1, this can be used to enforce in-order delivery of messages to a
+ peer that is doing Xref slaving, avoiding cases where a
+ higher-numbered message is received before a lower-numbered message in
+ the same group.
+
+ * Several other, more minor protocol issues have been fixed:
+ connections rejected due to the connection rate limiting in innd
+ receive 400 replies instead of 504 or 505, and ARTICLE without an
+ argument will always either retrieve the current article or return a
+ 423 error, never advance the current article number to the next valid
+ article.
+
+ See doc/compliance-nntp for all of the known issues with INN's
+ compliance with the current NNTP draft.
+
+ * All accesses to the history file for all parts of INN now go through a
+ generic API like the storage and overview subsystems do. This will
+ eventually allow new history implementations to be dropped in without
+ affecting the rest of INN, and will significantly improve the
+ encapsulation of the history subsystem. See the libinnhist(3) man
+ page for the details of the interface.
+
+ * INN now uses a new parser for the inn.conf file. This means that
+ parameters containing whitespace or other special characters must now
+ be quoted; see inn.conf(5). It fixes the long-standing bug that
+ certain values must be included in inn.conf even if using the defaults
+ for the use of shell or Perl scripts, and it will serve as the basis
+ for standardizing and cleaning up the configuration file parsing in
+ other parts of INN. innupgrade is run during "make update" and should
+ convert an existing inn.conf file for you.
+
+ * send-uucp has been replaced by a completely rewritten version from
+ Marco d'Itri, Edvard Tuinder, and Miquel van Smoorenburg, which uses a
+ configuration file that specifies batch sizes, compression methods,
+ and hours during which batches should be generated. The old sendbatch
+ script has been retired, since send-uucp can now handle everything
+ that it did.
+
+ * Two "configure" options have changed names: --with-tmp-path is now
+ --with-tmp-dir, and --with-largefiles is now --enable-largefiles, to
+ improve consistency and better match the "autoconf" option guidelines.
+
+ * Variables can now be used in the newsfeeds file to make it easier to
+ specify many similar feeds or feed patterns. See the newsfeeds(5) man
+ page for details.
+
+ * Local connections to INN support a new special mode, MODE CANCEL, that
+ allows efficient batch cancellation of messages. This is intended to
+ be the preferred interface for external spam and abuse filters like
+ NoCeM. See "CANCEL FEEDS" in innd(8) for details.
+
+ * Two new options, *nfsreader* and *nfswriter*, have been added to
+ inn.conf to aid in building NFS based shared reader/writer platforms.
+ On the writer server configure *nfswriter* to "true" and on all of the
+ readers configure *nfsreader* to "true"; these options add calls to
+ force data out to the NFS server and force it to be read directly from
+ the NFS server at the appropriate moments. Note that it has only been
+ tested on Solaris 8, using CNFS as the storage mechanism and
+ tradindexed as the overview method.
+
+ * A new option, *tradindexedmmap*, has been added to inn.conf. If set
+ to "true" (the default), then the tradindexed overview method will use
+ mmap() to access its overview data (in 2.3 you couldn't control this;
+ it always used mmap).
+
+ * Thanks to code contributed by CMU, innfeed can now feed an IMAP server
+ as well as other NNTP servers. See the man page for innfeed(8) for
+ more information.
+
+ * An authenticator, auth_smb, that checks a username and password
+ against a remote Samba server is now included. See auth_smb(8) for
+ details.
+
+ * The wildmat functions in INN now support UTF-8, in a way that should
+ allow them to still work with most simple 8-bit character sets in
+ widespread use. As part of this change, some additional wildmat
+ interfaces are now available and the names have changed (to uwildmat,
+ where "u" is for Unicode). See uwildmat(3) for the details.
+
+ * The interface between external authenticators and nnrpd is now
+ properly documented, in doc/external-auth. A library implementing
+ this interface in C is provided, which should make it easier to write
+ additional authenticators resolvers. See libauth(3) for details, and
+ any of the existing programs in authprogs/ for examples.
+
+ * Most (if not all) of the temporary file creation in INN now uses
+ functions that create temporary files properly and safely.
+
+Changes in 2.3.5
+
+ * Clients using POST are no longer permitted to provide an
+ Injector-Info: header.
+
+ * Fixed a bug causing posts with Followup-To: set to a moderated group
+ to be rejected if the posting user didn't have permission to approve
+ postings.
+
+ * Fixed bugs in inncheck with setuid rnews or setgid inews, in
+ *innconfval* with inn.conf parameters containing shell metacharacters
+ but no spaces, and in parsedate.y with some versions of yacc. Fixed a
+ variety of size-related printf format warnings (e.g., %d vs. %ld)
+ thanks to the work of Winfried Szukalski.
+
+Changes in 2.3.4
+
+ * LIST ACTIVE no longer returns data when given a single group argument
+ if the client is not authorized to read that group.
+
+ * XHDR and XPAT weren't correctly parsing article headers, resulting in
+ searches for the header "newsgroup" matching the header "newsgroups".
+
+ * Made CNFS more robust against crashes by actually syncing the cycbuff
+ headers to disk as was originally intended. Fixed a memory leak in
+ the tradspool code.
+
+ * Two bugs in pgpverify when using GnuPG were fixed: it now correctly
+ checks for gpgv (rather than pgp) when told to use GnuPG and expects
+ the keyring to be pubring.gpg (not pubring.pgp).
+
+ * Substantial updates to the sample provided control.ctl file.
+
+ * Compilation fixes with Perl 5.8.0, Berkeley DB 4.x, current versions
+ of Linux (including with large file support), and Tru64. inndf fixes
+ for ReiserFS.
+
+ * Various bugs in the header handling in nnrpd have been fixed,
+ including hangs when using virtual domains and improper processing of
+ folded headers under certain circumstances.
+
+ * Other minor bug fixes and documentation improvements.
+
+Changes in 2.3.3
+
+ * pgpverify now supports using GnuPG to check signatures (rather than
+ PGP) without the pgpgpg wrapper. GnuPG can check both old-style RSA
+ signatures and new OpenPGP signatures and is recommended over PGP 2.6.
+ If you have GnuPG installed, pgpverify will use it rather than PGP,
+ which means that you may have to create a new key ring for GnuPG to
+ use to verify signatures if you were previously using PGP.
+
+ * Users can no longer post articles containing Approved: headers to
+ moderated groups by default; they must be specifically given that
+ permission with the *access* parameter in readers.conf. See the man
+ page for more details.
+
+ * Two bugs in repacking overview index files and a reliability bug with
+ writing overview data were all fixed in the tradindexed overview
+ method, hopefully making it somewhat more reliable, particularly for
+ makehistory.
+
+ * If rc.news.local exists in the INN binary directory, it will be run
+ with the start or stop argument whenever rc.news is run. This is
+ available as a hook for local startup and shutdown code.
+
+ * The default history table hash sizes were increased because a
+ too-small value can cause serious performance problems (whereas a
+ too-large hash just wastes a bit of disk space).
+
+ * The sample control.ctl file has been extensively updated.
+
+ * Wildmat exclusions ("@" and "!") should now work properly in
+ storage.conf newsgroup patterns.
+
+ * The implementation of the -w flag for expireover was fixed;
+ previously, the value given to -w to change expireover's notion of the
+ current time was scaled by too much.
+
+ * Various other more minor bug fixes, standards compliance fixes, and
+ documentation improvements.
+
+Changes in 2.3.2
+
+ * innxmit can again handle regular filenames as input as well as storage
+ API tokens (allowing it to be used to import an old traditional
+ spool).
+
+ * Several problems with tagged-hash history files have been fixed thanks
+ to the debugging efforts of Andrew Gierth and Sang-yong Suh.
+
+ * A very long-standing (since INN 1.0!) NNTP protocol bug in nnrpd was
+ fixed. The response to an ARTICLE command retrieving a message by
+ Message-ID should have the Message-ID as the third word of the
+ response, not the fourth. Fixing this is reported to *possibly* cause
+ problems with some Netscape browsers, but other news servers correctly
+ follow the protocol.
+
+ * Some serious performance problems with expiration of tradspool should
+ now be at least somewhat alleviated. tradspool and timehash now know
+ how to output file names for removal rather than tokens, and fastrm's
+ ability to remove regular files has been restored. This should bring
+ expiration times for tradspool back to within a factor of two of
+ pre-storage-API expiration times.
+
+ * Added a sample subscriptions file and documentation for it and
+ innmail.
+
+Changes in 2.3.1
+
+ * inews no longer downloads the active file, no longer tries to send
+ postings to moderated groups to the moderator directly, and in general
+ duplicates less of the functionality of nnrpd, instead letting nnrpd
+ handle it. This fixes the problem of inews not working properly for
+ users other than news without being setgid.
+
+ * Added a man page for ckpasswd.
+
+ * A serious bug in the embedded Perl authentication hooks was fixed,
+ thanks to Jan Rychter.
+
+ * The annoying compilation problem with embedded Perl filtering on Linux
+ systems without libgdbm installed should be fixed.
+
+ * INN now complains loudly at "configure" time if the configured path
+ for temporary files is world-writeable, since this configuration can
+ be a security hole.
+
+ * Many other varied bug fixes and documentation fixes of all sorts.
+
+Upgrading from 2.2 to 2.3
+
+ There may be additional things to watch out for not listed here; if you
+ run across any, please let <inn-bugs@isc.org> know about them.
+
+ Simply doing a "make update" is not sufficient to upgrade; the history
+ and overview information will also have to be regenerated, since the
+ formats of both files have changed between 2.2 and 2.3. Regardless of
+ whether you were using the storage API or traditional spool under 2.2,
+ you'll need to rebuild your overview and history files. You will also
+ need to add a storage.conf file, if you weren't using the storage API
+ under INN 2.2. A good default storage.conf file for 2.2 users would be:
+
+ method tradspool {
+ newsgroups: *
+ class: 0
+ }
+
+ Create this storage.conf file before rebuilding history or overview.
+
+ If you want to allow readers, or if you want to expire based on
+ newsgroup name, you need to tell INN to generate overview data and pick
+ an overview method by setting *ovmethod* in inn.conf. See INSTALL and
+ inn.conf(5) for more details.
+
+ The code that generates the dbz index files has been split into a
+ separate program, makedbz. makehistory still generates the base history
+ file and the overview information, but some of its options have been
+ changed. To rebuild the history and overview files, use something like:
+
+ makehistory -b -f history.n -O -T /usr/local/news/tmp -l 600000
+
+ (change the /usr/local/news/tmp path to some directory that has plenty
+ of temporary space, and leave off -O if you're running a transit-only
+ server and don't intend to expire based on group name, and therefore
+ don't need overview.) Or if your overview is buffindexed, use:
+
+ makehistory -b -f history.n -O -F
+
+ Both will generate a new history file as history.n and rebuild overview
+ at the same time. If you want to preseve a record of expired
+ Message-IDs in the history file, run:
+
+ awk 'NF==2 { print; }' < history >> history.n
+
+ to append them to the new history file you created above. Look over the
+ new history file and make sure it looks right, then generate the new
+ index files and move them into place:
+
+ makedbz -s `wc -l < history.n` -f history.n
+ mv history.n history
+ mv history.n.dir history.dir
+ mv history.n.hash history.hash
+ mv history.n.index history.index
+
+ (Rather than .hash and .index files, you may have a .pag file if you're
+ using tagged hash.)
+
+ For reader machines, nnrp.access has been replaced by readers.conf.
+ There currently isn't a program to convert between the old format and
+ the new format (if you'd like to contribute one, it would be welcomed
+ gratefully). The new file is unfortunately considerably more complex as
+ a result of its new capabilities; please carefully read the example
+ readers.conf provided and the man page when setting up your initial
+ configuration. The provided commented-out examples cover the most
+ common installation (IP-based authentication for all machines on the
+ local network).
+
+ INN makes extensive use of mmap(2) for the new overview mechanisms, so
+ at the present time NFS-mounting the spool and overview on multiple
+ reader machines from one central server probably isn't feasible in this
+ version. mmap tends to interact poorly with NFS (at the least, NFS
+ clients won't see updates to the mapped files in situations where they
+ should). (The preferred way to fix this would, rather than backing out
+ the use of mmap or making it optional, to add support for Diablo-style
+ header feeds and pull-on-demand of articles from a master server.)
+
+ The flags for overchan have changed, plus you probably don't want to run
+ overchan at all any more. Letting innd write overview data itself
+ results in somewhat slower performance, but is more reliable and has a
+ better failure mode under high loads. Writing overview data directly is
+ the default, so in a normal upgrade from 2.2 to 2.3 you'll want to
+ comment out or remove your overchan entry in newsfeeds and set
+ *useoverchan* to "false" in inn.conf.
+
+ crosspost is no longer installed, and no longer works (even with
+ traditional spool). If you have an entry for crosspost in newsfeeds,
+ remove it.
+
+ If you're importing a traditional spool from a pre-storage API INN
+ server, it's strongly recommended that you use NNTP to feed the articles
+ to your new server rather than trying to build overview and history
+ directly from the old spool. It's more reliable and ensures that
+ everything gets put into the right place. The easiest way to do this is
+ to generate, on your old server, a list of all of your existing article
+ files and then feed that list to innxmit. Further details can be found
+ in the FAQ at <http://www.eyrie.org/~eagle/faqs/inn.html>.
+
+ If you are using a version of Cleanfeed that still has a line in it
+ like:
+
+ $lines = $hdr{'__BODY__'} =~ tr/\n/\n/;
+
+ you will need to change this line to:
+
+ $lines = $hdr{'__LINES__'};
+
+ to work with INN 2.3 or later. This is due to an internal optimization
+ of the interface to embedded filters that's new in INN 2.3.
+
+Changes in 2.3.0
+
+ * New readers.conf file (replaces nnrp.access) which allows more
+ flexible specification of access restrictions. Included in the sample
+ implementations is a RADIUS-based authenticator.
+
+ * Unified overview has been replaced with an overview API, and there are
+ now three separate overview implementations to choose from. One
+ (tradindexed) is very like traditional overview but uses an additional
+ index file. The second (buffindexed) uses large buffers rather than
+ separate files for each group and can handle a higher incoming article
+ rate while still being fast for readers. The third (ovdb) uses
+ Berkeley DB to store overview information (so you need to have
+ Berkeley DB installed to use it). The *ovmethod* key in inn.conf
+ chooses the overview method to use.
+
+ Note that ovdb has not been as widely tested as the other overview
+ mechanisms and should be considered experimental.
+
+ * All article storage and retrieval is now done via the storage API.
+ Traditional spool is now available as a storage type under the storage
+ API. (Note that the current traditional spool implementation causes
+ nightly expire to be extremely slow for a large number of articles, so
+ it's not recommended that you use the tradspool storage method for the
+ majority of a large spool.)
+
+ * The timecaf storage method has been added, similar to timehash but
+ storing multiple articles in a single file. See INSTALL for details
+ on it.
+
+ * INN now supports embedded Python filters as well as Perl and Tcl
+ filters, and supports Python authentication hooks.
+
+ * There is preliminary support for news reading over SSL, using OpenSSL.
+
+ * To simplify anti-abuse filtering, and to be more compliant with news
+ standards and proposed standards, INN now treats as control messages
+ only articles containing a Control: header. A Subject: line beginning
+ with "cmsg " is no longer sufficient for a message to be considered a
+ control message, and the Also-Control: header is no longer supported.
+
+ * The INN build system no longer uses subst. (This will be transparent
+ to most users; it's an improvement and modernization of how INN is
+ configured.)
+
+ * The build and installation system has been substantially overhauled.
+ "make update" now updates scripts as well as binaries and
+ documentation, there is better support for parallel builds ("make
+ -j"), there is less "make" recursion, and far more of the
+ system-dependent configuration is handled directly by "autoconf".
+ libtool build support (including shared library support) should be
+ better than previous releases.
+
+Changes in 2.2.3
+
+ * inews is not installed setgid news and rnews is not installed setuid
+ root by default any more. If you need the old permissions, you have
+ to give a flag to configure. See INSTALL for more details.
+
+ * Fixed a security hole when *verifycancels* was enabled in inn.conf
+ (not the default).
+
+ * Message-IDs are now limited to 250 octets to prevent interoperability
+ problems with other servers.
+
+ * Embedded Perl filters now work with Perl 5.6.0.
+
+ * Lots of bug fixes and changes for security paranoia.
+
+Changes in 2.2.2
+
+ * Various minor bug fixes and a Y2K bug fix. The Y2K bug is in version
+ version 2.2.1 only and will show up after Jan 1st, 2000 when a news
+ reader issues a NEWNEWS command for a date prior to the year 2000.
+
+Changes in 2.2.1
+
+ * Various bug fixes, mostly notably fixes for potential buffer overflow
+ security vulnerabilities.
+
+Changes in 2.2.0
+
+ * New storage.conf file (replaces storage.ctl).
+
+ * New (optional) way of handling non-cancel control messages
+ (controlchan) that serializes them and prevents server overload from
+ control message storms.
+
+ * Support for actsyncd to fetch active file with ftp; configured by
+ default to use <ftp://ftp.isc.org/pub/usenet/CONFIG/active.Z> if you
+ run actsyncd. Be sure to read the manual page for actsync to
+ configure an actsync.ign file for your site, and test simpleftp if you
+ do not "configure" with wget or ncftp. Also see
+ <ftp://ftp.isc.org/pub/usenet/CONFIG/README>.
+
+ * Some options to "configure" are now moved to inn.conf
+ (*merge-to-groups* and *pgp-verify*, without the hyphen).
+
+ * inndf, a portable version of df(1), is supplied.
+
+ * New cnfsstat program to show stats of CNFS buffers.
+
+ * news2mail and mailpost programs for gatewaying news to mail and mail
+ to news are supplied.
+
+ * pullnews program for doing a sucking feed is provided (not meant for
+ large feeds).
+
+ * The innshellvars.csh.in script is obsolete (and lives in the obsolete
+ directory, for now).
+
--- /dev/null
+Welcome to INN 2.4!
+
+ This work is sponsored by Internet Systems Consortium.
+
+ Please see INSTALL for installation instructions, NEWS for what's
+ changed from the previous release, and LICENSE for the copyright,
+ license, and distribution terms.
+
+What is INN?
+
+ INN (InterNetNews), originally written by Rich Salz, is an extremely
+ flexible and configurable Usenet / netnews news server. For a complete
+ description of the protocols behind Usenet and netnews, see RFC 1036 and
+ RFC 977 (or their replacements). In brief, netnews is a set of
+ protocols for exchanging messages between a decentralized network of
+ news servers. News articles are organized into newsgroups, which are
+ themselves organized into hierarchies. Each individual news server
+ stores locally all articles it has received for a given newsgroup,
+ making access to stored articles extremely fast. Netnews does not
+ require any central server; instead, each news server passes along
+ articles it receives to all of the news servers it peers with, those
+ servers pass the articles along to their peers, and so on, resulting in
+ "flood fill" propagation of news articles.
+
+ A news server performs three basic functions: it accepts articles from
+ other servers and stores them on disk, sends articles it has received
+ out to other servers, and offers stored news articles to readers on
+ demand. It additionally has to perform some periodic maintenance tasks,
+ such as deleting older articles to make room for new ones.
+
+ Originally, a news server would just store all of the news articles it
+ had received in a file system. Users could then read news by reading
+ the article files on disk (or more commonly using news reading software
+ that did this efficiently). These days, news servers are almost always
+ stand-alone systems and news reading is supported via network
+ connections. A user who wants to read a newsgroup opens that newsgroup
+ in their newsreader software, which opens a network connection to the
+ news server and sends requests for articles and related information.
+ The protocol that a newsreader uses to talk to a news server and that a
+ news server uses to talk to another news server over TCP/IP is called
+ NNTP (Network News Transport Protocol).
+
+ INN supports accepting articles via either NNTP connections or via UUCP.
+ innd, the heart of INN, handles NNTP feeding connections directly; UUCP
+ newsfeeds use rnews (included in INN) to hand articles off to innd.
+ Other parts of INN handle feeding articles out to other news servers,
+ most commonly innfeed (for real-time outgoing feeds) or nntpsend and
+ innxmit (used to send batches of news created by innd to a remote site
+ via TCP/IP). INN can also handle outgoing UUCP feeds.
+
+ The part of INN that handles connections from newsreaders is nnrpd.
+
+ Also included in INN are a wide variety of supporting programs to handle
+ periodic maintenance and recovery from crashes, process special control
+ messages, maintain the list of active newsgroups, and generate and
+ record a staggering variety of statistics and summary information on the
+ usage and performance of the server.
+
+ INN also supports an extremely powerful filtering system that allows the
+ server administrator to reject unwanted articles (such as spam and other
+ abuses of Usenet).
+
+ INN is free software, supported by Internet Systems Consortium and
+ volunteers around the world. See "Supporting the INN Effort" below.
+
+Prerequisites
+
+ Compiling INN requires an ANSI C compiler (gcc is recommended). INN was
+ originally written in K&R C, but supporting pre-ANSI compilers has
+ become enough of a headache that a lot of the newer parts of INN will no
+ longer compile with a non-ANSI compiler. gcc itself will compile with
+ most vendor non-ANSI compilers, however, so if you're stuck with one,
+ installing gcc is highly recommended. Not only will it let you build
+ INN, it will make installing lots of other software much easier. You
+ may also need GNU make (particularly if your system make is
+ BSD-derived), although most SysV make programs should work fine.
+ Compiling INN also currently requires a yacc implementation (bison will
+ do fine).
+
+ INN uses GNU autoconf to probe the capabilities of your system, and
+ therefore should compile on nearly any Unix system. It does, however,
+ make extensive use of mmap(), which can cause problems on some older
+ operating systems. See INSTALL for a list of systems it is known to
+ work on. If you encounter problems compiling or running INN, or if you
+ successfully run INN on a platform that isn't listed in INSTALL, please
+ let us know (see "Reporting Bugs" below).
+
+ Perl 5.003 or later is required to build INN. Perl 5.004 is required if
+ you want the embedded Perl filter support (which is highly recommended;
+ some excellent spam filters have been written for INN). Since all
+ versions of Perl previous to 5.004 are buggy (including security
+ problems) and have fewer features, installing Perl 5.004 or later is
+ recommended.
+
+ If you want to enable PGP verification of control messages (highly
+ recommended), you will need to have a PGP implementation installed. See
+ INSTALL for more details.
+
+Getting Started
+
+ A news server can be a fairly complicated piece of software to set up
+ just because of the wide variety of pieces that have to be configured
+ (who is authorized to read from the server, what newsgroups it carries,
+ and how the articles are stored on disk at a bare minimum, and if the
+ server isn't completely stand-alone -- and very few servers are -- both
+ incoming and outgoing feeds have to be set up and tested). Be prepared
+ to take some time to understand what's going on and how all the pieces
+ fit together. If you have any specific suggestions for documentation,
+ or comments about things that are unclear, please send them to the INN
+ maintainers (see "Reporting Bugs" below).
+
+ See INSTALL for step-by-step instructions for setting up and configuring
+ a news server.
+
+ INN also comes with a very complete set of man pages; there is a man
+ page for every configuration file and program that comes with INN. (If
+ you find one that doesn't have a man page, that's a bug. Please do
+ report it.) When trying to figure out some specific problem, reading
+ the man pages for all of the configuration files involved is a very good
+ start.
+
+Reporting Bugs
+
+ We're interested in all bug reports. Not just on the programs, but on
+ the documentation too. Please send *all* such reports to
+
+ inn-bugs@isc.org
+
+ (patches are certainly welcome, see below). Even if you post to Usenet,
+ please CC the above address. All other INN mail should go to
+
+ inn@isc.org
+
+ (please do *not* send bug reports to this address).
+
+ If you have general "how do I do this" questions or problems configuring
+ your server that you don't believe are due to a bug in INN, you should
+ post them to news.software.nntp. A lot of experienced INN users,
+ including several of the INN maintainers, read that newsgroup regularly.
+ Please don't send general questions to the above addresses; those
+ addresses are specifically for INN, and the INN maintainers usually
+ won't have time to answer general questions.
+
+Contributing Code
+
+ If you have a patch or a utility that you'd like to be considered for
+ inclusion into INN, please mail it to
+
+ inn-patches@isc.org
+
+ in the body of the message (not as an attachment), or put it on a
+ webpage and send a link. Patches included with a bug report as
+ described above should follow the same procedure, but need not be sent
+ to both addresses (either will do).
+
+ Have fun!
+
+Mailing Lists
+
+ There are various INN-related mailing lists you can join or send
+ messages to if you like. Some of them you must be a member of before
+ you can send mail to them (thank the spammers for that policy), and one
+ of them is read-only (no postings allowed).
+
+ inn-announce@isc.org Where announcements about INN are set (only
+ maintainers may post).
+
+ inn-workers@isc.org Discussion of INN development (postings by
+ members only).
+
+ inn-patches@isc.org Where to send patches for consideration for
+ inclusion into INN (open posting).
+
+ inn-committers@isc.org CVS commit messages for INN are sent to this
+ list (only the automated messages are sent here,
+ no regular posting).
+
+ inn-bugs@isc.org Where to send bug reports (open posting). If
+ you're an INN expert and have the time to help
+ out other users, we encourage you to join this
+ mailing list to answer questions. (You may also
+ want to read the newsgroup news.software.nntp,
+ which gets a lot of INN-related questions.)
+
+ To join these lists, send a subscription request to the "-request"
+ address. The addresses for the above lists are:
+
+ inn-announce-request@isc.org
+ inn-workers-request@isc.org
+ inn-patches-request@isc.org
+ inn-committers-request@isc.org
+ inn-bugs-request@isc.org
+
+Who's Responsible / Who to Thank
+
+ See CONTRIBUTORS for a long list of past contributors as well as people
+ from the inn-workers mailing list who have dedicated a lot of time and
+ effort to getting this new version together. They deserve a big round
+ of applause. They've certainly got our thanks.
+
+ This product includes software developed by UUNET Technologies, Inc. and
+ by the University of California, Berkeley and its contributors.
+
+ Last, but certainly not least, Rich Salz, the original author of INN
+ deserves a lion's share of the credit for writing INN in the first place
+ and making it the most popular news server software on the planet (no
+ NNTP yet to the moon, but we plan to be there first).
+
+Related Packages
+
+ INN users may also be interested in the following software packages that
+ work with INN or are based on it. Please note that none of this
+ software is developed or maintained by ISC; we don't support it and
+ generally can't answer questions about it.
+
+ CleanFeed
+ URL: <http://www.bofh.it/~md/cleanfeed/>
+
+ CleanFeed is an extremely powerful spam filter, probably the most
+ widely used spam filter on Usenet currently. It catches excessive
+ multiposting and a host of other things, and is highly configurable.
+ Note that it requires that INN be built with Perl support (the
+ --with-perl option to configure).
+
+ GUP (Group Update Program)
+ URL: <ftp://ftp.debian.org/debian/pool/main/g/gup/>
+
+ GUP provides a way for your peers to update their newsfeeds entries
+ as they want without having to ask you to edit the configuration
+ file all the time. It's useful when feeding peers who take limited
+ and very specific feeds that change periodically.
+
+ inflow
+ URL: <http://www.switch.ch/netnews/wg/netnews-wg.html>
+
+ inflow generates graphs of news flow statistics in real time from
+ INN's logs (things like articles accepted per peer, volume accepted
+ per peer, and the like).
+
+ News-Portal
+ URL: <http://floh.gartenhaus.net/newsportal/>
+
+ A PHP-based web news reader that works as a front-end to a regular
+ news server such as INN and lets people read and post without
+ learning a news reader.
+
+ PersonalINN
+ URL: <http://www.ritual.org/summer/pinn/>
+
+ PersonalINN is a version of INN modified for personal use and with a
+ friendly GUI built on top of it. It is available for NeXTSTEP or
+ OPENSTEP only, unfortunately.
+
+ suck
+ URL: <http://home.comcast.net/~bobyetman/index.html>
+
+ suck is a separate package for downloading a news feed via a reading
+ connection (rather than via a direct NNTP or UUCP feed) and sending
+ outgoing local posts via POST. It's intended primarily for personal
+ or small-organization news servers who get their news via an ISP and
+ are too small to warrant setting up a regular news feed.
+
+ newsx
+ URL: <http://www.kvaleberg.com/newsx.html>
+
+ Serving the same purpose as suck, newsx is a separate package for
+ downloading a news feed via a reading connectino and sending
+ outgoing local posts via POST. Some people find suck easier to
+ configure and use, and some people find newsx easier. If you have
+ problems with one, try the other.
+
+Supporting the INN Effort
+
+ Note that INN is supported by Internet Systems Consortium, and although
+ it is free for use and redistribution and incorporation into vendor
+ products and export and anything else you can think of, it costs money
+ to produce. That money comes from ISPs, hardware and software vendors,
+ companies who make extensive use of the software, and generally
+ kind-hearted folk such as yourself.
+
+ Internet Systems Consortium has also commissioned a DHCP server
+ implementation and handles the official support/release of BIND. You
+ can learn more about the ISC's goals and accomplishments from the web
+ page at <http://www.isc.org/>.
+
+ Russ Allbery
+ Katsuhiro Kondou
+ <inn@isc.org>
--- /dev/null
+This is a rough and informal list of suggested improvements to INN, parts
+of INN that need work, and other tasks yet undone. Some of these may be
+in progress, in which case the person working on them will be noted in
+square brackets and should be contacted if you want to help. Otherwise,
+let inn-workers@isc.org know if you'd like to work on any item listed
+below.
+
+The list is divided into changes already tentatively scheduled for a
+particular release, higher priority changes that will hopefully be done in
+the near future, small or medium-scale projects for the future, and
+long-term, large-scale problems. Note that just because a particular
+feature is scheduled for a later release doesn't mean it can't be
+completed earlier if someone decides to take it on. The association of
+features with releases is intended to be a rough guide for prioritization
+and a set of milestones to use to judge when a new major release is
+justified.
+
+Also, one major thing that is *always* welcome is additions to the test
+suite, which is currently very minimal. Any work done on the test suite
+to allow more portions of INN to be automatically tested will make all
+changes easier and will be *greatly* appreciated.
+
+Last modified $Id: TODO 7575 2006-09-11 22:59:38Z eagle $.
+
+
+Scheduled for INN 2.5
+
+* Rewrite configure, breaking all of the tests out into separate files
+ using the new capabilities in autoconf 2.5x. Replace our local macros
+ with the more general features provided by autoconf. At the same time,
+ configure.in and Makefile.global.in should be fixed to use the same
+ names as each other for various parameters. [Russ plans to work on
+ this.]
+
+* Add support for groups, nesting, and vectors to the new configuration
+ parsing code. [Russ plans on doing this.]
+
+* Convert readers.conf and storage.conf (and related configuration files)
+ to use the new parsing system and break out program-specific sections
+ of inn.conf into their own groups.
+
+* The current WIP cache and history cache should be integrated into the
+ history API, things like message ID hashing should become a selectable
+ property of the history file, and the history API should support
+ multiple backend storage formats and automatically select the right one
+ for an existing history file based on stored metainformation.
+
+* The interface to embedded filters needs to be reworked. The information
+ about which filters are enabled should be isolated in the filtering API,
+ and there should be standard API calls for filtering message IDs, remote
+ posts, and local posts. As part of this revision, all of the Perl
+ callbacks should be defined before any of the user code is loaded, and
+ the Perl loading code needs considerable cleanup. At the same time as
+ this is done, the implementation should really be documented; we do some
+ interesting things with embedded filters and it would be nice to have a
+ general document describing how we do it. [Russ is planning on working
+ on this at some point, but won't get upset if someone starts first.]
+
+* All of INN's documentation should be written in POD, with text and man
+ pages generated from the POD source. Anyone is encouraged to work on
+ this by just taking any existing documentation in man format and convert
+ it to POD while checking that it's still accurate and adding any
+ additional useful information that was missed.
+
+* Replace the current innshellvars.pl file with a real INN Perl module for
+ Perl programs, and include the necessary glue so that other Perl modules
+ can be added to INN's build tree and installed with INN, allowing their
+ capabilities to be available to the portions of INN written in Perl.
+
+* Switch nnrpd over to using the new wildmat routines rather than breaking
+ apart strings on commas and matching each expression separately. This
+ involves a lot of surgery, since PERMmatch is used all over the place,
+ and may change the interpretation of ! and @ in group permission
+ wildmats.
+
+* Rework and clean up the storage API. The major change is that the
+ initialization function should return a pointer to an opaque struct
+ which stores all of the state of the storage subsystem, rather than all
+ of that being stored in static variables, and then all other functions
+ should take that pointer. More of the structures should also be opaque,
+ all-caps structure names should be avoided in favor of named structures,
+ SMsetup and SMinit should be combined into one function that takes
+ flags, SMerrno and SMerrorstr should be replaced with functions that
+ return that information, and the wire format utilities should be moved
+ into libinn.
+
+* Rework and clean up the overview API. The major change is that the
+ initialization function should return a pointer to an opaque struct
+ which stores all of the state of the overview subsystem, rather than all
+ of that being stored in static variables, and then all other functions
+ should take that pointer. OVctl possibly should instead take and return
+ a struct rather than using an ioctl-style interface. Currently, the
+ overview functions do a lot of breaking apart of Xref headers and
+ parsing them, which is very ugly; consider having the overview interface
+ always key off a newsgroup name and article number, even for storing.
+ OVadd should probably take a structure and OVsearch should probably
+ return a structure.
+
+
+Scheduled for INN 2.6
+
+* Add a generic, modular anti-spam and anti-abuse filter, off by default,
+ but coming with INN and prominently mentioned in the INSTALL
+ documentation. [Andrew Gierth has work in progress that may be usable
+ for this.]
+
+* A unified configuration file combining the facilities of newsfeeds,
+ incoming.conf, and innfeed.conf, but hopefully more readable and easier
+ for new INN users to edit. This should have all of the capabilities of
+ the existing configuration files, but specifying common things (such as
+ file feeds or innfeed feeds) should be very simple and straightforward.
+ This configuration file should use the new parsing infrastructure.
+
+* Convert all remaining INN configuration files to the new parsing
+ infrastructure.
+
+* INN really should be capable of both sending and receiving a
+ headers-only feed (or even an overview-only feed) similar to Diablo and
+ using it for the same things that Diablo does, namely clustering,
+ pull-on-demand for articles, and the like. This should be implementable
+ as a new backend, although the API may need a few more hooks. Both a
+ straight headers-only feed that only pulls articles down via NNTP from a
+ remote server and a caching feed where some articles are pre-fed, some
+ articles are pulled down at first read, and some articles are never
+ stored locally should be possible. [Patches for a header-only feed have
+ already been written and submitted to inn-workers.]
+
+* The libinn, libstorage, and other library interfaces should be treated
+ as stable libraries and properly versioned using libtool's
+ recommendation for library versioning when changes are made so that they
+ can be installed as shared libraries and work properly through releases
+ of INN. This is currently waiting on a systematic review of the
+ interface and removal of things that we don't want to support long-term.
+
+* The include files necessary to use libinn, libstorage, and other
+ libraries should be installed in a suitable directory so that other
+ programs can link against them. All such include files should be under
+ include/inn and included with <inn/header.h>. All such include files
+ should only depend on other inn/* header files and not on, e.g.,
+ config.h. All such include files should be careful about namespace to
+ avoid conflicts with other include files used by applications.
+
+
+High Priority Projects
+
+* Modulo warnings from system headers and warnings where the compiler is
+ simply wrong and there's no equally readable way to rewrite the code,
+ INN should compile cleanly under "make warnings". It should be possible
+ for maintainers to routinely compile INN with make warnings to catch
+ problems. Note that -Wcast-qual warnings cannot be avoided entirely
+ because we don't want to write redundant functions for regular and const
+ strings and because of such things as struct iovec; -Wcast-qual will be
+ removed from make warnings when this task is reasonably complete.
+
+* INN shouldn't flush all feeds (particularly all program feeds) on
+ newgroup or rmgroup. Currently it reloads newsfeeds to reparse all of
+ the wildmat patterns and rebuild the peer lists associated with the
+ active file on group changes, and this forces a flush of all feeds.
+ The best fix is probably to stash the wildmat pattern (and flags) for
+ each peer when newsfeeds is read and then just using the stashed copy on
+ newgroup or rmgroup, since otherwise the newsfeeds loading code would
+ need significant modification. But in general, innd is too
+ reload-happy; it should be better at making incremental changes without
+ reloading everything.
+
+* Add authenticated Path support, based on the current USEFOR draft or the
+ behavior of some other servers (such as Diablo). [Andrew Gierth wrote a
+ patch for part of this a while back, which Russ has. Marco d'Itri
+ expressed some interest in working on this.]
+
+* Various parts of INN are using write or writev; they should all use
+ xwrite or xwritev instead. Even for writes that are unlikely to ever be
+ partial, on some systems system calls aren't restartable and xwrite and
+ xwritev properly handle EINTR returns.
+
+* Apparently on Solaris open can also be interrupted by a signal; we may
+ need to have an xopen wrapper that checks for EINTR and retries.
+
+* tradspool has a few annoying problems. Deleted newsgroups never have
+ their last articles expired, and there is no way of forcibly
+ resynchronizing the articles stored on disk with what overview knows
+ about unless tradindexed is used. Some sort of utility program to take
+ care of these and to do things like analyze the tradspool.map file
+ should be provided.
+
+* Rewrite inndstart as a helper program that only binds the relevant
+ sockets and then returns them to innd. Since file descriptors are
+ shared by child processes, this can be done with a program spawned by
+ innd. This may have gotten more complicated with IPv6. Drop
+ startinnfeed entirely in favor of recommending people use ulimit in the
+ news init script.
+
+* contrib/mkbuf and contrib/reset-cnfs.c should be combined into a utility
+ for creating and clearing cycbuffs, perhaps combined with cnfsheadconf,
+ and the whole thing moved into storage/cnfs rather than frontends (along
+ with cnfsstat). pullart.c may also stand to be merged into the same
+ utility (cnfs-util might not be a bad name).
+
+
+Documentation Projects
+
+* Add man pages for all libinn interfaces. There should be a subdirectory
+ of doc/pod for this since there will be a lot of them; installing them
+ as libinn_<section>.3 seems to make the most sense (so, for example,
+ error handling routines would be documented in libinn_error.3).
+
+* Better documentation of and support for UUCP feeds. send-uucp is now
+ easier to use, but there's still a paucity of documentation covering the
+ whole theory and mechanisms of UUCP feeding.
+
+* Everything installed by INN should have a man page. Currently, there
+ are several binaries and configuration files that don't have man pages.
+ (In some cases, the best thing to do with the configuration file may be
+ to merge it into another one or find a way to eliminate it.)
+
+* Document the internal formats of the various overview methods, CNFS,
+ timehash, and timecaf. A lot of this documentation already exists in
+ various forms, but it needs to be cleaned up and collected in one place
+ for each format, preferrably as a man page.
+
+* Add documentation for slave servers. [Russ has articles from
+ inn-workers that can be used as a beginning.]
+
+* Write complete documentation for all of our extensions to RFC 977 or RFC
+ 1036, preferrably in a format that could be suitable for future
+ inclusion into new revisions of the RFCs.
+
+* Audit readers.conf.5 against perm.c for missing options ("include" at
+ least is missing from the documentation).
+
+* The distributions file is undocumented.
+
+
+Code Cleanup Projects
+
+* Eliminate everything in the LEGACY section of config.h.
+
+* Move all compile-time configuration in config.h either into a separate
+ header (such as inn/options.h) or turn it into a configuration file
+ directive or a command-line option. In particular, the rnews
+ configuration should probably be an rnews-specific section of inn.conf.
+
+* Move include/paths.h to include/inn/paths.h and change _PATH as a prefix
+ to INN_PATH to move the identifiers out of the C reserved namespace.
+ Check to be sure we still need all of the #defines and look at adding
+ anything needed by innfeed (and eliminating the separate innfeed header
+ serving the same purpose).
+
+* Move include/nntp.h to include/inn/nntp.h and at the same time look at
+ standardizing the names of all of the #defines it provides, including
+ the message class. [Russ has a start on this.]
+
+* Get rid of GetTimeInfo and TIMEINFO. All the struct is is a struct
+ timeval plus time zone information. All of the parts of INN that deal
+ with time zone information are isolated in lib/date.c. The rest of INN
+ uses GetTimeInfo where a plain call to time would often work fine, or
+ at most gettimeofday, and there's no reason to compute the time zone
+ everywhere. Plus, it makes the code more readable to use standard
+ functions and data types.
+
+* putman.sh should be merged into support/install-sh (which would mean
+ giving up any pretext of using the standard install-sh script, but that
+ should be fine).
+
+* Use vectors or cvectors everywhere that argify and friends are currently
+ used and eliminate the separate implementation in nnrpd/misc.c.
+
+* Break up the remainder of libinn.h into multiple inn/* include files for
+ specific functions (such as memory management, wildmat, date handling,
+ NNTP commands, etc.), with an inn/util.h header to collect the remaining
+ random utilities. Consider adding some sort of prefix, like inn_, to all
+ functions that aren't part of some other logical set with its own prefix.
+
+* Break the CNFS and tradspool code into multiple source files to make it
+ easier to understand the logical divisions of the code and consider
+ doing the same with the other overview and storage methods.
+
+* Examine the (mostly socket) code that currently should probably be
+ compiled with -fno-strict-aliasing on gcc and move the relevant casts
+ to within function calls. [Russ knows about this.]
+
+* Clean up the use of #ifdef for sockets and IPv6, perhaps involving
+ addition of more to include/portable/socket.h.
+
+
+Needed Bug Fixes
+
+* tradspool currently uses stdio to write out tradspool.map, which can
+ cause problems if more than 256 file descriptors are in use for other
+ things (such as incoming connections or tradindexed overview cache).
+ It should use write() instead.
+
+* LIST NEWSGROUPS should probably only list newsgroups that are marked in
+ the active file as valid groups.
+
+* INN's startup script should be sure to clean out old lock files and PID
+ files for innfeed. Be careful, though, since innfeed may still be
+ running, spawned from a previous innd.
+
+* makedbz should be more robust in the presence of malformed history
+ lines, discarding with them or otherwise dealing with them.
+
+* CNFS, if the cycbuff is larger than 2GB and it doesn't have large file
+ support, reports a mysterious file not found error because it assumes
+ all errors from stat are the result of the cycbuff not being found.
+
+* Some servers reject some IHAVE, TAKETHIS, or CHECK commands with 500
+ syntax errors (particularly for long message IDs), and innfeed doesn't
+ handle this particularly well at the moment. It really should have an
+ error handler for this case. [Sven Paulus has a preliminary patch that
+ needs testing.]
+
+* Editing the active file by hand can currently munge it fairly badly even
+ if the server is throttled unless you reload active before restarting
+ the server. This could be avoidable for at least that particular case
+ by checking the mtime of active before and after the server was
+ throttled.
+
+* innreport silently discards news.notice entries about most of the errors
+ innfeed generates. It should ideally generate some summary, or at least
+ note that some error has occurred and the logs should be examined.
+
+* INN's message ID parser should be more forgiving about surrounding
+ whitespace. Right now, it will reject messages with a trailing space in
+ the Message-ID header.
+
+* nnrpd doesn't check the message ID of a posted article for syntactic
+ validity before remailing it to the moderator, since normally it relies
+ on innd to check the message ID. The message ID checking code from
+ innd/art.c should be moved into lib so that nnrpd can use it as well.
+
+* Currently, if the list of newsgroups on an Xref slave is out of sync
+ with the newsgroups on the master, receiving an article crossposted to
+ one of the groups that doesn't exist on the slave will cause the slave
+ to throttle. This isn't the best behavior; the server should either
+ optionally create the missing newsgroup or just ignore that crossposted
+ group (and modify Xref accordingly?).
+
+* Handling of compressed batches needs to be thoroughly reviewed by
+ someone who understands how they're supposed to work. It's not clear
+ that _PATH_GZIP is being used correctly at the moment and that
+ compressed batch handling will work right now on systems that don't have
+ gzip installed (but that do have uncompress).
+
+* innfeed's statistics don't add up properly all the time. All of the
+ article dispositions don't add up to the offered count like they should.
+ Some article handling must not be recorded properly.
+
+* innd's counting of article size doesn't always work properly, and it can
+ accept articles that are larger than its configured limit. It's not
+ clear exactly where this is happening.
+
+* If a channel feed exits immediately, innd respawns it immediately,
+ causing thrashing of the system and a huge spew of errors in syslog. It
+ should mark the channel as dormant for some period of time before
+ respawning it, perhaps only if it's already died multiple times in a
+ short interval.
+
+* ctlinnd begin <site-name> was causing innd to core dump.
+
+* Handling of innfeed's dropped batches needs looking at. There are three
+ places where articles can fall between the cracks: an innfeed.togo file
+ written by innd when the feed can't be spawned, a batch file named after
+ the feed name which can be created under similar circumstances, and the
+ dropped files written by innfeed itself. procbatch can clean these up,
+ but has to be run by hand.
+
+* When using tradspool, groups are not immediately added to tradspool.map
+ when created, making innfeed unable to find the articles until after
+ some period of time. Part of the problem here is that tradspool only
+ updates tradspool.map on a lazy basis, when it sees an article in that
+ group, since there is no storage hook for creation of a new group.
+
+* nntpget doesn't handle long lines in messages.
+
+* WP feeds break if there are spaces in the Path header, and the inn.conf
+ parser doesn't check for this case and will allow people to configure
+ their server that way. (It's not clear that the latter is actually a
+ bug, given the new USEFOR attempt to allow folding of Path headers, but
+ the space needs to be removed for WP feeds.)
+
+* Error handling in the history backend needs to be reviewed, since it
+ currently is always printing out errno regardless of whether it's
+ meaningful. The error handling needs to record errno if it's useful and
+ the reporting function should only print it out if it's useful for that
+ error.
+
+* innd returns 437 for articles that were accepted but filed in the junk
+ group. It should probably return the appropriate 2xx status code in
+ that case instead.
+
+* Someone should go through the BUGS sections of all of the manpages and
+ fix those for which the current behavior is unacceptable.
+
+
+Requested New Features
+
+* Consider implementing the HEADERS command as discussed rather
+ extensively in news.software.nntp. [Greg Andruk has a preliminary
+ patch.]
+
+* There have been a few requests for the ability to programmatically set
+ the subject of the report generated by news.daily, with escapes that are
+ filled in by the various pieces of information that might be useful.
+
+* A bulk cancel command using the MODE CANCEL interface. Possibly through
+ ctlinnd, although it may be a bit afield of what ctlinnd is currently
+ for.
+
+* Sven Paulus's patch for nnrpd volume reports should be integrated. See
+ <ftp://ftp.tin.org/pub/news/servers/inn/unofficial-patches/
+ patch-inn-2.2.x-artstat+list+overstat>.
+
+* Lots of people encrypt X-Trace in various ways. Should that be offered
+ as a standard option? The first data element should probably remain
+ unencrypted so that the O flag in newsfeeds doesn't break.
+
+ Should there also be an option not to generate X-Trace? And this whole
+ area may change if USEFOR ever standardizes poster trace information;
+ it's been proposed to put it in the path tail instead. The current
+ USEFOR trend as of January, 2001 appears to be towards an Injector-Info
+ header with this information, allowing a token or an injecting hostname.
+ For a token, one really wants it to be hierarchically structured for
+ spam filtering even if it's encrypted (in other words, to get a "group"
+ of clients, one could just match the first n bytes of the token instead
+ of the whole thing).
+
+ Olaf Titz suggests:
+
+ This can be done by formatting the (rest of) the header in a way
+ that fields are always a multiple of 8 bytes and applying a 64 bit
+ block cipher in ECB mode on it. But then we would be better off
+ using binary fields, as the timestamp is 9 bytes and an IP address
+ 10-12 bytes.
+
+ Combining the timestamp and PID into one block, adding an
+ authenticated user field and omitting the redundant formatted time
+ would give the following format:
+
+ X-Trace: g212.hadiko.de [395109AA000016FF] [AC14302A00000000] [...]
+ time | pid ip |reserved user
+
+* ctlinnd flushlogs currently renames all of the log files. It would be
+ nice to support the method of log rotation that most other daemons
+ support, namely to move the logs aside and then tell innd to reopen its
+ log files. Ideally, that behavior would be triggered with a SIGHUP.
+ scanlogs would have to be modified to handle this.
+
+ The best way to support this seems to be to leave scanlogs as is by
+ default, but also add two additional modes. One would flush all the
+ logs and prepare for the syslog logs to be rotated, and the other would
+ do all the work needed after the logs have been rotated. That way, if
+ someone wanted to plug in a separate log rotation handler, they could do
+ so and just call scanlogs on either side of it. The reporting portions
+ of scanlogs should be in a separate program.
+
+* Several people have Perl interfaces to pieces of INN that should ideally
+ be part of the INN source tree in some fashion. Greg Andruk has a bunch
+ of stuff that Russ has copies of, for example.
+
+* Investigate using the new, stricter date parsing code in libinn for
+ nnrpd rather than the extremely lenient parsedate routine.
+
+* There are various available patches for Cancel-Lock and an Internet
+ draft; support should be added to INN for both generation and
+ verification (definitely optional and not on by default at this point).
+
+* It would be nice to be able to reload inn.conf (although difficult, due
+ to the amount of data that's generated from it and stashed in various
+ places). This will need to wait for the new configuration parsing
+ library and an inn.conf parser that uses it.
+
+* remembertrash currently rejects and remembers articles with syntax
+ errors as well as things like unwanted newsgroups and unwanted
+ distributions, which means that if a peer sends you a bunch of mangled
+ articles, you'll then also reject the correct versions of the articles
+ from other peers. This should probably be rethought.
+
+* Additional limits for readers.conf: Limit on concurrent parallel reader
+ streams, limit on KB/second download (preliminary support for this is
+ already in), and a limit on maximum posted articles per day (tied in
+ with the backoff stuff?). These should be per-IP or per-user, but
+ possibly also per-access group. (Consider pulling the -H, -T, -X, and
+ -i code out from innd and using it here.)
+
+* timecaf should have more configurable parameters (at the least, how
+ frequently to switch to a new CAF file should be an option).
+ storage.conf should really be extended to allow method-specific
+ configuration for things like this (and to allow the cycbuff.conf file
+ to be merged into storage.conf).
+
+* Allow generation of arbitrary additional information that could go in
+ overview by using embedded Perl or Python code. This might be a cleaner
+ way to do the keywords code, which really wants Perl's regex engine
+ ideally. It would also let one do something like doing MD5 hashes of
+ each article and putting that in the overview if you care a lot about
+ making sure that articles aren't corrupted.
+
+* Allow some way of accepting articles regardless of the Date header, even
+ if it's far into the future. Some people are running into articles that
+ are dated years into the future for some reason that they still want to
+ store on the server.
+
+* There was a request to make --program-suffix and the other name
+ transformation options to autoconf work. The standard GNU package does
+ this with really ugly sed commands in the Makefile rules; we could
+ probably do better, perhaps by substituting the autoconf results into
+ support/install-sh.
+
+* INN currently uses hash tables to store the active file internally. It
+ would be worth trying ternary search trees to see if they're faster; the
+ data structure is simpler, performance may be comparable for hits and
+ significantly better for misses, sizing and resizing becomes a non-issue,
+ and the space penalty isn't too bad. A generic implementation is already
+ available in libinn. (An even better place to use ternary search trees
+ may be the configuration parser.)
+
+* Provide an innshellvars equivalent for Python.
+
+* inncheck should check the syntax of all the various files that are
+ returned by LIST commands, since having those files present with the
+ wrong syntax could result in non-compliant responses from the server.
+ Possibly the server should also refuse to send malformatted lines to
+ the client.
+
+* ctlinnd reload incoming.conf could return a count of the hosts that
+ failed, or even better a list of them. This would make pruning old
+ stuff out of incoming.conf much easier.
+
+* nnrpd could use sendfile(2), if available, to send articles directly
+ to the socket (for those storage methods where to-wire conversion is
+ not needed). This would need to be added to the storage API.
+
+* Somebody should look at keeping the "newsgroups" file more accurate
+ (e.g. newgroups for existing groups should change description, better
+ checkgroups handling, checking for duplicates)
+
+* The by-domain statistics innreport generates for nnrpd count all local
+ connections (those with no "." in the hostname) in with the errors as
+ just "?". The host2dom function could be updated to group these as
+ something like "Local".
+
+* news.daily could detect if expire segfaults and unpause the server.
+
+* When using SSL, track the amount of data that's been transferred to the
+ client and periodically renegotiate the session key.
+
+* When using SSL, use SSL_get_peer to get a verified client certificate,
+ if available, and use it to create an additional header line when
+ posting articles (X-Auth-Poster?). This header could use:
+
+ X509_NAME_oneline(X509_get_subject_name(peer),...)
+
+ for the full distinguished name, or
+
+ X509_name_get_text_by_NID(X509_get_subject_name(peer),
+ NID_commonName, ...)
+
+ for the client's "common name" alone.
+
+* When using SSL, use the server's key to generate an HMAC of the body of
+ the message (and most headers?), then include that digest in the
+ headers. This allows a news administrator to determine if a complaint
+ about the content of a message is fradulent since the message was
+ changed after transmission.
+
+
+General Projects
+
+* All the old packages in unoff-contrib should be reviewed for integration
+ into INN.
+
+* It may be better for INN on SysV-derived systems to use poll rather than
+ select. The semantics are better, and on some systems (such as Solaris)
+ select is limited to 1024 file descriptors whereas poll can handle any
+ number. Unfortunately, the API is drastically different between the
+ two and poll isn't portable, so supporting both cleanly would require a
+ bit of thought.
+
+* Currently only innd and innfeed increase their file descriptor limits.
+ Other parts of INN, notably makehistory, may benefit from doing the same
+ thing if they can without root privileges.
+
+* The Tcl filtering support code has undergone serious bitrot and needs
+ some work to fix it and make it work with modern versions of Tcl and the
+ current version of INN. It also lacks a lot of the functionality of the
+ Perl and Python filters, if anyone cares.
+
+* Revisit support for aliased groups and what nnrpd does with them.
+ Should posts to the alias automatically be redirected to the real group?
+ Regardless, the error return should provide useful information about
+ where to post instead. Also, the new overview API, for at least some of
+ the overview methods, truncated the group status at one character and
+ lost the name of the group to which a group is aliased; that needs to be
+ fixed.
+
+* More details as to why a message ID is bad would be useful to return to
+ the user, particularly for rnews, inews, etc. innd also rejects message
+ IDs with trailing spaces, which can be hard to check.
+
+* Support putting the active file and history file in different
+ directories without hand-editing a bunch of files.
+
+* nnrpd's NNTP command parsing interacts poorly with AUTHINFO and
+ passwords containing spaces. The correct solution isn't clear; check
+ with the current NNTP RFC draft and how existing clients handle it?
+
+* frontends/pullnews and contrib/backupfeed solve the same problem; the
+ best ideas of both should be unified into one script.
+
+* actsyncd could stand a rewrite and cleaner handling of both
+ configuration and syncing against multiple sources which are canonical
+ for different sets of groups.
+
+* send-nntp and nntpsend basically do the same thing; send-nntp could
+ probably be removed (possibly with some extra support in nntpsend for
+ doing simpler things).
+
+
+Long-Term Projects
+
+* Look at turning header parsing into a library of some sort. Lots of INN
+ does this, but different parts of INN need subtly different things, so
+ the best best API is unclear.
+
+* INN's header handling needs to be checked against the current USEFOR
+ draft. This may want wait until after we have a header parsing library.
+
+* The innd filter should be able to specify additional or replacement
+ groups into which an article should be filed, or even spool the article
+ to a local disk file rather than storing it. (See the stuff that the
+ nnrpd filter can already do.)
+
+* Add authentication via SASL to nnrpd. This is a boatload of additional
+ issues, particularly if we want to add authentication methods like
+ Kerberos that require their own separate libraries (although we should
+ use Cyrus's SASL libraries, which will simplify a lot of that).
+ [Jeffrey Vinocur is working on a standard for this.]
+
+* When articles expire out of a storage method with self-expire
+ functionality, the overview and history entries for those articles
+ should also be expired immediately. Otherwise, things like the GROUP
+ command don't give the correct results. This will likely require a
+ callback that can be passed to CNFS that is called to do the overview
+ and history cleanup for each article overwritten. It will also require
+ the new history API.
+
+* Feed control, namely allowing your peers to set policy on what articles
+ you feed them (not just newsgroups but max article size and perhaps even
+ filter properties like "non-binary"). Every site does this a bit
+ differently. Some people have web interfaces, some people use GUP, some
+ people roll their own alternate things. It would really be nice to have
+ some good way of doing this as part of INN. It's worth considering an
+ NNTP extension for this purpose, although the first step is to build a
+ generic interface that an NNTP extension, a web page, etc. could all
+ use. (An alternate way of doing this would be to extend IHAVE to pass
+ the list of newsgroups as part of the command, although this doesn't
+ seem as generally useful.)
+
+* Traffic classification as an extension of filtering. The filter should
+ be able to label traffic as binary (e.g.) without rejecting it, and
+ newsfeeds should be extended to allow feeding only non-binary articles
+ (e.g.) to a peer.
+
+* External authenticators should also be able to do things like return a
+ list of groups that a person is allowed to read or post to. Currently,
+ maintaining a set of users and a set of groups, each of which some
+ subset of the users is allowed to access, is far too difficult. For a
+ good starting list of additional functionality that should be made
+ available, look at everything the Perl authentication hooks can do.
+ This should probably wait for the configuration file parsing rewrite.
+
+* Allow nnrpd to spawn long-running helper processes. Not only would this
+ be useful for handling authentication (so that the auth hooks could work
+ without execing a program on every connection), but it may allow for
+ other architectures for handling requests (such as a pool of helpers
+ that deal only with overview requests). More than that, nnrpd should
+ *be* a long-running helper process that innd can feed open file
+ descriptors to. [Aidan Culley has ideas along these lines.]
+
+* The tradspool storage method requires assigning a number to every
+ newsgroup (for use in a token). Currently this is maintained in a
+ separate tradspool.map file, but it would be much better to keep that
+ information in the active file where it can't drop out of sync. A code
+ assigned to each newsgroup would be useful for other things as well,
+ such as hashing the directories for the tradindexed overview. For use
+ for that purpose, though, the active file would have to be extended to
+ include removed groups, since they'd need to be kept in the active file
+ to reserve their numbers until the last articles expired.
+
+* The locking of the active file leaves something to be desired; in
+ general, the locking in INN (for the active file, the history file,
+ spool updates, overview updates, and the like) needs a thorough
+ inspection and some cleanup. A good place to start would be tracing
+ through the pause and throttle code and write up a clear description of
+ what gets locked where and what is safely restarted and what isn't.
+ Long term, there needs to be a library locking routine used by
+ *everything* that needs to write to the history file, active file, etc.
+ and that keeps track of the PID of the process locking things and is
+ accessible via ctlinnd.
+
+* There is a fundamental problem with the current design of the
+ control.ctl file. It combines two things: A database of hierarchies,
+ their maintainers, and related information, and a list of which
+ hierarchies the local server should honor. These should be separated
+ out into the database (which could mostly be updated from a remote
+ source like ftp.isc.org and then combined with local additions) and a
+ configured list of hierarchies (or sub-hierarchies within hierarchies)
+ that control messages should be honored for. This should be reasonably
+ simple although correct handling of checkgroups could get a mite tricky.
+
+* Possible NNTP extension: Compression of the protocol, using gzip,
+ bzip2, or some other technique. Particularly useful for long lists like
+ the active file information or the overview information, but possibly
+ useful in general for other things.
+
+* Install wizards. Configuring INN is currently very complex even for an
+ experienced news admin, and there are several fairly standard
+ configurations that shouldn't be nearly that complicated to get running
+ out of the box. A little interactive Perl script asking some simple
+ questions could probably get a lot of cases easily right.
+
+* One ideally wants to be able to easily convert between different
+ overview formats or storage methods, refiling articles in place. This
+ should be possible once we have a history API that allows changing the
+ storage location of an article in-place.
+
+* Set up the infrastructure required so that INN can use alloca. This
+ would significantly decrease the number of calls to malloc needed and
+ would be a lot more convenient.
+
+* A serious investigation into whether INN could use a garbage collector
+ is probably a good idea. The network buffers probably need to be
+ handled with decidated code, but there are a lot of other incidental
+ allocations and deallocations that may be much more efficient and safer
+ using a garbage collector.
+
+* Look at integrating asprintf and vasprintf. Russ already tried this
+ once and couldn't see a good way of doing it (particularly vasprintf)
+ without hooking deep into an sprintf implementation, because the simple
+ hack of calling vsnprintf first, allocating that much memory, and then
+ calling it again on the new buffer doesn't work for vasprintf (you can't
+ reprocess the arguments).
+
+* Support building in a separate directory than the source tree. It may
+ be best to just support this via lndir rather than try to do it in
+ configure, but it would be ideal to add support for this to the autoconf
+ system. Unfortunately, the standard method requires letting configure
+ generate all of the makefiles, which would make running configure and
+ config.status take much longer than it does currently.
+
+* Look at adding some kind of support for MODE CANCEL via network sockets
+ and fixing up the protocol so that it could possibly be standardized
+ (the easiest thing to do would probably be to change it into a CANCEL
+ command). If we want to get to the point where INN can accept and even
+ propagate such feeds from dedicated spam filters or the like, there must
+ also be some mechanism of negotiating policy in order to decide what
+ cancels the server wants to be fed.
+
+* The "possibly signed" char data type is one of the inherent flaws of C.
+ Some other projects have successfully gotten completely away from this
+ by declaring all of their strings to be unsigned char, defining a macro
+ like U that casts strings to unsigned char for use with literal strings,
+ and always using unsigned char everywhere. Unfortunately, this also
+ requires wrappering all of the standard libc string functions, since
+ they're prototyped as taking char rather than unsigned char. The
+ benefits include cleaner and consistent handling of characters over 127,
+ better warnings from the compiler, consistent behavior across platforms
+ with different notions about the signedness of char, and the elimination
+ of warnings from the <ctype.h> macros on platforms like Solaris where
+ those macros can't handle signed characters. We should look at doing
+ this for INN.
+
+* It would clean up a lot of code considerably if we could just use mmap
+ semantics regardless of whether the system has mmap. It may be possible
+ to emulate mmap on systems that don't have it by reading the entirety of
+ the file into memory and setting the flags that require things to call
+ mmap_flush and mmap_invalidate on a regular basis, but it's not clear
+ where to stash the file descriptor that corresponds to the mapped file.
+
+* Figure out some Samba library that we can link against for the Samba
+ authenticator so that we can get all the Samba code back out of INN's
+ source tree; we don't want to maintain it.
+
+* Consider replacing the awkward access: parameter in readers.conf with
+ separate commands (e.g. "allow_newnews: true") or otherwise cleaning up
+ the interaction between access: and read:/post:. Note that at least
+ allownewnews: can be treated as a setting for overriding inn.conf and
+ should be very easy to add.
+
+* Add a localport: parameter (similar to localaddress:) to readers.conf
+ auth groups. With those two parameters (and ssl_required:) we
+ essentially eliminate the need to run multiple instances of nnrpd just to
+ use different configurations.
+
+* Various things may break when trying to use data written while compiled
+ with large file support using a server that wasn't so compiled (and vice
+ versa). The main one is the history file, but tradindexed is also
+ affected and buffindexed has been reported to have problems with this
+ as well. Ideally, all of INN's data files should be as portable as
+ possible.
+
+
+Complete Code Reorganization
+
+At some point, we should probably abandon and archive the current CVS
+repository, reimport all of the current source files, and start with a
+fresh repository with a better revision control system such as Subversion.
+A better revision control system would let us rename and move things
+around arbitrarily, something CVS doesn't handle at all well. Should this
+ever be done, we should consider doing all of the following at the same
+time:
+
+* Don't include any generated files in the CVS tree. Maintainers should
+ have autoconf and friends, pod2text and pod2man, and bison around anyway.
+ This would save a bunch of extra check-ins, remove the danger of the
+ generated files getting out of sync, and drastically reduce the
+ repository size in the case of configure.
+
+* Don't include any of the generated man pages in the CVS tree, as an
+ additional case of the above. All of the documentation should be in POD
+ and we can generate the man pages as part of the snapshot process.
+
+* storage should be reserved just for article storage; the overview
+ methods should be in a separate overview tree.
+
+* The split between frontends and backends is highly non-intuitive. Some
+ better organization scheme should be arrived at. Perhaps something
+ related to incoming and outgoing, with programs like cnfsstat moved into
+ the storage directory with the other storage-related code?
+
+* Add a separate utils directory for things like convdate, shlock,
+ shrinkfile, and the like. Some of the scripts may possibly want to go
+ into that directory too.
+
+* The lib directory possibly should be split so that it contains only code
+ always compiled and part of INN, and the various replacements for
+ possibly missing system routines are in a separate directory (such as
+ replace). These should possibly be separate libraries; there are things
+ that currently link against libinn that only need the portability
+ pieces.
+
+* The doc directory really should be broken down further by type of
+ documentation or section or something; it's getting a bit unwieldy.
+
+* Untabify and reformat all of the code according to a consistent coding
+ style which would then be enforced for all future check-ins.
--- /dev/null
+# libtool.m4 - Configure libtool for the host system. -*-Shell-script-*-
+## Copyright 1996, 1997, 1998, 1999, 2000, 2001
+## Free Software Foundation, Inc.
+## Originally by Gordon Matzigkeit <gord@gnu.ai.mit.edu>, 1996
+##
+## This program is free software; you can redistribute it and/or modify
+## it under the terms of the GNU General Public License as published by
+## the Free Software Foundation; either version 2 of the License, or
+## (at your option) any later version.
+##
+## This program is distributed in the hope that it will be useful, but
+## WITHOUT ANY WARRANTY; without even the implied warranty of
+## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+## General Public License for more details.
+##
+## You should have received a copy of the GNU General Public License
+## along with this program; if not, write to the Free Software
+## Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+##
+## As a special exception to the GNU General Public License, if you
+## distribute this file as part of a program that contains a
+## configuration script generated by Autoconf, you may include it under
+## the same distribution terms that you use for the rest of that program.
+
+# serial 46 AC_PROG_LIBTOOL
+
+AC_DEFUN([AC_PROG_LIBTOOL],
+[AC_REQUIRE([AC_LIBTOOL_SETUP])dnl
+
+# This can be used to rebuild libtool when needed
+LIBTOOL_DEPS="$ac_aux_dir/ltmain.sh"
+
+# Always use our own libtool.
+LIBTOOL='$(SHELL) $(top_builddir)/libtool'
+AC_SUBST(LIBTOOL)dnl
+
+# Prevent multiple expansion
+define([AC_PROG_LIBTOOL], [])
+])
+
+AC_DEFUN([AC_LIBTOOL_SETUP],
+[AC_PREREQ(2.13)dnl
+AC_REQUIRE([AC_ENABLE_SHARED])dnl
+AC_REQUIRE([AC_ENABLE_STATIC])dnl
+AC_REQUIRE([AC_ENABLE_FAST_INSTALL])dnl
+AC_REQUIRE([AC_CANONICAL_HOST])dnl
+AC_REQUIRE([AC_CANONICAL_BUILD])dnl
+AC_REQUIRE([AC_PROG_CC])dnl
+AC_REQUIRE([AC_PROG_LD])dnl
+AC_REQUIRE([AC_PROG_LD_RELOAD_FLAG])dnl
+AC_REQUIRE([AC_PROG_NM])dnl
+AC_REQUIRE([AC_PROG_LN_S])dnl
+AC_REQUIRE([AC_DEPLIBS_CHECK_METHOD])dnl
+AC_REQUIRE([AC_OBJEXT])dnl
+AC_REQUIRE([AC_EXEEXT])dnl
+dnl
+
+_LT_AC_PROG_ECHO_BACKSLASH
+# Only perform the check for file, if the check method requires it
+case $deplibs_check_method in
+file_magic*)
+ if test "$file_magic_cmd" = '$MAGIC_CMD'; then
+ AC_PATH_MAGIC
+ fi
+ ;;
+esac
+
+AC_CHECK_TOOL(RANLIB, ranlib, :)
+AC_CHECK_TOOL(STRIP, strip, :)
+
+ifdef([AC_PROVIDE_AC_LIBTOOL_DLOPEN], enable_dlopen=yes, enable_dlopen=no)
+ifdef([AC_PROVIDE_AC_LIBTOOL_WIN32_DLL],
+enable_win32_dll=yes, enable_win32_dll=no)
+
+AC_ARG_ENABLE(libtool-lock,
+ [ --disable-libtool-lock avoid locking (might break parallel builds)])
+test "x$enable_libtool_lock" != xno && enable_libtool_lock=yes
+
+# Some flags need to be propagated to the compiler or linker for good
+# libtool support.
+case $host in
+*-*-irix6*)
+ # Find out which ABI we are using.
+ echo '[#]line __oline__ "configure"' > conftest.$ac_ext
+ if AC_TRY_EVAL(ac_compile); then
+ case `/usr/bin/file conftest.$ac_objext` in
+ *32-bit*)
+ LD="${LD-ld} -32"
+ ;;
+ *N32*)
+ LD="${LD-ld} -n32"
+ ;;
+ *64-bit*)
+ LD="${LD-ld} -64"
+ ;;
+ esac
+ fi
+ rm -rf conftest*
+ ;;
+
+*-*-sco3.2v5*)
+ # On SCO OpenServer 5, we need -belf to get full-featured binaries.
+ SAVE_CFLAGS="$CFLAGS"
+ CFLAGS="$CFLAGS -belf"
+ AC_CACHE_CHECK([whether the C compiler needs -belf], lt_cv_cc_needs_belf,
+ [AC_LANG_SAVE
+ AC_LANG_C
+ AC_TRY_LINK([],[],[lt_cv_cc_needs_belf=yes],[lt_cv_cc_needs_belf=no])
+ AC_LANG_RESTORE])
+ if test x"$lt_cv_cc_needs_belf" != x"yes"; then
+ # this is probably gcc 2.8.0, egcs 1.0 or newer; no need for -belf
+ CFLAGS="$SAVE_CFLAGS"
+ fi
+ ;;
+
+ifdef([AC_PROVIDE_AC_LIBTOOL_WIN32_DLL],
+[*-*-cygwin* | *-*-mingw* | *-*-pw32*)
+ AC_CHECK_TOOL(DLLTOOL, dlltool, false)
+ AC_CHECK_TOOL(AS, as, false)
+ AC_CHECK_TOOL(OBJDUMP, objdump, false)
+
+ # recent cygwin and mingw systems supply a stub DllMain which the user
+ # can override, but on older systems we have to supply one
+ AC_CACHE_CHECK([if libtool should supply DllMain function], lt_cv_need_dllmain,
+ [AC_TRY_LINK([],
+ [extern int __attribute__((__stdcall__)) DllMain(void*, int, void*);
+ DllMain (0, 0, 0);],
+ [lt_cv_need_dllmain=no],[lt_cv_need_dllmain=yes])])
+
+ case $host/$CC in
+ *-*-cygwin*/gcc*-mno-cygwin*|*-*-mingw*)
+ # old mingw systems require "-dll" to link a DLL, while more recent ones
+ # require "-mdll"
+ SAVE_CFLAGS="$CFLAGS"
+ CFLAGS="$CFLAGS -mdll"
+ AC_CACHE_CHECK([how to link DLLs], lt_cv_cc_dll_switch,
+ [AC_TRY_LINK([], [], [lt_cv_cc_dll_switch=-mdll],[lt_cv_cc_dll_switch=-dll])])
+ CFLAGS="$SAVE_CFLAGS" ;;
+ *-*-cygwin* | *-*-pw32*)
+ # cygwin systems need to pass --dll to the linker, and not link
+ # crt.o which will require a WinMain@16 definition.
+ lt_cv_cc_dll_switch="-Wl,--dll -nostartfiles" ;;
+ esac
+ ;;
+ ])
+esac
+
+_LT_AC_LTCONFIG_HACK
+
+])
+
+# AC_LIBTOOL_HEADER_ASSERT
+# ------------------------
+AC_DEFUN([AC_LIBTOOL_HEADER_ASSERT],
+[AC_CACHE_CHECK([whether $CC supports assert without backlinking],
+ [lt_cv_func_assert_works],
+ [case $host in
+ *-*-solaris*)
+ if test "$GCC" = yes && test "$with_gnu_ld" != yes; then
+ case `$CC --version 2>/dev/null` in
+ [[12]].*) lt_cv_func_assert_works=no ;;
+ *) lt_cv_func_assert_works=yes ;;
+ esac
+ fi
+ ;;
+ esac])
+
+if test "x$lt_cv_func_assert_works" = xyes; then
+ AC_CHECK_HEADERS(assert.h)
+fi
+])# AC_LIBTOOL_HEADER_ASSERT
+
+# _LT_AC_CHECK_DLFCN
+# --------------------
+AC_DEFUN([_LT_AC_CHECK_DLFCN],
+[AC_CHECK_HEADERS(dlfcn.h)
+])# _LT_AC_CHECK_DLFCN
+
+# AC_LIBTOOL_SYS_GLOBAL_SYMBOL_PIPE
+# ---------------------------------
+AC_DEFUN([AC_LIBTOOL_SYS_GLOBAL_SYMBOL_PIPE],
+[AC_REQUIRE([AC_CANONICAL_HOST])
+AC_REQUIRE([AC_PROG_NM])
+AC_REQUIRE([AC_OBJEXT])
+# Check for command to grab the raw symbol name followed by C symbol from nm.
+AC_MSG_CHECKING([command to parse $NM output])
+AC_CACHE_VAL([lt_cv_sys_global_symbol_pipe], [dnl
+
+# These are sane defaults that work on at least a few old systems.
+# [They come from Ultrix. What could be older than Ultrix?!! ;)]
+
+# Character class describing NM global symbol codes.
+symcode='[[BCDEGRST]]'
+
+# Regexp to match symbols that can be accessed directly from C.
+sympat='\([[_A-Za-z]][[_A-Za-z0-9]]*\)'
+
+# Transform the above into a raw symbol and a C symbol.
+symxfrm='\1 \2\3 \3'
+
+# Transform an extracted symbol line into a proper C declaration
+lt_cv_global_symbol_to_cdecl="sed -n -e 's/^. .* \(.*\)$/extern char \1;/p'"
+
+# Transform an extracted symbol line into symbol name and symbol address
+lt_cv_global_symbol_to_c_name_address="sed -n -e 's/^: \([[^ ]]*\) $/ {\\\"\1\\\", (lt_ptr) 0},/p' -e 's/^$symcode \([[^ ]]*\) \([[^ ]]*\)$/ {\"\2\", (lt_ptr) \&\2},/p'"
+
+# Define system-specific variables.
+case $host_os in
+aix*)
+ symcode='[[BCDT]]'
+ ;;
+cygwin* | mingw* | pw32*)
+ symcode='[[ABCDGISTW]]'
+ ;;
+hpux*) # Its linker distinguishes data from code symbols
+ lt_cv_global_symbol_to_cdecl="sed -n -e 's/^T .* \(.*\)$/extern char \1();/p' -e 's/^$symcode* .* \(.*\)$/extern char \1;/p'"
+ lt_cv_global_symbol_to_c_name_address="sed -n -e 's/^: \([[^ ]]*\) $/ {\\\"\1\\\", (lt_ptr) 0},/p' -e 's/^$symcode* \([[^ ]]*\) \([[^ ]]*\)$/ {\"\2\", (lt_ptr) \&\2},/p'"
+ ;;
+irix*)
+ symcode='[[BCDEGRST]]'
+ ;;
+solaris* | sysv5*)
+ symcode='[[BDT]]'
+ ;;
+sysv4)
+ symcode='[[DFNSTU]]'
+ ;;
+esac
+
+# Handle CRLF in mingw tool chain
+opt_cr=
+case $host_os in
+mingw*)
+ opt_cr=`echo 'x\{0,1\}' | tr x '\015'` # option cr in regexp
+ ;;
+esac
+
+# If we're using GNU nm, then use its standard symbol codes.
+if $NM -V 2>&1 | egrep '(GNU|with BFD)' > /dev/null; then
+ symcode='[[ABCDGISTW]]'
+fi
+
+# Try without a prefix undercore, then with it.
+for ac_symprfx in "" "_"; do
+
+ # Write the raw and C identifiers.
+lt_cv_sys_global_symbol_pipe="sed -n -e 's/^.*[[ ]]\($symcode$symcode*\)[[ ]][[ ]]*\($ac_symprfx\)$sympat$opt_cr$/$symxfrm/p'"
+
+ # Check to see that the pipe works correctly.
+ pipe_works=no
+ rm -f conftest*
+ cat > conftest.$ac_ext <<EOF
+#ifdef __cplusplus
+extern "C" {
+#endif
+char nm_test_var;
+void nm_test_func(){}
+#ifdef __cplusplus
+}
+#endif
+int main(){nm_test_var='a';nm_test_func();return(0);}
+EOF
+
+ if AC_TRY_EVAL(ac_compile); then
+ # Now try to grab the symbols.
+ nlist=conftest.nm
+ if AC_TRY_EVAL(NM conftest.$ac_objext \| $lt_cv_sys_global_symbol_pipe \> $nlist) && test -s "$nlist"; then
+ # Try sorting and uniquifying the output.
+ if sort "$nlist" | uniq > "$nlist"T; then
+ mv -f "$nlist"T "$nlist"
+ else
+ rm -f "$nlist"T
+ fi
+
+ # Make sure that we snagged all the symbols we need.
+ if egrep ' nm_test_var$' "$nlist" >/dev/null; then
+ if egrep ' nm_test_func$' "$nlist" >/dev/null; then
+ cat <<EOF > conftest.$ac_ext
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+EOF
+ # Now generate the symbol file.
+ eval "$lt_cv_global_symbol_to_cdecl"' < "$nlist" >> conftest.$ac_ext'
+
+ cat <<EOF >> conftest.$ac_ext
+#if defined (__STDC__) && __STDC__
+# define lt_ptr void *
+#else
+# define lt_ptr char *
+# define const
+#endif
+
+/* The mapping between symbol names and symbols. */
+const struct {
+ const char *name;
+ lt_ptr address;
+}
+lt_preloaded_symbols[[]] =
+{
+EOF
+ sed "s/^$symcode$symcode* \(.*\) \(.*\)$/ {\"\2\", (lt_ptr) \&\2},/" < "$nlist" >> conftest.$ac_ext
+ cat <<\EOF >> conftest.$ac_ext
+ {0, (lt_ptr) 0}
+};
+
+#ifdef __cplusplus
+}
+#endif
+EOF
+ # Now try linking the two files.
+ mv conftest.$ac_objext conftstm.$ac_objext
+ save_LIBS="$LIBS"
+ save_CFLAGS="$CFLAGS"
+ LIBS="conftstm.$ac_objext"
+ CFLAGS="$CFLAGS$no_builtin_flag"
+ if AC_TRY_EVAL(ac_link) && test -s conftest; then
+ pipe_works=yes
+ fi
+ LIBS="$save_LIBS"
+ CFLAGS="$save_CFLAGS"
+ else
+ echo "cannot find nm_test_func in $nlist" >&AC_FD_CC
+ fi
+ else
+ echo "cannot find nm_test_var in $nlist" >&AC_FD_CC
+ fi
+ else
+ echo "cannot run $lt_cv_sys_global_symbol_pipe" >&AC_FD_CC
+ fi
+ else
+ echo "$progname: failed program was:" >&AC_FD_CC
+ cat conftest.$ac_ext >&5
+ fi
+ rm -f conftest* conftst*
+
+ # Do not use the global_symbol_pipe unless it works.
+ if test "$pipe_works" = yes; then
+ break
+ else
+ lt_cv_sys_global_symbol_pipe=
+ fi
+done
+])
+global_symbol_pipe="$lt_cv_sys_global_symbol_pipe"
+if test -z "$lt_cv_sys_global_symbol_pipe"; then
+ global_symbol_to_cdecl=
+ global_symbol_to_c_name_address=
+else
+ global_symbol_to_cdecl="$lt_cv_global_symbol_to_cdecl"
+ global_symbol_to_c_name_address="$lt_cv_global_symbol_to_c_name_address"
+fi
+if test -z "$global_symbol_pipe$global_symbol_to_cdec$global_symbol_to_c_name_address";
+then
+ AC_MSG_RESULT(failed)
+else
+ AC_MSG_RESULT(ok)
+fi
+]) # AC_LIBTOOL_SYS_GLOBAL_SYMBOL_PIPE
+
+# _LT_AC_LIBTOOL_SYS_PATH_SEPARATOR
+# ---------------------------------
+AC_DEFUN([_LT_AC_LIBTOOL_SYS_PATH_SEPARATOR],
+[# Find the correct PATH separator. Usually this is `:', but
+# DJGPP uses `;' like DOS.
+if test "X${PATH_SEPARATOR+set}" != Xset; then
+ UNAME=${UNAME-`uname 2>/dev/null`}
+ case X$UNAME in
+ *-DOS) lt_cv_sys_path_separator=';' ;;
+ *) lt_cv_sys_path_separator=':' ;;
+ esac
+ PATH_SEPARATOR=$lt_cv_sys_path_separator
+fi
+])# _LT_AC_LIBTOOL_SYS_PATH_SEPARATOR
+
+# _LT_AC_PROG_ECHO_BACKSLASH
+# --------------------------
+# Add some code to the start of the generated configure script which
+# will find an echo command which doesn't interpret backslashes.
+AC_DEFUN([_LT_AC_PROG_ECHO_BACKSLASH],
+[ifdef([AC_DIVERSION_NOTICE], [AC_DIVERT_PUSH(AC_DIVERSION_NOTICE)],
+ [AC_DIVERT_PUSH(NOTICE)])
+_LT_AC_LIBTOOL_SYS_PATH_SEPARATOR
+
+# Check that we are running under the correct shell.
+SHELL=${CONFIG_SHELL-/bin/sh}
+
+case X$ECHO in
+X*--fallback-echo)
+ # Remove one level of quotation (which was required for Make).
+ ECHO=`echo "$ECHO" | sed 's,\\\\\[$]\\[$]0,'[$]0','`
+ ;;
+esac
+
+echo=${ECHO-echo}
+if test "X[$]1" = X--no-reexec; then
+ # Discard the --no-reexec flag, and continue.
+ shift
+elif test "X[$]1" = X--fallback-echo; then
+ # Avoid inline document here, it may be left over
+ :
+elif test "X`($echo '\t') 2>/dev/null`" = 'X\t'; then
+ # Yippee, $echo works!
+ :
+else
+ # Restart under the correct shell.
+ exec $SHELL "[$]0" --no-reexec ${1+"[$]@"}
+fi
+
+if test "X[$]1" = X--fallback-echo; then
+ # used as fallback echo
+ shift
+ cat <<EOF
+$*
+EOF
+ exit 0
+fi
+
+# The HP-UX ksh and POSIX shell print the target directory to stdout
+# if CDPATH is set.
+if test "X${CDPATH+set}" = Xset; then CDPATH=:; export CDPATH; fi
+
+if test -z "$ECHO"; then
+if test "X${echo_test_string+set}" != Xset; then
+# find a string as large as possible, as long as the shell can cope with it
+ for cmd in 'sed 50q "[$]0"' 'sed 20q "[$]0"' 'sed 10q "[$]0"' 'sed 2q "[$]0"' 'echo test'; do
+ # expected sizes: less than 2Kb, 1Kb, 512 bytes, 16 bytes, ...
+ if (echo_test_string="`eval $cmd`") 2>/dev/null &&
+ echo_test_string="`eval $cmd`" &&
+ (test "X$echo_test_string" = "X$echo_test_string") 2>/dev/null
+ then
+ break
+ fi
+ done
+fi
+
+if test "X`($echo '\t') 2>/dev/null`" = 'X\t' &&
+ echo_testing_string=`($echo "$echo_test_string") 2>/dev/null` &&
+ test "X$echo_testing_string" = "X$echo_test_string"; then
+ :
+else
+ # The Solaris, AIX, and Digital Unix default echo programs unquote
+ # backslashes. This makes it impossible to quote backslashes using
+ # echo "$something" | sed 's/\\/\\\\/g'
+ #
+ # So, first we look for a working echo in the user's PATH.
+
+ IFS="${IFS= }"; save_ifs="$IFS"; IFS=$PATH_SEPARATOR
+ for dir in $PATH /usr/ucb; do
+ if (test -f $dir/echo || test -f $dir/echo$ac_exeext) &&
+ test "X`($dir/echo '\t') 2>/dev/null`" = 'X\t' &&
+ echo_testing_string=`($dir/echo "$echo_test_string") 2>/dev/null` &&
+ test "X$echo_testing_string" = "X$echo_test_string"; then
+ echo="$dir/echo"
+ break
+ fi
+ done
+ IFS="$save_ifs"
+
+ if test "X$echo" = Xecho; then
+ # We didn't find a better echo, so look for alternatives.
+ if test "X`(print -r '\t') 2>/dev/null`" = 'X\t' &&
+ echo_testing_string=`(print -r "$echo_test_string") 2>/dev/null` &&
+ test "X$echo_testing_string" = "X$echo_test_string"; then
+ # This shell has a builtin print -r that does the trick.
+ echo='print -r'
+ elif (test -f /bin/ksh || test -f /bin/ksh$ac_exeext) &&
+ test "X$CONFIG_SHELL" != X/bin/ksh; then
+ # If we have ksh, try running configure again with it.
+ ORIGINAL_CONFIG_SHELL=${CONFIG_SHELL-/bin/sh}
+ export ORIGINAL_CONFIG_SHELL
+ CONFIG_SHELL=/bin/ksh
+ export CONFIG_SHELL
+ exec $CONFIG_SHELL "[$]0" --no-reexec ${1+"[$]@"}
+ else
+ # Try using printf.
+ echo='printf %s\n'
+ if test "X`($echo '\t') 2>/dev/null`" = 'X\t' &&
+ echo_testing_string=`($echo "$echo_test_string") 2>/dev/null` &&
+ test "X$echo_testing_string" = "X$echo_test_string"; then
+ # Cool, printf works
+ :
+ elif echo_testing_string=`($ORIGINAL_CONFIG_SHELL "[$]0" --fallback-echo '\t') 2>/dev/null` &&
+ test "X$echo_testing_string" = 'X\t' &&
+ echo_testing_string=`($ORIGINAL_CONFIG_SHELL "[$]0" --fallback-echo "$echo_test_string") 2>/dev/null` &&
+ test "X$echo_testing_string" = "X$echo_test_string"; then
+ CONFIG_SHELL=$ORIGINAL_CONFIG_SHELL
+ export CONFIG_SHELL
+ SHELL="$CONFIG_SHELL"
+ export SHELL
+ echo="$CONFIG_SHELL [$]0 --fallback-echo"
+ elif echo_testing_string=`($CONFIG_SHELL "[$]0" --fallback-echo '\t') 2>/dev/null` &&
+ test "X$echo_testing_string" = 'X\t' &&
+ echo_testing_string=`($CONFIG_SHELL "[$]0" --fallback-echo "$echo_test_string") 2>/dev/null` &&
+ test "X$echo_testing_string" = "X$echo_test_string"; then
+ echo="$CONFIG_SHELL [$]0 --fallback-echo"
+ else
+ # maybe with a smaller string...
+ prev=:
+
+ for cmd in 'echo test' 'sed 2q "[$]0"' 'sed 10q "[$]0"' 'sed 20q "[$]0"' 'sed 50q "[$]0"'; do
+ if (test "X$echo_test_string" = "X`eval $cmd`") 2>/dev/null
+ then
+ break
+ fi
+ prev="$cmd"
+ done
+
+ if test "$prev" != 'sed 50q "[$]0"'; then
+ echo_test_string=`eval $prev`
+ export echo_test_string
+ exec ${ORIGINAL_CONFIG_SHELL-${CONFIG_SHELL-/bin/sh}} "[$]0" ${1+"[$]@"}
+ else
+ # Oops. We lost completely, so just stick with echo.
+ echo=echo
+ fi
+ fi
+ fi
+ fi
+fi
+fi
+
+# Copy echo and quote the copy suitably for passing to libtool from
+# the Makefile, instead of quoting the original, which is used later.
+ECHO=$echo
+if test "X$ECHO" = "X$CONFIG_SHELL [$]0 --fallback-echo"; then
+ ECHO="$CONFIG_SHELL \\\$\[$]0 --fallback-echo"
+fi
+
+AC_SUBST(ECHO)
+AC_DIVERT_POP
+])# _LT_AC_PROG_ECHO_BACKSLASH
+
+# _LT_AC_TRY_DLOPEN_SELF (ACTION-IF-TRUE, ACTION-IF-TRUE-W-USCORE,
+# ACTION-IF-FALSE, ACTION-IF-CROSS-COMPILING)
+# ------------------------------------------------------------------
+AC_DEFUN([_LT_AC_TRY_DLOPEN_SELF],
+[if test "$cross_compiling" = yes; then :
+ [$4]
+else
+ AC_REQUIRE([_LT_AC_CHECK_DLFCN])dnl
+ lt_dlunknown=0; lt_dlno_uscore=1; lt_dlneed_uscore=2
+ lt_status=$lt_dlunknown
+ cat > conftest.$ac_ext <<EOF
+[#line __oline__ "configure"
+#include "confdefs.h"
+
+#if HAVE_DLFCN_H
+#include <dlfcn.h>
+#endif
+
+#include <stdio.h>
+
+#ifdef RTLD_GLOBAL
+# define LT_DLGLOBAL RTLD_GLOBAL
+#else
+# ifdef DL_GLOBAL
+# define LT_DLGLOBAL DL_GLOBAL
+# else
+# define LT_DLGLOBAL 0
+# endif
+#endif
+
+/* We may have to define LT_DLLAZY_OR_NOW in the command line if we
+ find out it does not work in some platform. */
+#ifndef LT_DLLAZY_OR_NOW
+# ifdef RTLD_LAZY
+# define LT_DLLAZY_OR_NOW RTLD_LAZY
+# else
+# ifdef DL_LAZY
+# define LT_DLLAZY_OR_NOW DL_LAZY
+# else
+# ifdef RTLD_NOW
+# define LT_DLLAZY_OR_NOW RTLD_NOW
+# else
+# ifdef DL_NOW
+# define LT_DLLAZY_OR_NOW DL_NOW
+# else
+# define LT_DLLAZY_OR_NOW 0
+# endif
+# endif
+# endif
+# endif
+#endif
+
+#ifdef __cplusplus
+extern "C" void exit (int);
+#endif
+
+void fnord() { int i=42;}
+int main ()
+{
+ void *self = dlopen (0, LT_DLGLOBAL|LT_DLLAZY_OR_NOW);
+ int status = $lt_dlunknown;
+
+ if (self)
+ {
+ if (dlsym (self,"fnord")) status = $lt_dlno_uscore;
+ else if (dlsym( self,"_fnord")) status = $lt_dlneed_uscore;
+ /* dlclose (self); */
+ }
+
+ exit (status);
+}]
+EOF
+ if AC_TRY_EVAL(ac_link) && test -s conftest${ac_exeext} 2>/dev/null; then
+ (./conftest; exit; ) 2>/dev/null
+ lt_status=$?
+ case x$lt_status in
+ x$lt_dlno_uscore) $1 ;;
+ x$lt_dlneed_uscore) $2 ;;
+ x$lt_unknown|x*) $3 ;;
+ esac
+ else :
+ # compilation failed
+ $3
+ fi
+fi
+rm -fr conftest*
+])# _LT_AC_TRY_DLOPEN_SELF
+
+# AC_LIBTOOL_DLOPEN_SELF
+# -------------------
+AC_DEFUN([AC_LIBTOOL_DLOPEN_SELF],
+[if test "x$enable_dlopen" != xyes; then
+ enable_dlopen=unknown
+ enable_dlopen_self=unknown
+ enable_dlopen_self_static=unknown
+else
+ lt_cv_dlopen=no
+ lt_cv_dlopen_libs=
+
+ case $host_os in
+ beos*)
+ lt_cv_dlopen="load_add_on"
+ lt_cv_dlopen_libs=
+ lt_cv_dlopen_self=yes
+ ;;
+
+ cygwin* | mingw* | pw32*)
+ lt_cv_dlopen="LoadLibrary"
+ lt_cv_dlopen_libs=
+ ;;
+
+ *)
+ AC_CHECK_FUNC([shl_load],
+ [lt_cv_dlopen="shl_load"],
+ [AC_CHECK_LIB([dld], [shl_load],
+ [lt_cv_dlopen="shl_load" lt_cv_dlopen_libs="-dld"],
+ [AC_CHECK_FUNC([dlopen],
+ [lt_cv_dlopen="dlopen"],
+ [AC_CHECK_LIB([dl], [dlopen],
+ [lt_cv_dlopen="dlopen" lt_cv_dlopen_libs="-ldl"],
+ [AC_CHECK_LIB([svld], [dlopen],
+ [lt_cv_dlopen="dlopen" lt_cv_dlopen_libs="-lsvld"],
+ [AC_CHECK_LIB([dld], [dld_link],
+ [lt_cv_dlopen="dld_link" lt_cv_dlopen_libs="-dld"])
+ ])
+ ])
+ ])
+ ])
+ ])
+ ;;
+ esac
+
+ if test "x$lt_cv_dlopen" != xno; then
+ enable_dlopen=yes
+ else
+ enable_dlopen=no
+ fi
+
+ case $lt_cv_dlopen in
+ dlopen)
+ save_CPPFLAGS="$CPPFLAGS"
+ AC_REQUIRE([_LT_AC_CHECK_DLFCN])dnl
+ test "x$ac_cv_header_dlfcn_h" = xyes && CPPFLAGS="$CPPFLAGS -DHAVE_DLFCN_H"
+
+ save_LDFLAGS="$LDFLAGS"
+ eval LDFLAGS=\"\$LDFLAGS $export_dynamic_flag_spec\"
+
+ save_LIBS="$LIBS"
+ LIBS="$lt_cv_dlopen_libs $LIBS"
+
+ AC_CACHE_CHECK([whether a program can dlopen itself],
+ lt_cv_dlopen_self, [dnl
+ _LT_AC_TRY_DLOPEN_SELF(
+ lt_cv_dlopen_self=yes, lt_cv_dlopen_self=yes,
+ lt_cv_dlopen_self=no, lt_cv_dlopen_self=cross)
+ ])
+
+ if test "x$lt_cv_dlopen_self" = xyes; then
+ LDFLAGS="$LDFLAGS $link_static_flag"
+ AC_CACHE_CHECK([whether a statically linked program can dlopen itself],
+ lt_cv_dlopen_self_static, [dnl
+ _LT_AC_TRY_DLOPEN_SELF(
+ lt_cv_dlopen_self_static=yes, lt_cv_dlopen_self_static=yes,
+ lt_cv_dlopen_self_static=no, lt_cv_dlopen_self_static=cross)
+ ])
+ fi
+
+ CPPFLAGS="$save_CPPFLAGS"
+ LDFLAGS="$save_LDFLAGS"
+ LIBS="$save_LIBS"
+ ;;
+ esac
+
+ case $lt_cv_dlopen_self in
+ yes|no) enable_dlopen_self=$lt_cv_dlopen_self ;;
+ *) enable_dlopen_self=unknown ;;
+ esac
+
+ case $lt_cv_dlopen_self_static in
+ yes|no) enable_dlopen_self_static=$lt_cv_dlopen_self_static ;;
+ *) enable_dlopen_self_static=unknown ;;
+ esac
+fi
+])# AC_LIBTOOL_DLOPEN_SELF
+
+AC_DEFUN([_LT_AC_LTCONFIG_HACK],
+[AC_REQUIRE([AC_LIBTOOL_SYS_GLOBAL_SYMBOL_PIPE])dnl
+# Sed substitution that helps us do robust quoting. It backslashifies
+# metacharacters that are still active within double-quoted strings.
+Xsed='sed -e s/^X//'
+sed_quote_subst='s/\([[\\"\\`$\\\\]]\)/\\\1/g'
+
+# Same as above, but do not quote variable references.
+double_quote_subst='s/\([[\\"\\`\\\\]]\)/\\\1/g'
+
+# Sed substitution to delay expansion of an escaped shell variable in a
+# double_quote_subst'ed string.
+delay_variable_subst='s/\\\\\\\\\\\$/\\\\\\$/g'
+
+# Constants:
+rm="rm -f"
+
+# Global variables:
+default_ofile=libtool
+can_build_shared=yes
+
+# All known linkers require a `.a' archive for static linking (except M$VC,
+# which needs '.lib').
+libext=a
+ltmain="$ac_aux_dir/ltmain.sh"
+ofile="$default_ofile"
+with_gnu_ld="$lt_cv_prog_gnu_ld"
+need_locks="$enable_libtool_lock"
+
+old_CC="$CC"
+old_CFLAGS="$CFLAGS"
+
+# Set sane defaults for various variables
+test -z "$AR" && AR=ar
+test -z "$AR_FLAGS" && AR_FLAGS=cru
+test -z "$AS" && AS=as
+test -z "$CC" && CC=cc
+test -z "$DLLTOOL" && DLLTOOL=dlltool
+test -z "$LD" && LD=ld
+test -z "$LN_S" && LN_S="ln -s"
+test -z "$MAGIC_CMD" && MAGIC_CMD=file
+test -z "$NM" && NM=nm
+test -z "$OBJDUMP" && OBJDUMP=objdump
+test -z "$RANLIB" && RANLIB=:
+test -z "$STRIP" && STRIP=:
+test -z "$ac_objext" && ac_objext=o
+
+if test x"$host" != x"$build"; then
+ ac_tool_prefix=${host_alias}-
+else
+ ac_tool_prefix=
+fi
+
+# Transform linux* to *-*-linux-gnu*, to support old configure scripts.
+case $host_os in
+linux-gnu*) ;;
+linux*) host=`echo $host | sed 's/^\(.*-.*-linux\)\(.*\)$/\1-gnu\2/'`
+esac
+
+case $host_os in
+aix3*)
+ # AIX sometimes has problems with the GCC collect2 program. For some
+ # reason, if we set the COLLECT_NAMES environment variable, the problems
+ # vanish in a puff of smoke.
+ if test "X${COLLECT_NAMES+set}" != Xset; then
+ COLLECT_NAMES=
+ export COLLECT_NAMES
+ fi
+ ;;
+esac
+
+# Determine commands to create old-style static archives.
+old_archive_cmds='$AR $AR_FLAGS $oldlib$oldobjs$old_deplibs'
+old_postinstall_cmds='chmod 644 $oldlib'
+old_postuninstall_cmds=
+
+if test -n "$RANLIB"; then
+ case $host_os in
+ openbsd*)
+ old_postinstall_cmds="\$RANLIB -t \$oldlib~$old_postinstall_cmds"
+ ;;
+ *)
+ old_postinstall_cmds="\$RANLIB \$oldlib~$old_postinstall_cmds"
+ ;;
+ esac
+ old_archive_cmds="$old_archive_cmds~\$RANLIB \$oldlib"
+fi
+
+# Allow CC to be a program name with arguments.
+set dummy $CC
+compiler="[$]2"
+
+## FIXME: this should be a separate macro
+##
+AC_MSG_CHECKING([for objdir])
+rm -f .libs 2>/dev/null
+mkdir .libs 2>/dev/null
+if test -d .libs; then
+ objdir=.libs
+else
+ # MS-DOS does not allow filenames that begin with a dot.
+ objdir=_libs
+fi
+rmdir .libs 2>/dev/null
+AC_MSG_RESULT($objdir)
+##
+## END FIXME
+
+
+## FIXME: this should be a separate macro
+##
+AC_ARG_WITH(pic,
+[ --with-pic try to use only PIC/non-PIC objects [default=use both]],
+pic_mode="$withval", pic_mode=default)
+test -z "$pic_mode" && pic_mode=default
+
+# We assume here that the value for lt_cv_prog_cc_pic will not be cached
+# in isolation, and that seeing it set (from the cache) indicates that
+# the associated values are set (in the cache) correctly too.
+AC_MSG_CHECKING([for $compiler option to produce PIC])
+AC_CACHE_VAL(lt_cv_prog_cc_pic,
+[ lt_cv_prog_cc_pic=
+ lt_cv_prog_cc_shlib=
+ lt_cv_prog_cc_wl=
+ lt_cv_prog_cc_static=
+ lt_cv_prog_cc_no_builtin=
+ lt_cv_prog_cc_can_build_shared=$can_build_shared
+
+ if test "$GCC" = yes; then
+ lt_cv_prog_cc_wl='-Wl,'
+ lt_cv_prog_cc_static='-static'
+
+ case $host_os in
+ aix*)
+ # Below there is a dirty hack to force normal static linking with -ldl
+ # The problem is because libdl dynamically linked with both libc and
+ # libC (AIX C++ library), which obviously doesn't included in libraries
+ # list by gcc. This cause undefined symbols with -static flags.
+ # This hack allows C programs to be linked with "-static -ldl", but
+ # not sure about C++ programs.
+ lt_cv_prog_cc_static="$lt_cv_prog_cc_static ${lt_cv_prog_cc_wl}-lC"
+ ;;
+ amigaos*)
+ # FIXME: we need at least 68020 code to build shared libraries, but
+ # adding the `-m68020' flag to GCC prevents building anything better,
+ # like `-m68040'.
+ lt_cv_prog_cc_pic='-m68020 -resident32 -malways-restore-a4'
+ ;;
+ beos* | irix5* | irix6* | osf3* | osf4* | osf5*)
+ # PIC is the default for these OSes.
+ ;;
+ darwin* | rhapsody*)
+ # PIC is the default on this platform
+ # Common symbols not allowed in MH_DYLIB files
+ lt_cv_prog_cc_pic='-fno-common'
+ ;;
+ cygwin* | mingw* | pw32* | os2*)
+ # This hack is so that the source file can tell whether it is being
+ # built for inclusion in a dll (and should export symbols for example).
+ lt_cv_prog_cc_pic='-DDLL_EXPORT'
+ ;;
+ sysv4*MP*)
+ if test -d /usr/nec; then
+ lt_cv_prog_cc_pic=-Kconform_pic
+ fi
+ ;;
+ *)
+ lt_cv_prog_cc_pic='-fPIC'
+ ;;
+ esac
+ else
+ # PORTME Check for PIC flags for the system compiler.
+ case $host_os in
+ aix3* | aix4* | aix5*)
+ lt_cv_prog_cc_wl='-Wl,'
+ # All AIX code is PIC.
+ if test "$host_cpu" = ia64; then
+ # AIX 5 now supports IA64 processor
+ lt_cv_prog_cc_static='-Bstatic'
+ else
+ lt_cv_prog_cc_static='-bnso -bI:/lib/syscalls.exp'
+ fi
+ ;;
+
+ hpux9* | hpux10* | hpux11*)
+ # Is there a better lt_cv_prog_cc_static that works with the bundled CC?
+ lt_cv_prog_cc_wl='-Wl,'
+ lt_cv_prog_cc_static="${lt_cv_prog_cc_wl}-a ${lt_cv_prog_cc_wl}archive"
+ lt_cv_prog_cc_pic='+Z'
+ ;;
+
+ irix5* | irix6*)
+ lt_cv_prog_cc_wl='-Wl,'
+ lt_cv_prog_cc_static='-non_shared'
+ # PIC (with -KPIC) is the default.
+ ;;
+
+ cygwin* | mingw* | pw32* | os2*)
+ # This hack is so that the source file can tell whether it is being
+ # built for inclusion in a dll (and should export symbols for example).
+ lt_cv_prog_cc_pic='-DDLL_EXPORT'
+ ;;
+
+ newsos6)
+ lt_cv_prog_cc_pic='-KPIC'
+ lt_cv_prog_cc_static='-Bstatic'
+ ;;
+
+ osf3* | osf4* | osf5*)
+ # All OSF/1 code is PIC.
+ lt_cv_prog_cc_wl='-Wl,'
+ lt_cv_prog_cc_static='-non_shared'
+ ;;
+
+ sco3.2v5*)
+ lt_cv_prog_cc_pic='-Kpic'
+ lt_cv_prog_cc_static='-dn'
+ lt_cv_prog_cc_shlib='-belf'
+ ;;
+
+ solaris*)
+ lt_cv_prog_cc_pic='-KPIC'
+ lt_cv_prog_cc_static='-Bstatic'
+ lt_cv_prog_cc_wl='-Wl,'
+ ;;
+
+ sunos4*)
+ lt_cv_prog_cc_pic='-PIC'
+ lt_cv_prog_cc_static='-Bstatic'
+ lt_cv_prog_cc_wl='-Qoption ld '
+ ;;
+
+ sysv4 | sysv4.2uw2* | sysv4.3* | sysv5*)
+ lt_cv_prog_cc_pic='-KPIC'
+ lt_cv_prog_cc_static='-Bstatic'
+ if test "x$host_vendor" = xsni; then
+ lt_cv_prog_cc_wl='-LD'
+ else
+ lt_cv_prog_cc_wl='-Wl,'
+ fi
+ ;;
+
+ uts4*)
+ lt_cv_prog_cc_pic='-pic'
+ lt_cv_prog_cc_static='-Bstatic'
+ ;;
+
+ sysv4*MP*)
+ if test -d /usr/nec ;then
+ lt_cv_prog_cc_pic='-Kconform_pic'
+ lt_cv_prog_cc_static='-Bstatic'
+ fi
+ ;;
+
+ *)
+ lt_cv_prog_cc_can_build_shared=no
+ ;;
+ esac
+ fi
+])
+if test -z "$lt_cv_prog_cc_pic"; then
+ AC_MSG_RESULT([none])
+else
+ AC_MSG_RESULT([$lt_cv_prog_cc_pic])
+
+ # Check to make sure the pic_flag actually works.
+ AC_MSG_CHECKING([if $compiler PIC flag $lt_cv_prog_cc_pic works])
+ AC_CACHE_VAL(lt_cv_prog_cc_pic_works, [dnl
+ save_CFLAGS="$CFLAGS"
+ CFLAGS="$CFLAGS $lt_cv_prog_cc_pic -DPIC"
+ AC_TRY_COMPILE([], [], [dnl
+ case $host_os in
+ hpux9* | hpux10* | hpux11*)
+ # On HP-UX, both CC and GCC only warn that PIC is supported... then
+ # they create non-PIC objects. So, if there were any warnings, we
+ # assume that PIC is not supported.
+ if test -s conftest.err; then
+ lt_cv_prog_cc_pic_works=no
+ else
+ lt_cv_prog_cc_pic_works=yes
+ fi
+ ;;
+ *)
+ lt_cv_prog_cc_pic_works=yes
+ ;;
+ esac
+ ], [dnl
+ lt_cv_prog_cc_pic_works=no
+ ])
+ CFLAGS="$save_CFLAGS"
+ ])
+
+ if test "X$lt_cv_prog_cc_pic_works" = Xno; then
+ lt_cv_prog_cc_pic=
+ lt_cv_prog_cc_can_build_shared=no
+ else
+ lt_cv_prog_cc_pic=" $lt_cv_prog_cc_pic"
+ fi
+
+ AC_MSG_RESULT([$lt_cv_prog_cc_pic_works])
+fi
+##
+## END FIXME
+
+# Check for any special shared library compilation flags.
+if test -n "$lt_cv_prog_cc_shlib"; then
+ AC_MSG_WARN([\`$CC' requires \`$lt_cv_prog_cc_shlib' to build shared libraries])
+ if echo "$old_CC $old_CFLAGS " | egrep -e "[[ ]]$lt_cv_prog_cc_shlib[[ ]]" >/dev/null; then :
+ else
+ AC_MSG_WARN([add \`$lt_cv_prog_cc_shlib' to the CC or CFLAGS env variable and reconfigure])
+ lt_cv_prog_cc_can_build_shared=no
+ fi
+fi
+
+## FIXME: this should be a separate macro
+##
+AC_MSG_CHECKING([if $compiler static flag $lt_cv_prog_cc_static works])
+AC_CACHE_VAL([lt_cv_prog_cc_static_works], [dnl
+ lt_cv_prog_cc_static_works=no
+ save_LDFLAGS="$LDFLAGS"
+ LDFLAGS="$LDFLAGS $lt_cv_prog_cc_static"
+ AC_TRY_LINK([], [], [lt_cv_prog_cc_static_works=yes])
+ LDFLAGS="$save_LDFLAGS"
+])
+
+# Belt *and* braces to stop my trousers falling down:
+test "X$lt_cv_prog_cc_static_works" = Xno && lt_cv_prog_cc_static=
+AC_MSG_RESULT([$lt_cv_prog_cc_static_works])
+
+pic_flag="$lt_cv_prog_cc_pic"
+special_shlib_compile_flags="$lt_cv_prog_cc_shlib"
+wl="$lt_cv_prog_cc_wl"
+link_static_flag="$lt_cv_prog_cc_static"
+no_builtin_flag="$lt_cv_prog_cc_no_builtin"
+can_build_shared="$lt_cv_prog_cc_can_build_shared"
+##
+## END FIXME
+
+
+## FIXME: this should be a separate macro
+##
+# Check to see if options -o and -c are simultaneously supported by compiler
+AC_MSG_CHECKING([if $compiler supports -c -o file.$ac_objext])
+AC_CACHE_VAL([lt_cv_compiler_c_o], [
+$rm -r conftest 2>/dev/null
+mkdir conftest
+cd conftest
+echo "int some_variable = 0;" > conftest.$ac_ext
+mkdir out
+# According to Tom Tromey, Ian Lance Taylor reported there are C compilers
+# that will create temporary files in the current directory regardless of
+# the output directory. Thus, making CWD read-only will cause this test
+# to fail, enabling locking or at least warning the user not to do parallel
+# builds.
+chmod -w .
+save_CFLAGS="$CFLAGS"
+CFLAGS="$CFLAGS -o out/conftest2.$ac_objext"
+compiler_c_o=no
+if { (eval echo configure:__oline__: \"$ac_compile\") 1>&5; (eval $ac_compile) 2>out/conftest.err; } && test -s out/conftest2.$ac_objext; then
+ # The compiler can only warn and ignore the option if not recognized
+ # So say no if there are warnings
+ if test -s out/conftest.err; then
+ lt_cv_compiler_c_o=no
+ else
+ lt_cv_compiler_c_o=yes
+ fi
+else
+ # Append any errors to the config.log.
+ cat out/conftest.err 1>&AC_FD_CC
+ lt_cv_compiler_c_o=no
+fi
+CFLAGS="$save_CFLAGS"
+chmod u+w .
+$rm conftest* out/*
+rmdir out
+cd ..
+rmdir conftest
+$rm -r conftest 2>/dev/null
+])
+compiler_c_o=$lt_cv_compiler_c_o
+AC_MSG_RESULT([$compiler_c_o])
+
+if test x"$compiler_c_o" = x"yes"; then
+ # Check to see if we can write to a .lo
+ AC_MSG_CHECKING([if $compiler supports -c -o file.lo])
+ AC_CACHE_VAL([lt_cv_compiler_o_lo], [
+ lt_cv_compiler_o_lo=no
+ save_CFLAGS="$CFLAGS"
+ CFLAGS="$CFLAGS -c -o conftest.lo"
+ save_objext="$ac_objext"
+ ac_objext=lo
+ AC_TRY_COMPILE([], [int some_variable = 0;], [dnl
+ # The compiler can only warn and ignore the option if not recognized
+ # So say no if there are warnings
+ if test -s conftest.err; then
+ lt_cv_compiler_o_lo=no
+ else
+ lt_cv_compiler_o_lo=yes
+ fi
+ ])
+ ac_objext="$save_objext"
+ CFLAGS="$save_CFLAGS"
+ ])
+ compiler_o_lo=$lt_cv_compiler_o_lo
+ AC_MSG_RESULT([$compiler_o_lo])
+else
+ compiler_o_lo=no
+fi
+##
+## END FIXME
+
+## FIXME: this should be a separate macro
+##
+# Check to see if we can do hard links to lock some files if needed
+hard_links="nottested"
+if test "$compiler_c_o" = no && test "$need_locks" != no; then
+ # do not overwrite the value of need_locks provided by the user
+ AC_MSG_CHECKING([if we can lock with hard links])
+ hard_links=yes
+ $rm conftest*
+ ln conftest.a conftest.b 2>/dev/null && hard_links=no
+ touch conftest.a
+ ln conftest.a conftest.b 2>&5 || hard_links=no
+ ln conftest.a conftest.b 2>/dev/null && hard_links=no
+ AC_MSG_RESULT([$hard_links])
+ if test "$hard_links" = no; then
+ AC_MSG_WARN([\`$CC' does not support \`-c -o', so \`make -j' may be unsafe])
+ need_locks=warn
+ fi
+else
+ need_locks=no
+fi
+##
+## END FIXME
+
+## FIXME: this should be a separate macro
+##
+if test "$GCC" = yes; then
+ # Check to see if options -fno-rtti -fno-exceptions are supported by compiler
+ AC_MSG_CHECKING([if $compiler supports -fno-rtti -fno-exceptions])
+ echo "int some_variable = 0;" > conftest.$ac_ext
+ save_CFLAGS="$CFLAGS"
+ CFLAGS="$CFLAGS -fno-rtti -fno-exceptions -c conftest.$ac_ext"
+ compiler_rtti_exceptions=no
+ AC_TRY_COMPILE([], [int some_variable = 0;], [dnl
+ # The compiler can only warn and ignore the option if not recognized
+ # So say no if there are warnings
+ if test -s conftest.err; then
+ compiler_rtti_exceptions=no
+ else
+ compiler_rtti_exceptions=yes
+ fi
+ ])
+ CFLAGS="$save_CFLAGS"
+ AC_MSG_RESULT([$compiler_rtti_exceptions])
+
+ if test "$compiler_rtti_exceptions" = "yes"; then
+ no_builtin_flag=' -fno-builtin -fno-rtti -fno-exceptions'
+ else
+ no_builtin_flag=' -fno-builtin'
+ fi
+fi
+##
+## END FIXME
+
+## FIXME: this should be a separate macro
+##
+# See if the linker supports building shared libraries.
+AC_MSG_CHECKING([whether the linker ($LD) supports shared libraries])
+
+allow_undefined_flag=
+no_undefined_flag=
+need_lib_prefix=unknown
+need_version=unknown
+# when you set need_version to no, make sure it does not cause -set_version
+# flags to be left without arguments
+archive_cmds=
+archive_expsym_cmds=
+old_archive_from_new_cmds=
+old_archive_from_expsyms_cmds=
+export_dynamic_flag_spec=
+whole_archive_flag_spec=
+thread_safe_flag_spec=
+hardcode_into_libs=no
+hardcode_libdir_flag_spec=
+hardcode_libdir_separator=
+hardcode_direct=no
+hardcode_minus_L=no
+hardcode_shlibpath_var=unsupported
+runpath_var=
+link_all_deplibs=unknown
+always_export_symbols=no
+export_symbols_cmds='$NM $libobjs $convenience | $global_symbol_pipe | sed '\''s/.* //'\'' | sort | uniq > $export_symbols'
+# include_expsyms should be a list of space-separated symbols to be *always*
+# included in the symbol list
+include_expsyms=
+# exclude_expsyms can be an egrep regular expression of symbols to exclude
+# it will be wrapped by ` (' and `)$', so one must not match beginning or
+# end of line. Example: `a|bc|.*d.*' will exclude the symbols `a' and `bc',
+# as well as any symbol that contains `d'.
+exclude_expsyms="_GLOBAL_OFFSET_TABLE_"
+# Although _GLOBAL_OFFSET_TABLE_ is a valid symbol C name, most a.out
+# platforms (ab)use it in PIC code, but their linkers get confused if
+# the symbol is explicitly referenced. Since portable code cannot
+# rely on this symbol name, it's probably fine to never include it in
+# preloaded symbol tables.
+extract_expsyms_cmds=
+
+case $host_os in
+cygwin* | mingw* | pw32*)
+ # FIXME: the MSVC++ port hasn't been tested in a loooong time
+ # When not using gcc, we currently assume that we are using
+ # Microsoft Visual C++.
+ if test "$GCC" != yes; then
+ with_gnu_ld=no
+ fi
+ ;;
+openbsd*)
+ with_gnu_ld=no
+ ;;
+esac
+
+ld_shlibs=yes
+if test "$with_gnu_ld" = yes; then
+ # If archive_cmds runs LD, not CC, wlarc should be empty
+ wlarc='${wl}'
+
+ # See if GNU ld supports shared libraries.
+ case $host_os in
+ aix3* | aix4* | aix5*)
+ # On AIX, the GNU linker is very broken
+ # Note:Check GNU linker on AIX 5-IA64 when/if it becomes available.
+ ld_shlibs=no
+ cat <<EOF 1>&2
+
+*** Warning: the GNU linker, at least up to release 2.9.1, is reported
+*** to be unable to reliably create shared libraries on AIX.
+*** Therefore, libtool is disabling shared libraries support. If you
+*** really care for shared libraries, you may want to modify your PATH
+*** so that a non-GNU linker is found, and then restart.
+
+EOF
+ ;;
+
+ amigaos*)
+ archive_cmds='$rm $output_objdir/a2ixlibrary.data~$echo "#define NAME $libname" > $output_objdir/a2ixlibrary.data~$echo "#define LIBRARY_ID 1" >> $output_objdir/a2ixlibrary.data~$echo "#define VERSION $major" >> $output_objdir/a2ixlibrary.data~$echo "#define REVISION $revision" >> $output_objdir/a2ixlibrary.data~$AR $AR_FLAGS $lib $libobjs~$RANLIB $lib~(cd $output_objdir && a2ixlibrary -32)'
+ hardcode_libdir_flag_spec='-L$libdir'
+ hardcode_minus_L=yes
+
+ # Samuel A. Falvo II <kc5tja@dolphin.openprojects.net> reports
+ # that the semantics of dynamic libraries on AmigaOS, at least up
+ # to version 4, is to share data among multiple programs linked
+ # with the same dynamic library. Since this doesn't match the
+ # behavior of shared libraries on other platforms, we can use
+ # them.
+ ld_shlibs=no
+ ;;
+
+ beos*)
+ if $LD --help 2>&1 | egrep ': supported targets:.* elf' > /dev/null; then
+ allow_undefined_flag=unsupported
+ # Joseph Beckenbach <jrb3@best.com> says some releases of gcc
+ # support --undefined. This deserves some investigation. FIXME
+ archive_cmds='$CC -nostart $libobjs $deplibs $compiler_flags ${wl}-soname $wl$soname -o $lib'
+ else
+ ld_shlibs=no
+ fi
+ ;;
+
+ cygwin* | mingw* | pw32*)
+ # hardcode_libdir_flag_spec is actually meaningless, as there is
+ # no search path for DLLs.
+ hardcode_libdir_flag_spec='-L$libdir'
+ allow_undefined_flag=unsupported
+ always_export_symbols=yes
+
+ extract_expsyms_cmds='test -f $output_objdir/impgen.c || \
+ sed -e "/^# \/\* impgen\.c starts here \*\//,/^# \/\* impgen.c ends here \*\// { s/^# //;s/^# *$//; p; }" -e d < $''0 > $output_objdir/impgen.c~
+ test -f $output_objdir/impgen.exe || (cd $output_objdir && \
+ if test "x$HOST_CC" != "x" ; then $HOST_CC -o impgen impgen.c ; \
+ else $CC -o impgen impgen.c ; fi)~
+ $output_objdir/impgen $dir/$soroot > $output_objdir/$soname-def'
+
+ old_archive_from_expsyms_cmds='$DLLTOOL --as=$AS --dllname $soname --def $output_objdir/$soname-def --output-lib $output_objdir/$newlib'
+
+ # cygwin and mingw dlls have different entry points and sets of symbols
+ # to exclude.
+ # FIXME: what about values for MSVC?
+ dll_entry=__cygwin_dll_entry@12
+ dll_exclude_symbols=DllMain@12,_cygwin_dll_entry@12,_cygwin_noncygwin_dll_entry@12~
+ case $host_os in
+ mingw*)
+ # mingw values
+ dll_entry=_DllMainCRTStartup@12
+ dll_exclude_symbols=DllMain@12,DllMainCRTStartup@12,DllEntryPoint@12~
+ ;;
+ esac
+
+ # mingw and cygwin differ, and it's simplest to just exclude the union
+ # of the two symbol sets.
+ dll_exclude_symbols=DllMain@12,_cygwin_dll_entry@12,_cygwin_noncygwin_dll_entry@12,DllMainCRTStartup@12,DllEntryPoint@12
+
+ # recent cygwin and mingw systems supply a stub DllMain which the user
+ # can override, but on older systems we have to supply one (in ltdll.c)
+ if test "x$lt_cv_need_dllmain" = "xyes"; then
+ ltdll_obj='$output_objdir/$soname-ltdll.'"$ac_objext "
+ ltdll_cmds='test -f $output_objdir/$soname-ltdll.c || sed -e "/^# \/\* ltdll\.c starts here \*\//,/^# \/\* ltdll.c ends here \*\// { s/^# //; p; }" -e d < $''0 > $output_objdir/$soname-ltdll.c~
+ test -f $output_objdir/$soname-ltdll.$ac_objext || (cd $output_objdir && $CC -c $soname-ltdll.c)~'
+ else
+ ltdll_obj=
+ ltdll_cmds=
+ fi
+
+ # Extract the symbol export list from an `--export-all' def file,
+ # then regenerate the def file from the symbol export list, so that
+ # the compiled dll only exports the symbol export list.
+ # Be careful not to strip the DATA tag left be newer dlltools.
+ export_symbols_cmds="$ltdll_cmds"'
+ $DLLTOOL --export-all --exclude-symbols '$dll_exclude_symbols' --output-def $output_objdir/$soname-def '$ltdll_obj'$libobjs $convenience~
+ sed -e "1,/EXPORTS/d" -e "s/ @ [[0-9]]*//" -e "s/ *;.*$//" < $output_objdir/$soname-def > $export_symbols'
+
+ # If the export-symbols file already is a .def file (1st line
+ # is EXPORTS), use it as is.
+ # If DATA tags from a recent dlltool are present, honour them!
+ archive_expsym_cmds='if test "x`head -1 $export_symbols`" = xEXPORTS; then
+ cp $export_symbols $output_objdir/$soname-def;
+ else
+ echo EXPORTS > $output_objdir/$soname-def;
+ _lt_hint=1;
+ cat $export_symbols | while read symbol; do
+ set dummy \$symbol;
+ case \[$]# in
+ 2) echo " \[$]2 @ \$_lt_hint ; " >> $output_objdir/$soname-def;;
+ *) echo " \[$]2 @ \$_lt_hint \[$]3 ; " >> $output_objdir/$soname-def;;
+ esac;
+ _lt_hint=`expr 1 + \$_lt_hint`;
+ done;
+ fi~
+ '"$ltdll_cmds"'
+ $CC -Wl,--base-file,$output_objdir/$soname-base '$lt_cv_cc_dll_switch' -Wl,-e,'$dll_entry' -o $output_objdir/$soname '$ltdll_obj'$libobjs $deplibs $compiler_flags~
+ $DLLTOOL --as=$AS --dllname $soname --exclude-symbols '$dll_exclude_symbols' --def $output_objdir/$soname-def --base-file $output_objdir/$soname-base --output-exp $output_objdir/$soname-exp~
+ $CC -Wl,--base-file,$output_objdir/$soname-base $output_objdir/$soname-exp '$lt_cv_cc_dll_switch' -Wl,-e,'$dll_entry' -o $output_objdir/$soname '$ltdll_obj'$libobjs $deplibs $compiler_flags~
+ $DLLTOOL --as=$AS --dllname $soname --exclude-symbols '$dll_exclude_symbols' --def $output_objdir/$soname-def --base-file $output_objdir/$soname-base --output-exp $output_objdir/$soname-exp --output-lib $output_objdir/$libname.dll.a~
+ $CC $output_objdir/$soname-exp '$lt_cv_cc_dll_switch' -Wl,-e,'$dll_entry' -o $output_objdir/$soname '$ltdll_obj'$libobjs $deplibs $compiler_flags'
+ ;;
+
+ netbsd*)
+ if echo __ELF__ | $CC -E - | grep __ELF__ >/dev/null; then
+ archive_cmds='$LD -Bshareable $libobjs $deplibs $linker_flags -o $lib'
+ wlarc=
+ else
+ archive_cmds='$CC -shared -nodefaultlibs $libobjs $deplibs $compiler_flags ${wl}-soname $wl$soname -o $lib'
+ archive_expsym_cmds='$CC -shared -nodefaultlibs $libobjs $deplibs $compiler_flags ${wl}-soname $wl$soname ${wl}-retain-symbols-file $wl$export_symbols -o $lib'
+ fi
+ ;;
+
+ solaris* | sysv5*)
+ if $LD -v 2>&1 | egrep 'BFD 2\.8' > /dev/null; then
+ ld_shlibs=no
+ cat <<EOF 1>&2
+
+*** Warning: The releases 2.8.* of the GNU linker cannot reliably
+*** create shared libraries on Solaris systems. Therefore, libtool
+*** is disabling shared libraries support. We urge you to upgrade GNU
+*** binutils to release 2.9.1 or newer. Another option is to modify
+*** your PATH or compiler configuration so that the native linker is
+*** used, and then restart.
+
+EOF
+ elif $LD --help 2>&1 | egrep ': supported targets:.* elf' > /dev/null; then
+ archive_cmds='$CC -shared $libobjs $deplibs $compiler_flags ${wl}-soname $wl$soname -o $lib'
+ archive_expsym_cmds='$CC -shared $libobjs $deplibs $compiler_flags ${wl}-soname $wl$soname ${wl}-retain-symbols-file $wl$export_symbols -o $lib'
+ else
+ ld_shlibs=no
+ fi
+ ;;
+
+ sunos4*)
+ archive_cmds='$LD -assert pure-text -Bshareable -o $lib $libobjs $deplibs $linker_flags'
+ wlarc=
+ hardcode_direct=yes
+ hardcode_shlibpath_var=no
+ ;;
+
+ *)
+ if $LD --help 2>&1 | egrep ': supported targets:.* elf' > /dev/null; then
+ archive_cmds='$CC -shared $libobjs $deplibs $compiler_flags ${wl}-soname $wl$soname -o $lib'
+ archive_expsym_cmds='$CC -shared $libobjs $deplibs $compiler_flags ${wl}-soname $wl$soname ${wl}-retain-symbols-file $wl$export_symbols -o $lib'
+ else
+ ld_shlibs=no
+ fi
+ ;;
+ esac
+
+ if test "$ld_shlibs" = yes; then
+ runpath_var=LD_RUN_PATH
+ hardcode_libdir_flag_spec='${wl}--rpath ${wl}$libdir'
+ export_dynamic_flag_spec='${wl}--export-dynamic'
+ case $host_os in
+ cygwin* | mingw* | pw32*)
+ # dlltool doesn't understand --whole-archive et. al.
+ whole_archive_flag_spec=
+ ;;
+ *)
+ # ancient GNU ld didn't support --whole-archive et. al.
+ if $LD --help 2>&1 | egrep 'no-whole-archive' > /dev/null; then
+ whole_archive_flag_spec="$wlarc"'--whole-archive$convenience '"$wlarc"'--no-whole-archive'
+ else
+ whole_archive_flag_spec=
+ fi
+ ;;
+ esac
+ fi
+else
+ # PORTME fill in a description of your system's linker (not GNU ld)
+ case $host_os in
+ aix3*)
+ allow_undefined_flag=unsupported
+ always_export_symbols=yes
+ archive_expsym_cmds='$LD -o $output_objdir/$soname $libobjs $deplibs $linker_flags -bE:$export_symbols -T512 -H512 -bM:SRE~$AR $AR_FLAGS $lib $output_objdir/$soname'
+ # Note: this linker hardcodes the directories in LIBPATH if there
+ # are no directories specified by -L.
+ hardcode_minus_L=yes
+ if test "$GCC" = yes && test -z "$link_static_flag"; then
+ # Neither direct hardcoding nor static linking is supported with a
+ # broken collect2.
+ hardcode_direct=unsupported
+ fi
+ ;;
+
+ aix4* | aix5*)
+ if test "$host_cpu" = ia64; then
+ # On IA64, the linker does run time linking by default, so we don't
+ # have to do anything special.
+ aix_use_runtimelinking=no
+ exp_sym_flag='-Bexport'
+ no_entry_flag=""
+ else
+ aix_use_runtimelinking=no
+
+ # Test if we are trying to use run time linking or normal
+ # AIX style linking. If -brtl is somewhere in LDFLAGS, we
+ # need to do runtime linking.
+ case $host_os in aix4.[[23]]|aix4.[[23]].*|aix5*)
+ for ld_flag in $LDFLAGS; do
+ if (test $ld_flag = "-brtl" || test $ld_flag = "-Wl,-brtl"); then
+ aix_use_runtimelinking=yes
+ break
+ fi
+ done
+ esac
+
+ exp_sym_flag='-bexport'
+ no_entry_flag='-bnoentry'
+ fi
+
+ # When large executables or shared objects are built, AIX ld can
+ # have problems creating the table of contents. If linking a library
+ # or program results in "error TOC overflow" add -mminimal-toc to
+ # CXXFLAGS/CFLAGS for g++/gcc. In the cases where that is not
+ # enough to fix the problem, add -Wl,-bbigtoc to LDFLAGS.
+
+ hardcode_direct=yes
+ archive_cmds=''
+ hardcode_libdir_separator=':'
+ if test "$GCC" = yes; then
+ case $host_os in aix4.[[012]]|aix4.[[012]].*)
+ collect2name=`${CC} -print-prog-name=collect2`
+ if test -f "$collect2name" && \
+ strings "$collect2name" | grep resolve_lib_name >/dev/null
+ then
+ # We have reworked collect2
+ hardcode_direct=yes
+ else
+ # We have old collect2
+ hardcode_direct=unsupported
+ # It fails to find uninstalled libraries when the uninstalled
+ # path is not listed in the libpath. Setting hardcode_minus_L
+ # to unsupported forces relinking
+ hardcode_minus_L=yes
+ hardcode_libdir_flag_spec='-L$libdir'
+ hardcode_libdir_separator=
+ fi
+ esac
+
+ shared_flag='-shared'
+ else
+ # not using gcc
+ if test "$host_cpu" = ia64; then
+ shared_flag='${wl}-G'
+ else
+ if test "$aix_use_runtimelinking" = yes; then
+ shared_flag='${wl}-G'
+ else
+ shared_flag='${wl}-bM:SRE'
+ fi
+ fi
+ fi
+
+ # It seems that -bexpall can do strange things, so it is better to
+ # generate a list of symbols to export.
+ always_export_symbols=yes
+ if test "$aix_use_runtimelinking" = yes; then
+ # Warning - without using the other runtime loading flags (-brtl),
+ # -berok will link without error, but may produce a broken library.
+ allow_undefined_flag='-berok'
+ hardcode_libdir_flag_spec='${wl}-blibpath:$libdir:/usr/lib:/lib'
+ archive_expsym_cmds="\$CC"' -o $output_objdir/$soname $libobjs $deplibs $compiler_flags `if test "x${allow_undefined_flag}" != "x"; then echo "${wl}${allow_undefined_flag}"; else :; fi` '"\${wl}$no_entry_flag \${wl}$exp_sym_flag:\$export_symbols $shared_flag"
+ else
+ if test "$host_cpu" = ia64; then
+ hardcode_libdir_flag_spec='${wl}-R $libdir:/usr/lib:/lib'
+ allow_undefined_flag="-z nodefs"
+ archive_expsym_cmds="\$CC $shared_flag"' -o $output_objdir/$soname ${wl}-h$soname $libobjs $deplibs $compiler_flags ${wl}${allow_undefined_flag} '"\${wl}$no_entry_flag \${wl}$exp_sym_flag:\$export_symbols"
+ else
+ hardcode_libdir_flag_spec='${wl}-bnolibpath ${wl}-blibpath:$libdir:/usr/lib:/lib'
+ # Warning - without using the other run time loading flags,
+ # -berok will link without error, but may produce a broken library.
+ allow_undefined_flag='${wl}-berok'
+ # This is a bit strange, but is similar to how AIX traditionally builds
+ # it's shared libraries.
+ archive_expsym_cmds="\$CC $shared_flag"' -o $output_objdir/$soname $libobjs $deplibs $compiler_flags ${allow_undefined_flag} '"\${wl}$no_entry_flag \${wl}$exp_sym_flag:\$export_symbols"' ~$AR -crlo $objdir/$libname$release.a $objdir/$soname'
+ fi
+ fi
+ ;;
+
+ amigaos*)
+ archive_cmds='$rm $output_objdir/a2ixlibrary.data~$echo "#define NAME $libname" > $output_objdir/a2ixlibrary.data~$echo "#define LIBRARY_ID 1" >> $output_objdir/a2ixlibrary.data~$echo "#define VERSION $major" >> $output_objdir/a2ixlibrary.data~$echo "#define REVISION $revision" >> $output_objdir/a2ixlibrary.data~$AR $AR_FLAGS $lib $libobjs~$RANLIB $lib~(cd $output_objdir && a2ixlibrary -32)'
+ hardcode_libdir_flag_spec='-L$libdir'
+ hardcode_minus_L=yes
+ # see comment about different semantics on the GNU ld section
+ ld_shlibs=no
+ ;;
+
+ cygwin* | mingw* | pw32*)
+ # When not using gcc, we currently assume that we are using
+ # Microsoft Visual C++.
+ # hardcode_libdir_flag_spec is actually meaningless, as there is
+ # no search path for DLLs.
+ hardcode_libdir_flag_spec=' '
+ allow_undefined_flag=unsupported
+ # Tell ltmain to make .lib files, not .a files.
+ libext=lib
+ # FIXME: Setting linknames here is a bad hack.
+ archive_cmds='$CC -o $lib $libobjs $compiler_flags `echo "$deplibs" | sed -e '\''s/ -lc$//'\''` -link -dll~linknames='
+ # The linker will automatically build a .lib file if we build a DLL.
+ old_archive_from_new_cmds='true'
+ # FIXME: Should let the user specify the lib program.
+ old_archive_cmds='lib /OUT:$oldlib$oldobjs$old_deplibs'
+ fix_srcfile_path='`cygpath -w "$srcfile"`'
+ ;;
+
+ darwin* | rhapsody*)
+ case "$host_os" in
+ rhapsody* | darwin1.[[012]])
+ allow_undefined_flag='-undefined suppress'
+ ;;
+ *) # Darwin 1.3 on
+ allow_undefined_flag='-flat_namespace -undefined suppress'
+ ;;
+ esac
+ # FIXME: Relying on posixy $() will cause problems for
+ # cross-compilation, but unfortunately the echo tests do not
+ # yet detect zsh echo's removal of \ escapes.
+ archive_cmds='$nonopt $(test "x$module" = xyes && echo -bundle || echo -dynamiclib) $allow_undefined_flag -o $lib $libobjs $deplibs$linker_flags -install_name $rpath/$soname $verstring'
+ # We need to add '_' to the symbols in $export_symbols first
+ #archive_expsym_cmds="$archive_cmds"' && strip -s $export_symbols'
+ hardcode_direct=yes
+ hardcode_shlibpath_var=no
+ whole_archive_flag_spec='-all_load $convenience'
+ ;;
+
+ freebsd1*)
+ ld_shlibs=no
+ ;;
+
+ # FreeBSD 2.2.[012] allows us to include c++rt0.o to get C++ constructor
+ # support. Future versions do this automatically, but an explicit c++rt0.o
+ # does not break anything, and helps significantly (at the cost of a little
+ # extra space).
+ freebsd2.2*)
+ archive_cmds='$LD -Bshareable -o $lib $libobjs $deplibs $linker_flags /usr/lib/c++rt0.o'
+ hardcode_libdir_flag_spec='-R$libdir'
+ hardcode_direct=yes
+ hardcode_shlibpath_var=no
+ ;;
+
+ # Unfortunately, older versions of FreeBSD 2 do not have this feature.
+ freebsd2*)
+ archive_cmds='$LD -Bshareable -o $lib $libobjs $deplibs $linker_flags'
+ hardcode_direct=yes
+ hardcode_minus_L=yes
+ hardcode_shlibpath_var=no
+ ;;
+
+ # FreeBSD 3 and greater uses gcc -shared to do shared libraries.
+ freebsd*)
+ archive_cmds='$CC -shared -o $lib $libobjs $deplibs $compiler_flags'
+ hardcode_libdir_flag_spec='-R$libdir'
+ hardcode_direct=yes
+ hardcode_shlibpath_var=no
+ ;;
+
+ hpux9* | hpux10* | hpux11*)
+ case $host_os in
+ hpux9*) archive_cmds='$rm $output_objdir/$soname~$LD -b +b $install_libdir -o $output_objdir/$soname $libobjs $deplibs $linker_flags~test $output_objdir/$soname = $lib || mv $output_objdir/$soname $lib' ;;
+ *) archive_cmds='$LD -b +h $soname +b $install_libdir -o $lib $libobjs $deplibs $linker_flags' ;;
+ esac
+ hardcode_libdir_flag_spec='${wl}+b ${wl}$libdir'
+ hardcode_libdir_separator=:
+ hardcode_direct=yes
+ hardcode_minus_L=yes # Not in the search PATH, but as the default
+ # location of the library.
+ export_dynamic_flag_spec='${wl}-E'
+ ;;
+
+ irix5* | irix6*)
+ if test "$GCC" = yes; then
+ archive_cmds='$CC -shared $libobjs $deplibs $compiler_flags ${wl}-soname ${wl}$soname `test -n "$verstring" && echo ${wl}-set_version ${wl}$verstring` ${wl}-update_registry ${wl}${output_objdir}/so_locations -o $lib'
+ else
+ archive_cmds='$LD -shared $libobjs $deplibs $linker_flags -soname $soname `test -n "$verstring" && echo -set_version $verstring` -update_registry ${output_objdir}/so_locations -o $lib'
+ fi
+ hardcode_libdir_flag_spec='${wl}-rpath ${wl}$libdir'
+ hardcode_libdir_separator=:
+ link_all_deplibs=yes
+ ;;
+
+ netbsd*)
+ if echo __ELF__ | $CC -E - | grep __ELF__ >/dev/null; then
+ archive_cmds='$LD -Bshareable -o $lib $libobjs $deplibs $linker_flags' # a.out
+ else
+ archive_cmds='$LD -shared -o $lib $libobjs $deplibs $linker_flags' # ELF
+ fi
+ hardcode_libdir_flag_spec='-R$libdir'
+ hardcode_direct=yes
+ hardcode_shlibpath_var=no
+ ;;
+
+ newsos6)
+ archive_cmds='$LD -G -h $soname -o $lib $libobjs $deplibs $linker_flags'
+ hardcode_direct=yes
+ hardcode_libdir_flag_spec='${wl}-rpath ${wl}$libdir'
+ hardcode_libdir_separator=:
+ hardcode_shlibpath_var=no
+ ;;
+
+ openbsd*)
+ hardcode_direct=yes
+ hardcode_shlibpath_var=no
+ if test -z "`echo __ELF__ | $CC -E - | grep __ELF__`" || test "$host_os-$host_cpu" = "openbsd2.8-powerpc"; then
+ archive_cmds='$CC -shared $pic_flag -o $lib $libobjs $deplibs $linker_flags'
+ hardcode_libdir_flag_spec='${wl}-rpath,$libdir'
+ export_dynamic_flag_spec='${wl}-E'
+ else
+ case "$host_os" in
+ openbsd[[01]].* | openbsd2.[[0-7]] | openbsd2.[[0-7]].*)
+ archive_cmds='$LD -Bshareable -o $lib $libobjs $deplibs $linker_flags'
+ hardcode_libdir_flag_spec='-R$libdir'
+ ;;
+ *)
+ archive_cmds='$CC -shared $pic_flag -o $lib $libobjs $deplibs $linker_flags'
+ hardcode_libdir_flag_spec='${wl}-rpath,$libdir'
+ ;;
+ esac
+ fi
+ ;;
+
+ os2*)
+ hardcode_libdir_flag_spec='-L$libdir'
+ hardcode_minus_L=yes
+ allow_undefined_flag=unsupported
+ archive_cmds='$echo "LIBRARY $libname INITINSTANCE" > $output_objdir/$libname.def~$echo "DESCRIPTION \"$libname\"" >> $output_objdir/$libname.def~$echo DATA >> $output_objdir/$libname.def~$echo " SINGLE NONSHARED" >> $output_objdir/$libname.def~$echo EXPORTS >> $output_objdir/$libname.def~emxexp $libobjs >> $output_objdir/$libname.def~$CC -Zdll -Zcrtdll -o $lib $libobjs $deplibs $compiler_flags $output_objdir/$libname.def'
+ old_archive_from_new_cmds='emximp -o $output_objdir/$libname.a $output_objdir/$libname.def'
+ ;;
+
+ osf3*)
+ if test "$GCC" = yes; then
+ allow_undefined_flag=' ${wl}-expect_unresolved ${wl}\*'
+ archive_cmds='$CC -shared${allow_undefined_flag} $libobjs $deplibs $compiler_flags ${wl}-soname ${wl}$soname `test -n "$verstring" && echo ${wl}-set_version ${wl}$verstring` ${wl}-update_registry ${wl}${output_objdir}/so_locations -o $lib'
+ else
+ allow_undefined_flag=' -expect_unresolved \*'
+ archive_cmds='$LD -shared${allow_undefined_flag} $libobjs $deplibs $linker_flags -soname $soname `test -n "$verstring" && echo -set_version $verstring` -update_registry ${output_objdir}/so_locations -o $lib'
+ fi
+ hardcode_libdir_flag_spec='${wl}-rpath ${wl}$libdir'
+ hardcode_libdir_separator=:
+ ;;
+
+ osf4* | osf5*) # as osf3* with the addition of -msym flag
+ if test "$GCC" = yes; then
+ allow_undefined_flag=' ${wl}-expect_unresolved ${wl}\*'
+ archive_cmds='$CC -shared${allow_undefined_flag} $libobjs $deplibs $compiler_flags ${wl}-msym ${wl}-soname ${wl}$soname `test -n "$verstring" && echo ${wl}-set_version ${wl}$verstring` ${wl}-update_registry ${wl}${output_objdir}/so_locations -o $lib'
+ hardcode_libdir_flag_spec='${wl}-rpath ${wl}$libdir'
+ else
+ allow_undefined_flag=' -expect_unresolved \*'
+ archive_cmds='$LD -shared${allow_undefined_flag} $libobjs $deplibs $linker_flags -msym -soname $soname `test -n "$verstring" && echo -set_version $verstring` -update_registry ${output_objdir}/so_locations -o $lib'
+ archive_expsym_cmds='for i in `cat $export_symbols`; do printf "-exported_symbol " >> $lib.exp; echo "\$i" >> $lib.exp; done; echo "-hidden">> $lib.exp~
+ $LD -shared${allow_undefined_flag} -input $lib.exp $linker_flags $libobjs $deplibs -soname $soname `test -n "$verstring" && echo -set_version $verstring` -update_registry ${objdir}/so_locations -o $lib~$rm $lib.exp'
+
+ #Both c and cxx compiler support -rpath directly
+ hardcode_libdir_flag_spec='-rpath $libdir'
+ fi
+ hardcode_libdir_separator=:
+ ;;
+
+ sco3.2v5*)
+ archive_cmds='$LD -G -h $soname -o $lib $libobjs $deplibs $linker_flags'
+ hardcode_shlibpath_var=no
+ runpath_var=LD_RUN_PATH
+ hardcode_runpath_var=yes
+ export_dynamic_flag_spec='${wl}-Bexport'
+ ;;
+
+ solaris*)
+ # gcc --version < 3.0 without binutils cannot create self contained
+ # shared libraries reliably, requiring libgcc.a to resolve some of
+ # the object symbols generated in some cases. Libraries that use
+ # assert need libgcc.a to resolve __eprintf, for example. Linking
+ # a copy of libgcc.a into every shared library to guarantee resolving
+ # such symbols causes other problems: According to Tim Van Holder
+ # <tim.van.holder@pandora.be>, C++ libraries end up with a separate
+ # (to the application) exception stack for one thing.
+ no_undefined_flag=' -z defs'
+ if test "$GCC" = yes; then
+ case `$CC --version 2>/dev/null` in
+ [[12]].*)
+ cat <<EOF 1>&2
+
+*** Warning: Releases of GCC earlier than version 3.0 cannot reliably
+*** create self contained shared libraries on Solaris systems, without
+*** introducing a dependency on libgcc.a. Therefore, libtool is disabling
+*** -no-undefined support, which will at least allow you to build shared
+*** libraries. However, you may find that when you link such libraries
+*** into an application without using GCC, you have to manually add
+*** \`gcc --print-libgcc-file-name\` to the link command. We urge you to
+*** upgrade to a newer version of GCC. Another option is to rebuild your
+*** current GCC to use the GNU linker from GNU binutils 2.9.1 or newer.
+
+EOF
+ no_undefined_flag=
+ ;;
+ esac
+ fi
+ # $CC -shared without GNU ld will not create a library from C++
+ # object files and a static libstdc++, better avoid it by now
+ archive_cmds='$LD -G${allow_undefined_flag} -h $soname -o $lib $libobjs $deplibs $linker_flags'
+ archive_expsym_cmds='$echo "{ global:" > $lib.exp~cat $export_symbols | sed -e "s/\(.*\)/\1;/" >> $lib.exp~$echo "local: *; };" >> $lib.exp~
+ $LD -G${allow_undefined_flag} -M $lib.exp -h $soname -o $lib $libobjs $deplibs $linker_flags~$rm $lib.exp'
+ hardcode_libdir_flag_spec='-R$libdir'
+ hardcode_shlibpath_var=no
+ case $host_os in
+ solaris2.[[0-5]] | solaris2.[[0-5]].*) ;;
+ *) # Supported since Solaris 2.6 (maybe 2.5.1?)
+ whole_archive_flag_spec='-z allextract$convenience -z defaultextract' ;;
+ esac
+ link_all_deplibs=yes
+ ;;
+
+ sunos4*)
+ if test "x$host_vendor" = xsequent; then
+ # Use $CC to link under sequent, because it throws in some extra .o
+ # files that make .init and .fini sections work.
+ archive_cmds='$CC -G ${wl}-h $soname -o $lib $libobjs $deplibs $compiler_flags'
+ else
+ archive_cmds='$LD -assert pure-text -Bstatic -o $lib $libobjs $deplibs $linker_flags'
+ fi
+ hardcode_libdir_flag_spec='-L$libdir'
+ hardcode_direct=yes
+ hardcode_minus_L=yes
+ hardcode_shlibpath_var=no
+ ;;
+
+ sysv4)
+ if test "x$host_vendor" = xsno; then
+ archive_cmds='$LD -G -Bsymbolic -h $soname -o $lib $libobjs $deplibs $linker_flags'
+ hardcode_direct=yes # is this really true???
+ else
+ archive_cmds='$LD -G -h $soname -o $lib $libobjs $deplibs $linker_flags'
+ hardcode_direct=no #Motorola manual says yes, but my tests say they lie
+ fi
+ runpath_var='LD_RUN_PATH'
+ hardcode_shlibpath_var=no
+ ;;
+
+ sysv4.3*)
+ archive_cmds='$LD -G -h $soname -o $lib $libobjs $deplibs $linker_flags'
+ hardcode_shlibpath_var=no
+ export_dynamic_flag_spec='-Bexport'
+ ;;
+
+ sysv5*)
+ no_undefined_flag=' -z text'
+ # $CC -shared without GNU ld will not create a library from C++
+ # object files and a static libstdc++, better avoid it by now
+ archive_cmds='$LD -G${allow_undefined_flag} -h $soname -o $lib $libobjs $deplibs $linker_flags'
+ archive_expsym_cmds='$echo "{ global:" > $lib.exp~cat $export_symbols | sed -e "s/\(.*\)/\1;/" >> $lib.exp~$echo "local: *; };" >> $lib.exp~
+ $LD -G${allow_undefined_flag} -M $lib.exp -h $soname -o $lib $libobjs $deplibs $linker_flags~$rm $lib.exp'
+ hardcode_libdir_flag_spec=
+ hardcode_shlibpath_var=no
+ runpath_var='LD_RUN_PATH'
+ ;;
+
+ uts4*)
+ archive_cmds='$LD -G -h $soname -o $lib $libobjs $deplibs $linker_flags'
+ hardcode_libdir_flag_spec='-L$libdir'
+ hardcode_shlibpath_var=no
+ ;;
+
+ dgux*)
+ archive_cmds='$LD -G -h $soname -o $lib $libobjs $deplibs $linker_flags'
+ hardcode_libdir_flag_spec='-L$libdir'
+ hardcode_shlibpath_var=no
+ ;;
+
+ sysv4*MP*)
+ if test -d /usr/nec; then
+ archive_cmds='$LD -G -h $soname -o $lib $libobjs $deplibs $linker_flags'
+ hardcode_shlibpath_var=no
+ runpath_var=LD_RUN_PATH
+ hardcode_runpath_var=yes
+ ld_shlibs=yes
+ fi
+ ;;
+
+ sysv4.2uw2*)
+ archive_cmds='$LD -G -o $lib $libobjs $deplibs $linker_flags'
+ hardcode_direct=yes
+ hardcode_minus_L=no
+ hardcode_shlibpath_var=no
+ hardcode_runpath_var=yes
+ runpath_var=LD_RUN_PATH
+ ;;
+
+ sysv5uw7* | unixware7*)
+ no_undefined_flag='${wl}-z ${wl}text'
+ if test "$GCC" = yes; then
+ archive_cmds='$CC -shared ${wl}-h ${wl}$soname -o $lib $libobjs $deplibs $compiler_flags'
+ else
+ archive_cmds='$CC -G ${wl}-h ${wl}$soname -o $lib $libobjs $deplibs $compiler_flags'
+ fi
+ runpath_var='LD_RUN_PATH'
+ hardcode_shlibpath_var=no
+ ;;
+
+ *)
+ ld_shlibs=no
+ ;;
+ esac
+fi
+AC_MSG_RESULT([$ld_shlibs])
+test "$ld_shlibs" = no && can_build_shared=no
+##
+## END FIXME
+
+## FIXME: this should be a separate macro
+##
+# Check hardcoding attributes.
+AC_MSG_CHECKING([how to hardcode library paths into programs])
+hardcode_action=
+if test -n "$hardcode_libdir_flag_spec" || \
+ test -n "$runpath_var"; then
+
+ # We can hardcode non-existant directories.
+ if test "$hardcode_direct" != no &&
+ # If the only mechanism to avoid hardcoding is shlibpath_var, we
+ # have to relink, otherwise we might link with an installed library
+ # when we should be linking with a yet-to-be-installed one
+ ## test "$hardcode_shlibpath_var" != no &&
+ test "$hardcode_minus_L" != no; then
+ # Linking always hardcodes the temporary library directory.
+ hardcode_action=relink
+ else
+ # We can link without hardcoding, and we can hardcode nonexisting dirs.
+ hardcode_action=immediate
+ fi
+else
+ # We cannot hardcode anything, or else we can only hardcode existing
+ # directories.
+ hardcode_action=unsupported
+fi
+AC_MSG_RESULT([$hardcode_action])
+##
+## END FIXME
+
+## FIXME: this should be a separate macro
+##
+striplib=
+old_striplib=
+AC_MSG_CHECKING([whether stripping libraries is possible])
+if test -n "$STRIP" && $STRIP -V 2>&1 | grep "GNU strip" >/dev/null; then
+ test -z "$old_striplib" && old_striplib="$STRIP --strip-debug"
+ test -z "$striplib" && striplib="$STRIP --strip-unneeded"
+ AC_MSG_RESULT([yes])
+else
+ AC_MSG_RESULT([no])
+fi
+##
+## END FIXME
+
+reload_cmds='$LD$reload_flag -o $output$reload_objs'
+test -z "$deplibs_check_method" && deplibs_check_method=unknown
+
+## FIXME: this should be a separate macro
+##
+# PORTME Fill in your ld.so characteristics
+AC_MSG_CHECKING([dynamic linker characteristics])
+library_names_spec=
+libname_spec='lib$name'
+soname_spec=
+postinstall_cmds=
+postuninstall_cmds=
+finish_cmds=
+finish_eval=
+shlibpath_var=
+shlibpath_overrides_runpath=unknown
+version_type=none
+dynamic_linker="$host_os ld.so"
+sys_lib_dlsearch_path_spec="/lib /usr/lib"
+sys_lib_search_path_spec="/lib /usr/lib /usr/local/lib"
+
+case $host_os in
+aix3*)
+ version_type=linux
+ library_names_spec='${libname}${release}.so$versuffix $libname.a'
+ shlibpath_var=LIBPATH
+
+ # AIX has no versioning support, so we append a major version to the name.
+ soname_spec='${libname}${release}.so$major'
+ ;;
+
+aix4* | aix5*)
+ version_type=linux
+ if test "$host_cpu" = ia64; then
+ # AIX 5 supports IA64
+ library_names_spec='${libname}${release}.so$major ${libname}${release}.so$versuffix $libname.so'
+ shlibpath_var=LD_LIBRARY_PATH
+ else
+ # With GCC up to 2.95.x, collect2 would create an import file
+ # for dependence libraries. The import file would start with
+ # the line `#! .'. This would cause the generated library to
+ # depend on `.', always an invalid library. This was fixed in
+ # development snapshots of GCC prior to 3.0.
+ case $host_os in
+ aix4 | aix4.[[01]] | aix4.[[01]].*)
+ if { echo '#if __GNUC__ > 2 || (__GNUC__ == 2 && __GNUC_MINOR__ >= 97)'
+ echo ' yes '
+ echo '#endif'; } | ${CC} -E - | grep yes > /dev/null; then
+ :
+ else
+ can_build_shared=no
+ fi
+ ;;
+ esac
+ # AIX (on Power*) has no versioning support, so currently we can
+ # not hardcode correct soname into executable. Probably we can
+ # add versioning support to collect2, so additional links can
+ # be useful in future.
+ if test "$aix_use_runtimelinking" = yes; then
+ # If using run time linking (on AIX 4.2 or later) use lib<name>.so
+ # instead of lib<name>.a to let people know that these are not
+ # typical AIX shared libraries.
+ library_names_spec='${libname}${release}.so$versuffix ${libname}${release}.so$major $libname.so'
+ else
+ # We preserve .a as extension for shared libraries through AIX4.2
+ # and later when we are not doing run time linking.
+ library_names_spec='${libname}${release}.a $libname.a'
+ soname_spec='${libname}${release}.so$major'
+ fi
+ shlibpath_var=LIBPATH
+ fi
+ ;;
+
+amigaos*)
+ library_names_spec='$libname.ixlibrary $libname.a'
+ # Create ${libname}_ixlibrary.a entries in /sys/libs.
+ finish_eval='for lib in `ls $libdir/*.ixlibrary 2>/dev/null`; do libname=`$echo "X$lib" | $Xsed -e '\''s%^.*/\([[^/]]*\)\.ixlibrary$%\1%'\''`; test $rm /sys/libs/${libname}_ixlibrary.a; $show "(cd /sys/libs && $LN_S $lib ${libname}_ixlibrary.a)"; (cd /sys/libs && $LN_S $lib ${libname}_ixlibrary.a) || exit 1; done'
+ ;;
+
+beos*)
+ library_names_spec='${libname}.so'
+ dynamic_linker="$host_os ld.so"
+ shlibpath_var=LIBRARY_PATH
+ ;;
+
+bsdi4*)
+ version_type=linux
+ need_version=no
+ library_names_spec='${libname}${release}.so$versuffix ${libname}${release}.so$major $libname.so'
+ soname_spec='${libname}${release}.so$major'
+ finish_cmds='PATH="\$PATH:/sbin" ldconfig $libdir'
+ shlibpath_var=LD_LIBRARY_PATH
+ sys_lib_search_path_spec="/shlib /usr/lib /usr/X11/lib /usr/contrib/lib /lib /usr/local/lib"
+ sys_lib_dlsearch_path_spec="/shlib /usr/lib /usr/local/lib"
+ export_dynamic_flag_spec=-rdynamic
+ # the default ld.so.conf also contains /usr/contrib/lib and
+ # /usr/X11R6/lib (/usr/X11 is a link to /usr/X11R6), but let us allow
+ # libtool to hard-code these into programs
+ ;;
+
+cygwin* | mingw* | pw32*)
+ version_type=windows
+ need_version=no
+ need_lib_prefix=no
+ case $GCC,$host_os in
+ yes,cygwin*)
+ library_names_spec='$libname.dll.a'
+ soname_spec='`echo ${libname} | sed -e 's/^lib/cyg/'``echo ${release} | sed -e 's/[[.]]/-/g'`${versuffix}.dll'
+ postinstall_cmds='dlpath=`bash 2>&1 -c '\''. $dir/${file}i;echo \$dlname'\''`~
+ dldir=$destdir/`dirname \$dlpath`~
+ test -d \$dldir || mkdir -p \$dldir~
+ $install_prog .libs/$dlname \$dldir/$dlname'
+ postuninstall_cmds='dldll=`bash 2>&1 -c '\''. $file; echo \$dlname'\''`~
+ dlpath=$dir/\$dldll~
+ $rm \$dlpath'
+ ;;
+ yes,mingw*)
+ library_names_spec='${libname}`echo ${release} | sed -e 's/[[.]]/-/g'`${versuffix}.dll'
+ sys_lib_search_path_spec=`$CC -print-search-dirs | grep "^libraries:" | sed -e "s/^libraries://" -e "s/;/ /g"`
+ ;;
+ yes,pw32*)
+ library_names_spec='`echo ${libname} | sed -e 's/^lib/pw/'``echo ${release} | sed -e 's/[.]/-/g'`${versuffix}.dll'
+ ;;
+ *)
+ library_names_spec='${libname}`echo ${release} | sed -e 's/[[.]]/-/g'`${versuffix}.dll $libname.lib'
+ ;;
+ esac
+ dynamic_linker='Win32 ld.exe'
+ # FIXME: first we should search . and the directory the executable is in
+ shlibpath_var=PATH
+ ;;
+
+darwin* | rhapsody*)
+ dynamic_linker="$host_os dyld"
+ version_type=darwin
+ need_lib_prefix=no
+ need_version=no
+ # FIXME: Relying on posixy $() will cause problems for
+ # cross-compilation, but unfortunately the echo tests do not
+ # yet detect zsh echo's removal of \ escapes.
+ library_names_spec='${libname}${release}${versuffix}.$(test .$module = .yes && echo so || echo dylib) ${libname}${release}${major}.$(test .$module = .yes && echo so || echo dylib) ${libname}.$(test .$module = .yes && echo so || echo dylib)'
+ soname_spec='${libname}${release}${major}.$(test .$module = .yes && echo so || echo dylib)'
+ shlibpath_overrides_runpath=yes
+ shlibpath_var=DYLD_LIBRARY_PATH
+ ;;
+
+freebsd1*)
+ dynamic_linker=no
+ ;;
+
+freebsd*)
+ objformat=`test -x /usr/bin/objformat && /usr/bin/objformat || echo aout`
+ version_type=freebsd-$objformat
+ case $version_type in
+ freebsd-elf*)
+ library_names_spec='${libname}${release}.so$versuffix ${libname}${release}.so $libname.so'
+ need_version=no
+ need_lib_prefix=no
+ ;;
+ freebsd-*)
+ library_names_spec='${libname}${release}.so$versuffix $libname.so$versuffix'
+ need_version=yes
+ ;;
+ esac
+ shlibpath_var=LD_LIBRARY_PATH
+ case $host_os in
+ freebsd2*)
+ shlibpath_overrides_runpath=yes
+ ;;
+ *)
+ shlibpath_overrides_runpath=no
+ hardcode_into_libs=yes
+ ;;
+ esac
+ ;;
+
+gnu*)
+ version_type=linux
+ need_lib_prefix=no
+ need_version=no
+ library_names_spec='${libname}${release}.so$versuffix ${libname}${release}.so${major} ${libname}.so'
+ soname_spec='${libname}${release}.so$major'
+ shlibpath_var=LD_LIBRARY_PATH
+ hardcode_into_libs=yes
+ ;;
+
+hpux9* | hpux10* | hpux11*)
+ # Give a soname corresponding to the major version so that dld.sl refuses to
+ # link against other versions.
+ dynamic_linker="$host_os dld.sl"
+ version_type=sunos
+ need_lib_prefix=no
+ need_version=no
+ shlibpath_var=SHLIB_PATH
+ shlibpath_overrides_runpath=no # +s is required to enable SHLIB_PATH
+ library_names_spec='${libname}${release}.sl$versuffix ${libname}${release}.sl$major $libname.sl'
+ soname_spec='${libname}${release}.sl$major'
+ # HP-UX runs *really* slowly unless shared libraries are mode 555.
+ postinstall_cmds='chmod 555 $lib'
+ ;;
+
+irix5* | irix6*)
+ version_type=irix
+ need_lib_prefix=no
+ need_version=no
+ soname_spec='${libname}${release}.so$major'
+ library_names_spec='${libname}${release}.so$versuffix ${libname}${release}.so$major ${libname}${release}.so $libname.so'
+ case $host_os in
+ irix5*)
+ libsuff= shlibsuff=
+ ;;
+ *)
+ case $LD in # libtool.m4 will add one of these switches to LD
+ *-32|*"-32 ") libsuff= shlibsuff= libmagic=32-bit;;
+ *-n32|*"-n32 ") libsuff=32 shlibsuff=N32 libmagic=N32;;
+ *-64|*"-64 ") libsuff=64 shlibsuff=64 libmagic=64-bit;;
+ *) libsuff= shlibsuff= libmagic=never-match;;
+ esac
+ ;;
+ esac
+ shlibpath_var=LD_LIBRARY${shlibsuff}_PATH
+ shlibpath_overrides_runpath=no
+ sys_lib_search_path_spec="/usr/lib${libsuff} /lib${libsuff} /usr/local/lib${libsuff}"
+ sys_lib_dlsearch_path_spec="/usr/lib${libsuff} /lib${libsuff}"
+ ;;
+
+# No shared lib support for Linux oldld, aout, or coff.
+linux-gnuoldld* | linux-gnuaout* | linux-gnucoff*)
+ dynamic_linker=no
+ ;;
+
+# This must be Linux ELF.
+linux-gnu*)
+ version_type=linux
+ need_lib_prefix=no
+ need_version=no
+ library_names_spec='${libname}${release}.so$versuffix ${libname}${release}.so$major $libname.so'
+ soname_spec='${libname}${release}.so$major'
+ finish_cmds='PATH="\$PATH:/sbin" ldconfig -n $libdir'
+ shlibpath_var=LD_LIBRARY_PATH
+ shlibpath_overrides_runpath=no
+ # This implies no fast_install, which is unacceptable.
+ # Some rework will be needed to allow for fast_install
+ # before this can be enabled.
+ hardcode_into_libs=yes
+
+ # We used to test for /lib/ld.so.1 and disable shared libraries on
+ # powerpc, because MkLinux only supported shared libraries with the
+ # GNU dynamic linker. Since this was broken with cross compilers,
+ # most powerpc-linux boxes support dynamic linking these days and
+ # people can always --disable-shared, the test was removed, and we
+ # assume the GNU/Linux dynamic linker is in use.
+ dynamic_linker='GNU/Linux ld.so'
+ ;;
+
+netbsd*)
+ version_type=sunos
+ need_lib_prefix=no
+ need_version=no
+ if echo __ELF__ | $CC -E - | grep __ELF__ >/dev/null; then
+ library_names_spec='${libname}${release}.so$versuffix ${libname}.so$versuffix'
+ finish_cmds='PATH="\$PATH:/sbin" ldconfig -m $libdir'
+ dynamic_linker='NetBSD (a.out) ld.so'
+ else
+ library_names_spec='${libname}${release}.so$versuffix ${libname}${release}.so$major ${libname}${release}.so ${libname}.so'
+ soname_spec='${libname}${release}.so$major'
+ dynamic_linker='NetBSD ld.elf_so'
+ fi
+ shlibpath_var=LD_LIBRARY_PATH
+ shlibpath_overrides_runpath=yes
+ hardcode_into_libs=yes
+ ;;
+
+newsos6)
+ version_type=linux
+ library_names_spec='${libname}${release}.so$versuffix ${libname}${release}.so$major $libname.so'
+ shlibpath_var=LD_LIBRARY_PATH
+ shlibpath_overrides_runpath=yes
+ ;;
+
+openbsd*)
+ version_type=sunos
+ need_lib_prefix=no
+ need_version=no
+ if test -z "`echo __ELF__ | $CC -E - | grep __ELF__`" || test "$host_os-$host_cpu" = "openbsd2.8-powerpc"; then
+ case "$host_os" in
+ openbsd2.[[89]] | openbsd2.[[89]].*)
+ shlibpath_overrides_runpath=no
+ ;;
+ *)
+ shlibpath_overrides_runpath=yes
+ ;;
+ esac
+ else
+ shlibpath_overrides_runpath=yes
+ fi
+ library_names_spec='${libname}${release}.so$versuffix ${libname}.so$versuffix'
+ finish_cmds='PATH="\$PATH:/sbin" ldconfig -m $libdir'
+ shlibpath_var=LD_LIBRARY_PATH
+ ;;
+
+os2*)
+ libname_spec='$name'
+ need_lib_prefix=no
+ library_names_spec='$libname.dll $libname.a'
+ dynamic_linker='OS/2 ld.exe'
+ shlibpath_var=LIBPATH
+ ;;
+
+osf3* | osf4* | osf5*)
+ version_type=osf
+ need_version=no
+ soname_spec='${libname}${release}.so'
+ library_names_spec='${libname}${release}.so$versuffix ${libname}${release}.so $libname.so'
+ shlibpath_var=LD_LIBRARY_PATH
+ sys_lib_search_path_spec="/usr/shlib /usr/ccs/lib /usr/lib/cmplrs/cc /usr/lib /usr/local/lib /var/shlib"
+ sys_lib_dlsearch_path_spec="$sys_lib_search_path_spec"
+ ;;
+
+sco3.2v5*)
+ version_type=osf
+ soname_spec='${libname}${release}.so$major'
+ library_names_spec='${libname}${release}.so$versuffix ${libname}${release}.so$major $libname.so'
+ shlibpath_var=LD_LIBRARY_PATH
+ ;;
+
+solaris*)
+ version_type=linux
+ need_lib_prefix=no
+ need_version=no
+ library_names_spec='${libname}${release}.so$versuffix ${libname}${release}.so$major $libname.so'
+ soname_spec='${libname}${release}.so$major'
+ shlibpath_var=LD_LIBRARY_PATH
+ shlibpath_overrides_runpath=yes
+ hardcode_into_libs=yes
+ # ldd complains unless libraries are executable
+ postinstall_cmds='chmod +x $lib'
+ ;;
+
+sunos4*)
+ version_type=sunos
+ library_names_spec='${libname}${release}.so$versuffix ${libname}.so$versuffix'
+ finish_cmds='PATH="\$PATH:/usr/etc" ldconfig $libdir'
+ shlibpath_var=LD_LIBRARY_PATH
+ shlibpath_overrides_runpath=yes
+ if test "$with_gnu_ld" = yes; then
+ need_lib_prefix=no
+ fi
+ need_version=yes
+ ;;
+
+sysv4 | sysv4.2uw2* | sysv4.3* | sysv5*)
+ version_type=linux
+ library_names_spec='${libname}${release}.so$versuffix ${libname}${release}.so$major $libname.so'
+ soname_spec='${libname}${release}.so$major'
+ shlibpath_var=LD_LIBRARY_PATH
+ case $host_vendor in
+ sni)
+ shlibpath_overrides_runpath=no
+ ;;
+ motorola)
+ need_lib_prefix=no
+ need_version=no
+ shlibpath_overrides_runpath=no
+ sys_lib_search_path_spec='/lib /usr/lib /usr/ccs/lib'
+ ;;
+ esac
+ ;;
+
+uts4*)
+ version_type=linux
+ library_names_spec='${libname}${release}.so$versuffix ${libname}${release}.so$major $libname.so'
+ soname_spec='${libname}${release}.so$major'
+ shlibpath_var=LD_LIBRARY_PATH
+ ;;
+
+dgux*)
+ version_type=linux
+ need_lib_prefix=no
+ need_version=no
+ library_names_spec='${libname}${release}.so$versuffix ${libname}${release}.so$major $libname.so'
+ soname_spec='${libname}${release}.so$major'
+ shlibpath_var=LD_LIBRARY_PATH
+ ;;
+
+sysv4*MP*)
+ if test -d /usr/nec ;then
+ version_type=linux
+ library_names_spec='$libname.so.$versuffix $libname.so.$major $libname.so'
+ soname_spec='$libname.so.$major'
+ shlibpath_var=LD_LIBRARY_PATH
+ fi
+ ;;
+
+*)
+ dynamic_linker=no
+ ;;
+esac
+AC_MSG_RESULT([$dynamic_linker])
+test "$dynamic_linker" = no && can_build_shared=no
+##
+## END FIXME
+
+## FIXME: this should be a separate macro
+##
+# Report the final consequences.
+AC_MSG_CHECKING([if libtool supports shared libraries])
+AC_MSG_RESULT([$can_build_shared])
+##
+## END FIXME
+
+## FIXME: this should be a separate macro
+##
+AC_MSG_CHECKING([whether to build shared libraries])
+test "$can_build_shared" = "no" && enable_shared=no
+
+# On AIX, shared libraries and static libraries use the same namespace, and
+# are all built from PIC.
+case "$host_os" in
+aix3*)
+ test "$enable_shared" = yes && enable_static=no
+ if test -n "$RANLIB"; then
+ archive_cmds="$archive_cmds~\$RANLIB \$lib"
+ postinstall_cmds='$RANLIB $lib'
+ fi
+ ;;
+
+aix4*)
+ if test "$host_cpu" != ia64 && test "$aix_use_runtimelinking" = no ; then
+ test "$enable_shared" = yes && enable_static=no
+ fi
+ ;;
+esac
+AC_MSG_RESULT([$enable_shared])
+##
+## END FIXME
+
+## FIXME: this should be a separate macro
+##
+AC_MSG_CHECKING([whether to build static libraries])
+# Make sure either enable_shared or enable_static is yes.
+test "$enable_shared" = yes || enable_static=yes
+AC_MSG_RESULT([$enable_static])
+##
+## END FIXME
+
+if test "$hardcode_action" = relink; then
+ # Fast installation is not supported
+ enable_fast_install=no
+elif test "$shlibpath_overrides_runpath" = yes ||
+ test "$enable_shared" = no; then
+ # Fast installation is not necessary
+ enable_fast_install=needless
+fi
+
+variables_saved_for_relink="PATH $shlibpath_var $runpath_var"
+if test "$GCC" = yes; then
+ variables_saved_for_relink="$variables_saved_for_relink GCC_EXEC_PREFIX COMPILER_PATH LIBRARY_PATH"
+fi
+
+AC_LIBTOOL_DLOPEN_SELF
+
+## FIXME: this should be a separate macro
+##
+if test "$enable_shared" = yes && test "$GCC" = yes; then
+ case $archive_cmds in
+ *'~'*)
+ # FIXME: we may have to deal with multi-command sequences.
+ ;;
+ '$CC '*)
+ # Test whether the compiler implicitly links with -lc since on some
+ # systems, -lgcc has to come before -lc. If gcc already passes -lc
+ # to ld, don't add -lc before -lgcc.
+ AC_MSG_CHECKING([whether -lc should be explicitly linked in])
+ AC_CACHE_VAL([lt_cv_archive_cmds_need_lc],
+ [$rm conftest*
+ echo 'static int dummy;' > conftest.$ac_ext
+
+ if AC_TRY_EVAL(ac_compile); then
+ soname=conftest
+ lib=conftest
+ libobjs=conftest.$ac_objext
+ deplibs=
+ wl=$lt_cv_prog_cc_wl
+ compiler_flags=-v
+ linker_flags=-v
+ verstring=
+ output_objdir=.
+ libname=conftest
+ save_allow_undefined_flag=$allow_undefined_flag
+ allow_undefined_flag=
+ if AC_TRY_EVAL(archive_cmds 2\>\&1 \| grep \" -lc \" \>/dev/null 2\>\&1)
+ then
+ lt_cv_archive_cmds_need_lc=no
+ else
+ lt_cv_archive_cmds_need_lc=yes
+ fi
+ allow_undefined_flag=$save_allow_undefined_flag
+ else
+ cat conftest.err 1>&5
+ fi])
+ AC_MSG_RESULT([$lt_cv_archive_cmds_need_lc])
+ ;;
+ esac
+fi
+need_lc=${lt_cv_archive_cmds_need_lc-yes}
+##
+## END FIXME
+
+## FIXME: this should be a separate macro
+##
+# The second clause should only fire when bootstrapping the
+# libtool distribution, otherwise you forgot to ship ltmain.sh
+# with your package, and you will get complaints that there are
+# no rules to generate ltmain.sh.
+if test -f "$ltmain"; then
+ :
+else
+ # If there is no Makefile yet, we rely on a make rule to execute
+ # `config.status --recheck' to rerun these tests and create the
+ # libtool script then.
+ test -f Makefile && make "$ltmain"
+fi
+
+if test -f "$ltmain"; then
+ trap "$rm \"${ofile}T\"; exit 1" 1 2 15
+ $rm -f "${ofile}T"
+
+ echo creating $ofile
+
+ # Now quote all the things that may contain metacharacters while being
+ # careful not to overquote the AC_SUBSTed values. We take copies of the
+ # variables and quote the copies for generation of the libtool script.
+ for var in echo old_CC old_CFLAGS \
+ AR AR_FLAGS CC LD LN_S NM SHELL \
+ reload_flag reload_cmds wl \
+ pic_flag link_static_flag no_builtin_flag export_dynamic_flag_spec \
+ thread_safe_flag_spec whole_archive_flag_spec libname_spec \
+ library_names_spec soname_spec \
+ RANLIB old_archive_cmds old_archive_from_new_cmds old_postinstall_cmds \
+ old_postuninstall_cmds archive_cmds archive_expsym_cmds postinstall_cmds \
+ postuninstall_cmds extract_expsyms_cmds old_archive_from_expsyms_cmds \
+ old_striplib striplib file_magic_cmd export_symbols_cmds \
+ deplibs_check_method allow_undefined_flag no_undefined_flag \
+ finish_cmds finish_eval global_symbol_pipe global_symbol_to_cdecl \
+ global_symbol_to_c_name_address \
+ hardcode_libdir_flag_spec hardcode_libdir_separator \
+ sys_lib_search_path_spec sys_lib_dlsearch_path_spec \
+ compiler_c_o compiler_o_lo need_locks exclude_expsyms include_expsyms; do
+
+ case $var in
+ reload_cmds | old_archive_cmds | old_archive_from_new_cmds | \
+ old_postinstall_cmds | old_postuninstall_cmds | \
+ export_symbols_cmds | archive_cmds | archive_expsym_cmds | \
+ extract_expsyms_cmds | old_archive_from_expsyms_cmds | \
+ postinstall_cmds | postuninstall_cmds | \
+ finish_cmds | sys_lib_search_path_spec | sys_lib_dlsearch_path_spec)
+ # Double-quote double-evaled strings.
+ eval "lt_$var=\\\"\`\$echo \"X\$$var\" | \$Xsed -e \"\$double_quote_subst\" -e \"\$sed_quote_subst\" -e \"\$delay_variable_subst\"\`\\\""
+ ;;
+ *)
+ eval "lt_$var=\\\"\`\$echo \"X\$$var\" | \$Xsed -e \"\$sed_quote_subst\"\`\\\""
+ ;;
+ esac
+ done
+
+ cat <<__EOF__ > "${ofile}T"
+#! $SHELL
+
+# `$echo "$ofile" | sed 's%^.*/%%'` - Provide generalized library-building support services.
+# Generated automatically by $PROGRAM (GNU $PACKAGE $VERSION$TIMESTAMP)
+# NOTE: Changes made to this file will be lost: look at ltmain.sh.
+#
+# Copyright (C) 1996-2000 Free Software Foundation, Inc.
+# Originally by Gordon Matzigkeit <gord@gnu.ai.mit.edu>, 1996
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful, but
+# WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+# General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+#
+# As a special exception to the GNU General Public License, if you
+# distribute this file as part of a program that contains a
+# configuration script generated by Autoconf, you may include it under
+# the same distribution terms that you use for the rest of that program.
+
+# Sed that helps us avoid accidentally triggering echo(1) options like -n.
+Xsed="sed -e s/^X//"
+
+# The HP-UX ksh and POSIX shell print the target directory to stdout
+# if CDPATH is set.
+if test "X\${CDPATH+set}" = Xset; then CDPATH=:; export CDPATH; fi
+
+# ### BEGIN LIBTOOL CONFIG
+
+# Libtool was configured on host `(hostname || uname -n) 2>/dev/null | sed 1q`:
+
+# Shell to use when invoking shell scripts.
+SHELL=$lt_SHELL
+
+# Whether or not to build shared libraries.
+build_libtool_libs=$enable_shared
+
+# Whether or not to build static libraries.
+build_old_libs=$enable_static
+
+# Whether or not to add -lc for building shared libraries.
+build_libtool_need_lc=$need_lc
+
+# Whether or not to optimize for fast installation.
+fast_install=$enable_fast_install
+
+# The host system.
+host_alias=$host_alias
+host=$host
+
+# An echo program that does not interpret backslashes.
+echo=$lt_echo
+
+# The archiver.
+AR=$lt_AR
+AR_FLAGS=$lt_AR_FLAGS
+
+# The default C compiler.
+CC=$lt_CC
+
+# Is the compiler the GNU C compiler?
+with_gcc=$GCC
+
+# The linker used to build libraries.
+LD=$lt_LD
+
+# Whether we need hard or soft links.
+LN_S=$lt_LN_S
+
+# A BSD-compatible nm program.
+NM=$lt_NM
+
+# A symbol stripping program
+STRIP=$STRIP
+
+# Used to examine libraries when file_magic_cmd begins "file"
+MAGIC_CMD=$MAGIC_CMD
+
+# Used on cygwin: DLL creation program.
+DLLTOOL="$DLLTOOL"
+
+# Used on cygwin: object dumper.
+OBJDUMP="$OBJDUMP"
+
+# Used on cygwin: assembler.
+AS="$AS"
+
+# The name of the directory that contains temporary libtool files.
+objdir=$objdir
+
+# How to create reloadable object files.
+reload_flag=$lt_reload_flag
+reload_cmds=$lt_reload_cmds
+
+# How to pass a linker flag through the compiler.
+wl=$lt_wl
+
+# Object file suffix (normally "o").
+objext="$ac_objext"
+
+# Old archive suffix (normally "a").
+libext="$libext"
+
+# Executable file suffix (normally "").
+exeext="$exeext"
+
+# Additional compiler flags for building library objects.
+pic_flag=$lt_pic_flag
+pic_mode=$pic_mode
+
+# Does compiler simultaneously support -c and -o options?
+compiler_c_o=$lt_compiler_c_o
+
+# Can we write directly to a .lo ?
+compiler_o_lo=$lt_compiler_o_lo
+
+# Must we lock files when doing compilation ?
+need_locks=$lt_need_locks
+
+# Do we need the lib prefix for modules?
+need_lib_prefix=$need_lib_prefix
+
+# Do we need a version for libraries?
+need_version=$need_version
+
+# Whether dlopen is supported.
+dlopen_support=$enable_dlopen
+
+# Whether dlopen of programs is supported.
+dlopen_self=$enable_dlopen_self
+
+# Whether dlopen of statically linked programs is supported.
+dlopen_self_static=$enable_dlopen_self_static
+
+# Compiler flag to prevent dynamic linking.
+link_static_flag=$lt_link_static_flag
+
+# Compiler flag to turn off builtin functions.
+no_builtin_flag=$lt_no_builtin_flag
+
+# Compiler flag to allow reflexive dlopens.
+export_dynamic_flag_spec=$lt_export_dynamic_flag_spec
+
+# Compiler flag to generate shared objects directly from archives.
+whole_archive_flag_spec=$lt_whole_archive_flag_spec
+
+# Compiler flag to generate thread-safe objects.
+thread_safe_flag_spec=$lt_thread_safe_flag_spec
+
+# Library versioning type.
+version_type=$version_type
+
+# Format of library name prefix.
+libname_spec=$lt_libname_spec
+
+# List of archive names. First name is the real one, the rest are links.
+# The last name is the one that the linker finds with -lNAME.
+library_names_spec=$lt_library_names_spec
+
+# The coded name of the library, if different from the real name.
+soname_spec=$lt_soname_spec
+
+# Commands used to build and install an old-style archive.
+RANLIB=$lt_RANLIB
+old_archive_cmds=$lt_old_archive_cmds
+old_postinstall_cmds=$lt_old_postinstall_cmds
+old_postuninstall_cmds=$lt_old_postuninstall_cmds
+
+# Create an old-style archive from a shared archive.
+old_archive_from_new_cmds=$lt_old_archive_from_new_cmds
+
+# Create a temporary old-style archive to link instead of a shared archive.
+old_archive_from_expsyms_cmds=$lt_old_archive_from_expsyms_cmds
+
+# Commands used to build and install a shared archive.
+archive_cmds=$lt_archive_cmds
+archive_expsym_cmds=$lt_archive_expsym_cmds
+postinstall_cmds=$lt_postinstall_cmds
+postuninstall_cmds=$lt_postuninstall_cmds
+
+# Commands to strip libraries.
+old_striplib=$lt_old_striplib
+striplib=$lt_striplib
+
+# Method to check whether dependent libraries are shared objects.
+deplibs_check_method=$lt_deplibs_check_method
+
+# Command to use when deplibs_check_method == file_magic.
+file_magic_cmd=$lt_file_magic_cmd
+
+# Flag that allows shared libraries with undefined symbols to be built.
+allow_undefined_flag=$lt_allow_undefined_flag
+
+# Flag that forces no undefined symbols.
+no_undefined_flag=$lt_no_undefined_flag
+
+# Commands used to finish a libtool library installation in a directory.
+finish_cmds=$lt_finish_cmds
+
+# Same as above, but a single script fragment to be evaled but not shown.
+finish_eval=$lt_finish_eval
+
+# Take the output of nm and produce a listing of raw symbols and C names.
+global_symbol_pipe=$lt_global_symbol_pipe
+
+# Transform the output of nm in a proper C declaration
+global_symbol_to_cdecl=$lt_global_symbol_to_cdecl
+
+# Transform the output of nm in a C name address pair
+global_symbol_to_c_name_address=$lt_global_symbol_to_c_name_address
+
+# This is the shared library runtime path variable.
+runpath_var=$runpath_var
+
+# This is the shared library path variable.
+shlibpath_var=$shlibpath_var
+
+# Is shlibpath searched before the hard-coded library search path?
+shlibpath_overrides_runpath=$shlibpath_overrides_runpath
+
+# How to hardcode a shared library path into an executable.
+hardcode_action=$hardcode_action
+
+# Whether we should hardcode library paths into libraries.
+hardcode_into_libs=$hardcode_into_libs
+
+# Flag to hardcode \$libdir into a binary during linking.
+# This must work even if \$libdir does not exist.
+hardcode_libdir_flag_spec=$lt_hardcode_libdir_flag_spec
+
+# Whether we need a single -rpath flag with a separated argument.
+hardcode_libdir_separator=$lt_hardcode_libdir_separator
+
+# Set to yes if using DIR/libNAME.so during linking hardcodes DIR into the
+# resulting binary.
+hardcode_direct=$hardcode_direct
+
+# Set to yes if using the -LDIR flag during linking hardcodes DIR into the
+# resulting binary.
+hardcode_minus_L=$hardcode_minus_L
+
+# Set to yes if using SHLIBPATH_VAR=DIR during linking hardcodes DIR into
+# the resulting binary.
+hardcode_shlibpath_var=$hardcode_shlibpath_var
+
+# Variables whose values should be saved in libtool wrapper scripts and
+# restored at relink time.
+variables_saved_for_relink="$variables_saved_for_relink"
+
+# Whether libtool must link a program against all its dependency libraries.
+link_all_deplibs=$link_all_deplibs
+
+# Compile-time system search path for libraries
+sys_lib_search_path_spec=$lt_sys_lib_search_path_spec
+
+# Run-time system search path for libraries
+sys_lib_dlsearch_path_spec=$lt_sys_lib_dlsearch_path_spec
+
+# Fix the shell variable \$srcfile for the compiler.
+fix_srcfile_path="$fix_srcfile_path"
+
+# Set to yes if exported symbols are required.
+always_export_symbols=$always_export_symbols
+
+# The commands to list exported symbols.
+export_symbols_cmds=$lt_export_symbols_cmds
+
+# The commands to extract the exported symbol list from a shared archive.
+extract_expsyms_cmds=$lt_extract_expsyms_cmds
+
+# Symbols that should not be listed in the preloaded symbols.
+exclude_expsyms=$lt_exclude_expsyms
+
+# Symbols that must always be exported.
+include_expsyms=$lt_include_expsyms
+
+# ### END LIBTOOL CONFIG
+
+__EOF__
+
+ case $host_os in
+ aix3*)
+ cat <<\EOF >> "${ofile}T"
+
+# AIX sometimes has problems with the GCC collect2 program. For some
+# reason, if we set the COLLECT_NAMES environment variable, the problems
+# vanish in a puff of smoke.
+if test "X${COLLECT_NAMES+set}" != Xset; then
+ COLLECT_NAMES=
+ export COLLECT_NAMES
+fi
+EOF
+ ;;
+ esac
+
+ case $host_os in
+ cygwin* | mingw* | pw32* | os2*)
+ cat <<'EOF' >> "${ofile}T"
+ # This is a source program that is used to create dlls on Windows
+ # Don't remove nor modify the starting and closing comments
+# /* ltdll.c starts here */
+# #define WIN32_LEAN_AND_MEAN
+# #include <windows.h>
+# #undef WIN32_LEAN_AND_MEAN
+# #include <stdio.h>
+#
+# #ifndef __CYGWIN__
+# # ifdef __CYGWIN32__
+# # define __CYGWIN__ __CYGWIN32__
+# # endif
+# #endif
+#
+# #ifdef __cplusplus
+# extern "C" {
+# #endif
+# BOOL APIENTRY DllMain (HINSTANCE hInst, DWORD reason, LPVOID reserved);
+# #ifdef __cplusplus
+# }
+# #endif
+#
+# #ifdef __CYGWIN__
+# #include <cygwin/cygwin_dll.h>
+# DECLARE_CYGWIN_DLL( DllMain );
+# #endif
+# HINSTANCE __hDllInstance_base;
+#
+# BOOL APIENTRY
+# DllMain (HINSTANCE hInst, DWORD reason, LPVOID reserved)
+# {
+# __hDllInstance_base = hInst;
+# return TRUE;
+# }
+# /* ltdll.c ends here */
+ # This is a source program that is used to create import libraries
+ # on Windows for dlls which lack them. Don't remove nor modify the
+ # starting and closing comments
+# /* impgen.c starts here */
+# /* Copyright (C) 1999-2000 Free Software Foundation, Inc.
+#
+# This file is part of GNU libtool.
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+# */
+#
+# #include <stdio.h> /* for printf() */
+# #include <unistd.h> /* for open(), lseek(), read() */
+# #include <fcntl.h> /* for O_RDONLY, O_BINARY */
+# #include <string.h> /* for strdup() */
+#
+# /* O_BINARY isn't required (or even defined sometimes) under Unix */
+# #ifndef O_BINARY
+# #define O_BINARY 0
+# #endif
+#
+# static unsigned int
+# pe_get16 (fd, offset)
+# int fd;
+# int offset;
+# {
+# unsigned char b[2];
+# lseek (fd, offset, SEEK_SET);
+# read (fd, b, 2);
+# return b[0] + (b[1]<<8);
+# }
+#
+# static unsigned int
+# pe_get32 (fd, offset)
+# int fd;
+# int offset;
+# {
+# unsigned char b[4];
+# lseek (fd, offset, SEEK_SET);
+# read (fd, b, 4);
+# return b[0] + (b[1]<<8) + (b[2]<<16) + (b[3]<<24);
+# }
+#
+# static unsigned int
+# pe_as32 (ptr)
+# void *ptr;
+# {
+# unsigned char *b = ptr;
+# return b[0] + (b[1]<<8) + (b[2]<<16) + (b[3]<<24);
+# }
+#
+# int
+# main (argc, argv)
+# int argc;
+# char *argv[];
+# {
+# int dll;
+# unsigned long pe_header_offset, opthdr_ofs, num_entries, i;
+# unsigned long export_rva, export_size, nsections, secptr, expptr;
+# unsigned long name_rvas, nexp;
+# unsigned char *expdata, *erva;
+# char *filename, *dll_name;
+#
+# filename = argv[1];
+#
+# dll = open(filename, O_RDONLY|O_BINARY);
+# if (dll < 1)
+# return 1;
+#
+# dll_name = filename;
+#
+# for (i=0; filename[i]; i++)
+# if (filename[i] == '/' || filename[i] == '\\' || filename[i] == ':')
+# dll_name = filename + i +1;
+#
+# pe_header_offset = pe_get32 (dll, 0x3c);
+# opthdr_ofs = pe_header_offset + 4 + 20;
+# num_entries = pe_get32 (dll, opthdr_ofs + 92);
+#
+# if (num_entries < 1) /* no exports */
+# return 1;
+#
+# export_rva = pe_get32 (dll, opthdr_ofs + 96);
+# export_size = pe_get32 (dll, opthdr_ofs + 100);
+# nsections = pe_get16 (dll, pe_header_offset + 4 +2);
+# secptr = (pe_header_offset + 4 + 20 +
+# pe_get16 (dll, pe_header_offset + 4 + 16));
+#
+# expptr = 0;
+# for (i = 0; i < nsections; i++)
+# {
+# char sname[8];
+# unsigned long secptr1 = secptr + 40 * i;
+# unsigned long vaddr = pe_get32 (dll, secptr1 + 12);
+# unsigned long vsize = pe_get32 (dll, secptr1 + 16);
+# unsigned long fptr = pe_get32 (dll, secptr1 + 20);
+# lseek(dll, secptr1, SEEK_SET);
+# read(dll, sname, 8);
+# if (vaddr <= export_rva && vaddr+vsize > export_rva)
+# {
+# expptr = fptr + (export_rva - vaddr);
+# if (export_rva + export_size > vaddr + vsize)
+# export_size = vsize - (export_rva - vaddr);
+# break;
+# }
+# }
+#
+# expdata = (unsigned char*)malloc(export_size);
+# lseek (dll, expptr, SEEK_SET);
+# read (dll, expdata, export_size);
+# erva = expdata - export_rva;
+#
+# nexp = pe_as32 (expdata+24);
+# name_rvas = pe_as32 (expdata+32);
+#
+# printf ("EXPORTS\n");
+# for (i = 0; i<nexp; i++)
+# {
+# unsigned long name_rva = pe_as32 (erva+name_rvas+i*4);
+# printf ("\t%s @ %ld ;\n", erva+name_rva, 1+ i);
+# }
+#
+# return 0;
+# }
+# /* impgen.c ends here */
+
+EOF
+ ;;
+ esac
+
+ # We use sed instead of cat because bash on DJGPP gets confused if
+ # if finds mixed CR/LF and LF-only lines. Since sed operates in
+ # text mode, it properly converts lines to CR/LF. This bash problem
+ # is reportedly fixed, but why not run on old versions too?
+ sed '$q' "$ltmain" >> "${ofile}T" || (rm -f "${ofile}T"; exit 1)
+
+ mv -f "${ofile}T" "$ofile" || \
+ (rm -f "$ofile" && cp "${ofile}T" "$ofile" && rm -f "${ofile}T")
+ chmod +x "$ofile"
+fi
+##
+## END FIXME
+
+])# _LT_AC_LTCONFIG_HACK
+
+# AC_LIBTOOL_DLOPEN - enable checks for dlopen support
+AC_DEFUN([AC_LIBTOOL_DLOPEN], [AC_BEFORE([$0],[AC_LIBTOOL_SETUP])])
+
+# AC_LIBTOOL_WIN32_DLL - declare package support for building win32 dll's
+AC_DEFUN([AC_LIBTOOL_WIN32_DLL], [AC_BEFORE([$0], [AC_LIBTOOL_SETUP])])
+
+# AC_ENABLE_SHARED - implement the --enable-shared flag
+# Usage: AC_ENABLE_SHARED[(DEFAULT)]
+# Where DEFAULT is either `yes' or `no'. If omitted, it defaults to
+# `yes'.
+AC_DEFUN([AC_ENABLE_SHARED],
+[define([AC_ENABLE_SHARED_DEFAULT], ifelse($1, no, no, yes))dnl
+AC_ARG_ENABLE(shared,
+changequote(<<, >>)dnl
+<< --enable-shared[=PKGS] build shared libraries [default=>>AC_ENABLE_SHARED_DEFAULT],
+changequote([, ])dnl
+[p=${PACKAGE-default}
+case $enableval in
+yes) enable_shared=yes ;;
+no) enable_shared=no ;;
+*)
+ enable_shared=no
+ # Look at the argument we got. We use all the common list separators.
+ IFS="${IFS= }"; ac_save_ifs="$IFS"; IFS="${IFS}:,"
+ for pkg in $enableval; do
+ if test "X$pkg" = "X$p"; then
+ enable_shared=yes
+ fi
+ done
+ IFS="$ac_save_ifs"
+ ;;
+esac],
+enable_shared=AC_ENABLE_SHARED_DEFAULT)dnl
+])
+
+# AC_DISABLE_SHARED - set the default shared flag to --disable-shared
+AC_DEFUN([AC_DISABLE_SHARED],
+[AC_BEFORE([$0],[AC_LIBTOOL_SETUP])dnl
+AC_ENABLE_SHARED(no)])
+
+# AC_ENABLE_STATIC - implement the --enable-static flag
+# Usage: AC_ENABLE_STATIC[(DEFAULT)]
+# Where DEFAULT is either `yes' or `no'. If omitted, it defaults to
+# `yes'.
+AC_DEFUN([AC_ENABLE_STATIC],
+[define([AC_ENABLE_STATIC_DEFAULT], ifelse($1, no, no, yes))dnl
+AC_ARG_ENABLE(static,
+changequote(<<, >>)dnl
+<< --enable-static[=PKGS] build static libraries [default=>>AC_ENABLE_STATIC_DEFAULT],
+changequote([, ])dnl
+[p=${PACKAGE-default}
+case $enableval in
+yes) enable_static=yes ;;
+no) enable_static=no ;;
+*)
+ enable_static=no
+ # Look at the argument we got. We use all the common list separators.
+ IFS="${IFS= }"; ac_save_ifs="$IFS"; IFS="${IFS}:,"
+ for pkg in $enableval; do
+ if test "X$pkg" = "X$p"; then
+ enable_static=yes
+ fi
+ done
+ IFS="$ac_save_ifs"
+ ;;
+esac],
+enable_static=AC_ENABLE_STATIC_DEFAULT)dnl
+])
+
+# AC_DISABLE_STATIC - set the default static flag to --disable-static
+AC_DEFUN([AC_DISABLE_STATIC],
+[AC_BEFORE([$0],[AC_LIBTOOL_SETUP])dnl
+AC_ENABLE_STATIC(no)])
+
+
+# AC_ENABLE_FAST_INSTALL - implement the --enable-fast-install flag
+# Usage: AC_ENABLE_FAST_INSTALL[(DEFAULT)]
+# Where DEFAULT is either `yes' or `no'. If omitted, it defaults to
+# `yes'.
+AC_DEFUN([AC_ENABLE_FAST_INSTALL],
+[define([AC_ENABLE_FAST_INSTALL_DEFAULT], ifelse($1, no, no, yes))dnl
+AC_ARG_ENABLE(fast-install,
+changequote(<<, >>)dnl
+<< --enable-fast-install[=PKGS] optimize for fast installation [default=>>AC_ENABLE_FAST_INSTALL_DEFAULT],
+changequote([, ])dnl
+[p=${PACKAGE-default}
+case $enableval in
+yes) enable_fast_install=yes ;;
+no) enable_fast_install=no ;;
+*)
+ enable_fast_install=no
+ # Look at the argument we got. We use all the common list separators.
+ IFS="${IFS= }"; ac_save_ifs="$IFS"; IFS="${IFS}:,"
+ for pkg in $enableval; do
+ if test "X$pkg" = "X$p"; then
+ enable_fast_install=yes
+ fi
+ done
+ IFS="$ac_save_ifs"
+ ;;
+esac],
+enable_fast_install=AC_ENABLE_FAST_INSTALL_DEFAULT)dnl
+])
+
+# AC_DISABLE_FAST_INSTALL - set the default to --disable-fast-install
+AC_DEFUN([AC_DISABLE_FAST_INSTALL],
+[AC_BEFORE([$0],[AC_LIBTOOL_SETUP])dnl
+AC_ENABLE_FAST_INSTALL(no)])
+
+# AC_LIBTOOL_PICMODE - implement the --with-pic flag
+# Usage: AC_LIBTOOL_PICMODE[(MODE)]
+# Where MODE is either `yes' or `no'. If omitted, it defaults to
+# `both'.
+AC_DEFUN([AC_LIBTOOL_PICMODE],
+[AC_BEFORE([$0],[AC_LIBTOOL_SETUP])dnl
+pic_mode=ifelse($#,1,$1,default)])
+
+
+# AC_PATH_TOOL_PREFIX - find a file program which can recognise shared library
+AC_DEFUN([AC_PATH_TOOL_PREFIX],
+[AC_MSG_CHECKING([for $1])
+AC_CACHE_VAL(lt_cv_path_MAGIC_CMD,
+[case $MAGIC_CMD in
+ /*)
+ lt_cv_path_MAGIC_CMD="$MAGIC_CMD" # Let the user override the test with a path.
+ ;;
+ ?:/*)
+ lt_cv_path_MAGIC_CMD="$MAGIC_CMD" # Let the user override the test with a dos path.
+ ;;
+ *)
+ ac_save_MAGIC_CMD="$MAGIC_CMD"
+ IFS="${IFS= }"; ac_save_ifs="$IFS"; IFS=":"
+dnl $ac_dummy forces splitting on constant user-supplied paths.
+dnl POSIX.2 word splitting is done only on the output of word expansions,
+dnl not every word. This closes a longstanding sh security hole.
+ ac_dummy="ifelse([$2], , $PATH, [$2])"
+ for ac_dir in $ac_dummy; do
+ test -z "$ac_dir" && ac_dir=.
+ if test -f $ac_dir/$1; then
+ lt_cv_path_MAGIC_CMD="$ac_dir/$1"
+ if test -n "$file_magic_test_file"; then
+ case $deplibs_check_method in
+ "file_magic "*)
+ file_magic_regex="`expr \"$deplibs_check_method\" : \"file_magic \(.*\)\"`"
+ MAGIC_CMD="$lt_cv_path_MAGIC_CMD"
+ if eval $file_magic_cmd \$file_magic_test_file 2> /dev/null |
+ egrep "$file_magic_regex" > /dev/null; then
+ :
+ else
+ cat <<EOF 1>&2
+
+*** Warning: the command libtool uses to detect shared libraries,
+*** $file_magic_cmd, produces output that libtool cannot recognize.
+*** The result is that libtool may fail to recognize shared libraries
+*** as such. This will affect the creation of libtool libraries that
+*** depend on shared libraries, but programs linked with such libtool
+*** libraries will work regardless of this problem. Nevertheless, you
+*** may want to report the problem to your system manager and/or to
+*** bug-libtool@gnu.org
+
+EOF
+ fi ;;
+ esac
+ fi
+ break
+ fi
+ done
+ IFS="$ac_save_ifs"
+ MAGIC_CMD="$ac_save_MAGIC_CMD"
+ ;;
+esac])
+MAGIC_CMD="$lt_cv_path_MAGIC_CMD"
+if test -n "$MAGIC_CMD"; then
+ AC_MSG_RESULT($MAGIC_CMD)
+else
+ AC_MSG_RESULT(no)
+fi
+])
+
+
+# AC_PATH_MAGIC - find a file program which can recognise a shared library
+AC_DEFUN([AC_PATH_MAGIC],
+[AC_REQUIRE([AC_CHECK_TOOL_PREFIX])dnl
+AC_PATH_TOOL_PREFIX(${ac_tool_prefix}file, /usr/bin:$PATH)
+if test -z "$lt_cv_path_MAGIC_CMD"; then
+ if test -n "$ac_tool_prefix"; then
+ AC_PATH_TOOL_PREFIX(file, /usr/bin:$PATH)
+ else
+ MAGIC_CMD=:
+ fi
+fi
+])
+
+
+# AC_PROG_LD - find the path to the GNU or non-GNU linker
+AC_DEFUN([AC_PROG_LD],
+[AC_ARG_WITH(gnu-ld,
+[ --with-gnu-ld assume the C compiler uses GNU ld [default=no]],
+test "$withval" = no || with_gnu_ld=yes, with_gnu_ld=no)
+AC_REQUIRE([AC_PROG_CC])dnl
+AC_REQUIRE([AC_CANONICAL_HOST])dnl
+AC_REQUIRE([AC_CANONICAL_BUILD])dnl
+AC_REQUIRE([_LT_AC_LIBTOOL_SYS_PATH_SEPARATOR])dnl
+ac_prog=ld
+if test "$GCC" = yes; then
+ # Check if gcc -print-prog-name=ld gives a path.
+ AC_MSG_CHECKING([for ld used by GCC])
+ case $host in
+ *-*-mingw*)
+ # gcc leaves a trailing carriage return which upsets mingw
+ ac_prog=`($CC -print-prog-name=ld) 2>&5 | tr -d '\015'` ;;
+ *)
+ ac_prog=`($CC -print-prog-name=ld) 2>&5` ;;
+ esac
+ case $ac_prog in
+ # Accept absolute paths.
+ [[\\/]]* | [[A-Za-z]]:[[\\/]]*)
+ re_direlt='/[[^/]][[^/]]*/\.\./'
+ # Canonicalize the path of ld
+ ac_prog=`echo $ac_prog| sed 's%\\\\%/%g'`
+ while echo $ac_prog | grep "$re_direlt" > /dev/null 2>&1; do
+ ac_prog=`echo $ac_prog| sed "s%$re_direlt%/%"`
+ done
+ test -z "$LD" && LD="$ac_prog"
+ ;;
+ "")
+ # If it fails, then pretend we aren't using GCC.
+ ac_prog=ld
+ ;;
+ *)
+ # If it is relative, then search for the first ld in PATH.
+ with_gnu_ld=unknown
+ ;;
+ esac
+elif test "$with_gnu_ld" = yes; then
+ AC_MSG_CHECKING([for GNU ld])
+else
+ AC_MSG_CHECKING([for non-GNU ld])
+fi
+AC_CACHE_VAL(lt_cv_path_LD,
+[if test -z "$LD"; then
+ IFS="${IFS= }"; ac_save_ifs="$IFS"; IFS=$PATH_SEPARATOR
+ for ac_dir in $PATH; do
+ test -z "$ac_dir" && ac_dir=.
+ if test -f "$ac_dir/$ac_prog" || test -f "$ac_dir/$ac_prog$ac_exeext"; then
+ lt_cv_path_LD="$ac_dir/$ac_prog"
+ # Check to see if the program is GNU ld. I'd rather use --version,
+ # but apparently some GNU ld's only accept -v.
+ # Break only if it was the GNU/non-GNU ld that we prefer.
+ if "$lt_cv_path_LD" -v 2>&1 < /dev/null | egrep '(GNU|with BFD)' > /dev/null; then
+ test "$with_gnu_ld" != no && break
+ else
+ test "$with_gnu_ld" != yes && break
+ fi
+ fi
+ done
+ IFS="$ac_save_ifs"
+else
+ lt_cv_path_LD="$LD" # Let the user override the test with a path.
+fi])
+LD="$lt_cv_path_LD"
+if test -n "$LD"; then
+ AC_MSG_RESULT($LD)
+else
+ AC_MSG_RESULT(no)
+fi
+test -z "$LD" && AC_MSG_ERROR([no acceptable ld found in \$PATH])
+AC_PROG_LD_GNU
+])
+
+# AC_PROG_LD_GNU -
+AC_DEFUN([AC_PROG_LD_GNU],
+[AC_CACHE_CHECK([if the linker ($LD) is GNU ld], lt_cv_prog_gnu_ld,
+[# I'd rather use --version here, but apparently some GNU ld's only accept -v.
+if $LD -v 2>&1 </dev/null | egrep '(GNU|with BFD)' 1>&5; then
+ lt_cv_prog_gnu_ld=yes
+else
+ lt_cv_prog_gnu_ld=no
+fi])
+with_gnu_ld=$lt_cv_prog_gnu_ld
+])
+
+# AC_PROG_LD_RELOAD_FLAG - find reload flag for linker
+# -- PORTME Some linkers may need a different reload flag.
+AC_DEFUN([AC_PROG_LD_RELOAD_FLAG],
+[AC_CACHE_CHECK([for $LD option to reload object files], lt_cv_ld_reload_flag,
+[lt_cv_ld_reload_flag='-r'])
+reload_flag=$lt_cv_ld_reload_flag
+test -n "$reload_flag" && reload_flag=" $reload_flag"
+])
+
+# AC_DEPLIBS_CHECK_METHOD - how to check for library dependencies
+# -- PORTME fill in with the dynamic library characteristics
+AC_DEFUN([AC_DEPLIBS_CHECK_METHOD],
+[AC_CACHE_CHECK([how to recognise dependant libraries],
+lt_cv_deplibs_check_method,
+[lt_cv_file_magic_cmd='$MAGIC_CMD'
+lt_cv_file_magic_test_file=
+lt_cv_deplibs_check_method='unknown'
+# Need to set the preceding variable on all platforms that support
+# interlibrary dependencies.
+# 'none' -- dependencies not supported.
+# `unknown' -- same as none, but documents that we really don't know.
+# 'pass_all' -- all dependencies passed with no checks.
+# 'test_compile' -- check by making test program.
+# 'file_magic [[regex]]' -- check by looking for files in library path
+# which responds to the $file_magic_cmd with a given egrep regex.
+# If you have `file' or equivalent on your system and you're not sure
+# whether `pass_all' will *always* work, you probably want this one.
+
+case $host_os in
+aix4* | aix5*)
+ lt_cv_deplibs_check_method=pass_all
+ ;;
+
+beos*)
+ lt_cv_deplibs_check_method=pass_all
+ ;;
+
+bsdi4*)
+ lt_cv_deplibs_check_method='file_magic ELF [[0-9]][[0-9]]*-bit [[ML]]SB (shared object|dynamic lib)'
+ lt_cv_file_magic_cmd='/usr/bin/file -L'
+ lt_cv_file_magic_test_file=/shlib/libc.so
+ ;;
+
+cygwin* | mingw* | pw32*)
+ lt_cv_deplibs_check_method='file_magic file format pei*-i386(.*architecture: i386)?'
+ lt_cv_file_magic_cmd='$OBJDUMP -f'
+ ;;
+
+darwin* | rhapsody*)
+ lt_cv_deplibs_check_method='file_magic Mach-O dynamically linked shared library'
+ lt_cv_file_magic_cmd='/usr/bin/file -L'
+ case "$host_os" in
+ rhapsody* | darwin1.[[012]])
+ lt_cv_file_magic_test_file=`echo /System/Library/Frameworks/System.framework/Versions/*/System | head -1`
+ ;;
+ *) # Darwin 1.3 on
+ lt_cv_file_magic_test_file='/usr/lib/libSystem.dylib'
+ ;;
+ esac
+ ;;
+
+freebsd*)
+ if echo __ELF__ | $CC -E - | grep __ELF__ > /dev/null; then
+ case $host_cpu in
+ i*86 )
+ # Not sure whether the presence of OpenBSD here was a mistake.
+ # Let's accept both of them until this is cleared up.
+ lt_cv_deplibs_check_method='file_magic (FreeBSD|OpenBSD)/i[[3-9]]86 (compact )?demand paged shared library'
+ lt_cv_file_magic_cmd=/usr/bin/file
+ lt_cv_file_magic_test_file=`echo /usr/lib/libc.so.*`
+ ;;
+ esac
+ else
+ lt_cv_deplibs_check_method=pass_all
+ fi
+ ;;
+
+gnu*)
+ lt_cv_deplibs_check_method=pass_all
+ ;;
+
+hpux10.20*|hpux11*)
+ lt_cv_deplibs_check_method='file_magic (s[[0-9]][[0-9]][[0-9]]|PA-RISC[[0-9]].[[0-9]]) shared library'
+ lt_cv_file_magic_cmd=/usr/bin/file
+ lt_cv_file_magic_test_file=/usr/lib/libc.sl
+ ;;
+
+irix5* | irix6*)
+ case $host_os in
+ irix5*)
+ # this will be overridden with pass_all, but let us keep it just in case
+ lt_cv_deplibs_check_method="file_magic ELF 32-bit MSB dynamic lib MIPS - version 1"
+ ;;
+ *)
+ case $LD in
+ *-32|*"-32 ") libmagic=32-bit;;
+ *-n32|*"-n32 ") libmagic=N32;;
+ *-64|*"-64 ") libmagic=64-bit;;
+ *) libmagic=never-match;;
+ esac
+ # this will be overridden with pass_all, but let us keep it just in case
+ lt_cv_deplibs_check_method="file_magic ELF ${libmagic} MSB mips-[[1234]] dynamic lib MIPS - version 1"
+ ;;
+ esac
+ lt_cv_file_magic_test_file=`echo /lib${libsuff}/libc.so*`
+ lt_cv_deplibs_check_method=pass_all
+ ;;
+
+# This must be Linux ELF.
+linux-gnu*)
+ case $host_cpu in
+ alpha* | hppa* | i*86 | powerpc* | sparc* | ia64* )
+ lt_cv_deplibs_check_method=pass_all ;;
+ *)
+ # glibc up to 2.1.1 does not perform some relocations on ARM
+ lt_cv_deplibs_check_method='file_magic ELF [[0-9]][[0-9]]*-bit [[LM]]SB (shared object|dynamic lib )' ;;
+ esac
+ lt_cv_file_magic_test_file=`echo /lib/libc.so* /lib/libc-*.so`
+ ;;
+
+netbsd*)
+ if echo __ELF__ | $CC -E - | grep __ELF__ > /dev/null; then
+ lt_cv_deplibs_check_method='match_pattern /lib[[^/\.]]+\.so\.[[0-9]]+\.[[0-9]]+$'
+ else
+ lt_cv_deplibs_check_method='match_pattern /lib[[^/\.]]+\.so$'
+ fi
+ ;;
+
+newos6*)
+ lt_cv_deplibs_check_method='file_magic ELF [[0-9]][[0-9]]*-bit [[ML]]SB (executable|dynamic lib)'
+ lt_cv_file_magic_cmd=/usr/bin/file
+ lt_cv_file_magic_test_file=/usr/lib/libnls.so
+ ;;
+
+openbsd*)
+ lt_cv_file_magic_cmd=/usr/bin/file
+ lt_cv_file_magic_test_file=`echo /usr/lib/libc.so.*`
+ if test -z "`echo __ELF__ | $CC -E - | grep __ELF__`" || test "$host_os-$host_cpu" = "openbsd2.8-powerpc"; then
+ lt_cv_deplibs_check_method='file_magic ELF [[0-9]][[0-9]]*-bit [[LM]]SB shared object'
+ else
+ lt_cv_deplibs_check_method='file_magic OpenBSD.* shared library'
+ fi
+ ;;
+
+osf3* | osf4* | osf5*)
+ # this will be overridden with pass_all, but let us keep it just in case
+ lt_cv_deplibs_check_method='file_magic COFF format alpha shared library'
+ lt_cv_file_magic_test_file=/shlib/libc.so
+ lt_cv_deplibs_check_method=pass_all
+ ;;
+
+sco3.2v5*)
+ lt_cv_deplibs_check_method=pass_all
+ ;;
+
+solaris*)
+ lt_cv_deplibs_check_method=pass_all
+ lt_cv_file_magic_test_file=/lib/libc.so
+ ;;
+
+sysv5uw[[78]]* | sysv4*uw2*)
+ lt_cv_deplibs_check_method=pass_all
+ ;;
+
+sysv4 | sysv4.2uw2* | sysv4.3* | sysv5*)
+ case $host_vendor in
+ motorola)
+ lt_cv_deplibs_check_method='file_magic ELF [[0-9]][[0-9]]*-bit [[ML]]SB (shared object|dynamic lib) M[[0-9]][[0-9]]* Version [[0-9]]'
+ lt_cv_file_magic_test_file=`echo /usr/lib/libc.so*`
+ ;;
+ ncr)
+ lt_cv_deplibs_check_method=pass_all
+ ;;
+ sequent)
+ lt_cv_file_magic_cmd='/bin/file'
+ lt_cv_deplibs_check_method='file_magic ELF [[0-9]][[0-9]]*-bit [[LM]]SB (shared object|dynamic lib )'
+ ;;
+ sni)
+ lt_cv_file_magic_cmd='/bin/file'
+ lt_cv_deplibs_check_method="file_magic ELF [[0-9]][[0-9]]*-bit [[LM]]SB dynamic lib"
+ lt_cv_file_magic_test_file=/lib/libc.so
+ ;;
+ esac
+ ;;
+esac
+])
+file_magic_cmd=$lt_cv_file_magic_cmd
+deplibs_check_method=$lt_cv_deplibs_check_method
+])
+
+
+# AC_PROG_NM - find the path to a BSD-compatible name lister
+AC_DEFUN([AC_PROG_NM],
+[AC_REQUIRE([_LT_AC_LIBTOOL_SYS_PATH_SEPARATOR])dnl
+AC_MSG_CHECKING([for BSD-compatible nm])
+AC_CACHE_VAL(lt_cv_path_NM,
+[if test -n "$NM"; then
+ # Let the user override the test.
+ lt_cv_path_NM="$NM"
+else
+ IFS="${IFS= }"; ac_save_ifs="$IFS"; IFS=$PATH_SEPARATOR
+ for ac_dir in $PATH /usr/ccs/bin /usr/ucb /bin; do
+ test -z "$ac_dir" && ac_dir=.
+ tmp_nm=$ac_dir/${ac_tool_prefix}nm
+ if test -f $tmp_nm || test -f $tmp_nm$ac_exeext ; then
+ # Check to see if the nm accepts a BSD-compat flag.
+ # Adding the `sed 1q' prevents false positives on HP-UX, which says:
+ # nm: unknown option "B" ignored
+ # Tru64's nm complains that /dev/null is an invalid object file
+ if ($tmp_nm -B /dev/null 2>&1 | sed '1q'; exit 0) | egrep '(/dev/null|Invalid file or object type)' >/dev/null; then
+ lt_cv_path_NM="$tmp_nm -B"
+ break
+ elif ($tmp_nm -p /dev/null 2>&1 | sed '1q'; exit 0) | egrep /dev/null >/dev/null; then
+ lt_cv_path_NM="$tmp_nm -p"
+ break
+ else
+ lt_cv_path_NM=${lt_cv_path_NM="$tmp_nm"} # keep the first match, but
+ continue # so that we can try to find one that supports BSD flags
+ fi
+ fi
+ done
+ IFS="$ac_save_ifs"
+ test -z "$lt_cv_path_NM" && lt_cv_path_NM=nm
+fi])
+NM="$lt_cv_path_NM"
+AC_MSG_RESULT([$NM])
+])
+
+# AC_CHECK_LIBM - check for math library
+AC_DEFUN([AC_CHECK_LIBM],
+[AC_REQUIRE([AC_CANONICAL_HOST])dnl
+LIBM=
+case $host in
+*-*-beos* | *-*-cygwin* | *-*-pw32*)
+ # These system don't have libm
+ ;;
+*-ncr-sysv4.3*)
+ AC_CHECK_LIB(mw, _mwvalidcheckl, LIBM="-lmw")
+ AC_CHECK_LIB(m, main, LIBM="$LIBM -lm")
+ ;;
+*)
+ AC_CHECK_LIB(m, main, LIBM="-lm")
+ ;;
+esac
+])
+
+# AC_LIBLTDL_CONVENIENCE[(dir)] - sets LIBLTDL to the link flags for
+# the libltdl convenience library and INCLTDL to the include flags for
+# the libltdl header and adds --enable-ltdl-convenience to the
+# configure arguments. Note that LIBLTDL and INCLTDL are not
+# AC_SUBSTed, nor is AC_CONFIG_SUBDIRS called. If DIR is not
+# provided, it is assumed to be `libltdl'. LIBLTDL will be prefixed
+# with '${top_builddir}/' and INCLTDL will be prefixed with
+# '${top_srcdir}/' (note the single quotes!). If your package is not
+# flat and you're not using automake, define top_builddir and
+# top_srcdir appropriately in the Makefiles.
+AC_DEFUN([AC_LIBLTDL_CONVENIENCE],
+[AC_BEFORE([$0],[AC_LIBTOOL_SETUP])dnl
+ case $enable_ltdl_convenience in
+ no) AC_MSG_ERROR([this package needs a convenience libltdl]) ;;
+ "") enable_ltdl_convenience=yes
+ ac_configure_args="$ac_configure_args --enable-ltdl-convenience" ;;
+ esac
+ LIBLTDL='${top_builddir}/'ifelse($#,1,[$1],['libltdl'])/libltdlc.la
+ INCLTDL='-I${top_srcdir}/'ifelse($#,1,[$1],['libltdl'])
+])
+
+# AC_LIBLTDL_INSTALLABLE[(dir)] - sets LIBLTDL to the link flags for
+# the libltdl installable library and INCLTDL to the include flags for
+# the libltdl header and adds --enable-ltdl-install to the configure
+# arguments. Note that LIBLTDL and INCLTDL are not AC_SUBSTed, nor is
+# AC_CONFIG_SUBDIRS called. If DIR is not provided and an installed
+# libltdl is not found, it is assumed to be `libltdl'. LIBLTDL will
+# be prefixed with '${top_builddir}/' and INCLTDL will be prefixed
+# with '${top_srcdir}/' (note the single quotes!). If your package is
+# not flat and you're not using automake, define top_builddir and
+# top_srcdir appropriately in the Makefiles.
+# In the future, this macro may have to be called after AC_PROG_LIBTOOL.
+AC_DEFUN([AC_LIBLTDL_INSTALLABLE],
+[AC_BEFORE([$0],[AC_LIBTOOL_SETUP])dnl
+ AC_CHECK_LIB(ltdl, main,
+ [test x"$enable_ltdl_install" != xyes && enable_ltdl_install=no],
+ [if test x"$enable_ltdl_install" = xno; then
+ AC_MSG_WARN([libltdl not installed, but installation disabled])
+ else
+ enable_ltdl_install=yes
+ fi
+ ])
+ if test x"$enable_ltdl_install" = x"yes"; then
+ ac_configure_args="$ac_configure_args --enable-ltdl-install"
+ LIBLTDL='${top_builddir}/'ifelse($#,1,[$1],['libltdl'])/libltdl.la
+ INCLTDL='-I${top_srcdir}/'ifelse($#,1,[$1],['libltdl'])
+ else
+ ac_configure_args="$ac_configure_args --enable-ltdl-install=no"
+ LIBLTDL="-lltdl"
+ INCLTDL=
+ fi
+])
+
+# old names
+AC_DEFUN([AM_PROG_LIBTOOL], [AC_PROG_LIBTOOL])
+AC_DEFUN([AM_ENABLE_SHARED], [AC_ENABLE_SHARED($@)])
+AC_DEFUN([AM_ENABLE_STATIC], [AC_ENABLE_STATIC($@)])
+AC_DEFUN([AM_DISABLE_SHARED], [AC_DISABLE_SHARED($@)])
+AC_DEFUN([AM_DISABLE_STATIC], [AC_DISABLE_STATIC($@)])
+AC_DEFUN([AM_PROG_LD], [AC_PROG_LD])
+AC_DEFUN([AM_PROG_NM], [AC_PROG_NM])
+
+# This is just to silence aclocal about the macro not being used
+ifelse([AC_DISABLE_FAST_INSTALL])
--- /dev/null
+## $Id: Makefile 7727 2008-04-06 07:59:46Z iulius $
+
+include ../Makefile.global
+
+top = ..
+CFLAGS = $(GCFLAGS)
+
+ALL = auth_smb ckpasswd domain ident radius $(KRB5_AUTH)
+
+LIBSMB = smbval/smbvalid.a
+
+LIBAUTH = libauth.o
+
+SOURCES = auth_krb5.c auth_smb.c ckpasswd.c domain.c ident.c libauth.c \
+ radius.c
+
+all: $(ALL)
+
+warnings:
+ $(MAKE) COPT='$(WARNINGS)' all
+
+install: all
+ if [ x"$(KRB5_AUTH)" != x ] ; then \
+ $(LI_XPUB) auth_krb5 $(D)$(PATHAUTHPASSWD)/auth_krb5 ; \
+ fi
+ for F in auth_smb ckpasswd radius ; do \
+ $(LI_XPUB) $$F $D$(PATHAUTHPASSWD)/$$F ; \
+ done
+ for F in domain ident ; do \
+ $(LI_XPUB) $$F $D$(PATHAUTHRESOLV)/$$F ; \
+ done
+
+clobber clean distclean:
+ rm -f *.o $(ALL)
+ rm -rf .libs
+ cd smbval && $(MAKE) clean
+
+tags ctags: $(SOURCES)
+ $(CTAGS) $(SOURCES) ../lib/*.c ../include/*.h
+
+profiled:
+ $(MAKEPROFILING) all
+
+
+## Compilation rules.
+
+LINK = $(LIBLD) $(LDFLAGS) -o $@
+CKLIBS = $(CRYPTLIB) $(SHADOWLIB) $(PAMLIB) $(DBMLIB)
+
+auth_krb5: auth_krb5.o $(LIBAUTH) $(LIBINN)
+ $(LINK) auth_krb5.o $(LIBAUTH) $(KRB5LIB) $(LIBINN) $(LIBS)
+
+auth_smb: auth_smb.o $(LIBSMB) $(LIBAUTH) $(LIBINN)
+ $(LINK) auth_smb.o $(LIBSMB) $(LIBAUTH) $(LIBINN) $(LIBS)
+
+ckpasswd: ckpasswd.o $(LIBAUTH) $(LIBINN)
+ $(LINK) ckpasswd.o $(LIBAUTH) $(CKLIBS) $(LIBINN) $(LIBS)
+
+domain: domain.o $(LIBAUTH) $(LIBINN)
+ $(LINK) domain.o $(LIBAUTH) $(LIBINN) $(LIBS)
+
+ident: ident.o $(LIBAUTH) $(LIBINN)
+ $(LINK) ident.o $(LIBAUTH) $(LIBINN) $(LIBS)
+
+radius: radius.o $(LIBAUTH) $(LIBINN)
+ $(LINK) radius.o $(LIBAUTH) $(LIBINN) $(LIBS)
+
+auth_krb5.o: auth_krb5.c
+ $(CC) $(CFLAGS) $(KRB5INC) -c auth_krb5.c
+
+ckpasswd.o: ckpasswd.c
+ $(CC) $(CFLAGS) $(DBMINC) -c ckpasswd.c
+
+$(LIBINN): ; (cd ../lib ; $(MAKE))
+$(LIBSMB): ; (cd smbval ; $(MAKE))
+$(LIBAUTH): libauth.h libauth.c
+
+
+## Dependencies. Default list, below, is probably good enough.
+
+depend: Makefile $(SOURCES)
+ $(MAKEDEPEND) '$(CFLAGS)' $(SOURCES)
+
+# DO NOT DELETE THIS LINE -- make depend depends on it.
+auth_krb5.o: auth_krb5.c ../include/config.h ../include/inn/defines.h \
+ ../include/inn/system.h ../include/clibrary.h ../include/config.h \
+ libauth.h ../include/portable/socket.h ../include/config.h \
+ ../include/inn/messages.h ../include/inn/defines.h ../include/libinn.h
+auth_smb.o: auth_smb.c ../include/config.h ../include/inn/defines.h \
+ ../include/inn/system.h ../include/clibrary.h ../include/config.h \
+ ../include/inn/messages.h ../include/inn/defines.h libauth.h \
+ ../include/portable/socket.h ../include/config.h smbval/valid.h
+ckpasswd.o: ckpasswd.c ../include/config.h ../include/inn/defines.h \
+ ../include/inn/system.h ../include/clibrary.h ../include/config.h \
+ ../include/inn/messages.h ../include/inn/defines.h ../include/inn/qio.h \
+ ../include/inn/vector.h ../include/libinn.h libauth.h \
+ ../include/portable/socket.h ../include/config.h
+domain.o: domain.c ../include/config.h ../include/inn/defines.h \
+ ../include/inn/system.h ../include/clibrary.h ../include/config.h \
+ ../include/inn/messages.h ../include/inn/defines.h ../include/libinn.h \
+ libauth.h ../include/portable/socket.h ../include/config.h
+ident.o: ident.c ../include/config.h ../include/inn/defines.h \
+ ../include/inn/system.h ../include/clibrary.h ../include/config.h \
+ ../include/inn/messages.h ../include/inn/defines.h ../include/libinn.h \
+ libauth.h ../include/portable/socket.h ../include/config.h
+libauth.o: libauth.c ../include/config.h ../include/inn/defines.h \
+ ../include/inn/system.h ../include/clibrary.h ../include/config.h \
+ ../include/libinn.h libauth.h ../include/portable/socket.h \
+ ../include/config.h ../include/inn/messages.h ../include/inn/defines.h
+radius.o: radius.c ../include/config.h ../include/inn/defines.h \
+ ../include/inn/system.h ../include/clibrary.h ../include/config.h \
+ ../include/portable/time.h ../include/config.h ../include/inn/innconf.h \
+ ../include/inn/defines.h ../include/inn/md5.h ../include/inn/messages.h \
+ ../include/libinn.h ../include/nntp.h ../include/paths.h \
+ ../include/conffile.h libauth.h ../include/portable/socket.h
--- /dev/null
+/* $Id: auth_krb5.c 7462 2005-12-12 01:06:54Z eagle $
+**
+** Check an username and password against Kerberos v5.
+**
+** Based on nnrpkrb5auth by Christopher P. Lindsey <lindsey@mallorn.com>
+** See <http://www.mallorn.com/tools/nnrpkrb5auth>
+**
+** This program takes a username and password pair from nnrpd and checks
+** checks their validity against a Kerberos v5 KDC by attempting to obtain a
+** TGT. With the -i <instance> command line option, appends /<instance> to
+** the username prior to authentication.
+**
+** Special thanks to Von Welch <vwelch@vwelch.com> for giving me the initial
+** code on which the Kerberos V authentication is based many years ago, and
+** for introducing me to Kerberos back in '96.
+**
+** Also, thanks to Graeme Mathieson <graeme@mathie.cx> for his inspiration
+** through the pamckpasswd program.
+*/
+
+#include "config.h"
+#include "clibrary.h"
+#include "libauth.h"
+#ifdef HAVE_ET_COM_ERR_H
+# include <et/com_err.h>
+#else
+# include <com_err.h>
+#endif
+
+/* krb5_get_in_tkt_with_password is deprecated. */
+#define KRB5_DEPRECATED 1
+#include <krb5.h>
+
+#include "inn/messages.h"
+#include "libinn.h"
+
+/*
+ * Default life of the ticket we are getting. Since we are just checking
+ * to see if the user can get one, it doesn't need a long lifetime.
+ */
+#define KRB5_DEFAULT_LIFE 60 * 5 /* 5 minutes */
+
+
+/*
+** Check the username and password by attempting to get a TGT. Returns 1 on
+** success and 0 on failure. Errors are reported via com_err.
+*/
+static int
+krb5_check_password (char *principal_name, char *password)
+{
+ krb5_context kcontext;
+ krb5_creds creds;
+ krb5_principal user_principal;
+ krb5_data *user_realm;
+ krb5_principal service_principal;
+ krb5_timestamp now;
+ krb5_address **addrs = (krb5_address **) NULL; /* Use default */
+ long lifetime = KRB5_DEFAULT_LIFE;
+ int options = 0;
+
+ /* TGT service name for convenience */
+ krb5_data tgtname = { 0, KRB5_TGS_NAME_SIZE, KRB5_TGS_NAME };
+
+ krb5_preauthtype *preauth = NULL;
+
+ krb5_error_code code;
+
+ /* Our return code - 1 is success */
+ int result = 0;
+
+ /* Initialize our Kerberos state */
+ code = krb5_init_context (&kcontext);
+ if (code) {
+ com_err (message_program_name, code, "initializing krb5 context");
+ return 0;
+ }
+
+#ifdef HAVE_KRB5_INIT_ETS
+ /* Initialize krb5 error tables */
+ krb5_init_ets (kcontext);
+#endif
+
+ /* Get current time */
+ code = krb5_timeofday (kcontext, &now);
+ if (code) {
+ com_err (message_program_name, code, "getting time of day");
+ return 0;
+ }
+
+ /* Set up credentials to be filled in */
+ memset (&creds, 0, sizeof(creds));
+
+ /* From here on, goto cleanup to exit */
+
+ /* Parse the username into a krb5 principal */
+ if (!principal_name) {
+ com_err (message_program_name, 0, "passed NULL principal name");
+ goto cleanup;
+ }
+
+ code = krb5_parse_name (kcontext, principal_name, &user_principal);
+ if (code) {
+ com_err (message_program_name, code,
+ "parsing user principal name %.100s", principal_name);
+ goto cleanup;
+ }
+
+ creds.client = user_principal;
+
+ /* Get the user's realm for building service principal */
+ user_realm = krb5_princ_realm (kcontext, user_principal);
+
+ /*
+ * Build the service name into a principal. Right now this is
+ * a TGT for the user's realm.
+ */
+ code = krb5_build_principal_ext (kcontext,
+ &service_principal,
+ user_realm->length,
+ user_realm->data,
+ tgtname.length,
+ tgtname.data,
+ user_realm->length,
+ user_realm->data,
+ 0 /* terminator */);
+ if (code) {
+ com_err(message_program_name, code, "building service principal name");
+ goto cleanup;
+ }
+
+ creds.server = service_principal;
+
+ creds.times.starttime = 0; /* Now */
+ creds.times.endtime = now + lifetime;
+ creds.times.renew_till = 0; /* Unrenewable */
+
+ /* DO IT */
+ code = krb5_get_in_tkt_with_password (kcontext,
+ options,
+ addrs,
+ NULL,
+ preauth,
+ password,
+ 0,
+ &creds,
+ 0);
+
+ /* We are done with password at this point... */
+
+ if (code) {
+ /* FAILURE - Parse a few common errors here */
+ switch (code) {
+ case KRB5KRB_AP_ERR_BAD_INTEGRITY:
+ com_err (message_program_name, 0, "bad password for %.100s",
+ principal_name);
+ break;
+ case KRB5KDC_ERR_C_PRINCIPAL_UNKNOWN:
+ com_err (message_program_name, 0, "unknown user \"%.100s\"",
+ principal_name);
+ break;
+ default:
+ com_err (message_program_name, code,
+ "checking Kerberos password for %.100s", principal_name);
+ }
+ result = 0;
+ } else {
+ /* SUCCESS */
+ result = 1;
+ }
+
+ /* Cleanup */
+ cleanup:
+ krb5_free_cred_contents (kcontext, &creds);
+
+ return result;
+}
+
+int
+main (int argc, char *argv[])
+{
+ struct auth_info *authinfo;
+ char *new_user;
+
+ message_program_name = "auth_krb5";
+
+ /* Retrieve the username and passwd from nnrpd. */
+ authinfo = get_auth_info(stdin);
+
+ /* Must have a username/password, and no '@' in the address. @ checking
+ is there to prevent authentication against another Kerberos realm; there
+ should be a -r <realm> commandline option to make this check unnecessary
+ in the future. */
+ if (authinfo == NULL)
+ die("no authentication information from nnrpd");
+ if (authinfo->username[0] == '\0')
+ die("null username");
+ if (strchr(authinfo->username, '@') != NULL)
+ die("username contains @, not allowed");
+
+ /* May need to prepend instance name if -i option was given. */
+ if (argc > 1) {
+ if (argc == 3 && strcmp(argv[1], "-i") == 0) {
+ new_user = concat(authinfo->username, "/", argv[2], (char *) 0);
+ free(authinfo->username);
+ authinfo->username = new_user;
+ } else {
+ die("error parsing command-line options");
+ }
+ }
+
+ if (krb5_check_password(authinfo->username, authinfo->password)) {
+ printf("User:%s\r\n", authinfo->username);
+ exit(0);
+ } else {
+ die("failure validating password");
+ }
+}
--- /dev/null
+/*
+ * Samba authenticator.
+ * usage: auth_smb <server> [<backup_server>] <domain>
+ *
+ * Heavily based on:
+ * pam_smb -- David Airlie 1998-2000 v1.1.6 <airlied@samba.org>
+ * http://www.csn.ul.ie/~airlied
+ *
+ * Written 2000 October by Krischan Jodies <krischan@jodies.cx>
+ *
+ */
+
+#include "config.h"
+#include "clibrary.h"
+#include "inn/messages.h"
+
+#include "libauth.h"
+#include "smbval/valid.h"
+
+int
+main(int argc, char *argv[])
+{
+ struct auth_info *authinfo;
+ int result;
+ char *server, *backup, *domain;
+
+ message_program_name = "auth_smb";
+
+ if ((argc > 4) || (argc < 3))
+ die("wrong number of arguments"
+ " (auth_smb <server> [<backup-server>] <domain>");
+
+ authinfo = get_auth_info(stdin);
+ if (authinfo == NULL)
+ die("no user information provided by nnrpd");
+
+ /* Got a username and password. Now check to see if they're valid. */
+ server = argv[1];
+ backup = (argc > 3) ? argv[2] : argv[1];
+ domain = (argc > 3) ? argv[3] : argv[2];
+ result = Valid_User(authinfo->username, authinfo->password, server,
+ backup, domain);
+
+ /* Analyze the result. */
+ switch (result) {
+ case NTV_NO_ERROR:
+ printf("User:%s\n", authinfo->username);
+ exit(0);
+ break;
+ case NTV_SERVER_ERROR:
+ die("server error");
+ break;
+ case NTV_PROTOCOL_ERROR:
+ die("protocol error");
+ break;
+ case NTV_LOGON_ERROR:
+ die("logon error");
+ break;
+ default:
+ die("unknown error");
+ break;
+ }
+
+ /* Never reached. */
+ return 1;
+}
--- /dev/null
+/* $Id: ckpasswd.c 7565 2006-08-28 02:42:54Z eagle $
+**
+** The default username/password authenticator.
+**
+** This program is intended to be run by nnrpd and handle usernames and
+** passwords. It can authenticate against a regular flat file (the type
+** managed by htpasswd), a DBM file, the system password file or shadow file,
+** or PAM.
+*/
+
+#include "config.h"
+#include "clibrary.h"
+
+#include "inn/messages.h"
+#include "inn/qio.h"
+#include "inn/vector.h"
+#include "libinn.h"
+
+#include "libauth.h"
+
+#if HAVE_CRYPT_H
+# include <crypt.h>
+#endif
+#include <fcntl.h>
+#include <pwd.h>
+#include <grp.h>
+
+#if defined(HAVE_DBM) || defined(HAVE_BDB_DBM)
+# if HAVE_NDBM_H
+# include <ndbm.h>
+# elif HAVE_BDB_DBM
+# define DB_DBM_HSEARCH 1
+# include <db.h>
+# elif HAVE_GDBM_NDBM_H
+# include <gdbm-ndbm.h>
+# elif HAVE_DB1_NDBM_H
+# include <db1/ndbm.h>
+# endif
+# define OPT_DBM "d:"
+#else
+# define OPT_DBM ""
+#endif
+
+#if HAVE_GETSPNAM
+# include <shadow.h>
+# define OPT_SHADOW "s"
+#else
+# define OPT_SHADOW ""
+#endif
+
+#if HAVE_PAM
+# if HAVE_PAM_PAM_APPL_H
+# include <pam/pam_appl.h>
+# else
+# include <security/pam_appl.h>
+# endif
+#endif
+
+
+/*
+** The PAM conversation function.
+**
+** Since we already have all the information and can't ask the user
+** questions, we can't quite follow the real PAM protocol. Instead, we just
+** return the password in response to every question that PAM asks. There
+** appears to be no generic way to determine whether the message in question
+** is indeed asking for the password....
+**
+** This function allocates an array of struct pam_response to return to the
+** PAM libraries that's never freed. For this program, this isn't much of an
+** issue, since it will likely only be called once and then the program will
+** exit. This function uses malloc and strdup instead of xmalloc and xstrdup
+** intentionally so that the PAM conversation will be closed cleanly if we
+** run out of memory rather than simply terminated.
+**
+** appdata_ptr contains the password we were given.
+*/
+#if HAVE_PAM
+static int
+pass_conv(int num_msg, const struct pam_message **msgm UNUSED,
+ struct pam_response **response, void *appdata_ptr)
+{
+ int i;
+
+ *response = malloc(num_msg * sizeof(struct pam_response));
+ if (*response == NULL)
+ return PAM_CONV_ERR;
+ for (i = 0; i < num_msg; i++) {
+ (*response)[i].resp = strdup((char *)appdata_ptr);
+ (*response)[i].resp_retcode = 0;
+ }
+ return PAM_SUCCESS;
+}
+#endif /* HAVE_PAM */
+
+
+/*
+** Authenticate a user via PAM.
+**
+** Attempts to authenticate a user with PAM, returning true if the user
+** successfully authenticates and false otherwise. Note that this function
+** doesn't attempt to handle any remapping of the authenticated user by the
+** PAM stack, but just assumes that the authenticated user was the same as
+** the username given.
+**
+** Right now, all failures are handled via die. This may be worth revisiting
+** in case we want to try other authentication methods if this fails for a
+** reason other than the system not having PAM support.
+*/
+#if !HAVE_PAM
+static bool
+auth_pam(char *username UNUSED, char *password UNUSED)
+{
+ return false;
+}
+#else
+static bool
+auth_pam(const char *username, char *password)
+{
+ pam_handle_t *pamh;
+ struct pam_conv conv;
+ int status;
+
+ conv.conv = pass_conv;
+ conv.appdata_ptr = password;
+ status = pam_start("nnrpd", username, &conv, &pamh);
+ if (status != PAM_SUCCESS)
+ die("pam_start failed: %s", pam_strerror(pamh, status));
+ status = pam_authenticate(pamh, PAM_SILENT);
+ if (status != PAM_SUCCESS)
+ die("pam_authenticate failed: %s", pam_strerror(pamh, status));
+ status = pam_acct_mgmt(pamh, PAM_SILENT);
+ if (status != PAM_SUCCESS)
+ die("pam_acct_mgmt failed: %s", pam_strerror(pamh, status));
+ status = pam_end(pamh, status);
+ if (status != PAM_SUCCESS)
+ die("pam_end failed: %s", pam_strerror(pamh, status));
+
+ /* If we get to here, the user successfully authenticated. */
+ return true;
+}
+#endif /* HAVE_PAM */
+
+
+/*
+** Try to get a password out of a dbm file. The dbm file should have the
+** username for the key and the crypted password as the value. The crypted
+** password, if found, is returned as a newly allocated string; otherwise,
+** NULL is returned.
+*/
+#if !(defined(HAVE_DBM) || defined(HAVE_BDB_DBM))
+static char *
+password_dbm(char *user UNUSED, const char *file UNUSED)
+{
+ return NULL;
+}
+#else
+static char *
+password_dbm(char *name, const char *file)
+{
+ datum key, value;
+ DBM *database;
+ char *password;
+
+ database = dbm_open(file, O_RDONLY, 0600);
+ if (database == NULL)
+ return NULL;
+ key.dptr = name;
+ key.dsize = strlen(name);
+ value = dbm_fetch(database, key);
+ if (value.dptr == NULL) {
+ dbm_close(database);
+ return NULL;
+ }
+ password = xmalloc(value.dsize + 1);
+ strlcpy(password, value.dptr, value.dsize + 1);
+ dbm_close(database);
+ return password;
+}
+#endif /* HAVE_DBM || HAVE_BDB_DBM */
+
+
+/*
+** Try to get a password out of the system /etc/shadow file. The crypted
+** password, if found, is returned as a newly allocated string; otherwise,
+** NULL is returned.
+*/
+#if !HAVE_GETSPNAM
+static char *
+password_shadow(const char *user UNUSED)
+{
+ return NULL;
+}
+#else
+static char *
+password_shadow(const char *user)
+{
+ struct spwd *spwd;
+
+ spwd = getspnam(user);
+ if (spwd != NULL)
+ return xstrdup(spwd->sp_pwdp);
+ return NULL;
+}
+#endif /* HAVE_GETSPNAM */
+
+
+/*
+** Try to get a password out of a file. The crypted password, if found, is
+** returned as a newly allocated string; otherwise, NULL is returned.
+*/
+static char *
+password_file(const char *username, const char *file)
+{
+ QIOSTATE *qp;
+ char *line, *password;
+ struct cvector *info = NULL;
+
+ qp = QIOopen(file);
+ if (qp == NULL)
+ return NULL;
+ for (line = QIOread(qp); line != NULL; line = QIOread(qp)) {
+ if (*line == '#' || *line == '\n')
+ continue;
+ info = cvector_split(line, ':', info);
+ if (info->count < 2 || strcmp(info->strings[0], username) != 0)
+ continue;
+ password = xstrdup(info->strings[1]);
+ QIOclose(qp);
+ cvector_free(info);
+ return password;
+ }
+ if (QIOtoolong(qp))
+ die("line too long in %s", file);
+ if (QIOerror(qp))
+ sysdie("error reading %s", file);
+ QIOclose(qp);
+ cvector_free(info);
+ return NULL;
+}
+
+
+/*
+** Try to get a password out of the system password file. The crypted
+** password, if found, is returned as a newly allocated string; otherwise,
+** NULL is returned.
+*/
+static char *
+password_system(const char *username)
+{
+ struct passwd *pwd;
+
+ pwd = getpwnam(username);
+ if (pwd != NULL)
+ return xstrdup(pwd->pw_passwd);
+ return NULL;
+}
+
+
+/*
+** Try to get the name of a user's primary group out of the system group
+** file. The group, if found, is returned as a newly allocated string;
+** otherwise, NULL is returned. If the username is not found, NULL is
+** returned.
+*/
+static char *
+group_system(const char *username)
+{
+ struct passwd *pwd;
+ struct group *gr;
+
+ pwd = getpwnam(username);
+ if (pwd == NULL)
+ return NULL;
+ gr = getgrgid(pwd->pw_gid);
+ if (gr == NULL)
+ return NULL;
+ return xstrdup(gr->gr_name);
+}
+
+
+/*
+** Output username (and group, if desired) in correct return format.
+*/
+static void
+output_user(const char *username, bool wantgroup)
+{
+ if (wantgroup) {
+ char *group = group_system(username);
+ if (group == NULL)
+ die("group info for user %s not available", username);
+ printf("User:%s@%s\n", username, group);
+ }
+ else
+ printf("User:%s\n", username);
+}
+
+
+/*
+** Main routine.
+**
+** We handle the variences between systems with #if blocks above, so that
+** this code can look fairly clean.
+*/
+int
+main(int argc, char *argv[])
+{
+ enum authtype { AUTH_NONE, AUTH_SHADOW, AUTH_FILE, AUTH_DBM };
+
+ int opt;
+ enum authtype type = AUTH_NONE;
+ bool wantgroup = false;
+ const char *filename = NULL;
+ struct auth_info *authinfo = NULL;
+ char *password = NULL;
+
+ message_program_name = "ckpasswd";
+
+ while ((opt = getopt(argc, argv, "gf:u:p:" OPT_DBM OPT_SHADOW)) != -1) {
+ switch (opt) {
+ case 'g':
+ if (type == AUTH_DBM || type == AUTH_FILE)
+ die("-g option is incompatible with -d or -f");
+ wantgroup = true;
+ break;
+ case 'd':
+ if (type != AUTH_NONE)
+ die("only one of -s, -f, or -d allowed");
+ if (wantgroup)
+ die("-g option is incompatible with -d or -f");
+ type = AUTH_DBM;
+ filename = optarg;
+ break;
+ case 'f':
+ if (type != AUTH_NONE)
+ die("only one of -s, -f, or -d allowed");
+ if (wantgroup)
+ die("-g option is incompatible with -d or -f");
+ type = AUTH_FILE;
+ filename = optarg;
+ break;
+ case 's':
+ if (type != AUTH_NONE)
+ die("only one of -s, -f, or -d allowed");
+ type = AUTH_SHADOW;
+ break;
+ case 'u':
+ if (authinfo == NULL) {
+ authinfo = xmalloc(sizeof(struct auth_info));
+ authinfo->password = NULL;
+ }
+ authinfo->username = optarg;
+ break;
+ case 'p':
+ if (authinfo == NULL) {
+ authinfo = xmalloc(sizeof(struct auth_info));
+ authinfo->username = NULL;
+ }
+ authinfo->password = optarg;
+ break;
+ default:
+ exit(1);
+ }
+ }
+ if (argc != optind)
+ die("extra arguments given");
+ if (authinfo != NULL && authinfo->username == NULL)
+ die("-u option is required if -p option is given");
+ if (authinfo != NULL && authinfo->password == NULL)
+ die("-p option is required if -u option is given");
+
+ /* Unless a username or password was given on the command line, assume
+ we're being run by nnrpd. */
+ if (authinfo == NULL)
+ authinfo = get_auth_info(stdin);
+ if (authinfo == NULL)
+ die("no authentication information from nnrpd");
+ if (authinfo->username[0] == '\0')
+ die("null username");
+
+ /* Run the appropriate authentication routines. */
+ switch (type) {
+ case AUTH_SHADOW:
+ password = password_shadow(authinfo->username);
+ if (password == NULL)
+ password = password_system(authinfo->username);
+ break;
+ case AUTH_FILE:
+ password = password_file(authinfo->username, filename);
+ break;
+ case AUTH_DBM:
+ password = password_dbm(authinfo->username, filename);
+ break;
+ case AUTH_NONE:
+ if (auth_pam(authinfo->username, authinfo->password)) {
+ output_user(authinfo->username, wantgroup);
+ exit(0);
+ }
+ password = password_system(authinfo->username);
+ break;
+ }
+
+ if (password == NULL)
+ die("user %s unknown", authinfo->username);
+ if (strcmp(password, crypt(authinfo->password, password)) != 0)
+ die("invalid password for user %s", authinfo->username);
+
+ /* The password matched. */
+ output_user(authinfo->username, wantgroup);
+ exit(0);
+}
--- /dev/null
+/* $Id: domain.c 7141 2005-03-17 11:42:46Z vinocur $
+**
+** Domain authenticator.
+**
+** Compares the domain of the client connection to the first argument given
+** on the command line, and returns the host portion of the connecting host
+** as the user if it matches.
+*/
+
+#include "config.h"
+#include "clibrary.h"
+
+#include "inn/messages.h"
+#include "libinn.h"
+#include "libauth.h"
+
+int
+main(int argc, char *argv[])
+{
+ char *p, *host;
+ struct res_info *res;
+
+ if (argc != 2)
+ die("Usage: domain <domain>");
+ message_program_name = "domain";
+
+ /* Read the connection information from stdin. */
+ res = get_res_info(stdin);
+ if (res == NULL)
+ die("did not get ClientHost data from nnrpd");
+ host = res->clienthostname;
+
+ /* Check the host against the provided domain. Allow the domain to be
+ specified both with and without a leading period; if without, make sure
+ that there is a period right before where it matches in the host. */
+ p = strstr(host, argv[1]);
+ if (p == host)
+ die("host %s matches the domain exactly", host);
+ if (p == NULL || (argv[1][0] != '.' && p != host && *(p - 1) != '.'))
+ die("host %s didn't match domain %s", host, argv[1]);
+
+ /* Peel off the portion of the host before where the provided domain
+ matches and return it as the user. */
+ if (argv[1][0] != '.')
+ p--;
+ *p = '\0';
+ printf("User:%s\n", host);
+ return 0;
+}
--- /dev/null
+/* $Id: ident.c 6135 2003-01-19 01:15:40Z rra $
+**
+** Ident authenticator.
+*/
+
+#include "config.h"
+#include "clibrary.h"
+#include <errno.h>
+#include <netdb.h>
+#include <signal.h>
+#include <syslog.h>
+
+#include "inn/messages.h"
+#include "libinn.h"
+
+#include "libauth.h"
+
+#define IDENT_PORT 113
+
+static void out(int sig UNUSED) {
+ exit(1);
+}
+
+int main(int argc, char *argv[])
+{
+ struct servent *s;
+ char buf[2048];
+ struct res_info *res;
+ struct sockaddr_in *lsin, *csin;
+#ifdef HAVE_INET6
+ struct sockaddr_storage *lss;
+ struct sockaddr_in6 *lsin6, *csin6;
+#endif
+ int sock;
+ int opt;
+ int truncate_domain = 0;
+ char *iter;
+ char *p;
+ unsigned int got;
+ int lport, cport, identport;
+ char *endstr;
+
+ message_program_name = "ident";
+
+ xsignal_norestart(SIGALRM,out);
+ alarm(15);
+
+ s = getservbyname("ident", "tcp");
+ if (!s)
+ identport = IDENT_PORT;
+ else
+ identport = ntohs(s->s_port);
+
+ while ((opt = getopt(argc, argv, "p:t")) != -1) {
+ switch (opt) {
+ case 'p':
+ for (iter = optarg; *iter; iter++)
+ if (*iter < '0' || *iter > '9')
+ break;
+ if (*iter) {
+ /* not entirely numeric */
+ s = getservbyname(optarg, "tcp");
+ if (s == NULL)
+ die("cannot getsrvbyname(%s, tcp)", optarg);
+ identport = s->s_port;
+ } else
+ identport = atoi(optarg);
+ break;
+ case 't':
+ truncate_domain = 1;
+ break;
+ }
+ }
+
+ /* read the connection info from stdin */
+ res = get_res_info(stdin);
+ if (res == NULL)
+ die("did not get client information from nnrpd");
+
+#ifdef HAVE_INET6
+ lss = (struct sockaddr_storage *)(res->local);
+ lsin6 = (struct sockaddr_in6 *)(res->local);
+ csin6 = (struct sockaddr_in6 *)(res->client);
+ if( lss->ss_family == AF_INET6 )
+ {
+ lport = ntohs( lsin6->sin6_port );
+ lsin6->sin6_port = 0;
+ cport = ntohs( csin6->sin6_port );
+ csin6->sin6_port = htons( identport );
+ sock = socket(PF_INET6, SOCK_STREAM, 0);
+ } else
+#endif
+ {
+ lsin = (struct sockaddr_in *)(res->local);
+ lport = htons( lsin->sin_port );
+ lsin->sin_port = 0;
+ csin = (struct sockaddr_in *)(res->client);
+ cport = htons( csin->sin_port );
+ csin->sin_port = htons( identport );
+ sock = socket(PF_INET, SOCK_STREAM, 0);
+ }
+ if (sock < 0)
+ sysdie("cannot create socket");
+ if (bind(sock, res->local, SA_LEN(res->local)) < 0)
+ sysdie("cannot bind socket");
+ if (connect(sock, res->client, SA_LEN(res->local)) < 0) {
+ if (errno != ECONNREFUSED)
+ sysdie("cannot connect to ident server");
+ else
+ sysdie("client host does not accept ident connections");
+ }
+ free_res_info(res);
+
+ /* send the request out */
+ snprintf(buf, sizeof(buf), "%d , %d\r\n", cport, lport);
+ opt = xwrite(sock, buf, strlen(buf));
+ if (opt < 0)
+ sysdie("cannot write to ident server");
+
+ /* get the answer back */
+ got = 0;
+ do {
+ opt = read(sock, buf+got, sizeof(buf)-got);
+ if (opt < 0)
+ sysdie("cannot read from ident server");
+ else if (!opt)
+ die("end of file from ident server before response");
+ while (opt--)
+ if (buf[got] != '\n')
+ got++;
+ } while (buf[got] != '\n');
+ buf[got] = '\0';
+ if (buf[got-1] == '\r')
+ buf[got-1] = '\0';
+
+ /* buf now contains the entire ident response. */
+ if (!(iter = strchr(buf, ':')))
+ /* malformed response */
+ die("malformed response \"%s\" from ident server", buf);
+ iter++;
+
+ while (*iter && ISWHITE(*iter))
+ iter++;
+ endstr = iter;
+ while (*endstr && *endstr != ':' && !ISWHITE(*endstr))
+ endstr++;
+ if (!*endstr)
+ /* malformed response */
+ die("malformed response \"%s\" from ident server", buf);
+ if (*endstr != ':') {
+ *endstr++ = '\0';
+ while (*endstr != ':')
+ endstr++;
+ }
+
+ *endstr = '\0';
+
+ if (strcmp(iter, "ERROR") == 0)
+ die("ident server reported an error");
+ else if (strcmp(iter, "USERID") != 0)
+ die("ident server returned \"%s\", not USERID", iter);
+
+ /* skip the operating system */
+ if (!(iter = strchr(endstr+1, ':')))
+ exit(1);
+
+ /* everything else is username */
+ iter++;
+ while (*iter && ISWHITE(*iter))
+ iter++;
+ if (!*iter || *iter == '[')
+ /* null, or encrypted response */
+ die("ident response is null or encrypted");
+ if ((truncate_domain == 1) && ((p = strchr(iter, '@')) != NULL))
+ *p = '\0';
+ printf("User:%s\n", iter);
+
+ exit(0);
+}
--- /dev/null
+/* $Id: libauth.c 7500 2006-03-20 01:52:44Z eagle $
+**
+** Common code for authenticators and resolvers.
+**
+** Collects common code to read information from nnrpd that should be done
+** the same for all authenticators, and common code to get information about
+** the incoming connection.
+*/
+
+#include "config.h"
+#include "clibrary.h"
+#include "libinn.h"
+
+#include "libauth.h"
+#include "inn/messages.h"
+
+#define NAMESTR "ClientAuthname: "
+#define PASSSTR "ClientPassword: "
+
+#define CLIHOST "ClientHost: "
+#define CLIIP "ClientIP: "
+#define CLIPORT "ClientPort: "
+#define LOCIP "LocalIP: "
+#define LOCPORT "LocalPort: "
+
+#ifdef HAVE_INET6
+# include <netdb.h>
+#endif
+
+/* Main loop. If res != NULL, expects to get resolver info from nnrpd, and
+ writes it into the struct. If auth != NULL, expects to get authentication
+ info from nnrpd, and writes it into the struct. */
+
+static bool
+get_connection_info(FILE *stream, struct res_info *res, struct auth_info *auth)
+{
+ char buff[SMBUF];
+ size_t length;
+ char *cip = NULL, *sip = NULL, *cport = NULL, *sport = NULL;
+#ifdef HAVE_INET6
+ struct addrinfo *r, hints;
+#else
+ struct sockaddr_in *loc_sin, *cli_sin;
+#endif
+
+ /* Zero fields first (anything remaining NULL after is missing data) */
+ if (res != NULL) {
+ res->clienthostname = NULL;
+ res->client = NULL;
+ res->local = NULL;
+ }
+ if (auth != NULL) {
+ auth->username = NULL;
+ auth->password = NULL;
+ }
+
+ /* Read input from nnrpd a line at a time, stripping \r\n. */
+ while (fgets(buff, sizeof(buff), stream) != NULL) {
+ length = strlen(buff);
+ if (length == 0 || buff[length - 1] != '\n')
+ goto error;
+ buff[length - 1] = '\0';
+ if (length > 1 && buff[length - 2] == '\r')
+ buff[length - 2] = '\0';
+
+ /* Parse */
+ if (strncmp(buff, ".", 2) == 0)
+ break;
+ else if (auth != NULL && strncmp(buff, NAMESTR, strlen(NAMESTR)) == 0)
+ auth->username = xstrdup(buff + strlen(NAMESTR));
+ else if (auth != NULL && strncmp(buff, PASSSTR, strlen(PASSSTR)) == 0)
+ auth->password = xstrdup(buff + strlen(PASSSTR));
+ else if (res != NULL && strncmp(buff, CLIHOST, strlen(CLIHOST)) == 0)
+ res->clienthostname = xstrdup(buff + strlen(CLIHOST));
+ else if (res != NULL && strncmp(buff, CLIIP, strlen(CLIIP)) == 0)
+ cip = xstrdup(buff + strlen(CLIIP));
+ else if (res != NULL && strncmp(buff, CLIPORT, strlen(CLIPORT)) == 0)
+ cport = xstrdup(buff + strlen(CLIPORT));
+ else if (res != NULL && strncmp(buff, LOCIP, strlen(LOCIP)) == 0)
+ sip = xstrdup(buff + strlen(LOCIP));
+ else if (res != NULL && strncmp(buff, LOCPORT, strlen(LOCPORT)) == 0)
+ sport = xstrdup(buff + strlen(LOCPORT));
+ else {
+ /**** We just ignore excess fields for now ****/
+
+ /* warn("libauth: unexpected data from nnrpd: \"%s\"", buff); */
+ /* goto error; */
+ }
+ }
+
+ /* If some field is missing, free the rest and error out. */
+ if (auth != NULL && (auth->username == NULL || auth->password == NULL)) {
+ warn("libauth: requested authenticator data not sent by nnrpd");
+ goto error;
+ }
+ if (res != NULL && (res->clienthostname == NULL || cip == NULL ||
+ cport == NULL || sip == NULL || sport == NULL)) {
+ warn("libauth: requested resolver data not sent by nnrpd");
+ goto error;
+ }
+
+ /* Generate sockaddrs from IP and port strings */
+ if (res != NULL) {
+#ifdef HAVE_INET6
+ /* sockaddr_in6 may be overkill for PF_INET case, but oh well */
+ res->client = xcalloc(1, sizeof(struct sockaddr_in6));
+ res->local = xcalloc(1, sizeof(struct sockaddr_in6));
+
+ memset( &hints, 0, sizeof( hints ) );
+ hints.ai_flags = AI_NUMERICHOST;
+ hints.ai_socktype = SOCK_STREAM;
+
+ hints.ai_family = strchr( cip, ':' ) != NULL ? PF_INET6 : PF_INET;
+ if( getaddrinfo( cip, cport, &hints, &r ) != 0)
+ goto error;
+ if( r->ai_addrlen > sizeof(struct sockaddr_in6) )
+ goto error;
+ memcpy( res->client, r->ai_addr, r->ai_addrlen );
+ freeaddrinfo( r );
+
+ hints.ai_family = strchr( sip, ':' ) != NULL ? PF_INET6 : PF_INET;
+ if( getaddrinfo( sip, sport, &hints, &r ) != 0)
+ goto error;
+ if( r->ai_addrlen > sizeof(struct sockaddr_in6) )
+ goto error;
+ memcpy( res->local, r->ai_addr, r->ai_addrlen );
+ freeaddrinfo( r );
+#else
+ res->client = xcalloc(1, sizeof(struct sockaddr_in));
+ res->local = xcalloc(1, sizeof(struct sockaddr_in));
+
+ cli_sin = (struct sockaddr_in *)(res->client);
+ loc_sin = (struct sockaddr_in *)(res->local);
+ cli_sin->sin_family = AF_INET;
+ if (!inet_aton(cip, &cli_sin->sin_addr))
+ goto error;
+ cli_sin->sin_port = htons( atoi(cport) );
+
+ loc_sin->sin_family = AF_INET;
+ if (!inet_aton(sip, &loc_sin->sin_addr))
+ goto error;
+ loc_sin->sin_port = htons( atoi(sport) );
+
+# ifdef HAVE_SOCKADDR_LEN
+ cli_sin->sin_len = sizeof(struct sockaddr_in);
+ loc_sin->sin_len = sizeof(struct sockaddr_in);
+# endif
+#endif
+
+ free(sip);
+ free(sport);
+ free(cip);
+ free(cport);
+ }
+
+ return true;
+
+error:
+ if (auth != NULL && auth->username != NULL) free(auth->username);
+ if (auth != NULL && auth->password != NULL) free(auth->password);
+ if (res != NULL && res->clienthostname != NULL) free(res->clienthostname);
+ if (res != NULL && res->client != NULL) free(res->client);
+ if (res != NULL && res->local != NULL) free(res->local);
+ if (sip != NULL) free(sip);
+ if (sport != NULL) free(sport);
+ if (cip != NULL) free(cip);
+ if (cport != NULL) free(cport);
+ return false;
+}
+
+
+/* Wrappers to read information from nnrpd, returning an allocated struct on
+ success. */
+
+struct res_info *
+get_res_info(FILE *stream) {
+ struct res_info *res = xmalloc(sizeof(struct res_info));
+
+ if(get_connection_info(stream, res, NULL))
+ return res;
+
+ free(res);
+ return NULL;
+}
+
+
+struct auth_info *
+get_auth_info(FILE *stream) {
+ struct auth_info *auth = xmalloc(sizeof(struct auth_info));
+
+ if(get_connection_info(stream, NULL, auth))
+ return auth;
+
+ free(auth);
+ return NULL;
+}
+
+void
+free_res_info(struct res_info *res) {
+ if(res == NULL)
+ return;
+ if(res->client != NULL) free(res->client);
+ if(res->local != NULL) free(res->local);
+ if(res->clienthostname != NULL) free(res->clienthostname);
+ free(res);
+}
+
+void
+free_auth_info(struct auth_info *auth) {
+ if(auth == NULL)
+ return;
+ if(auth->username != NULL) free(auth->username);
+ if(auth->password != NULL) free(auth->password);
+ free(auth);
+}
+
--- /dev/null
+/*
+**
+** Common headers for authenticators and resolvers.
+**
+*/
+
+#include "config.h"
+#include "portable/socket.h"
+
+/* Holds the resolver information from nnrpd. */
+struct res_info {
+ struct sockaddr *client;
+ struct sockaddr *local;
+ char *clienthostname;
+};
+
+/* Holds the authentication information from nnrpd. */
+struct auth_info {
+ char *username;
+ char *password;
+};
+
+/*
+ * Reads connection information from a file descriptor (normally stdin, when
+ * talking to nnrpd) and returns a new res_info or auth_info struct, or
+ * returns NULL on failure. Note that the fields will never be NULL; if the
+ * corresponding information is missing, it is an error (which will be
+ * logged and NULL will be returned). The client is responsible for freeing
+ * the struct and its fields; this can be done by calling the appropriate
+ * destruction function below.
+ */
+
+extern struct auth_info *get_auth_info(FILE *);
+extern struct res_info *get_res_info (FILE *);
+
+extern void free_auth_info(struct auth_info*);
+extern void free_res_info (struct res_info*);
+
+
--- /dev/null
+/* $Id: radius.c 7745 2008-04-06 10:18:54Z iulius $
+**
+** Authenticate a user against a remote radius server.
+*/
+
+#include "config.h"
+#include "clibrary.h"
+#include "portable/time.h"
+#include <ctype.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <netdb.h>
+#include <signal.h>
+
+/* Needed on AIX 4.1 to get fd_set and friends. */
+#if HAVE_SYS_SELECT_H
+# include <sys/select.h>
+#endif
+
+#include "inn/innconf.h"
+#include "inn/md5.h"
+#include "inn/messages.h"
+#include "libinn.h"
+#include "nntp.h"
+#include "paths.h"
+#include "conffile.h"
+
+#include "libauth.h"
+
+#define RADIUS_LOCAL_PORT NNTP_PORT
+
+#define AUTH_VECTOR_LEN 16
+
+typedef struct _auth_req {
+ unsigned char code;
+ unsigned char id;
+ unsigned short length;
+ unsigned char vector[AUTH_VECTOR_LEN];
+ unsigned char data[NNTP_STRLEN*2];
+ int datalen;
+} auth_req;
+
+typedef struct _rad_config_t {
+ char *secret; /* pseudo encryption thingy secret that radius uses */
+
+ char *radhost; /* parameters for talking to the remote radius sever */
+ int radport;
+ char *lochost;
+ int locport;
+
+ char *prefix, *suffix; /* futz with the username, if necessary */
+ int ignore_source;
+
+ struct _rad_config_t *next; /* point to any additional servers */
+} rad_config_t;
+
+typedef struct _sending_t {
+ auth_req req;
+ int reqlen;
+ struct sockaddr_in sinr;
+ struct _sending_t *next;
+} sending_t;
+
+#define RADlbrace 1
+#define RADrbrace 2
+#define RADserver 10
+#define RADhost 11
+#define RADsecret 12
+#define RADport 13
+#define RADlochost 14
+#define RADlocport 15
+#define RADprefix 16
+#define RADsuffix 17
+#define RADsource 18
+
+static CONFTOKEN radtoks[] = {
+ { RADlbrace, "{" },
+ { RADrbrace, "}" },
+ { RADserver, "server" },
+ { RADhost, "radhost:" },
+ { RADsecret, "secret:" },
+ { RADport, "radport:" },
+ { RADlochost, "lochost:" },
+ { RADlocport, "locport:" },
+ { RADprefix, "prefix:" },
+ { RADsuffix, "suffix:" },
+ { RADsource, "ignore-source:" },
+ { 0, 0 }
+};
+
+static rad_config_t *get_radconf(void)
+{
+ rad_config_t *new;
+
+ new = xcalloc(1, sizeof(rad_config_t));
+ new->next = NULL;
+
+ return new;
+}
+
+static int read_config(char *authfile, rad_config_t *radconf)
+{
+ int inbrace;
+ rad_config_t *radconfig=NULL;
+ CONFFILE *file;
+ CONFTOKEN *token;
+ char *server;
+ int type;
+ char *iter;
+
+ if ((file = CONFfopen(authfile)) == NULL)
+ sysdie("cannot open config file %s", authfile);
+
+ inbrace = 0;
+ while ((token = CONFgettoken(radtoks, file)) != NULL) {
+ if (!inbrace) {
+ if (token->type != RADserver)
+ die("expected server keyword on line %d", file->lineno);
+ if ((token = CONFgettoken(0, file)) == NULL)
+ die("expected server name on line %d", file->lineno);
+ server = xstrdup(token->name);
+ if ((token = CONFgettoken(radtoks, file)) == NULL
+ || token->type != RADlbrace)
+ die("expected { on line %d", file->lineno);
+ inbrace = 1;
+
+ if (radconfig == NULL)
+ radconfig = radconf;
+ else {
+ radconfig->next = get_radconf();
+ radconfig = radconfig->next;
+ }
+ }
+ else {
+ type = token->type;
+ if (type == RADrbrace)
+ inbrace = 0;
+ else {
+ if ((token = CONFgettoken(0, file)) == NULL)
+ die("keyword with no value on line %d", file->lineno);
+ iter = token->name;
+
+ /* what are we setting? */
+ switch(type) {
+ case RADsecret:
+ if (radconfig->secret) continue;
+ radconfig->secret = xstrdup(iter);
+ break;
+ case RADhost:
+ if (radconfig->radhost) continue;
+ radconfig->radhost = xstrdup(iter);
+ break;
+ case RADport:
+ if (radconfig->radport) continue;
+ radconfig->radport = atoi(iter);
+ break;
+ case RADlochost:
+ if (radconfig->lochost) continue;
+ radconfig->lochost = xstrdup(iter);
+ break;
+ case RADlocport:
+ if (radconfig->locport) continue;
+ radconfig->locport = atoi(iter);
+ break;
+ case RADprefix:
+ if (radconfig->prefix) continue;
+ radconfig->prefix = xstrdup(iter);
+ break;
+ case RADsuffix:
+ if (radconfig->suffix) continue;
+ radconfig->suffix = xstrdup(iter);
+ break;
+ case RADsource:
+ if (!strcasecmp(iter, "true"))
+ radconfig->ignore_source = 1;
+ else if (!strcasecmp(iter, "false"))
+ radconfig->ignore_source = 0;
+ else
+ die("expected true or false after ignore-source on line %d",
+ file->lineno);
+ break;
+ default:
+ die("unknown keyword on line %d", file->lineno);
+ }
+ }
+ }
+ }
+
+ CONFfclose(file);
+
+ if (!radconf->radhost)
+ die("no radius host specified");
+ else if (!radconf->secret)
+ die("no shared secret with radius host specified");
+
+ return(0);
+}
+
+#define PW_AUTH_UDP_PORT 1645
+
+#define PW_AUTHENTICATION_REQUEST 1
+#define PW_AUTHENTICATION_ACK 2
+#define PW_AUTHENTICATION_REJECT 3
+
+#define PW_USER_NAME 1
+#define PW_PASSWORD 2
+
+#define PW_SERVICE_TYPE 6
+#define PW_SERVICE_AUTH_ONLY 8
+
+#define RAD_NAS_IP_ADDRESS 4 /* IP address */
+#define RAD_NAS_PORT 5 /* Integer */
+
+static void req_copyto (auth_req to, sending_t *from)
+{
+ to = from->req;
+}
+
+static void req_copyfrom (sending_t *to, auth_req from)
+{
+ to->req = from;
+}
+
+static int rad_auth(rad_config_t *radconfig, char *uname, char *pass)
+{
+ auth_req req;
+ int i, j, jlen, passstart;
+ unsigned char secbuf[128];
+ char hostname[SMBUF];
+ unsigned char digest[MD5_DIGESTSIZE];
+ struct timeval seed;
+ struct sockaddr_in sinl;
+ int sock;
+ struct hostent *hent;
+ int passlen;
+ time_t now, end;
+ struct timeval tmout;
+ int got;
+ fd_set rdfds;
+ uint32_t nvalue;
+ socklen_t slen;
+ int authtries= 3; /* number of times to try reaching the radius server */
+ rad_config_t *config;
+ sending_t *reqtop, *sreq, *new;
+ int done;
+
+ /* set up the linked list */
+ config = radconfig;
+
+ if (config == NULL) {
+ warn("no configuration file");
+ return(-2);
+ } else {
+ /* setting sreq to NULL guarantees reqtop will be properly set later */
+ sreq = NULL;
+ reqtop = NULL;
+ }
+
+ while (config != NULL){
+ new = xmalloc(sizeof(sending_t));
+ new->next = NULL;
+
+ if (sreq == NULL){
+ reqtop = new;
+ sreq = new;
+ } else {
+ sreq->next = new;
+ sreq = sreq->next;
+ }
+ req_copyto(req, sreq);
+
+ /* first, build the sockaddrs */
+ memset(&sinl, '\0', sizeof(sinl));
+ memset(&sreq->sinr, '\0', sizeof(sreq->sinr));
+ sinl.sin_family = AF_INET;
+ sreq->sinr.sin_family = AF_INET;
+ if (config->lochost == NULL) {
+ if (gethostname(hostname, sizeof(hostname)) != 0) {
+ syswarn("cannot get local hostname");
+ return(-2);
+ }
+ config->lochost = xstrdup(hostname);
+ }
+ if (config->lochost) {
+ if (inet_aton(config->lochost, &sinl.sin_addr) != 1) {
+ if ((hent = gethostbyname(config->lochost)) == NULL) {
+ warn("cannot gethostbyname lochost %s", config->lochost);
+ return(-2);
+ }
+ memcpy(&sinl.sin_addr.s_addr, hent->h_addr,
+ sizeof(struct in_addr));
+ }
+ }
+ if (inet_aton(config->radhost, &sreq->sinr.sin_addr) != 1) {
+ if ((hent = gethostbyname(config->radhost)) == NULL) {
+ warn("cannot gethostbyname radhost %s", config->radhost);
+ return(-2);
+ }
+ memcpy(&sreq->sinr.sin_addr.s_addr, hent->h_addr_list[0],
+ sizeof(struct in_addr));
+ }
+
+ if (config->radport)
+ sreq->sinr.sin_port = htons(config->radport);
+ else
+ sreq->sinr.sin_port = htons(PW_AUTH_UDP_PORT);
+
+ /* seed the random number generator for the auth vector */
+ gettimeofday(&seed, 0);
+ srandom((unsigned) seed.tv_sec+seed.tv_usec);
+ /* build the visible part of the auth vector randomly */
+ for (i = 0; i < AUTH_VECTOR_LEN; i++)
+ req.vector[i] = random() % 256;
+ strlcpy((char *) secbuf, config->secret, sizeof(secbuf));
+ memcpy(secbuf+strlen(config->secret), req.vector, AUTH_VECTOR_LEN);
+ md5_hash(secbuf, strlen(config->secret)+AUTH_VECTOR_LEN, digest);
+ /* fill in the auth_req data */
+ req.code = PW_AUTHENTICATION_REQUEST;
+ req.id = 0;
+
+ /* bracket the username in the configured prefix/suffix */
+ req.data[0] = PW_USER_NAME;
+ req.data[1] = 2;
+ req.data[2] = '\0';
+ if (config->prefix) {
+ req.data[1] += strlen(config->prefix);
+ strlcat((char *) &req.data[2], config->prefix, sizeof(req.data) - 2);
+ }
+ req.data[1] += strlen(uname);
+ strlcat((char *)&req.data[2], uname, sizeof(req.data) - 2);
+ if (!strchr(uname, '@') && config->suffix) {
+ req.data[1] += strlen(config->suffix);
+ strlcat((char *)&req.data[2], config->suffix, sizeof(req.data) - 2);
+ }
+ req.datalen = req.data[1];
+
+ /* set the password */
+ passstart = req.datalen;
+ req.data[req.datalen] = PW_PASSWORD;
+ /* Null pad the password */
+ passlen = (strlen(pass) + 15) / 16;
+ passlen *= 16;
+ req.data[req.datalen+1] = passlen+2;
+ strlcpy((char *)&req.data[req.datalen+2], pass,
+ sizeof(req.data) - req.datalen - 2);
+ passlen -= strlen(pass);
+ while (passlen--)
+ req.data[req.datalen+passlen+2+strlen(pass)] = '\0';
+ req.datalen += req.data[req.datalen+1];
+
+ /* Add NAS_PORT and NAS_IP_ADDRESS into request */
+ if ((nvalue = config->locport) == 0)
+ nvalue = RADIUS_LOCAL_PORT;
+ req.data[req.datalen++] = RAD_NAS_PORT;
+ req.data[req.datalen++] = sizeof(nvalue) + 2;
+ nvalue = htonl(nvalue);
+ memcpy(req.data + req.datalen, &nvalue, sizeof(nvalue));
+ req.datalen += sizeof(nvalue);
+ req.data[req.datalen++] = RAD_NAS_IP_ADDRESS;
+ req.data[req.datalen++] = sizeof(struct in_addr) + 2;
+ memcpy(req.data + req.datalen, &sinl.sin_addr.s_addr,
+ sizeof(struct in_addr));
+ req.datalen += sizeof(struct in_addr);
+
+ /* we're only doing authentication */
+ req.data[req.datalen] = PW_SERVICE_TYPE;
+ req.data[req.datalen+1] = 6;
+ req.data[req.datalen+2] = (PW_SERVICE_AUTH_ONLY >> 24) & 0x000000ff;
+ req.data[req.datalen+3] = (PW_SERVICE_AUTH_ONLY >> 16) & 0x000000ff;
+ req.data[req.datalen+4] = (PW_SERVICE_AUTH_ONLY >> 8) & 0x000000ff;
+ req.data[req.datalen+5] = PW_SERVICE_AUTH_ONLY & 0x000000ff;
+ req.datalen += req.data[req.datalen+1];
+
+ /* filled in the data, now we know what the actual length is. */
+ req.length = 4+AUTH_VECTOR_LEN+req.datalen;
+
+ /* "encrypt" the password */
+ for (i = 0; i < req.data[passstart+1]-2; i += sizeof(HASH)) {
+ jlen = sizeof(HASH);
+ if (req.data[passstart+1]-(unsigned)i-2 < sizeof(HASH))
+ jlen = req.data[passstart+1]-i-2;
+ for (j = 0; j < jlen; j++)
+ req.data[passstart+2+i+j] ^= digest[j];
+ if (jlen == sizeof(HASH)) {
+ /* Recalculate the digest from the HASHed previous */
+ strlcpy((char *) secbuf, config->secret, sizeof(secbuf));
+ memcpy(secbuf+strlen(config->secret), &req.data[passstart+2+i],
+ sizeof(HASH));
+ md5_hash(secbuf, strlen(config->secret)+sizeof(HASH), digest);
+ }
+ }
+ sreq->reqlen = req.length;
+ req.length = htons(req.length);
+
+ req_copyfrom(sreq, req);
+
+ /* Go to the next record in the list */
+ config = config->next;
+ }
+
+ /* YAYY! The auth_req is ready to go! Build the reply socket and send out
+ * the message. */
+
+ /* now, build the sockets */
+ if ((sock = socket(AF_INET, SOCK_DGRAM, 0)) < 0) {
+ syswarn("cannot build reply socket");
+ return(-1);
+ }
+ if (bind(sock, (struct sockaddr*) &sinl, sizeof(sinl)) < 0) {
+ syswarn("cannot bind reply socket");
+ close(sock);
+ return(-1);
+ }
+
+ for(done = 0; authtries > 0 && !done; authtries--) {
+ for (config = radconfig, sreq = reqtop; sreq != NULL && !done;
+ config = config->next, sreq = sreq->next){
+ req_copyto(req, sreq);
+
+ /* send out the packet and wait for reply. */
+ if (sendto(sock, (char *)&req, sreq->reqlen, 0,
+ (struct sockaddr*) &sreq->sinr,
+ sizeof (struct sockaddr_in)) < 0) {
+ syswarn("cannot send auth_reg");
+ close(sock);
+ return(-1);
+ }
+
+ /* wait 5 seconds maximum for a radius reply. */
+ now = time(0);
+ end = now+5;
+ tmout.tv_sec = 6;
+ tmout.tv_usec = 0;
+ FD_ZERO(&rdfds);
+ /* store the old vector to verify next checksum */
+ memcpy(secbuf+sizeof(req.vector), req.vector, sizeof(req.vector));
+ FD_SET(sock, &rdfds);
+ got = select(sock+1, &rdfds, 0, 0, &tmout);
+ if (got < 0) {
+ syswarn("cannot not select");
+ break;
+ } else if (got == 0) {
+ /* timer ran out */
+ now = time(0);
+ tmout.tv_sec = end - now + 1;
+ tmout.tv_usec = 0;
+ continue;
+ }
+ slen = sizeof(sinl);
+ if ((jlen = recvfrom(sock, (char *)&req, sizeof(req)-sizeof(int), 0,
+ (struct sockaddr*) &sinl, &slen)) < 0) {
+ syswarn("cannot recvfrom");
+ break;
+ }
+ if (!config->ignore_source) {
+ if (sinl.sin_addr.s_addr != sreq->sinr.sin_addr.s_addr ||
+ (sinl.sin_port != sreq->sinr.sin_port)) {
+ warn("received unexpected UDP packet from %s:%d",
+ inet_ntoa(sinl.sin_addr), ntohs(sinl.sin_port));
+ continue;
+ }
+ }
+ sreq->reqlen = ntohs(req.length);
+ if (jlen < 4+AUTH_VECTOR_LEN || jlen != sreq->reqlen) {
+ warn("received badly-sized packet");
+ continue;
+ }
+ /* verify the checksum */
+ memcpy(((char*)&req)+sreq->reqlen, config->secret, strlen(config->secret));
+ memcpy(secbuf, req.vector, sizeof(req.vector));
+ memcpy(req.vector, secbuf+sizeof(req.vector), sizeof(req.vector));
+ md5_hash((unsigned char *)&req, strlen(config->secret)+sreq->reqlen,
+ digest);
+ if (memcmp(digest, secbuf, sizeof(HASH)) != 0) {
+ warn("checksum didn't match");
+ continue;
+ }
+ /* FINALLY! Got back a known-good packet. See if we're in. */
+ close(sock);
+ return (req.code == PW_AUTHENTICATION_ACK) ? 0 : -1;
+ done = 1;
+ req_copyfrom(sreq, req);
+ break;
+ }
+ }
+ if (authtries == 0)
+ warn("cannot talk to remote radius server %s:%d",
+ inet_ntoa(sreq->sinr.sin_addr), ntohs(sreq->sinr.sin_port));
+ return(-2);
+}
+
+#define RAD_HAVE_HOST 1
+#define RAD_HAVE_PORT 2
+#define RAD_HAVE_PREFIX 4
+#define RAD_HAVE_SUFFIX 8
+#define RAD_HAVE_LOCHOST 16
+#define RAD_HAVE_LOCPORT 32
+
+int main(int argc, char *argv[])
+{
+ int opt;
+ int havefile, haveother;
+ struct auth_info *authinfo;
+ rad_config_t radconfig;
+ int retval;
+ char *radius_config;
+
+ message_program_name = "radius";
+
+ if (!innconf_read(NULL))
+ exit(1);
+
+ memset(&radconfig, '\0', sizeof(rad_config_t));
+ haveother = havefile = 0;
+
+ while ((opt = getopt(argc, argv, "f:h")) != -1) {
+ switch (opt) {
+ case 'f':
+ if (haveother)
+ die("-f flag after another flag");
+ if (!havefile) {
+ /* override the standard config completely if the user
+ * specifies an alternate config file */
+ memset(&radconfig, '\0', sizeof(rad_config_t));
+ havefile = 1;
+ }
+ read_config(optarg, &radconfig);
+ break;
+ case 'h':
+ printf("Usage: radius [-f config]\n");
+ exit(0);
+ }
+ }
+ if (argc != optind)
+ exit(2);
+ if (!havefile) {
+ radius_config = concatpath(innconf->pathetc, _PATH_RADIUS_CONFIG);
+ read_config(radius_config, &radconfig);
+
+ free(radius_config);
+ }
+
+ authinfo = get_auth_info(stdin);
+ if (authinfo == NULL)
+ die("failed getting auth info");
+ if (authinfo->username[0] == '\0')
+ die("empty username");
+
+ /* got username and password, check that they're valid */
+
+ retval = rad_auth(&radconfig, authinfo->username, authinfo->password);
+ if (retval == -1)
+ die("user %s password doesn't match", authinfo->username);
+ else if (retval == -2)
+ /* couldn't talk to the radius server.. output logged above. */
+ exit(1);
+ else if (retval != 0)
+ die("unexpected return code from authentication function: %d",
+ retval);
+
+ /* radius password matches! */
+ printf("User:%s\n", authinfo->username);
+ exit(0);
+}
--- /dev/null
+## $Id: Makefile 5789 2002-09-29 23:34:26Z rra $
+
+include ../../Makefile.global
+
+top = ../..
+CFLAGS = $(GCFLAGS)
+
+ALL = smbvalid.a
+
+SOURCES = rfcnb-io.c rfcnb-util.c session.c smbdes.c \
+ smbencrypt.c smblib-util.c smblib.c valid.c
+
+OBJECTS = $(SOURCES:.c=.o)
+
+all: $(ALL)
+
+warnings:
+ $(MAKE) COPT='$(WARNINGS)' all
+
+smbvalid.a: $(OBJECTS)
+ ar rc $@ $(OBJECTS)
+ $(RANLIB) $@
+
+clobber clean distclean:
+ rm -f *.o smbvalid.a
+
+depend: Makefile $(SOURCES)
+ $(MAKEDEPEND) '$(CFLAGS)' $(SOURCES)
+
+# DO NOT DELETE THIS LINE -- make depend depends on it.
+rfcnb-io.o: rfcnb-io.c ../../include/config.h \
+ ../../include/inn/defines.h ../../include/clibrary.h rfcnb-priv.h \
+ rfcnb-error.h rfcnb-common.h byteorder.h rfcnb-util.h rfcnb-io.h
+rfcnb-util.o: rfcnb-util.c ../../include/config.h \
+ ../../include/inn/defines.h ../../include/clibrary.h rfcnb-priv.h \
+ rfcnb-error.h rfcnb-common.h byteorder.h rfcnb-util.h rfcnb-io.h
+session.o: session.c ../../include/config.h \
+ ../../include/inn/defines.h ../../include/clibrary.h rfcnb-priv.h \
+ rfcnb-error.h rfcnb-common.h byteorder.h rfcnb-util.h
+smbdes.o: smbdes.c
+smbencrypt.o: smbencrypt.c ../../include/config.h \
+ ../../include/inn/defines.h ../../include/clibrary.h smblib-priv.h \
+ smblib-common.h byteorder.h
+smblib-util.o: smblib-util.c ../../include/config.h \
+ ../../include/inn/defines.h ../../include/clibrary.h smblib-priv.h \
+ smblib-common.h byteorder.h rfcnb.h rfcnb-error.h rfcnb-common.h
+smblib.o: smblib.c ../../include/config.h ../../include/inn/defines.h \
+ ../../include/clibrary.h smblib-priv.h smblib-common.h byteorder.h \
+ rfcnb.h rfcnb-error.h rfcnb-common.h
+valid.o: valid.c ../../include/config.h ../../include/inn/defines.h \
+ smblib-priv.h smblib-common.h byteorder.h valid.h
--- /dev/null
+/*
+ Unix SMB/Netbios implementation.
+ Version 1.9.
+ SMB Byte handling
+ Copyright (C) Andrew Tridgell 1992-1995
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 2 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program; if not, write to the Free Software
+ Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+*/
+
+/*
+ This file implements macros for machine independent short and
+ int manipulation
+*/
+
+#undef CAREFUL_ALIGNMENT
+
+/* we know that the 386 can handle misalignment and has the "right"
+ byteorder */
+#ifdef __i386__
+#define CAREFUL_ALIGNMENT 0
+#endif
+
+#ifndef CAREFUL_ALIGNMENT
+#define CAREFUL_ALIGNMENT 1
+#endif
+
+#define CVAL(buf,pos) (((unsigned char *)(buf))[pos])
+#define PVAL(buf,pos) ((unsigned)CVAL(buf,pos))
+#define SCVAL(buf,pos,val) (CVAL(buf,pos) = (val))
+
+
+#if CAREFUL_ALIGNMENT
+#define SVAL(buf,pos) (PVAL(buf,pos)|PVAL(buf,(pos)+1)<<8)
+#define IVAL(buf,pos) (SVAL(buf,pos)|SVAL(buf,(pos)+2)<<16)
+#define SSVALX(buf,pos,val) (CVAL(buf,pos)=(val)&0xFF,CVAL(buf,pos+1)=(val)>>8)
+#define SIVALX(buf,pos,val) (SSVALX(buf,pos,val&0xFFFF),SSVALX(buf,pos+2,val>>16))
+#define SVALS(buf,pos) ((int16)SVAL(buf,pos))
+#define IVALS(buf,pos) ((int32)IVAL(buf,pos))
+#define SSVAL(buf,pos,val) SSVALX((buf),(pos),((uint16)(val)))
+#define SIVAL(buf,pos,val) SIVALX((buf),(pos),((uint32)(val)))
+#define SSVALS(buf,pos,val) SSVALX((buf),(pos),((int16)(val)))
+#define SIVALS(buf,pos,val) SIVALX((buf),(pos),((int32)(val)))
+#else
+/* this handles things for architectures like the 386 that can handle
+ alignment errors */
+/*
+ WARNING: This section is dependent on the length of int16 and int32
+ being correct
+*/
+#define SVAL(buf,pos) (*(uint16 *)((char *)(buf) + (pos)))
+#define IVAL(buf,pos) (*(uint32 *)((char *)(buf) + (pos)))
+#define SVALS(buf,pos) (*(int16 *)((char *)(buf) + (pos)))
+#define IVALS(buf,pos) (*(int32 *)((char *)(buf) + (pos)))
+#define SSVAL(buf,pos,val) SVAL(buf,pos)=((uint16)(val))
+#define SIVAL(buf,pos,val) IVAL(buf,pos)=((uint32)(val))
+#define SSVALS(buf,pos,val) SVALS(buf,pos)=((int16)(val))
+#define SIVALS(buf,pos,val) IVALS(buf,pos)=((int32)(val))
+#endif
--- /dev/null
+/* UNIX RFCNB (RFC1001/RFC1002) NetBIOS implementation
+
+ Version 1.0
+ RFCNB Common Structures etc Defines
+
+ Copyright (C) Richard Sharpe 1996
+
+*/
+
+/*
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 2 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program; if not, write to the Free Software
+ Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+*/
+
+/* A data structure we need */
+
+typedef struct RFCNB_Pkt {
+
+ char * data; /* The data in this portion */
+ int len;
+ struct RFCNB_Pkt *next;
+
+} RFCNB_Pkt;
+
+void RFCNB_Free_Pkt(struct RFCNB_Pkt *pkt);
--- /dev/null
+/* UNIX RFCNB (RFC1001/RFC1002) NetBIOS implementation
+
+ Version 1.0
+ RFCNB Error Response Defines
+
+ Copyright (C) Richard Sharpe 1996
+
+*/
+
+/*
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 2 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program; if not, write to the Free Software
+ Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+*/
+
+/* Error responses */
+
+#define RFCNBE_Bad -1 /* Bad response */
+#define RFCNBE_OK 0
+
+/* these should follow the spec ... is there one ?*/
+
+#define RFCNBE_NoSpace 1 /* Could not allocate space for a struct */
+#define RFCNBE_BadName 2 /* Could not translate a name */
+#define RFCNBE_BadRead 3 /* Read sys call failed */
+#define RFCNBE_BadWrite 4 /* Write Sys call failed */
+#define RFCNBE_ProtErr 5 /* Protocol Error */
+#define RFCNBE_ConGone 6 /* Connection dropped */
+#define RFCNBE_BadHandle 7 /* Handle passed was bad */
+#define RFCNBE_BadSocket 8 /* Problems creating socket */
+#define RFCNBE_ConnectFailed 9 /* Connect failed */
+#define RFCNBE_CallRejNLOCN 10 /* Call rejected, not listening on CN */
+#define RFCNBE_CallRejNLFCN 11 /* Call rejected, not listening for CN */
+#define RFCNBE_CallRejCNNP 12 /* Call rejected, called name not present */
+#define RFCNBE_CallRejInfRes 13/* Call rejetced, name ok, no resources */
+#define RFCNBE_CallRejUnSpec 14/* Call rejected, unspecified error */
+#define RFCNBE_BadParam 15/* Bad parameters passed ... */
+#define RFCNBE_Timeout 16/* IO Timed out */
--- /dev/null
+/* UNIX RFCNB (RFC1001/RFC1002) NEtBIOS implementation
+
+ Version 1.0
+ RFCNB IO Routines ...
+
+ Copyright (C) Richard Sharpe 1996
+
+*/
+
+/*
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 2 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program; if not, write to the Free Software
+ Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+*/
+
+#include "config.h"
+#include "clibrary.h"
+#include <errno.h>
+#include <sys/uio.h>
+
+#include "rfcnb-priv.h"
+#include "rfcnb-util.h"
+#include "rfcnb-io.h"
+
+int RFCNB_Timeout = 0; /* Timeout in seconds ... */
+
+/* Discard the rest of an incoming packet as we do not have space for it
+ in the buffer we allocated or were passed ... */
+
+int RFCNB_Discard_Rest(struct RFCNB_Con *con, int len)
+
+{ char temp[100]; /* Read into here */
+ int rest, this_read, bytes_read;
+
+ /* len is the amount we should read */
+
+ rest = len;
+
+ while (rest > 0) {
+
+ this_read = (rest > sizeof(temp)?sizeof(temp):rest);
+
+ bytes_read = read(con -> fd, temp, this_read);
+
+ if (bytes_read <= 0) { /* Error so return */
+
+ if (bytes_read < 0)
+ RFCNB_errno = RFCNBE_BadRead;
+ else
+ RFCNB_errno = RFCNBE_ConGone;
+
+ RFCNB_saved_errno = errno;
+ return(RFCNBE_Bad);
+
+ }
+
+ rest = rest - bytes_read;
+
+ }
+
+ return(0);
+
+}
+
+
+/* Send an RFCNB packet to the connection.
+
+ We just send each of the blocks linked together ...
+
+ If we can, try to send it as one iovec ...
+
+*/
+
+int RFCNB_Put_Pkt(struct RFCNB_Con *con, struct RFCNB_Pkt *pkt, int len)
+
+{ int len_sent, tot_sent, this_len;
+ struct RFCNB_Pkt *pkt_ptr;
+ char *this_data;
+ int i;
+ struct iovec io_list[10]; /* We should never have more */
+ /* If we do, this will blow up ...*/
+
+ /* Try to send the data ... We only send as many bytes as len claims */
+ /* We should try to stuff it into an IOVEC and send as one write */
+
+
+ pkt_ptr = pkt;
+ len_sent = tot_sent = 0; /* Nothing sent so far */
+ i = 0;
+
+ while ((pkt_ptr != NULL) & (i < 10)) { /* Watch that magic number! */
+
+ this_len = pkt_ptr -> len;
+ this_data = pkt_ptr -> data;
+ if ((tot_sent + this_len) > len)
+ this_len = len - tot_sent; /* Adjust so we don't send too much */
+
+ /* Now plug into the iovec ... */
+
+ io_list[i].iov_len = this_len;
+ io_list[i].iov_base = this_data;
+ i++;
+
+ tot_sent += this_len;
+
+ if (tot_sent == len) break; /* Let's not send too much */
+
+ pkt_ptr = pkt_ptr -> next;
+
+ }
+
+ /* Set up an alarm if timeouts are set ... */
+
+ if (RFCNB_Timeout > 0)
+ alarm(RFCNB_Timeout);
+
+ if ((len_sent = writev(con -> fd, io_list, i)) < 0) { /* An error */
+
+ con -> rfc_errno = errno;
+ if (errno == EINTR) /* We were interrupted ... */
+ RFCNB_errno = RFCNBE_Timeout;
+ else
+ RFCNB_errno = RFCNBE_BadWrite;
+ RFCNB_saved_errno = errno;
+ return(RFCNBE_Bad);
+
+ }
+
+ if (len_sent < tot_sent) { /* Less than we wanted */
+ if (errno == EINTR) /* We were interrupted */
+ RFCNB_errno = RFCNBE_Timeout;
+ else
+ RFCNB_errno = RFCNBE_BadWrite;
+ RFCNB_saved_errno = errno;
+ return(RFCNBE_Bad);
+ }
+
+ if (RFCNB_Timeout > 0)
+ alarm(0); /* Reset that sucker */
+
+ return(len_sent);
+
+}
+
+/* Read an RFCNB packet off the connection.
+
+ We read the first 4 bytes, that tells us the length, then read the
+ rest. We should implement a timeout, but we don't just yet
+
+*/
+
+
+int RFCNB_Get_Pkt(struct RFCNB_Con *con, struct RFCNB_Pkt *pkt, int len)
+
+{ int read_len, pkt_len;
+ char hdr[RFCNB_Pkt_Hdr_Len]; /* Local space for the header */
+ struct RFCNB_Pkt *pkt_frag;
+ int more, this_time, offset, frag_len, this_len;
+ bool seen_keep_alive = true;
+
+ /* Read that header straight into the buffer */
+
+ if (len < RFCNB_Pkt_Hdr_Len) { /* What a bozo */
+
+ RFCNB_errno = RFCNBE_BadParam;
+ return(RFCNBE_Bad);
+
+ }
+
+ /* We discard keep alives here ... */
+
+ if (RFCNB_Timeout > 0)
+ alarm(RFCNB_Timeout);
+
+ while (seen_keep_alive) {
+
+ if ((read_len = read(con -> fd, hdr, sizeof(hdr))) < 0) { /* Problems */
+ if (errno == EINTR)
+ RFCNB_errno = RFCNBE_Timeout;
+ else
+ RFCNB_errno = RFCNBE_BadRead;
+ RFCNB_saved_errno = errno;
+ return(RFCNBE_Bad);
+
+ }
+
+ /* Now we check out what we got */
+
+ if (read_len == 0) { /* Connection closed, send back eof? */
+
+ if (errno == EINTR)
+ RFCNB_errno = RFCNBE_Timeout;
+ else
+ RFCNB_errno = RFCNBE_ConGone;
+ RFCNB_saved_errno = errno;
+ return(RFCNBE_Bad);
+
+ }
+
+ if (RFCNB_Pkt_Type(hdr) != RFCNB_SESSION_KEEP_ALIVE) {
+ seen_keep_alive = false;
+ }
+
+ }
+
+ /* What if we got less than or equal to a hdr size in bytes? */
+
+ if (read_len < sizeof(hdr)) { /* We got a small packet */
+
+ /* Now we need to copy the hdr portion we got into the supplied packet */
+
+ memcpy(pkt -> data, hdr, read_len); /*Copy data */
+
+ return(read_len);
+
+ }
+
+ /* Now, if we got at least a hdr size, alloc space for rest, if we need it */
+
+ pkt_len = RFCNB_Pkt_Len(hdr);
+
+ /* Now copy in the hdr */
+
+ memcpy(pkt -> data, hdr, sizeof(hdr));
+
+ /* Get the rest of the packet ... first figure out how big our buf is? */
+ /* And make sure that we handle the fragments properly ... Sure should */
+ /* use an iovec ... */
+
+ if (len < pkt_len) /* Only get as much as we have space for */
+ more = len - RFCNB_Pkt_Hdr_Len;
+ else
+ more = pkt_len;
+
+ this_time = 0;
+
+ /* We read for each fragment ... */
+
+ if (pkt -> len == read_len){ /* If this frag was exact size */
+ pkt_frag = pkt -> next; /* Stick next lot in next frag */
+ offset = 0; /* then we start at 0 in next */
+ }
+ else {
+ pkt_frag = pkt; /* Otherwise use rest of this frag */
+ offset = RFCNB_Pkt_Hdr_Len; /* Otherwise skip the header */
+ }
+
+ frag_len = pkt_frag -> len;
+
+ if (more <= frag_len) /* If len left to get less than frag space */
+ this_len = more; /* Get the rest ... */
+ else
+ this_len = frag_len - offset;
+
+ while (more > 0) {
+
+ if ((this_time = read(con -> fd, (pkt_frag -> data) + offset, this_len)) <= 0) { /* Problems */
+
+ if (errno == EINTR) {
+
+ RFCNB_errno = RFCNB_Timeout;
+
+ }
+ else {
+ if (this_time < 0)
+ RFCNB_errno = RFCNBE_BadRead;
+ else
+ RFCNB_errno = RFCNBE_ConGone;
+ }
+
+ RFCNB_saved_errno = errno;
+ return(RFCNBE_Bad);
+
+ }
+
+ read_len = read_len + this_time; /* How much have we read ... */
+
+ /* Now set up the next part */
+
+ if (pkt_frag -> next == NULL) break; /* That's it here */
+
+ pkt_frag = pkt_frag -> next;
+ this_len = pkt_frag -> len;
+ offset = 0;
+
+ more = more - this_time;
+
+ }
+
+ if (read_len < (pkt_len + sizeof(hdr))) { /* Discard the rest */
+
+ return(RFCNB_Discard_Rest(con, (pkt_len + sizeof(hdr)) - read_len));
+
+ }
+
+ if (RFCNB_Timeout > 0)
+ alarm(0); /* Reset that sucker */
+
+ return(read_len + sizeof(RFCNB_Hdr));
+}
--- /dev/null
+/* UNIX RFCNB (RFC1001/RFC1002) NetBIOS implementation
+
+ Version 1.0
+ RFCNB IO Routines Defines
+
+ Copyright (C) Richard Sharpe 1996
+
+*/
+
+/*
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 2 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program; if not, write to the Free Software
+ Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+*/
+
+int RFCNB_Put_Pkt(struct RFCNB_Con *con, struct RFCNB_Pkt *pkt, int len);
+
+int RFCNB_Get_Pkt(struct RFCNB_Con *con, struct RFCNB_Pkt *pkt, int len);
--- /dev/null
+/* UNIX RFCNB (RFC1001/RFC1002) NetBIOS implementation
+
+ Version 1.0
+ RFCNB Defines
+
+ Copyright (C) Richard Sharpe 1996
+
+*/
+
+/*
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 2 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program; if not, write to the Free Software
+ Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+*/
+
+/* Defines we need */
+
+typedef unsigned short uint16;
+
+#define GLOBAL extern
+
+#include <netinet/in.h>
+
+#include "rfcnb-error.h"
+#include "rfcnb-common.h"
+#include "byteorder.h"
+
+#define RFCNB_Default_Port 139
+
+#define RFCNB_MAX_STATS 1
+
+/* Protocol defines we need */
+
+#define RFCNB_SESSION_MESSAGE 0
+#define RFCNB_SESSION_REQUEST 0x81
+#define RFCNB_SESSION_ACK 0x82
+#define RFCNB_SESSION_REJ 0x83
+#define RFCNB_SESSION_RETARGET 0x84
+#define RFCNB_SESSION_KEEP_ALIVE 0x85
+
+/* Structures */
+
+typedef struct redirect_addr * redirect_ptr;
+
+struct redirect_addr {
+
+ struct in_addr ip_addr;
+ int port;
+ redirect_ptr next;
+
+};
+
+typedef struct RFCNB_Con {
+
+ int fd; /* File descripter for TCP/IP connection */
+ int rfc_errno; /* last error */
+ int timeout; /* How many milli-secs before IO times out */
+ int redirects; /* How many times we were redirected */
+ struct redirect_addr *redirect_list; /* First is first address */
+ struct redirect_addr *last_addr;
+
+} RFCNB_Con;
+
+typedef char RFCNB_Hdr[4]; /* The header is 4 bytes long with */
+ /* char[0] as the type, char[1] the */
+ /* flags, and char[2..3] the length */
+
+/* Macros to extract things from the header. These are for portability
+ between architecture types where we are worried about byte order */
+
+#define RFCNB_Pkt_Hdr_Len 4
+#define RFCNB_Pkt_Sess_Len 72
+#define RFCNB_Pkt_Retarg_Len 10
+#define RFCNB_Pkt_Nack_Len 5
+#define RFCNB_Pkt_Type_Offset 0
+#define RFCNB_Pkt_Flags_Offset 1
+#define RFCNB_Pkt_Len_Offset 2 /* Length is 2 bytes plus a flag bit */
+#define RFCNB_Pkt_N1Len_Offset 4
+#define RFCNB_Pkt_Called_Offset 5
+#define RFCNB_Pkt_N2Len_Offset 38
+#define RFCNB_Pkt_Calling_Offset 39
+#define RFCNB_Pkt_Error_Offset 4
+#define RFCNB_Pkt_IP_Offset 4
+#define RFCNB_Pkt_Port_Offset 8
+
+/* The next macro isolates the length of a packet, including the bit in the
+ flags */
+
+#define RFCNB_Pkt_Len(p) (PVAL(p, 3) | (PVAL(p, 2) << 8) | \
+ ((PVAL(p, RFCNB_Pkt_Flags_Offset) & 0x01) << 16))
+
+#define RFCNB_Put_Pkt_Len(p, v) ((p)[1] = (((v) >> 16) & 1)); \
+ ((p)[2] = (((v) >> 8) & 0xFF)); \
+ ((p)[3] = ((v) & 0xFF));
+
+#define RFCNB_Pkt_Type(p) (CVAL(p, RFCNB_Pkt_Type_Offset))
+
+/* Static variables */
+
+/* Only declare this if not defined */
+
+#ifndef RFCNB_ERRNO
+extern int RFCNB_errno;
+extern int RFCNB_saved_errno; /* Save this from point of error */
+#endif
--- /dev/null
+/* UNIX RFCNB (RFC1001/RFC1002) NetBIOS implementation
+
+ Version 1.0
+ RFCNB Utility Routines ...
+
+ Copyright (C) Richard Sharpe 1996
+
+*/
+
+/*
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 2 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program; if not, write to the Free Software
+ Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+*/
+
+#include "config.h"
+#include "clibrary.h"
+#include "portable/socket.h"
+#include <errno.h>
+#include <netdb.h>
+
+#include "rfcnb-priv.h"
+#include "rfcnb-util.h"
+#include "rfcnb-io.h"
+
+#ifndef INADDR_NONE
+# define INADDR_NONE -1
+#endif
+
+extern void (*Prot_Print_Routine)(); /* Pointer to protocol print routine */
+
+/* Convert name and pad to 16 chars as needed */
+/* Name 1 is a C string with null termination, name 2 may not be */
+/* If SysName is true, then put a <00> on end, else space> */
+
+void RFCNB_CvtPad_Name(char *name1, char *name2)
+
+{ char c, c1, c2;
+ int i, len;
+
+ len = strlen(name1);
+
+ for (i = 0; i < 16; i++) {
+
+ if (i >= len) {
+
+ c1 = 'C'; c2 = 'A'; /* CA is a space */
+
+ } else {
+
+ c = name1[i];
+ c1 = (char)((int)c/16 + (int)'A');
+ c2 = (char)((int)c%16 + (int)'A');
+ }
+
+ name2[i*2] = c1;
+ name2[i*2+1] = c2;
+
+ }
+
+ name2[32] = 0; /* Put in the nll ...*/
+
+}
+
+/* Get a packet of size n */
+
+struct RFCNB_Pkt *RFCNB_Alloc_Pkt(int n)
+
+{ RFCNB_Pkt *pkt;
+
+ if ((pkt = (struct RFCNB_Pkt *)malloc(sizeof(struct RFCNB_Pkt))) == NULL) {
+
+ RFCNB_errno = RFCNBE_NoSpace;
+ RFCNB_saved_errno = errno;
+ return(NULL);
+
+ }
+
+ pkt -> next = NULL;
+ pkt -> len = n;
+
+ if (n == 0) return(pkt);
+
+ if ((pkt -> data = (char *)malloc(n)) == NULL) {
+
+ RFCNB_errno = RFCNBE_NoSpace;
+ RFCNB_saved_errno = errno;
+ free(pkt);
+ return(NULL);
+
+ }
+
+ return(pkt);
+
+}
+
+/* Free up a packet */
+
+void RFCNB_Free_Pkt(struct RFCNB_Pkt *pkt)
+
+{ struct RFCNB_Pkt *pkt_next; char *data_ptr;
+
+ while (pkt != NULL) {
+
+ pkt_next = pkt -> next;
+
+ data_ptr = pkt -> data;
+
+ if (data_ptr != NULL)
+ free(data_ptr);
+
+ free(pkt);
+
+ pkt = pkt_next;
+
+ }
+
+}
+
+/* Resolve a name into an address */
+
+int RFCNB_Name_To_IP(char *host, struct in_addr *Dest_IP)
+
+{ int addr; /* Assumes IP4, 32 bit network addresses */
+ struct hostent *hp;
+
+ /* Use inet_addr to try to convert the address */
+
+ if ((addr = inet_addr(host)) == INADDR_NONE) { /* Oh well, a good try :-) */
+
+ /* Now try a name look up with gethostbyname */
+
+ if ((hp = gethostbyname(host)) == NULL) { /* Not in DNS */
+
+ /* Try NetBIOS name lookup, how the hell do we do that? */
+
+ RFCNB_errno = RFCNBE_BadName; /* Is this right? */
+ RFCNB_saved_errno = errno;
+ return(RFCNBE_Bad);
+
+ }
+ else { /* We got a name */
+
+ memcpy((void *)Dest_IP, (void *)hp -> h_addr_list[0], sizeof(struct in_addr));
+
+ }
+ }
+ else { /* It was an IP address */
+
+ memcpy((void *)Dest_IP, (void *)&addr, sizeof(struct in_addr));
+
+ }
+
+ return 0;
+
+}
+
+/* Disconnect the TCP connection to the server */
+
+int RFCNB_Close(int socket)
+
+{
+
+ close(socket);
+
+ /* If we want to do error recovery, here is where we put it */
+
+ return 0;
+
+}
+
+/* Connect to the server specified in the IP address.
+ Not sure how to handle socket options etc. */
+
+int RFCNB_IP_Connect(struct in_addr Dest_IP, int port)
+
+{ struct sockaddr_in Socket;
+ int fd;
+
+ /* Create a socket */
+
+ if ((fd = socket(PF_INET, SOCK_STREAM, 0)) < 0) { /* Handle the error */
+
+ RFCNB_errno = RFCNBE_BadSocket;
+ RFCNB_saved_errno = errno;
+ return(RFCNBE_Bad);
+ }
+
+ memset(&Socket, 0, sizeof(Socket));
+ memcpy((char *)&Socket.sin_addr, (char *)&Dest_IP, sizeof(Dest_IP));
+
+ Socket.sin_port = htons(port);
+ Socket.sin_family = PF_INET;
+
+ /* Now connect to the destination */
+
+ if (connect(fd, (struct sockaddr *)&Socket, sizeof(Socket)) < 0) { /* Error */
+
+ close(fd);
+ RFCNB_errno = RFCNBE_ConnectFailed;
+ RFCNB_saved_errno = errno;
+ return(RFCNBE_Bad);
+ }
+
+ return(fd);
+
+}
+
+/* handle the details of establishing the RFCNB session with remote
+ end
+
+*/
+
+int RFCNB_Session_Req(struct RFCNB_Con *con,
+ char *Called_Name,
+ char *Calling_Name,
+ bool *redirect,
+ struct in_addr *Dest_IP,
+ int * port)
+
+{ char *sess_pkt;
+
+ /* Response packet should be no more than 9 bytes, make 16 jic */
+
+ char resp[16];
+ int len;
+ struct RFCNB_Pkt *pkt, res_pkt;
+
+ /* We build and send the session request, then read the response */
+
+ pkt = RFCNB_Alloc_Pkt(RFCNB_Pkt_Sess_Len);
+
+ if (pkt == NULL) {
+
+ return(RFCNBE_Bad); /* Leave the error that RFCNB_Alloc_Pkt gives) */
+
+ }
+
+ sess_pkt = pkt -> data; /* Get pointer to packet proper */
+
+ sess_pkt[RFCNB_Pkt_Type_Offset] = RFCNB_SESSION_REQUEST;
+ RFCNB_Put_Pkt_Len(sess_pkt, RFCNB_Pkt_Sess_Len-RFCNB_Pkt_Hdr_Len);
+ sess_pkt[RFCNB_Pkt_N1Len_Offset] = 32;
+ sess_pkt[RFCNB_Pkt_N2Len_Offset] = 32;
+
+ RFCNB_CvtPad_Name(Called_Name, (sess_pkt + RFCNB_Pkt_Called_Offset));
+ RFCNB_CvtPad_Name(Calling_Name, (sess_pkt + RFCNB_Pkt_Calling_Offset));
+
+ /* Now send the packet */
+
+ if ((len = RFCNB_Put_Pkt(con, pkt, RFCNB_Pkt_Sess_Len)) < 0) {
+
+ return(RFCNBE_Bad); /* Should be able to write that lot ... */
+
+ }
+
+ res_pkt.data = resp;
+ res_pkt.len = sizeof(resp);
+ res_pkt.next = NULL;
+
+ if ((len = RFCNB_Get_Pkt(con, &res_pkt, sizeof(resp))) < 0) {
+
+ return(RFCNBE_Bad);
+
+ }
+
+ /* Now analyze the packet ... */
+
+ switch (RFCNB_Pkt_Type(resp)) {
+
+ case RFCNB_SESSION_REJ: /* Didnt like us ... too bad */
+
+ /* Why did we get rejected ? */
+
+ switch (CVAL(resp,RFCNB_Pkt_Error_Offset)) {
+
+ case 0x80:
+ RFCNB_errno = RFCNBE_CallRejNLOCN;
+ break;
+ case 0x81:
+ RFCNB_errno = RFCNBE_CallRejNLFCN;
+ break;
+ case 0x82:
+ RFCNB_errno = RFCNBE_CallRejCNNP;
+ break;
+ case 0x83:
+ RFCNB_errno = RFCNBE_CallRejInfRes;
+ break;
+ case 0x8F:
+ RFCNB_errno = RFCNBE_CallRejUnSpec;
+ break;
+ default:
+ RFCNB_errno = RFCNBE_ProtErr;
+ break;
+ }
+
+ return(RFCNBE_Bad);
+ break;
+
+ case RFCNB_SESSION_ACK: /* Got what we wanted ... */
+
+ return(0);
+ break;
+
+ case RFCNB_SESSION_RETARGET: /* Go elsewhere */
+
+ *redirect = true; /* Copy port and ip addr */
+
+ memcpy(Dest_IP, (resp + RFCNB_Pkt_IP_Offset), sizeof(struct in_addr));
+ *port = SVAL(resp, RFCNB_Pkt_Port_Offset);
+
+ return(0);
+ break;
+
+ default: /* A protocol error */
+
+ RFCNB_errno = RFCNBE_ProtErr;
+ return(RFCNBE_Bad);
+ break;
+ }
+}
--- /dev/null
+/* UNIX RFCNB (RFC1001/RFC1002) NetBIOS implementation
+
+ Version 1.0
+ RFCNB Utility Defines
+
+ Copyright (C) Richard Sharpe 1996
+
+*/
+
+/*
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 2 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program; if not, write to the Free Software
+ Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+*/
+
+void RFCNB_CvtPad_Name(char *name1, char *name2);
+
+struct RFCNB_Pkt *RFCNB_Alloc_Pkt(int n);
+
+int RFCNB_Name_To_IP(char *host, struct in_addr *Dest_IP);
+
+int RFCNB_Close(int socket);
+
+int RFCNB_IP_Connect(struct in_addr Dest_IP, int port);
+
+int RFCNB_Session_Req(struct RFCNB_Con *con,
+ char *Called_Name,
+ char *Calling_Name,
+ bool *redirect,
+ struct in_addr *Dest_IP,
+ int * port);
+
--- /dev/null
+/* UNIX RFCNB (RFC1001/RFC1002) NetBIOS implementation
+
+ Version 1.0
+ RFCNB Defines
+
+ Copyright (C) Richard Sharpe 1996
+
+*/
+
+/*
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 2 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program; if not, write to the Free Software
+ Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+*/
+
+/* Error responses */
+
+#include "rfcnb-error.h"
+#include "rfcnb-common.h"
+
+/* Defines we need */
+
+#define RFCNB_Default_Port 139
+
+/* Definition of routines we define */
+
+void *RFCNB_Call(char *Called_Name, char *Calling_Name, char *Called_Address,
+ int port);
+
+int RFCNB_Send(void *Con_Handle, struct RFCNB_Pkt *Data, int Length);
+
+int RFCNB_Recv(void *Con_Handle, struct RFCNB_Pkt *Data, int Length);
+
+int RFCNB_Hangup(void *con_Handle);
+
+void *RFCNB_Listen();
+
+struct RFCNB_Pkt *RFCNB_Alloc_Pkt(int n);
--- /dev/null
+/* UNIX RFCNB (RFC1001/RFC1002) NetBIOS implementation
+
+ Version 1.0
+ Session Routines ...
+
+ Copyright (C) Richard Sharpe 1996
+
+*/
+
+/*
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 2 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program; if not, write to the Free Software
+ Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+*/
+
+#include "config.h"
+#include "clibrary.h"
+#include <errno.h>
+#include <netinet/in.h>
+#include <netinet/tcp.h>
+#include <sys/socket.h>
+
+int RFCNB_errno = 0;
+int RFCNB_saved_errno = 0;
+#define RFCNB_ERRNO
+
+#include "rfcnb-priv.h"
+#include "rfcnb-io.h"
+#include "rfcnb-util.h"
+
+int RFCNB_Stats[RFCNB_MAX_STATS];
+
+void (*Prot_Print_Routine)() = NULL; /* Pointer to print routine */
+
+/* Set up a session with a remote name. We are passed Called_Name as a
+ string which we convert to a NetBIOS name, ie space terminated, up to
+ 16 characters only if we need to. If Called_Address is not empty, then
+ we use it to connect to the remote end, but put in Called_Name ... Called
+ Address can be a DNS based name, or a TCP/IP address ...
+*/
+
+void *RFCNB_Call(char *Called_Name, char *Calling_Name, char *Called_Address,
+ int port)
+
+{ struct RFCNB_Con *con;
+ struct in_addr Dest_IP;
+ int Client;
+ bool redirect; struct redirect_addr *redir_addr;
+ char *Service_Address;
+
+ /* Now, we really should look up the port in /etc/services ... */
+
+ if (port == 0) port = RFCNB_Default_Port;
+
+ /* Create a connection structure first */
+
+ if ((con = (struct RFCNB_Con *)malloc(sizeof(struct RFCNB_Con))) == NULL) { /* Error in size */
+
+ RFCNB_errno = RFCNBE_NoSpace;
+ RFCNB_saved_errno = errno;
+ return(NULL);
+
+ }
+
+ con -> fd = -0; /* no descriptor yet */
+ con -> rfc_errno = 0; /* no error yet */
+ con -> timeout = 0; /* no timeout */
+ con -> redirects = 0;
+ con -> redirect_list = NULL; /* Fix bug still in version 0.50 */
+
+ /* Resolve that name into an IP address */
+
+ Service_Address = Called_Name;
+ if (strcmp(Called_Address, "") != 0) { /* If the Called Address = "" */
+ Service_Address = Called_Address;
+ }
+
+ if ((errno = RFCNB_Name_To_IP(Service_Address, &Dest_IP)) < 0) { /* Error */
+
+ /* No need to modify RFCNB_errno as it was done by RFCNB_Name_To_IP */
+
+ return(NULL);
+
+ }
+
+ /* Now connect to the remote end */
+
+ redirect = true; /* Fudge this one so we go once through */
+
+ while (redirect) { /* Connect and get session info etc */
+
+ redirect = false; /* Assume all OK */
+
+ /* Build the redirect info. First one is first addr called */
+ /* And tack it onto the list of addresses we called */
+
+ if ((redir_addr = (struct redirect_addr *)malloc(sizeof(struct redirect_addr))) == NULL) { /* Could not get space */
+
+ RFCNB_errno = RFCNBE_NoSpace;
+ RFCNB_saved_errno = errno;
+ return(NULL);
+
+ }
+
+ memcpy((char *)&(redir_addr -> ip_addr), (char *)&Dest_IP, sizeof(Dest_IP));
+ redir_addr -> port = port;
+ redir_addr -> next = NULL;
+
+ if (con -> redirect_list == NULL) { /* Stick on head */
+
+ con -> redirect_list = con -> last_addr = redir_addr;
+
+ } else {
+
+ con -> last_addr -> next = redir_addr;
+ con -> last_addr = redir_addr;
+
+ }
+
+ /* Now, make that connection */
+
+ if ((Client = RFCNB_IP_Connect(Dest_IP, port)) < 0) { /* Error */
+
+ /* No need to modify RFCNB_errno as it was done by RFCNB_IP_Connect */
+
+ return(NULL);
+
+ }
+
+ con -> fd = Client;
+
+ /* Now send and handle the RFCNB session request */
+ /* If we get a redirect, we will comeback with redirect true
+ and a new IP address in DEST_IP */
+
+ if ((errno = RFCNB_Session_Req(con,
+ Called_Name,
+ Calling_Name,
+ &redirect, &Dest_IP, &port)) < 0) {
+
+ /* No need to modify RFCNB_errno as it was done by RFCNB_Session.. */
+
+ return(NULL);
+
+ }
+
+ if (redirect) {
+
+ /* We have to close the connection, and then try again */
+
+ (con -> redirects)++;
+
+ RFCNB_Close(con -> fd); /* Close it */
+
+ }
+ }
+
+ return(con);
+
+}
+
+/* We send a packet to the other end ... for the moment, we treat the
+ data as a series of pointers to blocks of data ... we should check the
+ length ... */
+
+int RFCNB_Send(struct RFCNB_Con *Con_Handle, struct RFCNB_Pkt *udata, int Length)
+
+{ struct RFCNB_Pkt *pkt; char *hdr;
+ int len;
+
+ /* Plug in the header and send the data */
+
+ pkt = RFCNB_Alloc_Pkt(RFCNB_Pkt_Hdr_Len);
+
+ if (pkt == NULL) {
+
+ RFCNB_errno = RFCNBE_NoSpace;
+ RFCNB_saved_errno = errno;
+ return(RFCNBE_Bad);
+
+ }
+
+ pkt -> next = udata; /* The user data we want to send */
+
+ hdr = pkt -> data;
+
+ /* Following crap is for portability across multiple UNIX machines */
+
+ *(hdr + RFCNB_Pkt_Type_Offset) = RFCNB_SESSION_MESSAGE;
+ RFCNB_Put_Pkt_Len(hdr, Length);
+
+#ifdef RFCNB_DEBUG
+
+ fprintf(stderr, "Sending packet: ");
+
+#endif
+
+ if ((len = RFCNB_Put_Pkt(Con_Handle, pkt, Length + RFCNB_Pkt_Hdr_Len)) < 0) {
+
+ /* No need to change RFCNB_errno as it was done by put_pkt ... */
+
+ return(RFCNBE_Bad); /* Should be able to write that lot ... */
+
+ }
+
+ /* Now we have sent that lot, let's get rid of the RFCNB Header and return */
+
+ pkt -> next = NULL;
+
+ RFCNB_Free_Pkt(pkt);
+
+ return(len);
+
+}
+
+/* We pick up a message from the internet ... We have to worry about
+ non-message packets ... */
+
+int RFCNB_Recv(void *con_Handle, struct RFCNB_Pkt *Data, int Length)
+
+{ struct RFCNB_Pkt *pkt;
+ int ret_len;
+
+ if (con_Handle == NULL){
+
+ RFCNB_errno = RFCNBE_BadHandle;
+ RFCNB_saved_errno = errno;
+ return(RFCNBE_Bad);
+
+ }
+
+ /* Now get a packet from below. We allocate a header first */
+
+ /* Plug in the header and send the data */
+
+ pkt = RFCNB_Alloc_Pkt(RFCNB_Pkt_Hdr_Len);
+
+ if (pkt == NULL) {
+
+ RFCNB_errno = RFCNBE_NoSpace;
+ RFCNB_saved_errno = errno;
+ return(RFCNBE_Bad);
+
+ }
+
+ pkt -> next = Data; /* Plug in the data portion */
+
+ if ((ret_len = RFCNB_Get_Pkt(con_Handle, pkt, Length + RFCNB_Pkt_Hdr_Len)) < 0) {
+
+#ifdef RFCNB_DEBUG
+ fprintf(stderr, "Bad packet return in RFCNB_Recv... \n");
+#endif
+
+ return(RFCNBE_Bad);
+
+ }
+
+ /* We should check that we go a message and not a keep alive */
+
+ pkt -> next = NULL;
+
+ RFCNB_Free_Pkt(pkt);
+
+ return(ret_len);
+
+}
+
+/* We just disconnect from the other end, as there is nothing in the RFCNB */
+/* protocol that specifies any exchange as far as I can see */
+
+int RFCNB_Hangup(struct RFCNB_Con *con_Handle)
+
+{
+
+ if (con_Handle != NULL) {
+ RFCNB_Close(con_Handle -> fd); /* Could this fail? */
+ free(con_Handle);
+ }
+
+ return 0;
+
+
+}
+
+/* Set TCP_NODELAY on the socket */
+
+int RFCNB_Set_Sock_NoDelay(struct RFCNB_Con *con_Handle, bool yn)
+
+{
+
+ return(setsockopt(con_Handle -> fd, IPPROTO_TCP, TCP_NODELAY,
+ (char *)&yn, sizeof(yn)));
+
+}
--- /dev/null
+/*
+ Unix SMB/Netbios implementation.
+ Version 1.9.
+
+ a partial implementation of DES designed for use in the
+ SMB authentication protocol
+
+ Copyright (C) Andrew Tridgell 1997
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 2 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program; if not, write to the Free Software
+ Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+*/
+
+
+/* NOTES:
+
+ This code makes no attempt to be fast! In fact, it is a very
+ slow implementation
+
+ This code is NOT a complete DES implementation. It implements only
+ the minimum necessary for SMB authentication, as used by all SMB
+ products (including every copy of Microsoft Windows95 ever sold)
+
+ In particular, it can only do a unchained forward DES pass. This
+ means it is not possible to use this code for encryption/decryption
+ of data, instead it is only useful as a "hash" algorithm.
+
+ There is no entry point into this code that allows normal DES operation.
+
+ I believe this means that this code does not come under ITAR
+ regulations but this is NOT a legal opinion. If you are concerned
+ about the applicability of ITAR regulations to this code then you
+ should confirm it for yourself (and maybe let me know if you come
+ up with a different answer to the one above)
+*/
+
+
+
+static int perm1[56] = {57, 49, 41, 33, 25, 17, 9,
+ 1, 58, 50, 42, 34, 26, 18,
+ 10, 2, 59, 51, 43, 35, 27,
+ 19, 11, 3, 60, 52, 44, 36,
+ 63, 55, 47, 39, 31, 23, 15,
+ 7, 62, 54, 46, 38, 30, 22,
+ 14, 6, 61, 53, 45, 37, 29,
+ 21, 13, 5, 28, 20, 12, 4};
+
+static int perm2[48] = {14, 17, 11, 24, 1, 5,
+ 3, 28, 15, 6, 21, 10,
+ 23, 19, 12, 4, 26, 8,
+ 16, 7, 27, 20, 13, 2,
+ 41, 52, 31, 37, 47, 55,
+ 30, 40, 51, 45, 33, 48,
+ 44, 49, 39, 56, 34, 53,
+ 46, 42, 50, 36, 29, 32};
+
+static int perm3[64] = {58, 50, 42, 34, 26, 18, 10, 2,
+ 60, 52, 44, 36, 28, 20, 12, 4,
+ 62, 54, 46, 38, 30, 22, 14, 6,
+ 64, 56, 48, 40, 32, 24, 16, 8,
+ 57, 49, 41, 33, 25, 17, 9, 1,
+ 59, 51, 43, 35, 27, 19, 11, 3,
+ 61, 53, 45, 37, 29, 21, 13, 5,
+ 63, 55, 47, 39, 31, 23, 15, 7};
+
+static int perm4[48] = { 32, 1, 2, 3, 4, 5,
+ 4, 5, 6, 7, 8, 9,
+ 8, 9, 10, 11, 12, 13,
+ 12, 13, 14, 15, 16, 17,
+ 16, 17, 18, 19, 20, 21,
+ 20, 21, 22, 23, 24, 25,
+ 24, 25, 26, 27, 28, 29,
+ 28, 29, 30, 31, 32, 1};
+
+static int perm5[32] = { 16, 7, 20, 21,
+ 29, 12, 28, 17,
+ 1, 15, 23, 26,
+ 5, 18, 31, 10,
+ 2, 8, 24, 14,
+ 32, 27, 3, 9,
+ 19, 13, 30, 6,
+ 22, 11, 4, 25};
+
+
+static int perm6[64] ={ 40, 8, 48, 16, 56, 24, 64, 32,
+ 39, 7, 47, 15, 55, 23, 63, 31,
+ 38, 6, 46, 14, 54, 22, 62, 30,
+ 37, 5, 45, 13, 53, 21, 61, 29,
+ 36, 4, 44, 12, 52, 20, 60, 28,
+ 35, 3, 43, 11, 51, 19, 59, 27,
+ 34, 2, 42, 10, 50, 18, 58, 26,
+ 33, 1, 41, 9, 49, 17, 57, 25};
+
+
+static int sc[16] = {1, 1, 2, 2, 2, 2, 2, 2, 1, 2, 2, 2, 2, 2, 2, 1};
+
+static int sbox[8][4][16] = {
+ {{14, 4, 13, 1, 2, 15, 11, 8, 3, 10, 6, 12, 5, 9, 0, 7},
+ {0, 15, 7, 4, 14, 2, 13, 1, 10, 6, 12, 11, 9, 5, 3, 8},
+ {4, 1, 14, 8, 13, 6, 2, 11, 15, 12, 9, 7, 3, 10, 5, 0},
+ {15, 12, 8, 2, 4, 9, 1, 7, 5, 11, 3, 14, 10, 0, 6, 13}},
+
+ {{15, 1, 8, 14, 6, 11, 3, 4, 9, 7, 2, 13, 12, 0, 5, 10},
+ {3, 13, 4, 7, 15, 2, 8, 14, 12, 0, 1, 10, 6, 9, 11, 5},
+ {0, 14, 7, 11, 10, 4, 13, 1, 5, 8, 12, 6, 9, 3, 2, 15},
+ {13, 8, 10, 1, 3, 15, 4, 2, 11, 6, 7, 12, 0, 5, 14, 9}},
+
+ {{10, 0, 9, 14, 6, 3, 15, 5, 1, 13, 12, 7, 11, 4, 2, 8},
+ {13, 7, 0, 9, 3, 4, 6, 10, 2, 8, 5, 14, 12, 11, 15, 1},
+ {13, 6, 4, 9, 8, 15, 3, 0, 11, 1, 2, 12, 5, 10, 14, 7},
+ {1, 10, 13, 0, 6, 9, 8, 7, 4, 15, 14, 3, 11, 5, 2, 12}},
+
+ {{7, 13, 14, 3, 0, 6, 9, 10, 1, 2, 8, 5, 11, 12, 4, 15},
+ {13, 8, 11, 5, 6, 15, 0, 3, 4, 7, 2, 12, 1, 10, 14, 9},
+ {10, 6, 9, 0, 12, 11, 7, 13, 15, 1, 3, 14, 5, 2, 8, 4},
+ {3, 15, 0, 6, 10, 1, 13, 8, 9, 4, 5, 11, 12, 7, 2, 14}},
+
+ {{2, 12, 4, 1, 7, 10, 11, 6, 8, 5, 3, 15, 13, 0, 14, 9},
+ {14, 11, 2, 12, 4, 7, 13, 1, 5, 0, 15, 10, 3, 9, 8, 6},
+ {4, 2, 1, 11, 10, 13, 7, 8, 15, 9, 12, 5, 6, 3, 0, 14},
+ {11, 8, 12, 7, 1, 14, 2, 13, 6, 15, 0, 9, 10, 4, 5, 3}},
+
+ {{12, 1, 10, 15, 9, 2, 6, 8, 0, 13, 3, 4, 14, 7, 5, 11},
+ {10, 15, 4, 2, 7, 12, 9, 5, 6, 1, 13, 14, 0, 11, 3, 8},
+ {9, 14, 15, 5, 2, 8, 12, 3, 7, 0, 4, 10, 1, 13, 11, 6},
+ {4, 3, 2, 12, 9, 5, 15, 10, 11, 14, 1, 7, 6, 0, 8, 13}},
+
+ {{4, 11, 2, 14, 15, 0, 8, 13, 3, 12, 9, 7, 5, 10, 6, 1},
+ {13, 0, 11, 7, 4, 9, 1, 10, 14, 3, 5, 12, 2, 15, 8, 6},
+ {1, 4, 11, 13, 12, 3, 7, 14, 10, 15, 6, 8, 0, 5, 9, 2},
+ {6, 11, 13, 8, 1, 4, 10, 7, 9, 5, 0, 15, 14, 2, 3, 12}},
+
+ {{13, 2, 8, 4, 6, 15, 11, 1, 10, 9, 3, 14, 5, 0, 12, 7},
+ {1, 15, 13, 8, 10, 3, 7, 4, 12, 5, 6, 11, 0, 14, 9, 2},
+ {7, 11, 4, 1, 9, 12, 14, 2, 0, 6, 10, 13, 15, 3, 5, 8},
+ {2, 1, 14, 7, 4, 10, 8, 13, 15, 12, 9, 0, 3, 5, 6, 11}}};
+
+static void permute(char *out, char *in, int *p, int n)
+{
+ int i;
+ for (i=0;i<n;i++)
+ out[i] = in[p[i]-1];
+}
+
+static void lshift(char *d, int count, int n)
+{
+ char out[64];
+ int i;
+ for (i=0;i<n;i++)
+ out[i] = d[(i+count)%n];
+ for (i=0;i<n;i++)
+ d[i] = out[i];
+}
+
+static void concat(char *out, char *in1, char *in2, int l1, int l2)
+{
+ while (l1--)
+ *out++ = *in1++;
+ while (l2--)
+ *out++ = *in2++;
+}
+
+static void xor(char *out, char *in1, char *in2, int n)
+{
+ int i;
+ for (i=0;i<n;i++)
+ out[i] = in1[i] ^ in2[i];
+}
+
+static void dohash(char *out, char *in, char *key)
+{
+ int i, j, k;
+ char pk1[56];
+ char c[28];
+ char d[28];
+ char cd[56];
+ char ki[16][48];
+ char pd1[64];
+ char l[32], r[32];
+ char rl[64];
+
+ permute(pk1, key, perm1, 56);
+
+ for (i=0;i<28;i++)
+ c[i] = pk1[i];
+ for (i=0;i<28;i++)
+ d[i] = pk1[i+28];
+
+ for (i=0;i<16;i++) {
+ lshift(c, sc[i], 28);
+ lshift(d, sc[i], 28);
+
+ concat(cd, c, d, 28, 28);
+ permute(ki[i], cd, perm2, 48);
+ }
+
+ permute(pd1, in, perm3, 64);
+
+ for (j=0;j<32;j++) {
+ l[j] = pd1[j];
+ r[j] = pd1[j+32];
+ }
+
+ for (i=0;i<16;i++) {
+ char er[48];
+ char erk[48];
+ char b[8][6];
+ char cb[32];
+ char pcb[32];
+ char r2[32];
+
+ permute(er, r, perm4, 48);
+
+ xor(erk, er, ki[i], 48);
+
+ for (j=0;j<8;j++)
+ for (k=0;k<6;k++)
+ b[j][k] = erk[j*6 + k];
+
+ for (j=0;j<8;j++) {
+ int m, n;
+ m = (b[j][0]<<1) | b[j][5];
+
+ n = (b[j][1]<<3) | (b[j][2]<<2) | (b[j][3]<<1) | b[j][4];
+
+ for (k=0;k<4;k++)
+ b[j][k] = (sbox[j][m][n] & (1<<(3-k)))?1:0;
+ }
+
+ for (j=0;j<8;j++)
+ for (k=0;k<4;k++)
+ cb[j*4+k] = b[j][k];
+ permute(pcb, cb, perm5, 32);
+
+ xor(r2, l, pcb, 32);
+
+ for (j=0;j<32;j++)
+ l[j] = r[j];
+
+ for (j=0;j<32;j++)
+ r[j] = r2[j];
+ }
+
+ concat(rl, r, l, 32, 32);
+
+ permute(out, rl, perm6, 64);
+}
+
+static void str_to_key(unsigned char *str,unsigned char *key)
+{
+ int i;
+
+ key[0] = str[0]>>1;
+ key[1] = ((str[0]&0x01)<<6) | (str[1]>>2);
+ key[2] = ((str[1]&0x03)<<5) | (str[2]>>3);
+ key[3] = ((str[2]&0x07)<<4) | (str[3]>>4);
+ key[4] = ((str[3]&0x0F)<<3) | (str[4]>>5);
+ key[5] = ((str[4]&0x1F)<<2) | (str[5]>>6);
+ key[6] = ((str[5]&0x3F)<<1) | (str[6]>>7);
+ key[7] = str[6]&0x7F;
+ for (i=0;i<8;i++) {
+ key[i] = (key[i]<<1);
+ }
+}
+
+
+static void smbhash(unsigned char *out, unsigned char *in, unsigned char *key)
+{
+ int i;
+ char outb[64];
+ char inb[64];
+ char keyb[64];
+ unsigned char key2[8];
+
+ str_to_key(key, key2);
+
+ for (i=0;i<64;i++) {
+ inb[i] = (in[i/8] & (1<<(7-(i%8)))) ? 1 : 0;
+ keyb[i] = (key2[i/8] & (1<<(7-(i%8)))) ? 1 : 0;
+ outb[i] = 0;
+ }
+
+ dohash(outb, inb, keyb);
+
+ for (i=0;i<8;i++) {
+ out[i] = 0;
+ }
+
+ for (i=0;i<64;i++) {
+ if (outb[i])
+ out[i/8] |= (1<<(7-(i%8)));
+ }
+}
+
+void E_P16(unsigned char *p14,unsigned char *p16)
+{
+ unsigned char sp8[8] = {0x4b, 0x47, 0x53, 0x21, 0x40, 0x23, 0x24, 0x25};
+ smbhash(p16, sp8, p14);
+ smbhash(p16+8, sp8, p14+7);
+}
+
+void E_P24(unsigned char *p21, unsigned char *c8, unsigned char *p24)
+{
+ smbhash(p24, c8, p21);
+ smbhash(p24+8, c8, p21+7);
+ smbhash(p24+16, c8, p21+14);
+}
+
+void cred_hash1(unsigned char *out,unsigned char *in,unsigned char *key)
+{
+ unsigned char buf[8];
+
+ smbhash(buf, in, key);
+ smbhash(out, buf, key+9);
+}
+
+void cred_hash2(unsigned char *out,unsigned char *in,unsigned char *key)
+{
+ unsigned char buf[8];
+ static unsigned char key2[8];
+
+ smbhash(buf, in, key);
+ key2[0] = key[7];
+ smbhash(out, buf, key2);
+}
+
--- /dev/null
+/*
+ Unix SMB/Netbios implementation.
+ Version 1.9.
+ SMB parameters and setup
+ Copyright (C) Andrew Tridgell 1992-1997
+ Modified by Jeremy Allison 1995.
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 2 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program; if not, write to the Free Software
+ Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+*/
+
+#include "config.h"
+#include "clibrary.h"
+#include <ctype.h>
+
+#include "smblib-priv.h"
+
+typedef unsigned char uchar;
+
+void strupper(char *s);
+
+/*
+ This implements the X/Open SMB password encryption
+ It takes a password, a 8 byte "crypt key" and puts 24 bytes of
+ encrypted password into p24 */
+void SMBencrypt(uchar *passwd, uchar *c8, uchar *p24)
+{
+ uchar p14[15], p21[21];
+
+ memset(p21,'\0',21);
+ memset(p14,'\0',14);
+ strlcpy((char *) p14, (char *) passwd, sizeof(p14));
+
+ strupper((char *)p14);
+ E_P16(p14, p21);
+ E_P24(p21, c8, p24);
+}
+
+void strupper(char *s)
+{
+ while (*s)
+ {
+ {
+ if (CTYPE(islower, *s))
+ *s = toupper(*s);
+ s++;
+ }
+ }
+}
--- /dev/null
+/* UNIX SMBlib NetBIOS implementation
+
+ Version 1.0
+ SMBlib Common Defines
+
+ Copyright (C) Richard Sharpe 1996
+
+*/
+
+/*
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 2 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program; if not, write to the Free Software
+ Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+*/
+
+/* Error CLASS codes and etc ... */
+
+#define SMBC_SUCCESS 0
+#define SMBC_ERRDOS 0x01
+#define SMBC_ERRSRV 0x02
+#define SMBC_ERRHRD 0x03
+#define SMBC_ERRCMD 0xFF
+
+/* Define the protocol types ... */
+
+#define SMB_P_Unknown -1 /* Hmmm, is this smart? */
+#define SMB_P_Core 0
+#define SMB_P_CorePlus 1
+#define SMB_P_DOSLanMan1 2
+#define SMB_P_LanMan1 3
+#define SMB_P_DOSLanMan2 4
+#define SMB_P_LanMan2 5
+#define SMB_P_DOSLanMan2_1 6
+#define SMB_P_LanMan2_1 7
+#define SMB_P_NT1 8
+
+/* SMBlib return codes */
+/* We want something that indicates whether or not the return code was a */
+/* remote error, a local error in SMBlib or returned from lower layer ... */
+/* Wonder if this will work ... */
+/* SMBlibE_Remote = 1 indicates remote error */
+/* SMBlibE_ values < 0 indicate local error with more info available */
+/* SMBlibE_ values >1 indicate local from SMBlib code errors? */
+
+#define SMBlibE_Success 0
+#define SMBlibE_Remote 1 /* Remote error, get more info from con */
+#define SMBlibE_BAD -1
+#define SMBlibE_LowerLayer 2 /* Lower layer error */
+#define SMBlibE_NotImpl 3 /* Function not yet implemented */
+#define SMBlibE_ProtLow 4 /* Protocol negotiated does not support req */
+#define SMBlibE_NoSpace 5 /* No space to allocate a structure */
+#define SMBlibE_BadParam 6 /* Bad parameters */
+#define SMBlibE_NegNoProt 7 /* None of our protocols was liked */
+#define SMBlibE_SendFailed 8 /* Sending an SMB failed */
+#define SMBlibE_RecvFailed 9 /* Receiving an SMB failed */
+#define SMBlibE_GuestOnly 10 /* Logged in as guest */
+#define SMBlibE_CallFailed 11 /* Call remote end failed */
+#define SMBlibE_ProtUnknown 12 /* Protocol unknown */
+#define SMBlibE_NoSuchMsg 13 /* Keep this up to date */
--- /dev/null
+/* UNIX SMBlib NetBIOS implementation
+
+ Version 1.0
+ SMBlib private Defines
+
+ Copyright (C) Richard Sharpe 1996
+
+*/
+
+/*
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 2 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program; if not, write to the Free Software
+ Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+*/
+
+#include "smblib-common.h"
+#include <sys/types.h>
+#include <unistd.h>
+
+typedef unsigned short uint16;
+typedef unsigned int uint32;
+
+#include "byteorder.h" /* Hmmm ... hot good */
+
+#define SMB_DEF_IDF 0x424D53FF /* "\377SMB" */
+
+/* The protocol commands and constants we need */
+#define SMBnegprot 0x72 /* negotiate protocol */
+#define SMBsesssetupX 0x73 /* Session Set Up & X (including User Logon) */
+#define SMBdialectID 0x02 /* a dialect id */
+
+typedef unsigned short WORD;
+typedef unsigned short UWORD;
+typedef unsigned int ULONG;
+typedef unsigned char BYTE;
+typedef unsigned char UCHAR;
+
+/* Some macros to allow access to actual packet data so that we */
+/* can change the underlying representation of packets. */
+/* */
+/* The current formats vying for attention are a fragment */
+/* approach where the SMB header is a fragment linked to the */
+/* data portion with the transport protocol (rfcnb or whatever) */
+/* being linked on the front. */
+/* */
+/* The other approach is where the whole packet is one array */
+/* of bytes with space allowed on the front for the packet */
+/* headers. */
+
+#define SMB_Hdr(p) (char *)(p -> data)
+
+/* SMB Hdr def for File Sharing Protocol? From MS and Intel, */
+/* Intel PN 138446 Doc Version 2.0, Nov 7, 1988. This def also */
+/* applies to LANMAN1.0 as well as the Core Protocol */
+/* The spec states that wct and bcc must be present, even if 0 */
+
+/* We define these as offsets into a char SMB[] array for the */
+/* sake of portability */
+
+/* NOTE!. Some of the lenght defines, SMB_<protreq>_len do not include */
+/* the data that follows in the SMB packet, so the code will have to */
+/* take that into account. */
+
+#define SMB_hdr_idf_offset 0 /* 0xFF,'SMB' 0-3 */
+#define SMB_hdr_com_offset 4 /* BYTE 4 */
+#define SMB_hdr_rcls_offset 5 /* BYTE 5 */
+#define SMB_hdr_reh_offset 6 /* BYTE 6 */
+#define SMB_hdr_err_offset 7 /* WORD 7 */
+#define SMB_hdr_reb_offset 9 /* BYTE 9 */
+#define SMB_hdr_flg_offset 9 /* same as reb ...*/
+#define SMB_hdr_res_offset 10 /* 7 WORDs 10 */
+#define SMB_hdr_res0_offset 10 /* WORD 10 */
+#define SMB_hdr_flg2_offset 10 /* WORD */
+#define SMB_hdr_res1_offset 12 /* WORD 12 */
+#define SMB_hdr_res2_offset 14
+#define SMB_hdr_res3_offset 16
+#define SMB_hdr_res4_offset 18
+#define SMB_hdr_res5_offset 20
+#define SMB_hdr_res6_offset 22
+#define SMB_hdr_tid_offset 24
+#define SMB_hdr_pid_offset 26
+#define SMB_hdr_uid_offset 28
+#define SMB_hdr_mid_offset 30
+#define SMB_hdr_wct_offset 32
+
+#define SMB_hdr_len 33 /* 33 byte header? */
+
+#define SMB_hdr_axc_offset 33 /* AndX Command */
+#define SMB_hdr_axr_offset 34 /* AndX Reserved */
+#define SMB_hdr_axo_offset 35 /* Offset from start to WCT of AndX cmd */
+
+/* Format of the Negotiate Protocol SMB */
+
+#define SMB_negp_bcc_offset 33
+#define SMB_negp_buf_offset 35 /* Where the buffer starts */
+#define SMB_negp_len 35 /* plus the data */
+
+/* Format of the Negotiate Response SMB, for CoreProtocol, LM1.2 and */
+/* NT LM 0.12. wct will be 1 for CoreProtocol, 13 for LM 1.2, and 17 */
+/* for NT LM 0.12 */
+
+#define SMB_negrCP_idx_offset 33 /* Response to the neg req */
+#define SMB_negrCP_bcc_offset 35
+#define SMB_negrLM_idx_offset 33 /* dialect index */
+#define SMB_negrLM_sec_offset 35 /* Security mode */
+#define SMB_sec_user_mask 0x01 /* 0 = share, 1 = user */
+#define SMB_sec_encrypt_mask 0x02 /* pick out encrypt */
+#define SMB_negrLM_mbs_offset 37 /* max buffer size */
+#define SMB_negrLM_mmc_offset 39 /* max mpx count */
+#define SMB_negrLM_mnv_offset 41 /* max number of VCs */
+#define SMB_negrLM_rm_offset 43 /* raw mode support bit vec*/
+#define SMB_negrLM_sk_offset 45 /* session key, 32 bits */
+#define SMB_negrLM_st_offset 49 /* Current server time */
+#define SMB_negrLM_sd_offset 51 /* Current server date */
+#define SMB_negrLM_stz_offset 53 /* Server Time Zone */
+#define SMB_negrLM_ekl_offset 55 /* encryption key length */
+#define SMB_negrLM_res_offset 57 /* reserved */
+#define SMB_negrLM_bcc_offset 59 /* bcc */
+#define SMB_negrLM_len 61 /* 61 bytes ? */
+#define SMB_negrLM_buf_offset 61 /* Where the fun begins */
+
+#define SMB_negrNTLM_idx_offset 33 /* Selected protocol */
+#define SMB_negrNTLM_sec_offset 35 /* Security more */
+#define SMB_negrNTLM_mmc_offset 36 /* Different format above */
+#define SMB_negrNTLM_mnv_offset 38 /* Max VCs */
+#define SMB_negrNTLM_mbs_offset 40 /* MBS now a long */
+#define SMB_negrNTLM_mrs_offset 44 /* Max raw size */
+#define SMB_negrNTLM_sk_offset 48 /* Session Key */
+#define SMB_negrNTLM_cap_offset 52 /* Capabilities */
+#define SMB_negrNTLM_stl_offset 56 /* Server time low */
+#define SMB_negrNTLM_sth_offset 60 /* Server time high */
+#define SMB_negrNTLM_stz_offset 64 /* Server time zone */
+#define SMB_negrNTLM_ekl_offset 66 /* Encrypt key len */
+#define SMB_negrNTLM_bcc_offset 67 /* Bcc */
+#define SMB_negrNTLM_len 69
+#define SMB_negrNTLM_buf_offset 69
+
+/* Offsets for Delete file */
+
+#define SMB_delet_sat_offset 33 /* search attribites */
+#define SMB_delet_bcc_offset 35 /* bcc */
+#define SMB_delet_buf_offset 37
+#define SMB_delet_len 37
+
+/* Offsets for SESSION_SETUP_ANDX for both LM and NT LM protocols */
+
+#define SMB_ssetpLM_mbs_offset 37 /* Max buffer Size, allow for AndX */
+#define SMB_ssetpLM_mmc_offset 39 /* max multiplex count */
+#define SMB_ssetpLM_vcn_offset 41 /* VC number if new VC */
+#define SMB_ssetpLM_snk_offset 43 /* Session Key */
+#define SMB_ssetpLM_pwl_offset 47 /* password length */
+#define SMB_ssetpLM_res_offset 49 /* reserved */
+#define SMB_ssetpLM_bcc_offset 53 /* bcc */
+#define SMB_ssetpLM_len 55 /* before data ... */
+#define SMB_ssetpLM_buf_offset 55
+
+#define SMB_ssetpNTLM_mbs_offset 37 /* Max Buffer Size for NT LM 0.12 */
+ /* and above */
+#define SMB_ssetpNTLM_mmc_offset 39 /* Max Multiplex count */
+#define SMB_ssetpNTLM_vcn_offset 41 /* VC Number */
+#define SMB_ssetpNTLM_snk_offset 43 /* Session key */
+#define SMB_ssetpNTLM_cipl_offset 47 /* Case Insensitive PW Len */
+#define SMB_ssetpNTLM_cspl_offset 49 /* Unicode pw len */
+#define SMB_ssetpNTLM_res_offset 51 /* reserved */
+#define SMB_ssetpNTLM_cap_offset 55 /* server capabilities */
+#define SMB_ssetpNTLM_bcc_offset 59 /* bcc */
+#define SMB_ssetpNTLM_len 61 /* before data */
+#define SMB_ssetpNTLM_buf_offset 61
+
+#define SMB_ssetpr_axo_offset 35 /* Offset of next response ... */
+#define SMB_ssetpr_act_offset 37 /* action, bit 0 = 1 => guest */
+#define SMB_ssetpr_bcc_offset 39 /* bcc */
+#define SMB_ssetpr_buf_offset 41 /* Native OS etc */
+
+/* The following two arrays need to be in step! */
+/* We must make it possible for callers to specify these ... */
+
+extern const char *SMB_Prots[];
+extern int SMB_Types[];
+
+typedef struct SMB_Connect_Def * SMB_Handle_Type;
+
+struct SMB_Connect_Def {
+
+ SMB_Handle_Type Next_Con, Prev_Con; /* Next and previous conn */
+ int protocol; /* What is the protocol */
+ int prot_IDX; /* And what is the index */
+ void *Trans_Connect; /* The connection */
+
+ /* All these strings should be malloc'd */
+
+ char service[80], username[80], password[80], desthost[80], sock_options[80];
+ char address[80], myname[80];
+
+ int gid; /* Group ID, do we need it? */
+ int mid; /* Multiplex ID? We might need one per con */
+ int pid; /* Process ID */
+
+ int uid; /* Authenticated user id. */
+
+ /* It is pretty clear that we need to bust some of */
+ /* these out into a per TCon record, as there may */
+ /* be multiple TCon's per server, etc ... later */
+
+ int port; /* port to use in case not default, this is a TCPism! */
+
+ int max_xmit; /* Max xmit permitted by server */
+ int Security; /* 0 = share, 1 = user */
+ int Raw_Support; /* bit 0 = 1 = Read Raw supported, 1 = 1 Write raw */
+ bool encrypt_passwords; /* false = don't */
+ int MaxMPX, MaxVC, MaxRaw;
+ unsigned int SessionKey, Capabilities;
+ int SvrTZ; /* Server Time Zone */
+ int Encrypt_Key_Len;
+ char Encrypt_Key[80], Domain[80], PDomain[80], OSName[80], LMType[40];
+ char Svr_OS[80], Svr_LMType[80], Svr_PDom[80];
+
+};
+
+#define SMBLIB_DEFAULT_OSNAME "UNIX of some type"
+#define SMBLIB_DEFAULT_LMTYPE "SMBlib LM2.1 minus a bit"
+#define SMBLIB_MAX_XMIT 65535
+
+/* global Variables for the library */
+
+#ifndef SMBLIB_ERRNO
+extern int SMBlib_errno;
+extern int SMBlib_SMB_Error; /* last Error */
+#endif
+
+/* From smbdes.c. */
+void E_P16(unsigned char *, unsigned char *);
+void E_P24(unsigned char *, unsigned char *, unsigned char *);
+
+/* From smblib-util.c. */
+void SMB_Get_My_Name(char *name, int len);
+
+/* From smbencrypt.c. */
+void SMBencrypt(unsigned char *passwd, unsigned char *, unsigned char *);
--- /dev/null
+/* UNIX SMBlib NetBIOS implementation
+
+ Version 1.0
+ SMBlib Utility Routines
+
+ Copyright (C) Richard Sharpe 1996
+
+*/
+
+/*
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 2 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program; if not, write to the Free Software
+ Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+*/
+
+#include "config.h"
+#include "clibrary.h"
+
+#include "smblib-priv.h"
+#include "rfcnb.h"
+
+/* The following two arrays need to be in step! */
+/* We must make it possible for callers to specify these ... */
+
+const char *SMB_Prots[] = {"PC NETWORK PROGRAM 1.0",
+ "MICROSOFT NETWORKS 1.03",
+ "MICROSOFT NETWORKS 3.0",
+ "DOS LANMAN1.0",
+ "LANMAN1.0",
+ "DOS LM1.2X002",
+ "LM1.2X002",
+ "DOS LANMAN2.1",
+ "LANMAN2.1",
+ "Samba",
+ "NT LM 0.12",
+ "NT LANMAN 1.0",
+ NULL};
+
+int SMB_Types[] = {SMB_P_Core,
+ SMB_P_CorePlus,
+ SMB_P_DOSLanMan1,
+ SMB_P_DOSLanMan1,
+ SMB_P_LanMan1,
+ SMB_P_DOSLanMan2,
+ SMB_P_LanMan2,
+ SMB_P_LanMan2_1,
+ SMB_P_LanMan2_1,
+ SMB_P_NT1,
+ SMB_P_NT1,
+ SMB_P_NT1,
+ -1};
+
+/* Figure out what protocol was accepted, given the list of dialect strings */
+/* We offered, and the index back from the server. We allow for a user */
+/* supplied list, and assume that it is a subset of our list */
+
+int SMB_Figure_Protocol(const char *dialects[], int prot_index)
+
+{ int i;
+
+ if (dialects == SMB_Prots) { /* The jobs is easy, just index into table */
+
+ return(SMB_Types[prot_index]);
+ }
+ else { /* Search through SMB_Prots looking for a match */
+
+ for (i = 0; SMB_Prots[i] != NULL; i++) {
+
+ if (strcmp(dialects[prot_index], SMB_Prots[i]) == 0) { /* A match */
+
+ return(SMB_Types[i]);
+
+ }
+
+ }
+
+ /* If we got here, then we are in trouble, because the protocol was not */
+ /* One we understand ... */
+
+ return(SMB_P_Unknown);
+
+ }
+
+}
+
+
+/* Negotiate the protocol we will use from the list passed in Prots */
+/* we return the index of the accepted protocol in NegProt, -1 indicates */
+/* none acceptible, and our return value is 0 if ok, <0 if problems */
+
+int SMB_Negotiate(SMB_Handle_Type Con_Handle, const char *Prots[])
+{
+ struct RFCNB_Pkt *pkt;
+ int prots_len, i, pkt_len, prot, alloc_len;
+ char *p;
+
+ /* Figure out how long the prot list will be and allocate space for it */
+
+ prots_len = 0;
+
+ for (i = 0; Prots[i] != NULL; i++) {
+
+ prots_len = prots_len + strlen(Prots[i]) + 2; /* Account for null etc */
+
+ }
+
+ /* The -1 accounts for the one byte smb_buf we have because some systems */
+ /* don't like char msg_buf[] */
+
+ pkt_len = SMB_negp_len + prots_len;
+
+ /* Make sure that the pkt len is long enough for the max response ... */
+ /* Which is a problem, because the encryption key len eec may be long */
+
+ if (pkt_len < (SMB_hdr_wct_offset + (19 * 2) + 40)) {
+
+ alloc_len = SMB_hdr_wct_offset + (19 * 2) + 40;
+
+ }
+ else {
+
+ alloc_len = pkt_len;
+
+ }
+
+ pkt = (struct RFCNB_Pkt *)RFCNB_Alloc_Pkt(alloc_len);
+
+ if (pkt == NULL) {
+
+ SMBlib_errno = SMBlibE_NoSpace;
+ return(SMBlibE_BAD);
+
+ }
+
+ /* Now plug in the bits we need */
+
+ memset(SMB_Hdr(pkt), 0, SMB_negp_len);
+ SIVAL(SMB_Hdr(pkt), SMB_hdr_idf_offset, SMB_DEF_IDF); /* Plunk in IDF */
+ *(SMB_Hdr(pkt) + SMB_hdr_com_offset) = SMBnegprot;
+ SSVAL(SMB_Hdr(pkt), SMB_hdr_pid_offset, Con_Handle -> pid);
+ SSVAL(SMB_Hdr(pkt), SMB_hdr_tid_offset, 0);
+ SSVAL(SMB_Hdr(pkt), SMB_hdr_mid_offset, Con_Handle -> mid);
+ SSVAL(SMB_Hdr(pkt), SMB_hdr_uid_offset, Con_Handle -> uid);
+ *(SMB_Hdr(pkt) + SMB_hdr_wct_offset) = 0;
+
+ SSVAL(SMB_Hdr(pkt), SMB_negp_bcc_offset, prots_len);
+
+ /* Now copy the prot strings in with the right stuff */
+
+ p = (char *)(SMB_Hdr(pkt) + SMB_negp_buf_offset);
+
+ for (i = 0; Prots[i] != NULL; i++) {
+
+ *p = SMBdialectID;
+ strcpy(p + 1, Prots[i]);
+ p = p + strlen(Prots[i]) + 2; /* Adjust len of p for null plus dialectID */
+
+ }
+
+ /* Now send the packet and sit back ... */
+
+ if (RFCNB_Send(Con_Handle -> Trans_Connect, pkt, pkt_len) < 0){
+
+
+#ifdef DEBUG
+ fprintf(stderr, "Error sending negotiate protocol\n");
+#endif
+
+ RFCNB_Free_Pkt(pkt);
+ SMBlib_errno = -SMBlibE_SendFailed; /* Failed, check lower layer errno */
+ return(SMBlibE_BAD);
+
+ }
+
+ /* Now get the response ... */
+
+ if (RFCNB_Recv(Con_Handle -> Trans_Connect, pkt, alloc_len) < 0) {
+
+#ifdef DEBUG
+ fprintf(stderr, "Error receiving response to negotiate\n");
+#endif
+
+ RFCNB_Free_Pkt(pkt);
+ SMBlib_errno = -SMBlibE_RecvFailed; /* Failed, check lower layer errno */
+ return(SMBlibE_BAD);
+
+ }
+
+ if (CVAL(SMB_Hdr(pkt), SMB_hdr_rcls_offset) != SMBC_SUCCESS) { /* Process error */
+
+#ifdef DEBUG
+ fprintf(stderr, "SMB_Negotiate failed with errorclass = %i, Error Code = %i\n",
+ CVAL(SMB_Hdr(pkt), SMB_hdr_rcls_offset),
+ SVAL(SMB_Hdr(pkt), SMB_hdr_err_offset));
+#endif
+
+ SMBlib_SMB_Error = IVAL(SMB_Hdr(pkt), SMB_hdr_rcls_offset);
+ RFCNB_Free_Pkt(pkt);
+ SMBlib_errno = SMBlibE_Remote;
+ return(SMBlibE_BAD);
+
+ }
+
+ if (SVAL(SMB_Hdr(pkt), SMB_negrCP_idx_offset) == 0xFFFF) {
+
+#ifdef DEBUG
+ fprintf(stderr, "None of our protocols was accepted ... ");
+#endif
+
+ RFCNB_Free_Pkt(pkt);
+ SMBlib_errno = SMBlibE_NegNoProt;
+ return(SMBlibE_BAD);
+
+ }
+
+ /* Now, unpack the info from the response, if any and evaluate the proto */
+ /* selected. We must make sure it is one we like ... */
+
+ Con_Handle -> prot_IDX = prot = SVAL(SMB_Hdr(pkt), SMB_negrCP_idx_offset);
+ Con_Handle -> protocol = SMB_Figure_Protocol(Prots, prot);
+
+ if (Con_Handle -> protocol == SMB_P_Unknown) { /* No good ... */
+
+ RFCNB_Free_Pkt(pkt);
+ SMBlib_errno = SMBlibE_ProtUnknown;
+ return(SMBlibE_BAD);
+
+ }
+
+ switch (CVAL(SMB_Hdr(pkt), SMB_hdr_wct_offset)) {
+
+ case 0x01: /* No more info ... */
+
+ break;
+
+ case 13: /* Up to and including LanMan 2.1 */
+
+ Con_Handle -> Security = SVAL(SMB_Hdr(pkt), SMB_negrLM_sec_offset);
+ Con_Handle -> encrypt_passwords = ((Con_Handle -> Security & SMB_sec_encrypt_mask) != 0x00);
+ Con_Handle -> Security = Con_Handle -> Security & SMB_sec_user_mask;
+
+ Con_Handle -> max_xmit = SVAL(SMB_Hdr(pkt), SMB_negrLM_mbs_offset);
+ Con_Handle -> MaxMPX = SVAL(SMB_Hdr(pkt), SMB_negrLM_mmc_offset);
+ Con_Handle -> MaxVC = SVAL(SMB_Hdr(pkt), SMB_negrLM_mnv_offset);
+ Con_Handle -> Raw_Support = SVAL(SMB_Hdr(pkt), SMB_negrLM_rm_offset);
+ Con_Handle -> SessionKey = IVAL(SMB_Hdr(pkt), SMB_negrLM_sk_offset);
+ Con_Handle -> SvrTZ = SVAL(SMB_Hdr(pkt), SMB_negrLM_stz_offset);
+ Con_Handle -> Encrypt_Key_Len = SVAL(SMB_Hdr(pkt), SMB_negrLM_ekl_offset);
+
+ p = (SMB_Hdr(pkt) + SMB_negrLM_buf_offset);
+ fprintf(stderr, "%p", (char *)(SMB_Hdr(pkt) + SMB_negrLM_buf_offset));
+ memcpy(Con_Handle->Encrypt_Key, p, 8);
+
+ p = (SMB_Hdr(pkt) + SMB_negrLM_buf_offset + Con_Handle -> Encrypt_Key_Len);
+
+ strncpy(p, Con_Handle -> Svr_PDom, sizeof(Con_Handle -> Svr_PDom) - 1);
+
+ break;
+
+ case 17: /* NT LM 0.12 and LN LM 1.0 */
+
+ Con_Handle -> Security = SVAL(SMB_Hdr(pkt), SMB_negrNTLM_sec_offset);
+ Con_Handle -> encrypt_passwords = ((Con_Handle -> Security & SMB_sec_encrypt_mask) != 0x00);
+ Con_Handle -> Security = Con_Handle -> Security & SMB_sec_user_mask;
+
+ Con_Handle -> max_xmit = IVAL(SMB_Hdr(pkt), SMB_negrNTLM_mbs_offset);
+ Con_Handle -> MaxMPX = SVAL(SMB_Hdr(pkt), SMB_negrNTLM_mmc_offset);
+ Con_Handle -> MaxVC = SVAL(SMB_Hdr(pkt), SMB_negrNTLM_mnv_offset);
+ Con_Handle -> MaxRaw = IVAL(SMB_Hdr(pkt), SMB_negrNTLM_mrs_offset);
+ Con_Handle -> SessionKey = IVAL(SMB_Hdr(pkt), SMB_negrNTLM_sk_offset);
+ Con_Handle -> SvrTZ = SVAL(SMB_Hdr(pkt), SMB_negrNTLM_stz_offset);
+ Con_Handle -> Encrypt_Key_Len = CVAL(SMB_Hdr(pkt), SMB_negrNTLM_ekl_offset);
+
+ p = (SMB_Hdr(pkt) + SMB_negrNTLM_buf_offset );
+ memcpy(Con_Handle -> Encrypt_Key, p, 8);
+ p = (SMB_Hdr(pkt) + SMB_negrNTLM_buf_offset + Con_Handle -> Encrypt_Key_Len);
+
+ strncpy(p, Con_Handle -> Svr_PDom, sizeof(Con_Handle -> Svr_PDom) - 1);
+
+ break;
+
+ default:
+
+#ifdef DEBUG
+ fprintf(stderr, "Unknown NegProt response format ... Ignored\n");
+ fprintf(stderr, " wct = %i\n", CVAL(SMB_Hdr(pkt), SMB_hdr_wct_offset));
+#endif
+
+ break;
+ }
+
+#ifdef DEBUG
+ fprintf(stderr, "Protocol selected is: %i:%s\n", prot, Prots[prot]);
+#endif
+
+ RFCNB_Free_Pkt(pkt);
+ return(0);
+
+}
+
+/* Get our hostname */
+
+void SMB_Get_My_Name(char *name, int len)
+
+{
+ if (gethostname(name, len) < 0) { /* Error getting name */
+
+ strncpy(name, "unknown", len);
+
+ /* Should check the error */
+
+#ifdef DEBUG
+ fprintf(stderr, "gethostname in SMB_Get_My_Name returned error:");
+ perror("");
+#endif
+
+ }
+
+ /* only keep the portion up to the first "." */
+
+
+}
--- /dev/null
+/* UNIX SMBlib NetBIOS implementation
+
+ Version 1.0
+ SMBlib Routines
+
+ Copyright (C) Richard Sharpe 1996
+
+*/
+
+/*
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 2 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program; if not, write to the Free Software
+ Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+*/
+
+#include "config.h"
+#include "clibrary.h"
+#include <ctype.h>
+#include <signal.h>
+
+int SMBlib_errno;
+int SMBlib_SMB_Error;
+#define SMBLIB_ERRNO
+typedef unsigned char uchar;
+#include "smblib-priv.h"
+
+#include "rfcnb.h"
+
+/* Initialize the SMBlib package */
+
+int SMB_Init()
+
+{
+ signal(SIGPIPE, SIG_IGN); /* Ignore these ... */
+
+ return 0;
+
+}
+
+int SMB_Term()
+
+{
+
+ return 0;
+
+}
+
+/* SMB_Connect_Server: Connect to a server, but don't negotiate protocol */
+/* or anything else ... */
+
+SMB_Handle_Type SMB_Connect_Server(SMB_Handle_Type Con_Handle,
+ char *server, char *NTdomain)
+
+{ SMB_Handle_Type con;
+ char called[80], calling[80], *address;
+ int i;
+
+ /* Get a connection structure if one does not exist */
+
+ con = Con_Handle;
+
+ if (Con_Handle == NULL) {
+
+ if ((con = (struct SMB_Connect_Def *)malloc(sizeof(struct SMB_Connect_Def))) == NULL) {
+
+
+ SMBlib_errno = SMBlibE_NoSpace;
+ return NULL;
+ }
+
+ }
+
+ /* Init some things ... */
+
+ strlcpy(con->service, "", sizeof(con->service));
+ strlcpy(con->username, "", sizeof(con->username));
+ strlcpy(con->password, "", sizeof(con->password));
+ strlcpy(con->sock_options, "", sizeof(con->sock_options));
+ strlcpy(con->address, "", sizeof(con->address));
+ strlcpy(con->desthost, server, sizeof(con->desthost));
+ strlcpy(con->PDomain, NTdomain, sizeof(con->PDomain));
+ strlcpy(con->OSName, SMBLIB_DEFAULT_OSNAME, sizeof(con->OSName));
+ strlcpy(con->LMType, SMBLIB_DEFAULT_LMTYPE, sizeof(con->LMType));
+
+ SMB_Get_My_Name(con -> myname, sizeof(con -> myname));
+
+ con -> port = 0; /* No port selected */
+
+ /* Get some things we need for the SMB Header */
+
+ con -> pid = getpid();
+ con -> mid = con -> pid; /* This will do for now ... */
+ con -> uid = 0; /* Until we have done a logon, no uid ... */
+ con -> gid = getgid();
+
+ /* Now connect to the remote end, but first upper case the name of the
+ service we are going to call, sine some servers want it in uppercase */
+
+ for (i=0; i < strlen(server); i++)
+ called[i] = toupper(server[i]);
+
+ called[strlen(server)] = 0; /* Make it a string */
+
+ for (i=0; i < strlen(con -> myname); i++)
+ calling[i] = toupper(con -> myname[i]);
+
+ calling[strlen(con -> myname)] = 0; /* Make it a string */
+
+ if (strcmp(con -> address, "") == 0)
+ address = con -> desthost;
+ else
+ address = con -> address;
+
+ con -> Trans_Connect = RFCNB_Call(called,
+ calling,
+ address, /* Protocol specific */
+ con -> port);
+
+ /* Did we get one? */
+
+ if (con -> Trans_Connect == NULL) {
+
+ if (Con_Handle == NULL) {
+ Con_Handle = NULL;
+ free(con);
+ }
+ SMBlib_errno = -SMBlibE_CallFailed;
+ return NULL;
+
+ }
+
+ return(con);
+
+}
+
+/* Logon to the server. That is, do a session setup if we can. We do not do */
+/* Unicode yet! */
+
+int SMB_Logon_Server(SMB_Handle_Type Con_Handle, char *UserName,
+ char *PassWord)
+
+{ struct RFCNB_Pkt *pkt;
+ int param_len, pkt_len, pass_len;
+ char *p, pword[128];
+
+ /* First we need a packet etc ... but we need to know what protocol has */
+ /* been negotiated to figure out if we can do it and what SMB format to */
+ /* use ... */
+
+ if (Con_Handle -> protocol < SMB_P_LanMan1) {
+
+ SMBlib_errno = SMBlibE_ProtLow;
+ return(SMBlibE_BAD);
+
+ }
+
+ strlcpy(pword, PassWord, sizeof(pword));
+ if (Con_Handle -> encrypt_passwords)
+ {
+ pass_len=24;
+ SMBencrypt((uchar *) PassWord, (uchar *)Con_Handle -> Encrypt_Key,(uchar *)pword);
+ }
+ else
+ pass_len=strlen(pword);
+
+
+ /* Now build the correct structure */
+
+ if (Con_Handle -> protocol < SMB_P_NT1) {
+
+ param_len = strlen(UserName) + 1 + pass_len + 1 +
+ strlen(Con_Handle -> PDomain) + 1 +
+ strlen(Con_Handle -> OSName) + 1;
+
+ pkt_len = SMB_ssetpLM_len + param_len;
+
+ pkt = (struct RFCNB_Pkt *)RFCNB_Alloc_Pkt(pkt_len);
+
+ if (pkt == NULL) {
+
+ SMBlib_errno = SMBlibE_NoSpace;
+ return(SMBlibE_BAD); /* Should handle the error */
+
+ }
+
+ memset(SMB_Hdr(pkt), 0, SMB_ssetpLM_len);
+ SIVAL(SMB_Hdr(pkt), SMB_hdr_idf_offset, SMB_DEF_IDF); /* Plunk in IDF */
+ *(SMB_Hdr(pkt) + SMB_hdr_com_offset) = SMBsesssetupX;
+ SSVAL(SMB_Hdr(pkt), SMB_hdr_pid_offset, Con_Handle -> pid);
+ SSVAL(SMB_Hdr(pkt), SMB_hdr_tid_offset, 0);
+ SSVAL(SMB_Hdr(pkt), SMB_hdr_mid_offset, Con_Handle -> mid);
+ SSVAL(SMB_Hdr(pkt), SMB_hdr_uid_offset, Con_Handle -> uid);
+ *(SMB_Hdr(pkt) + SMB_hdr_wct_offset) = 10;
+ *(SMB_Hdr(pkt) + SMB_hdr_axc_offset) = 0xFF; /* No extra command */
+ SSVAL(SMB_Hdr(pkt), SMB_hdr_axo_offset, 0);
+
+ SSVAL(SMB_Hdr(pkt), SMB_ssetpLM_mbs_offset, SMBLIB_MAX_XMIT);
+ SSVAL(SMB_Hdr(pkt), SMB_ssetpLM_mmc_offset, 2);
+ SSVAL(SMB_Hdr(pkt), SMB_ssetpLM_vcn_offset, Con_Handle -> pid);
+ SIVAL(SMB_Hdr(pkt), SMB_ssetpLM_snk_offset, 0);
+ SSVAL(SMB_Hdr(pkt), SMB_ssetpLM_pwl_offset, pass_len + 1);
+ SIVAL(SMB_Hdr(pkt), SMB_ssetpLM_res_offset, 0);
+ SSVAL(SMB_Hdr(pkt), SMB_ssetpLM_bcc_offset, param_len);
+
+ /* Now copy the param strings in with the right stuff */
+
+ p = (char *)(SMB_Hdr(pkt) + SMB_ssetpLM_buf_offset);
+
+ /* Copy in password, then the rest. Password has a null at end */
+
+ memcpy(p, pword, pass_len);
+
+ p = p + pass_len + 1;
+
+ strcpy(p, UserName);
+ p = p + strlen(UserName);
+ *p = 0;
+
+ p = p + 1;
+
+ strcpy(p, Con_Handle -> PDomain);
+ p = p + strlen(Con_Handle -> PDomain);
+ *p = 0;
+ p = p + 1;
+
+ strcpy(p, Con_Handle -> OSName);
+ p = p + strlen(Con_Handle -> OSName);
+ *p = 0;
+
+ }
+ else {
+
+ /* We don't admit to UNICODE support ... */
+
+ param_len = strlen(UserName) + 1 + pass_len +
+ strlen(Con_Handle -> PDomain) + 1 +
+ strlen(Con_Handle -> OSName) + 1 +
+ strlen(Con_Handle -> LMType) + 1;
+
+ pkt_len = SMB_ssetpNTLM_len + param_len;
+
+ pkt = (struct RFCNB_Pkt *)RFCNB_Alloc_Pkt(pkt_len);
+
+ if (pkt == NULL) {
+
+ SMBlib_errno = SMBlibE_NoSpace;
+ return(-1); /* Should handle the error */
+
+ }
+
+ memset(SMB_Hdr(pkt), 0, SMB_ssetpNTLM_len);
+ SIVAL(SMB_Hdr(pkt), SMB_hdr_idf_offset, SMB_DEF_IDF); /* Plunk in IDF */
+ *(SMB_Hdr(pkt) + SMB_hdr_com_offset) = SMBsesssetupX;
+ SSVAL(SMB_Hdr(pkt), SMB_hdr_pid_offset, Con_Handle -> pid);
+ SSVAL(SMB_Hdr(pkt), SMB_hdr_tid_offset, 0);
+ SSVAL(SMB_Hdr(pkt), SMB_hdr_mid_offset, Con_Handle -> mid);
+ SSVAL(SMB_Hdr(pkt), SMB_hdr_uid_offset, Con_Handle -> uid);
+ *(SMB_Hdr(pkt) + SMB_hdr_wct_offset) = 13;
+ *(SMB_Hdr(pkt) + SMB_hdr_axc_offset) = 0xFF; /* No extra command */
+ SSVAL(SMB_Hdr(pkt), SMB_hdr_axo_offset, 0);
+
+ SSVAL(SMB_Hdr(pkt), SMB_ssetpNTLM_mbs_offset, SMBLIB_MAX_XMIT);
+ SSVAL(SMB_Hdr(pkt), SMB_ssetpNTLM_mmc_offset, 0);
+ SSVAL(SMB_Hdr(pkt), SMB_ssetpNTLM_vcn_offset, 0);
+ SIVAL(SMB_Hdr(pkt), SMB_ssetpNTLM_snk_offset, 0);
+ SSVAL(SMB_Hdr(pkt), SMB_ssetpNTLM_cipl_offset, pass_len);
+ SSVAL(SMB_Hdr(pkt), SMB_ssetpNTLM_cspl_offset, 0);
+ SIVAL(SMB_Hdr(pkt), SMB_ssetpNTLM_res_offset, 0);
+ SIVAL(SMB_Hdr(pkt), SMB_ssetpNTLM_cap_offset, 0);
+ SSVAL(SMB_Hdr(pkt), SMB_ssetpNTLM_bcc_offset, param_len);
+
+ /* Now copy the param strings in with the right stuff */
+
+ p = (char *)(SMB_Hdr(pkt) + SMB_ssetpNTLM_buf_offset);
+
+ /* Copy in password, then the rest. Password has no null at end */
+
+ memcpy(p, pword, pass_len);
+
+ p = p + pass_len;
+
+ strcpy(p, UserName);
+ p = p + strlen(UserName);
+ *p = 0;
+
+ p = p + 1;
+
+ strcpy(p, Con_Handle -> PDomain);
+ p = p + strlen(Con_Handle -> PDomain);
+ *p = 0;
+ p = p + 1;
+
+ strcpy(p, Con_Handle -> OSName);
+ p = p + strlen(Con_Handle -> OSName);
+ *p = 0;
+ p = p + 1;
+
+ strcpy(p, Con_Handle -> LMType);
+ p = p + strlen(Con_Handle -> LMType);
+ *p = 0;
+
+ }
+
+ /* Now send it and get a response */
+
+ if (RFCNB_Send(Con_Handle -> Trans_Connect, pkt, pkt_len) < 0){
+
+ RFCNB_Free_Pkt(pkt);
+ SMBlib_errno = SMBlibE_SendFailed;
+ return(SMBlibE_BAD);
+
+ }
+
+ /* Now get the response ... */
+
+ if (RFCNB_Recv(Con_Handle -> Trans_Connect, pkt, pkt_len) < 0) {
+
+ RFCNB_Free_Pkt(pkt);
+ SMBlib_errno = SMBlibE_RecvFailed;
+ return(SMBlibE_BAD);
+
+ }
+
+ /* Check out the response type ... */
+
+ if (CVAL(SMB_Hdr(pkt), SMB_hdr_rcls_offset) != SMBC_SUCCESS) { /* Process error */
+
+ SMBlib_SMB_Error = IVAL(SMB_Hdr(pkt), SMB_hdr_rcls_offset);
+ RFCNB_Free_Pkt(pkt);
+ SMBlib_errno = SMBlibE_Remote;
+ return(SMBlibE_BAD);
+
+ }
+/** @@@ mdz: check for guest login { **/
+ if (SVAL(SMB_Hdr(pkt), SMB_ssetpr_act_offset) & 0x1)
+ {
+ /* do we allow guest login? NO! */
+ return(SMBlibE_BAD);
+
+ }
+ /** @@@ mdz: } **/
+
+
+ /* Now pick up the UID for future reference ... */
+
+ Con_Handle -> uid = SVAL(SMB_Hdr(pkt), SMB_hdr_uid_offset);
+ RFCNB_Free_Pkt(pkt);
+
+ return(0);
+
+}
+
+
+/* Disconnect from the server, and disconnect all tree connects */
+
+int SMB_Discon(SMB_Handle_Type Con_Handle, bool KeepHandle)
+
+{
+
+ /* We just disconnect the connection for now ... */
+
+ RFCNB_Hangup(Con_Handle -> Trans_Connect);
+
+ if (!KeepHandle)
+ free(Con_Handle);
+
+ return(0);
+
+}
--- /dev/null
+/* UNIX SMBlib NetBIOS implementation
+
+ Version 1.0
+ SMBlib Defines
+
+ Copyright (C) Richard Sharpe 1996
+
+*/
+
+/*
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 2 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program; if not, write to the Free Software
+ Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+*/
+
+#include "smblib-common.h"
+
+/* Just define all the entry points */
+
+/* Initialize the library. */
+
+int SMB_Init(void);
+
+/* Connect to a server, but do not do a tree con etc ... */
+
+void *SMB_Connect_Server(void *Con, char *server, char *NTdomain);
+
+/* Negotiate a protocol */
+
+int SMB_Negotiate(void *Con_Handle, char *Prots[]);
+
+/* Disconnect from server. Has flag to specify whether or not we keep the */
+/* handle. */
+
+int SMB_Discon(void *Con, bool KeepHandle);
+
+/* Log on to a server. */
+
+int SMB_Logon_Server(SMB_Handle_Type Con_Handle, char *UserName,
+ char *PassWord);
--- /dev/null
+#include <sys/types.h>
+#include <unistd.h>
+#include <syslog.h>
+#include "config.h"
+#include "smblib-priv.h"
+#include "smblib.h"
+#include "valid.h"
+
+int Valid_User(char *USERNAME,char *PASSWORD,char *SERVER,char *BACKUP, char *DOMAIN)
+{
+ char *SMB_Prots[] = {"PC NETWORK PROGRAM 1.0",
+ "MICROSOFT NETWORKS 1.03",
+ "MICROSOFT NETWORKS 3.0",
+ "LANMAN1.0",
+ "LM1.2X002",
+ "Samba",
+ "NT LM 0.12",
+ "NT LANMAN 1.0",
+ NULL};
+ SMB_Handle_Type con;
+
+ SMB_Init();
+ con = SMB_Connect_Server(NULL, SERVER, DOMAIN);
+ if (con == NULL) { /* Error ... */
+ con = SMB_Connect_Server(NULL, BACKUP, DOMAIN);
+ if (con == NULL) {
+ return(NTV_SERVER_ERROR);
+ }
+ }
+ if (SMB_Negotiate(con, SMB_Prots) < 0) { /* An error */
+ SMB_Discon(con,0);
+ return(NTV_PROTOCOL_ERROR);
+ }
+ /* Test for a server in share level mode do not authenticate against it */
+ if (con -> Security == 0)
+ {
+ SMB_Discon(con,0);
+ return(NTV_PROTOCOL_ERROR);
+ }
+
+ if (SMB_Logon_Server(con, USERNAME, PASSWORD) < 0) {
+ SMB_Discon(con,0);
+ return(NTV_LOGON_ERROR);
+ }
+
+ SMB_Discon(con,0);
+ return(NTV_NO_ERROR);
+}
--- /dev/null
+#ifndef _VALID_H_
+#define _VALID_H_
+/* SMB User verification function */
+
+#define NTV_NO_ERROR 0
+#define NTV_SERVER_ERROR 1
+#define NTV_PROTOCOL_ERROR 2
+#define NTV_LOGON_ERROR 3
+
+int Valid_User(char *USERNAME,char *PASSWORD,char *SERVER, char *BACKUP, char *DOMAIN);
+
+#endif
--- /dev/null
+## $Id: Makefile 7734 2008-04-06 09:25:56Z iulius $
+
+include ../Makefile.global
+
+top = ..
+CFLAGS = $(GCFLAGS)
+
+ALL = actmerge actsync actsyncd archive batcher buffchan \
+ cvtbatch filechan inndf innxmit innxbatch mod-active \
+ news2mail ninpaths nntpget nntpsend overchan send-ihave \
+ send-nntp send-uucp sendinpaths sendxbatches shlock \
+ shrinkfile
+
+MAN = ../doc/man/send-uucp.8
+
+SOURCES = actsync.c archive.c batcher.c buffchan.c cvtbatch.c \
+ filechan.c inndf.c innxbatch.c innxmit.c map.c ninpaths.c \
+ nntpget.c overchan.c shlock.c shrinkfile.c
+
+all: $(ALL)
+
+man: $(MAN)
+
+warnings:
+ $(MAKE) COPT='$(WARNINGS)' all
+
+install: all
+ for F in actmerge actsyncd news2mail nntpsend send-ihave send-nntp \
+ send-uucp sendinpaths sendxbatches ; do \
+ $(CP_XPUB) $$F $D$(PATHBIN)/$$F ; \
+ done
+ $(CP_XPRI) mod-active $D$(PATHBIN)/mod-active
+ $(LI_XPRI) overchan $D$(PATHBIN)/overchan
+ for F in actsync archive batcher buffchan cvtbatch filechan inndf \
+ innxbatch innxmit ninpaths nntpget shlock shrinkfile ; do \
+ $(LI_XPUB) $$F $D$(PATHBIN)/$$F ; \
+ done
+
+clean:
+ rm -f *.o $(ALL)
+ rm -rf .libs
+
+clobber distclean: clean
+ rm -f tags
+
+tags ctags: $(SOURCES)
+ $(CTAGS) $(SOURCES)
+
+profiled:
+ $(MAKEPROFILING) all
+
+## Compilation rules.
+
+BOTH = $(LIBSTORAGE) $(LIBHIST) $(LIBSTORAGE) $(LIBINN)
+
+LINK = $(LIBLD) $(LDFLAGS) -o $@
+INNLIBS = $(LIBINN) $(LIBS)
+STORELIBS = $(BOTH) $(EXTSTORAGELIBS) $(LIBS)
+
+FIX = $(FIXSCRIPT)
+
+$(FIXSCRIPT):
+ @echo Run configure before running make. See INSTALL for details.
+ @exit 1
+
+actsync: actsync.o $(LIBINN) ; $(LINK) actsync.o $(INNLIBS)
+archive: archive.o $(BOTH) ; $(LINK) archive.o $(STORELIBS)
+batcher: batcher.o $(BOTH) ; $(LINK) batcher.o $(STORELIBS)
+cvtbatch: cvtbatch.o $(BOTH) ; $(LINK) cvtbatch.o $(STORELIBS)
+inndf: inndf.o $(BOTH) ; $(LINK) inndf.o $(STORELIBS)
+innxbatch: innxbatch.o $(LIBINN) ; $(LINK) innxbatch.o $(INNLIBS)
+innxmit: innxmit.o $(BOTH) ; $(LINK) innxmit.o $(STORELIBS)
+ninpaths: ninpaths.o ; $(LINK) ninpaths.o
+nntpget: nntpget.o $(BOTH) ; $(LINK) nntpget.o $(STORELIBS)
+overchan: overchan.o $(BOTH) ; $(LINK) overchan.o $(STORELIBS)
+shlock: shlock.o $(LIBINN) ; $(LINK) shlock.o $(INNLIBS)
+shrinkfile: shrinkfile.o $(LIBINN) ; $(LINK) shrinkfile.o $(INNLIBS)
+
+buffchan: buffchan.o map.o $(LIBINN)
+ $(LINK) buffchan.o map.o $(LIBINN) $(LIBS)
+
+filechan: filechan.o map.o $(LIBINN)
+ $(LINK) filechan.o map.o $(LIBINN) $(LIBS)
+
+actmerge: actmerge.in $(FIX) ; $(FIX) actmerge.in
+actsyncd: actsyncd.in $(FIX) ; $(FIX) actsyncd.in
+mod-active: mod-active.in $(FIX) ; $(FIX) mod-active.in
+news2mail: news2mail.in $(FIX) ; $(FIX) news2mail.in
+nntpsend: nntpsend.in $(FIX) ; $(FIX) nntpsend.in
+send-ihave: send-ihave.in $(FIX) ; $(FIX) send-ihave.in
+send-nntp: send-nntp.in $(FIX) ; $(FIX) send-nntp.in
+send-uucp: send-uucp.in $(FIX) ; $(FIX) send-uucp.in
+sendinpaths: sendinpaths.in $(FIX) ; $(FIX) sendinpaths.in
+sendxbatches: sendxbatches.in $(FIX) ; $(FIX) sendxbatches.in
+
+$(LIBINN): ; (cd ../lib ; $(MAKE))
+$(LIBSTORAGE): ; (cd ../storage ; $(MAKE))
+$(LIBHIST): ; (cd ../history ; $(MAKE))
+
+../doc/man/send-uucp.8: send-uucp
+ $(POD2MAN) -s 8 $? > $@
+
+
+## Dependencies. Default list, below, is probably good enough.
+
+depend: Makefile $(SOURCES)
+ $(MAKEDEPEND) '$(CFLAGS)' $(SOURCES)
+
+# DO NOT DELETE THIS LINE -- make depend depends on it.
+actsync.o: actsync.c ../include/config.h ../include/inn/defines.h \
+ ../include/inn/system.h ../include/clibrary.h ../include/config.h \
+ ../include/portable/wait.h ../include/config.h ../include/inn/innconf.h \
+ ../include/inn/defines.h ../include/inn/messages.h ../include/inn/qio.h \
+ ../include/libinn.h ../include/paths.h
+archive.o: archive.c ../include/config.h ../include/inn/defines.h \
+ ../include/inn/system.h ../include/clibrary.h ../include/config.h \
+ ../include/inn/innconf.h ../include/inn/defines.h \
+ ../include/inn/messages.h ../include/inn/wire.h ../include/libinn.h \
+ ../include/paths.h ../include/storage.h
+batcher.o: batcher.c ../include/config.h ../include/inn/defines.h \
+ ../include/inn/system.h ../include/clibrary.h ../include/config.h \
+ ../include/inn/innconf.h ../include/inn/defines.h \
+ ../include/inn/messages.h ../include/inn/timer.h ../include/libinn.h \
+ ../include/paths.h ../include/storage.h
+buffchan.o: buffchan.c ../include/config.h ../include/inn/defines.h \
+ ../include/inn/system.h ../include/clibrary.h ../include/config.h \
+ ../include/inn/innconf.h ../include/inn/defines.h \
+ ../include/inn/messages.h ../include/inn/qio.h ../include/libinn.h \
+ ../include/paths.h map.h
+cvtbatch.o: cvtbatch.c ../include/config.h ../include/inn/defines.h \
+ ../include/inn/system.h ../include/clibrary.h ../include/config.h \
+ ../include/inn/innconf.h ../include/inn/defines.h \
+ ../include/inn/messages.h ../include/inn/qio.h ../include/inn/wire.h \
+ ../include/libinn.h ../include/paths.h ../include/storage.h
+filechan.o: filechan.c ../include/config.h ../include/inn/defines.h \
+ ../include/inn/system.h ../include/clibrary.h ../include/config.h \
+ ../include/inn/innconf.h ../include/inn/defines.h \
+ ../include/inn/messages.h ../include/libinn.h ../include/paths.h map.h
+inndf.o: inndf.c ../include/config.h ../include/inn/defines.h \
+ ../include/inn/system.h ../include/clibrary.h ../include/config.h \
+ ../include/inn/innconf.h ../include/inn/defines.h \
+ ../include/inn/messages.h ../include/inn/qio.h ../include/libinn.h \
+ ../include/ov.h ../include/storage.h ../include/inn/history.h \
+ ../include/paths.h
+innxbatch.o: innxbatch.c ../include/config.h ../include/inn/defines.h \
+ ../include/inn/system.h ../include/clibrary.h ../include/config.h \
+ ../include/portable/socket.h ../include/config.h \
+ ../include/portable/time.h ../include/inn/innconf.h \
+ ../include/inn/defines.h ../include/inn/messages.h \
+ ../include/inn/timer.h ../include/libinn.h ../include/nntp.h
+innxmit.o: innxmit.c ../include/config.h ../include/inn/defines.h \
+ ../include/inn/system.h ../include/clibrary.h ../include/config.h \
+ ../include/portable/socket.h ../include/config.h \
+ ../include/portable/time.h ../include/inn/history.h \
+ ../include/inn/defines.h ../include/inn/innconf.h \
+ ../include/inn/messages.h ../include/inn/qio.h ../include/inn/timer.h \
+ ../include/inn/wire.h ../include/libinn.h ../include/nntp.h \
+ ../include/paths.h ../include/storage.h
+map.o: map.c ../include/config.h ../include/inn/defines.h \
+ ../include/inn/system.h ../include/clibrary.h ../include/config.h \
+ ../include/libinn.h ../include/paths.h map.h
+ninpaths.o: ninpaths.c ../include/config.h ../include/inn/defines.h \
+ ../include/inn/system.h ../include/clibrary.h ../include/config.h
+nntpget.o: nntpget.c ../include/config.h ../include/inn/defines.h \
+ ../include/inn/system.h ../include/clibrary.h ../include/config.h \
+ ../include/portable/socket.h ../include/config.h \
+ ../include/portable/time.h ../include/inn/history.h \
+ ../include/inn/defines.h ../include/inn/innconf.h \
+ ../include/inn/messages.h ../include/libinn.h ../include/nntp.h \
+ ../include/paths.h
+overchan.o: overchan.c ../include/config.h ../include/inn/defines.h \
+ ../include/inn/system.h ../include/clibrary.h ../include/config.h \
+ ../include/portable/time.h ../include/config.h ../include/inn/innconf.h \
+ ../include/inn/defines.h ../include/inn/messages.h ../include/inn/qio.h \
+ ../include/libinn.h ../include/ov.h ../include/storage.h \
+ ../include/inn/history.h ../include/paths.h
+shlock.o: shlock.c ../include/config.h ../include/inn/defines.h \
+ ../include/inn/system.h ../include/clibrary.h ../include/config.h \
+ ../include/inn/messages.h ../include/inn/defines.h
+shrinkfile.o: shrinkfile.c ../include/config.h ../include/inn/defines.h \
+ ../include/inn/system.h ../include/clibrary.h ../include/config.h \
+ ../include/inn/innconf.h ../include/inn/defines.h \
+ ../include/inn/messages.h ../include/libinn.h
--- /dev/null
+#! /bin/sh
+# fixscript will replace this line with code to load innshellvars
+
+# @(#) $Id: actmerge.in 2674 1999-11-15 06:28:29Z rra $
+# @(#) Under RCS control in /usr/local/news/src/inn/local/RCS/actmerge.sh,v
+#
+# actmerge - merge two active files
+#
+# usage:
+# actmerge [-s] ign1 ign2 host1 host2
+#
+# -s - write status on stderr even if no fatal error
+# ign1 - ignore file for host1
+# ign2 - ignore file for host2
+# host1 - 1st active file or host
+# host2 - 2nd active file or host
+#
+# The merge of two active files are sent to stdout. The status is
+# written to stderr.
+
+# By: Landon Curt Noll chongo@toad.com (chongo was here /\../\)
+#
+# Copyright (c) Landon Curt Noll, 1996.
+# All rights reserved.
+#
+# Permission to use and modify is hereby granted so long as this
+# notice remains. Use at your own risk. No warranty is implied.
+
+# preset vars
+#
+
+# Our lock file
+LOCK=${LOCKS}/LOCK.actmerge
+# where actsync is located
+ACTSYNC=${PATHBIN}/actsync
+# exit value of actsync if unable to get an active file
+NOSYNC=127
+# args used by actsync a fetch of an active file
+FETCH="-b 0 -d 0 -g 0 -o aK -p 0 -q 12 -s 0 -t 0 -v 2"
+# args used to merge two active files
+MERGE="-b 0 -d 0 -g 0 -m -o aK -p 0 -q 12 -s 0 -t 0 -v 3"
+# unless -q
+QUIET=true
+
+# parse args
+#
+if [ $# -gt 1 ]; then
+ if [ X"-s" = X"$1" ]; then
+ QUIET=
+ shift
+ fi
+fi
+if [ $# -ne 4 ]; then
+ echo "usage: $0 ign1 ign2 host1 host2" 1>&2
+ exit 1
+fi
+ign1="$1"
+if [ ! -s "$ign1" ]; then
+ echo "$0: host1 ignore file not found or empty: $ign1" 1>&2
+ exit 2
+fi
+ign2="$2"
+if [ ! -s "$ign2" ]; then
+ echo "$0: host2 ignore file not found or empty: $ign2" 1>&2
+ exit 3
+fi
+host1="$3"
+host2="$4"
+
+
+# Lock out others
+#
+trap 'rm -f ${LOCK}; exit 1' 0 1 2 3 15
+shlock -p $$ -f ${LOCK} || {
+ echo "$0: Locked by `cat ${LOCK}`" 1>&2
+ exit 4
+}
+
+# setup
+#
+tmp="$TMPDIR/.merge$$"
+act1="$TMPDIR/.act1$$"
+act2="$TMPDIR/.act2$$"
+trap "rm -f $tmp ${LOCK} $act1 $act2; exit" 0 1 2 3 15
+rm -f "$tmp"
+touch "$tmp"
+chmod 0600 "$tmp"
+rm -f "$act1"
+touch "$act1"
+chmod 0600 "$act1"
+rm -f "$act2"
+touch "$act2"
+chmod 0600 "$act2"
+
+# try to fetch the first active file
+#
+echo "=-= fetching $host1" >>$tmp
+eval "$ACTSYNC -i $ign1 $FETCH /dev/null $host1 > $act1 2>>$tmp"
+status=$?
+if [ "$status" -ne 0 ]; then
+
+ # We failed on our first try, so we will trice knock 3 times after
+ # waiting 5 minutes.
+ #
+ for loop in 1 2 3; do
+
+ # wait 5 minutes
+ sleep 300
+
+ # try #1
+ eval "$ACTSYNC -i $ign1 $FETCH /dev/null $host1 > $act1 2>>$tmp"
+ status=$?
+ if [ "$status" -eq "$NOSYNC" ]; then
+ break;
+ fi
+
+ # try #2
+ eval "$ACTSYNC -i $ign1 $FETCH /dev/null $host1 > $act1 2>>$tmp"
+ status=$?
+ if [ "$status" -eq "$NOSYNC" ]; then
+ break;
+ fi
+
+ # try #3
+ eval "$ACTSYNC -i $ign1 $FETCH /dev/null $host1 > $act1 2>>$tmp"
+ status=$?
+ if [ "$status" -eq "$NOSYNC" ]; then
+ break;
+ fi
+ done
+
+ # give up
+ #
+ if [ "$status" -ne 0 ]; then
+ echo "=-= `date` merge $host1 $host2 exit $status" 1>&2
+ sed -e 's/^/ /' < "$tmp" 1>&2
+ exit "$status"
+ fi
+fi
+if [ ! -s "$act1" ]; then
+ echo "$0: host1 active file not found or empty: $act1" 1>&2
+ exit 5
+fi
+
+# try to fetch the second active file
+#
+echo "=-= fetching $host2" >>$tmp
+eval "$ACTSYNC -i $ign2 $FETCH /dev/null $host2 > $act2 2>>$tmp"
+status=$?
+if [ "$status" -ne 0 ]; then
+
+ # We failed on our first try, so we will trice knock 3 times after
+ # waiting 5 minutes.
+ #
+ for loop in 1 2 3; do
+
+ # wait 5 minutes
+ sleep 300
+
+ # try #1
+ eval "$ACTSYNC -i $ign2 $FETCH /dev/null $host2 > $act2 2>>$tmp"
+ status=$?
+ if [ "$status" -eq "$NOSYNC" ]; then
+ break;
+ fi
+
+ # try #2
+ eval "$ACTSYNC -i $ign2 $FETCH /dev/null $host2 > $act2 2>>$tmp"
+ status=$?
+ if [ "$status" -eq "$NOSYNC" ]; then
+ break;
+ fi
+
+ # try #3
+ eval "$ACTSYNC -i $ign2 $FETCH /dev/null $host2 > $act2 2>>$tmp"
+ status=$?
+ if [ "$status" -eq "$NOSYNC" ]; then
+ break;
+ fi
+ done
+
+ # give up
+ #
+ if [ "$status" -ne 0 ]; then
+ echo "=-= `date` merge $host1 $host2 exit $status" 1>&2
+ sed -e 's/^/ /' < "$tmp" 1>&2
+ exit "$status"
+ fi
+fi
+if [ ! -s "$act2" ]; then
+ echo "$0: host2 active file not found or empty: $act2" 1>&2
+ exit 6
+fi
+
+# merge the 2 active files to stdout
+#
+echo "=-= merging $host1 and $host2" >>$tmp
+eval "$ACTSYNC $MERGE $act1 $act2" 2>>$tmp
+status=$?
+if [ "$status" -ne 0 ]; then
+ echo "=-= `date` merge $host1 $host2 exit $status" 1>&2
+ sed -e 's/^/ /' < "$tmp" 1>&2
+ exit "$status"
+fi
+
+# if not -q, send status to stderr
+#
+if [ -z "$QUIET" ]; then
+ echo "=-= `date` merge $host1 $host2 successful" 1>&2
+ sed -e 's/^/ /' < "$tmp" 1>&2
+fi
+
+# all done
+#
+rm -f "${LOCK}"
+exit 0
--- /dev/null
+/* @(#) $Id: actsync.c 6372 2003-05-31 19:48:28Z rra $ */
+/* @(#) Under RCS control in /usr/local/news/src/inn/local/RCS/actsync.c,v */
+/*
+ * actsync - sync or merge two active files
+ *
+ * usage:
+ * actsync [-b hostid][-d hostid][-g max][-i ignore_file][-I][-k][-l hostid]
+ * [-m][-n name][-o fmt][-p %][-q hostid][-s size]
+ * [-t hostid][-T][-v verbose_lvl][-z sec]
+ * [host1] host2
+ *
+ * -A use authentication to server
+ * -b hostid ignore *.bork.bork.bork groups from: (def: -b 0)
+ * 0 from neither host
+ * 1 from host1
+ * 2 from host2
+ * 12 from host1 and host2
+ * 21 from host1 and host2
+ * -d hostid ignore groups with all numeric components (def: -d 0)
+ * -g max ignore group >max levels (0=dont ignore) (def: -g 0)
+ * -i ignore_file file with list/types of groups to ignore (def: no file)
+ * -I hostid ignore_file applies only to hostid (def: -I 12)
+ * -k keep host1 groups with errors (def: remove)
+ * -l hostid flag =group problems as errors (def: -l 12)
+ * -m merge, keep group not on host2 (def: sync)
+ * -n name name given to ctlinnd newgroup commands (def: actsync)
+ * -o fmt type of output: (def: -o c)
+ * a output groups in active format
+ * a1 like 'a', but output ignored non-err host1 grps
+ * ak like 'a', keep host2 hi/low values on new groups
+ * aK like 'a', use host2 hi/low values always
+ * c output in ctlinnd change commands
+ * x no output, safely exec ctlinnd commands
+ * xi no output, safely exec commands interactively
+ * -p % min % host1 lines unchanged allowed (def: -p 96)
+ * -q hostid silence errors from a host (see -b) (def: -q 0)
+ * -s size ignore names longer than size (0=no lim) (def: -s 0)
+ * -t hostid ignore bad top level groups from:(see -b) (def: -t 2)
+ * -T no new hierarchies (def: allow)
+ * -v verbose_lvl verbosity level (def: -v 0)
+ * 0 no debug or status reports
+ * 1 summary if work done
+ * 2 summary & actions (if exec output) only if done
+ * 3 summary & actions (if exec output)
+ * 4 debug output plus all -v 3 messages
+ * -z sec sleep sec seconds per exec if -o x (def: -z 4)
+ * host1 host to be changed (def: local server)
+ * host2 reference host used in merge
+ */
+/*
+ * By: Landon Curt Noll chongo@toad.com (chongo was here /\../\)
+ *
+ * Copyright (c) Landon Curt Noll, 1996.
+ * All rights reserved.
+ *
+ * Permission to use and modify is hereby granted so long as this
+ * notice remains. Use at your own risk. No warranty is implied.
+ */
+
+#include "config.h"
+#include "clibrary.h"
+#include "portable/wait.h"
+#include <ctype.h>
+#include <dirent.h>
+#include <fcntl.h>
+#include <errno.h>
+#include <math.h>
+#include <sys/stat.h>
+#include <signal.h>
+
+#include "inn/innconf.h"
+#include "inn/messages.h"
+#include "inn/qio.h"
+#include "libinn.h"
+#include "paths.h"
+
+static const char usage[] = "\
+Usage: actsync [-A][-b hostid][-d hostid][-i ignore_file][-I hostid][-k]\n\
+ [-l hostid][-m][-n name][-o fmt][-p min_%_unchg][-q hostid]\n\
+ [-s size][-t hostid][-T][-v verbose_lvl][-z sec]\n\
+ [host1] host2\n\
+\n\
+ -A use authentication to server\n\
+ -b hostid ignore *.bork.bork.bork groups from: (def: -b 0)\n\
+ 0 from neither host\n\
+ 1 from host1\n\
+ 2 from host2\n\
+ 12 from host1 and host2\n\
+ 21 from host1 and host2\n\
+ -d hostid ignore grps with all numeric components (def: -d 0)\n\
+ -g max ignore group >max levels (0=don't) (def: -g 0)\n\
+ -i file file with groups to ignore (def: no file)\n\
+ -I hostid ignore_file applies only to hostid (def: -I 12)\n\
+ -k keep host1 groups with errors (def: remove)\n\
+ -l hostid flag =group problems as errors (def: -l 12)\n\
+ -m merge, keep group not on host2 (def: sync)\n\
+ -n name name given to ctlinnd newgroup cmds (def: actsync)\n\
+ -o fmt type of output: (def: -o c)\n\
+ a output groups in active format\n\
+ a1 like 'a', but output ignored non-err host1 grps\n\
+ ak like 'a', keep host2 hi/low values on new groups\n\
+ aK like 'a', use host2 hi/low values always\n\
+ c output in ctlinnd change commands\n\
+ x no output, safely exec ctlinnd commands\n\
+ xi no output, safely exec commands interactively\n\
+ -p % min % host1 lines unchanged allowed (def: -p 96)\n\
+ -q hostid silence errors from a host (see -b) (def: -q 0)\n\
+ -s size ignore names > than size (0=no lim) (def: -s 0)\n\
+ -t hostid ignore bad top level grps from: (see -b)(def: -t 2)\n\
+ -T no new hierarchies (def: allow)\n\
+ -v level verbosity level (def: -v 0)\n\
+ 0 no debug or status reports\n\
+ 1 summary if work done\n\
+ 2 summary & actions (if exec output) only if done\n\
+ 3 summary & actions (if exec output)\n\
+ 4 debug output plus all -v 3 messages\n\
+ -z sec sleep sec seconds per exec if -o x (def: -z 4)\n\
+\n\
+ host1 host to be changed (def: local server)\n\
+ host2 reference host used in merge\n";
+
+
+/*
+ * pat - internal ignore/check pattern
+ *
+ * A pattern, derived from an ignore file, will determine if a group
+ * is will be checked if it is on both hosts or ignored altogether.
+ *
+ * The type related to the 4th field of an active file. Types may
+ * currently be one of [ymjnx=]. If '=' is one of the types, an
+ * optional equivalence pattern may be given in the 'epat' element.
+ *
+ * For example, to ignore "foo.bar.*", if it is junked or equated to
+ * a group of the form "alt.*.foo.bar.*":
+ *
+ * x.pat = "foo.bar.*";
+ * x.type = "j=";
+ * x.epat = "alt.*.foo.bar.*";
+ * x.ignore = 1;
+ *
+ * To further check "foo.bar.mod" if it is moderated:
+ *
+ * x.pat = "foo.bar.mod";
+ * x.type = "m";
+ * x.epat = NULL;
+ * x.ignore = 0;
+ *
+ * The 'i' value means ignore, 'c' value means 'compare'. The last pattern
+ * that matches a group determines the fate of the group. By default all
+ * groups are included.
+ */
+struct pat {
+ char *pat; /* newsgroup pattern */
+ int type_match; /* 1 => match only if group type matches */
+ int y_type; /* 1 => match if a 'y' type group */
+ int m_type; /* 1 => match if a 'm' type group */
+ int n_type; /* 1 => match if a 'n' type group */
+ int j_type; /* 1 => match if a 'j' type group */
+ int x_type; /* 1 => match if a 'x' type group */
+ int eq_type; /* 1 => match if a 'eq' type group */
+ char *epat; /* =pattern to match, if non-NULL and = is in type */
+ int ignore; /* 0 => check matching group, 1 => ignore it */
+};
+
+/* internal representation of an active line */
+struct grp {
+ int ignore; /* ignore reason, 0 => not ignore (see below) */
+ int hostid; /* HOSTID this group is from */
+ int linenum; /* >0 => active line number, <=0 => not a line */
+ int output; /* 1 => output to produce the merged active file */
+ int remove; /* 1 => remove this group */
+ char *name; /* newsgroup name */
+ char *hi; /* high article string */
+ char *low; /* low article string */
+ char *type; /* newsgroup type string */
+ char *outhi; /* output high article string */
+ char *outlow; /* output low article string */
+ char *outtype; /* output newsgroup type string */
+};
+
+/* structure used in the process of looking for =group type problems */
+struct eqgrp {
+ int skip; /* 1 => skip this entry */
+ struct grp *g; /* =group that is being examined */
+ char *eq; /* current equivalence name */
+};
+
+/*
+ * These ignore reasons are listed in order severity; from mild to severe.
+ */
+#define NOT_IGNORED 0x0000 /* newsgroup has not been ignored */
+#define CHECK_IGNORE 0x0001 /* ignore file ignores this entry */
+#define CHECK_TYPE 0x0002 /* group type is ignored */
+#define CHECK_BORK 0x0004 /* group is a *.bork.bork.bork group */
+#define CHECK_HIER 0x0008 /* -T && new group's hierarchy does not exist */
+#define ERROR_LONGLOOP 0x0010 /* =name refers to long =grp chain or cycle */
+#define ERROR_EQLOOP 0x0020 /* =name refers to itself in some way */
+#define ERROR_NONEQ 0x0040 /* =name does not refer to a valid group */
+#define ERROR_DUP 0x0080 /* newsgroup is a duplicate of another */
+#define ERROR_EQNAME 0x0100 /* =name is a bad group name */
+#define ERROR_BADTYPE 0x0200 /* newsgroup type is invalid */
+#define ERROR_BADNAME 0x0400 /* newsgroup name is invalid */
+#define ERROR_FORMAT 0x0800 /* entry line is malformed */
+
+#define IS_IGNORE(ign) ((ign) & (CHECK_IGNORE|CHECK_TYPE|CHECK_BORK|CHECK_HIER))
+#define IS_ERROR(ign) ((ign) & ~(CHECK_IGNORE|CHECK_TYPE|CHECK_BORK|CHECK_HIER))
+
+#define NOHOST 0 /* neither host1 nor host2 */
+#define HOSTID1 1 /* entry from the first host */
+#define HOSTID2 2 /* entry from the second host */
+
+#define CHUNK 5000 /* number of elements to alloc at a time */
+
+#define TYPES "ymjnx=" /* group types (1st char of 4th active fld) */
+#define TYPECNT (sizeof(TYPES)-1)
+
+#define DEF_HI "0000000000" /* default hi string value for new groups */
+#define DEF_LOW "0000000001" /* default low string value for new groups */
+#define WATER_LEN 10 /* string length of hi/low water mark */
+
+#define DEF_NAME "actsync" /* default name to use for ctlinnd newgroup */
+
+#define MIN_UNCHG (double)96.0 /* min % of host1 lines unchanged allowed */
+
+#define DEV_NULL "/dev/null" /* path to the bit bucket */
+#define CTLINND_NAME "ctlinnd" /* basename of ctlinnd command */
+#define CTLINND_TIME_OUT "-t30" /* seconds to wait before timeout */
+
+#define READ_SIDE 0 /* read side of a pipe */
+#define WRITE_SIDE 1 /* write side of a pipe */
+
+#define EQ_LOOP 16 /* give up if =eq loop/chain is this long */
+#define NOT_REACHED 127 /* exit value if unable to get active files */
+
+#define NEWGRP_EMPTY 0 /* no new group dir was found */
+#define NEWGRP_NOCHG 1 /* new group dir found but no hi/low change */
+#define NEWGRP_CHG 2 /* new group dir found but no hi/low change */
+
+/* -b macros */
+#define BORK_CHECK(hostid) \
+ ((hostid == HOSTID1 && bork_host1_flag) || \
+ (hostid == HOSTID2 && bork_host2_flag))
+
+/* -d macros */
+#define NUM_CHECK(hostid) \
+ ((hostid == HOSTID1 && num_host1_flag) || \
+ (hostid == HOSTID2 && num_host2_flag))
+
+/* -t macros */
+#define TOP_CHECK(hostid) \
+ ((hostid == HOSTID1 && t_host1_flag) || \
+ (hostid == HOSTID2 && t_host2_flag))
+
+/* -o output types */
+#define OUTPUT_ACTIVE 1 /* output in active file format */
+#define OUTPUT_CTLINND 2 /* output in ctlinnd change commands */
+#define OUTPUT_EXEC 3 /* no output, safely exec commands */
+#define OUTPUT_IEXEC 4 /* no output, exec commands interactively */
+
+/* -q macros */
+#define QUIET(hostid) \
+ ((hostid == HOSTID1 && quiet_host1) || (hostid == HOSTID2 && quiet_host2))
+
+/* -v verbosity level */
+#define VER_MIN 0 /* minimum -v level */
+#define VER_NONE 0 /* no -v output */
+#define VER_SUMM_IF_WORK 1 /* output summary if actions were performed */
+#define VER_REPT_IF_WORK 2 /* output summary & actions only if performed */
+#define VER_REPORT 3 /* output summary & actions performed */
+#define VER_FULL 4 /* output all summary, actins and debug */
+#define VER_MAX 4 /* maximum -v level */
+#define D_IF_SUMM (v_flag >= VER_SUMM_IF_WORK) /* true => give summary always */
+#define D_REPORT (v_flag >= VER_REPT_IF_WORK) /* true => give reports */
+#define D_BUG (v_flag == VER_FULL) /* true => debug processing */
+#define D_SUMMARY (v_flag >= VER_REPORT) /* true => give summary always */
+
+/* flag and arg related defaults */
+int bork_host1_flag = 0; /* 1 => -b 1 or -b 12 or -b 21 given */
+int bork_host2_flag = 0; /* 1 => -b 2 or -b 12 or -b 21 given */
+int num_host1_flag = 0; /* 1 => -d 1 or -d 12 or -d 21 given */
+int num_host2_flag = 0; /* 1 => -d 2 or -d 12 or -d 21 given */
+char *ign_file = NULL; /* default ignore file */
+int ign_host1_flag = 1; /* 1 => -i ign_file applies to host1 */
+int ign_host2_flag = 1; /* 1 => -i ign_file applies to host2 */
+int g_flag = 0; /* ignore grps deeper than > g_flag, 0=>dont */
+int k_flag = 0; /* 1 => -k given */
+int l_host1_flag = HOSTID1; /* HOSTID1 => host1 =group error detection */
+int l_host2_flag = HOSTID2; /* HOSTID2 => host2 =group error detection */
+int m_flag = 0; /* 1 => merge active files, don't sync */
+const char *new_name = DEF_NAME; /* ctlinnd newgroup name */
+int o_flag = OUTPUT_CTLINND; /* default output type */
+double p_flag = MIN_UNCHG; /* min % host1 lines allowed to be unchanged */
+int host1_errs = 0; /* errors found in host1 active file */
+int host2_errs = 0; /* errors found in host2 active file */
+int quiet_host1 = 0; /* 1 => -q 1 or -q 12 or -q 21 given */
+int quiet_host2 = 0; /* 1 => -q 2 or -q 12 or -q 21 given */
+int s_flag = 0; /* max group size (length), 0 => do not check */
+int t_host1_flag = 0; /* 1 => -t 1 or -t 12 or -t 21 given */
+int t_host2_flag = 1; /* 1 => -t 2 or -d 12 or -t 21 given */
+int no_new_hier = 0; /* 1 => -T; no new hierarchies */
+int host2_hilow_newgrp = 0; /* 1 => use host2 hi/low on new groups */
+int host2_hilow_all = 0; /* 1 => use host2 hi/low on all groups */
+int host1_ign_print = 0; /* 1 => print host1 ignored groups too */
+int v_flag = 0; /* default verbosity level */
+int z_flag = 4; /* sleep z_flag sec per exec if -o x */
+int A_flag = 0;
+
+/* forward declarations */
+static struct grp *get_active(); /* get an active file from a remote host */
+static int bad_grpname(); /* test if string is a valid group name */
+static struct pat *get_ignore(); /* read in an ignore file */
+static void ignore(); /* ignore newsgroups given an ignore list */
+static int merge_cmp(); /* qsort compare for active file merge */
+static void merge_grps(); /* merge groups from active files */
+static int active_cmp(); /* qsort compare for active file output */
+static void output_grps(); /* output the merged groups */
+static void process_args(); /* process command line arguments */
+static void error_mark(); /* mark for removal, error grps from host */
+static int eq_merge_cmp(); /* qsort compare for =type grp processing */
+static int mark_eq_probs(); /* mark =type problems from a host */
+static int exec_cmd(); /* exec a ctlinnd command */
+static int new_top_hier(); /* see if we have a new top level */
+
+int
+main(argc, argv)
+ int argc; /* arg count */
+ char *argv[]; /* the args */
+{
+ struct grp *grp; /* struct grp array for host1 & host2 */
+ struct pat *ignor; /* ignore list from ignore file */
+ int grplen; /* length of host1/host2 group array */
+ int iglen; /* length of ignore list */
+ char *host1; /* host to change */
+ char *host2; /* comparison host */
+
+ /* First thing, set up our identity. */
+ message_program_name = "actsync";
+
+ /* Read in default info from inn.conf. */
+ if (!innconf_read(NULL))
+ exit(1);
+ process_args(argc, argv, &host1, &host2);
+
+ /* obtain the active files */
+ grp = get_active(host1, HOSTID1, &grplen, NULL, &host1_errs);
+ grp = get_active(host2, HOSTID2, &grplen, grp, &host2_errs);
+
+ /* ignore groups from both active files, if -i */
+ if (ign_file != NULL) {
+
+ /* read in the ignore file */
+ ignor = get_ignore(ign_file, &iglen);
+
+ /* ignore groups */
+ ignore(grp, grplen, ignor, iglen);
+ }
+
+ /* compare groups from both hosts */
+ merge_grps(grp, grplen, host1, host2);
+
+ /* mark for removal, error groups from host1 if -e */
+ if (! k_flag) {
+
+ /* mark error groups for removal */
+ error_mark(grp, grplen, HOSTID1);
+ }
+
+ /* output result of merge */
+ output_grps(grp, grplen);
+
+ /* all done */
+ exit(0);
+}
+
+/*
+ * process_args - process the command line arguments
+ *
+ * given:
+ * argc arg count
+ * argv the args
+ * host1 name of first host (may be 2nd if -R)
+ * host2 name of second host2 *may be 1st if -R)
+ */
+static void
+process_args(argc, argv, host1, host2)
+ int argc; /* arg count */
+ char *argv[]; /* the arg array */
+ char **host1; /* where to place name of host1 */
+ char **host2; /* where to place name of host2 */
+{
+ char *def_serv = NULL; /* name of default server */
+ int i;
+
+ /* parse args */
+ while ((i = getopt(argc,argv,"Ab:d:g:i:I:kl:mn:o:p:q:s:t:Tv:z:")) != EOF) {
+ switch (i) {
+ case 'A':
+ A_flag = 1;
+ break;
+ case 'b': /* -b {0|1|2|12|21} */
+ switch (atoi(optarg)) {
+ case 0:
+ bork_host1_flag = 0;
+ bork_host2_flag = 0;
+ break;
+ case 1:
+ bork_host1_flag = 1;
+ break;
+ case 2:
+ bork_host2_flag = 1;
+ break;
+ case 12:
+ case 21:
+ bork_host1_flag = 1;
+ bork_host2_flag = 1;
+ break;
+ default:
+ warn("-b option must be 0, 1, 2, 12, or 21");
+ die("%s", usage);
+ }
+ break;
+ case 'd': /* -d {0|1|2|12|21} */
+ switch (atoi(optarg)) {
+ case 0:
+ num_host1_flag = 0;
+ num_host2_flag = 0;
+ break;
+ case 1:
+ num_host1_flag = 1;
+ break;
+ case 2:
+ num_host2_flag = 1;
+ break;
+ case 12:
+ case 21:
+ num_host1_flag = 1;
+ num_host2_flag = 1;
+ break;
+ default:
+ warn("-d option must be 0, 1, 2, 12, or 21");
+ die("%s", usage);
+ }
+ break;
+ case 'g': /* -g max */
+ g_flag = atoi(optarg);
+ break;
+ case 'i': /* -i ignore_file */
+ ign_file = optarg;
+ break;
+ case 'I': /* -I {0|1|2|12|21} */
+ switch (atoi(optarg)) {
+ case 0:
+ ign_host1_flag = 0;
+ ign_host2_flag = 0;
+ break;
+ case 1:
+ ign_host1_flag = 1;
+ ign_host2_flag = 0;
+ break;
+ case 2:
+ ign_host1_flag = 0;
+ ign_host2_flag = 1;
+ break;
+ case 12:
+ case 21:
+ ign_host1_flag = 1;
+ ign_host2_flag = 1;
+ break;
+ default:
+ warn("-I option must be 0, 1, 2, 12, or 21");
+ die("%s", usage);
+ }
+ break;
+ case 'k': /* -k */
+ k_flag = 1;
+ break;
+ case 'l': /* -l {0|1|2|12|21} */
+ switch (atoi(optarg)) {
+ case 0:
+ l_host1_flag = NOHOST;
+ l_host2_flag = NOHOST;
+ break;
+ case 1:
+ l_host1_flag = HOSTID1;
+ l_host2_flag = NOHOST;
+ break;
+ case 2:
+ l_host1_flag = NOHOST;
+ l_host2_flag = HOSTID2;
+ break;
+ case 12:
+ case 21:
+ l_host1_flag = HOSTID1;
+ l_host2_flag = HOSTID2;
+ break;
+ default:
+ warn("-l option must be 0, 1, 2, 12, or 21");
+ die("%s", usage);
+ }
+ break;
+ case 'm': /* -m */
+ m_flag = 1;
+ break;
+ case 'n': /* -n name */
+ new_name = optarg;
+ break;
+ case 'o': /* -o out_type */
+ switch (optarg[0]) {
+ case 'a':
+ o_flag = OUTPUT_ACTIVE;
+ switch (optarg[1]) {
+ case '1':
+ switch(optarg[2]) {
+ case 'K': /* -o a1K */
+ host1_ign_print = 1;
+ host2_hilow_all = 1;
+ host2_hilow_newgrp = 1;
+ break;
+ case 'k': /* -o a1k */
+ host1_ign_print = 1;
+ host2_hilow_newgrp = 1;
+ break;
+ default: /* -o a1 */
+ host1_ign_print = 1;
+ break;
+ }
+ break;
+ case 'K':
+ switch(optarg[2]) {
+ case '1': /* -o aK1 */
+ host1_ign_print = 1;
+ host2_hilow_all = 1;
+ host2_hilow_newgrp = 1;
+ break;
+ default: /* -o aK */
+ host2_hilow_all = 1;
+ host2_hilow_newgrp = 1;
+ break;
+ };
+ break;
+ case 'k':
+ switch(optarg[2]) {
+ case '1': /* -o ak1 */
+ host1_ign_print = 1;
+ host2_hilow_newgrp = 1;
+ break;
+ default: /* -o ak */
+ host2_hilow_newgrp = 1;
+ break;
+ };
+ break;
+ case '\0': /* -o a */
+ break;
+ default:
+ warn("-o type must be a, a1, ak, aK, ak1, or aK1");
+ die("%s", usage);
+ }
+ break;
+ case 'c':
+ o_flag = OUTPUT_CTLINND;
+ break;
+ case 'x':
+ if (optarg[1] == 'i') {
+ o_flag = OUTPUT_IEXEC;
+ } else {
+ o_flag = OUTPUT_EXEC;
+ }
+ break;
+ default:
+ warn("-o type must be a, a1, ak, aK, ak1, aK1, c, x, or xi");
+ die("%s", usage);
+ }
+ break;
+ case 'p': /* -p %_min_host1_change */
+ /* parse % into [0,100] */
+ p_flag = atof(optarg);
+ if (p_flag > (double)100.0) {
+ p_flag = (double)100.0;
+ } else if (p_flag < (double)0.0) {
+ p_flag = (double)0.0;
+ }
+ break;
+ case 'q': /* -q {0|1|2|12|21} */
+ switch (atoi(optarg)) {
+ case 0:
+ quiet_host1 = 0;
+ quiet_host2 = 0;
+ break;
+ case 1:
+ quiet_host1 = 1;
+ break;
+ case 2:
+ quiet_host2 = 1;
+ break;
+ case 12:
+ case 21:
+ quiet_host1 = 1;
+ quiet_host2 = 1;
+ break;
+ default:
+ warn("-q option must be 0, 1, 2, 12, or 21");
+ die("%s", usage);
+ }
+ break;
+ case 's': /* -s size */
+ s_flag = atoi(optarg);
+ break;
+ case 't': /* -t {0|1|2|12|21} */
+ switch (atoi(optarg)) {
+ case 0:
+ t_host1_flag = NOHOST;
+ t_host2_flag = NOHOST;
+ break;
+ case 1:
+ t_host1_flag = HOSTID1;
+ t_host2_flag = NOHOST;
+ break;
+ case 2:
+ t_host1_flag = NOHOST;
+ t_host2_flag = HOSTID2;
+ break;
+ case 12:
+ case 21:
+ t_host1_flag = HOSTID1;
+ t_host2_flag = HOSTID2;
+ break;
+ default:
+ warn("-t option must be 0, 1, 2, 12, or 21");
+ die("%s", usage);
+ }
+ break;
+ case 'T': /* -T */
+ no_new_hier = 1;
+ break;
+ case 'v': /* -v verbose_lvl */
+ v_flag = atoi(optarg);
+ if (v_flag < VER_MIN || v_flag > VER_MAX) {
+ warn("-v level must be >= %d and <= %d", VER_MIN, VER_MAX);
+ die("%s", usage);
+ }
+ break;
+ case 'z': /* -z sec */
+ z_flag = atoi(optarg);
+ break;
+ default:
+ warn("unknown flag");
+ die("%s", usage);
+ }
+ }
+
+ /* process the remaining args */
+ argc -= optind;
+ argv += optind;
+ *host1 = NULL;
+ switch (argc) {
+ case 1:
+ /* assume host1 is the local server */
+ *host2 = argv[0];
+ break;
+ case 2:
+ *host1 = argv[0];
+ *host2 = argv[1];
+ break;
+ default:
+ warn("expected 1 or 2 host args, found %d", argc);
+ die("%s", usage);
+ }
+
+ /* determine default host name if needed */
+ if (*host1 == NULL || strcmp(*host1, "-") == 0) {
+ def_serv = innconf->server;
+ *host1 = def_serv;
+ }
+ if (*host2 == NULL || strcmp(*host2, "-") == 0) {
+ def_serv = innconf->server;
+ *host2 = def_serv;
+ }
+ if (*host1 == NULL || *host2 == NULL)
+ die("unable to determine default server name");
+ if (D_BUG && def_serv != NULL)
+ warn("STATUS: using default server: %s", def_serv);
+
+ /* processing done */
+ return;
+}
+
+/*
+ * get_active - get an active file from a host
+ *
+ * given:
+ * host host to contact or file to read, NULL => local server
+ * hostid HOST_ID of host
+ * len pointer to length of grp return array
+ * grp existing host array to add, or NULL
+ * errs count of lines that were found to have some error
+ *
+ * returns;
+ * Pointer to an array of grp structures describing each active entry.
+ * Does not return on fatal error.
+ *
+ * If host starts with a '/' or '.', then it is assumed to be a local file.
+ * In that case, the local file is opened and read.
+ */
+static struct grp *
+get_active(host, hostid, len, grp, errs)
+ char *host; /* the host to contact */
+ int hostid; /* HOST_ID of host */
+ int *len; /* length of returned grp array in elements */
+ struct grp* grp; /* existing group array or NULL */
+ int *errs; /* line error count */
+{
+ FILE *active; /* stream for fetched active data */
+ FILE *FromServer; /* stream from server */
+ FILE *ToServer; /* stream to server */
+ QIOSTATE *qp; /* QIO active state */
+ char buff[8192+1]; /* QIO buffer */
+ char *line; /* the line just read */
+ struct grp *ret; /* array of groups to return */
+ struct grp *cur; /* current grp entry being formed */
+ int max; /* max length of ret */
+ int cnt; /* number of entries read */
+ int ucnt; /* number of entries to be used */
+ int namelen; /* length of newsgroup name */
+ int is_file; /* 1 => host is actually a filename */
+ int num_check; /* true => check for all numeric components */
+ char *rhost;
+ int rport;
+ char *p;
+ int i;
+
+ /* firewall */
+ if (len == NULL)
+ die("internal error #1: len is NULL");
+ if (errs == NULL)
+ die("internal error #2: errs in NULL");
+ if (D_BUG)
+ warn("STATUS: obtaining active file from %s", host);
+
+ /* setup return array if needed */
+ if (grp == NULL) {
+ ret = xmalloc(CHUNK * sizeof(struct grp));
+ max = CHUNK;
+ *len = 0;
+
+ /* or prep to use the existing array */
+ } else {
+ ret = grp;
+ max = ((*len + CHUNK-1)/CHUNK)*CHUNK;
+ }
+
+ /* check for host being a filename */
+ if (host != NULL && (host[0] == '/' || host[0] == '.')) {
+
+ /* note that host is actually a file */
+ is_file = 1;
+
+ /* setup to read the local file quickly */
+ if ((qp = QIOopen(host)) == NULL)
+ sysdie("cannot open active file");
+
+ /* case: host is a hostname or NULL (default server) */
+ } else {
+
+ /* note that host is actually a hostname or NULL */
+ is_file = 0;
+
+ /* prepare remote host variables */
+ if ((p = strchr(host, ':')) != NULL) {
+ rport = atoi(p + 1);
+ *p = '\0';
+ rhost = xstrdup(host);
+ *p = ':';
+ } else {
+ rhost = xstrdup(host);
+ rport = NNTP_PORT;
+ }
+
+ /* open a connection to the server */
+ buff[0] = '\0';
+ if (NNTPconnect(rhost, rport, &FromServer, &ToServer, buff) < 0)
+ die("cannot connect to server: %s",
+ buff[0] ? buff : strerror(errno));
+
+ if (A_flag && NNTPsendpassword(rhost, FromServer, ToServer) < 0)
+ die("cannot authenticate to server");
+
+ free(rhost);
+
+ /* get the active data from the server */
+ active = CAlistopen(FromServer, ToServer, NULL);
+ if (active == NULL)
+ sysdie("cannot retrieve data");
+
+ /* setup to read the retrieved data quickly */
+ if ((qp = QIOfdopen((int)fileno(active))) == NULL)
+ sysdie("cannot read temp file");
+ }
+
+ /* scan server's output, processing appropriate lines */
+ num_check = NUM_CHECK(hostid);
+ for (cnt=0, ucnt=0; (line = QIOread(qp)) != NULL; ++(*len), ++cnt) {
+
+ /* expand return array if needed */
+ if (*len >= max) {
+ max += CHUNK;
+ ret = xrealloc(ret, sizeof(struct grp) * max);
+ }
+
+ /* setup the next return element */
+ cur = &ret[*len];
+ cur->ignore = NOT_IGNORED;
+ cur->hostid = hostid;
+ cur->linenum = cnt+1;
+ cur->output = 0;
+ cur->remove = 0;
+ cur->name = NULL;
+ cur->hi = NULL;
+ cur->low = NULL;
+ cur->type = NULL;
+ cur->outhi = NULL;
+ cur->outlow = NULL;
+ cur->outtype = NULL;
+
+ /* obtain a copy of the current line */
+ cur->name = xstrdup(line);
+
+ /* get the group name */
+ if ((p = strchr(cur->name, ' ')) == NULL) {
+ if (!QUIET(hostid))
+ warn("line %d from %s is malformed, skipping line", cnt + 1,
+ host);
+
+ /* don't form an entry for this group */
+ --(*len);
+ continue;
+ }
+ *p = '\0';
+ namelen = p - cur->name;
+
+ /* find the other 3 fields, ignore if not found */
+ cur->hi = p+1;
+ if ((p = strchr(p + 1, ' ')) == NULL) {
+ if (!QUIET(hostid))
+ warn("skipping malformed line %d (field 2) from %s", cnt + 1,
+ host);
+
+ /* don't form an entry for this group */
+ --(*len);
+ continue;
+ }
+ *p = '\0';
+ cur->low = p+1;
+ if ((p = strchr(p + 1, ' ')) == NULL) {
+ if (!QUIET(hostid))
+ warn("skipping malformed line %d (field 3) from %s", cnt + 1,
+ host);
+
+ /* don't form an entry for this group */
+ --(*len);
+ continue;
+ }
+ *p = '\0';
+ cur->type = p+1;
+ if ((p = strchr(p + 1, ' ')) != NULL) {
+ if (!QUIET(hostid))
+ warn("skipping line %d from %s, it has more than 4 fields",
+ cnt + 1, host);
+
+ /* don't form an entry for this group */
+ --(*len);
+ continue;
+ }
+
+ /* check for bad group name */
+ if (bad_grpname(cur->name, num_check)) {
+ if (!QUIET(hostid))
+ warn("line %d <%s> from %s has a bad newsgroup name",
+ cnt + 1, cur->name, host);
+ cur->ignore |= ERROR_BADNAME;
+ continue;
+ }
+
+ /* check for long name if requested */
+ if (s_flag > 0 && strlen(cur->name) > (size_t)s_flag) {
+ if (!QUIET(hostid))
+ warn("line %d <%s> from %s has a name that is too long",
+ cnt + 1, cur->name, host);
+ cur->ignore |= ERROR_BADNAME;
+ continue;
+ }
+
+ /* look for only a bad top level element if the proper -t was given */
+ if (TOP_CHECK(hostid)) {
+
+ /* look for a '.' in the name */
+ if (strcmp(cur->name, "junk") != 0 &&
+ strcmp(cur->name, "control") != 0 &&
+ strcmp(cur->name, "to") != 0 &&
+ strcmp(cur->name, "test") != 0 &&
+ strcmp(cur->name, "general") != 0 &&
+ strchr(cur->name, '.') == NULL) {
+ if (!QUIET(hostid))
+ warn("line %d <%s> from %s is an invalid top level name",
+ cnt + 1, cur->name, host);
+ cur->ignore |= ERROR_BADNAME;
+ continue;
+ }
+ }
+
+ /* look for *.bork.bork.bork groups if the proper -b was given */
+ if (BORK_CHECK(cur->hostid)) {
+ int elmlen; /* length of element */
+ char *q; /* beyond end of element */
+
+ /* scan the name backwards */
+ q = &(cur->name[namelen]);
+ for (p = &(cur->name[namelen-1]); p >= cur->name; --p) {
+ /* if '.', see if this is a bork element */
+ if (*p == '.') {
+ /* see if the bork element is short enough */
+ elmlen = q-p;
+ if (3*elmlen <= q-cur->name) {
+ /* look for a triple match */
+ if (strncmp(p,p-elmlen,elmlen) == 0 &&
+ strncmp(p,p-(elmlen*2),elmlen) == 0) {
+ /* found a *.bork.bork.bork group */
+ cur->ignore |= CHECK_BORK;
+ break;
+ }
+ }
+ /* note the end of a new element */
+ q = p;
+ }
+ }
+ }
+
+ /*
+ * check for bad chars in the hi water mark
+ */
+ for (p=cur->hi, i=0; *p && isascii(*p) && isdigit((int)*p); ++p, ++i) {
+ }
+ if (*p) {
+ if (!QUIET(hostid))
+ warn("line %d <%s> from %s has non-digits in hi water",
+ cnt + 1, cur->name, cur->hi);
+ cur->ignore |= ERROR_FORMAT;
+ continue;
+ }
+
+ /*
+ * check for excessive hi water length
+ */
+ if (i > WATER_LEN) {
+ if (!QUIET(hostid))
+ warn("line %d <%s> from %s hi water len: %d < %d",
+ cnt + 1, cur->name, cur->hi, i, WATER_LEN);
+ cur->ignore |= ERROR_FORMAT;
+ continue;
+ }
+
+ /*
+ * if the hi water length is too small, malloc and resize
+ */
+ if (i != WATER_LEN) {
+ p = xmalloc(WATER_LEN + 1);
+ memcpy(p, cur->hi, ((i > WATER_LEN) ? WATER_LEN : i)+1);
+ }
+
+ /*
+ * check for bad chars in the low water mark
+ */
+ for (p=cur->low, i=0; *p && isascii(*p) && isdigit((int)*p); ++p, ++i) {
+ }
+ if (*p) {
+ if (!QUIET(hostid))
+ warn("line %d <%s> from %s has non-digits in low water",
+ cnt + 1, cur->name, cur->low);
+ cur->ignore |= ERROR_FORMAT;
+ continue;
+ }
+
+ /*
+ * check for excessive low water length
+ */
+ if (i > WATER_LEN) {
+ if (!QUIET(hostid))
+ warn("line %d <%s> from %s low water len: %d < %d",
+ cnt + 1, cur->name, cur->hi, i, WATER_LEN);
+ cur->ignore |= ERROR_FORMAT;
+ continue;
+ }
+
+ /*
+ * if the low water length is too small, malloc and resize
+ */
+ if (i != WATER_LEN) {
+ p = xmalloc(WATER_LEN + 1);
+ memcpy(p, cur->low, ((i > WATER_LEN) ? WATER_LEN : i)+1);
+ }
+
+ /* check for a bad group type */
+ switch (cur->type[0]) {
+ case 'y':
+ /* of COURSE: collabra has incompatible flags. but it */
+ /* looks like they can be fixed easily enough. */
+ if (cur->type[1] == 'g') {
+ cur->type[1] = '\0';
+ }
+ case 'm':
+ case 'j':
+ case 'n':
+ case 'x':
+ if (cur->type[1] != '\0') {
+ if (!QUIET(hostid))
+ warn("line %d <%s> from %s has a bad newsgroup type",
+ cnt + 1, cur->name, host);
+ cur->ignore |= ERROR_BADTYPE;
+ }
+ break;
+ case '=':
+ if (cur->type[1] == '\0') {
+ if (!QUIET(hostid))
+ warn("line %d <%s> from %s has an empty =group name",
+ cnt + 1, cur->name, host);
+ cur->ignore |= ERROR_BADTYPE;
+ }
+ break;
+ default:
+ if (!QUIET(hostid))
+ warn("line %d <%s> from %s has an unknown newsgroup type",
+ cnt + 1, cur->name, host);
+ cur->ignore |= ERROR_BADTYPE;
+ break;
+ }
+ if (cur->ignore & ERROR_BADTYPE) {
+ continue;
+ }
+
+ /* if an = type, check for bad = name */
+ if (cur->type[0] == '=' && bad_grpname(&(cur->type[1]), num_check)) {
+ if (!QUIET(hostid))
+ warn("line %d <%s> from %s is equivalenced to a bad name:"
+ " <%s>", cnt+1, cur->name, host,
+ (cur->type) ? cur->type : "NULL");
+ cur->ignore |= ERROR_EQNAME;
+ continue;
+ }
+
+ /* if an = type, check for long = name if requested */
+ if (cur->type[0] == '=' && s_flag > 0 &&
+ strlen(&(cur->type[1])) > (size_t)s_flag) {
+ if (!QUIET(hostid))
+ warn("line %d <%s> from %s is equivalenced to a long name:"
+ " <%s>", cnt+1, cur->name, host,
+ (cur->type) ? cur->type : "NULL");
+ cur->ignore |= ERROR_EQNAME;
+ continue;
+ }
+
+ /* count this entry which will be used */
+ ++ucnt;
+ }
+ if (D_BUG)
+ warn("STATUS: read %d groups, will merge %d groups from %s",
+ cnt, ucnt, host);
+
+ /* count the errors */
+ *errs = cnt - ucnt;
+ if (D_BUG)
+ warn("STATUS: found %d line errors from %s", *errs, host);
+
+ /* determine why we stopped */
+ if (QIOerror(qp))
+ sysdie("cannot read temp file for %s at line %d", host, cnt);
+ else if (QIOtoolong(qp))
+ sysdie("line %d from host %s is too long", cnt, host);
+
+ /* all done */
+ if (is_file) {
+ QIOclose(qp);
+ } else {
+ CAclose();
+ fprintf(ToServer, "quit\r\n");
+ fclose(ToServer);
+ fgets(buff, sizeof buff, FromServer);
+ fclose(FromServer);
+ }
+ return ret;
+}
+
+/*
+ * bad_grpname - test if the string is a valid group name
+ *
+ * Newsgroup names must consist of only alphanumeric chars and
+ * characters from the following regular expression:
+ *
+ * [.+-_]
+ *
+ * One cannot have two '.'s in a row. The first character must be
+ * alphanumeric. The character following a '.' must be alphanumeric.
+ * The name cannot end in a '.' character.
+ *
+ * If we are checking for all numeric compnents, (see num_chk) then
+ * a component cannot be all numeric. I.e,. there must be a non-numeric
+ * character in the name, there must be a non-numeric character between
+ * the start and the first '.', there must be a non-numeric character
+ * between two '.'s anmd there must be a non-numeric character between
+ * the last '.' and the end.
+ *
+ * given:
+ * name newsgroup name to check
+ * num_chk true => all numeric newsgroups components are invalid
+ * false => do not check for numeric newsgroups
+ *
+ * returns:
+ * 0 group is ok
+ * 1 group is bad
+ */
+static int
+bad_grpname(name, num_chk)
+ char *name; /* newsgroup name to check */
+ int num_chk; /* true => check for numeric newsgroup */
+{
+ char *p;
+ int non_num; /* true => found a non-numeric, non-. character */
+ int level; /* group levels (.'s) */
+
+ /* firewall */
+ if (name == NULL) {
+ return 1;
+ }
+
+ /* must start with a alpha numeric ascii character */
+ if (!isascii(name[0])) {
+ return 1;
+ }
+ /* set non_num as needed */
+ if (isalpha((int)name[0])) {
+ non_num = true;
+ } else if ((int)isdigit((int)name[0])) {
+ non_num = false;
+ } else {
+ return 1;
+ }
+
+ /* scan each char */
+ level = 0;
+ for (p=name+1; *p; ++p) {
+
+ /* name must contain ASCII chars */
+ if (!isascii(*p)) {
+ return 1;
+ }
+
+ /* alpha chars are ok */
+ if (isalpha((int)*p)) {
+ non_num = true;
+ continue;
+ }
+
+ /* numeric chars are ok */
+ if (isdigit((int)*p)) {
+ continue;
+ }
+
+ /* +, - and _ are ok */
+ if (*p == '+' || *p == '-' || *p == '_') {
+ non_num = true;
+ continue;
+ }
+
+ /* check for the '.' case */
+ if (*p == '.') {
+ /*
+ * look for groups that are too deep, if requested by -g
+ */
+ if (g_flag > 0 && ++level > g_flag) {
+ /* we are too deep */
+ return 1;
+ }
+
+ /*
+ * A '.' is ok as long as the next character is alphanumeric.
+ * This imples that '.' cannot before a previous '.' and
+ * that it cannot be at the end.
+ *
+ * If we are checking for all numeric compnents, then
+ * '.' is ok if we saw a non-numeric char before the
+ * last '.', or before the beginning if no previous '.'
+ * has been seen.
+ */
+ if ((!num_chk || non_num) && isascii(*(p+1)) && isalnum((int)*(p+1))) {
+ ++p; /* '.' is ok, and so is the next char */
+ if (isdigit((int)*p)) { /* reset non_num as needed */
+ non_num = false;
+ } else {
+ non_num = true;
+ }
+ continue;
+ }
+ }
+
+ /* this character must be invalid */
+ return 1;
+ }
+ if (num_chk && !non_num) {
+ /* last component is all numeric */
+ return 1;
+ }
+
+ /* the name must be ok */
+ return 0;
+}
+
+/*
+ * get_ignore - get the ignore list from an ignore file
+ *
+ * given:
+ * filename name of the ignore file to read
+ * *len pointer to length of ignore return array
+ *
+ * returns:
+ * returns a malloced ignore pattern array, changes len
+ *
+ * An ignore file is of the form:
+ *
+ * # this is a comment which is ignored
+ * # comments begin at the first # character
+ * # comments may follow text on the same line
+ *
+ * # blank lines are ignored too
+ *
+ * # lines are [ic] <spaces-tabs> pattern [<spaces-tabs> type] ...
+ * i foo.* # ignore foo.* groups,
+ * c foo.bar m # but check foo.bar if moderated
+ * c foo.keep.* # and check foo.keep.*
+ * i foo.keep.* j =alt.* # except when foo.keep.* is junked
+ * # or equivalenced to an alt.* group
+ *
+ * The 'i' value means ignore, 'c' value means 'compare'. The last pattern
+ * that matches a group determines the fate of the group. By default all
+ * groups are included.
+ *
+ * NOTE: Only one '=name' is allowed per line.
+ * "=" is considered to be equivalent to "=*".
+ */
+static struct pat *
+get_ignore(filename, len)
+ char *filename; /* name of the ignore file to read */
+ int *len; /* length of return array */
+{
+ QIOSTATE *qp; /* QIO ignore file state */
+ char *line; /* the line just read */
+ struct pat *ret; /* array of ignore patterns to return */
+ struct pat *cur; /* current pattern entry being formed */
+ int max; /* max length (in elements) of ret */
+ int linenum; /* current line number */
+ char *p;
+ int i;
+
+ /* firewall */
+ if (filename == NULL)
+ die("internal error #3: filename is NULL");
+ if (len == NULL)
+ die("internal error #4: len is NULL");
+ if (D_BUG)
+ warn("STATUS: reading ignore file %s", filename);
+
+ /* setup return array */
+ ret = xmalloc(CHUNK * sizeof(struct grp));
+ max = CHUNK;
+
+ /* setup to read the ignore file data quickly */
+ if ((qp = QIOopen(filename)) == NULL)
+ sysdie("cannot read ignore file %s", filename);
+
+ /* scan server's output, displaying appropriate lines */
+ *len = 0;
+ for (linenum = 1; (line = QIOread(qp)) != NULL; ++linenum) {
+
+ /* expand return array if needed */
+ if (*len >= max) {
+ max += CHUNK;
+ ret = xrealloc(ret, sizeof(struct pat) * max);
+ }
+
+ /* remove any trailing comments */
+ p = strchr(line, '#');
+ if (p != NULL) {
+ *p = '\0';
+ }
+
+ /* remove any trailing spaces and tabs */
+ for (p = &line[strlen(line)-1];
+ p >= line && (*p == ' ' || *p == '\t');
+ --p) {
+ *p = '\0';
+ }
+
+ /* ignore line if the remainder of the line is empty */
+ if (line[0] == '\0') {
+ continue;
+ }
+
+ /* ensure that the line starts with an i or c token */
+ if ((line[0] != 'i' && line[0] != 'c') ||
+ (line[1] != ' ' && line[1] != '\t'))
+ die("first token is not i or c in line %d of %s", linenum,
+ filename);
+
+ /* ensure that the second newsgroup pattern token follows */
+ p = strtok(line+2, " \t");
+ if (p == NULL)
+ die("did not find 2nd field in line %d of %s", linenum,
+ filename);
+
+ /* setup the next return element */
+ cur = &ret[*len];
+ cur->pat = NULL;
+ cur->type_match = 0;
+ cur->y_type = 0;
+ cur->m_type = 0;
+ cur->n_type = 0;
+ cur->j_type = 0;
+ cur->x_type = 0;
+ cur->eq_type = 0;
+ cur->epat = NULL;
+ cur->ignore = (line[0] == 'i');
+
+ /* obtain a copy of the newsgroup pattern token */
+ cur->pat = xstrdup(p);
+
+ /* process any other type tokens */
+ for (p=strtok(NULL, " \t"), i=3;
+ p != NULL;
+ p=strtok(NULL, " \t"), ++i) {
+
+ /* ensure that this next token is a valid type */
+ switch (p[0]) {
+ case 'y':
+ case 'm':
+ case 'j':
+ case 'n':
+ case 'x':
+ if (p[1] != '\0') {
+ warn("field %d on line %d of %s not a valid type",
+ i, linenum, filename);
+ die("valid types are a char from [ymnjx=] or =name");
+ }
+ break;
+ case '=':
+ break;
+ default:
+ warn("field %d on line %d of %s is not a valid type",
+ i, linenum, filename);
+ die("valid types are a char from [ymnjx=] or =name");
+ }
+
+ /* note that we have a type specific pattern */
+ cur->type_match = 1;
+
+ /* ensure that type is not a duplicate */
+ if ((p[0] == 'y' && cur->y_type) ||
+ (p[0] == 'm' && cur->m_type) ||
+ (p[0] == 'n' && cur->n_type) ||
+ (p[0] == 'j' && cur->j_type) ||
+ (p[0] == 'x' && cur->x_type) ||
+ (p[0] == '=' && cur->eq_type)) {
+ warn("only one %c type allowed per line", p[0]);
+ die("field %d on line %d of %s is a duplicate type",
+ i, linenum, filename);
+ }
+
+ /* note what we have seen */
+ switch (p[0]) {
+ case 'y':
+ cur->y_type = 1;
+ break;
+ case 'm':
+ cur->m_type = 1;
+ break;
+ case 'j':
+ cur->j_type = 1;
+ break;
+ case 'n':
+ cur->n_type = 1;
+ break;
+ case 'x':
+ cur->x_type = 1;
+ break;
+ case '=':
+ cur->eq_type = 1;
+ if (p[0] == '=' && p[1] != '\0')
+ cur->epat = xstrdup(p + 1);
+ break;
+ }
+
+ /* object if too many fields */
+ if (i-3 > TYPECNT)
+ die("too many fields on line %d of %s", linenum, filename);
+ }
+
+ /* count another pat element */
+ ++(*len);
+ }
+
+ /* return the pattern array */
+ return ret;
+}
+
+/*
+ * ignore - ignore newsgroups given an ignore list
+ *
+ * given:
+ * grp array of groups
+ * grplen length of grp array in elements
+ * igcl array of ignore
+ * iglen length of igcl array in elements
+ */
+static void
+ignore(grp, grplen, igcl, iglen)
+ struct grp *grp; /* array of groups */
+ int grplen; /* length of grp array in elements */
+ struct pat *igcl; /* array of ignore patterns */
+ int iglen; /* length of igcl array in elements */
+{
+ struct grp *gp; /* current group element being examined */
+ struct pat *pp; /* current pattern element being examined */
+ int g; /* current group index number */
+ int p; /* current pattern index number */
+ int ign; /* 1 => ignore this group, 0 => check it */
+ int icnt; /* groups ignored */
+ int ccnt; /* groups to be checked */
+
+ /* firewall */
+ if (grp == NULL)
+ die("internal error #5: grp is NULL");
+ if (igcl == NULL)
+ die("internal error $6: igcl is NULL");
+ if (D_BUG)
+ warn("STATUS: determining which groups to ignore");
+
+ /* if nothing to do, return quickly */
+ if (grplen <= 0 || iglen <= 0) {
+ return;
+ }
+
+ /* examine each group */
+ icnt = 0;
+ ccnt = 0;
+ for (g=0; g < grplen; ++g) {
+
+ /* check the group to examine */
+ gp = &grp[g];
+ if (gp->ignore) {
+ /* already ignored no need to examine */
+ continue;
+ }
+
+ /* check group against all patterns */
+ ign = 0;
+ for (p=0, pp=igcl; p < iglen; ++p, ++pp) {
+
+ /* if pattern has a specific type, check it first */
+ if (pp->type_match) {
+
+ /* specific type required, check for match */
+ switch (gp->type[0]) {
+ case 'y':
+ if (! pp->y_type) continue; /* pattern does not apply */
+ break;
+ case 'm':
+ if (! pp->m_type) continue; /* pattern does not apply */
+ break;
+ case 'n':
+ if (! pp->n_type) continue; /* pattern does not apply */
+ break;
+ case 'j':
+ if (! pp->j_type) continue; /* pattern does not apply */
+ break;
+ case 'x':
+ if (! pp->x_type) continue; /* pattern does not apply */
+ break;
+ case '=':
+ if (! pp->eq_type) continue; /* pattern does not apply */
+ if (pp->epat != NULL && !uwildmat(&gp->type[1], pp->epat)) {
+ /* equiv pattern doesn't match, patt does not apply */
+ continue;
+ }
+ break;
+ }
+ }
+
+ /* perform a match on group name */
+ if (uwildmat(gp->name, pp->pat)) {
+ /* this pattern fully matches, use the ignore value */
+ ign = pp->ignore;
+ }
+ }
+
+ /* if this group is to be ignored, note it */
+ if (ign) {
+ switch (gp->hostid) {
+ case HOSTID1:
+ if (ign_host1_flag) {
+ gp->ignore |= CHECK_IGNORE;
+ ++icnt;
+ }
+ break;
+ case HOSTID2:
+ if (ign_host2_flag) {
+ gp->ignore |= CHECK_IGNORE;
+ ++icnt;
+ }
+ break;
+ default:
+ die("newsgroup %s bad hostid: %d", gp->name, gp->hostid);
+ }
+ } else {
+ ++ccnt;
+ }
+ }
+ if (D_BUG)
+ warn("STATUS: examined %d groups: %d ignored, %d to be checked",
+ grplen, icnt, ccnt);
+}
+
+/*
+ * merge_cmp - qsort compare function for later group merge
+ *
+ * given:
+ * a group a to compare
+ * b group b to compare
+ *
+ * returns:
+ * >0 a > b
+ * 0 a == b elements match (fatal error if a and b are different)
+ * <0 a < b
+ *
+ * To speed up group comparison, we compare by the following items listed
+ * in order of sorting:
+ *
+ * group name
+ * hostid (host1 ahead of host2)
+ * linenum (active file line number)
+ */
+static int
+merge_cmp(arg_a, arg_b)
+ const void *arg_a; /* first qsort compare arg */
+ const void *arg_b; /* first qsort compare arg */
+{
+ const struct grp *a = arg_a; /* group a to compare */
+ const struct grp *b = arg_b; /* group b to compare */
+ int i;
+
+ /* firewall */
+ if (a == b) {
+ /* we guess this could happen */
+ return(0);
+ }
+
+ /* compare group names */
+ i = strcmp(a->name, b->name);
+ if (i != 0) {
+ return i;
+ }
+
+ /* compare hostid's */
+ if (a->hostid != b->hostid) {
+ if (a->hostid > b->hostid) {
+ return 1;
+ } else {
+ return -1;
+ }
+ }
+
+ /* compare active line numbers */
+ if (a->linenum != b->linenum) {
+ if (a->linenum > b->linenum) {
+ return 1;
+ } else {
+ return -1;
+ }
+ }
+
+ /* two different elements match, this should not happen! */
+ die("two internal grp elements match!");
+ /*NOTREACHED*/
+}
+
+/*
+ * merge_grps - compare groups from both hosts
+ *
+ * given:
+ * grp array of groups
+ * grplen length of grp array in elements
+ * host1 name of host with HOSTID1
+ * host2 name of host with HOSTID2
+ *
+ * This routine will select which groups to output form a merged active file.
+ */
+static void
+merge_grps(grp, grplen, host1, host2)
+ struct grp *grp; /* array of groups */
+ int grplen; /* length of grp array in elements */
+ char *host1; /* name of host with HOSTID1 */
+ char *host2; /* name of host with HOSTID2 */
+{
+ int cur; /* current group index being examined */
+ int nxt; /* next group index being examined */
+ int outcnt; /* groups to output */
+ int rmcnt; /* groups to remove */
+ int h1_probs; /* =type problem groups from host1 */
+ int h2_probs; /* =type problem groups from host2 */
+
+ /* firewall */
+ if (grp == NULL)
+ die("internal error #7: grp is NULL");
+
+ /* sort groups for the merge */
+ if (D_BUG)
+ warn("STATUS: sorting groups");
+ qsort((char *)grp, grplen, sizeof(grp[0]), merge_cmp);
+
+ /* mark =type problem groups from host2, if needed */
+ h2_probs = mark_eq_probs(grp, grplen, l_host2_flag, host1, host2);
+
+ /*
+ * We will walk thru the sorted group array, looking for pairs
+ * among the groups that we have not already ignored.
+ *
+ * If a host has duplicate groups, then the duplicates will
+ * be next to each other.
+ *
+ * If both hosts have the name group, they will be next to each other.
+ */
+ if (D_BUG)
+ warn("STATUS: merging groups");
+ outcnt = 0;
+ rmcnt = 0;
+ for (cur=0; cur < grplen; cur=nxt) {
+
+ /* determine the next group index */
+ nxt = cur+1;
+
+ /* skip if this group is ignored */
+ if (grp[cur].ignore) {
+ continue;
+ }
+ /* assert: cur is not ignored */
+
+ /* check for duplicate groups from the same host */
+ while (nxt < grplen) {
+
+ /* mark the later as a duplicate */
+ if (grp[cur].hostid == grp[nxt].hostid &&
+ strcmp(grp[cur].name, grp[nxt].name) == 0) {
+ grp[nxt].ignore |= ERROR_DUP;
+ if (!QUIET(grp[cur].hostid))
+ warn("lines %d and %d from %s refer to the same group",
+ grp[cur].linenum, grp[nxt].linenum,
+ ((grp[cur].hostid == HOSTID1) ? host1 : host2));
+ ++nxt;
+ } else {
+ break;
+ }
+ }
+ /* assert: cur is not ignored */
+ /* assert: cur & nxt are not the same group from the same host */
+
+ /* if nxt is ignored, look for the next non-ignored group */
+ while (nxt < grplen && grp[nxt].ignore) {
+ ++nxt;
+ }
+ /* assert: cur is not ignored */
+ /* assert: nxt is not ignored or is beyond end */
+ /* assert: cur & nxt are not the same group from the same host */
+
+ /* case: cur and nxt are the same group */
+ if (nxt < grplen && strcmp(grp[cur].name, grp[nxt].name) == 0) {
+
+ /* assert: cur is HOSTID1 */
+ if (grp[cur].hostid != HOSTID1)
+ die("internal error #8: grp[%d].hostid: %d != %d",
+ cur, grp[cur].hostid, HOSTID1);
+
+ /*
+ * Both hosts have the same group. Make host1 group type
+ * match host2. (it may already)
+ */
+ grp[cur].output = 1;
+ grp[cur].outhi = (host2_hilow_all ? grp[nxt].hi : grp[cur].hi);
+ grp[cur].outlow = (host2_hilow_all ? grp[nxt].low : grp[cur].low);
+ grp[cur].outtype = grp[nxt].type;
+ ++outcnt;
+
+ /* do not process nxt, skip to the one beyond */
+ ++nxt;
+
+ /* case: cur and nxt are different groups */
+ } else {
+
+ /*
+ * if cur is host2, then host1 doesn't have it, so output it
+ */
+ if (grp[cur].hostid == HOSTID2) {
+ grp[cur].output = 1;
+ grp[cur].outhi = (host2_hilow_newgrp ? grp[cur].hi : DEF_HI);
+ grp[cur].outlow = (host2_hilow_newgrp ? grp[cur].low : DEF_LOW);
+ grp[cur].outtype = grp[cur].type;
+ ++outcnt;
+
+ /*
+ * If cur is host1, then host2 doesn't have it.
+ * Mark for removal if -m was not given.
+ */
+ } else {
+ grp[cur].output = 1;
+ grp[cur].outhi = grp[cur].hi;
+ grp[cur].outlow = grp[cur].low;
+ grp[cur].outtype = grp[cur].type;
+ if (! m_flag) {
+ grp[cur].remove = 1;
+ ++rmcnt;
+ }
+ }
+
+ /* if no more groups to examine, we are done */
+ if (nxt >= grplen) {
+ break;
+ }
+ }
+ }
+
+ /* mark =type problem groups from host1, if needed */
+ h1_probs = mark_eq_probs(grp, grplen, l_host1_flag, host1, host2);
+
+ /* all done */
+ if (D_BUG) {
+ warn("STATUS: sort-merge passed thru %d groups", outcnt);
+ warn("STATUS: sort-merge marked %d groups for removal", rmcnt);
+ warn("STATUS: marked %d =type error groups from host1", h1_probs);
+ warn("STATUS: marked %d =type error groups from host2", h2_probs);
+ }
+ return;
+}
+
+/*
+ * active_cmp - qsort compare function for active file style output
+ *
+ * given:
+ * a group a to compare
+ * b group b to compare
+ *
+ * returns:
+ * >0 a > b
+ * 0 a == b elements match (fatal error if a and b are different)
+ * <0 a < b
+ *
+ * This sort will sort groups so that the lines that will we output
+ * host1 lines followed by host2 lines. Thus, we will sort by
+ * the following keys:
+ *
+ * hostid (host1 ahead of host2)
+ * linenum (active file line number)
+ */
+static int
+active_cmp(arg_a, arg_b)
+ const void *arg_a; /* first qsort compare arg */
+ const void *arg_b; /* first qsort compare arg */
+{
+ const struct grp *a = arg_a; /* group a to compare */
+ const struct grp *b = arg_b; /* group b to compare */
+
+ /* firewall */
+ if (a == b) {
+ /* we guess this could happen */
+ return(0);
+ }
+
+ /* compare hostid's */
+ if (a->hostid != b->hostid) {
+ if (a->hostid > b->hostid) {
+ return 1;
+ } else {
+ return -1;
+ }
+ }
+
+ /* compare active line numbers */
+ if (a->linenum != b->linenum) {
+ if (a->linenum > b->linenum) {
+ return 1;
+ } else {
+ return -1;
+ }
+ }
+
+ /* two different elements match, this should not happen! */
+ die("two internal grp elements match!");
+ /*NOTREACHED*/
+}
+
+/*
+ * output_grps - output the result of the merge
+ *
+ * given:
+ * grp array of groups
+ * grplen length of grp array in elements
+ */
+static void
+output_grps(grp, grplen)
+ struct grp *grp; /* array of groups */
+ int grplen; /* length of grp array in elements */
+{
+ int add; /* number of groups added */
+ int change; /* number of groups changed */
+ int remove; /* number of groups removed */
+ int no_new_dir; /* number of new groups with missing/empty dirs */
+ int new_dir; /* number of new groupsm, non-empty dir no water chg */
+ int water_change; /* number of new groups where hi&low water changed */
+ int work; /* adds + changes + removals */
+ int same; /* the number of groups the same */
+ int ignore; /* host1 newsgroups to ignore */
+ int not_done; /* exec errors and execs not performed */
+ int rm_cycle; /* 1 => removals only, 0 => adds & changes only */
+ int sleep_msg; /* 1 => -o x sleep message was given */
+ int top_ignore; /* number of groups ignored because of no top level */
+ int restore; /* host1 groups restored due to -o a1 */
+ double host1_same; /* % of host1 that is the same */
+ int i;
+
+ /* firewall */
+ if (grp == NULL)
+ die("internal error #9: grp is NULL");
+
+ /*
+ * If -a1 was given, mark for output any host1 newsgroup that was
+ * simply ignored due to the -i ign_file.
+ */
+ if (host1_ign_print) {
+ restore = 0;
+ for (i=0; i < grplen; ++i) {
+ if (grp[i].hostid == HOSTID1 &&
+ (grp[i].ignore == CHECK_IGNORE ||
+ grp[i].ignore == CHECK_TYPE ||
+ grp[i].ignore == (CHECK_IGNORE|CHECK_TYPE))) {
+ /* force group to output and not be ignored */
+ grp[i].ignore = 0;
+ grp[i].output = 1;
+ grp[i].remove = 0;
+ grp[i].outhi = grp[i].hi;
+ grp[i].outlow = grp[i].low;
+ grp[i].outtype = grp[i].type;
+ ++restore;
+ }
+ }
+ if (D_BUG)
+ warn("STATUS: restored %d host1 groups", restore);
+ }
+
+ /*
+ * If -T, ignore new top level groups from host2
+ */
+ if (no_new_hier) {
+ top_ignore = 0;
+ for (i=0; i < grplen; ++i) {
+ /* look at new newsgroups */
+ if (grp[i].hostid == HOSTID2 &&
+ grp[i].output != 0 &&
+ new_top_hier(grp[i].name)) {
+ /* no top level ignore this new group */
+ grp[i].ignore |= CHECK_HIER;
+ grp[i].output = 0;
+ if (D_BUG)
+ warn("ignore new newsgroup: %s, new hierarchy",
+ grp[i].name);
+ ++top_ignore;
+ }
+ }
+ if (D_SUMMARY)
+ warn("STATUS: ignored %d new newsgroups due to new hierarchy",
+ top_ignore);
+ }
+
+ /* sort by active file order if active style output (-a) */
+ if (o_flag == OUTPUT_ACTIVE) {
+ if (D_BUG)
+ warn("STATUS: sorting groups in output order");
+ qsort((char *)grp, grplen, sizeof(grp[0]), active_cmp);
+ }
+
+ /*
+ * Determine the % of lines from host1 active file that remain unchanged
+ * ignoring any low/high water mark changes.
+ *
+ * Determine the number of old groups that will remain the same
+ * the number of new groups that will be added.
+ */
+ add = 0;
+ change = 0;
+ remove = 0;
+ same = 0;
+ ignore = 0;
+ no_new_dir = 0;
+ new_dir = 0;
+ water_change = 0;
+ for (i=0; i < grplen; ++i) {
+ /* skip non-output ... */
+ if (grp[i].output == 0) {
+ if (grp[i].hostid == HOSTID1) {
+ ++ignore;
+ }
+ continue;
+
+ /* case: group needs removal */
+ } else if (grp[i].remove) {
+ ++remove;
+
+ /* case: group is from host2, so we need a newgroup */
+ } else if (grp[i].hostid == HOSTID2) {
+ ++add;
+
+ /* case: group is from host1, but the type changed */
+ } else if (grp[i].type != grp[i].outtype &&
+ strcmp(grp[i].type,grp[i].outtype) != 0) {
+ ++change;
+
+ /* case: group did not change */
+ } else {
+ ++same;
+ }
+ }
+ work = add+change+remove;
+ if (same+work+host1_errs <= 0) {
+ /* no lines, no work, no errors == nothing changed == 100% the same */
+ host1_same = (double)100.0;
+ } else {
+ /* calculate % unchanged */
+ host1_same = (double)100.0 *
+ ((double)same / (double)(same+work+host1_errs));
+ }
+ if (D_BUG) {
+ warn("STATUS: same=%d add=%d, change=%d, remove=%d",
+ same, add, change, remove);
+ warn("STATUS: ignore=%d, work=%d, err=%d",
+ ignore, work, host1_errs);
+ warn("STATUS: same+work+err=%d, host1_same=%.2f%%",
+ same+work+host1_errs, host1_same);
+ }
+
+ /*
+ * Bail out if we too few lines in host1 active file (ignoring
+ * low/high water mark changes) remaining unchanged.
+ *
+ * We define change as:
+ *
+ * line errors from host1 active file
+ * newsgroups to be added to host1
+ * newsgroups to be removed from host1
+ * newsgroups to be change in host1
+ */
+ if (host1_same < p_flag) {
+ warn("HALT: lines unchanged: %.2f%% < min change limit: %.2f%%",
+ host1_same, p_flag);
+ warn(" No output or commands executed. Determine if the degree");
+ warn(" of changes is okay and re-execute with a lower -p value");
+ die(" or with the problem fixed.");
+ }
+
+ /*
+ * look at all groups
+ *
+ * If we are not producing active file output, we must do removals
+ * before we do any adds and changes.
+ *
+ * We recalculate the work stats in finer detail as well as noting how
+ * many actions were successful.
+ */
+ add = 0;
+ change = 0;
+ remove = 0;
+ same = 0;
+ ignore = 0;
+ work = 0;
+ not_done = 0;
+ sleep_msg = 0;
+ rm_cycle = ((o_flag == OUTPUT_ACTIVE) ? 0 : 1);
+ do {
+ for (i=0; i < grplen; ++i) {
+
+ /* if -o Ax, output ignored non-error groups too */
+
+ /*
+ * skip non-output ...
+ *
+ * but if '-a' and active output mode, then don't skip ignored,
+ * non-error, non-removed groups from host1
+ */
+ if (grp[i].output == 0) {
+ if (grp[i].hostid == HOSTID1) {
+ ++ignore;
+ }
+ continue;
+ }
+
+ /* case: output active lines */
+ if (o_flag == OUTPUT_ACTIVE) {
+
+ /* case: group needs removal */
+ if (grp[i].remove) {
+ ++remove;
+ ++work;
+
+ /* case: group will be kept */
+ } else {
+
+ /* output in active file format */
+ printf("%s %s %s %s\n",
+ grp[i].name, grp[i].outhi, grp[i].outlow,
+ grp[i].outtype);
+
+ /* if -v level is high enough, do group accounting */
+ if (D_IF_SUMM) {
+
+ /* case: group is from host2, so we need a newgroup */
+ if (grp[i].hostid == HOSTID2) {
+ ++add;
+ ++work;
+
+ /* case: group is from host1, but the type changed */
+ } else if (grp[i].type != grp[i].outtype &&
+ strcmp(grp[i].type,grp[i].outtype) != 0) {
+ ++change;
+ ++work;
+
+ /* case: group did not change */
+ } else {
+ ++same;
+ }
+ }
+ }
+
+ /* case: output ctlinnd commands */
+ } else if (o_flag == OUTPUT_CTLINND) {
+
+ /* case: group needs removal */
+ if (grp[i].remove) {
+
+ /* output rmgroup */
+ if (rm_cycle) {
+ printf("ctlinnd rmgroup %s\n", grp[i].name);
+ ++remove;
+ ++work;
+ }
+
+ /* case: group is from host2, so we need a newgroup */
+ } else if (grp[i].hostid == HOSTID2) {
+
+ /* output newgroup */
+ if (! rm_cycle) {
+ printf("ctlinnd newgroup %s %s %s\n",
+ grp[i].name, grp[i].outtype, new_name);
+ ++add;
+ ++work;
+ }
+
+ /* case: group is from host1, but the type changed */
+ } else if (grp[i].type != grp[i].outtype &&
+ strcmp(grp[i].type,grp[i].outtype) != 0) {
+
+ /* output changegroup */
+ if (! rm_cycle) {
+ printf("ctlinnd changegroup %s %s\n",
+ grp[i].name, grp[i].outtype);
+ ++change;
+ ++work;
+ }
+
+ /* case: group did not change */
+ } else {
+ if (! rm_cycle) {
+ ++same;
+ }
+ }
+
+ /* case: exec ctlinnd commands */
+ } else if (o_flag == OUTPUT_EXEC || o_flag == OUTPUT_IEXEC) {
+
+ /* warn about sleeping if needed and first time */
+ if (o_flag == OUTPUT_EXEC && z_flag > 0 && sleep_msg == 0) {
+ if (D_SUMMARY)
+ warn("will sleep %d seconds before each fork/exec",
+ z_flag);
+ sleep_msg = 1;
+ }
+
+ /* case: group needs removal */
+ if (grp[i].remove) {
+
+ /* exec rmgroup */
+ if (rm_cycle) {
+ if (D_REPORT && o_flag == OUTPUT_EXEC)
+ warn("rmgroup %s", grp[i].name);
+ if (! exec_cmd(o_flag, "rmgroup",
+ grp[i].name, NULL, NULL)) {
+ ++not_done;
+ } else {
+ ++remove;
+ ++work;
+ }
+ }
+
+ /* case: group is from host2, so we need a newgroup */
+ } else if (grp[i].hostid == HOSTID2) {
+
+ /* exec newgroup */
+ if (!rm_cycle) {
+ if (D_REPORT && o_flag == OUTPUT_EXEC)
+ warn("newgroup %s %s %s",
+ grp[i].name, grp[i].outtype, new_name);
+ if (! exec_cmd(o_flag, "newgroup", grp[i].name,
+ grp[i].outtype, new_name)) {
+ ++not_done;
+ } else {
+ ++add;
+ ++work;
+ }
+ }
+
+ /* case: group is from host1, but the type changed */
+ } else if (grp[i].type != grp[i].outtype &&
+ strcmp(grp[i].type,grp[i].outtype) != 0) {
+
+ /* exec changegroup */
+ if (!rm_cycle) {
+ if (D_REPORT && o_flag == OUTPUT_EXEC)
+ warn("changegroup %s %s",
+ grp[i].name, grp[i].outtype);
+ if (! exec_cmd(o_flag, "changegroup", grp[i].name,
+ grp[i].outtype, NULL)) {
+ ++not_done;
+ } else {
+ ++change;
+ ++work;
+ }
+ }
+
+ /* case: group did not change */
+ } else {
+ if (! rm_cycle) {
+ ++same;
+ }
+ }
+ }
+ }
+ } while (--rm_cycle >= 0);
+
+ /* final accounting, if -v */
+ if (D_SUMMARY || (D_IF_SUMM && (work > 0 || not_done > 0))) {
+ warn("STATUS: %d group(s)", add+remove+change+same);
+ warn("STATUS: %d group(s)%s added", add,
+ ((o_flag == OUTPUT_EXEC || o_flag == OUTPUT_IEXEC) ?
+ "" : " to be"));
+ warn("STATUS: %d group(s)%s removed", remove,
+ ((o_flag == OUTPUT_EXEC || o_flag == OUTPUT_IEXEC) ?
+ "" : " to be"));
+ warn("STATUS: %d group(s)%s changed", change,
+ ((o_flag == OUTPUT_EXEC || o_flag == OUTPUT_IEXEC) ?
+ "" : " to be"));
+ warn("STATUS: %d group(s) %s the same", same,
+ ((o_flag == OUTPUT_EXEC || o_flag == OUTPUT_IEXEC) ?
+ "remain" : "are"));
+ warn("STATUS: %.2f%% of lines unchanged", host1_same);
+ warn("STATUS: %d group(s) ignored", ignore);
+ if (o_flag == OUTPUT_EXEC || o_flag == OUTPUT_IEXEC)
+ warn("STATUS: %d exec(s) not performed", not_done);
+ }
+}
+
+/*
+ * error_mark - mark for removal, error groups from a given host
+ *
+ * given:
+ * grp array of groups
+ * grplen length of grp array in elements
+ * hostid host to mark error groups for removal
+ */
+static void
+error_mark(grp, grplen, hostid)
+ struct grp *grp; /* array of groups */
+ int grplen; /* length of grp array in elements */
+ int hostid; /* host to mark error groups for removal */
+{
+ int i;
+ int errcnt;
+
+ /* firewall */
+ if (grp == NULL)
+ die("internal error #11: grp is NULL");
+
+ /* loop thru groups, looking for error groups from a given host */
+ errcnt = 0;
+ for (i=0; i < grplen; ++i) {
+
+ /* skip if not from hostid */
+ if (grp[i].hostid != hostid) {
+ continue;
+ }
+
+ /* mark for removal if an error group not already removed */
+ if (IS_ERROR(grp[i].ignore)) {
+
+ /* mark for removal */
+ if (grp[i].output != 1 || grp[i].remove != 1) {
+ grp[i].output = 1;
+ grp[i].remove = 1;
+ }
+ ++errcnt;
+ }
+ }
+
+ /* all done */
+ if (D_SUMMARY || (D_IF_SUMM && errcnt > 0))
+ warn("STATUS: marked %d error groups for removal", errcnt);
+ return;
+}
+
+/*
+ * eq_merge_cmp - qsort compare function for =type group processing
+ *
+ * given:
+ * a =group a to compare
+ * b =group b to compare
+ *
+ * returns:
+ * >0 a > b
+ * 0 a == b elements match (fatal error if a and b are different)
+ * <0 a < b
+ *
+ * To speed up group comparison, we compare by the following items listed
+ * in order of sorting:
+ *
+ * skip (non-skipped groups after skipped ones)
+ * group equiv name
+ * group name
+ * hostid (host1 ahead of host2)
+ * linenum (active file line number)
+ */
+static int
+eq_merge_cmp(arg_a, arg_b)
+ const void *arg_a; /* first qsort compare arg */
+ const void *arg_b; /* first qsort compare arg */
+{
+ const struct eqgrp *a = arg_a; /* group a to compare */
+ const struct eqgrp *b = arg_b; /* group b to compare */
+ int i;
+
+ /* firewall */
+ if (a == b) {
+ /* we guess this could happen */
+ return(0);
+ }
+
+ /* compare skip values */
+ if (a->skip != b->skip) {
+ if (a->skip > b->skip) {
+ /* a is skipped, b is not */
+ return 1;
+ } else {
+ /* b is skipped, a is not */
+ return -1;
+ }
+ }
+
+ /* compare the names the groups are equivalenced to */
+ i = strcmp(a->eq, b->eq);
+ if (i != 0) {
+ return i;
+ }
+
+ /* compare the group names themselves */
+ i = strcmp(a->g->name, b->g->name);
+ if (i != 0) {
+ return i;
+ }
+
+ /* compare hostid's */
+ if (a->g->hostid != b->g->hostid) {
+ if (a->g->hostid > b->g->hostid) {
+ return 1;
+ } else {
+ return -1;
+ }
+ }
+
+ /* compare active line numbers */
+ if (a->g->linenum != b->g->linenum) {
+ if (a->g->linenum > b->g->linenum) {
+ return 1;
+ } else {
+ return -1;
+ }
+ }
+
+ /* two different elements match, this should not happen! */
+ die("two internal eqgrp elements match!");
+}
+
+/*
+ * mark_eq_probs - mark =type groups from a given host that have problems
+ *
+ * given:
+ * grp sorted array of groups
+ * grplen length of grp array in elements
+ * hostid host to mark error groups for removal, or NOHOST
+ * host1 name of host with HOSTID1
+ * host2 name of host with HOSTID2
+ *
+ * This function assumes that the grp array has been sorted by name.
+ */
+static int
+mark_eq_probs(grp, grplen, hostid, host1, host2)
+ struct grp *grp; /* array of groups */
+ int grplen; /* length of grp array in elements */
+ int hostid; /* host to mark error groups for removal */
+ char *host1; /* name of host with HOSTID1 */
+ char *host2; /* name of host with HOSTID2 */
+{
+ struct eqgrp *eqgrp; /* =type pointer array */
+ int eq_cnt; /* number of =type groups from host */
+ int new_eq_cnt; /* number of =type groups remaining */
+ int missing; /* =type groups equiv to missing groups */
+ int cycled; /* =type groups equiv to themselves */
+ int chained; /* =type groups in long chain or loop */
+ int cmp; /* strcmp of two names */
+ int step; /* equiv loop step */
+ int i;
+ int j;
+
+ /* firewall */
+ if (grp == NULL)
+ die("internal error #12: grp is NULL");
+ if (hostid == NOHOST) {
+ /* nothing to detect, nothing else to do */
+ return 0;
+ }
+
+ /* count the =type groups from hostid that are not in error */
+ eq_cnt = 0;
+ for (i=0; i < grplen; ++i) {
+ if (grp[i].hostid == hostid &&
+ ! IS_ERROR(grp[i].ignore) &&
+ grp[i].type != NULL &&
+ grp[i].type[0] == '=') {
+ ++eq_cnt;
+ }
+ }
+ if (D_BUG && hostid != NOHOST)
+ warn("STATUS: host%d has %d =type groups", hostid, eq_cnt);
+
+ /* if no groups, then there is nothing to do */
+ if (eq_cnt == 0) {
+ return 0;
+ }
+
+ /* setup the =group record array */
+ eqgrp = xmalloc(eq_cnt * sizeof(eqgrp[0]));
+ for (i=0, j=0; i < grplen && j < eq_cnt; ++i) {
+ if (grp[i].hostid == hostid &&
+ ! IS_ERROR(grp[i].ignore) &&
+ grp[i].type != NULL &&
+ grp[i].type[0] == '=') {
+
+ /* initialize record */
+ eqgrp[j].skip = 0;
+ eqgrp[j].g = &grp[i];
+ eqgrp[j].eq = &(grp[i].type[1]);
+ ++j;
+ }
+ }
+
+ /*
+ * try to resolve =type groups in at least EQ_LOOP equiv links
+ */
+ new_eq_cnt = eq_cnt;
+ missing = 0;
+ cycled = 0;
+ for (step=0; step < EQ_LOOP && new_eq_cnt >= 0; ++step) {
+
+ /* sort the =group record array */
+ qsort((char *)eqgrp, eq_cnt, sizeof(eqgrp[0]), eq_merge_cmp);
+
+ /* look for the groups to which =type group point at */
+ eq_cnt = new_eq_cnt;
+ for (i=0, j=0; i < grplen && j < eq_cnt; ++i) {
+
+ /* we will skip any group in error or from the wrong host */
+ if (grp[i].hostid != hostid || IS_ERROR(grp[i].ignore)) {
+ continue;
+ }
+
+ /* we will skip any skipped eqgrp's */
+ if (eqgrp[j].skip) {
+ /* try the same group against the next eqgrp */
+ --i;
+ ++j;
+ continue;
+ }
+
+ /* compare the =name of the eqgrp with the name of the grp */
+ cmp = strcmp(grp[i].name, eqgrp[j].eq);
+
+ /* case: this group is pointed at by an eqgrp */
+ if (cmp == 0) {
+
+ /* see if we have looped around to the original group name */
+ if (strcmp(grp[i].name, eqgrp[j].g->name) == 0) {
+
+ /* note the detected loop */
+ if (! QUIET(hostid))
+ warn("%s from %s line %d =loops around to itself",
+ eqgrp[j].g->name,
+ ((eqgrp[j].g->hostid == HOSTID1) ? host1 : host2),
+ eqgrp[j].g->linenum);
+ eqgrp[j].g->ignore |= ERROR_EQLOOP;
+
+ /* the =group is bad, so we don't need to bother with it */
+ eqgrp[j].skip = 1;
+ --new_eq_cnt;
+ ++cycled;
+ --i;
+ ++j;
+ continue;
+ }
+
+ /* if =group refers to a valid group, we are done with it */
+ if (grp[i].type != NULL && grp[i].type[0] != '=') {
+ eqgrp[j].skip = 1;
+ --new_eq_cnt;
+ /* otherwise note the equiv name */
+ } else {
+ eqgrp[j].eq = &(grp[i].type[1]);
+ }
+ --i;
+ ++j;
+
+ /* case: we missed the =name */
+ } else if (cmp > 0) {
+
+ /* mark the eqgrp in error */
+ eqgrp[j].g->ignore |= ERROR_NONEQ;
+ if (! QUIET(hostid))
+ warn("%s from %s line %d not equiv to a valid group",
+ eqgrp[j].g->name,
+ ((eqgrp[j].g->hostid == HOSTID1) ? host1 : host2),
+ eqgrp[j].g->linenum);
+
+ /* =group is bad, so we don't need to bother with it anymore */
+ eqgrp[j].skip = 1;
+ --new_eq_cnt;
+ ++missing;
+ ++j;
+ }
+ }
+
+ /* any remaining non-skipped eqgrps are bad */
+ while (j < eq_cnt) {
+
+ /* mark the eqgrp in error */
+ eqgrp[j].g->ignore |= ERROR_NONEQ;
+ if (! QUIET(hostid))
+ warn("%s from %s line %d isn't equiv to a valid group",
+ eqgrp[j].g->name,
+ ((hostid == HOSTID1) ? host1 : host2),
+ eqgrp[j].g->linenum);
+
+ /* the =group is bad, so we don't need to bother with it anymore */
+ eqgrp[j].skip = 1;
+ --new_eq_cnt;
+ ++missing;
+ ++j;
+ }
+ }
+
+ /* note groups that are in a long chain or loop */
+ chained = new_eq_cnt;
+ qsort((char *)eqgrp, eq_cnt, sizeof(eqgrp[0]), eq_merge_cmp);
+ for (j=0; j < new_eq_cnt; ++j) {
+
+ /* skip if already skipped */
+ if (eqgrp[j].skip == 1) {
+ continue;
+ }
+
+ /* mark as a long loop group */
+ eqgrp[j].g->ignore |= ERROR_LONGLOOP;
+ if (! QUIET(hostid))
+ warn("%s from %s line %d in a long equiv chain or loop > %d",
+ eqgrp[j].g->name,
+ ((hostid == HOSTID1) ? host1 : host2),
+ eqgrp[j].g->linenum, EQ_LOOP);
+ }
+
+ /* all done */
+ if (D_BUG) {
+ warn("%d =type groups from %s are not equiv to a valid group",
+ missing, ((hostid == HOSTID1) ? host1 : host2));
+ warn("%d =type groups from %s are equiv to themselves",
+ cycled, ((hostid == HOSTID1) ? host1 : host2));
+ warn("%d =type groups from %s are in a long chain or loop > %d",
+ chained, ((hostid == HOSTID1) ? host1 : host2), EQ_LOOP);
+ }
+ free(eqgrp);
+ return missing+cycled+chained;
+}
+
+/*
+ * exec_cmd - exec a ctlinnd command in forked process
+ *
+ * given:
+ * mode OUTPUT_EXEC or OUTPUT_IEXEC (interactive mode)
+ * cmd "changegroup", "newgroup", "rmgroup"
+ * grp name of group
+ * type type of group or NULL
+ * who newgroup creator or NULL
+ *
+ * returns:
+ * 1 exec was performed
+ * 0 exec was not performed
+ */
+static int
+exec_cmd(mode, cmd, grp, type, who)
+ int mode; /* OUTPUT_EXEC or OUTPUT_IEXEC (interactive mode) */
+ char *cmd; /* changegroup, newgroup or rmgroup */
+ char *grp; /* name of group to change, add, remove */
+ char *type; /* type of group or NULL */
+ char *who; /* newgroup creator or NULL */
+{
+ FILE *ch_stream = NULL; /* stream from a child process */
+ char buf[BUFSIZ+1]; /* interactive buffer */
+ int pid; /* pid of child process */
+ int io[2]; /* pair of pipe descriptors */
+ int status; /* wait status */
+ int exitval; /* exit status of the child */
+ char *p;
+
+ /* firewall */
+ if (cmd == NULL || grp == NULL)
+ die("internal error #13, cmd or grp is NULL");
+
+ /* if interactive, ask the question */
+ if (mode == OUTPUT_IEXEC) {
+
+ /* ask the question */
+ fflush(stdin);
+ fflush(stdout);
+ fflush(stderr);
+ if (type == NULL) {
+ printf("%s %s [yn]? ", cmd, grp);
+ } else if (who == NULL) {
+ printf("%s %s %s [yn]? ", cmd, grp, type);
+ } else {
+ printf("%s %s %s %s [yn]? ", cmd, grp, type, who);
+ }
+ fflush(stdout);
+ buf[0] = '\0';
+ buf[BUFSIZ] = '\0';
+ p = fgets(buf, BUFSIZ, stdin);
+ if (p == NULL) {
+ /* EOF/ERROR on interactive input, silently stop processing */
+ exit(43);
+ }
+
+ /* if non-empty line doesn't start with 'y' or 'Y', skip command */
+ if (buf[0] != 'y' && buf[0] != 'Y' && buf[0] != '\n') {
+ /* indicate nothing was done */
+ return 0;
+ }
+ }
+
+ /* build a pipe for output from child interactive mode */
+ if (mode == OUTPUT_IEXEC) {
+ if (pipe(io) < 0)
+ sysdie("pipe create failed");
+
+ /* setup a fake pipe to /dev/null for non-interactive mode */
+ } else {
+ io[READ_SIDE] = open(DEV_NULL, 0);
+ if (io[READ_SIDE] < 0)
+ sysdie("unable to open %s for reading", DEV_NULL);
+ io[WRITE_SIDE] = open(DEV_NULL, 1);
+ if (io[WRITE_SIDE] < 0)
+ sysdie("unable to open %s for writing", DEV_NULL);
+ }
+
+ /* pause if in non-interactive mode so as to not busy-out the server */
+ if (mode == OUTPUT_EXEC && z_flag > 0) {
+ if (D_BUG)
+ warn("sleeping %d seconds before fork/exec", z_flag);
+ /* be sure they know what we are stalling */
+ fflush(stderr);
+ sleep(z_flag);
+ }
+
+ /* fork the child process */
+ fflush(stdout);
+ fflush(stderr);
+ pid = fork();
+ if (pid == -1)
+ sysdie("fork failed");
+
+ /* case: child process */
+ if (pid == 0) {
+
+ /*
+ * prep file descriptors
+ */
+ fclose(stdin);
+ close(io[READ_SIDE]);
+ if (dup2(io[WRITE_SIDE], 1) < 0)
+ sysdie("child: dup of write I/O pipe to stdout failed");
+ if (dup2(io[WRITE_SIDE], 2) < 0)
+ sysdie("child: dup of write I/O pipe to stderr failed");
+
+ /* exec the ctlinnd command */
+ p = concatpath(innconf->pathbin, _PATH_CTLINND);
+ if (type == NULL) {
+ execl(p,
+ CTLINND_NAME, CTLINND_TIME_OUT, cmd, grp, (char *) 0);
+ } else if (who == NULL) {
+ execl(p,
+ CTLINND_NAME, CTLINND_TIME_OUT, cmd, grp, type, (char *) 0);
+ } else {
+ execl(p,
+ CTLINND_NAME, CTLINND_TIME_OUT, cmd, grp, type, who, (char *) 0);
+ }
+
+ /* child exec failed */
+ sysdie("child process exec failed");
+
+ /* case: parent process */
+ } else {
+
+ /* prep file descriptors */
+ if (mode != OUTPUT_IEXEC) {
+ close(io[READ_SIDE]);
+ }
+ close(io[WRITE_SIDE]);
+
+ /* print a line from the child, if interactive */
+ if (mode == OUTPUT_IEXEC) {
+
+ /* read what the child says */
+ buf[0] = '\0';
+ buf[BUFSIZ] = '\0';
+ ch_stream = fdopen(io[READ_SIDE], "r");
+ if (ch_stream == NULL)
+ sysdie("fdopen of pipe failed");
+ p = fgets(buf, BUFSIZ, ch_stream);
+
+ /* print what the child said, if anything */
+ if (p != NULL) {
+ if (buf[strlen(buf)-1] == '\n')
+ buf[strlen(buf)-1] = '\0';
+ warn(" %s", buf);
+ }
+ }
+
+ /* look for abnormal child termination/status */
+ errno = 0;
+ while (wait(&status) < 0) {
+ if (errno == EINTR) {
+ /* just an interrupt, try to wait again */
+ errno = 0;
+ } else {
+ sysdie("wait returned -1");
+ }
+ }
+ if (mode == OUTPUT_IEXEC) {
+ /* close the pipe now that we are done with reading it */
+ fclose(ch_stream);
+ }
+ if (WIFSTOPPED(status)) {
+ warn(" %s %s %s%s%s%s%s stopped",
+ CTLINND_NAME, cmd, grp,
+ (type ? "" : " "), (type ? type : ""),
+ (who ? "" : " "), (who ? who : ""));
+ /* assume no work was done */
+ return 0;
+ }
+ if (WIFSIGNALED(status)) {
+ warn(" %s %s %s%s%s%s%s killed by signal %d",
+ CTLINND_NAME, cmd, grp,
+ (type ? "" : " "), (type ? type : ""),
+ (who ? "" : " "), (who ? who : ""), WTERMSIG(status));
+ /* assume no work was done */
+ return 0;
+ }
+ if (!WIFEXITED(status)) {
+ warn(" %s %s %s%s%s%s%s returned unknown wait status: 0x%x",
+ CTLINND_NAME, cmd, grp,
+ (type ? "" : " "), (type ? type : ""),
+ (who ? "" : " "), (who ? who : ""), status);
+ /* assume no work was done */
+ return 0;
+ }
+ exitval = WEXITSTATUS(status);
+ if (exitval != 0) {
+ warn(" %s %s %s%s%s%s%s exited with status: %d",
+ CTLINND_NAME, cmd, grp,
+ (type ? "" : " "), (type ? type : ""),
+ (who ? "" : " "), (who ? who : ""), exitval);
+ /* assume no work was done */
+ return 0;
+ }
+ }
+
+ /* all done */
+ return 1;
+}
+
+/*
+ * new_top_hier - determine if the newsgroup represents a new hierarchy
+ *
+ * Determine of the newsgroup name is a new hierarchy.
+ *
+ * given:
+ * name name of newsgroup to check
+ *
+ * returns:
+ * false hierarchy already exists
+ * true hierarchy does not exist, name represents a new hierarchy
+ *
+ * NOTE: This function assumes that we are at the top of the news spool.
+ */
+static int
+new_top_hier(name)
+ char *name;
+{
+ struct stat statbuf; /* stat of the hierarchy */
+ int result; /* return result */
+ char *dot;
+
+ /*
+ * temp change name to just the top level
+ */
+ dot = strchr(name, '.');
+ if (dot != NULL) {
+ *dot = '\0';
+ }
+
+ /*
+ * determine if we can find this top level hierarchy directory
+ */
+ result = !(stat(name, &statbuf) >= 0 && S_ISDIR(statbuf.st_mode));
+ /* restore name */
+ if (dot != NULL) {
+ *dot = '.';
+ }
+
+ /*
+ * return the result
+ */
+ return result;
+}
--- /dev/null
+#! /bin/sh
+# fixscript will replace this line with code to load innshellvars
+
+# @(#) $Id: actsyncd.in 6490 2003-10-18 05:49:04Z rra $
+# @(#) Under RCS control in /usr/local/news/src/inn/local/RCS/actsyncd.sh,v
+#
+# actsyncd - actsync daemon
+#
+# usage:
+# actsyncd [-x] config_file [debug_level [debug_outfmt]]
+#
+# -x xexec instead of reload
+# config_file name of file used to determine how to run actsync
+# debug_level force no action and use -v debug_level
+# debug_outfmt change -o a1 output to -o debug_outfmt for debug
+
+# By: Landon Curt Noll chongo@toad.com (chongo was here /\../\)
+#
+# Copyright (c) Landon Curt Noll, 1993.
+# All rights reserved.
+#
+# Permission to use and modify is hereby granted so long as this
+# notice remains. Use at your own risk. No warranty is implied.
+
+# preset vars
+#
+
+# Our lock file
+LOCK="${LOCKS}/LOCK.actsyncd"
+# where actsync is located
+ACTSYNC="${PATHBIN}/actsync"
+# exit value of actsync if unable to get an active file
+NOSYNC=127
+
+# parse args
+#
+if [ $# -gt 1 ]; then
+ case $1 in
+ -x|-r) shift ;; # no longer relevant
+ esac
+fi
+case $# in
+ 1) cfg="$1"; DEBUG=; DEBUG_FMT=; ;;
+ 2) cfg="$1"; DEBUG="$2"; DEBUG_FMT=; ;;
+ 3) cfg="$1"; DEBUG="$2"; DEBUG_FMT="$3"; ;;
+ *) echo "usage: $0 [-x] config_file [debug_level [debug_outfmt]]" 1>&2;
+ exit 1 ;;
+esac
+if [ ! -s "$cfg" ]; then
+ echo "$0: config_file not found or empty: $ign" 1>&2
+ exit 2
+fi
+
+# parse config_file
+#
+host="`sed -n -e 's/^host=[ ]*//p' $cfg | tail -1`"
+if [ -z "$host" ]; then
+ echo "$0: no host specified in $cfg" 1>&2
+ exit 3
+fi
+flags="`sed -n -e 's/^flags=[ ]*//p' $cfg | tail -1`"
+if [ -z "$flags" ]; then
+ echo "$0: no flags specified in $cfg" 1>&2
+ exit 4
+fi
+ign="`sed -n -e 's/^ignore_file=[ ]*//p' $cfg | tail -1`"
+if [ -z "$ign" ]; then
+ echo "$0: no ignore file specified in $cfg" 1>&2
+ exit 5
+fi
+ftp="`sed -n -e 's/^ftppath=[ ]*//p' $cfg | tail -1`"
+spool="`sed -n -e 's/^spool=[ ]*//p' $cfg | tail -1`"
+if [ -z "$spool" ]; then
+ spool=$SPOOL
+ #echo "$0: no spool directory specified in $cfg" 1>&2
+ #exit 6
+fi
+if [ ! -f "$ign" ]; then
+ ign="${PATHETC}/$ign"
+fi
+if [ ! -s "$ign" ]; then
+ echo "$0: ignore_file not found or empty: $ign" 1>&2
+ exit 7
+fi
+
+# force -o c mode (overrides any -o argument in the command line)
+#
+if [ -z "$DEBUG" ]; then
+
+ # standard actsyncd output mode
+ flags="$flags -o c"
+
+# DEBUG processing, if debug_level was given
+#
+else
+
+ if [ ! -z "$ftp" ]; then
+ echo "$0: cannot use DEBUG mode with ftp (yet)" >&2
+ exit 88;
+ fi
+
+ # force -v level as needed
+ flags="$flags -v $DEBUG"
+
+ # force -o level but reject -o x modes
+ if [ ! -z "$DEBUG_FMT" ]; then
+ case "$DEBUG_FMT" in
+ x*) echo "$0: do not use any of the -o x debug_outfmt modes!" 1>&2;
+ exit 8 ;;
+ *) flags="$flags -o $DEBUG_FMT" ;;
+ esac
+ fi
+
+ # execute actsync directly
+ echo "DEBUG: will execute $ACTSYNC -i $ign $flags $host" 1>&2
+ eval "$ACTSYNC -i $ign $flags $host"
+ status="$?"
+ echo "DEBUG: exit status $status" 1>&2
+ exit "$status"
+fi
+
+# Lock out others
+#
+shlock -p $$ -f "${LOCK}" || {
+ echo "$0: Locked by `cat '${LOCK}'`" 1>&2
+ exit 9
+}
+
+# setup
+#
+origdir=`pwd`
+workdir="${TMPDIR}/actsyncd"
+ctlinndcmds="cc_commands"
+out="sync.msg"
+cleanup="$SED -e 's/^/ /' < $out; cd ${origdir}; rm -rf '$workdir' '$LOCK'"
+trap "eval $cleanup; exit 123" 1 2 3 15
+
+set -e
+rm -rf "$workdir"
+mkdir "$workdir"
+cd "$workdir"
+set +e
+
+rm -f "$out"
+touch "$out"
+chmod 0644 "$out"
+
+# try to sync
+#
+# Try to sync off of the host. If unable to connect/sync then retry
+# up to 9 more times waiting 6 minutes between each try.
+#
+echo "=-= `date` for $host" >>$out 2>&1
+for loop in 1 2 3 4 5 6 7 8 9 10; do
+
+ # get the active file to compare against
+ status=0
+ case $host in
+ /*) cp $host active; status=$? ;;
+ .*) cp $origdir/$host active; status=$? ;;
+ *)
+ if [ -z "$ftp" ]; then
+ port=`expr "$host" : '.*:\(.*\)'`
+ if [ -n "$port" ]; then
+ port="-p $port"
+ host=`expr "$host" : '\(.*\):.*'`
+ fi
+ echo "getlist -h $host $port" >>$out
+ if getlist -h $host $port > active 2>>$out; then
+ :
+ else
+ status=$NOSYNC
+ fi
+ else
+ echo "$GETFTP ftp://$host/$ftp" >>$out
+ $GETFTP ftp://$host/$ftp >>$out 2>&1
+ status=$?
+ if [ "$status" -ne 0 ]; then
+ status=$NOSYNC
+ else
+ case "$ftp" in
+ *.gz)
+ echo "$GZIP -d active" >>$out
+ if $GZIP -d active >>$out 2>&1; then
+ :
+ else
+ status=1
+ fi
+ ;;
+ *.Z)
+ echo "$UNCOMPRESS active" >>$out
+ if $UNCOMPRESS active >>$out 2>&1; then
+ :
+ else
+ status=1
+ fi
+ ;;
+ esac
+ fi
+ fi
+ ;;
+ esac
+
+ if [ "$status" -ne "$NOSYNC" ]; then
+
+ # detect bad status
+ #
+ if [ "$status" -ne 0 ]; then
+ echo "FATAL: `date` for $host exit $status" >>$out
+ eval $cleanup
+ exit "$status"
+ fi
+
+ echo "$ACTSYNC -i $ign $flags ./active" >>$out
+ eval "$ACTSYNC -i $ign $flags ./active >$ctlinndcmds 2>>$out"
+
+ if [ $? -ne 0 ]; then
+ echo "FATAL: `date` for $host actsync balked" >>$out
+ eval $cleanup
+ exit $?
+ fi
+
+ if [ ! -s $ctlinndcmds ]; then
+ echo "No changes need to be made" >>$out
+ else
+ echo "=-= `date` for $host, updating active" >>$out
+ echo "mod-active $ctlinndcmds" >>$out
+ mod-active $ctlinndcmds >>$out 2>&1
+
+ if [ $? -ne 0 ]; then
+ echo "FATAL: `date` for $host mod-active FAILED" >>$out
+ eval $cleanup
+ exit 1
+ fi
+ fi
+
+ # normal exit - all done
+ #
+ echo "=-= `date` for $host, end" >>$out
+ eval $cleanup
+ exit 0
+ fi
+
+ # failed to get the remote active file
+ echo "=-= `date` for $host failed to connect/sync, retrying" >>$out
+
+ # wait 6 minutes
+ #
+ sleep 360
+done
+
+# give up
+#
+echo "FATAL: `date` for $host failed to connect/sync 10 times" >>$out 2>&1
+eval $cleanup
+exit 1
--- /dev/null
+/* $Id: archive.c 6138 2003-01-19 04:13:51Z rra $
+**
+** Read batchfiles on standard input and archive them.
+*/
+
+#include "config.h"
+#include "clibrary.h"
+#include <ctype.h>
+#include <errno.h>
+#include <sys/stat.h>
+#include <time.h>
+
+#ifdef TM_IN_SYS_TIME
+# include <sys/time.h>
+#endif
+
+#include "inn/innconf.h"
+#include "inn/messages.h"
+#include "inn/wire.h"
+#include "libinn.h"
+#include "paths.h"
+#include "storage.h"
+
+
+static char *Archive = NULL;
+static char *ERRLOG = NULL;
+
+/*
+** Return a YYYYMM string that represents the current year/month
+*/
+static char *
+DateString(void)
+{
+ static char ds[10];
+ time_t now;
+ struct tm *x;
+
+ time(&now);
+ x = localtime(&now);
+ snprintf(ds, sizeof(ds), "%d%d", x->tm_year + 1900, x->tm_mon + 1);
+
+ return ds;
+}
+
+
+/*
+** Try to make one directory. Return false on error.
+*/
+static bool
+MakeDir(char *Name)
+{
+ struct stat Sb;
+
+ if (mkdir(Name, GROUPDIR_MODE) >= 0)
+ return true;
+
+ /* See if it failed because it already exists. */
+ return stat(Name, &Sb) >= 0 && S_ISDIR(Sb.st_mode);
+}
+
+
+/*
+** Given an entry, comp/foo/bar/1123, create the directory and all
+** parent directories needed. Return false on error.
+*/
+static bool
+MakeArchiveDirectory(char *Name)
+{
+ char *p;
+ char *save;
+ bool made;
+
+ if ((save = strrchr(Name, '/')) != NULL)
+ *save = '\0';
+
+ /* Optimize common case -- parent almost always exists. */
+ if (MakeDir(Name)) {
+ if (save)
+ *save = '/';
+ return true;
+ }
+
+ /* Try to make each of comp and comp/foo in turn. */
+ for (p = Name; *p; p++)
+ if (*p == '/' && p != Name) {
+ *p = '\0';
+ made = MakeDir(Name);
+ *p = '/';
+ if (!made) {
+ if (save)
+ *save = '/';
+ return false;
+ }
+ }
+
+ made = MakeDir(Name);
+ if (save)
+ *save = '/';
+ return made;
+}
+
+
+/*
+** Copy a file. Return false if error.
+*/
+static bool
+Copy(char *src, char *dest)
+{
+ FILE *in;
+ FILE *out;
+ size_t i;
+ char *p;
+ char buff[BUFSIZ];
+
+ /* Open the output file. */
+ if ((out = fopen(dest, "w")) == NULL) {
+ /* Failed; make any missing directories and try again. */
+ if ((p = strrchr(dest, '/')) != NULL) {
+ if (!MakeArchiveDirectory(dest)) {
+ syswarn("cannot mkdir for %s", dest);
+ return false;
+ }
+ out = fopen(dest, "w");
+ }
+ if (p == NULL || out == NULL) {
+ syswarn("cannot open %s for writing", dest);
+ return false;
+ }
+ }
+
+ /* Opening the input file is easier. */
+ if ((in = fopen(src, "r")) == NULL) {
+ syswarn("cannot open %s for reading", src);
+ fclose(out);
+ unlink(dest);
+ return false;
+ }
+
+ /* Write the data. */
+ while ((i = fread(buff, 1, sizeof buff, in)) != 0)
+ if (fwrite(buff, 1, i, out) != i) {
+ syswarn("cannot write to %s", dest);
+ fclose(in);
+ fclose(out);
+ unlink(dest);
+ return false;
+ }
+ fclose(in);
+
+ /* Flush and close the output. */
+ if (ferror(out) || fflush(out) == EOF) {
+ syswarn("cannot flush %s", dest);
+ unlink(dest);
+ fclose(out);
+ return false;
+ }
+ if (fclose(out) == EOF) {
+ syswarn("cannot close %s", dest);
+ unlink(dest);
+ return false;
+ }
+
+ return true;
+}
+
+
+/*
+** Copy an article from memory into a file.
+*/
+static bool
+CopyArt(ARTHANDLE *art, char *dest, bool Concat)
+{
+ FILE *out;
+ const char *p;
+ char *q, *article;
+ size_t i;
+ const char *mode = "w";
+
+ if (Concat) mode = "a";
+
+ /* Open the output file. */
+ if ((out = fopen(dest, mode)) == NULL) {
+ /* Failed; make any missing directories and try again. */
+ if ((p = strrchr(dest, '/')) != NULL) {
+ if (!MakeArchiveDirectory(dest)) {
+ syswarn("cannot mkdir for %s", dest);
+ return false;
+ }
+ out = fopen(dest, mode);
+ }
+ if (p == NULL || out == NULL) {
+ syswarn("cannot open %s for writing", dest);
+ return false;
+ }
+ }
+
+ /* Copy the data. */
+ article = xmalloc(art->len);
+ for (i=0, q=article, p=art->data; p<art->data+art->len;) {
+ if (&p[1] < art->data + art->len && p[0] == '\r' && p[1] == '\n') {
+ p += 2;
+ *q++ = '\n';
+ i++;
+ if (&p[1] < art->data + art->len && p[0] == '.' && p[1] == '.') {
+ p += 2;
+ *q++ = '.';
+ i++;
+ }
+ if (&p[2] < art->data + art->len && p[0] == '.' && p[1] == '\r' && p[2] == '\n') {
+ break;
+ }
+ } else {
+ *q++ = *p++;
+ i++;
+ }
+ }
+ *q++ = '\0';
+
+ /* Write the data. */
+ if (Concat) {
+ /* Write a separator... */
+ fprintf(out, "-----------\n");
+ }
+ if (fwrite(article, i, 1, out) != 1) {
+ syswarn("cannot write to %s", dest);
+ fclose(out);
+ if (!Concat) unlink(dest);
+ free(article);
+ return false;
+ }
+ free(article);
+
+ /* Flush and close the output. */
+ if (ferror(out) || fflush(out) == EOF) {
+ syswarn("cannot flush %s", dest);
+ if (!Concat) unlink(dest);
+ fclose(out);
+ return false;
+ }
+ if (fclose(out) == EOF) {
+ syswarn("cannot close %s", dest);
+ if (!Concat) unlink(dest);
+ return false;
+ }
+
+ return true;
+}
+
+
+/*
+** Write an index entry. Ignore I/O errors; our caller checks for them.
+*/
+static void
+WriteArtIndex(ARTHANDLE *art, char *ShortName)
+{
+ const char *p;
+ int i;
+ char Subject[BUFSIZ];
+ char MessageID[BUFSIZ];
+
+ Subject[0] = '\0'; /* default to null string */
+ p = wire_findheader(art->data, art->len, "Subject");
+ if (p != NULL) {
+ for (i=0; *p != '\r' && *p != '\n' && *p != '\0'; i++) {
+ Subject[i] = *p++;
+ }
+ Subject[i] = '\0';
+ }
+
+ MessageID[0] = '\0'; /* default to null string */
+ p = wire_findheader(art->data, art->len, "Message-ID");
+ if (p != NULL) {
+ for (i=0; *p != '\r' && *p != '\n' && *p != '\0'; i++) {
+ MessageID[i] = *p++;
+ }
+ MessageID[i] = '\0';
+ }
+
+ printf("%s %s %s\n",
+ ShortName,
+ MessageID[0] ? MessageID : "<none>",
+ Subject[0] ? Subject : "<none>");
+}
+
+
+/*
+** Crack an Xref line apart into separate strings, each of the form "ng:artnum".
+** Return in "lenp" the number of newsgroups found.
+**
+** This routine blatantly stolen from tradspool.c
+*/
+static char **
+CrackXref(const char *xref, unsigned int *lenp) {
+ char *p;
+ char **xrefs;
+ char *q;
+ unsigned int len, xrefsize;
+
+ len = 0;
+ xrefsize = 5;
+ xrefs = xmalloc(xrefsize * sizeof(char *));
+
+ /* skip pathhost */
+ if ((p = strchr(xref, ' ')) == NULL) {
+ warn("cannot find pathhost in Xref header");
+ return NULL;
+ }
+ /* skip next spaces */
+ for (p++; *p == ' ' ; p++) ;
+ while (true) {
+ /* check for EOL */
+ /* shouldn't ever hit null w/o hitting a \r\n first, but best to be paranoid */
+ if (*p == '\n' || *p == '\r' || *p == 0) {
+ /* hit EOL, return. */
+ *lenp = len;
+ return xrefs;
+ }
+ /* skip to next space or EOL */
+ for (q=p; *q && *q != ' ' && *q != '\n' && *q != '\r' ; ++q) ;
+
+ xrefs[len] = xstrndup(p, q - p);
+
+ if (++len == xrefsize) {
+ /* grow xrefs if needed. */
+ xrefsize *= 2;
+ xrefs = xrealloc(xrefs, xrefsize * sizeof(char *));
+ }
+
+ p = q;
+ /* skip spaces */
+ for ( ; *p == ' ' ; p++) ;
+ }
+}
+
+
+/*
+** Crack an groups pattern parameter apart into separate strings
+** Return in "lenp" the number of patterns found.
+*/
+static char **
+CrackGroups(char *group, unsigned int *lenp) {
+ char *p;
+ char **groups;
+ char *q;
+ unsigned int len, grpsize;
+
+ len = 0;
+ grpsize = 5;
+ groups = xmalloc(grpsize * sizeof(char *));
+
+ /* skip leading spaces */
+ for (p=group; *p == ' ' ; p++) ;
+ while (true) {
+ /* check for EOL */
+ /* shouldn't ever hit null w/o hitting a \r\n first, but best to be paranoid */
+ if (*p == '\n' || *p == '\r' || *p == 0) {
+ /* hit EOL, return. */
+ *lenp = len;
+ return groups;
+ }
+ /* skip to next comma, space, or EOL */
+ for (q=p; *q && *q != ',' && *q != ' ' && *q != '\n' && *q != '\r' ; ++q) ;
+
+ groups[len] = xstrndup(p, q - p);
+
+ if (++len == grpsize) {
+ /* grow groups if needed. */
+ grpsize *= 2;
+ groups = xrealloc(groups, grpsize * sizeof(char *));
+ }
+
+ p = q;
+ /* skip commas and spaces */
+ for ( ; *p == ' ' || *p == ',' ; p++) ;
+ }
+}
+
+
+int
+main(int ac, char *av[])
+{
+ char *Name;
+ char *p;
+ FILE *F;
+ int i;
+ bool Flat;
+ bool Redirect;
+ bool Concat;
+ char *Index;
+ char buff[BUFSIZ];
+ char *spool;
+ char dest[BUFSIZ];
+ char **groups, *q, *ng;
+ char **xrefs;
+ const char *xrefhdr;
+ ARTHANDLE *art;
+ TOKEN token;
+ unsigned int numgroups, numxrefs;
+ int j;
+ char *base = NULL;
+ bool doit;
+
+ /* First thing, set up our identity. */
+ message_program_name = "archive";
+
+ /* Set defaults. */
+ if (!innconf_read(NULL))
+ exit(1);
+ Concat = false;
+ Flat = false;
+ Index = NULL;
+ Redirect = true;
+ umask(NEWSUMASK);
+ ERRLOG = concatpath(innconf->pathlog, _PATH_ERRLOG);
+ Archive = innconf->patharchive;
+ groups = NULL;
+ numgroups = 0;
+
+ /* Parse JCL. */
+ while ((i = getopt(ac, av, "a:cfi:p:r")) != EOF)
+ switch (i) {
+ default:
+ die("usage error");
+ break;
+ case 'a':
+ Archive = optarg;
+ break;
+ case 'c':
+ Flat = true;
+ Concat = true;
+ break;
+ case 'f':
+ Flat = true;
+ break;
+ case 'i':
+ Index = optarg;
+ break;
+ case 'p':
+ groups = CrackGroups(optarg, &numgroups);
+ break;
+ case 'r':
+ Redirect = false;
+ break;
+ }
+
+ /* Parse arguments -- at most one, the batchfile. */
+ ac -= optind;
+ av += optind;
+ if (ac > 2)
+ die("usage error");
+
+ /* Do file redirections. */
+ if (Redirect)
+ freopen(ERRLOG, "a", stderr);
+ if (ac == 1 && freopen(av[0], "r", stdin) == NULL)
+ sysdie("cannot open %s for input", av[0]);
+ if (Index && freopen(Index, "a", stdout) == NULL)
+ sysdie("cannot open %s for output", Index);
+
+ /* Go to where the action is. */
+ if (chdir(innconf->patharticles) < 0)
+ sysdie("cannot chdir to %s", innconf->patharticles);
+
+ /* Set up the destination. */
+ strcpy(dest, Archive);
+ Name = dest + strlen(dest);
+ *Name++ = '/';
+
+ if (!SMinit())
+ die("cannot initialize storage manager: %s", SMerrorstr);
+
+ /* Read input. */
+ while (fgets(buff, sizeof buff, stdin) != NULL) {
+ if ((p = strchr(buff, '\n')) == NULL) {
+ warn("skipping %.40s: too long", buff);
+ continue;
+ }
+ *p = '\0';
+ if (buff[0] == '\0' || buff[0] == '#')
+ continue;
+
+ /* Check to see if this is a token... */
+ if (IsToken(buff)) {
+ /* Get a copy of the article. */
+ token = TextToToken(buff);
+ if ((art = SMretrieve(token, RETR_ALL)) == NULL) {
+ warn("cannot retrieve %s", buff);
+ continue;
+ }
+
+ /* Determine groups from the Xref header */
+ xrefhdr = wire_findheader(art->data, art->len, "Xref");
+ if (xrefhdr == NULL) {
+ warn("cannot find Xref header");
+ SMfreearticle(art);
+ continue;
+ }
+
+ if ((xrefs = CrackXref(xrefhdr, &numxrefs)) == NULL || numxrefs == 0) {
+ warn("bogus Xref header");
+ SMfreearticle(art);
+ continue;
+ }
+
+ /* Process each newsgroup... */
+ if (base) {
+ free(base);
+ base = NULL;
+ }
+ for (i=0; (unsigned)i<numxrefs; i++) {
+ /* Check for group limits... -p flag */
+ if ((p=strchr(xrefs[i], ':')) == NULL) {
+ warn("bogus Xref entry %s", xrefs[i]);
+ continue; /* Skip to next xref */
+ }
+ if (numgroups > 0) {
+ *p = '\0';
+ ng = xrefs[i];
+ doit = false;
+ for (j=0; (unsigned)j<numgroups && !doit; j++) {
+ if (uwildmat(ng, groups[j]) != 0) doit=true;
+ }
+ }
+ else {
+ doit = true;
+ }
+ *p = '/';
+ if (doit) {
+ p = Name;
+ q = xrefs[i];
+ while(*q) {
+ *p++ = *q++;
+ }
+ *p='\0';
+
+ if (!Flat) {
+ for (p=Name; *p; p++) {
+ if (*p == '.') {
+ *p = '/';
+ }
+ }
+ }
+
+ if (Concat) {
+ p = strrchr(Name, '/');
+ q = DateString();
+ p++;
+ while (*q) {
+ *p++ = *q++;
+ }
+ *p = '\0';
+ }
+
+ if (base && !Concat) {
+ /* Try to link the file into the archive. */
+ if (link(base, dest) < 0) {
+
+ /* Make the archive directory. */
+ if (!MakeArchiveDirectory(dest)) {
+ syswarn("cannot mkdir for %s", dest);
+ continue;
+ }
+
+ /* Try to link again; if that fails, make a copy. */
+ if (link(base, dest) < 0) {
+#if defined(HAVE_SYMLINK)
+ if (symlink(base, dest) < 0)
+ syswarn("cannot symlink %s to %s",
+ dest, base);
+ else
+#endif /* defined(HAVE_SYMLINK) */
+ if (!Copy(base, dest))
+ continue;
+ continue;
+ }
+ }
+ } else {
+ if (!CopyArt(art, dest, Concat))
+ syswarn("copying %s to %s failed", buff, dest);
+ base = xstrdup(dest);
+ }
+
+ /* Write index. */
+ if (Index) {
+ WriteArtIndex(art, Name);
+ if (ferror(stdout) || fflush(stdout) == EOF)
+ syswarn("cannot write index for %s", Name);
+ }
+ }
+ }
+
+ /* Free up the article storage space */
+ SMfreearticle(art);
+ art = NULL;
+ /* Free up the xrefs storage space */
+ for ( i=0; (unsigned)i<numxrefs; i++) free(xrefs[i]);
+ free(xrefs);
+ numxrefs = 0;
+ xrefs = NULL;
+ } else {
+ warn("%s is not a token", buff);
+ continue;
+ }
+ }
+
+ /* close down the storage manager api */
+ SMshutdown();
+
+ /* If we read all our input, try to remove the file, and we're done. */
+ if (feof(stdin)) {
+ fclose(stdin);
+ if (av[0])
+ unlink(av[0]);
+ exit(0);
+ }
+
+ /* Make an appropriate spool file. */
+ p = av[0];
+ if (p == NULL)
+ spool = concatpath(innconf->pathoutgoing, "archive");
+ else if (*p == '/')
+ spool = concat(p, ".bch", (char *) 0);
+ else
+ spool = concat(innconf->pathoutgoing, "/", p, ".bch", (char *) 0);
+ if ((F = xfopena(spool)) == NULL)
+ sysdie("cannot spool to %s", spool);
+
+ /* Write the rest of stdin to the spool file. */
+ i = 0;
+ if (fprintf(F, "%s\n", buff) == EOF) {
+ syswarn("cannot start spool");
+ i = 1;
+ }
+ while (fgets(buff, sizeof buff, stdin) != NULL)
+ if (fputs(buff, F) == EOF) {
+ syswarn("cannot write to spool");
+ i = 1;
+ break;
+ }
+ if (fclose(F) == EOF) {
+ syswarn("cannot close spool");
+ i = 1;
+ }
+
+ /* If we had a named input file, try to rename the spool. */
+ if (p != NULL && rename(spool, av[0]) < 0) {
+ syswarn("cannot rename spool");
+ i = 1;
+ }
+
+ exit(i);
+ /* NOTREACHED */
+}
--- /dev/null
+/* $Id: batcher.c 6762 2004-05-17 04:24:53Z rra $
+**
+** Read batchfiles on standard input and spew out batches.
+*/
+
+#include "config.h"
+#include "clibrary.h"
+#include <ctype.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <signal.h>
+#include <syslog.h>
+#include <sys/stat.h>
+
+#include "inn/innconf.h"
+#include "inn/messages.h"
+#include "inn/timer.h"
+#include "libinn.h"
+#include "paths.h"
+#include "storage.h"
+
+
+/*
+** Global variables.
+*/
+static bool BATCHopen;
+static bool STATprint;
+static double STATbegin;
+static double STATend;
+static char *Host;
+static char *InitialString;
+static char *Input;
+static char *Processor;
+static int ArtsInBatch;
+static int ArtsWritten;
+static int BATCHcount;
+static int MaxBatches;
+static int BATCHstatus;
+static long BytesInBatch = 60 * 1024;
+static long BytesWritten;
+static long MaxArts;
+static long MaxBytes;
+static sig_atomic_t GotInterrupt;
+static const char *Separator = "#! rnews %ld";
+static char *ERRLOG;
+
+/*
+** Start a batch process.
+*/
+static FILE *
+BATCHstart(void)
+{
+ FILE *F;
+ char buff[SMBUF];
+
+ if (Processor && *Processor) {
+ snprintf(buff, sizeof(buff), Processor, Host);
+ F = popen(buff, "w");
+ if (F == NULL)
+ return NULL;
+ }
+ else
+ F = stdout;
+ BATCHopen = true;
+ BATCHcount++;
+ return F;
+}
+
+
+/*
+** Close a batch, return exit status.
+*/
+static int
+BATCHclose(FILE *F)
+{
+ BATCHopen = false;
+ if (F == stdout)
+ return fflush(stdout) == EOF ? 1 : 0;
+ return pclose(F);
+}
+
+
+/*
+** Update the batch file and exit.
+*/
+static void
+RequeueAndExit(off_t Cookie, char *line, long BytesInArt)
+{
+ static char LINE1[] = "batcher %s times user %.3f system %.3f elapsed %.3f";
+ static char LINE2[] ="batcher %s stats batches %d articles %d bytes %ld";
+ char *spool;
+ char buff[BIG_BUFFER];
+ int i;
+ FILE *F;
+ double usertime;
+ double systime;
+
+ /* Do statistics. */
+ STATend = TMRnow_double();
+ if (GetResourceUsage(&usertime, &systime) < 0) {
+ usertime = 0;
+ systime = 0;
+ }
+
+ if (STATprint) {
+ printf(LINE1, Host, usertime, systime, STATend - STATbegin);
+ printf("\n");
+ printf(LINE2, Host, BATCHcount, ArtsWritten, BytesWritten);
+ printf("\n");
+ }
+
+ syslog(L_NOTICE, LINE1, Host, usertime, systime, STATend - STATbegin);
+ syslog(L_NOTICE, LINE2, Host, BATCHcount, ArtsWritten, BytesWritten);
+
+ /* Last batch exit okay? */
+ if (BATCHstatus == 0) {
+ if (feof(stdin) && Cookie != -1) {
+ /* Yes, and we're all done -- remove input and exit. */
+ fclose(stdin);
+ if (Input)
+ unlink(Input);
+ exit(0);
+ }
+ }
+
+ /* Make an appropriate spool file. */
+ if (Input == NULL)
+ spool = concatpath(innconf->pathoutgoing, Host);
+ else
+ spool = concat(Input, ".bch", (char *) 0);
+ if ((F = xfopena(spool)) == NULL)
+ sysdie("%s cannot open %s", Host, spool);
+
+ /* If we can back up to where the batch started, do so. */
+ i = 0;
+ if (Cookie != -1 && fseeko(stdin, Cookie, SEEK_SET) == -1) {
+ syswarn("%s cannot seek", Host);
+ i = 1;
+ }
+
+ /* Write the line we had; if the fseeko worked, this will be an
+ * extra line, but that's okay. */
+ if (line && fprintf(F, "%s %ld\n", line, BytesInArt) == EOF) {
+ syswarn("%s cannot write spool", Host);
+ i = 1;
+ }
+
+ /* Write rest of stdin to spool. */
+ while (fgets(buff, sizeof buff, stdin) != NULL)
+ if (fputs(buff, F) == EOF) {
+ syswarn("%s cannot write spool", Host);
+ i = 1;
+ break;
+ }
+ if (fclose(F) == EOF) {
+ syswarn("%s cannot close spool", Host);
+ i = 1;
+ }
+
+ /* If we had a named input file, try to rename the spool. */
+ if (Input != NULL && rename(spool, Input) < 0) {
+ syswarn("%s cannot rename spool", Host);
+ i = 1;
+ }
+
+ exit(i);
+ /* NOTREACHED */
+}
+
+
+/*
+** Mark that we got interrupted.
+*/
+static RETSIGTYPE
+CATCHinterrupt(int s)
+{
+ GotInterrupt = true;
+
+ /* Let two interrupts kill us. */
+ xsignal(s, SIG_DFL);
+}
+
+
+int
+main(int ac, char *av[])
+{
+ bool Redirect;
+ FILE *F;
+ const char *AltSpool;
+ char *p;
+ char *data;
+ char line[BIG_BUFFER];
+ char buff[BIG_BUFFER];
+ int BytesInArt;
+ long BytesInCB;
+ off_t Cookie;
+ size_t datasize;
+ int i;
+ int ArtsInCB;
+ int length;
+ TOKEN token;
+ ARTHANDLE *art;
+ char *artdata;
+
+ /* Set defaults. */
+ openlog("batcher", L_OPENLOG_FLAGS | LOG_PID, LOG_INN_PROG);
+ message_program_name = "batcher";
+ if (!innconf_read(NULL))
+ exit(1);
+ AltSpool = NULL;
+ Redirect = true;
+ umask(NEWSUMASK);
+ ERRLOG = concatpath(innconf->pathlog, _PATH_ERRLOG);
+
+ /* Parse JCL. */
+ while ((i = getopt(ac, av, "a:A:b:B:i:N:p:rs:S:v")) != EOF)
+ switch (i) {
+ default:
+ die("usage error");
+ break;
+ case 'a':
+ ArtsInBatch = atoi(optarg);
+ break;
+ case 'A':
+ MaxArts = atol(optarg);
+ break;
+ case 'b':
+ BytesInBatch = atol(optarg);
+ break;
+ case 'B':
+ MaxBytes = atol(optarg);
+ break;
+ case 'i':
+ InitialString = optarg;
+ break;
+ case 'N':
+ MaxBatches = atoi(optarg);
+ break;
+ case 'p':
+ Processor = optarg;
+ break;
+ case 'r':
+ Redirect = false;
+ break;
+ case 's':
+ Separator = optarg;
+ break;
+ case 'S':
+ AltSpool = optarg;
+ break;
+ case 'v':
+ STATprint = true;
+ break;
+ }
+ if (MaxArts && ArtsInBatch == 0)
+ ArtsInBatch = MaxArts;
+ if (MaxBytes && BytesInBatch == 0)
+ BytesInBatch = MaxBytes;
+
+ /* Parse arguments. */
+ ac -= optind;
+ av += optind;
+ if (ac != 1 && ac != 2)
+ die("usage error");
+ Host = av[0];
+ if ((Input = av[1]) != NULL) {
+ if (Input[0] != '/')
+ Input = concatpath(innconf->pathoutgoing, av[1]);
+ if (freopen(Input, "r", stdin) == NULL)
+ sysdie("%s cannot open %s", Host, Input);
+ }
+
+ if (Redirect)
+ freopen(ERRLOG, "a", stderr);
+
+ /* Go to where the articles are. */
+ if (chdir(innconf->patharticles) < 0)
+ sysdie("%s cannot chdir to %s", Host, innconf->patharticles);
+
+ /* Set initial counters, etc. */
+ datasize = 8 * 1024;
+ data = xmalloc(datasize);
+ BytesInCB = 0;
+ ArtsInCB = 0;
+ Cookie = -1;
+ GotInterrupt = false;
+ xsignal(SIGHUP, CATCHinterrupt);
+ xsignal(SIGINT, CATCHinterrupt);
+ xsignal(SIGTERM, CATCHinterrupt);
+ /* xsignal(SIGPIPE, CATCHinterrupt); */
+ STATbegin = TMRnow_double();
+
+ SMinit();
+ F = NULL;
+ while (fgets(line, sizeof line, stdin) != NULL) {
+ /* Record line length in case we do an ftello. Not portable to
+ * systems with non-Unix file formats. */
+ length = strlen(line);
+ Cookie = ftello(stdin) - length;
+
+ /* Get lines like "name size" */
+ if ((p = strchr(line, '\n')) == NULL) {
+ warn("%s skipping %.40s: too long", Host, line);
+ continue;
+ }
+ *p = '\0';
+ if (line[0] == '\0' || line[0] == '#')
+ continue;
+ if ((p = strchr(line, ' ')) != NULL) {
+ *p++ = '\0';
+ /* Try to be forgiving of bad input. */
+ BytesInArt = CTYPE(isdigit, (int)*p) ? atol(p) : -1;
+ }
+ else
+ BytesInArt = -1;
+
+ /* Strip of leading spool pathname. */
+ if (line[0] == '/'
+ && line[strlen(innconf->patharticles)] == '/'
+ && strncmp(line, innconf->patharticles, strlen(innconf->patharticles)) == 0)
+ p = line + strlen(innconf->patharticles) + 1;
+ else
+ p = line;
+
+ /* Open the file. */
+ if (IsToken(p)) {
+ token = TextToToken(p);
+ if ((art = SMretrieve(token, RETR_ALL)) == NULL) {
+ if ((SMerrno != SMERR_NOENT) && (SMerrno != SMERR_UNINIT))
+ warn("%s skipping %.40s: %s", Host, p, SMerrorstr);
+ continue;
+ }
+ BytesInArt = -1;
+ artdata = FromWireFmt(art->data, art->len, (size_t *)&BytesInArt);
+ SMfreearticle(art);
+ } else {
+ warn("%s skipping %.40s: not token", Host, p);
+ continue;
+ }
+
+ /* Have an open article, do we need to open a batch? This code
+ * is here (rather then up before the while loop) so that we
+ * can avoid sending an empty batch. The goto makes the code
+ * a bit more clear. */
+ if (F == NULL) {
+ if (GotInterrupt) {
+ RequeueAndExit(Cookie, (char *)NULL, 0L);
+ }
+ if ((F = BATCHstart()) == NULL) {
+ syswarn("%s cannot start batch %d", Host, BATCHcount);
+ break;
+ }
+ if (InitialString && *InitialString) {
+ fprintf(F, "%s\n", InitialString);
+ BytesInCB += strlen(InitialString) + 1;
+ BytesWritten += strlen(InitialString) + 1;
+ }
+ goto SendIt;
+ }
+
+ /* We're writing a batch, see if adding the current article
+ * would exceed the limits. */
+ if ((ArtsInBatch > 0 && ArtsInCB + 1 >= ArtsInBatch)
+ || (BytesInBatch > 0 && BytesInCB + BytesInArt >= BytesInBatch)) {
+ if ((BATCHstatus = BATCHclose(F)) != 0) {
+ if (BATCHstatus == -1)
+ syswarn("%s cannot close batch %d", Host, BATCHcount);
+ else
+ syswarn("%s batch %d exit status %d", Host, BATCHcount,
+ BATCHstatus);
+ break;
+ }
+ ArtsInCB = 0;
+ BytesInCB = 0;
+
+ /* See if we can start a new batch. */
+ if ((MaxBatches > 0 && BATCHcount >= MaxBatches)
+ || (MaxBytes > 0 && BytesWritten + BytesInArt >= MaxBytes)
+ || (MaxArts > 0 && ArtsWritten + 1 >= MaxArts)) {
+ break;
+ }
+
+ if (GotInterrupt) {
+ RequeueAndExit(Cookie, (char *)NULL, 0L);
+ }
+
+ if ((F = BATCHstart()) == NULL) {
+ syswarn("%s cannot start batch %d", Host, BATCHcount);
+ break;
+ }
+ }
+
+ SendIt:
+ /* Now we can start to send the article! */
+ if (Separator && *Separator) {
+ snprintf(buff, sizeof(buff), Separator, BytesInArt);
+ BytesInCB += strlen(buff) + 1;
+ BytesWritten += strlen(buff) + 1;
+ if (fprintf(F, "%s\n", buff) == EOF || ferror(F)) {
+ syswarn("%s cannot write separator", Host);
+ break;
+ }
+ }
+
+ /* Write the article. In case of interrupts, retry the read but not
+ the fwrite because we can't check that reliably and portably. */
+ if ((fwrite(artdata, 1, BytesInArt, F) != BytesInArt) || ferror(F))
+ break;
+
+ /* Update the counts. */
+ BytesInCB += BytesInArt;
+ BytesWritten += BytesInArt;
+ ArtsInCB++;
+ ArtsWritten++;
+
+ if (GotInterrupt) {
+ Cookie = -1;
+ BATCHstatus = BATCHclose(F);
+ RequeueAndExit(Cookie, line, BytesInArt);
+ }
+ }
+
+ if (BATCHopen)
+ BATCHstatus = BATCHclose(F);
+ RequeueAndExit(Cookie, NULL, 0);
+
+ return 0;
+}
--- /dev/null
+/* $Id: buffchan.c 6163 2003-01-19 22:56:34Z rra $
+**
+** Buffered file exploder for innd.
+*/
+
+#include "config.h"
+#include "clibrary.h"
+#include <ctype.h>
+#include <errno.h>
+#include <signal.h>
+#include <sys/stat.h>
+
+#include "inn/innconf.h"
+#include "inn/messages.h"
+#include "inn/qio.h"
+#include "libinn.h"
+#include "paths.h"
+#include "map.h"
+
+/*
+** Hash functions for hashing sitenames.
+*/
+#define SITE_HASH(Name, p, j) \
+ for (p = Name, j = 0; *p; ) j = (j << 5) + j + *p++
+#define SITE_SIZE 128
+#define SITE_BUCKET(j) &SITEtable[j & (SITE_SIZE - 1)]
+
+
+/*
+** Entry for a single active site.
+*/
+typedef struct _SITE {
+ bool Dropped;
+ const char *Name;
+ int CloseLines;
+ int FlushLines;
+ time_t LastFlushed;
+ time_t LastClosed;
+ int CloseSeconds;
+ int FlushSeconds;
+ FILE *F;
+ const char *Filename;
+ char *Buffer;
+} SITE;
+
+
+/*
+** Site hashtable bucket.
+*/
+typedef struct _SITEHASH {
+ int Size;
+ int Used;
+ SITE *Sites;
+} SITEHASH;
+
+
+/* Global variables. */
+static char *Format;
+static const char *Map;
+static int BufferMode;
+static int CloseEvery;
+static int FlushEvery;
+static int CloseSeconds;
+static int FlushSeconds;
+static sig_atomic_t GotInterrupt;
+static SITEHASH SITEtable[SITE_SIZE];
+static TIMEINFO Now;
+
+
+/*
+** Set up the site information. Basically creating empty buckets.
+*/
+static void
+SITEsetup(void)
+{
+ SITEHASH *shp;
+
+ for (shp = SITEtable; shp < ARRAY_END(SITEtable); shp++) {
+ shp->Size = 3;
+ shp->Sites = xmalloc(shp->Size * sizeof(SITE));
+ shp->Used = 0;
+ }
+}
+
+
+/*
+** Close a site
+*/
+static void
+SITEclose(SITE *sp)
+{
+ FILE *F;
+
+ if ((F = sp->F) != NULL) {
+ if (fflush(F) == EOF || ferror(F)
+ || fchmod((int)fileno(F), 0664) < 0
+ || fclose(F) == EOF)
+ syswarn("%s cannot close %s", sp->Name, sp->Filename);
+ sp->F = NULL;
+ }
+}
+
+/*
+** Close all open sites.
+*/
+static void
+SITEcloseall(void)
+{
+ SITEHASH *shp;
+ SITE *sp;
+ int i;
+
+ for (shp = SITEtable; shp < ARRAY_END(SITEtable); shp++)
+ for (sp = shp->Sites, i = shp->Used; --i >= 0; sp++)
+ SITEclose(sp);
+}
+
+
+/*
+** Open the file for a site.
+*/
+static void SITEopen(SITE *sp)
+{
+ int e;
+
+ if ((sp->F = xfopena(sp->Filename)) == NULL
+ && ((e = errno) != EACCES || chmod(sp->Filename, 0644) < 0
+ || (sp->F = xfopena(sp->Filename)) == NULL)) {
+ syswarn("%s cannot fopen %s", sp->Name, sp->Filename);
+ if ((sp->F = fopen("/dev/null", "w")) == NULL)
+ /* This really should not happen. */
+ sysdie("%s cannot fopen /dev/null", sp->Name);
+ }
+ else if (fchmod((int)fileno(sp->F), 0444) < 0)
+ syswarn("%s cannot fchmod %s", sp->Name, sp->Filename);
+
+ if (BufferMode != '\0')
+ setbuf(sp->F, sp->Buffer);
+
+ /* Reset all counters. */
+ sp->FlushLines = 0;
+ sp->CloseLines = 0;
+ sp->LastFlushed = Now.time;
+ sp->LastClosed = Now.time;
+ sp->Dropped = false;
+}
+
+
+/*
+** Find a site, possibly create if not found.
+*/
+static SITE *
+SITEfind(char *Name, bool CanCreate)
+{
+ char *p;
+ int i;
+ unsigned int j;
+ SITE *sp;
+ SITEHASH *shp;
+ char c;
+ char buff[BUFSIZ];
+
+ /* Look for site in the hash table. */
+ SITE_HASH(Name, p, j);
+ shp = SITE_BUCKET(j);
+ for (c = *Name, sp = shp->Sites, i = shp->Used; --i >= 0; sp++)
+ if (c == sp->Name[0] && strcasecmp(Name, sp->Name) == 0)
+ return sp;
+ if (!CanCreate)
+ return NULL;
+
+ /* Adding a new site -- grow hash bucket if we need to. */
+ if (shp->Used == shp->Size - 1) {
+ shp->Size *= 2;
+ shp->Sites = xrealloc(shp->Sites, shp->Size * sizeof(SITE));
+ }
+ sp = &shp->Sites[shp->Used++];
+
+ /* Fill in the structure for the new site. */
+ sp->Name = xstrdup(Name);
+ snprintf(buff, sizeof(buff), Format, Map ? MAPname(Name) : sp->Name);
+ sp->Filename = xstrdup(buff);
+ if (BufferMode == 'u')
+ sp->Buffer = NULL;
+ else if (BufferMode == 'b')
+ sp->Buffer = xmalloc(BUFSIZ);
+ SITEopen(sp);
+
+ return sp;
+}
+
+
+/*
+** Flush a site -- close and re-open the file.
+*/
+static void
+SITEflush(SITE *sp)
+{
+ FILE *F;
+
+ if ((F = sp->F) != NULL) {
+ if (fflush(F) == EOF || ferror(F)
+ || fchmod((int)fileno(F), 0664) < 0
+ || fclose(F) == EOF)
+ syswarn("%s cannot close %s", sp->Name, sp->Filename);
+ sp->F = NULL;
+ }
+ if (!sp->Dropped)
+ SITEopen(sp);
+}
+
+
+/*
+** Flush all open sites.
+*/
+static void
+SITEflushall(void)
+{
+ SITEHASH *shp;
+ SITE *sp;
+ int i;
+
+ for (shp = SITEtable; shp < ARRAY_END(SITEtable); shp++)
+ for (sp = shp->Sites, i = shp->Used; --i >= 0; sp++)
+ SITEflush(sp);
+}
+
+
+/*
+** Write data to a site.
+*/
+static void
+SITEwrite(char *name, char *text, size_t len)
+{
+ SITE *sp;
+
+ sp = SITEfind(name, true);
+ if (sp->F == NULL)
+ SITEopen(sp);
+
+ if (fwrite(text, 1, len, sp->F) != len)
+ syswarn("%s cannot write", sp->Name);
+
+ /* Bump line count; see if time to close or flush. */
+ if (CloseEvery && ++(sp->CloseLines) >= CloseEvery) {
+ SITEflush(sp);
+ return;
+ }
+ if (CloseSeconds && sp->LastClosed + CloseSeconds < Now.time) {
+ SITEflush(sp);
+ return;
+ }
+ if (FlushEvery && ++(sp->FlushLines) >= FlushEvery) {
+ if (fflush(sp->F) == EOF || ferror(sp->F))
+ syswarn("%s cannot flush %s", sp->Name, sp->Filename);
+ sp->LastFlushed = Now.time;
+ sp->FlushLines = 0;
+ }
+ else if (FlushSeconds && sp->LastFlushed + FlushSeconds < Now.time) {
+ if (fflush(sp->F) == EOF || ferror(sp->F))
+ syswarn("%s cannot flush %s", sp->Name, sp->Filename);
+ sp->LastFlushed = Now.time;
+ sp->FlushLines = 0;
+ }
+}
+
+
+/*
+** Handle a command message.
+*/
+static void
+Process(char *p)
+{
+ SITE *sp;
+
+ if (*p == 'b' && strncmp(p, "begin", 5) == 0)
+ /* No-op. */
+ return;
+
+ if (*p == 'f' && strncmp(p, "flush", 5) == 0) {
+ for (p += 5; ISWHITE(*p); p++)
+ continue;
+ if (*p == '\0')
+ SITEflushall();
+ else if ((sp = SITEfind(p, false)) != NULL)
+ SITEflush(sp);
+ /*else
+ fprintf(stderr, "buffchan flush %s unknown site\n", p);*/
+ return;
+ }
+
+ if (*p == 'd' && strncmp(p, "drop", 4) == 0) {
+ for (p += 4; ISWHITE(*p); p++)
+ continue;
+ if (*p == '\0')
+ SITEcloseall();
+ else if ((sp = SITEfind(p, false)) == NULL)
+ warn("drop %s unknown site", p);
+ else {
+ SITEclose(sp);
+ sp->Dropped = true;
+ }
+ return;
+ }
+
+ if (*p == 'r' && strncmp(p, "readmap", 7) == 0) {
+ MAPread(Map);
+ return;
+ }
+
+ /* Other command messages -- ignored. */
+ warn("unknown message %s", p);
+}
+
+
+/*
+** Mark that we got a signal; let two signals kill us.
+*/
+static RETSIGTYPE
+CATCHinterrupt(int s)
+{
+ GotInterrupt = true;
+ xsignal(s, SIG_DFL);
+}
+
+
+int
+main(int ac, char *av[])
+{
+ QIOSTATE *qp;
+ int i;
+ int Fields;
+ char *p;
+ char *next;
+ char *line;
+ char *Directory;
+ bool Redirect;
+ FILE *F;
+ char *ERRLOG;
+
+ /* First thing, set up our identity. */
+ message_program_name = "buffchan";
+
+ /* Set defaults. */
+ if (!innconf_read(NULL))
+ exit(1);
+ ERRLOG = concatpath(innconf->pathlog, _PATH_ERRLOG);
+ Directory = NULL;
+ Fields = 1;
+ Format = NULL;
+ Redirect = true;
+ GotInterrupt = false;
+ umask(NEWSUMASK);
+
+ xsignal(SIGHUP, CATCHinterrupt);
+ xsignal(SIGINT, CATCHinterrupt);
+ xsignal(SIGQUIT, CATCHinterrupt);
+ xsignal(SIGPIPE, CATCHinterrupt);
+ xsignal(SIGTERM, CATCHinterrupt);
+ xsignal(SIGALRM, CATCHinterrupt);
+
+ /* Parse JCL. */
+ while ((i = getopt(ac, av, "bc:C:d:f:l:L:m:p:rs:u")) != EOF)
+ switch (i) {
+ default:
+ die("usage error");
+ break;
+ case 'b':
+ case 'u':
+ BufferMode = i;
+ break;
+ case 'c':
+ CloseEvery = atoi(optarg);
+ break;
+ case 'C':
+ CloseSeconds = atoi(optarg);
+ break;
+ case 'd':
+ Directory = optarg;
+ if (Format == NULL)
+ Format =xstrdup("%s");
+ break;
+ case 'f':
+ Fields = atoi(optarg);
+ break;
+ case 'l':
+ FlushEvery = atoi(optarg);
+ break;
+ case 'L':
+ FlushSeconds = atoi(optarg);
+ break;
+ case 'm':
+ Map = optarg;
+ MAPread(Map);
+ break;
+ case 'p':
+ if ((F = fopen(optarg, "w")) == NULL)
+ sysdie("cannot fopen %s", optarg);
+ fprintf(F, "%ld\n", (long)getpid());
+ if (ferror(F) || fclose(F) == EOF)
+ sysdie("cannot fclose %s", optarg);
+ break;
+ case 'r':
+ Redirect = false;
+ break;
+ case 's':
+ Format = optarg;
+ break;
+ }
+ ac -= optind;
+ av += optind;
+ if (ac)
+ die("usage error");
+
+ /* Do some basic set-ups. */
+ if (Redirect)
+ freopen(ERRLOG, "a", stderr);
+ if (Format == NULL) {
+ Format = concatpath(innconf->pathoutgoing, "%s");
+ }
+ if (Directory && chdir(Directory) < 0)
+ sysdie("cannot chdir to %s", Directory);
+ SITEsetup();
+
+ /* Read input. */
+ for (qp = QIOfdopen((int)fileno(stdin)); !GotInterrupt ; ) {
+ if ((line = QIOread(qp)) == NULL) {
+ if (QIOerror(qp)) {
+ syswarn("cannot read");
+ break;
+ }
+ if (QIOtoolong(qp)) {
+ warn("long line");
+ QIOread(qp);
+ continue;
+ }
+
+ /* Normal EOF. */
+ break;
+ }
+
+ /* Command? */
+ if (*line == EXP_CONTROL && *++line != EXP_CONTROL) {
+ Process(line);
+ continue;
+ }
+
+ /* Skip the right number of leading fields. */
+ for (i = Fields, p = line; *p; p++)
+ if (*p == ' ' && --i <= 0)
+ break;
+ if (*p == '\0')
+ /* Nothing to write. Probably shouldn't happen. */
+ continue;
+
+ /* Add a newline, get the length of all leading fields. */
+ *p++ = '\n';
+ i = p - line;
+
+ if (GetTimeInfo(&Now) < 0) {
+ syswarn("cannot get time");
+ break;
+ }
+
+ /* Rest of the line is space-separated list of filenames. */
+ for (; *p; p = next) {
+ /* Skip whitespace, get next word. */
+ while (*p == ' ')
+ p++;
+ for (next = p; *next && *next != ' '; next++)
+ continue;
+ if (*next)
+ *next++ = '\0';
+
+ SITEwrite(p, line, i);
+ }
+
+ }
+
+ SITEcloseall();
+ exit(0);
+ /* NOTREACHED */
+}
--- /dev/null
+/* $Id: crosspost.c 6135 2003-01-19 01:15:40Z rra $
+**
+** Parse input to add links for cross posted articles. Input format is one
+** line per article. Dots '.' are changed to '/'. Commas ',' or blanks
+** ' ' separate entries. Typically this is via a channel feed from innd
+** though an edit of the history file can also be used for recovery
+** purposes. Sample newsfeeds entry:
+**
+** # Create the links for cross posted articles
+** crosspost:*:Tc,Ap,WR:/usr/local/newsbin/crosspost
+**
+** WARNING: This no longer works with the current INN; don't use it
+** currently. It still exists in the source tree in case someone will
+** want to clean it up and make it useable again.
+*/
+
+#include "config.h"
+#include "clibrary.h"
+#include <errno.h>
+#include <fcntl.h>
+#include <syslog.h>
+#include <sys/stat.h>
+
+#include "inn/qio.h"
+#include "libinn.h"
+#include "paths.h"
+
+
+static char *Dir;
+
+static int debug = false;
+static int syncfiles = true;
+
+static unsigned long STATTime = 0;
+static unsigned long STATMissing = 0; /* Source file missing */
+static unsigned long STATTooLong = 0; /* Name Too Long (src or dest) */
+static unsigned long STATLink = 0; /* Link done */
+static unsigned long STATLError = 0; /* Link problem */
+static unsigned long STATSymlink = 0; /* Symlink done */
+static unsigned long STATSLError = 0; /* Symlink problem */
+static unsigned long STATMkdir = 0; /* Mkdir done */
+static unsigned long STATMdError = 0; /* Mkdir problem */
+static unsigned long STATOError = 0; /* Other errors */
+
+#define MAXXPOST 256
+#define STATREFRESH 10800 /* 3 hours */
+
+/*
+** Write some statistics and reset all counters.
+*/
+void
+ProcessStats()
+{
+ time_t Time;
+
+ Time = time (NULL);
+ syslog(L_NOTICE,
+ "seconds %lu links %lu %lu symlinks %lu %lu mkdirs %lu %lu missing %lu toolong %lu other %lu",
+ Time - STATTime, STATLink, STATLError, STATSymlink, STATSLError,
+ STATMkdir, STATMdError, STATMissing, STATTooLong, STATOError);
+
+ STATMissing = STATTooLong = STATLink = STATLError = 0;
+ STATSymlink = STATSLError = STATMkdir = STATMdError = STATOError = 0;
+ STATTime = Time;
+}
+
+/*
+** Try to make one directory. Return false on error.
+*/
+static bool
+MakeDir(Name)
+ char *Name;
+{
+ struct stat Sb;
+
+ if (mkdir(Name, GROUPDIR_MODE) >= 0) {
+ STATMkdir++;
+ return true;
+ }
+
+ /* See if it failed because it already exists. */
+ return stat(Name, &Sb) >= 0 && S_ISDIR(Sb.st_mode);
+}
+
+
+/*
+** Make spool directory. Return false on error.
+*/
+static bool
+MakeSpoolDir(Name)
+ char *Name;
+{
+ char *p;
+ bool made;
+
+ /* Optimize common case -- parent almost always exists. */
+ if (MakeDir(Name))
+ return true;
+
+ /* Try to make each of comp and comp/foo in turn. */
+ for (p = Name; *p; p++)
+ if (*p == '/') {
+ *p = '\0';
+ made = MakeDir(Name);
+ *p = '/';
+ if (!made) {
+ STATMdError++;
+ return false;
+ }
+ }
+
+ return MakeDir(Name);
+}
+
+
+/*
+** Process the input. Data can come from innd:
+** news/group/name/<number> [space news/group/<number>]...
+** or
+** news.group.name/<number>,[news.group.name/<number>]...
+*/
+static void
+ProcessIncoming(qp)
+ QIOSTATE *qp;
+{
+ char *p;
+ int i;
+ int nxp;
+ int fd;
+ int lnval ;
+ char *names[MAXXPOST];
+
+
+ for ( ; ; ) {
+
+ if (time(NULL) - STATTime > STATREFRESH)
+ ProcessStats();
+
+ /* Read the first line of data. */
+ if ((p = QIOread(qp)) == NULL) {
+ if (QIOtoolong(qp)) {
+ fprintf(stderr, "crosspost line too long\n");
+ STATTooLong++;
+ continue;
+ }
+ break;
+ }
+
+ for (i = 0; *p && (i < MAXXPOST); i++) { /* parse input into array */
+ names[i] = p;
+ for ( ; *p; p++) {
+ if (*p == '.') *p++ = '/'; /* dot to slash translation */
+ else if ((*p == ',') /* name separators */
+ || (*p == ' ')
+ || (*p == '\t')
+ || (*p == '\n')) {
+ *p++ = '\0';
+ break;
+ }
+ }
+ }
+ nxp = i;
+ if (debug) {
+ for (i = 0; i < nxp; i++)
+ fprintf(stderr, "crosspost: debug %d %s\n",
+ i, names[i]);
+ }
+
+ if(syncfiles) fd = open(names[0], O_RDWR);
+
+ for (i = 1; i < nxp; i++) {
+ lnval = link(names[0], names[i]) ;
+ if (lnval == 0) STATLink++;
+ if (lnval < 0 && errno != EXDEV) { /* first try to link */
+ int j;
+ char path[SPOOLNAMEBUFF+2];
+
+ for (j = 0; (path[j] = names[i][j]) != '\0' ; j++) ;
+ for (j--; (j > 0) && (path[j] != '/'); j--) ;
+ if (path[j] == '/') {
+ path[j] = '\0';
+ /* try making parent dir */
+ if (MakeSpoolDir(path) == false) {
+ fprintf(stderr, "crosspost cant mkdir %s\n",
+ path);
+ }
+ else {
+ /* 2nd try to link */
+ lnval = link(names[0], names[i]) ;
+ if (lnval == 0) STATLink++;
+ if (lnval < 0 && errno == EXDEV) {
+#if !defined(HAVE_SYMLINK)
+ fprintf(stderr, "crosspost cant link %s %s",
+ names[0], names[i]);
+ perror(" ");
+#else
+ /* Try to make a symbolic link
+ ** to the full pathname.
+ */
+ for (j = 0, p = Dir; (j < SPOOLNAMEBUFF) && *p; )
+ path[j++] = *p++; /* copy spool dir */
+ if (j < SPOOLNAMEBUFF) path[j++] = '/';
+ for (p = names[0]; (j < SPOOLNAMEBUFF) && *p; )
+ path[j++] = *p++; /* append path */
+ path[j++] = '\0';
+ if (symlink(path, names[i]) < 0) {
+ fprintf(stderr,
+ "crosspost cant symlink %s %s",
+ path, names[i]);
+ perror(" ");
+ STATSLError++;
+ }
+ else
+ STATSymlink++;
+#endif /* !defined(HAVE_SYMLINK) */
+ } else if (lnval < 0) {
+ if (lnval == ENOENT)
+ STATMissing++;
+ else {
+ fprintf(stderr, "crosspost cant link %s %s",
+ names[0], names[i]);
+ perror(" ");
+ STATLError++;
+ }
+ }
+ }
+ } else {
+ fprintf(stderr, "crosspost bad path %s\n",
+ names[i]);
+ STATOError++;
+ }
+ } else if (lnval < 0) {
+ int j;
+ char path[SPOOLNAMEBUFF+2];
+
+#if !defined(HAVE_SYMLINK)
+ fprintf(stderr, "crosspost cant link %s %s",
+ names[0], names[i]);
+ perror(" ");
+#else
+ /* Try to make a symbolic link
+ ** to the full pathname.
+ */
+ for (j = 0, p = Dir; (j < SPOOLNAMEBUFF) && *p; )
+ path[j++] = *p++; /* copy spool dir */
+ if (j < SPOOLNAMEBUFF) path[j++] = '/';
+ for (p = names[0]; (j < SPOOLNAMEBUFF) && *p; )
+ path[j++] = *p++; /* append path */
+ path[j++] = '\0';
+ if (symlink(path, names[i]) < 0) {
+ fprintf(stderr,
+ "crosspost cant symlink %s %s",
+ path, names[i]);
+ perror(" ");
+ STATSLError++;
+ }
+ else
+ STATSymlink++;
+#endif /* !defined(HAVE_SYMLINK) */
+ }
+ }
+
+ if (syncfiles && (fd >= 0)) {
+ fsync(fd);
+ close(fd);
+ }
+ }
+
+ if (QIOerror(qp))
+ fprintf(stderr, "crosspost cant read %s\n", strerror(errno));
+ QIOclose(qp);
+}
+
+
+static void
+Usage(void)
+{
+ fprintf(stderr, "usage: crosspost [-D dir] [files...]\n");
+ exit(1);
+}
+
+
+int
+main(ac, av)
+ int ac;
+ char *av[];
+{
+ int i;
+ QIOSTATE *qp;
+
+ /* Set defaults. */
+ if (ReadInnConf() < 0) exit(1);
+ Dir = innconf->patharticles;
+ umask(NEWSUMASK);
+
+ /* Parse JCL. */
+ while ((i = getopt(ac, av, "D:ds")) != EOF)
+ switch (i) {
+ default:
+ Usage();
+ /* NOTREACHED */
+ case 'D':
+ Dir = optarg; /* specify spool path */
+ break;
+ case 'd':
+ debug = true;
+ break;
+ case 's':
+ syncfiles = false; /* do not fsync articles */
+ break;
+ }
+ ac -= optind;
+ av += optind;
+
+ if (chdir(Dir) < 0) {
+ fprintf(stderr, "crosspost cant chdir %s %s\n",
+ Dir, strerror(errno));
+ exit(1);
+ }
+ openlog("crosspost", L_OPENLOG_FLAGS | LOG_PID, LOG_INN_PROG);
+ STATTime = time (NULL);
+ if (ac == 0)
+ ProcessIncoming(QIOfdopen(STDIN_FILENO));
+ else {
+ for ( ; *av; av++)
+ if (strcmp(*av, "-") == 0)
+ ProcessIncoming(QIOfdopen(STDIN_FILENO));
+ else if ((qp = QIOopen(*av)) == NULL)
+ fprintf(stderr, "crosspost cant open %s %s\n",
+ *av, strerror(errno));
+ else
+ ProcessIncoming(qp);
+ }
+
+ ProcessStats();
+ exit(0);
+ /* NOTREACHED */
+}
--- /dev/null
+/* $Id: cvtbatch.c 6135 2003-01-19 01:15:40Z rra $
+**
+** Read file list on standard input and spew out batchfiles.
+*/
+
+#include "config.h"
+#include "clibrary.h"
+
+#include "inn/innconf.h"
+#include "inn/messages.h"
+#include "inn/qio.h"
+#include "inn/wire.h"
+#include "libinn.h"
+#include "paths.h"
+#include "storage.h"
+
+
+int
+main(int ac, char *av[]) {
+ int i;
+ QIOSTATE *qp;
+ char *line;
+ const char *text;
+ char *format;
+ char *p, *q;
+ const char *r;
+ bool Dirty;
+ TOKEN token;
+ ARTHANDLE *art;
+ int len;
+
+ /* First thing, set up our identity. */
+ message_program_name = "cvtbatch";
+ if (!innconf_read(NULL))
+ exit(1);
+
+ /* Parse JCL. */
+ format = xstrdup("nm");
+ while ((i = getopt(ac, av, "w:")) != EOF)
+ switch (i) {
+ default:
+ die("usage error");
+ break;
+ case 'w':
+ for (p = format = optarg; *p; p++) {
+ switch (*p) {
+ case FEED_BYTESIZE:
+ case FEED_FULLNAME:
+ case FEED_MESSAGEID:
+ case FEED_NAME:
+ continue;
+ }
+ warn("ignoring %c in -w flag", *p);
+ }
+ }
+ ac -= optind;
+ av += optind;
+ if (ac)
+ die("usage error");
+
+ if (!SMinit())
+ die("cannot initialize storage manager: %s", SMerrorstr);
+
+ /* Loop over all input. */
+ qp = QIOfdopen((int)fileno(stdin));
+ while ((line = QIOread(qp)) != NULL) {
+ for (p = line; *p; p++)
+ if (ISWHITE(*p)) {
+ *p = '\0';
+ break;
+ }
+
+ if (!IsToken(line))
+ continue;
+ token = TextToToken(line);
+ if ((art = SMretrieve(token, RETR_HEAD)) == NULL)
+ continue;
+ text = wire_findheader(art->data, art->len, "Message-ID");
+ if (text == NULL) {
+ SMfreearticle(art);
+ continue;
+ }
+ len = art->len;
+ for (r = text; r < art->data + art->len; r++) {
+ if (*r == '\r' || *r == '\n')
+ break;
+ }
+ if (r == art->data + art->len) {
+ SMfreearticle(art);
+ continue;
+ }
+ q = xmalloc(r - text + 1);
+ memcpy(q, text, r - text);
+ SMfreearticle(art);
+ q[r - text] = '\0';
+
+ /* Write the desired info. */
+ for (Dirty = false, p = format; *p; p++) {
+ switch (*p) {
+ default:
+ continue;
+ case FEED_BYTESIZE:
+ if (Dirty)
+ putchar(' ');
+ printf("%d", len);
+ break;
+ case FEED_FULLNAME:
+ case FEED_NAME:
+ if (Dirty)
+ putchar(' ');
+ printf("%s", line);
+ break;
+ case FEED_MESSAGEID:
+ if (Dirty)
+ putchar(' ');
+ printf("%s", q);
+ break;
+ }
+ Dirty = true;
+ }
+ free(q);
+ if (Dirty)
+ putchar('\n');
+ }
+
+ exit(0);
+ /* NOTREACHED */
+}
--- /dev/null
+/* $Id: filechan.c 6135 2003-01-19 01:15:40Z rra $
+**
+** An InterNetNews channel process that splits a funnel entry into
+** separate files. Originally from Robert Elz <kre@munnari.oz.au>.
+*/
+
+#include "config.h"
+#include "clibrary.h"
+#include <errno.h>
+#include <fcntl.h>
+#include <sys/stat.h>
+
+#include "inn/innconf.h"
+#include "inn/messages.h"
+#include "libinn.h"
+#include "paths.h"
+
+#include "map.h"
+
+int
+main(int ac, char *av[])
+{
+ char buff[2048];
+ char *p;
+ char *next;
+ int i;
+ int fd;
+ int Fields;
+ const char *Directory;
+ bool Map;
+ FILE *F;
+ struct stat Sb;
+ uid_t uid;
+ gid_t gid;
+ uid_t myuid;
+
+ /* First thing, set up our identity. */
+ message_program_name = "filechan";
+
+ /* Set defaults. */
+ if (!innconf_read(NULL))
+ exit(1);
+ Fields = 1;
+ Directory = innconf->pathoutgoing;
+ Map = false;
+ myuid = geteuid();
+ umask(NEWSUMASK);
+
+ /* Parse JCL. */
+ while ((i = getopt(ac, av, "d:f:m:p:")) != EOF)
+ switch (i) {
+ default:
+ die("usage error");
+ break;
+ case 'd':
+ Directory = optarg;
+ break;
+ case 'f':
+ Fields = atoi(optarg);
+ break;
+ case 'm':
+ Map = true;
+ MAPread(optarg);
+ break;
+ case 'p':
+ if ((F = fopen(optarg, "w")) == NULL)
+ sysdie("cannot fopen %s", optarg);
+ fprintf(F, "%ld\n", (long)getpid());
+ if (ferror(F) || fclose(F) == EOF)
+ sysdie("cannot fclose %s", optarg);
+ break;
+ }
+
+ /* Move, and get owner of current directory. */
+ if (chdir(Directory) < 0)
+ sysdie("cannot chdir to %s", Directory);
+ if (stat(".", &Sb) < 0)
+ sysdie("cannot stat %s", Directory);
+ uid = Sb.st_uid;
+ gid = Sb.st_gid;
+
+ /* Read input. */
+ while (fgets(buff, sizeof buff, stdin) != NULL) {
+ if ((p = strchr(buff, '\n')) != NULL)
+ *p = '\0';
+
+ /* Skip the right number of leading fields. */
+ for (i = Fields, p = buff; *p; p++)
+ if (*p == ' ' && --i <= 0)
+ break;
+ if (*p == '\0')
+ /* Nothing to write. Probably shouldn't happen. */
+ continue;
+
+ /* Add a newline, get the length of all leading fields. */
+ *p++ = '\n';
+ i = p - buff;
+
+ /* Rest of the line is space-separated list of filenames. */
+ for (; *p; p = next) {
+ /* Skip whitespace, get next word. */
+ while (*p == ' ')
+ p++;
+ for (next = p; *next && *next != ' '; next++)
+ continue;
+ if (*next)
+ *next++ = '\0';
+
+ if (Map)
+ p = MAPname(p);
+ fd = open(p, O_CREAT | O_WRONLY | O_APPEND, BATCHFILE_MODE);
+ if (fd >= 0) {
+ /* Try to lock it and set the ownership right. */
+ inn_lock_file(fd, INN_LOCK_WRITE, true);
+ if (myuid == 0 && uid != 0)
+ chown(p, uid, gid);
+
+ /* Just in case, seek to the end. */
+ lseek(fd, 0, SEEK_END);
+
+ errno = 0;
+ if (write(fd, buff, i) != i)
+ sysdie("write failed");
+
+ close(fd);
+ }
+ }
+ }
+
+ exit(0);
+ /* NOTREACHED */
+}
--- /dev/null
+/* $Id: inndf.c 6677 2004-03-03 18:36:07Z hkehoe $
+**
+** Reports free kilobytes (not disk blocks) or free inodes.
+**
+** Written by Ian Dickinson <idickins@fore.com>
+** Wed Jul 26 10:11:38 BST 1995 (My birthday - 27 today!)
+**
+** inndf is a replacement for 'df | awk' in innwatch.ctl and for reporting
+** free space in other INN scripts. It doesn't sync, it forks less, and
+** it's generally less complicated.
+**
+** Usage: inndf [-i] <directory> [<directory> ...]
+** inndf -n
+** inndf -o
+**
+** Compile with -lserver (ie. /usr/lib/libserver.a) if you run Sun's Online
+** DiskSuite under SunOS 4.x. The wrapper functions there make the system
+** call transparent; they copy the f_spare values to the correct spots, so
+** f_blocks, f_bfree, f_bavail can exceed 2GB.
+**
+** Compile with -DHAVE_STATVFS for these systems:
+** System V Release 4.x
+** Solaris 2.x
+** HP-UX 10.x
+** OSF1
+**
+** Compile with -DHAVE_STATFS for these systems:
+** SunOS 4.x/Solaris 1.x
+** HP-UX 9.x
+** Linux
+** NeXTstep 3.x
+**
+** (Or even better, let autoconf take care of it.)
+**
+** Thanks to these folks for bug fixes and porting information:
+** Mahesh Ramachandran <rr@eel.ufl.edu>
+** Chuck Swiger <chuck@its.com>
+** Sang-yong Suh <sysuh@kigam.re.kr>
+** Swa Frantzen <Swa.Frantzen@Belgium.EU.net>
+** Brad Dickey <bdickey@haverford.edu>
+** Taso N. Devetzis <devetzis@snet.net>
+** Wei-Yeh Lee <weiyeh@columbia.edu>
+** Jeff Garzik <jeff.garzik@spinne.com>
+*/
+
+#include "config.h"
+#include "clibrary.h"
+
+#include "inn/innconf.h"
+#include "inn/messages.h"
+#include "inn/qio.h"
+#include "libinn.h"
+#include "ov.h"
+#include "paths.h"
+
+/* The portability mess. Hide everything in macros so that the actual code
+ is relatively clean. SysV uses statvfs, BSD uses statfs, and ULTRIX is
+ just weird (and isn't worth checking for in configure).
+
+ df_declare declares a variable of the appropriate type to pass to df_stat
+ along with a path; df_stat will return true on success, false on failure.
+ df_avail gives the number of free blocks, the size of those blocks given
+ in df_bsize (which handles SysV's weird fragment vs. preferred block size
+ thing). df_inodes returns the free inodes. */
+#if HAVE_STATVFS
+# include <sys/statvfs.h>
+# define df_stat(p, s) (statvfs((p), (s)) == 0)
+# define df_declare(s) struct statvfs s
+# define df_total(s) ((s).f_blocks)
+# define df_avail(s) ((s).f_bavail)
+# define df_scale(s) ((s).f_frsize == 0 ? (s).f_bsize : (s).f_frsize)
+# define df_files(s) ((s).f_files)
+# define df_favail(s) ((s).f_favail)
+#elif HAVE_STATFS
+# if HAVE_SYS_VFS_H
+# include <sys/vfs.h>
+# endif
+# if HAVE_SYS_PARAM_H
+# include <sys/param.h>
+# endif
+# if HAVE_SYS_MOUNT_H
+# include <sys/mount.h>
+# endif
+# ifdef __ultrix__
+# define df_stat(p, s) (statfs((p), (s)) >= 1)
+# define df_declare(s) struct fs_data s
+# define df_total(s) ((s).fd_btot)
+# define df_avail(s) ((s).fd_bfreen)
+# define df_scale(s) 1024
+# define df_files(s) ((s).fd_gtot)
+# define df_favail(s) ((s).fd_gfree)
+# else
+# define df_stat(p, s) (statfs((p), (s)) == 0)
+# define df_declare(s) struct statfs s
+# define df_total(s) ((s).f_blocks)
+# define df_avail(s) ((s).f_bavail)
+# define df_scale(s) ((s).f_bsize)
+# define df_files(s) ((s).f_files)
+# define df_favail(s) ((s).f_ffree)
+# endif
+#else
+# error "Platform not supported. Neither statvfs nor statfs available."
+#endif
+
+static const char usage[] = "\
+Usage: inndf [-i] [-f filename] [-F] <directory> [<directory> ...]\n\
+ inndf -n\n\
+ inndf -o\n\
+\n\
+The first form gives the free space in kilobytes (or the count of free\n\
+inodes if -i is given) in the file systems given by the arguments. If\n\
+-f is given, the corresponding file should be a list of directories to\n\
+check in addition to the arguments. -F uses <pathetc>/filesystems as the\n\
+file and is otherwise the same.\n\
+\n\
+The second form gives the total count of overview records stored. The\n\
+third form gives the percentage space allocated to overview that's been\n\
+used (if the overview method used supports this query).";
+
+/*
+** Given a path, a flag saying whether to look at inodes instead of free
+** disk space, and a flag saying whether to format in columns, print out
+** the amount of free space or inodes on that file system. Returns the
+** percentage free, which may be printed out by the caller.
+*/
+static void
+printspace(const char *path, bool inode, bool fancy)
+{
+ df_declare(info);
+ unsigned long amount;
+ double percent;
+
+ if (df_stat(path, &info)) {
+ if (inode) {
+ amount = df_favail(info);
+
+ /* This value is compared using the shell by innwatch, and some
+ shells can't cope with anything larger than the maximum value
+ of a signed long. ReiserFS returns 2^32 - 1, however, since it
+ has no concept of inodes. So cap the returned value at the max
+ value of a signed long. */
+ if (amount > (1UL << 31) - 1)
+ amount = (1UL << 31) - 1;
+
+ /* 2.6 kernels show 0 available and used inodes, instead. */
+ if (amount == 0 && df_files(info) == 0)
+ amount = (1UL << 31) - 1;
+ } else {
+ /* Do the multiplication in floating point to try to retain
+ accuracy if the free space in bytes would overflow an
+ unsigned long. This should be safe until file systems larger
+ than 4TB (which may not be much longer -- we should use long
+ long instead if we have it).
+
+ Be very careful about the order of casts here; it's too
+ easy to cast back into an unsigned long a value that
+ overflows, and one then gets silently wrong results. */
+ amount = (unsigned long)
+ (((double) df_avail(info) * df_scale(info)) / 1024.0);
+ }
+ } else {
+ /* On error, free space is zero. */
+ amount = 0;
+ }
+ printf(fancy ? "%10lu" : "%lu", amount);
+ if (fancy) {
+ printf(inode ? " inodes available " : " Kbytes available ");
+ if (inode)
+ percent = 100 * ((double) df_favail(info) / df_files(info));
+ else
+ percent = 100 * ((double) df_avail(info) / df_total(info));
+ if (percent < 9.95)
+ printf(" (%3.1f%%)", percent);
+ else if (percent < 99.95)
+ printf(" (%4.1f%%)", percent);
+ else
+ printf("(%5.1f%%)", percent);
+ }
+}
+
+static void
+printspace_formatted(const char *path, bool inode)
+{
+ printf("%-40s ", path);
+ printspace(path, inode, true);
+ printf("\n");
+}
+
+static char *
+readline(QIOSTATE *qp)
+{
+ char *line, *p;
+
+ for (line = QIOread(qp); line != NULL; line = QIOread(qp)) {
+ p = strchr(line, '#');
+ if (p != NULL)
+ *p = '\0';
+ for (; *line == ' ' || *line == '\t'; line++)
+ ;
+ if (*line != '\0') {
+ for (p = line; *p != '\0' && *p != ' ' && *p != '\t'; p++)
+ ;
+ *p = '\0';
+ return line;
+ }
+ }
+ return NULL;
+}
+
+int
+main(int argc, char *argv[])
+{
+ int option, i, count;
+ unsigned long total;
+ QIOSTATE *qp;
+ char *active, *group, *line, *p;
+ char *file = NULL;
+ bool inode = false;
+ bool overview = false;
+ bool ovcount = false;
+ bool use_filesystems = false;
+
+ while ((option = getopt(argc, argv, "hinof:F")) != EOF) {
+ switch (option) {
+ default:
+ die(usage);
+ case 'h':
+ printf("%s\n", usage);
+ exit(0);
+ case 'i':
+ inode = true;
+ break;
+ case 'n':
+ ovcount = true;
+ break;
+ case 'o':
+ overview = true;
+ break;
+ case 'f':
+ if (file != NULL)
+ die("inndf: Only one of -f or -F may be given");
+ file = xstrdup(optarg);
+ break;
+ case 'F':
+ if (file != NULL)
+ die("inndf: Only one of -f or -F may be given");
+ if (!innconf_read(NULL))
+ exit(1);
+ file = concatpath(innconf->pathetc, INN_PATH_FILESYSTEMS);
+ use_filesystems = true;
+ break;
+ }
+ }
+ argc -= optind;
+ argv += optind;
+
+ if (argc == 0 && !overview && !ovcount && file == NULL)
+ die(usage);
+
+ /* Set the program name now rather than earlier so that it doesn't get
+ prepended to usage messages. */
+ message_program_name = "inndf";
+
+ /* If directories were specified, get statistics about them. If only
+ one was given, just print out the number without the path or any
+ explanatory text; this mode is used by e.g. innwatch. Otherwise,
+ format things nicely. */
+ if (argc == 1 && !overview && !ovcount && file == NULL) {
+ printspace(argv[0], inode, false);
+ printf("\n");
+ } else {
+ for (i = 0; i < argc; i++)
+ printspace_formatted(argv[i], inode);
+ if (file != NULL) {
+ qp = QIOopen(file);
+ if (qp == NULL) {
+ if (!use_filesystems)
+ sysdie("can't open %s", file);
+ } else {
+ line = readline(qp);
+ while (line != NULL) {
+ printspace_formatted(line, inode);
+ line = readline(qp);
+ }
+ QIOclose(qp);
+ }
+ free(file);
+ }
+ }
+
+ /* If we're going to be getting information from overview, do the icky
+ initialization stuff. */
+ if (overview || ovcount) {
+ if (!use_filesystems)
+ if (!innconf_read(NULL))
+ exit(1);
+ if (!OVopen(OV_READ))
+ die("OVopen failed");
+ }
+
+ /* For the count, we have to troll through the active file and query the
+ overview backend for each group. */
+ if (ovcount) {
+ active = concatpath(innconf->pathdb, _PATH_ACTIVE);
+ qp = QIOopen(active);
+ if (qp == NULL)
+ sysdie("can't open %s", active);
+
+ total = 0;
+ group = QIOread(qp);
+ while (group != NULL) {
+ p = strchr(group, ' ');
+ if (p != NULL)
+ *p = '\0';
+ if (OVgroupstats(group, NULL, NULL, &count, NULL))
+ total += count;
+ group = QIOread(qp);
+ }
+ QIOclose(qp);
+ printf("%lu overview records stored\n", total);
+ }
+
+ /* Percentage used is simpler, but only some overview methods understand
+ that query. */
+ if (overview) {
+ if (OVctl(OVSPACE, &count)) {
+ if (count == -1)
+ printf("Space used is meaningless for the %s method\n",
+ innconf->ovmethod);
+ else
+ printf("%d%% overview space used\n", count);
+ }
+ }
+ exit(0);
+}
--- /dev/null
+/* $Id: innxbatch.c 6351 2003-05-19 02:00:06Z rra $
+**
+** Transmit batches to remote site, using the XBATCH command
+** Modelled after innxmit.c and nntpbatch.c
+**
+** Invocation:
+** innxbatch [options] <serverhost> <file> ...
+#ifdef FROMSTDIN
+** innxbatch -i <serverhost>
+#endif FROMSTDIN
+** will connect to serverhost's nntp port, and transfer the named files,
+** with an xbatch command for every file. Files that have been sent
+** successfully are unlink()ed. In case of any error, innxbatch terminates
+** and leaves any remaining files untouched, for later transmission.
+** Options:
+** -D increase debug level
+** -v report statistics to stdout
+#ifdef FROMSTDIN
+** -i read batch file names from stdin instead from command line.
+** For each successfully transmitted batch, an OK is printed on
+** stdout, to indicate that another file name is expected.
+#endif
+** -t Timeout for connection attempt
+** -T Timeout for batch transfers.
+** We do not use any file locking. At worst, a batch could be transmitted
+** twice in parallel by two independant invocations of innxbatch.
+** To prevent this, innxbatch should be invoked by a shell script that uses
+** shlock(1) to achieve mutual exclusion.
+*/
+
+#include "config.h"
+#include "clibrary.h"
+#include "portable/socket.h"
+#include "portable/time.h"
+#include <ctype.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <setjmp.h>
+#include <signal.h>
+#include <syslog.h>
+#include <sys/stat.h>
+
+/* Needed on AIX 4.1 to get fd_set and friends. */
+#ifdef HAVE_SYS_SELECT_H
+# include <sys/select.h>
+#endif
+
+#include "inn/innconf.h"
+#include "inn/messages.h"
+#include "inn/timer.h"
+#include "libinn.h"
+#include "nntp.h"
+
+/*
+** Syslog formats - collected together so they remain consistent
+*/
+static char STAT1[] =
+ "%s stats offered %lu accepted %lu refused %lu rejected %lu";
+static char STAT2[] = "%s times user %.3f system %.3f elapsed %.3f";
+static char CANT_CONNECT[] = "%s connect failed %s";
+static char CANT_AUTHENTICATE[] = "%s authenticate failed %s";
+static char XBATCH_FAIL[] = "%s xbatch failed %s";
+static char UNKNOWN_REPLY[] = "Unknown reply after sending batch -- %s";
+static char CANNOT_UNLINK[] = "cannot unlink %s: %m";
+/*
+** Global variables.
+*/
+static bool Debug = 0;
+static bool STATprint;
+static char *REMhost;
+static double STATbegin;
+static double STATend;
+static char *XBATCHname;
+static int FromServer;
+static int ToServer;
+static sig_atomic_t GotAlarm;
+static sig_atomic_t GotInterrupt;
+static sig_atomic_t JMPyes;
+static jmp_buf JMPwhere;
+static unsigned long STATaccepted;
+static unsigned long STAToffered;
+static unsigned long STATrefused;
+static unsigned long STATrejected;
+
+/*
+** Send a line to the server. \r\n will be appended
+*/
+static bool
+REMwrite(int fd, char *p)
+{
+ int i;
+ int err;
+ char *dest;
+ static char buff[NNTP_STRLEN];
+
+ for (dest = buff, i = 0; p[i]; ) *dest++ = p[i++];
+ *dest++ = '\r';
+ *dest++ = '\n';
+ *dest++ = '\0';
+
+ for (dest = buff, i+=2; i; dest += err, i -= err) {
+ err = write(fd, dest, i);
+ if (err < 0) {
+ syswarn("cannot write %s to %s", dest, REMhost);
+ return false;
+ }
+ }
+ if (Debug)
+ fprintf(stderr, "> %s\n", p);
+
+ return true;
+}
+
+/*
+** Print transfer statistics, clean up, and exit.
+*/
+static void
+ExitWithStats(int x)
+{
+ static char QUIT[] = "quit";
+ double usertime;
+ double systime;
+
+ REMwrite(ToServer, QUIT);
+
+ STATend = TMRnow_double();
+ if (GetResourceUsage(&usertime, &systime) < 0) {
+ usertime = 0;
+ systime = 0;
+ }
+
+ if (STATprint) {
+ printf(STAT1,
+ REMhost, STAToffered, STATaccepted, STATrefused, STATrejected);
+ printf("\n");
+ printf(STAT2, REMhost, usertime, systime, STATend - STATbegin);
+ printf("\n");
+ }
+
+ syslog(L_NOTICE, STAT1,
+ REMhost, STAToffered, STATaccepted, STATrefused, STATrejected);
+ syslog(L_NOTICE, STAT2, REMhost, usertime, systime, STATend - STATbegin);
+
+ exit(x);
+ /* NOTREACHED */
+}
+
+
+/*
+** Clean up the NNTP escapes from a line.
+*/
+static char *
+REMclean(char *buff)
+{
+ char *p;
+
+ if ((p = strchr(buff, '\r')) != NULL)
+ *p = '\0';
+ if ((p = strchr(buff, '\n')) != NULL)
+ *p = '\0';
+
+ /* The dot-escape is only in text, not command responses. */
+ return buff;
+}
+
+
+/*
+** Read a line of input, with timeout. We expect only answer lines, so
+** we ignore \r\n-->\n mapping and the dot escape.
+** Return true if okay, *or we got interrupted.*
+*/
+static bool
+REMread(char *start, int size)
+{
+ char *p, *h;
+ struct timeval t;
+ fd_set rmask;
+ int i;
+
+ for (p = start; size; ) {
+ FD_ZERO(&rmask);
+ FD_SET(FromServer, &rmask);
+ t.tv_sec = 10 * 60;
+ t.tv_usec = 0;
+ i = select(FromServer + 1, &rmask, NULL, NULL, &t);
+ if (GotInterrupt) return true;
+ if (i < 0) {
+ if (errno == EINTR) continue;
+ else return false;
+ }
+ if (i == 0 || !FD_ISSET(FromServer, &rmask)) return false;
+ i = read(FromServer, p, size-1);
+ if (GotInterrupt) return true;
+ if (i <= 0) return false;
+ h = p;
+ p += i;
+ size -= i;
+ for ( ; h < p; h++) {
+ if (h > start && '\n' == *h && '\r' == h[-1]) {
+ *h = h[-1] = '\0';
+ size = 0;
+ }
+ }
+ }
+
+ if (Debug)
+ fprintf(stderr, "< %s\n", start);
+
+ return true;
+}
+
+
+/*
+** Handle the interrupt.
+*/
+static void
+Interrupted(void)
+{
+ warn("interrupted");
+ ExitWithStats(1);
+}
+
+
+/*
+** Send a whole xbatch to the server. Uses the global variables
+** REMbuffer & friends
+*/
+static bool
+REMsendxbatch(int fd, char *buf, int size)
+{
+ char *p;
+ int i;
+ int err;
+
+ for (i = size, p = buf; i; p += err, i -= err) {
+ err = write(fd, p, i);
+ if (err < 0) {
+ syswarn("cannot write xbatch to %s", REMhost);
+ return false;
+ }
+ }
+ if (GotInterrupt) Interrupted();
+ if (Debug)
+ fprintf(stderr, "> [%d bytes of xbatch]\n", size);
+
+ /* What did the remote site say? */
+ if (!REMread(buf, size)) {
+ syswarn("no reply after sending xbatch");
+ return false;
+ }
+ if (GotInterrupt) Interrupted();
+
+ /* Parse the reply. */
+ switch (atoi(buf)) {
+ default:
+ warn("unknown reply after sending batch -- %s", buf);
+ syslog(L_ERROR, UNKNOWN_REPLY, buf);
+ return false;
+ /* NOTREACHED */
+ break;
+ case NNTP_RESENDIT_VAL:
+ case NNTP_GOODBYE_VAL:
+ syslog(L_NOTICE, XBATCH_FAIL, REMhost, buf);
+ STATrejected++;
+ return false;
+ /* NOTREACHED */
+ break;
+ case NNTP_OK_XBATCHED_VAL:
+ STATaccepted++;
+ if (Debug) fprintf(stderr, "will unlink(%s)\n", XBATCHname);
+ if (unlink(XBATCHname)) {
+ /* probably another incarantion was faster, so avoid further duplicate
+ * work
+ */
+ syswarn("cannot unlink %s", XBATCHname);
+ syslog(L_NOTICE, CANNOT_UNLINK, XBATCHname);
+ return false;
+ }
+ break;
+ }
+
+ /* Article sent */
+ return true;
+}
+
+/*
+** Mark that we got interrupted.
+*/
+static RETSIGTYPE
+CATCHinterrupt(int s)
+{
+ GotInterrupt = true;
+
+ /* Let two interrupts kill us. */
+ xsignal(s, SIG_DFL);
+}
+
+
+/*
+** Mark that the alarm went off.
+*/
+/* ARGSUSED0 */
+static RETSIGTYPE
+CATCHalarm(int s UNUSED)
+{
+ GotAlarm = true;
+ if (JMPyes)
+ longjmp(JMPwhere, 1);
+}
+
+
+/*
+** Print a usage message and exit.
+*/
+static void
+Usage(void)
+{
+ warn("Usage: innxbatch [-Dv] [-t#] [-T#] host file ...");
+#ifdef FROMSTDIN
+ warn(" innxbatch [-Dv] [-t#] [-T#] -i host");
+#endif
+ exit(1);
+}
+
+
+int
+main(int ac, char *av[])
+{
+ int i;
+ char *p;
+ FILE *From;
+ FILE *To;
+ char buff[NNTP_STRLEN];
+ RETSIGTYPE (*old)(int) = NULL;
+ unsigned int ConnectTimeout;
+ unsigned int TotalTimeout;
+ struct stat statbuf;
+ int fd;
+ int err;
+ char *XBATCHbuffer = NULL;
+ int XBATCHbuffersize = 0;
+ int XBATCHsize;
+
+ openlog("innxbatch", L_OPENLOG_FLAGS | LOG_PID, LOG_INN_PROG);
+ message_program_name = "innxbatch";
+
+ /* Set defaults. */
+ if (!innconf_read(NULL))
+ exit(1);
+ ConnectTimeout = 0;
+ TotalTimeout = 0;
+ umask(NEWSUMASK);
+
+ /* Parse JCL. */
+ while ((i = getopt(ac, av, "Dit:T:v")) != EOF)
+ switch (i) {
+ default:
+ Usage();
+ /* NOTREACHED */
+ break;
+ case 'D':
+ Debug++;
+ break;
+#ifdef FROMSTDIN
+ case 'i':
+ FromStdin = true;
+ break;
+#endif
+ case 't':
+ ConnectTimeout = atoi(optarg);
+ break;
+ case 'T':
+ TotalTimeout = atoi(optarg);
+ break;
+ case 'v':
+ STATprint = true;
+ break;
+ }
+ ac -= optind;
+ av += optind;
+
+ /* Parse arguments; host and filename. */
+ if (ac < 2)
+ Usage();
+ REMhost = av[0];
+ ac--;
+ av++;
+
+ /* Open a connection to the remote server. */
+ if (ConnectTimeout) {
+ GotAlarm = false;
+ old = xsignal(SIGALRM, CATCHalarm);
+ JMPyes = true;
+ if (setjmp(JMPwhere))
+ die("cannot connect to %s: timed out", REMhost);
+ alarm(ConnectTimeout);
+ }
+ if (NNTPconnect(REMhost, NNTP_PORT, &From, &To, buff) < 0 || GotAlarm) {
+ i = errno;
+ warn("cannot connect to %s: %s", REMhost,
+ buff[0] ? REMclean(buff): strerror(errno));
+ if (GotAlarm)
+ syslog(L_NOTICE, CANT_CONNECT, REMhost, "timeout");
+ else
+ syslog(L_NOTICE, CANT_CONNECT, REMhost,
+ buff[0] ? REMclean(buff) : strerror(i));
+ exit(1);
+ }
+
+ if (Debug)
+ fprintf(stderr, "< %s\n", REMclean(buff));
+ if (NNTPsendpassword(REMhost, From, To) < 0 || GotAlarm) {
+ i = errno;
+ syswarn("cannot authenticate with %s", REMhost);
+ syslog(L_ERROR, CANT_AUTHENTICATE,
+ REMhost, GotAlarm ? "timeout" : strerror(i));
+ /* Don't send quit; we want the remote to print a message. */
+ exit(1);
+ }
+ if (ConnectTimeout) {
+ alarm(0);
+ xsignal(SIGALRM, old);
+ JMPyes = false;
+ }
+
+ /* We no longer need standard I/O. */
+ FromServer = fileno(From);
+ ToServer = fileno(To);
+
+#if defined(SOL_SOCKET) && defined(SO_SNDBUF) && defined(SO_RCVBUF)
+ i = 24 * 1024;
+ if (setsockopt(ToServer, SOL_SOCKET, SO_SNDBUF, (char *)&i, sizeof i) < 0)
+ perror("cant setsockopt(SNDBUF)");
+ if (setsockopt(FromServer, SOL_SOCKET, SO_RCVBUF, (char *)&i, sizeof i) < 0)
+ perror("cant setsockopt(RCVBUF)");
+#endif /* defined(SOL_SOCKET) && defined(SO_SNDBUF) && defined(SO_RCVBUF) */
+
+ GotInterrupt = false;
+ GotAlarm = false;
+
+ /* Set up signal handlers. */
+ xsignal(SIGHUP, CATCHinterrupt);
+ xsignal(SIGINT, CATCHinterrupt);
+ xsignal(SIGTERM, CATCHinterrupt);
+ xsignal(SIGPIPE, SIG_IGN);
+ if (TotalTimeout) {
+ xsignal(SIGALRM, CATCHalarm);
+ alarm(TotalTimeout);
+ }
+
+ /* Start timing. */
+ STATbegin = TMRnow_double();
+
+ /* main loop over all specified files */
+ for (XBATCHname = *av; ac && (XBATCHname = *av); av++, ac--) {
+
+ if (Debug) fprintf(stderr, "will work on %s\n", XBATCHname);
+
+ if (GotAlarm) {
+ warn("timed out");
+ ExitWithStats(1);
+ }
+ if (GotInterrupt) Interrupted();
+
+ if ((fd = open(XBATCHname, O_RDONLY, 0)) < 0) {
+ syswarn("cannot open %s, skipping", XBATCHname);
+ continue;
+ }
+
+ if (fstat(fd, &statbuf)) {
+ syswarn("cannot stat %s, skipping", XBATCHname);
+ close(i);
+ continue;
+ }
+
+ XBATCHsize = statbuf.st_size;
+ if (XBATCHsize == 0) {
+ warn("batch file %s is zero length, skipping", XBATCHname);
+ close(i);
+ unlink(XBATCHname);
+ continue;
+ } else if (XBATCHsize > XBATCHbuffersize) {
+ XBATCHbuffersize = XBATCHsize;
+ if (XBATCHbuffer) free(XBATCHbuffer);
+ XBATCHbuffer = xmalloc(XBATCHsize);
+ }
+
+ err = 0; /* stupid compiler */
+ for (i = XBATCHsize, p = XBATCHbuffer; i; i -= err, p+= err) {
+ err = read(fd, p, i);
+ if (err < 0) {
+ syswarn("error reading %s, skipping", XBATCHname);
+ break;
+ } else if (0 == err) {
+ syswarn("unexpected EOF reading %s, truncated", XBATCHname);
+ XBATCHsize = p - XBATCHbuffer;
+ break;
+ }
+ }
+ close(fd);
+ if (err < 0)
+ continue;
+
+ if (GotInterrupt) Interrupted();
+
+ /* Offer the xbatch. */
+ snprintf(buff, sizeof(buff), "xbatch %d", XBATCHsize);
+ if (!REMwrite(ToServer, buff)) {
+ syswarn("cannot offer xbatch to %s", REMhost);
+ ExitWithStats(1);
+ }
+ STAToffered++;
+ if (GotInterrupt) Interrupted();
+
+ /* Does he want it? */
+ if (!REMread(buff, (int)sizeof buff)) {
+ syswarn("no reply to XBATCH %d from %s", XBATCHsize, REMhost);
+ ExitWithStats(1);
+ }
+ if (GotInterrupt) Interrupted();
+
+ /* Parse the reply. */
+ switch (atoi(buff)) {
+ default:
+ warn("unknown reply to %s -- %s", XBATCHname, buff);
+ ExitWithStats(1);
+ /* NOTREACHED */
+ break;
+ case NNTP_RESENDIT_VAL:
+ case NNTP_GOODBYE_VAL:
+ /* Most likely out of space -- no point in continuing. */
+ syslog(L_NOTICE, XBATCH_FAIL, REMhost, buff);
+ ExitWithStats(1);
+ /* NOTREACHED */
+ case NNTP_CONT_XBATCH_VAL:
+ if (!REMsendxbatch(ToServer, XBATCHbuffer, XBATCHsize))
+ ExitWithStats(1);
+ /* NOTREACHED */
+ break;
+ case NNTP_SYNTAX_VAL:
+ case NNTP_BAD_COMMAND_VAL:
+ warn("server %s seems not to understand XBATCH: %s", REMhost, buff);
+ syslog(L_FATAL, XBATCH_FAIL, REMhost, buff);
+ break;
+ }
+ }
+ ExitWithStats(0);
+ /* NOTREACHED */
+ return 0;
+}
--- /dev/null
+/* $Id: innxmit.c 6716 2004-05-16 20:26:56Z rra $
+**
+** Transmit articles to remote site.
+** Modified for NNTP streaming: 1996-01-03 Jerry Aguirre
+*/
+
+#include "config.h"
+#include "clibrary.h"
+#include "portable/socket.h"
+#include "portable/time.h"
+#include <ctype.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <setjmp.h>
+#include <signal.h>
+#include <syslog.h>
+#include <sys/stat.h>
+#include <sys/uio.h>
+
+/* Needed on AIX 4.1 to get fd_set and friends. */
+#ifdef HAVE_SYS_SELECT_H
+# include <sys/select.h>
+#endif
+
+#include "inn/history.h"
+#include "inn/innconf.h"
+#include "inn/messages.h"
+#include "inn/qio.h"
+#include "inn/timer.h"
+#include "inn/wire.h"
+#include "libinn.h"
+#include "nntp.h"
+#include "paths.h"
+#include "storage.h"
+
+#define OUTPUT_BUFFER_SIZE (16 * 1024)
+
+/* Streaming extensions to NNTP. This extension removes the lock-step
+** limitation of conventional NNTP. Article transfer is several times
+** faster. Negotiated and falls back to old mode if receiver refuses.
+*/
+
+/* max number of articles that can be streamed ahead */
+#define STNBUF 32
+
+/* Send "takethis" without "check" if this many articles were
+** accepted in a row.
+*/
+#define STNC 16
+
+/* typical number of articles to stream */
+/* must be able to fopen this many articles */
+#define STNBUFL (STNBUF/2)
+
+/* number of retries before requeueing to disk */
+#define STNRETRY 5
+
+struct stbufs { /* for each article we are procesing */
+ char *st_fname; /* file name */
+ char *st_id; /* message ID */
+ int st_retry; /* retry count */
+ int st_age; /* age count */
+ ARTHANDLE *art; /* arthandle to read article contents */
+ int st_hash; /* hash value to speed searches */
+ long st_size; /* article size */
+};
+static struct stbufs stbuf[STNBUF]; /* we keep track of this many articles */
+static int stnq; /* current number of active entries in stbuf */
+static long stnofail; /* Count of consecutive successful sends */
+
+static int TryStream = true; /* Should attempt stream negotation? */
+static int CanStream = false; /* Result of stream negotation */
+static int DoCheck = true; /* Should check before takethis? */
+static char modestream[] = "mode stream";
+static char modeheadfeed[] = "mode headfeed";
+static long retries = 0;
+static int logRejects = false ; /* syslog the 437 responses. */
+
+
+
+/*
+** Syslog formats - collected together so they remain consistent
+*/
+static char STAT1[] =
+ "%s stats offered %lu accepted %lu refused %lu rejected %lu missing %lu accsize %.0f rejsize %.0f";
+static char STAT2[] = "%s times user %.3f system %.3f elapsed %.3f";
+static char GOT_BADCOMMAND[] = "%s rejected %s %s";
+static char REJECTED[] = "%s rejected %s (%s) %s";
+static char REJ_STREAM[] = "%s rejected (%s) %s";
+static char CANT_CONNECT[] = "%s connect failed %s";
+static char CANT_AUTHENTICATE[] = "%s authenticate failed %s";
+static char IHAVE_FAIL[] = "%s ihave failed %s";
+
+static char CANT_FINDIT[] = "%s can't find %s";
+static char CANT_PARSEIT[] = "%s can't parse ID %s";
+static char UNEXPECTED[] = "%s unexpected response code %s";
+
+/*
+** Global variables.
+*/
+static bool AlwaysRewrite;
+static bool Debug;
+static bool DoRequeue = true;
+static bool Purging;
+static bool STATprint;
+static bool HeadersFeed;
+static char *BATCHname;
+static char *BATCHtemp;
+static char *REMhost;
+static double STATbegin;
+static double STATend;
+static FILE *BATCHfp;
+static int FromServer;
+static int ToServer;
+static struct history *History;
+static QIOSTATE *BATCHqp;
+static sig_atomic_t GotAlarm;
+static sig_atomic_t GotInterrupt;
+static sig_atomic_t JMPyes;
+static jmp_buf JMPwhere;
+static char *REMbuffer;
+static char *REMbuffptr;
+static char *REMbuffend;
+static unsigned long STATaccepted;
+static unsigned long STAToffered;
+static unsigned long STATrefused;
+static unsigned long STATrejected;
+static unsigned long STATmissing;
+static double STATacceptedsize;
+static double STATrejectedsize;
+
+
+/* Prototypes. */
+static ARTHANDLE *article_open(const char *path, const char *id);
+static void article_free(ARTHANDLE *);
+
+
+/*
+** Return true if the history file has the article expired.
+*/
+static bool
+Expired(char *MessageID) {
+ return !HISlookup(History, MessageID, NULL, NULL, NULL, NULL);
+}
+
+
+/*
+** Flush and reset the site's output buffer. Return false on error.
+*/
+static bool
+REMflush(void)
+{
+ int i;
+
+ if (REMbuffptr == REMbuffer) return true; /* nothing buffered */
+ i = xwrite(ToServer, REMbuffer, (int)(REMbuffptr - REMbuffer));
+ REMbuffptr = REMbuffer;
+ return i < 0 ? false : true;
+}
+
+/*
+** Return index to entry matching this message ID. Else return -1.
+** The hash is to speed up the search.
+** the protocol.
+*/
+static int
+stindex(char *MessageID, int hash) {
+ int i;
+
+ for (i = 0; i < STNBUF; i++) { /* linear search for ID */
+ if ((stbuf[i].st_id) && (stbuf[i].st_id[0])
+ && (stbuf[i].st_hash == hash)) {
+ int n;
+
+ if (strcasecmp(MessageID, stbuf[i].st_id)) continue;
+
+ /* left of '@' is case sensitive */
+ for (n = 0; (MessageID[n] != '@') && (MessageID[n] != '\0'); n++) ;
+ if (strncmp(MessageID, stbuf[i].st_id, n)) continue;
+ else break; /* found a match */
+ }
+ }
+ if (i >= STNBUF) i = -1; /* no match found ? */
+ return (i);
+}
+
+/* stidhash(): calculate a hash value for message IDs to speed comparisons */
+static int
+stidhash(char *MessageID) {
+ char *p;
+ int hash;
+
+ hash = 0;
+ for (p = MessageID + 1; *p && (*p != '>'); p++) {
+ hash <<= 1;
+ if (isascii((int)*p) && isupper((int)*p)) {
+ hash += tolower(*p);
+ } else {
+ hash += *p;
+ }
+ }
+ return hash;
+}
+
+/* stalloc(): save path, ID, and qp into one of the streaming mode entries */
+static int
+stalloc(char *Article, char *MessageID, ARTHANDLE *art, int hash) {
+ int i;
+
+ for (i = 0; i < STNBUF; i++) {
+ if ((!stbuf[i].st_fname) || (stbuf[i].st_fname[0] == '\0')) break;
+ }
+ if (i >= STNBUF) { /* stnq says not full but can not find unused */
+ syslog(L_ERROR, "stalloc: Internal error");
+ return (-1);
+ }
+ if ((int)strlen(Article) >= SPOOLNAMEBUFF) {
+ syslog(L_ERROR, "stalloc: filename longer than %d", SPOOLNAMEBUFF);
+ return (-1);
+ }
+ /* allocate buffers on first use.
+ ** If filename ever is longer than SPOOLNAMEBUFF then code will abort.
+ ** If ID is ever longer than NNTP_STRLEN then other code would break.
+ */
+ if (!stbuf[i].st_fname)
+ stbuf[i].st_fname = xmalloc(SPOOLNAMEBUFF);
+ if (!stbuf[i].st_id)
+ stbuf[i].st_id = xmalloc(NNTP_STRLEN);
+ strlcpy(stbuf[i].st_fname, Article, SPOOLNAMEBUFF);
+ strlcpy(stbuf[i].st_id, MessageID, NNTP_STRLEN);
+ stbuf[i].art = art;
+ stbuf[i].st_hash = hash;
+ stbuf[i].st_retry = 0;
+ stbuf[i].st_age = 0;
+ stnq++;
+ return i;
+}
+
+/* strel(): release for reuse one of the streaming mode entries */
+static void
+strel(int i) {
+ if (stbuf[i].art) {
+ article_free(stbuf[i].art);
+ stbuf[i].art = NULL;
+ }
+ if (stbuf[i].st_id) stbuf[i].st_id[0] = '\0';
+ if (stbuf[i].st_fname) stbuf[i].st_fname[0] = '\0';
+ stnq--;
+}
+
+/*
+** Send a line to the server, adding the dot escape and \r\n.
+*/
+static bool
+REMwrite(char *p, int i, bool escdot) {
+ int size;
+
+ /* Buffer too full? */
+ if (REMbuffend - REMbuffptr < i + 3) {
+ if (!REMflush())
+ return false;
+ if (REMbuffend - REMbuffer < i + 3) {
+ /* Line too long -- grow buffer. */
+ size = i * 2;
+ REMbuffer = xrealloc(REMbuffer, size);
+ REMbuffend = &REMbuffer[size];
+ }
+ }
+
+ /* Dot escape, text of the line, line terminator. */
+ if (escdot && (*p == '.'))
+ *REMbuffptr++ = '.';
+ memcpy(REMbuffptr, p, i);
+ REMbuffptr += i;
+ *REMbuffptr++ = '\r';
+ *REMbuffptr++ = '\n';
+
+ return true;
+}
+
+
+/*
+** Print transfer statistics, clean up, and exit.
+*/
+static void
+ExitWithStats(int x)
+{
+ static char QUIT[] = "quit";
+ double usertime;
+ double systime;
+
+ if (!Purging) {
+ REMwrite(QUIT, strlen(QUIT), false);
+ REMflush();
+ }
+ STATend = TMRnow_double();
+ if (GetResourceUsage(&usertime, &systime) < 0) {
+ usertime = 0;
+ systime = 0;
+ }
+
+ if (STATprint) {
+ printf(STAT1, REMhost, STAToffered, STATaccepted, STATrefused,
+ STATrejected, STATmissing, STATacceptedsize, STATrejectedsize);
+ printf("\n");
+ printf(STAT2, REMhost, usertime, systime, STATend - STATbegin);
+ printf("\n");
+ }
+
+ syslog(L_NOTICE, STAT1, REMhost, STAToffered, STATaccepted, STATrefused,
+ STATrejected, STATmissing, STATacceptedsize, STATrejectedsize);
+ syslog(L_NOTICE, STAT2, REMhost, usertime, systime, STATend - STATbegin);
+ if (retries)
+ syslog(L_NOTICE, "%s %lu Streaming retries", REMhost, retries);
+
+ if (BATCHfp != NULL && unlink(BATCHtemp) < 0 && errno != ENOENT)
+ syswarn("cannot remove %s", BATCHtemp);
+ sleep(1);
+ SMshutdown();
+ HISclose(History);
+ exit(x);
+ /* NOTREACHED */
+}
+
+
+/*
+** Close the batchfile and the temporary file, and rename the temporary
+** to be the batchfile.
+*/
+static void
+CloseAndRename(void)
+{
+ /* Close the files, rename the temporary. */
+ if (BATCHqp) {
+ QIOclose(BATCHqp);
+ BATCHqp = NULL;
+ }
+ if (ferror(BATCHfp)
+ || fflush(BATCHfp) == EOF
+ || fclose(BATCHfp) == EOF) {
+ unlink(BATCHtemp);
+ syswarn("cannot close %s", BATCHtemp);
+ ExitWithStats(1);
+ }
+ if (rename(BATCHtemp, BATCHname) < 0) {
+ syswarn("cannot rename %s", BATCHtemp);
+ ExitWithStats(1);
+ }
+}
+
+
+/*
+** Requeue an article, opening the temp file if we have to. If we get
+** a file write error, exit so that the original input is left alone.
+*/
+static void
+Requeue(const char *Article, const char *MessageID)
+{
+ int fd;
+
+ /* Temp file already open? */
+ if (BATCHfp == NULL) {
+ fd = mkstemp(BATCHtemp);
+ if (fd < 0) {
+ syswarn("cannot create a temporary file");
+ ExitWithStats(1);
+ }
+ BATCHfp = fdopen(fd, "w");
+ if (BATCHfp == NULL) {
+ syswarn("cannot open %s", BATCHtemp);
+ ExitWithStats(1);
+ }
+ }
+
+ /* Called only to get the file open? */
+ if (Article == NULL)
+ return;
+
+ if (MessageID != NULL)
+ fprintf(BATCHfp, "%s %s\n", Article, MessageID);
+ else
+ fprintf(BATCHfp, "%s\n", Article);
+ if (fflush(BATCHfp) == EOF || ferror(BATCHfp)) {
+ syswarn("cannot requeue %s", Article);
+ ExitWithStats(1);
+ }
+}
+
+
+/*
+** Requeue an article then copy the rest of the batch file out.
+*/
+static void
+RequeueRestAndExit(char *Article, char *MessageID) {
+ char *p;
+
+ if (!AlwaysRewrite
+ && STATaccepted == 0 && STATrejected == 0 && STATrefused == 0
+ && STATmissing == 0) {
+ warn("nothing sent -- leaving batchfile alone");
+ ExitWithStats(1);
+ }
+
+ warn("rewriting batch file and exiting");
+ if (CanStream) { /* streaming mode has a buffer of articles */
+ int i;
+
+ for (i = 0; i < STNBUF; i++) { /* requeue unacknowledged articles */
+ if ((stbuf[i].st_fname) && (stbuf[i].st_fname[0] != '\0')) {
+ if (Debug)
+ fprintf(stderr, "stbuf[%d]= %s, %s\n",
+ i, stbuf[i].st_fname, stbuf[i].st_id);
+ Requeue(stbuf[i].st_fname, stbuf[i].st_id);
+ if (Article == stbuf[i].st_fname) Article = NULL;
+ strel(i); /* release entry */
+ }
+ }
+ }
+ Requeue(Article, MessageID);
+
+ for ( ; BATCHqp; ) {
+ if ((p = QIOread(BATCHqp)) == NULL) {
+ if (QIOtoolong(BATCHqp)) {
+ warn("skipping long line in %s", BATCHname);
+ QIOread(BATCHqp);
+ continue;
+ }
+ if (QIOerror(BATCHqp)) {
+ syswarn("cannot read %s", BATCHname);
+ ExitWithStats(1);
+ }
+
+ /* Normal EOF. */
+ break;
+ }
+
+ if (fprintf(BATCHfp, "%s\n", p) == EOF
+ || ferror(BATCHfp)) {
+ syswarn("cannot requeue %s", p);
+ ExitWithStats(1);
+ }
+ }
+
+ CloseAndRename();
+ ExitWithStats(1);
+}
+
+
+/*
+** Clean up the NNTP escapes from a line.
+*/
+static char *
+REMclean(char *buff) {
+ char *p;
+
+ if ((p = strchr(buff, '\r')) != NULL)
+ *p = '\0';
+ if ((p = strchr(buff, '\n')) != NULL)
+ *p = '\0';
+
+ /* The dot-escape is only in text, not command responses. */
+ return buff;
+}
+
+
+/*
+** Read a line of input, with timeout. Also handle \r\n-->\n mapping
+** and the dot escape. Return true if okay, *or we got interrupted.*
+*/
+static bool
+REMread(char *start, int size) {
+ static int count;
+ static char buffer[BUFSIZ];
+ static char *bp;
+ char *p;
+ char *q;
+ char *end;
+ struct timeval t;
+ fd_set rmask;
+ int i;
+ char c;
+
+ if (!REMflush())
+ return false;
+
+ for (p = start, end = &start[size - 1]; ; ) {
+ if (count == 0) {
+ /* Fill the buffer. */
+ Again:
+ FD_ZERO(&rmask);
+ FD_SET(FromServer, &rmask);
+ t.tv_sec = 10 * 60;
+ t.tv_usec = 0;
+ i = select(FromServer + 1, &rmask, NULL, NULL, &t);
+ if (GotInterrupt)
+ return true;
+ if (i < 0) {
+ if (errno == EINTR)
+ goto Again;
+ return false;
+ }
+ if (i == 0 || !FD_ISSET(FromServer, &rmask))
+ return false;
+ count = read(FromServer, buffer, sizeof buffer);
+ if (GotInterrupt)
+ return true;
+ if (count <= 0)
+ return false;
+ bp = buffer;
+ }
+
+ /* Process next character. */
+ count--;
+ c = *bp++;
+ if (c == '\n')
+ break;
+ if (p < end)
+ *p++ = c;
+ }
+
+ /* We know we got \n; if previous char was \r, turn it into \n. */
+ if (p > start && p < end && p[-1] == '\r')
+ p[-1] = '\n';
+ *p = '\0';
+
+ /* Handle the dot escape. */
+ if (*p == '.') {
+ if (p[1] == '\n' && p[2] == '\0')
+ /* EOF. */
+ return false;
+ for (q = &start[1]; (*p++ = *q++) != '\0'; )
+ continue;
+ }
+ return true;
+}
+
+
+/*
+** Handle the interrupt.
+*/
+static void
+Interrupted(char *Article, char *MessageID) {
+ warn("interrupted");
+ RequeueRestAndExit(Article, MessageID);
+}
+
+
+/*
+** Returns the length of the headers.
+*/
+static int
+HeadersLen(ARTHANDLE *art, int *iscmsg) {
+ const char *p;
+ char lastchar = -1;
+
+ /* from nnrpd/article.c ARTsendmmap() */
+ for (p = art->data; p < (art->data + art->len); p++) {
+ if (*p == '\r')
+ continue;
+ if (*p == '\n') {
+ if (lastchar == '\n') {
+ if (*(p-1) == '\r')
+ p--;
+ break;
+ }
+ if (*(p + 1) == 'C' && strncasecmp(p + 1, "Control: ", 9) == 0)
+ *iscmsg = 1;
+ }
+ lastchar = *p;
+ }
+ return (p - art->data);
+}
+
+
+/*
+** Send a whole article to the server.
+*/
+static bool
+REMsendarticle(char *Article, char *MessageID, ARTHANDLE *art) {
+ char buff[NNTP_STRLEN];
+
+ if (!REMflush())
+ return false;
+ if (HeadersFeed) {
+ struct iovec vec[3];
+ char buf[20];
+ int iscmsg = 0;
+ int len = HeadersLen(art, &iscmsg);
+
+ vec[0].iov_base = (char *) art->data;
+ vec[0].iov_len = len;
+ /* Add 14 bytes, which maybe will be the length of the Bytes header */
+ snprintf(buf, sizeof(buf), "Bytes: %lu\r\n",
+ (unsigned long) art->len + 14);
+ vec[1].iov_base = buf;
+ vec[1].iov_len = strlen(buf);
+ if (iscmsg) {
+ vec[2].iov_base = (char *) art->data + len;
+ vec[2].iov_len = art->len - len;
+ } else {
+ vec[2].iov_base = (char *) "\r\n.\r\n";
+ vec[2].iov_len = 5;
+ }
+ if (xwritev(ToServer, vec, 3) < 0)
+ return false;
+ } else
+ if (xwrite(ToServer, art->data, art->len) < 0)
+ return false;
+ if (GotInterrupt)
+ Interrupted(Article, MessageID);
+ if (Debug) {
+ fprintf(stderr, "> [ article %lu ]\n", (unsigned long) art->len);
+ fprintf(stderr, "> .\n");
+ }
+
+ if (CanStream) return true; /* streaming mode does not wait for ACK */
+
+ /* What did the remote site say? */
+ if (!REMread(buff, (int)sizeof buff)) {
+ syswarn("no reply after sending %s", Article);
+ return false;
+ }
+ if (GotInterrupt)
+ Interrupted(Article, MessageID);
+ if (Debug)
+ fprintf(stderr, "< %s", buff);
+
+ /* Parse the reply. */
+ switch (atoi(buff)) {
+ default:
+ warn("unknown reply after %s -- %s", Article, buff);
+ if (DoRequeue)
+ Requeue(Article, MessageID);
+ break;
+ case NNTP_BAD_COMMAND_VAL:
+ case NNTP_SYNTAX_VAL:
+ case NNTP_ACCESS_VAL:
+ /* The receiving server is likely confused...no point in continuing */
+ syslog(L_FATAL, GOT_BADCOMMAND, REMhost, MessageID, REMclean(buff));
+ RequeueRestAndExit(Article, MessageID);
+ /* NOTREACHED */
+ case NNTP_RESENDIT_VAL:
+ case NNTP_GOODBYE_VAL:
+ Requeue(Article, MessageID);
+ break;
+ case NNTP_TOOKIT_VAL:
+ STATaccepted++;
+ STATacceptedsize += (double)art->len;
+ break;
+ case NNTP_REJECTIT_VAL:
+ if (logRejects)
+ syslog(L_NOTICE, REJECTED, REMhost,
+ MessageID, Article, REMclean(buff));
+ STATrejected++;
+ STATrejectedsize += (double)art->len;
+ break;
+ }
+
+ /* Article sent, or we requeued it. */
+ return true;
+}
+\f
+
+/*
+** Get the Message-ID header from an open article.
+*/
+static char *
+GetMessageID(ARTHANDLE *art) {
+ static char *buff;
+ static int buffsize = 0;
+ const char *p, *q;
+
+ p = wire_findheader(art->data, art->len, "Message-ID");
+ if (p == NULL)
+ return NULL;
+ for (q = p; q < art->data + art->len; q++) {
+ if (*q == '\r' || *q == '\n')
+ break;
+ }
+ if (q == art->data + art->len)
+ return NULL;
+ if (buffsize < q - p) {
+ if (buffsize == 0)
+ buff = xmalloc(q - p + 1);
+ else
+ buff = xrealloc(buff, q - p + 1);
+ buffsize = q - p;
+ }
+ memcpy(buff, p, q - p);
+ buff[q - p] = '\0';
+ return buff;
+}
+\f
+
+/*
+** Mark that we got interrupted.
+*/
+static RETSIGTYPE
+CATCHinterrupt(int s) {
+ GotInterrupt = true;
+
+ /* Let two interrupts kill us. */
+ xsignal(s, SIG_DFL);
+}
+
+
+/*
+** Mark that the alarm went off.
+*/
+static RETSIGTYPE
+CATCHalarm(int s UNUSED)
+{
+ GotAlarm = true;
+ if (JMPyes)
+ longjmp(JMPwhere, 1);
+}
+
+/* check articles in streaming NNTP mode
+** return true on failure.
+*/
+static bool
+check(int i) {
+ char buff[NNTP_STRLEN];
+
+ /* send "check <ID>" to the other system */
+ snprintf(buff, sizeof(buff), "check %s", stbuf[i].st_id);
+ if (!REMwrite(buff, (int)strlen(buff), false)) {
+ syswarn("cannot check article");
+ return true;
+ }
+ STAToffered++;
+ if (Debug) {
+ if (stbuf[i].st_retry)
+ fprintf(stderr, "> %s (retry %d)\n", buff, stbuf[i].st_retry);
+ else
+ fprintf(stderr, "> %s\n", buff);
+ }
+ if (GotInterrupt)
+ Interrupted(stbuf[i].st_fname, stbuf[i].st_id);
+
+ /* That all. Response is checked later by strlisten() */
+ return false;
+}
+
+/* Send article in "takethis <id> streaming NNTP mode.
+** return true on failure.
+*/
+static bool
+takethis(int i) {
+ char buff[NNTP_STRLEN];
+
+ if (!stbuf[i].art) {
+ warn("internal error: null article for %s in takethis",
+ stbuf[i].st_fname);
+ return true;
+ }
+ /* send "takethis <ID>" to the other system */
+ snprintf(buff, sizeof(buff), "takethis %s", stbuf[i].st_id);
+ if (!REMwrite(buff, (int)strlen(buff), false)) {
+ syswarn("cannot send takethis");
+ return true;
+ }
+ if (Debug)
+ fprintf(stderr, "> %s\n", buff);
+ if (GotInterrupt)
+ Interrupted((char *)0, (char *)0);
+ if (!REMsendarticle(stbuf[i].st_fname, stbuf[i].st_id, stbuf[i].art))
+ return true;
+ stbuf[i].st_size = stbuf[i].art->len;
+ article_free(stbuf[i].art); /* should not need file again */
+ stbuf[i].art = 0; /* so close to free descriptor */
+ stbuf[i].st_age = 0;
+ /* That all. Response is checked later by strlisten() */
+ return false;
+}
+
+
+/* listen for responses. Process acknowledgments to remove items from
+** the queue. Also sends the articles on request. Returns true on error.
+** return true on failure.
+*/
+static bool
+strlisten(void)
+{
+ int resp;
+ int i;
+ char *id, *p;
+ char buff[NNTP_STRLEN];
+ int hash;
+
+ while(true) {
+ if (!REMread(buff, (int)sizeof buff)) {
+ syswarn("no reply to check");
+ return true;
+ }
+ if (GotInterrupt)
+ Interrupted((char *)0, (char *)0);
+ if (Debug)
+ fprintf(stderr, "< %s", buff);
+
+ /* Parse the reply. */
+ resp = atoi(buff);
+ /* Skip the 1XX informational messages */
+ if ((resp >= 100) && (resp < 200)) continue;
+ switch (resp) { /* first time is to verify it */
+ case NNTP_ERR_GOTID_VAL:
+ case NNTP_OK_SENDID_VAL:
+ case NNTP_OK_RECID_VAL:
+ case NNTP_ERR_FAILID_VAL:
+ case NNTP_RESENDID_VAL:
+ if ((id = strchr(buff, '<')) != NULL) {
+ p = strchr(id, '>');
+ if (p) *(p+1) = '\0';
+ hash = stidhash(id);
+ i = stindex(id, hash); /* find table entry */
+ if (i < 0) { /* should not happen */
+ syslog(L_NOTICE, CANT_FINDIT, REMhost, REMclean(buff));
+ return (true); /* can't find it! */
+ }
+ } else {
+ syslog(L_NOTICE, CANT_PARSEIT, REMhost, REMclean(buff));
+ return (true);
+ }
+ break;
+ case NNTP_GOODBYE_VAL:
+ /* Most likely out of space -- no point in continuing. */
+ syslog(L_NOTICE, IHAVE_FAIL, REMhost, REMclean(buff));
+ return true;
+ /* NOTREACHED */
+ default:
+ syslog(L_NOTICE, UNEXPECTED, REMhost, REMclean(buff));
+ if (Debug)
+ fprintf(stderr, "Unknown reply \"%s\"",
+ buff);
+ return (true);
+ }
+ switch (resp) { /* now we take some action */
+ case NNTP_RESENDID_VAL: /* remote wants it later */
+ /* try again now because time has passed */
+ if (stbuf[i].st_retry < STNRETRY) {
+ if (check(i)) return true;
+ stbuf[i].st_retry++;
+ stbuf[i].st_age = 0;
+ } else { /* requeue to disk for later */
+ Requeue(stbuf[i].st_fname, stbuf[i].st_id);
+ strel(i); /* release entry */
+ }
+ break;
+ case NNTP_ERR_GOTID_VAL: /* remote doesn't want it */
+ strel(i); /* release entry */
+ STATrefused++;
+ stnofail = 0;
+ break;
+
+ case NNTP_OK_SENDID_VAL: /* remote wants article */
+ if (takethis(i)) return true;
+ stnofail++;
+ break;
+
+ case NNTP_OK_RECID_VAL: /* remote received it OK */
+ STATacceptedsize += (double) stbuf[i].st_size;
+ strel(i); /* release entry */
+ STATaccepted++;
+ break;
+
+ case NNTP_ERR_FAILID_VAL:
+ STATrejectedsize += (double) stbuf[i].st_size;
+ if (logRejects)
+ syslog(L_NOTICE, REJ_STREAM, REMhost,
+ stbuf[i].st_fname, REMclean(buff));
+/* XXXXX Caution THERE BE DRAGONS, I don't think this logs properly
+ The message ID is returned in the peer response... so this is redundant
+ stbuf[i].st_id, stbuf[i].st_fname, REMclean(buff)); */
+ strel(i); /* release entry */
+ STATrejected++;
+ stnofail = 0;
+ break;
+ }
+ break;
+ }
+ return (false);
+}
+
+/*
+** Print a usage message and exit.
+*/
+static void
+Usage(void)
+{
+ die("Usage: innxmit [-acdHlprs] [-t#] [-T#] host file");
+}
+
+
+/*
+** Open an article. If the argument is a token, retrieve the article via
+** the storage API. Otherwise, open the file and fake up an ARTHANDLE for
+** it. Only fill in those fields that we'll need. Articles not retrieved
+** via the storage API will have a type of TOKEN_EMPTY.
+*/
+static ARTHANDLE *
+article_open(const char *path, const char *id)
+{
+ TOKEN token;
+ ARTHANDLE *article;
+ int fd, length;
+ struct stat st;
+ char *p;
+
+ if (IsToken(path)) {
+ token = TextToToken(path);
+ article = SMretrieve(token, RETR_ALL);
+ if (article == NULL) {
+ if (SMerrno == SMERR_NOENT || SMerrno == SMERR_UNINIT)
+ STATmissing++;
+ else {
+ warn("requeue %s: %s", path, SMerrorstr);
+ Requeue(path, id);
+ }
+ }
+ return article;
+ } else {
+ char *data;
+ fd = open(path, O_RDONLY);
+ if (fd < 0)
+ return NULL;
+ if (fstat(fd, &st) < 0) {
+ syswarn("requeue %s", path);
+ Requeue(path, id);
+ return NULL;
+ }
+ article = xmalloc(sizeof(ARTHANDLE));
+ article->type = TOKEN_EMPTY;
+ article->len = st.st_size;
+ data = xmalloc(article->len);
+ if (xread(fd, data, article->len) < 0) {
+ syswarn("requeue %s", path);
+ free(data);
+ free(article);
+ close(fd);
+ Requeue(path, id);
+ return NULL;
+ }
+ close(fd);
+ p = memchr(data, '\n', article->len);
+ if (p == NULL || p == data) {
+ warn("requeue %s: cannot find headers", path);
+ free(data);
+ free(article);
+ Requeue(path, id);
+ return NULL;
+ }
+ if (p[-1] != '\r') {
+ p = ToWireFmt(data, article->len, (size_t *)&length);
+ free(data);
+ data = p;
+ article->len = length;
+ }
+ article->data = data;
+ return article;
+ }
+}
+
+
+/*
+** Free an article, using the type field to determine whether to free it
+** via the storage API.
+*/
+static void
+article_free(ARTHANDLE *article)
+{
+ if (article->type == TOKEN_EMPTY) {
+ free((char *)article->data);
+ free(article);
+ } else
+ SMfreearticle(article);
+}
+
+
+int main(int ac, char *av[]) {
+ static char SKIPPING[] = "Skipping \"%s\" --%s?\n";
+ int i;
+ char *p;
+ ARTHANDLE *art;
+ FILE *From;
+ FILE *To;
+ char buff[8192+128];
+ char *Article;
+ char *MessageID;
+ RETSIGTYPE (*old)(int) = NULL;
+ unsigned int ConnectTimeout;
+ unsigned int TotalTimeout;
+ int port = NNTP_PORT;
+ bool val;
+ char *path;
+
+ openlog("innxmit", L_OPENLOG_FLAGS | LOG_PID, LOG_INN_PROG);
+ message_program_name = "innxmit";
+
+ /* Set defaults. */
+ if (!innconf_read(NULL))
+ exit(1);
+
+ ConnectTimeout = 0;
+ TotalTimeout = 0;
+
+ umask(NEWSUMASK);
+
+ /* Parse JCL. */
+ while ((i = getopt(ac, av, "lacdHprst:T:vP:")) != EOF)
+ switch (i) {
+ default:
+ Usage();
+ /* NOTREACHED */
+ case 'P':
+ port = atoi(optarg);
+ break;
+ case 'a':
+ AlwaysRewrite = true;
+ break;
+ case 'c':
+ DoCheck = false;
+ break;
+ case 'd':
+ Debug = true;
+ break;
+ case 'H':
+ HeadersFeed = true;
+ break;
+ case 'l':
+ logRejects = true ;
+ break ;
+ case 'p':
+ AlwaysRewrite = true;
+ Purging = true;
+ break;
+ case 'r':
+ DoRequeue = false;
+ break;
+ case 's':
+ TryStream = false;
+ break;
+ case 't':
+ ConnectTimeout = atoi(optarg);
+ break;
+ case 'T':
+ TotalTimeout = atoi(optarg);
+ break;
+ case 'v':
+ STATprint = true;
+ break;
+ }
+ ac -= optind;
+ av += optind;
+
+ /* Parse arguments; host and filename. */
+ if (ac != 2)
+ Usage();
+ REMhost = av[0];
+ BATCHname = av[1];
+
+ if (chdir(innconf->patharticles) < 0)
+ sysdie("cannot cd to %s", innconf->patharticles);
+
+ val = true;
+ if (!SMsetup(SM_PREOPEN,(void *)&val))
+ die("cannot set up the storage manager");
+ if (!SMinit())
+ die("cannot initialize the storage manager: %s", SMerrorstr);
+
+ /* Open the batch file and lock others out. */
+ if (BATCHname[0] != '/') {
+ BATCHname = concatpath(innconf->pathoutgoing, av[1]);
+ }
+ if (((i = open(BATCHname, O_RDWR)) < 0) || ((BATCHqp = QIOfdopen(i)) == NULL)) {
+ syswarn("cannot open %s", BATCHname);
+ SMshutdown();
+ exit(1);
+ }
+ if (!inn_lock_file(QIOfileno(BATCHqp), INN_LOCK_WRITE, true)) {
+#if defined(EWOULDBLOCK)
+ if (errno == EWOULDBLOCK) {
+ SMshutdown();
+ exit(0);
+ }
+#endif /* defined(EWOULDBLOCK) */
+ syswarn("cannot lock %s", BATCHname);
+ SMshutdown();
+ exit(1);
+ }
+
+ /* Get a temporary name in the same directory as the batch file. */
+ p = strrchr(BATCHname, '/');
+ *p = '\0';
+ BATCHtemp = concatpath(BATCHname, "bchXXXXXX");
+ *p = '/';
+
+ /* Set up buffer used by REMwrite. */
+ REMbuffer = xmalloc(OUTPUT_BUFFER_SIZE);
+ REMbuffend = &REMbuffer[OUTPUT_BUFFER_SIZE];
+ REMbuffptr = REMbuffer;
+
+ /* Start timing. */
+ STATbegin = TMRnow_double();
+
+ if (!Purging) {
+ /* Open a connection to the remote server. */
+ if (ConnectTimeout) {
+ GotAlarm = false;
+ old = xsignal(SIGALRM, CATCHalarm);
+ if (setjmp(JMPwhere)) {
+ warn("cannot connect to %s: timed out", REMhost);
+ SMshutdown();
+ exit(1);
+ }
+ JMPyes = true;
+ alarm(ConnectTimeout);
+ }
+ if (NNTPconnect(REMhost, port, &From, &To, buff) < 0 || GotAlarm) {
+ i = errno;
+ warn("cannot connect to %s: %s", REMhost,
+ buff[0] ? REMclean(buff) : strerror(errno));
+ if (GotAlarm)
+ syslog(L_NOTICE, CANT_CONNECT, REMhost, "timeout");
+ else
+ syslog(L_NOTICE, CANT_CONNECT, REMhost,
+ buff[0] ? REMclean(buff) : strerror(i));
+ SMshutdown();
+ exit(1);
+ }
+ if (Debug)
+ fprintf(stderr, "< %s\n", REMclean(buff));
+ if (NNTPsendpassword(REMhost, From, To) < 0 || GotAlarm) {
+ i = errno;
+ syswarn("cannot authenticate with %s", REMhost);
+ syslog(L_ERROR, CANT_AUTHENTICATE,
+ REMhost, GotAlarm ? "timeout" : strerror(i));
+ /* Don't send quit; we want the remote to print a message. */
+ SMshutdown();
+ exit(1);
+ }
+ if (ConnectTimeout) {
+ alarm(0);
+ xsignal(SIGALRM, old);
+ JMPyes = false;
+ }
+
+ /* We no longer need standard I/O. */
+ FromServer = fileno(From);
+ ToServer = fileno(To);
+
+ if (TryStream) {
+ if (!REMwrite(modestream, (int)strlen(modestream), false)) {
+ syswarn("cannot negotiate %s", modestream);
+ }
+ if (Debug)
+ fprintf(stderr, ">%s\n", modestream);
+ /* Does he understand mode stream? */
+ if (!REMread(buff, (int)sizeof buff)) {
+ syswarn("no reply to %s", modestream);
+ } else {
+ if (Debug)
+ fprintf(stderr, "< %s", buff);
+
+ /* Parse the reply. */
+ switch (atoi(buff)) {
+ default:
+ warn("unknown reply to %s -- %s", modestream, buff);
+ CanStream = false;
+ break;
+ case NNTP_OK_STREAM_VAL: /* YES! */
+ CanStream = true;
+ break;
+ case NNTP_AUTH_NEEDED_VAL: /* authentication refusal */
+ case NNTP_BAD_COMMAND_VAL: /* normal refusal */
+ CanStream = false;
+ break;
+ }
+ }
+ if (CanStream) {
+ for (i = 0; i < STNBUF; i++) { /* reset buffers */
+ stbuf[i].st_fname = 0;
+ stbuf[i].st_id = 0;
+ stbuf[i].art = 0;
+ }
+ stnq = 0;
+ }
+ }
+ if (HeadersFeed) {
+ if (!REMwrite(modeheadfeed, strlen(modeheadfeed), false))
+ syswarn("cannot negotiate %s", modeheadfeed);
+ if (Debug)
+ fprintf(stderr, ">%s\n", modeheadfeed);
+ if (!REMread(buff, sizeof buff)) {
+ syswarn("no reply to %s", modeheadfeed);
+ } else {
+ if (Debug)
+ fprintf(stderr, "< %s", buff);
+
+ /* Parse the reply. */
+ switch (atoi(buff)) {
+ case 250: /* YES! */
+ break;
+ case NNTP_BAD_COMMAND_VAL: /* normal refusal */
+ die("%s not allowed -- %s", modeheadfeed, buff);
+ default:
+ die("unknown reply to %s -- %s", modeheadfeed, buff);
+ }
+ }
+ }
+ }
+
+ /* Set up signal handlers. */
+ xsignal(SIGHUP, CATCHinterrupt);
+ xsignal(SIGINT, CATCHinterrupt);
+ xsignal(SIGTERM, CATCHinterrupt);
+ xsignal(SIGPIPE, SIG_IGN);
+ if (TotalTimeout) {
+ xsignal(SIGALRM, CATCHalarm);
+ alarm(TotalTimeout);
+ }
+
+ path = concatpath(innconf->pathdb, _PATH_HISTORY);
+ History = HISopen(path, innconf->hismethod, HIS_RDONLY);
+ free(path);
+
+ /* Main processing loop. */
+ GotInterrupt = false;
+ GotAlarm = false;
+ for (Article = NULL, MessageID = NULL; ; ) {
+ if (GotAlarm) {
+ warn("timed out");
+ /* Don't resend the current article. */
+ RequeueRestAndExit((char *)NULL, (char *)NULL);
+ }
+ if (GotInterrupt)
+ Interrupted(Article, MessageID);
+
+ if ((Article = QIOread(BATCHqp)) == NULL) {
+ if (QIOtoolong(BATCHqp)) {
+ warn("skipping long line in %s", BATCHname);
+ QIOread(BATCHqp);
+ continue;
+ }
+ if (QIOerror(BATCHqp)) {
+ syswarn("cannot read %s", BATCHname);
+ ExitWithStats(1);
+ }
+
+ /* Normal EOF -- we're done. */
+ QIOclose(BATCHqp);
+ BATCHqp = NULL;
+ break;
+ }
+
+ /* Ignore blank lines. */
+ if (*Article == '\0')
+ continue;
+
+ /* Split the line into possibly two fields. */
+ if (Article[0] == '/'
+ && Article[strlen(innconf->patharticles)] == '/'
+ && strncmp(Article, innconf->patharticles, strlen(innconf->patharticles)) == 0)
+ Article += strlen(innconf->patharticles) + 1;
+ if ((MessageID = strchr(Article, ' ')) != NULL) {
+ *MessageID++ = '\0';
+ if (*MessageID != '<'
+ || (p = strrchr(MessageID, '>')) == NULL
+ || *++p != '\0') {
+ warn("ignoring line %s %s...", Article, MessageID);
+ continue;
+ }
+ }
+
+ if (*Article == '\0') {
+ if (MessageID)
+ warn("empty file name for %s in %s", MessageID, BATCHname);
+ else
+ warn("empty file name, no message ID in %s", BATCHname);
+ /* We could do a history lookup. */
+ continue;
+ }
+
+ if (Purging && MessageID != NULL && !Expired(MessageID)) {
+ Requeue(Article, MessageID);
+ continue;
+ }
+
+ /* Drop articles with a message ID longer than NNTP_MSGID_MAXLEN to
+ avoid overrunning buffers and throwing the server on the
+ receiving end a blow from behind. */
+ if (MessageID != NULL && strlen(MessageID) > NNTP_MSGID_MAXLEN) {
+ warn("dropping article in %s: long message ID %s", BATCHname,
+ MessageID);
+ continue;
+ }
+
+ art = article_open(Article, MessageID);
+ if (art == NULL)
+ continue;
+
+ if (Purging) {
+ article_free(art);
+ Requeue(Article, MessageID);
+ continue;
+ }
+
+ /* Get the Message-ID from the article if we need to. */
+ if (MessageID == NULL) {
+ if ((MessageID = GetMessageID(art)) == NULL) {
+ warn(SKIPPING, Article, "no message ID");
+ article_free(art);
+ continue;
+ }
+ }
+ if (GotInterrupt)
+ Interrupted(Article, MessageID);
+
+ /* Offer the article. */
+ if (CanStream) {
+ int lim;
+ int hash;
+
+ hash = stidhash(MessageID);
+ if (stindex(MessageID, hash) >= 0) { /* skip duplicates in queue */
+ if (Debug)
+ fprintf(stderr, "Skipping duplicate ID %s\n",
+ MessageID);
+ article_free(art);
+ continue;
+ }
+ /* This code tries to optimize by sending a burst of "check"
+ * commands before flushing the buffer. This should result
+ * in several being sent in one packet reducing the network
+ * overhead.
+ */
+ if (DoCheck && (stnofail < STNC)) lim = STNBUF;
+ else lim = STNBUFL;
+ if (stnq >= lim) { /* need to empty a buffer */
+ while (stnq >= STNBUFL) { /* or several */
+ if (strlisten()) {
+ RequeueRestAndExit(Article, MessageID);
+ }
+ }
+ }
+ /* save new article in the buffer */
+ i = stalloc(Article, MessageID, art, hash);
+ if (i < 0) {
+ article_free(art);
+ RequeueRestAndExit(Article, MessageID);
+ }
+ if (DoCheck && (stnofail < STNC)) {
+ if (check(i)) {
+ RequeueRestAndExit((char *)NULL, (char *)NULL);
+ }
+ } else {
+ STAToffered++ ;
+ if (takethis(i)) {
+ RequeueRestAndExit((char *)NULL, (char *)NULL);
+ }
+ }
+ /* check for need to resend any IDs */
+ for (i = 0; i < STNBUF; i++) {
+ if ((stbuf[i].st_fname) && (stbuf[i].st_fname[0] != '\0')) {
+ if (stbuf[i].st_age++ > stnq) {
+ /* This should not happen but just in case ... */
+ if (stbuf[i].st_retry < STNRETRY) {
+ if (check(i)) /* resend check */
+ RequeueRestAndExit((char *)NULL, (char *)NULL);
+ retries++;
+ stbuf[i].st_retry++;
+ stbuf[i].st_age = 0;
+ } else { /* requeue to disk for later */
+ Requeue(stbuf[i].st_fname, stbuf[i].st_id);
+ strel(i); /* release entry */
+ }
+ }
+ }
+ }
+ continue; /* next article */
+ }
+ snprintf(buff, sizeof(buff), "ihave %s", MessageID);
+ if (!REMwrite(buff, (int)strlen(buff), false)) {
+ syswarn("cannot offer article");
+ article_free(art);
+ RequeueRestAndExit(Article, MessageID);
+ }
+ STAToffered++;
+ if (Debug)
+ fprintf(stderr, "> %s\n", buff);
+ if (GotInterrupt)
+ Interrupted(Article, MessageID);
+
+ /* Does he want it? */
+ if (!REMread(buff, (int)sizeof buff)) {
+ syswarn("no reply to ihave");
+ article_free(art);
+ RequeueRestAndExit(Article, MessageID);
+ }
+ if (GotInterrupt)
+ Interrupted(Article, MessageID);
+ if (Debug)
+ fprintf(stderr, "< %s", buff);
+
+ /* Parse the reply. */
+ switch (atoi(buff)) {
+ default:
+ warn("unknown reply to %s -- %s", Article, buff);
+ if (DoRequeue)
+ Requeue(Article, MessageID);
+ break;
+ case NNTP_BAD_COMMAND_VAL:
+ case NNTP_SYNTAX_VAL:
+ case NNTP_ACCESS_VAL:
+ /* The receiving server is likely confused...no point in continuing */
+ syslog(L_FATAL, GOT_BADCOMMAND, REMhost, MessageID, REMclean(buff));
+ RequeueRestAndExit(Article, MessageID);
+ /* NOTREACHED */
+ case NNTP_AUTH_NEEDED_VAL:
+ case NNTP_RESENDIT_VAL:
+ case NNTP_GOODBYE_VAL:
+ /* Most likely out of space -- no point in continuing. */
+ syslog(L_NOTICE, IHAVE_FAIL, REMhost, REMclean(buff));
+ RequeueRestAndExit(Article, MessageID);
+ /* NOTREACHED */
+ case NNTP_SENDIT_VAL:
+ if (!REMsendarticle(Article, MessageID, art))
+ RequeueRestAndExit(Article, MessageID);
+ break;
+ case NNTP_HAVEIT_VAL:
+ STATrefused++;
+ break;
+#if defined(NNTP_SENDIT_LATER)
+ case NNTP_SENDIT_LATER_VAL:
+ Requeue(Article, MessageID);
+ break;
+#endif /* defined(NNTP_SENDIT_LATER) */
+ }
+
+ article_free(art);
+ }
+ if (CanStream) { /* need to wait for rest of ACKs */
+ while (stnq > 0) {
+ if (strlisten()) {
+ RequeueRestAndExit((char *)NULL, (char *)NULL);
+ }
+ }
+ }
+
+ if (BATCHfp != NULL)
+ /* We requeued something, so close the temp file. */
+ CloseAndRename();
+ else if (unlink(BATCHname) < 0 && errno != ENOENT)
+ syswarn("cannot remove %s", BATCHtemp);
+ ExitWithStats(0);
+ /* NOTREACHED */
+ return 0;
+}
--- /dev/null
+/* $Id: map.c 6135 2003-01-19 01:15:40Z rra $
+**
+*/
+
+#include "config.h"
+#include "clibrary.h"
+#include <errno.h>
+
+#include "libinn.h"
+#include "paths.h"
+
+#include "map.h"
+
+
+typedef struct _PAIR {
+ char First;
+ char *Key;
+ char *Value;
+} PAIR;
+
+static PAIR *MAPdata;
+static PAIR *MAPend;
+
+
+/*
+** Free the map.
+*/
+void
+MAPfree(void)
+{
+ PAIR *mp;
+
+ for (mp = MAPdata; mp < MAPend; mp++) {
+ free(mp->Key);
+ free(mp->Value);
+ }
+ free(MAPdata);
+ MAPdata = NULL;
+}
+
+
+/*
+** Read the map file.
+*/
+void
+MAPread(const char *name)
+{
+ FILE *F;
+ int i;
+ PAIR *mp;
+ char *p;
+ char buff[BUFSIZ];
+
+ if (MAPdata != NULL)
+ MAPfree();
+
+ /* Open file, count lines. */
+ if ((F = fopen(name, "r")) == NULL) {
+ fprintf(stderr, "Can't open %s, %s\n", name, strerror(errno));
+ exit(1);
+ }
+ for (i = 0; fgets(buff, sizeof buff, F) != NULL; i++)
+ continue;
+ mp = MAPdata = xmalloc((i + 1) * sizeof(PAIR));
+
+ /* Read each line; ignore blank and comment lines. */
+ fseeko(F, 0, SEEK_SET);
+ while (fgets(buff, sizeof buff, F) != NULL) {
+ if ((p = strchr(buff, '\n')) != NULL)
+ *p = '\0';
+ if (buff[0] == '\0'
+ || buff[0] == '#'
+ || (p = strchr(buff, ':')) == NULL)
+ continue;
+ *p++ = '\0';
+ mp->First = buff[0];
+ mp->Key = xstrdup(buff);
+ mp->Value = xstrdup(p);
+ mp++;
+ }
+ fclose(F);
+ MAPend = mp;
+}
+
+
+/*
+** Look up a name in the map, return original value if not found.
+*/
+char *
+MAPname(char *p)
+{
+ PAIR *mp;
+ char c;
+
+ for (c = *p, mp = MAPdata; mp < MAPend; mp++)
+ if (c == mp->First && strcmp(p, mp->Key) == 0)
+ return mp->Value;
+ return p;
+}
--- /dev/null
+/* $Id: map.h 5292 2002-03-10 08:59:54Z vinocur $
+**
+*/
+
+void MAPfree(void); /* free the map */
+void MAPread(const char *name); /* read the map file */
+char *MAPname(char *p); /* lookup in the map */
--- /dev/null
+#! /usr/bin/perl
+# fixscript will replace this line with require innshellvars.pl
+
+# batch-active-update
+# Author: David Lawrence <tale@isc.org>
+
+# Reads a series of ctlinnd newgroup/rmgroup/changegroup commands, such as
+# is output by checkgroups and actsync, and efficiently handles them all at
+# once. Input can come from command line files or stdin, a la awk/sed.
+
+$oldact = $inn::active; # active file location
+$oldact = $inn::active; # active file location (same; shut up, perl -w)
+$newact = "$oldact.new$$"; # temporary name for new active file
+$actime = "$oldact.times"; # active.times file
+$pausemsg = 'batch active update, ok'; # message to be used for pausing?
+$diff_flags = ''; # Flags for diff(1); default chosen if null.
+
+$0 =~ s#^.*/##;
+
+die "$0: must run as $inn::newsuser user"
+ unless $> == (getpwnam($inn::newsuser))[2];
+
+$debug = -t STDOUT ? 1 : 0;
+
+$| = 1; # show output as it happens (for an rsh/ssh pipe)
+
+# Guess at best flags for a condensed diff listing. The
+# checks for alternative operating systems is incomplete.
+unless ($diff_flags) {
+ if (`diff -v 2>&1` =~ /GNU/) {
+ $diff_flags = '-U0';
+ } elsif ($^O =~ /^(dec_osf|solaris)$/) {
+ $diff_flags = '-C0';
+ } elsif ($^O eq 'nextstep') {
+ $diff_flags = '-c0';
+ } else {
+ $diff_flags = '-c';
+ }
+}
+
+print "reading list of groups to update\n" if $debug;
+
+$eval = "while (<OLDACT>) {\n";
+$eval .= " \$group = (split)[0];\n";
+
+while (<>) {
+ if (/^\s*\S*ctlinnd newgroup (\S+) (\S)/) {
+ $toadd{$1} = $2;
+ } elsif (/^\s*\S*ctlinnd rmgroup (\S+)/) {
+ $eval .= " next if \$group eq '$1';\n";
+ } elsif (/^\s*\S*ctlinnd changegroup (\S+) (\S)/) {
+ $eval .= " s/ \\S+\$/ $2/ if \$group eq '$1';\n";
+ }
+}
+
+$eval .= " delete \$toadd{\$group};\n";
+$eval .= " if (!print(NEWACT \$_)) {\n";
+$eval .= " die \"\$0: writing \$newact failed (\$!), aborting\\n\";\n";
+$eval .= " }\n";
+$eval .= "}\n";
+
+&ctlinnd("pause $pausemsg");
+
+open(OLDACT, "< $oldact") || die "$0: open $oldact: $!\n";
+open(NEWACT, "> $newact") || die "$0: open $newact: $!\n";
+
+print "rewriting active file\n" if $debug;
+eval $eval;
+for (sort keys %toadd) {
+ $add = "$_ 0000000000 0000000001 $toadd{$_}\n";
+ if (!print( NEWACT $add)) {
+ &ctlinnd("go $pausemsg");
+ die "$0: writing $newact failed ($!), aborting\n";
+ }
+}
+
+close(OLDACT) || warn "$0: close $oldact: $!\n";
+close(NEWACT) || warn "$0: close $newact: $!\n";
+
+if (!rename("$oldact", "$oldact.old")) {
+ warn "$0: rename $oldact $oldact.old: $!\n";
+}
+
+if (!rename("$newact", "$oldact")) {
+ die "$0: rename $newact $oldact: $!\n";
+}
+
+&ctlinnd("reload active 'updated from checkgroups'");
+system("diff $diff_flags $oldact.old $oldact");
+&ctlinnd("go $pausemsg");
+
+print "updating $actime\n" if $debug;
+if (open(TIMES, ">> $actime")) {
+ $time = time;
+ for (sort keys %toadd) {
+ print TIMES "$_ $time checkgroups-update\n" || last;
+ }
+ close(TIMES) || warn "$0: close $actime: $!\n";
+} else {
+ warn "$0: $actime not updated: $!\n";
+}
+
+exit 0;
+
+sub
+ctlinnd
+
+{
+ local($command) = @_;
+
+ print "ctlinnd $command\n" if $debug;
+ if (system("$inn::newsbin/ctlinnd -s $command")) {
+ die "$0: \"$command\" failed, aborting\n";
+ }
+}
--- /dev/null
+#! /usr/bin/perl
+# fixscript will replace this line with require innshellvars.pl
+
+# news to mail channel backend
+#
+# INN gives us
+# @token@ addrs
+# for each article that needs to be mailed. We invoke sm on the
+# localhost to get the actual article and stuff
+# it down sendmail's throat.
+#
+# This program expect to find a file that maps listname to listaddrs,
+# @prefix@/etc/news2mail.cf
+# which must contain address mapping pairs such as
+#
+# big-red-ants@ucsd.edu big-red-ants-digest@ucsd.edu
+#
+# where the first token is the name fed to us from INN, and which is
+# also placed in the To: header of the outgoing mail. It's probably
+# the subscriber's list submittal address so that replies go to the
+# right place. The second token is the actual address sendmail ships
+# the article to.
+#
+# In the INN newsfeeds file, you need to have a channel feed:
+# n2m!:!*:Tc,Ac,Wn*:@prefix@/bin/news2mail
+# and a site for each of the various mailing lists you're feeding,
+# such as
+# big-red-ants@ucsd.edu:rec.pets.redants.*:Tm:n2m!
+#
+# Error handling is nearly nonexistent.
+#
+# - Brian Kantor, UCSD Aug 1998
+
+require 5.006;
+
+use FileHandle;
+use Sys::Syslog;
+use strict;
+
+my $cfFile = $inn::pathetc . "/news2mail.cf" ;
+my $sendmail = $inn::mta ;
+my $sm = $inn::pathbin . "/sm" ;
+my %maddr = ();
+
+#
+# the syslog calls are here but don't work on my system
+#
+openlog('news2mail', 'pid', 'mail');
+
+syslog('info', 'begin');
+
+#
+# load the list names and their mail addresses from cf file
+# #comments and blank lines are ignored
+#
+unless (open CF, "< $cfFile") {
+ syslog('notice', 'CF open failed %m');
+ die "bad CF";
+ }
+
+while ( <CF> ) {
+ next if /^#|^\s+$/;
+ my ( $ln, $ma ) = split /\s+/;
+ $maddr{ $ln } = $ma;
+ }
+close CF;
+
+#
+# for each incoming line from the INN channel
+#
+while ( <STDIN> ) {
+ chomp;
+
+ syslog('info', $_);
+
+ my ($token, $lnames) = split /\s+/, $_, 2;
+ my @addrs = split /\s+/, $lnames;
+
+ my @good = grep { defined $maddr{$_} } @addrs;
+ my @bad = grep { !defined $maddr{$_} } @addrs;
+
+ if (! @good) {
+ syslog('notice', "unknown listname $_");
+ next;
+ }
+
+ if (@bad) {
+ syslog('info', 'skipping unknown lists: ', join(' ', @bad));
+ }
+ mailto($token, $lnames, @maddr{@good});
+ }
+
+syslog ("info", "end") ;
+
+exit 0;
+
+sub mailto {
+ my($t, $l, @a) = @_ ;
+
+ my $sendmail = $inn::mta ;
+ $sendmail =~ s!\s*%s!! ;
+ my @command = (split (' ', $sendmail), '-ee', '-fnews', '-odq', @a);
+# @command[0] = '/usr/local/bin/debug';
+
+ syslog('info', join(' ', @command));
+
+ unless (open(SM, '|-', @command)) {
+ syslog('notice', join(' ', '|', @command), 'failed!');
+ die "bad $sendmail";
+ }
+
+ my $smgr = "$sm -q $t |";
+
+ unless (open(SMGR, $smgr)) {
+ syslog('notice', "$smgr failed!");
+ die "bad $smgr";
+ }
+
+ # header
+ while ( <SMGR> ) {
+ chomp;
+
+ # empty line signals end of header
+ if ( /^$/ ) {
+ print SM "To: $l\n\n";
+ last;
+ }
+
+ #
+ # skip unnecessary headers
+ #
+ next if /^NNTP-Posting-Date:/i;
+ next if /^NNTP-Posting-Host:/i;
+ next if /^X-Trace:/i;
+ next if /^Xref:/i;
+ next if /^Path:/i;
+
+ #
+ # convert Newsgroups header into X-Newsgroups
+ #
+ s/^Newsgroups:/X-Newsgroups:/i;
+
+ print SM "$_\n";
+ }
+
+ # body
+ while ( <SMGR> ) {
+ print SM $_;
+ }
+
+ close(SMGR);
+ close(SM);
+ }
--- /dev/null
+/* $Id: ninpaths.c 6362 2003-05-31 18:35:04Z rra $
+**
+** New inpaths reporting program.
+**
+** Idea, data structures and part of code based on inpaths 2.5
+** by Brian Reid, Landon Curt Noll
+**
+** This version written by Olaf Titz, Feb. 1997. Public domain.
+*/
+
+#include "config.h"
+#include "clibrary.h"
+#include <ctype.h>
+#include <time.h>
+
+#define VERSION "3.1.1"
+
+#define MAXFNAME 1024 /* max length of file name */
+#define MAXLINE 1024 /* max length of Path line */
+#define HASH_TBL 65536 /* hash table size (power of two) */
+#define MAXHOST 128 /* max length of host name */
+#define HOSTF "%127s" /* scanf format for host name */
+#define RECLINE 120 /* dump file line length softlimit */
+
+/* structure used to tally the traffic between two hosts */
+struct trec {
+ struct trec *rlink; /* next in chain */
+ struct nrec *linkid; /* pointer to... */
+ long tally; /* count */
+};
+
+/* structure to hold the information about a host */
+struct nrec {
+ struct nrec *link; /* next in chain */
+ struct trec *rlink; /* start of trec chain */
+ char *id; /* host name */
+ long no; /* identificator for dump file */
+ long sentto; /* tally of articles sent from here */
+};
+
+struct nrec *hosthash[HASH_TBL];
+
+time_t starttime; /* Start time */
+double atimes=0.0; /* Sum of articles times wrt. starttime */
+long total=0, /* Total articles processed */
+ sites=0; /* Total sites known */
+
+/* malloc and warn if out of mem */
+void *
+wmalloc(size_t s)
+{
+ void *p=malloc(s);
+ if (!p)
+ fprintf(stderr, "warning: out of memory\n");
+ return p;
+}
+
+/* Hash function due to Glenn Fowler / Landon Curt Noll / Phong Vo */
+int
+hash(const char *str)
+{
+ unsigned long val;
+ unsigned long c;
+
+ for (val = 0; (c=(unsigned long)(*str)); ++str) {
+ val *= 16777619; /* magic */
+ val ^= c; /* more magic */
+ }
+ return (int)(val & (unsigned long)(HASH_TBL-1));
+}
+
+/* Look up a host in the hash table. Add if necessary. */
+struct nrec *
+hhost(const char *n)
+{
+ struct nrec *h;
+ int i=hash(n);
+
+ for (h=hosthash[i]; h; h=h->link)
+ if (!strcmp(n, h->id))
+ return h;
+ /* not there - allocate */
+ h=wmalloc(sizeof(struct nrec));
+ if (!h)
+ return NULL;
+ h->id=strdup(n);
+ if (!h->id) {
+ free(h); return NULL;
+ }
+ h->link=hosthash[i];
+ h->rlink=NULL;
+ h->no=h->sentto=0;
+ hosthash[i]=h;
+ sites++;
+ return h;
+}
+
+/* Look up a tally record between hosts. Add if necessary. */
+struct trec *
+tallyrec(struct nrec *r, struct nrec *h)
+{
+ struct trec *t;
+ for (t=r->rlink; t; t=t->rlink)
+ if (t->linkid==h)
+ return t;
+ t=wmalloc(sizeof(struct trec));
+ if (!t)
+ return NULL;
+ t->rlink=r->rlink;
+ t->linkid=h;
+ t->tally=0;
+ r->rlink=t;
+ return t;
+}
+
+
+/* Dump file format:
+ "!!NINP" <version> <starttime> <endtime> <sites> <total> <avgtime> "\n"
+ followed by <sites> S-records,
+ "!!NLREC\n"
+ [3.0]
+ followed by max. <sites>^2 L-records
+ [3.1]
+ followed by max. <sites> L-records
+ "!!NEND" <nlrecs> "\n"
+ starttime, endtime, avgtime as UNIX date
+ the records are separated by space or \n
+ an S-record is "site count"
+ [3.0]
+ an L-record is "sitea!siteb!count"
+ [3.1]
+ an L-record is ":sitea" { "!siteb,count" }...
+ ",count" omitted if count==1
+ where sitea and siteb are numbers of the S-records starting at 0
+*/
+
+int
+writedump(FILE *f)
+{
+ int i, j;
+ long n;
+ struct nrec *h;
+ struct trec *t;
+
+ if (!total) {
+ return -1;
+ }
+ fprintf(f, "!!NINP " VERSION " %lu %lu %ld %ld %ld\n",
+ (unsigned long) starttime, (unsigned long) time(NULL), sites,
+ total, (long)(atimes/total)+starttime);
+ n=j=0;
+ /* write the S-records (hosts), numbering them in the process */
+ for (i=0; i<HASH_TBL; ++i)
+ for (h=hosthash[i]; h; h=h->link) {
+ h->no=n++;
+ j+=fprintf(f, "%s %ld", h->id, h->sentto);
+ if (j>RECLINE) {
+ j=0;
+ fprintf(f, "\n");
+ } else {
+ fprintf(f, " ");
+ }
+ }
+ if (n!=sites)
+ fprintf(stderr, "internal error: sites=%ld, dumped=%ld\n", sites, n);
+
+ fprintf(f, "\n!!NLREC\n");
+
+ n=j=0;
+ /* write the L-records (links) */
+ for (i=0; i<HASH_TBL; ++i)
+ for (h=hosthash[i]; h; h=h->link)
+ if ((t=h->rlink)) {
+ j+=fprintf(f, ":%ld", h->no);
+ for (; t; t=t->rlink) {
+ j+=fprintf(f, "!%ld", t->linkid->no);
+ if (t->tally>1)
+ j+=fprintf(f, ",%ld", t->tally);
+ n++;
+ }
+ if (j>RECLINE) {
+ j=0;
+ fprintf(f, "\n");
+ }
+ }
+ fprintf(f, "\n!!NLEND %ld\n", n);
+ return 0;
+}
+
+/* Write dump to a named file. Substitute %d in file name with system time. */
+
+void
+writedumpfile(const char *n)
+{
+ char buf[MAXFNAME];
+ FILE *d;
+
+ if (n[0]=='-' && n[1]=='\0') {
+ writedump(stdout);
+ return;
+ }
+ snprintf(buf, sizeof(buf), n, time(0));
+ d=fopen(buf, "w");
+ if (d) {
+ if (writedump(d)<0)
+ unlink(buf);
+ } else {
+ perror("writedumpfile: fopen");
+ }
+}
+
+/* Read a dump file. */
+
+int
+readdump(FILE *f)
+{
+ int a, b;
+ long i, m, l;
+ unsigned long st, et, at;
+ long sit, tot;
+ struct nrec **n;
+ struct trec *t;
+ char c[MAXHOST];
+ char v[16];
+
+ #define formerr(i) {\
+ fprintf(stderr, "dump file format error #%d\n", (i)); return -1; }
+
+ if (fscanf(f, "!!NINP %15s %lu %lu %ld %ld %lu\n",
+ v, &st, &et, &sit, &tot, &at)!=6)
+ formerr(0);
+
+ n=calloc(sit, sizeof(struct nrec *));
+ if (!n) {
+ fprintf(stderr, "error: out of memory\n");
+ return -1;
+ }
+ for (i=0; i<sit; i++) {
+ if (fscanf(f, HOSTF " %ld ", c, &l)!=2) {
+ fprintf(stderr, "read %ld ", i);
+ formerr(1);
+ }
+ n[i]=hhost(c);
+ if (!n[i])
+ return -1;
+ n[i]->sentto+=l;
+ }
+ if ((fscanf(f, HOSTF "\n", c)!=1) ||
+ strcmp(c, "!!NLREC"))
+ formerr(2);
+ m=0;
+ if (!strncmp(v, "3.0", 3)) {
+ /* Read 3.0-format L-records */
+ while (fscanf(f, "%d!%d!%ld ", &a, &b, &l)==3) {
+ t=tallyrec(n[a], n[b]);
+ if (!t)
+ return -1;
+ t->tally+=l;
+ ++m;
+ }
+ } else if (!strncmp(v, "3.1", 3)) {
+ /* Read L-records */
+ while (fscanf(f, " :%d", &a)==1) {
+ while ((i=fscanf(f, "!%d,%ld", &b, &l))>0) {
+ t=tallyrec(n[a], n[b]);
+ if (i<2)
+ l=1;
+ if (!t)
+ return -1;
+ t->tally+=l;
+ ++m;
+ }
+ }
+ } else {
+ fprintf(stderr, "version %s ", v);
+ formerr(9);
+ }
+ if ((fscanf(f, "!!NLEND %ld\n", &i)!=1)
+ || (i!=m))
+ formerr(3);
+#ifdef DEBUG
+ fprintf(stderr, " dumped start %s total=%ld atimes=%ld (%ld)\n",
+ ctime(&st), tot, at, at-st);
+#endif
+ /* Adjust the time average and total count */
+ if ((unsigned long) starttime > st) {
+ atimes+=(double)total*(starttime-st);
+ starttime=st;
+ }
+ atimes+=(double)tot*(at-starttime);
+ total+=tot;
+#ifdef DEBUG
+ fprintf(stderr, " current start %s total=%ld atimes=%.0f (%.0f)\n\n",
+ ctime(&starttime), total, atimes, atimes/total);
+#endif
+ free(n);
+ return 0;
+}
+
+/* Read dump from a file. */
+
+int
+readdumpfile(const char *n)
+{
+ FILE *d;
+ int i;
+
+ if (n[0]=='-' && n[1]=='\0')
+ return readdump(stdin);
+
+ d=fopen(n, "r");
+ if (d) {
+ /* fprintf(stderr, "Reading dump file %s\n", n); */
+ i=readdump(d);
+ fclose(d);
+ return i;
+ } else {
+ perror("readdumpfile: fopen");
+ return -1;
+ }
+}
+
+
+/* Process a Path line. */
+
+int
+pathline(char *c)
+{
+ char *c2;
+ struct nrec *h, *r;
+ struct trec *t;
+
+ r=NULL;
+ while (*c) {
+ for (c2=c; *c2 && *c2!='!'; c2++);
+ if (c2-c>MAXHOST-1)
+ /* looks broken, dont bother with rest */
+ return 0;
+ while (*c2=='!')
+ *c2++='\0'; /* skip "!!" too */
+ h=hhost(c);
+ if (!h)
+ return -1;
+ ++h->sentto;
+ if (r && r!=h) {
+ t=tallyrec(r, h);
+ if (!t)
+ return -1;
+ ++t->tally;
+ }
+ c=c2;
+ r=h;
+ }
+ return 0;
+}
+
+/* Take Path lines from file (stdin used here). */
+
+void
+procpaths(FILE *f)
+{
+ char buf[MAXLINE];
+ char *c, *ce;
+ int v=1; /* current line is valid */
+
+ while (fgets(buf, sizeof(buf), f)) {
+ c=buf;
+ if (!strncmp(c, "Path: ", 6))
+ c+=6;
+ /* find end of line. Some broken newsreaders preload Path with
+ a name containing spaces. Chop off those entries. */
+ for (ce=c; *ce && !CTYPE(isspace, *ce); ++ce);
+ if (!*ce) {
+ /* bogus line */
+ v=0;
+ } else if (v) {
+ /* valid line */
+ for (; ce>c && *ce!='!'; --ce); /* ignore last element */
+ *ce='\0';
+ if (pathline(c)<0) /* process it */
+ /* If an out of memory condition occurs while reading
+ Path lines, stop reading and write the dump so far.
+ INN will restart a fresh ninpaths. */
+ return;
+ /* update average age and grand total */
+ atimes+=(time(0)-starttime);
+ ++total;
+ } else {
+ /* next line is valid */
+ v=1;
+ }
+ }
+}
+
+/* Output a report suitable for mailing. From inpaths 2.5 */
+
+void
+report(const char *hostname, int verbose)
+{
+ double avgAge;
+ int i, columns, needHost;
+ long nhosts=0, nlinks=0;
+ struct nrec *list, *relay;
+ struct trec *rlist;
+ char hostString[MAXHOST];
+ time_t t0=time(0);
+
+ if (!total) {
+ fprintf(stderr, "report: no traffic\n");
+ return;
+ }
+ /* mark own site to not report it */
+ list=hhost(hostname);
+ if (list)
+ list->id[0]='\0';
+
+ avgAge=((double)t0 - (atimes/total + (double)starttime)) /86400.0;
+ printf("ZCZC begin inhosts %s %s %d %ld %3.1f\n",
+ VERSION,hostname,verbose,total,avgAge);
+ for (i=0; i<HASH_TBL-1; i++) {
+ list = hosthash[i];
+ while (list != NULL) {
+ if (list->id[0] != 0 && list->rlink != NULL) {
+ if (verbose > 0 || (100*list->sentto > total))
+ printf("%ld\t%s\n",list->sentto, list->id);
+ }
+ list = list->link;
+ }
+ }
+ printf("ZCZC end inhosts %s\n",hostname);
+
+ printf("ZCZC begin inpaths %s %s %d %ld %3.1f\n",
+ VERSION,hostname,verbose,total,avgAge);
+ for (i=0; i<HASH_TBL-1; i++) {
+ list = hosthash[i];
+ while (list != NULL) {
+ if (verbose > 1 || (100*list->sentto > total)) {
+ if (list->id[0] != 0 && list->rlink != NULL) {
+ columns = 3+strlen(list->id);
+ snprintf(hostString,sizeof(hostString),"%s H ",list->id);
+ needHost = 1;
+ rlist = list->rlink;
+ while (rlist != NULL) {
+ if (
+ (100*rlist->tally > total)
+ || ((verbose > 1)&&(5000*rlist->tally>total))
+ ) {
+ if (needHost) printf("%s",hostString);
+ needHost = 0;
+ relay = rlist->linkid;
+ if (relay->id[0] != 0) {
+ if (columns > 70) {
+ printf("\n%s",hostString);
+ columns = 3+strlen(list->id);
+ }
+ printf("%ld Z %s U ", rlist->tally, relay->id);
+ columns += 9+strlen(relay->id);
+ }
+ }
+ rlist = rlist->rlink;
+ ++nlinks;
+ }
+ if (!needHost) printf("\n");
+ }
+ }
+ list = list->link;
+ ++nhosts;
+ }
+ }
+ printf("ZCZC end inpaths %s\n",hostname);
+#ifdef DEBUG
+ fprintf(stderr, "Processed %ld hosts, %ld links.\n", nhosts, nlinks);
+#endif
+}
+
+extern char *optarg;
+
+int
+main(int argc, char *argv[])
+{
+ int i;
+ int pf=0, vf=2;
+ char *df=NULL, *rf=NULL;
+
+ for (i=0; i<HASH_TBL; i++)
+ hosthash[i]=NULL;
+ starttime=time(0);
+
+ while ((i=getopt(argc, argv, "pd:u:r:v:"))!=EOF)
+ switch (i) {
+ case 'p':
+ /* read Path lines from stdin */
+ pf=1; break;
+ case 'd':
+ /* make a dump to the named file */
+ df=optarg; break;
+ case 'u':
+ /* read dump from the named file */
+ if (readdumpfile(optarg)<0)
+ exit(1);
+ break;
+ case 'r':
+ /* make a report for the named site */
+ rf=optarg; break;
+ case 'v':
+ /* control report verbosity */
+ vf=atoi(optarg); break;
+ default:
+ fprintf(stderr, "unknown option %c\n", i);
+ }
+
+ if (pf)
+ procpaths(stdin);
+ if (df)
+ writedumpfile(df);
+ if (rf)
+ report(rf, vf);
+ return 0;
+}
--- /dev/null
+/* $Id: nntpget.c 6135 2003-01-19 01:15:40Z rra $
+**
+** Connect to a remote site, and get news from it to offer to our local
+** server. Read list on stdin, or get it via NEWNEWS command. Writes
+** list of articles still needed to stdout.
+*/
+
+#include "config.h"
+#include "clibrary.h"
+#include "portable/socket.h"
+#include "portable/time.h"
+#include <errno.h>
+#include <sys/stat.h>
+#include <sys/uio.h>
+
+/* Needed on AIX 4.1 to get fd_set and friends. */
+#ifdef HAVE_SYS_SELECT_H
+# include <sys/select.h>
+#endif
+
+#include "inn/history.h"
+#include "inn/innconf.h"
+#include "inn/messages.h"
+#include "libinn.h"
+#include "nntp.h"
+#include "paths.h"
+
+/*
+** All information about a site we are connected to.
+*/
+typedef struct _SITE {
+ char *Name;
+ int Rfd;
+ int Wfd;
+ char Buffer[BUFSIZ];
+ char *bp;
+ int Count;
+} SITE;
+
+
+/*
+** Global variables.
+*/
+static struct iovec SITEvec[2];
+static char SITEv1[] = "\r\n";
+static char READER[] = "mode reader";
+static unsigned long STATgot;
+static unsigned long STAToffered;
+static unsigned long STATsent;
+static unsigned long STATrejected;
+static struct history *History;
+
+\f
+
+/*
+** Read a line of input, with timeout.
+*/
+static bool
+SITEread(SITE *sp, char *start)
+{
+ char *p;
+ char *end;
+ struct timeval t;
+ fd_set rmask;
+ int i;
+ char c;
+
+ for (p = start, end = &start[NNTP_STRLEN - 1]; ; ) {
+ if (sp->Count == 0) {
+ /* Fill the buffer. */
+ Again:
+ FD_ZERO(&rmask);
+ FD_SET(sp->Rfd, &rmask);
+ t.tv_sec = DEFAULT_TIMEOUT;
+ t.tv_usec = 0;
+ i = select(sp->Rfd + 1, &rmask, NULL, NULL, &t);
+ if (i < 0) {
+ if (errno == EINTR)
+ goto Again;
+ return false;
+ }
+ if (i == 0
+ || !FD_ISSET(sp->Rfd, &rmask)
+ || (sp->Count = read(sp->Rfd, sp->Buffer, sizeof sp->Buffer)) < 0)
+ return false;
+ if (sp->Count == 0)
+ return false;
+ sp->bp = sp->Buffer;
+ }
+
+ /* Process next character. */
+ sp->Count--;
+ c = *sp->bp++;
+ if (c == '\n')
+ break;
+ if (p < end)
+ *p++ = c;
+ }
+
+ /* If last two characters are \r\n, kill the \r as well as the \n. */
+ if (p > start && p < end && p[-1] == '\r')
+ p--;
+ *p = '\0';
+ return true;
+}
+
+
+/*
+** Send a line to the server, adding \r\n. Don't need to do dot-escape
+** since it's only for sending DATA to local site, and the data we got from
+** the remote site already is escaped.
+*/
+static bool
+SITEwrite(SITE *sp, const char *p, int i)
+{
+ SITEvec[0].iov_base = (char *) p;
+ SITEvec[0].iov_len = i;
+ return xwritev(sp->Wfd, SITEvec, 2) >= 0;
+}
+
+
+static SITE *
+SITEconnect(char *host)
+{
+ FILE *From;
+ FILE *To;
+ SITE *sp;
+ int i;
+
+ /* Connect and identify ourselves. */
+ if (host)
+ i = NNTPconnect(host, NNTP_PORT, &From, &To, (char *)NULL);
+ else {
+ host = innconf->server;
+ if (host == NULL)
+ die("no server specified and server not set in inn.conf");
+ i = NNTPlocalopen(&From, &To, (char *)NULL);
+ }
+ if (i < 0)
+ sysdie("cannot connect to %s", host);
+
+ if (NNTPsendpassword(host, From, To) < 0)
+ sysdie("cannot authenticate to %s", host);
+
+ /* Build the structure. */
+ sp = xmalloc(sizeof(SITE));
+ sp->Name = host;
+ sp->Rfd = fileno(From);
+ sp->Wfd = fileno(To);
+ sp->bp = sp->Buffer;
+ sp->Count = 0;
+ return sp;
+}
+
+
+/*
+** Send "quit" to a site, and get its reply.
+*/
+static void
+SITEquit(SITE *sp)
+{
+ char buff[NNTP_STRLEN];
+
+ SITEwrite(sp, "quit", 4);
+ SITEread(sp, buff);
+}
+
+
+static bool
+HIShaveit(char *mesgid)
+{
+ return HIScheck(History, mesgid);
+}
+
+
+static void
+Usage(const char *p)
+{
+ warn("%s", p);
+ fprintf(stderr, "Usage: nntpget"
+ " [ -d dist -n grps [-f file | -t time -u file]] host\n");
+ exit(1);
+}
+
+
+int
+main(int ac, char *av[])
+{
+ char buff[NNTP_STRLEN];
+ char mesgid[NNTP_STRLEN];
+ char tbuff[SMBUF];
+ char *msgidfile = NULL;
+ int msgidfd;
+ const char *Groups;
+ char *distributions;
+ char *Since;
+ char *path;
+ int i;
+ struct tm *gt;
+ struct stat Sb;
+ SITE *Remote;
+ SITE *Local = NULL;
+ FILE *F;
+ bool Offer;
+ bool Error;
+ bool Verbose = false;
+ char *Update;
+ char *p;
+
+ /* First thing, set up our identity. */
+ message_program_name = "nntpget";
+
+ /* Set defaults. */
+ distributions = NULL;
+ Groups = NULL;
+ Since = NULL;
+ Offer = false;
+ Update = NULL;
+ if (!innconf_read(NULL))
+ exit(1);
+
+ umask(NEWSUMASK);
+
+ /* Parse JCL. */
+ while ((i = getopt(ac, av, "d:f:n:t:ovu:")) != EOF)
+ switch (i) {
+ default:
+ Usage("bad flag");
+ /* NOTREACHED */
+ case 'd':
+ distributions = optarg;
+ break;
+ case 'u':
+ Update = optarg;
+ /* FALLTHROUGH */
+ case 'f':
+ if (Since)
+ Usage("only one of -f, -t, or -u may be given");
+ if (stat(optarg, &Sb) < 0)
+ sysdie("cannot stat %s", optarg);
+ gt = gmtime(&Sb.st_mtime);
+ /* Y2K: NNTP Spec currently allows only two digit years. */
+ snprintf(tbuff, sizeof(tbuff), "%02d%02d%02d %02d%02d%02d GMT",
+ gt->tm_year % 100, gt->tm_mon + 1, gt->tm_mday,
+ gt->tm_hour, gt->tm_min, gt->tm_sec);
+ Since = tbuff;
+ break;
+ case 'n':
+ Groups = optarg;
+ break;
+ case 'o':
+ /* Open the history file. */
+ path = concatpath(innconf->pathdb, _PATH_HISTORY);
+ History = HISopen(path, innconf->hismethod, HIS_RDONLY);
+ if (!History)
+ sysdie("cannot open history");
+ free(path);
+ Offer = true;
+ break;
+ case 't':
+ if (Since)
+ Usage("only one of -t or -f may be given");
+ Since = optarg;
+ break;
+ case 'v':
+ Verbose = true;
+ break;
+ }
+ ac -= optind;
+ av += optind;
+ if (ac != 1)
+ Usage("no host given");
+
+ /* Set up the scatter/gather vectors used by SITEwrite. */
+ SITEvec[1].iov_base = SITEv1;
+ SITEvec[1].iov_len = strlen(SITEv1);
+
+ /* Connect to the remote server. */
+ if ((Remote = SITEconnect(av[0])) == NULL)
+ sysdie("cannot connect to %s", av[0]);
+ if (!SITEwrite(Remote, READER, (int)strlen(READER))
+ || !SITEread(Remote, buff))
+ sysdie("cannot start reading");
+
+ if (Since == NULL) {
+ F = stdin;
+ if (distributions || Groups)
+ Usage("no -d or -n flags allowed when reading stdin");
+ }
+ else {
+ /* Ask the server for a list of what's new. */
+ if (Groups == NULL)
+ Groups = "*";
+ if (distributions)
+ snprintf(buff, sizeof(buff), "NEWNEWS %s %s <%s>",
+ Groups, Since, distributions);
+ else
+ snprintf(buff, sizeof(buff), "NEWNEWS %s %s", Groups, Since);
+ if (!SITEwrite(Remote, buff, (int)strlen(buff))
+ || !SITEread(Remote, buff))
+ sysdie("cannot start list");
+ if (buff[0] != NNTP_CLASS_OK) {
+ SITEquit(Remote);
+ die("protocol error from %s, got %s", Remote->Name, buff);
+ }
+
+ /* Create a temporary file. */
+ msgidfile = concatpath(innconf->pathtmp, "nntpgetXXXXXX");
+ msgidfd = mkstemp(msgidfile);
+ if (msgidfd < 0)
+ sysdie("cannot create a temporary file");
+ F = fopen(msgidfile, "w+");
+ if (F == NULL)
+ sysdie("cannot open %s", msgidfile);
+
+ /* Read and store the Message-ID list. */
+ for ( ; ; ) {
+ if (!SITEread(Remote, buff)) {
+ syswarn("cannot read from %s", Remote->Name);
+ fclose(F);
+ SITEquit(Remote);
+ exit(1);
+ }
+ if (strcmp(buff, ".") == 0)
+ break;
+ if (Offer && HIShaveit(buff))
+ continue;
+ if (fprintf(F, "%s\n", buff) == EOF || ferror(F)) {
+ syswarn("cannot write %s", msgidfile);
+ fclose(F);
+ SITEquit(Remote);
+ exit(1);
+ }
+ }
+ if (fflush(F) == EOF) {
+ syswarn("cannot flush %s", msgidfile);
+ fclose(F);
+ SITEquit(Remote);
+ exit(1);
+ }
+ fseeko(F, 0, SEEK_SET);
+ }
+
+ if (Offer) {
+ /* Connect to the local server. */
+ if ((Local = SITEconnect((char *)NULL)) == NULL) {
+ syswarn("cannot connect to local server");
+ fclose(F);
+ exit(1);
+ }
+ }
+
+ /* Loop through the list of Message-ID's. */
+ while (fgets(mesgid, sizeof mesgid, F) != NULL) {
+ STATgot++;
+ if ((p = strchr(mesgid, '\n')) != NULL)
+ *p = '\0';
+
+ if (Offer) {
+ /* See if the local server wants it. */
+ STAToffered++;
+ snprintf(buff, sizeof(buff), "ihave %s", mesgid);
+ if (!SITEwrite(Local, buff, (int)strlen(buff))
+ || !SITEread(Local, buff)) {
+ syswarn("cannot offer %s", mesgid);
+ break;
+ }
+ if (atoi(buff) != NNTP_SENDIT_VAL)
+ continue;
+ }
+
+ /* Try to get the article. */
+ snprintf(buff, sizeof(buff), "article %s", mesgid);
+ if (!SITEwrite(Remote, buff, (int)strlen(buff))
+ || !SITEread(Remote, buff)) {
+ syswarn("cannot get %s", mesgid);
+ printf("%s\n", mesgid);
+ break;
+ }
+ if (atoi(buff) != NNTP_ARTICLE_FOLLOWS_VAL) {
+ if (Offer) {
+ SITEwrite(Local, ".", 1);
+ if (!SITEread(Local, buff)) {
+ syswarn("no reply after %s", mesgid);
+ break;
+ }
+ }
+ continue;
+ }
+
+ if (Verbose)
+ notice("%s...", mesgid);
+
+ /* Read each line in the article and write it. */
+ for (Error = false; ; ) {
+ if (!SITEread(Remote, buff)) {
+ syswarn("cannot read %s from %s", mesgid, Remote->Name);
+ Error = true;
+ break;
+ }
+ if (Offer) {
+ if (!SITEwrite(Local, buff, (int)strlen(buff))) {
+ syswarn("cannot send %s", mesgid);
+ Error = true;
+ break;
+ }
+ }
+ else
+ printf("%s\n", buff);
+ if (strcmp(buff, ".") == 0)
+ break;
+ }
+ if (Error) {
+ printf("%s\n", mesgid);
+ break;
+ }
+ STATsent++;
+
+ /* How did the local server respond? */
+ if (Offer) {
+ if (!SITEread(Local, buff)) {
+ syswarn("no reply after %s", mesgid);
+ printf("%s\n", mesgid);
+ break;
+ }
+ i = atoi(buff);
+ if (i == NNTP_TOOKIT_VAL)
+ continue;
+ if (i == NNTP_RESENDIT_VAL) {
+ printf("%s\n", mesgid);
+ break;
+ }
+ syswarn("%s to %s", buff, mesgid);
+ STATrejected++;
+ }
+ }
+
+ /* Write rest of the list, close the input. */
+ if (!feof(F))
+ while (fgets(mesgid, sizeof mesgid, F) != NULL) {
+ if ((p = strchr(mesgid, '\n')) != NULL)
+ *p = '\0';
+ printf("%s\n", mesgid);
+ STATgot++;
+ }
+ fclose(F);
+
+ /* Remove our temp file. */
+ if (msgidfile && unlink(msgidfile) < 0)
+ syswarn("cannot remove %s", msgidfile);
+
+ /* All done. */
+ SITEquit(Remote);
+ if (Offer)
+ SITEquit(Local);
+
+ /* Update timestamp file? */
+ if (Update) {
+ if ((F = fopen(Update, "w")) == NULL)
+ sysdie("cannot update %s", Update);
+ fprintf(F, "got %ld offered %ld sent %ld rejected %ld\n",
+ STATgot, STAToffered, STATsent, STATrejected);
+ if (ferror(F) || fclose(F) == EOF)
+ sysdie("cannot update %s", Update);
+ }
+
+ exit(0);
+ /* NOTREACHED */
+}
--- /dev/null
+#! /bin/sh
+# fixscript will replace this line with code to load innshellvars
+
+## $Revision: 5047 $
+## Send news via NNTP by running several innxmit processes in the background.
+## Usage:
+## nntpsend [-n][-p][-r][-s size][-S][-t timeout][-T limit][host fqdn]...
+## -a Always have innxmit rewrite the batchfile
+## -d debug mode, run innxmits with debug as well
+## -D same as -d except innxmits are not debugged
+## -p Run innxmit with -p to prune batch files
+## -r innxmit, don't requeue on unexpected error code
+## -s size limit the =n file to size bytes
+## -c disable message-ID checking in streaming mode
+## -t timeout innxmit timeout to make connection (def: 180)
+## -T limit innxmit connection transmit time limit (def: forever)
+## -P portnum port number to use
+## -l innxmit, log rejected articles
+## -N innxmit, disable streaming mode
+## -n do not lock for nntpsend, do not sleep between sets
+## -w delay wait delay seconds just before innxmit
+## host fqdn send to host and qualified domain (def: nntpsend.ctl)
+## If no "host fqdn" pairs appear on the command line, then ${CTLFILE}
+## file is read.
+
+PROGNAME=`basename $0`
+LOCK=${LOCKS}/LOCK.${PROGNAME}
+CTLFILE=${PATHETC}/${PROGNAME}.ctl
+LOG=${MOST_LOGS}/${PROGNAME}.log
+
+## Set defaults.
+A_FLAG=
+D_FLAG=
+NO_LOG_FLAG=
+P_FLAG=
+R_FLAG=
+S_FLAG=
+C_FLAG=
+L_FLAG=
+S2_FLAG=
+TRUNC_SIZE=
+T_FLAG=
+TIMELIMIT=
+PP_FLAG=
+NOLOCK=
+W_SECONDS=
+
+## Parse JCL.
+MORETODO=true
+while ${MORETODO} ; do
+ case X"$1" in
+ X-a)
+ A_FLAG="-a"
+ ;;
+ X-d)
+ D_FLAG="-d"
+ NO_LOG_FLAG="true"
+ ;;
+ X-D)
+ NO_LOG_FLAG="true"
+ ;;
+ X-l)
+ L_FLAG="-l"
+ ;;
+ X-p)
+ P_FLAG="-p"
+ ;;
+ X-r)
+ R_FLAG="-r"
+ ;;
+ X-S)
+ S_FLAG="-S"
+ ;;
+ X-N)
+ S2_FLAG="-s"
+ ;;
+ X-c)
+ C_FLAG="-c"
+ ;;
+ X-s)
+ if [ -z "$2" ] ; then
+ echo "${PROGNAME}: option requires an argument -- s" 1>&2
+ exit 1
+ fi
+ TRUNC_SIZE="$2"
+ shift
+ ;;
+ X-s*)
+ TRUNC_SIZE="`echo $1 | ${SED} -e 's/-s//'`"
+ ;;
+ X-t)
+ if [ -z "$2" ] ; then
+ echo "${PROGNAME}: option requires an argument -- t" 1>&2
+ exit 1
+ fi
+ T_FLAG="-t$2"
+ shift
+ ;;
+ X-t*)
+ T_FLAG="$1"
+ ;;
+ X-P)
+ if [ -z "$2" ] ; then
+ echo "${PROGNAME}: option requires an argument -- P" 1>&2
+ exit 1
+ fi
+ PP_FLAG="-P$2"
+ shift
+ ;;
+ X-P*)
+ PP_FLAG="$1"
+ ;;
+ X-T)
+ if [ -z "$2" ] ; then
+ echo "${PROGNAME}: option requires an argument -- T" 1>&2
+ exit 1
+ fi
+ TIMELIMIT="-T$2"
+ shift
+ ;;
+ X-T*)
+ TIMELIMIT="$1"
+ ;;
+ X-n)
+ NOLOCK=true
+ ;;
+ X-w)
+ if [ -z "$2" ] ; then
+ echo "${PROGNAME}: option requires an argument -- w" 1>&2
+ exit 1
+ fi
+ W_SECONDS="$2"
+ shift
+ ;;
+ X--)
+ shift
+ MORETODO=false
+ ;;
+ X-*)
+ echo "${PROGNAME}: illegal option -- $1" 1>&2
+ exit 1
+ ;;
+ *)
+ MORETODO=false
+ ;;
+ esac
+ ${MORETODO} && shift
+done
+
+## grab the lock if not -n
+NNTPLOCK=${LOCKS}/LOCK.nntpsend
+if [ -z "${NOLOCK}" ]; then
+ shlock -p $$ -f ${NNTPLOCK} || {
+ # nothing to do
+ exit 0
+ }
+fi
+
+## Parse arguments; host/fqdn pairs.
+INPUT=${TMPDIR}/nntpsend$$
+cp /dev/null ${INPUT}
+while [ $# -gt 0 ]; do
+ if [ $# -lt 2 ]; then
+ echo "${PROGNAME}: Bad host/fqdn pair" 1>&2
+ rm -f ${NNTPLOCK}
+ exit 1
+ fi
+ echo "$1 $2" >>${INPUT}
+ shift
+ shift
+done
+
+## If nothing specified on the command line, read the control file.
+if [ ! -s ${INPUT} ] ; then
+ if [ ! -r ${CTLFILE} ]; then
+ echo "${PROGNAME}: cannot read ${CTLFILE}"
+ rm -f ${NNTPLOCK}
+ exit 1
+ fi
+ ${SED} -e 's/#.*//' -e '/^$/d' -e 's/::\([^:]*\)$/:max:\1/' \
+ -e 's/:/ /g' <${CTLFILE} >${INPUT}
+fi
+
+## Go to where the action is.
+if [ ! -d ${BATCH} ]; then
+ echo "${PROGNAME}: directory ${BATCH} not found" 1>&2
+ rm -f ${NNTPLOCK}
+ exit 1
+fi
+cd ${BATCH}
+
+## Set up log file.
+umask 002
+if [ -z "${NO_LOG_FLAG}" ]; then
+ test ! -f ${LOG} && touch ${LOG}
+ chmod 0660 ${LOG}
+ exec >>${LOG} 2>&1
+fi
+PARENTPID=$$
+echo "${PROGNAME}: [${PARENTPID}] start"
+
+## Set up environment.
+export BATCH PROGNAME PARENTPID INNFLAGS
+
+## Loop over all sites.
+cat ${INPUT} | while read SITE HOST SIZE_ARG FLAGS; do
+ ## Parse the input parameters.
+ if [ -z "${SITE}" -o -z "${HOST}" ] ; then
+ echo "Ignoring bad line: ${SITE} ${HOST} ${SIZE_ARG} ${FLAGS}" 1>&2
+ continue
+ fi
+
+ ## give up early if we cannot even lock it
+ ##
+ ## NOTE: This lock is not nntpsend's lock but rather the
+ ## lock that the parent shell of innxmit will use.
+ ## Later on the child will take the lock from us.
+ ##
+ LOCK="${LOCKS}/LOCK.${SITE}"
+ shlock -p $$ -f "${LOCK}" || continue
+
+ ## Compute the specific parameters for this site.
+ test "${SIZE_ARG}" = "max" && SIZE_ARG=
+ if [ -n "${TRUNC_SIZE}" ]; then
+ SIZE_ARG="${TRUNC_SIZE}"
+ fi
+ ## Parse the SIZE_ARG for either MaxSize-TruncSize or TruncSize
+ case "${SIZE_ARG}" in
+ *-*) MAXSIZE="`echo ${SIZE_ARG} | ${SED} -e 's/-.*//'`";
+ SIZE="`echo ${SIZE_ARG} | ${SED} -e 's/^.*-//'`" ;;
+ *) MAXSIZE="${SIZE_ARG}";
+ SIZE="${SIZE_ARG}" ;;
+ esac
+ D_PARAM=
+ R_PARAM=
+ S_PARAM=
+ S2_PARAM=
+ C_PARAM=
+ PP_PARAM=
+ L_PARAM=
+ TIMEOUT_PARAM=
+ TIMELIMIT_PARAM=
+ if [ -z "${FLAGS}" ]; then
+ MORETODO=false
+ else
+ MORETODO=true
+ set -- ${FLAGS}
+ fi
+ while ${MORETODO} ; do
+ case "X$1" in
+ X-a)
+ ;;
+ X-d)
+ D_PARAM="-d"
+ ;;
+ X-c)
+ C_PARAM="-c"
+ ;;
+ X-p)
+ P_PARAM="-p"
+ ;;
+ X-r)
+ R_PARAM="-r"
+ ;;
+ X-S)
+ S_PARAM="-S"
+ ;;
+ X-s)
+ S2_PARAM="-s"
+ ;;
+ X-l)
+ L_PARAM="-l"
+ ;;
+ X-t)
+ if [ -z "$2" ] ; then
+ echo "${PROGNAME}: option requires an argument -- t" 1>&2
+ rm -f "${NNTPLOCK}" "${LOCK}"
+ exit 1
+ fi
+ TIMEOUT_PARAM="-t$2"
+ shift
+ ;;
+ X-t*)
+ TIMEOUT_PARAM="$1"
+ ;;
+ X-P)
+ if [ -z "$2" ] ; then
+ echo "${PROGNAME}: option requires an argument -- P" 1>&2
+ rm -f "${NNTPLOCK}" "${LOCK}"
+ exit 1
+ fi
+ PP_PARAM="-P$2"
+ shift
+ ;;
+ X-P*)
+ PP_PARAM="$1"
+ ;;
+ X-T)
+ if [ -z "$2" ] ; then
+ echo "${PROGNAME}: option requires an argument -- T" 1>&2
+ rm -f "${NNTPLOCK}" "${LOCK}"
+ exit 1
+ fi
+ TIMELIMIT_PARAM="-T$2"
+ shift
+ ;;
+ X-T*)
+ TIMELIMIT_PARAM="$1"
+ ;;
+ X-w)
+ if [ -z "$2" ] ; then
+ echo "${PROGNAME}: option requires an argument -- w" 1>&2
+ rm -f "${NNTPLOCK}" "${LOCK}"
+ exit 1
+ fi
+ W_SECONDS="$2"
+ shift
+ ;;
+ *)
+ MORETODO=false
+ ;;
+ esac
+ ${MORETODO} && shift
+ done
+ if [ -z "${SIZE}" -o -n "${A_FLAG}" ]; then
+ # rewrite batch file if we do not have a size limit
+ INNFLAGS="-a"
+ else
+ # we have a size limit, let shrinkfile rewrite the file
+ INNFLAGS=
+ fi
+ if [ -n "${D_FLAG}" ]; then
+ INNFLAGS="${INNFLAGS} ${D_FLAG}"
+ else
+ test -n "${D_PARAM}" && INNFLAGS="${INNFLAGS} ${D_PARAM}"
+ fi
+ if [ -n "${C_FLAG}" ]; then
+ INNFLAGS="${INNFLAGS} ${C_FLAG}"
+ else
+ test -n "${C_PARAM}" && INNFLAGS="${INNFLAGS} ${C_PARAM}"
+ fi
+ if [ -n "${P_FLAG}" ]; then
+ INNFLAGS="${INNFLAGS} ${P_FLAG}"
+ else
+ test -n "${P_PARAM}" && INNFLAGS="${INNFLAGS} ${P_PARAM}"
+ fi
+ if [ -n "${L_FLAG}" ]; then
+ INNFLAGS="${INNFLAGS} ${L_FLAG}"
+ else
+ test -n "${L_PARAM}" && INNFLAGS="${INNFLAGS} ${L_PARAM}"
+ fi
+ if [ -n "${R_FLAG}" ]; then
+ INNFLAGS="${INNFLAGS} ${R_FLAG}"
+ else
+ test -n "${R_PARAM}" && INNFLAGS="${INNFLAGS} ${R_PARAM}"
+ fi
+ if [ -n "${S_FLAG}" ]; then
+ INNFLAGS="${INNFLAGS} ${S_FLAG}"
+ else
+ test -n "${S_PARAM}" && INNFLAGS="${INNFLAGS} ${S_PARAM}"
+ fi
+ if [ -n "${S2_FLAG}" ]; then
+ INNFLAGS="${INNFLAGS} ${S2_FLAG}"
+ else
+ test -n "${S2_PARAM}" && INNFLAGS="${INNFLAGS} ${S2_PARAM}"
+ fi
+ if [ -n "${T_FLAG}" ]; then
+ INNFLAGS="${INNFLAGS} ${T_FLAG}"
+ else
+ test -n "${TIMEOUT_PARAM}" && INNFLAGS="${INNFLAGS} ${TIMEOUT_PARAM}"
+ fi
+ if [ -n "${PP_FLAG}" ]; then
+ INNFLAGS="${INNFLAGS} ${PP_FLAG}"
+ else
+ test -n "${PP_PARAM}" && INNFLAGS="${INNFLAGS} ${PP_PARAM}"
+ fi
+ if [ -n "${TIMELIMIT}" ]; then
+ INNFLAGS="${INNFLAGS} ${TIMELIMIT}"
+ else
+ test -n "${TIMELIMIT_PARAM}" \
+ && INNFLAGS="${INNFLAGS} ${TIMELIMIT_PARAM}"
+ fi
+
+ ## Flush the buffers for the site now, rather than in the child.
+ ## This helps pace the number of ctlinnd commands because the
+ ## nntpsend process does not proceed until the site flush has
+ ## been completed.
+ ##
+ # carry old unfinished work over to this task
+ BATCHFILE="${SITE}=n"
+ if [ -f "${SITE}.work" ] ; then
+ cat ${SITE}.work >>"${BATCHFILE}"
+ rm -f "${SITE}.work"
+ fi
+ # form BATCHFILE to hold the work for this site
+ if [ -f "${SITE}" ]; then
+ mv "${SITE}" "${SITE}.work"
+ if ctlinnd -s -t30 flush ${SITE} ; then
+ cat ${SITE}.work >>"${BATCHFILE}"
+ rm -f ${SITE}.work
+ else
+ # flush failed, continue if we have any batchfile to work on
+ echo "${PROGNAME}: bad flush for ${HOST} via ${SITE}"
+ if [ -f "${BATCHFILE}" ]; then
+ echo "${PROGNAME}: trying ${HOST} via ${SITE} anyway"
+ else
+ echo "${PROGNAME}: skipping ${HOST} via ${SITE}"
+ rm -f ${LOCK}
+ continue
+ fi
+ fi
+ else
+ # nothing to work on, so flush and move on
+ ctlinnd -s -t30 flush ${SITE}
+ echo "${PROGNAME}: file ${BATCH}/${SITE} for ${HOST} not found"
+ if [ -f "${BATCHFILE}" ]; then
+ echo "${PROGNAME}: trying ${HOST} via ${SITE} anyway"
+ else
+ echo "${PROGNAME}: skipping ${HOST} via ${SITE}"
+ rm -f ${LOCK}
+ continue
+ fi
+ fi
+
+ ## Start sending this site in the background.
+ export MAXSIZE SITE HOST PROGNAME PARENTPID SIZE TMPDIR LOCK BATCHFILE W_SECONDS
+ sh -c '
+ # grab the lock from the parent
+ #
+ # This is safe because only the parent will have locked
+ # the site. We break the lock and reclaim it.
+ rm -f ${LOCK}
+ trap "rm -f ${LOCK} ; exit 1" 1 2 3 15
+ shlock -p $$ -f ${LOCK} || {
+ WHY="`cat ${LOCK}`"
+ echo "${PROGNAME}: [${PARENTPID}:$$] ${SITE} locked ${WHY} `date`"
+ exit
+ }
+ # process the site BATCHFILE
+ if [ -f "${BATCHFILE}" ]; then
+ test -n "${SIZE}" && shrinkfile -m${MAXSIZE} -s${SIZE} -v ${BATCHFILE}
+ if [ -s ${BATCHFILE} ] ; then
+ if [ -n "${W_SECONDS}" ] ; then
+ echo "${PROGNAME}: [${PARENTPID}:$$] sleeping ${W_SECONDS} seconds before ${SITE}"
+ sleep "${W_SECONDS}"
+ fi
+ echo "${PROGNAME}: [${PARENTPID}:$$] begin ${SITE} `date`"
+ echo "${PROGNAME}: [${PARENTPID}:$$] innxmit ${INNFLAGS} ${HOST} ..."
+ eval innxmit ${INNFLAGS} ${HOST} ${BATCH}/${BATCHFILE}
+ echo "${PROGNAME}: [${PARENTPID}:$$] end ${SITE} `date`"
+ else
+ rm -f ${BATCHFILE}
+ fi
+ else
+ echo "${PROGNAME}: file ${BATCH}/${BATCHFILE} for ${HOST} not found"
+ fi
+ rm -f ${LOCK}
+ ' &
+done
+
+## release the nntpsend lock and clean up before we wait on child processes
+if [ -z "${NOLOCK}" ]; then
+ rm -f ${NNTPLOCK}
+fi
+rm -f ${INPUT}
+
+## wait for child processes to finish
+wait
+
+## all done
+echo "${PROGNAME}: [${PARENTPID}] stop"
+exit 0
--- /dev/null
+/* $Id: overchan.c 6135 2003-01-19 01:15:40Z rra $
+**
+** Parse input to add to news overview database.
+*/
+
+#include "config.h"
+#include "clibrary.h"
+#include "portable/time.h"
+#include <errno.h>
+#include <syslog.h>
+#include <sys/stat.h>
+
+#include "inn/innconf.h"
+#include "inn/messages.h"
+#include "inn/qio.h"
+#include "libinn.h"
+#include "ov.h"
+#include "paths.h"
+
+unsigned int NumArts;
+unsigned int StartTime;
+unsigned int TotOvTime;
+
+/*
+ * Timer function (lifted from innd/timer.c).
+ * This function is designed to report the number of milliseconds since
+ * the first invocation. I wanted better resolution than time(), and
+ * something easier to work with than gettimeofday()'s struct timeval's.
+ */
+
+static unsigned gettime(void)
+{
+ static int init = 0;
+ static struct timeval start_tv;
+ struct timeval tv;
+
+ if (! init) {
+ gettimeofday(&start_tv, NULL);
+ init++;
+ }
+ gettimeofday(&tv, NULL);
+ return((tv.tv_sec - start_tv.tv_sec) * 1000 + (tv.tv_usec - start_tv.tv_usec) / 1000);
+}
+
+/*
+** Process the input. Data comes from innd in the form:
+** @token@ data
+*/
+
+#define TEXT_TOKEN_LEN (2*sizeof(TOKEN)+2)
+static void ProcessIncoming(QIOSTATE *qp)
+{
+ char *Data;
+ char *p;
+ TOKEN token;
+ unsigned int starttime, endtime;
+ time_t Time, Expires;
+
+ for ( ; ; ) {
+ /* Read the first line of data. */
+ if ((Data = QIOread(qp)) == NULL) {
+ if (QIOtoolong(qp)) {
+ warn("line too long");
+ continue;
+ }
+ break;
+ }
+
+ if (Data[0] != '@' || strlen(Data) < TEXT_TOKEN_LEN+2
+ || Data[TEXT_TOKEN_LEN-1] != '@' || Data[TEXT_TOKEN_LEN] != ' ') {
+ warn("malformed token %s", Data);
+ continue;
+ }
+ token = TextToToken(Data);
+ Data += TEXT_TOKEN_LEN+1; /* skip over token and space */
+ for (p = Data; !ISWHITE(*p) ;p++) ;
+ *p++ = '\0';
+ Time = (time_t)atol(Data);
+ for (Data = p; !ISWHITE(*p) ;p++) ;
+ *p++ = '\0';
+ Expires = (time_t)atol(Data);
+ Data = p;
+ NumArts++;
+ starttime = gettime();
+ if (OVadd(token, Data, strlen(Data), Time, Expires) == OVADDFAILED)
+ syswarn("cannot write overview %s", Data);
+ endtime = gettime();
+ TotOvTime += endtime - starttime;
+ }
+ QIOclose(qp);
+}
+
+
+int main(int ac, char *av[])
+{
+ QIOSTATE *qp;
+ unsigned int now;
+
+ /* First thing, set up our identity. */
+ message_program_name = "overchan";
+
+ /* Log warnings and fatal errors to syslog unless we were given command
+ line arguments, since we're probably running under innd. */
+ if (ac == 0) {
+ openlog("overchan", L_OPENLOG_FLAGS | LOG_PID, LOG_INN_PROG);
+ message_handlers_warn(1, message_log_syslog_err);
+ message_handlers_die(1, message_log_syslog_err);
+ message_handlers_notice(1, message_log_syslog_notice);
+ }
+
+ /* Set defaults. */
+ if (!innconf_read(NULL))
+ exit(1);
+ umask(NEWSUMASK);
+ if (innconf->enableoverview && !innconf->useoverchan)
+ warn("overchan is running while innd is creating overview data (you"
+ " can ignore this message if you are running makehistory -F)");
+
+ ac -= 1;
+ av += 1;
+
+ if (!OVopen(OV_WRITE))
+ die("cannot open overview");
+
+ StartTime = gettime();
+ if (ac == 0)
+ ProcessIncoming(QIOfdopen(STDIN_FILENO));
+ else {
+ for ( ; *av; av++)
+ if (strcmp(*av, "-") == 0)
+ ProcessIncoming(QIOfdopen(STDIN_FILENO));
+ else if ((qp = QIOopen(*av)) == NULL)
+ syswarn("cannot open %s", *av);
+ else
+ ProcessIncoming(qp);
+ }
+ OVclose();
+ now = gettime();
+ notice("timings %u arts %u of %u ms", NumArts, TotOvTime, now - StartTime);
+ exit(0);
+ /* NOTREACHED */
+}
--- /dev/null
+#! /bin/sh
+# fixscript will replace this line with code to load innshellvars
+
+## $Revision: 2674 $
+## SH script to send IHAVE batches out.
+
+PROGNAME=`basename $0`
+LOG=${MOST_LOGS}/${PROGNAME}.log
+
+## How many Message-ID's per message.
+PERMESSAGE=1000
+
+## Go to where the action is, start logging
+cd $BATCH
+umask 002
+DEBUG=""
+if [ "X$1" = X-d ] ; then
+ DEBUG="-d"
+ shift
+else
+ test ! -f ${LOG} && touch ${LOG}
+ chmod 0660 ${LOG}
+ exec >>${LOG} 2>&1
+fi
+
+echo "${PROGNAME}: [$$] begin `date`"
+
+## List of sitename:hostname pairs to send to
+if [ -n "$1" ] ; then
+ LIST="$*"
+else
+ echo "${PROGNAME}: [$$] no sites specified" >&2
+ exit 1
+fi
+
+## Do the work...
+for SITE in ${LIST}; do
+ case $SITE in
+ *:*)
+ HOST=`expr $SITE : '.*:\(.*\)'`
+ SITE=`expr $SITE : '\(.*\):.*'`
+ ;;
+ *)
+ HOST=$SITE
+ ;;
+ esac
+ BATCHFILE=${SITE}.ihave.batch
+ LOCK=${LOCKS}/LOCK.${SITE}.ihave
+ trap 'rm -f ${LOCK} ; exit 1' 1 2 3 15
+ shlock -p $$ -f ${LOCK} || {
+ echo "${PROGNAME}: [$$] ${SITE}.ihave locked by `cat ${LOCK}`"
+ continue
+ }
+
+ ## See if any data is ready for host.
+ if [ -f ${SITE}.ihave.work ] ; then
+ cat ${SITE}.ihave.work >>${BATCHFILE}
+ rm -f ${SITE}.ihave.work
+ fi
+ if [ ! -f ${SITE}.ihave -o ! -s ${SITE}.ihave ] ; then
+ if [ ! -f ${BATCHFILE} -o ! -s ${BATCHFILE} ] ; then
+ rm -f ${LOCK}
+ continue
+ fi
+ fi
+ mv ${SITE}.ihave ${SITE}.ihave.work
+ ctlinnd -s -t30 flush ${SITE}.ihave || continue
+ cat ${SITE}.ihave.work >>${BATCHFILE}
+ rm -f ${SITE}.ihave.work
+ if [ ! -s ${BATCHFILE} ] ; then
+ echo "${PROGNAME}: [$$] no articles for ${SITE}.ihave"
+ rm -f ${BATCHFILE}
+ continue
+ fi
+
+ echo "${PROGNAME}: [$$] begin ${SITE}.ihave"
+
+ ## Write out the batchfile as a control message, in clumps.
+ export SITE PERMESSAGE BATCHFILE
+ while test -s ${BATCHFILE} ; do
+ (
+ echo Newsgroups: to.${SITE}
+ echo Control: ihave `innconfval pathhost`
+ echo Subject: cmsg ihave `innconfval pathhost`
+ echo ''
+ ${SED} -e ${PERMESSAGE}q <${BATCHFILE}
+ ) | ${INEWS} -h
+ ${SED} -e "1,${PERMESSAGE}d" <${BATCHFILE} >${BATCHFILE}.tmp
+ mv ${BATCHFILE}.tmp ${BATCHFILE}
+ done
+ echo "${PROGNAME}: [$$] end ${SITE}.ihave"
+ rm -f ${LOCK}
+done
+
+echo "${PROGNAME}: [$$] end `date`"
--- /dev/null
+#! /bin/sh
+# fixscript will replace this line with code to load innshellvars
+
+## $Revision: 4115 $
+## SH script to send NNTP news out.
+
+PROGNAME=`basename $0`
+LOG=${MOST_LOGS}/${PROGNAME}.log
+
+## Go to where the action is, start logging
+cd $BATCH
+umask 002
+DEBUG=""
+if [ "X$1" = X-d ] ; then
+ DEBUG="-d"
+ shift
+else
+ test ! -f ${LOG} && touch ${LOG}
+ chmod 0660 ${LOG}
+ exec >>${LOG} 2>&1
+fi
+
+echo "${PROGNAME}: [$$] begin `date`"
+
+## List of sitename:hostname pairs to send to
+if [ -n "$1" ] ; then
+ LIST="$*"
+else
+ echo "${PROGNAME}: [$$] no sites specified" >&2
+ exit 1
+fi
+
+## Do the work...
+for SITE in ${LIST}; do
+ case $SITE in
+ *:*)
+ HOST=`expr $SITE : '.*:\(.*\)'`
+ SITE=`expr $SITE : '\(.*\):.*'`
+ ;;
+ *)
+ HOST=$SITE
+ ;;
+ esac
+ case $HOST in
+ *@*)
+ PORT=`expr $HOST : '\(.*\)@.*'`
+ HOST=`expr $HOST : '.*@\(.*\)'`
+ ;;
+ *)
+ PORT=119
+ ;;
+ esac
+ BATCHFILE=${SITE}.nntp
+ LOCK=${LOCKS}/LOCK.${SITE}
+ trap 'rm -f ${LOCK} ; exit 1' 1 2 3 15
+ shlock -p $$ -f ${LOCK} || {
+ echo "${PROGNAME}: [$$] ${SITE} locked by `cat ${LOCK}`"
+ continue
+ }
+
+ ## See if any data is ready for host.
+ if [ -f ${SITE}.work ] ; then
+ cat ${SITE}.work >>${BATCHFILE}
+ rm -f ${SITE}.work
+ fi
+ if [ ! -f ${SITE} -o ! -s ${SITE} ] ; then
+ if [ ! -f ${BATCHFILE} -o ! -s ${BATCHFILE} ] ; then
+ rm -f ${LOCK}
+ continue
+ fi
+ fi
+ mv ${SITE} ${SITE}.work
+ ctlinnd -s -t30 flush ${SITE} || continue
+ cat ${SITE}.work >>${BATCHFILE}
+ rm -f ${SITE}.work
+ if [ ! -s ${BATCHFILE} ] ; then
+ echo "${PROGNAME}: [$$] no articles for ${SITE}"
+ rm -f ${BATCHFILE}
+ continue
+ fi
+
+ echo "${PROGNAME}: [$$] begin ${SITE}"
+ time innxmit ${DEBUG} -P ${PORT} ${HOST} ${BATCH}/${BATCHFILE}
+ echo "${PROGNAME}: [$$] end ${SITE}"
+ rm -f ${LOCK}
+done
+
+echo "${PROGNAME}: [$$] end `date`"
--- /dev/null
+#!/usr/bin/perl -w
+# fixscript will replace this line with code to load innshellvars
+
+##############################################################################
+# send-uucp.pl create news batches from the outgoing files
+#
+# Author: Edvard Tuinder <ed@elm.net>
+#
+# Copyright (C) 1994 Edvard Tuinder - ELM Consultancy B.V.
+# Copyright (C) 1995-1997 Miquel van Smoorenburg - Cistron Internet Services
+#
+# Copyright (C) 2003 Marco d'Itri <md@linux.it>
+# Nearly rewritten. Added syslog support, real errors checking and more.
+#
+# This program is free software; you can redistribute it and/or modify it
+# under the terms of the GNU General Public License as published by the Free
+# Software Foundation; either version 2 of the License, or (at your option)
+# any later version.
+##############################################################################
+
+use strict;
+use Sys::Syslog;
+
+# for compatibility with earlier versions of INN
+$inn::pathetc ||= '/etc/news';
+$inn::syslog_facility ||= 'news';
+$inn::uux ||= 'uux';
+
+# some default values
+my $MAXSIZE = 500000;
+my $MAXJOBS = 200;
+
+my %UNBATCHER = (
+ compress => 'cunbatch',
+ bzip2 => 'bunbatch',
+ gzip => 'gunbatch',
+);
+
+my $UUX_FLAGS = '- -z -r -gd';
+my $BATCHER_FLAGS = '';
+
+##############################################################################
+my $config_file = $inn::pathetc . '/send-uucp.cf';
+my $lockfile = $inn::locks . '/LOCK.send-uucp';
+
+openlog('send-uucp', 'pid', $inn::syslog_facility);
+
+my @sitelist;
+if (@ARGV) {
+ foreach my $site (@ARGV) {
+ my @cfg = read_cf($config_file, $site);
+ if (not @cfg) {
+ logmsg("site $site not found in the configuration", 'err');
+ next;
+ }
+ push @sitelist, @cfg;
+ }
+} else {
+ @sitelist = read_cf($config_file, undef);
+}
+
+if (not @sitelist) {
+ logmsg('nothing to do', 'debug');
+ exit 0;
+}
+
+chdir $inn::batch or logdie("Can't access $inn::batch: $!", 'crit');
+
+shlock($lockfile);
+
+run_site($_) foreach @sitelist;
+unlink $lockfile;
+exit 0;
+
+# lint food
+$inn::compress.$inn::locks.$inn::syslog_facility.$inn::have_uustat = 0 if 0;
+
+##############################################################################
+sub read_cf {
+ my ($conf_file, $site_wanted) = @_;
+
+ my $hour = (localtime time)[2];
+
+ my @sites;
+ open(CF, $conf_file) or logdie("cannot open $conf_file: $!", 'crit');
+ while (<CF>) {
+ chop;
+ s/\s*\#.*$//;
+ next if /^$/;
+
+ my ($sitespec, $compress, $size, $time) = split(/\s+/);
+ next if not $sitespec;
+
+ my ($site, $host, $funnel) = split(/:/, $sitespec);
+ $host = $site if not $host;
+ $funnel = $site if not $funnel;
+
+ $compress =~ s/_/ /g if $compress;
+
+ if ($site_wanted) {
+ if ($site eq $site_wanted) {
+ push @sites, [$site, $host, $funnel, $compress, $size];
+ last;
+ }
+ next;
+ }
+
+ if ($time) {
+ foreach my $time (split(/,/, $time)) {
+ next if $time != $hour;
+ push @sites, [$site, $host, $funnel, $compress, $size];
+ }
+ } else {
+ push @sites, [$site, $host, $funnel, $compress, $size];
+ }
+ }
+ close CF;
+ return @sites;
+}
+
+##############################################################################
+# count number of jobs in the UUCP queue for a given site
+sub count_jobs {
+ my ($site) = @_;
+
+ return 0 if not $inn::have_uustat;
+ open(JOBS, "uustat -s $site 2> /dev/null |") or logdie("cannot fork: $!");
+ my $count = grep(/ Executing rnews /, <JOBS>);
+ close JOBS; # ignore errors, uustat may fail
+ return $count;
+}
+
+# select the rnews label appropriate for the compressor program used
+sub unbatcher {
+ my ($compressor) = @_;
+
+ $compressor =~ s%.*/%%; # Do not keep the complete path.
+ $compressor =~ s% .*%%; # Do not keep the optional parameters.
+ return $UNBATCHER{$compressor} || 'cunbatch';
+}
+
+##############################################################################
+# batch articles for one site
+sub run_site {
+ my ($cfg) = @_;
+ my ($site, $host, $funnel, $compress, $size) = @$cfg;
+
+ logmsg("checking site $site", 'debug');
+ my $maxjobs = '';
+ if ($MAXJOBS) {
+ my $jobs = count_jobs($site);
+ if ($jobs >= $MAXJOBS) {
+ logmsg("too many jobs queued for $site");
+ return;
+ }
+ $maxjobs = '-N ' . ($MAXJOBS - $jobs);
+ }
+
+ $compress ||= $inn::compress;
+ $size ||= $MAXSIZE;
+
+ # if exists a .work temp file left by a previous invocation, rename
+ # it to .work.tmp, we'll append it to the current batch file once it
+ # has been renamed and flushed.
+ if (-f "$site.work") {
+ rename("$site.work", "$site.work.tmp")
+ or logdie("cannot rename $site.work: $!", 'crit');
+ }
+
+ if (not -f $site and not -f "$site.work.tmp") {
+ logmsg("no batch file for site $site", 'err');
+ return;
+ }
+
+ rename($site, "$site.work") or logdie("cannot rename $site: $!", 'crit');
+ logmsg("Flushing $funnel for site $site", 'debug');
+ ctlinnd('-t120', 'flush', $funnel);
+
+ # append the old .work temp file to the current batch file if needed
+ if (-f "$site.work.tmp") {
+ my $err = '';
+ open(OUT, ">>$site.work")
+ or logdie("cannot open $site.work: $!", 'crit');
+ open(IN, "$site.work.tmp")
+ or logdie("cannot open $site.work.tmp: $!", 'crit');
+ print OUT while <IN>;
+ close IN;
+ close OUT or logdie("cannot close $site.work: $!");;
+ unlink "$site.work.tmp"
+ or logmsg("cannot delete $site.work.tmp: $!", 'err');
+ }
+
+ if (not -s "$site.work") {
+ logmsg("no articles for $site", 'debug');
+ unlink "$site.work" or logmsg("cannot delete $site.work: $!", 'err');
+ } else {
+ if ($compress eq 'none') {
+ system "batcher -b $size $maxjobs $BATCHER_FLAGS "
+ . "-p\"$inn::uux $UUX_FLAGS %s!rnews\" $host $site.work";
+ } else {
+ system "batcher -b $size $maxjobs $BATCHER_FLAGS "
+ . "-p\"{ echo '#! " . unbatcher($compress)
+ . "' ; exec $compress; } | "
+ . "$inn::uux $UUX_FLAGS %s!rnews\" $host $site.work";
+ }
+ logmsg("batched articles for $site", 'debug');
+ }
+}
+
+##############################################################################
+sub logmsg {
+ my ($msg, $lvl) = @_;
+
+ syslog($lvl || 'notice', '%s', $msg);
+}
+
+sub logdie {
+ my ($msg, $lvl) = @_;
+
+ logmsg($msg, $lvl || 'err');
+ unlink $lockfile;
+ exit 1;
+}
+
+sub ctlinnd {
+ my ($cmd, @args) = @_;
+
+ my $st = system("$inn::newsbin/ctlinnd", '-s', $cmd, @args);
+ logdie('Cannot run ctlinnd: ' . $!) if $st == -1;
+ logdie('ctlinnd returned status ' . ($st & 255)) if $st > 0;
+}
+
+sub shlock {
+ my $lockfile = shift;
+
+ my $locktry = 0;
+ while ($locktry < 60) {
+ if (system("$inn::newsbin/shlock", '-p', $$, '-f', $lockfile) == 0) {
+ return 1;
+ }
+ $locktry++;
+ sleep 2;
+ }
+
+ my $lockreason;
+ if (open(LOCKFILE, $lockfile)) {
+ $lockreason = 'held by ' . (<LOCKFILE> || '?');
+ close LOCKFILE;
+ } else {
+ $lockreason = $!;
+ }
+ logdie("Cannot get lock $lockfile: $lockreason");
+ return undef;
+}
+
+__END__
+
+=head1 NAME
+
+send-uucp - Send Usenet articles via UUCP
+
+=head1 SYNOPSIS
+
+B<send-uucp> [I<SITE> ...]
+
+=head1 DESCRIPTION
+
+The B<send-uucp> program processes batch files written by innd(8) to send
+Usenet articles to UUCP sites. It reads a configuration file to control how
+it behaves with various sites. Normally, it's run periodically out of cron
+to put together batches and send them to remote UUCP sites.
+
+=head1 OPTIONS
+
+Any arguments provided to the program are interpreted as a list of sites
+specfied in F<send-uucp.cf> for which batches should be generated. If no
+arguments are supplied then batches will be generated for all sites listed
+in that configuration file.
+
+=head1 CONFIGURATION
+
+The sites to which articles are to be sent must be configured in the
+configuration file F<send-uucp.cf>. Each site is specified with a line of
+the form:
+
+ site[:host[:funnel]] [compressor [maxsize [batchtime]]]
+
+=over 4
+
+=item I<site>
+
+The news site name being configured. This must match a site name
+from newsfeeds(5).
+
+=item I<host>
+
+The UUCP host name to which batches should be sent for this site.
+If omitted, the news site name will be used as the UUCP host name.
+
+=item I<funnel>
+
+In the case of a site configured as a funnel, B<send-uucp> needs to flush
+the channel (or exploder) being used as the target of the funnel instead of
+flushing the site. This is the way to tell B<send-uucp> the name of the
+channel or exploder to flush for this site. If not specified, default to
+flushing the site.
+
+=item I<compressor>
+
+The compression method to use for batches. This should be one of compress,
+gzip or none. Arguments for the compression command may be specified by
+using C<_> instead of spaces. For example, C<gzip_-9>. The default value is
+C<compress>.
+
+=item I<maxsize>
+
+The maximum size of a single batch before compression. The default value is
+500,000 bytes.
+
+=item I<batchtime>
+
+A comma separated list of hours during which batches should be generated for
+a given site. When B<send-uucp> runs, a site will only be processed if the
+current hour matches one of the hours in I<batchtime>. The default is no
+limitation on when to generate batches.
+
+=back
+
+Fields are seperated by spaces and only the site name needs to be specified,
+with defaults being used for unspecified values. If the first character on
+a line is a C<#> then the rest of the line is ignored.
+
+=head1 EXAMPLE
+
+Here is an example send-uucp.cf configuration file:
+
+ zoetermeer gzip 1048576 5,18,22
+ hoofddorp gzip 1048576 5,18,22
+ pa3ebv gzip 1048576 5,18,22
+ drinkel gzip 1048576 5,6,18,20,22,0,2
+ manhole compress 1048576 5,18,22
+ owl compress 1048576
+ able
+ pern::MYFUNNEL!
+
+This defines eight UUCP sites. The first four use gzip compression and the
+last three use compress. The first six use a batch size of 1MB, and the
+last site (able) uses the default of 500,000 bytes. The zoetermeer,
+hoofddorp, pa3ebv, and manhole sites will only have batches generated for
+them during the hours of 05:00, 18:00, and 22:00, and the drinkel site will
+only have batches generated during those hours and 20:00, 00:00, and 02:00.
+There are no restrictions on when batches will be generated for owl or able.
+
+The pern site is configured as a funnel into C<MYFUNNEL!>. B<send-uucp> will
+issue C<ctlinnd flush MYFUNNEL!> instead of C<ctlinnd flush pern>.
+
+=head1 FILES
+
+=over 4
+
+=item I<pathetc>/send-uucp.cf
+
+Configuration file specifying a list of sites to be processed.
+
+=back
+
+=head1 NOTES
+
+The usual flags used for a UUCP feed in the I<newsfeeds> file are C<Tf,Wfb>.
+
+=head1 SEE ALSO
+
+innd(8), newsfeeds(5), uucp(8)
+
+=head1 AUTHOR
+
+This program was originally written by Edvard Tuinder <ed@elm.net> and then
+maintained and extended by Miquel van Smoorenburg <miquels@cistron.nl>.
+Marco d'Itri <md@linux.it> cleaned up the code for inclusion in INN. This
+manual page was written by Mark Brown <broonie@sirena.org.uk>.
+
+=cut
--- /dev/null
+#!/bin/sh
+# fixscript will replace this line with code to load innshellvars
+#
+# Submit path statistics based on ninpaths
+# $Id: sendinpaths.in 5854 2002-11-25 17:53:06Z rra $
+
+# Assuming the ninpaths dump files are in ${MOST_LOGS}/path/inpaths.%d
+
+cd ${MOST_LOGS}/path
+ME=`${NEWSBIN}/innconfval pathhost`
+report=30
+keep=14
+TMP=""
+defaddr="pathsurvey@top1000.org top1000@anthologeek.net"
+
+# Renice to give other processes priority, since this isn't too important.
+renice 20 -p $$ > /dev/null
+
+# Make report from (up to) $report days of dumps
+LOGS=`find . -name 'inpaths.*' ! -size 0 -mtime -$report -print`
+if [ -z "$LOGS" ] ; then
+ echo "No data has been collected this month!"
+ exit 1
+fi
+
+# for check dumps
+for i in $LOGS
+do
+ ninpaths -u $i -r $ME > /dev/null 2>&1
+ if test $? -eq 0; then :
+ TMP="$TMP -u $i"
+ fi
+done
+
+if [ "$1" = "-n" ] ; then
+ ninpaths $TMP -r $ME
+else
+ ninpaths $TMP -r $ME |\
+ $MAILCMD -s "inpaths $ME" ${1:-$defaddr}
+ # remove dumps older than $keep days
+ find . -name 'inpaths.*' -mtime +$keep -exec rm '{}' \;
+fi
+
+exit 0
--- /dev/null
+#! /bin/sh
+# fixscript will replace this line with code to load innshellvars
+
+# $Id: sendxbatches.in 2674 1999-11-15 06:28:29Z rra $
+# By petri@ibr.cs.tu-bs.de with mods by libove@jerry.alf.dec.com
+#
+# Script to send xbatches for a site, wrapped around innxbatch
+# Invocation: sendxbatches <sitename> <hostname> <xbatch file name> ...
+#
+## TODO: - we should check the amount of queued batches for the site,
+## to prevent disk overflow due to unreachable sites.
+
+if [ $# -lt 3 ]
+then
+ echo "usage: $0 <sitename> <hostname> <xbatch file name>"
+ exit 1
+fi
+
+LOCK=${LOCKS}/LOCK.sendxbatches
+shlock -p $$ -f ${LOCK}
+if [ $? -ne 0 ]
+then
+ echo Locked by `cat ${LOCK}`
+ exit 1
+fi
+
+trap 'rm -f ${LOCK} ; exit 1' 1 2 3 15
+site="$1"
+host="$2"
+shift; shift
+
+ctlinnd -s flush "$site"
+if [ $? -ne 0 ]
+then
+ echo "ctlinnd flush $site failed."
+ exit 1
+fi
+sleep 5
+$NEWSBIN/innxbatch -D -v "$host" $*
--- /dev/null
+/* $Id: shlock.c 6124 2003-01-14 06:03:29Z rra $
+**
+** Produce reliable locks for shell scripts, by Peter Honeyman as told
+** to Rich $alz.
+*/
+
+#include "config.h"
+#include "clibrary.h"
+#include <errno.h>
+#include <fcntl.h>
+#include <signal.h>
+#include <sys/stat.h>
+
+#include "inn/messages.h"
+
+
+static bool BinaryLock;
+
+
+/*
+** See if the process named in an existing lock still exists by
+** sending it a null signal.
+*/
+static bool
+ValidLock(char *name, bool JustChecking)
+{
+ int fd;
+ int i;
+ pid_t pid;
+ char buff[BUFSIZ];
+
+ /* Open the file. */
+ if ((fd = open(name, O_RDONLY)) < 0) {
+ if (JustChecking)
+ return false;
+ syswarn("cannot open %s", name);
+ return true;
+ }
+
+ /* Read the PID that is written there. */
+ if (BinaryLock) {
+ if (read(fd, (char *)&pid, sizeof pid) != sizeof pid) {
+ close(fd);
+ return false;
+ }
+ }
+ else {
+ if ((i = read(fd, buff, sizeof buff - 1)) <= 0) {
+ close(fd);
+ return false;
+ }
+ buff[i] = '\0';
+ pid = (pid_t) atol(buff);
+ }
+ close(fd);
+ if (pid <= 0)
+ return false;
+
+ /* Send the signal. */
+ if (kill(pid, 0) < 0 && errno == ESRCH)
+ return false;
+
+ /* Either the kill worked, or we're optimistic about the error code. */
+ return true;
+}
+
+
+/*
+** Unlink a file, print a message on error, and exit.
+*/
+static void
+UnlinkAndExit(char *name, int x)
+{
+ if (unlink(name) < 0)
+ syswarn("cannot unlink %s", name);
+ exit(x);
+}
+
+
+/*
+** Print a usage message and exit.
+*/
+static void
+Usage(void)
+{
+ fprintf(stderr, "Usage: shlock [-u|-b] -f file -p pid\n");
+ exit(1);
+}
+
+
+int
+main(int ac, char *av[])
+{
+ int i;
+ char *p;
+ int fd;
+ char tmp[BUFSIZ];
+ char buff[BUFSIZ];
+ char *name;
+ pid_t pid;
+ bool ok;
+ bool JustChecking;
+
+ /* Establish our identity. */
+ message_program_name = "shlock";
+
+ /* Set defaults. */
+ pid = 0;
+ name = NULL;
+ JustChecking = false;
+ umask(NEWSUMASK);
+
+ /* Parse JCL. */
+ while ((i = getopt(ac, av, "bcup:f:")) != EOF)
+ switch (i) {
+ default:
+ Usage();
+ /* NOTREACHED */
+ case 'b':
+ case 'u':
+ BinaryLock = true;
+ break;
+ case 'c':
+ JustChecking = true;
+ break;
+ case 'p':
+ pid = (pid_t) atol(optarg);
+ break;
+ case 'f':
+ name = optarg;
+ break;
+ }
+ ac -= optind;
+ av += optind;
+ if (ac || pid == 0 || name == NULL)
+ Usage();
+
+ /* Create the temp file in the same directory as the destination. */
+ if ((p = strrchr(name, '/')) != NULL) {
+ *p = '\0';
+ snprintf(tmp, sizeof(tmp), "%s/shlock%ld", name, (long)getpid());
+ *p = '/';
+ }
+ else
+ snprintf(tmp, sizeof(tmp), "shlock%ld", (long)getpid());
+
+ /* Loop until we can open the file. */
+ while ((fd = open(tmp, O_RDWR | O_CREAT | O_EXCL, 0644)) < 0)
+ switch (errno) {
+ default:
+ /* Unknown error -- give up. */
+ sysdie("cannot open %s", tmp);
+ case EEXIST:
+ /* If we can remove the old temporary, retry the open. */
+ if (unlink(tmp) < 0)
+ sysdie("cannot unlink %s", tmp);
+ break;
+ }
+
+ /* Write the process ID. */
+ if (BinaryLock)
+ ok = write(fd, &pid, sizeof pid) == sizeof pid;
+ else {
+ snprintf(buff, sizeof(buff), "%ld\n", (long) pid);
+ i = strlen(buff);
+ ok = write(fd, buff, i) == i;
+ }
+ if (!ok) {
+ syswarn("cannot write PID to %s", tmp);
+ close(fd);
+ UnlinkAndExit(tmp, 1);
+ }
+
+ close(fd);
+
+ /* Handle the "-c" flag. */
+ if (JustChecking) {
+ if (ValidLock(name, true))
+ UnlinkAndExit(tmp, 1);
+ UnlinkAndExit(tmp, 0);
+ }
+
+ /* Try to link the temporary to the lockfile. */
+ while (link(tmp, name) < 0)
+ switch (errno) {
+ default:
+ /* Unknown error -- give up. */
+ syswarn("cannot link %s to %s", tmp, name);
+ UnlinkAndExit(tmp, 1);
+ /* NOTREACHED */
+ case EEXIST:
+ /* File exists; if lock is valid, give up. */
+ if (ValidLock(name, false))
+ UnlinkAndExit(tmp, 1);
+ if (unlink(name) < 0) {
+ syswarn("cannot unlink %s", name);
+ UnlinkAndExit(tmp, 1);
+ }
+ }
+
+ UnlinkAndExit(tmp, 0);
+ /* NOTREACHED */
+ return 1;
+}
--- /dev/null
+/* $Id: shrinkfile.c 6135 2003-01-19 01:15:40Z rra $
+**
+** Shrink files on line boundaries.
+**
+** Written by Landon Curt Noll <chongo@toad.com>, and placed in the
+** public domain. Rewritten for INN by Rich Salz.
+**
+** Usage:
+** shrinkfile [-n] [-s size [-m maxsize]] [-v] file...
+** -n No writes, exit 0 if any file is too large, 1 otherwise
+** -s size Truncation size (0 default); suffix may be k, m,
+** or g to scale. Must not be larger than 2^31 - 1.
+** -m maxsize Maximum size allowed before truncation. If maxsize
+** <= size, then it is reset to size. Default == size.
+** -v Print status line.
+**
+** Files will be shrunk an end of line boundary. In no case will the
+** file be longer than size bytes if it was longer than maxsize bytes.
+** If the first line is longer than the absolute value of size, the file
+** will be truncated to zero length.
+**
+** The -n flag may be used to determine of any file is too large. No
+** files will be altered in this mode.
+*/
+
+#include "config.h"
+#include "clibrary.h"
+#include <ctype.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <sys/stat.h>
+
+#include "inn/innconf.h"
+#include "inn/messages.h"
+#include "libinn.h"
+
+#define MAX_SIZE 0x7fffffffUL
+
+
+/*
+** Open a safe unique temporary file that will go away when closed.
+*/
+static FILE *
+OpenTemp(void)
+{
+ FILE *F;
+ char *filename;
+ int fd;
+
+ filename = concatpath(innconf->pathtmp, "shrinkXXXXXX");
+ fd = mkstemp(filename);
+ if (fd < 0)
+ sysdie("cannot create temporary file");
+ F = fdopen(fd, "w+");
+ if (F == NULL)
+ sysdie("cannot fdopen %s", filename);
+ unlink(filename);
+ free(filename);
+ return F;
+}
+
+
+/*
+** Does file end with \n? Assume it does on I/O error, to avoid doing I/O.
+*/
+static int
+EndsWithNewline(FILE *F)
+{
+ int c;
+
+ if (fseeko(F, 1, SEEK_END) < 0) {
+ syswarn("cannot seek to end of file");
+ return true;
+ }
+
+ /* return the actual character or EOF */
+ if ((c = fgetc(F)) == EOF) {
+ if (ferror(F))
+ syswarn("cannot read last byte");
+ return true;
+ }
+ return c == '\n';
+}
+
+
+/*
+** Add a newline to location of a file.
+*/
+static bool
+AppendNewline(char *name)
+{
+ FILE *F;
+
+ if ((F = xfopena(name)) == NULL) {
+ syswarn("cannot add newline");
+ return false;
+ }
+
+ if (fputc('\n', F) == EOF
+ || fflush(F) == EOF
+ || ferror(F)
+ || fclose(F) == EOF) {
+ syswarn("cannot add newline");
+ return false;
+ }
+
+ return true;
+}
+
+/*
+** Just check if it is too big
+*/
+static bool
+TooBig(FILE *F, off_t maxsize)
+{
+ struct stat Sb;
+
+ /* Get the file's size. */
+ if (fstat((int)fileno(F), &Sb) < 0) {
+ syswarn("cannot fstat");
+ return false;
+ }
+
+ /* return true if too large */
+ return (maxsize > Sb.st_size ? false : true);
+}
+
+/*
+** This routine does all the work.
+*/
+static bool
+Process(FILE *F, char *name, off_t size, off_t maxsize, bool *Changedp)
+{
+ off_t len;
+ FILE *tmp;
+ struct stat Sb;
+ char buff[BUFSIZ + 1];
+ int c;
+ size_t i;
+ bool err;
+
+ /* Get the file's size. */
+ if (fstat((int)fileno(F), &Sb) < 0) {
+ syswarn("cannot fstat");
+ return false;
+ }
+ len = Sb.st_size;
+
+ /* Process a zero size request. */
+ if (size == 0 && len > maxsize) {
+ if (len > 0) {
+ fclose(F);
+ if ((F = fopen(name, "w")) == NULL) {
+ syswarn("cannot overwrite");
+ return false;
+ }
+ fclose(F);
+ *Changedp = true;
+ }
+ return true;
+ }
+
+ /* See if already small enough. */
+ if (len <= maxsize) {
+ /* Newline already present? */
+ if (EndsWithNewline(F)) {
+ fclose(F);
+ return true;
+ }
+
+ /* No newline, add it if it fits. */
+ if (len < size - 1) {
+ fclose(F);
+ *Changedp = true;
+ return AppendNewline(name);
+ }
+ }
+ else if (!EndsWithNewline(F)) {
+ if (!AppendNewline(name)) {
+ fclose(F);
+ return false;
+ }
+ }
+
+ /* We now have a file that ends with a newline that is bigger than
+ * we want. Starting from {size} bytes from end, move forward
+ * until we get a newline. */
+ if (fseeko(F, -size, SEEK_END) < 0) {
+ syswarn("cannot fseeko");
+ fclose(F);
+ return false;
+ }
+
+ while ((c = getc(F)) != '\n')
+ if (c == EOF) {
+ syswarn("cannot read");
+ fclose(F);
+ return false;
+ }
+
+ /* Copy rest of file to temp. */
+ tmp = OpenTemp();
+ err = false;
+ while ((i = fread(buff, 1, sizeof buff, F)) > 0)
+ if (fwrite(buff, 1, i, tmp) != i) {
+ err = true;
+ break;
+ }
+ if (err) {
+ syswarn("cannot copy to temporary file");
+ fclose(F);
+ fclose(tmp);
+ return false;
+ }
+
+ /* Now copy temp back to original file. */
+ fclose(F);
+ if ((F = fopen(name, "w")) == NULL) {
+ syswarn("cannot overwrite file");
+ fclose(tmp);
+ return false;
+ }
+ fseeko(tmp, 0, SEEK_SET);
+
+ while ((i = fread(buff, 1, sizeof buff, tmp)) > 0)
+ if (fwrite(buff, 1, i, F) != i) {
+ err = true;
+ break;
+ }
+ if (err) {
+ syswarn("cannot overwrite file");
+ fclose(F);
+ fclose(tmp);
+ return false;
+ }
+
+ fclose(F);
+ fclose(tmp);
+ *Changedp = true;
+ return true;
+}
+
+
+/*
+** Convert size argument to numeric value. Return -1 on error.
+*/
+static off_t
+ParseSize(char *p)
+{
+ off_t scale;
+ unsigned long str_num;
+ char *q;
+
+ /* Skip leading spaces */
+ while (ISWHITE(*p))
+ p++;
+ if (*p == '\0')
+ return -1;
+
+ /* determine the scaling factor */
+ q = &p[strlen(p) - 1];
+ switch (*q) {
+ default:
+ return -1;
+ case '0': case '1': case '2': case '3': case '4':
+ case '5': case '6': case '7': case '8': case '9':
+ scale = 1;
+ break;
+ case 'k': case 'K':
+ scale = 1024;
+ *q = '\0';
+ break;
+ case 'm': case 'M':
+ scale = 1024 * 1024;
+ *q = '\0';
+ break;
+ case 'g': case 'G':
+ scale = 1024 * 1024 * 1024;
+ *q = '\0';
+ break;
+ }
+
+ /* Convert string to number. */
+ if (sscanf(p, "%lud", &str_num) != 1)
+ return -1;
+ if (str_num > MAX_SIZE / scale)
+ die("size is too big");
+
+ return scale * str_num;
+}
+
+
+/*
+** Print usage message and exit.
+*/
+static void
+Usage(void)
+{
+ fprintf(stderr,
+ "Usage: shrinkfile [-n] [ -m maxsize ] [-s size] [-v] file...");
+ exit(1);
+}
+
+
+int
+main(int ac, char *av[])
+{
+ bool Changed;
+ bool Verbose;
+ bool no_op;
+ FILE *F;
+ char *p;
+ int i;
+ off_t size = 0;
+ off_t maxsize = 0;
+
+ /* First thing, set up our identity. */
+ message_program_name = "shrinkfile";
+
+ /* Set defaults. */
+ Verbose = false;
+ no_op = false;
+ umask(NEWSUMASK);
+
+ if (!innconf_read(NULL))
+ exit(1);
+
+ /* Parse JCL. */
+ while ((i = getopt(ac, av, "m:s:vn")) != EOF)
+ switch (i) {
+ default:
+ Usage();
+ /* NOTREACHED */
+ case 'n':
+ no_op = true;
+ break;
+ case 'm':
+ if ((maxsize = ParseSize(optarg)) < 0)
+ Usage();
+ break;
+ case 's':
+ if ((size = ParseSize(optarg)) < 0)
+ Usage();
+ break;
+ case 'v':
+ Verbose = true;
+ break;
+ }
+ if (maxsize < size) {
+ maxsize = size;
+ }
+ ac -= optind;
+ av += optind;
+ if (ac == 0)
+ Usage();
+
+ while ((p = *av++) != NULL) {
+ if ((F = fopen(p, "r")) == NULL) {
+ syswarn("cannot open %s", p);
+ continue;
+ }
+
+ /* -n (no_op) or normal processing */
+ if (no_op) {
+
+ /* check if too big and exit zero if it is */
+ if (TooBig(F, maxsize)) {
+ if (Verbose)
+ notice("%s is too large", p);
+ exit(0);
+ /* NOTREACHED */
+ }
+
+ /* no -n, do some real work */
+ } else {
+ Changed = false;
+ if (!Process(F, p, size, maxsize, &Changed))
+ syswarn("cannot shrink %s", p);
+ else if (Verbose && Changed)
+ notice("shrunk %s", p);
+ }
+ }
+ if (no_op && Verbose) {
+ notice("did not find a file that was too large");
+ }
+
+ /* if -n, then exit non-zero to indicate no file too big */
+ exit(no_op ? 1 : 0);
+ /* NOTREACHED */
+}
--- /dev/null
+#! /bin/sh
+
+# From configure.in Revision: 7466
+# libtool.m4 - Configure libtool for the host system. -*-Shell-script-*-
+## Copyright 1996, 1997, 1998, 1999, 2000, 2001
+## Free Software Foundation, Inc.
+## Originally by Gordon Matzigkeit <gord@gnu.ai.mit.edu>, 1996
+##
+## This program is free software; you can redistribute it and/or modify
+## it under the terms of the GNU General Public License as published by
+## the Free Software Foundation; either version 2 of the License, or
+## (at your option) any later version.
+##
+## This program is distributed in the hope that it will be useful, but
+## WITHOUT ANY WARRANTY; without even the implied warranty of
+## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+## General Public License for more details.
+##
+## You should have received a copy of the GNU General Public License
+## along with this program; if not, write to the Free Software
+## Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+##
+## As a special exception to the GNU General Public License, if you
+## distribute this file as part of a program that contains a
+## configuration script generated by Autoconf, you may include it under
+## the same distribution terms that you use for the rest of that program.
+
+# serial 46 AC_PROG_LIBTOOL
+
+
+
+
+
+# AC_LIBTOOL_HEADER_ASSERT
+# ------------------------
+# AC_LIBTOOL_HEADER_ASSERT
+
+# _LT_AC_CHECK_DLFCN
+# --------------------
+# _LT_AC_CHECK_DLFCN
+
+# AC_LIBTOOL_SYS_GLOBAL_SYMBOL_PIPE
+# ---------------------------------
+ # AC_LIBTOOL_SYS_GLOBAL_SYMBOL_PIPE
+
+# _LT_AC_LIBTOOL_SYS_PATH_SEPARATOR
+# ---------------------------------
+# _LT_AC_LIBTOOL_SYS_PATH_SEPARATOR
+
+# _LT_AC_PROG_ECHO_BACKSLASH
+# --------------------------
+# Add some code to the start of the generated configure script which
+# will find an echo command which doesn't interpret backslashes.
+# _LT_AC_PROG_ECHO_BACKSLASH
+
+# _LT_AC_TRY_DLOPEN_SELF (ACTION-IF-TRUE, ACTION-IF-TRUE-W-USCORE,
+# ACTION-IF-FALSE, ACTION-IF-CROSS-COMPILING)
+# ------------------------------------------------------------------
+# _LT_AC_TRY_DLOPEN_SELF
+
+# AC_LIBTOOL_DLOPEN_SELF
+# -------------------
+# AC_LIBTOOL_DLOPEN_SELF
+
+# _LT_AC_LTCONFIG_HACK
+
+# AC_LIBTOOL_DLOPEN - enable checks for dlopen support
+
+
+# AC_LIBTOOL_WIN32_DLL - declare package support for building win32 dll's
+
+
+# AC_ENABLE_SHARED - implement the --enable-shared flag
+# Usage: AC_ENABLE_SHARED[(DEFAULT)]
+# Where DEFAULT is either `yes' or `no'. If omitted, it defaults to
+# `yes'.
+
+
+# AC_DISABLE_SHARED - set the default shared flag to --disable-shared
+
+
+# AC_ENABLE_STATIC - implement the --enable-static flag
+# Usage: AC_ENABLE_STATIC[(DEFAULT)]
+# Where DEFAULT is either `yes' or `no'. If omitted, it defaults to
+# `yes'.
+
+
+# AC_DISABLE_STATIC - set the default static flag to --disable-static
+
+
+
+# AC_ENABLE_FAST_INSTALL - implement the --enable-fast-install flag
+# Usage: AC_ENABLE_FAST_INSTALL[(DEFAULT)]
+# Where DEFAULT is either `yes' or `no'. If omitted, it defaults to
+# `yes'.
+
+
+# AC_DISABLE_FAST_INSTALL - set the default to --disable-fast-install
+
+
+# AC_LIBTOOL_PICMODE - implement the --with-pic flag
+# Usage: AC_LIBTOOL_PICMODE[(MODE)]
+# Where MODE is either `yes' or `no'. If omitted, it defaults to
+# `both'.
+
+
+
+# AC_PATH_TOOL_PREFIX - find a file program which can recognise shared library
+
+
+
+# AC_PATH_MAGIC - find a file program which can recognise a shared library
+
+
+
+# AC_PROG_LD - find the path to the GNU or non-GNU linker
+
+
+# AC_PROG_LD_GNU -
+
+
+# AC_PROG_LD_RELOAD_FLAG - find reload flag for linker
+# -- PORTME Some linkers may need a different reload flag.
+
+
+# AC_DEPLIBS_CHECK_METHOD - how to check for library dependencies
+# -- PORTME fill in with the dynamic library characteristics
+
+
+
+# AC_PROG_NM - find the path to a BSD-compatible name lister
+
+
+# AC_CHECK_LIBM - check for math library
+
+
+# AC_LIBLTDL_CONVENIENCE[(dir)] - sets LIBLTDL to the link flags for
+# the libltdl convenience library and INCLTDL to the include flags for
+# the libltdl header and adds --enable-ltdl-convenience to the
+# configure arguments. Note that LIBLTDL and INCLTDL are not
+# AC_SUBSTed, nor is AC_CONFIG_SUBDIRS called. If DIR is not
+# provided, it is assumed to be `libltdl'. LIBLTDL will be prefixed
+# with '${top_builddir}/' and INCLTDL will be prefixed with
+# '${top_srcdir}/' (note the single quotes!). If your package is not
+# flat and you're not using automake, define top_builddir and
+# top_srcdir appropriately in the Makefiles.
+
+
+# AC_LIBLTDL_INSTALLABLE[(dir)] - sets LIBLTDL to the link flags for
+# the libltdl installable library and INCLTDL to the include flags for
+# the libltdl header and adds --enable-ltdl-install to the configure
+# arguments. Note that LIBLTDL and INCLTDL are not AC_SUBSTed, nor is
+# AC_CONFIG_SUBDIRS called. If DIR is not provided and an installed
+# libltdl is not found, it is assumed to be `libltdl'. LIBLTDL will
+# be prefixed with '${top_builddir}/' and INCLTDL will be prefixed
+# with '${top_srcdir}/' (note the single quotes!). If your package is
+# not flat and you're not using automake, define top_builddir and
+# top_srcdir appropriately in the Makefiles.
+# In the future, this macro may have to be called after AC_PROG_LIBTOOL.
+
+
+# old names
+
+
+
+
+
+
+
+
+# This is just to silence aclocal about the macro not being used
+
+# Guess values for system-dependent variables and create Makefiles.
+# Generated automatically using autoconf version 2.13
+# Copyright (C) 1992, 93, 94, 95, 96 Free Software Foundation, Inc.
+#
+# This configure script is free software; the Free Software Foundation
+# gives unlimited permission to copy, distribute and modify it.
+
+# Defaults:
+ac_help=
+ac_default_prefix=/usr/local
+# Any additions from configure.in:
+ac_default_prefix=/usr/local/news
+ac_help="$ac_help
+ --enable-libtool Use libtool for lib generation [default=no]"
+ac_help="$ac_help
+ --enable-shared[=PKGS] build shared libraries [default=yes]"
+ac_help="$ac_help
+ --enable-static[=PKGS] build static libraries [default=yes]"
+ac_help="$ac_help
+ --enable-fast-install[=PKGS] optimize for fast installation [default=yes]"
+ac_help="$ac_help
+ --with-gnu-ld assume the C compiler uses GNU ld [default=no]"
+
+# Find the correct PATH separator. Usually this is `:', but
+# DJGPP uses `;' like DOS.
+if test "X${PATH_SEPARATOR+set}" != Xset; then
+ UNAME=${UNAME-`uname 2>/dev/null`}
+ case X$UNAME in
+ *-DOS) lt_cv_sys_path_separator=';' ;;
+ *) lt_cv_sys_path_separator=':' ;;
+ esac
+ PATH_SEPARATOR=$lt_cv_sys_path_separator
+fi
+
+
+# Check that we are running under the correct shell.
+SHELL=${CONFIG_SHELL-/bin/sh}
+
+case X$ECHO in
+X*--fallback-echo)
+ # Remove one level of quotation (which was required for Make).
+ ECHO=`echo "$ECHO" | sed 's,\\\\\$\\$0,'$0','`
+ ;;
+esac
+
+echo=${ECHO-echo}
+if test "X$1" = X--no-reexec; then
+ # Discard the --no-reexec flag, and continue.
+ shift
+elif test "X$1" = X--fallback-echo; then
+ # Avoid inline document here, it may be left over
+ :
+elif test "X`($echo '\t') 2>/dev/null`" = 'X\t'; then
+ # Yippee, $echo works!
+ :
+else
+ # Restart under the correct shell.
+ exec $SHELL "$0" --no-reexec ${1+"$@"}
+fi
+
+if test "X$1" = X--fallback-echo; then
+ # used as fallback echo
+ shift
+ cat <<EOF
+
+EOF
+ exit 0
+fi
+
+# The HP-UX ksh and POSIX shell print the target directory to stdout
+# if CDPATH is set.
+if test "X${CDPATH+set}" = Xset; then CDPATH=:; export CDPATH; fi
+
+if test -z "$ECHO"; then
+if test "X${echo_test_string+set}" != Xset; then
+# find a string as large as possible, as long as the shell can cope with it
+ for cmd in 'sed 50q "$0"' 'sed 20q "$0"' 'sed 10q "$0"' 'sed 2q "$0"' 'echo test'; do
+ # expected sizes: less than 2Kb, 1Kb, 512 bytes, 16 bytes, ...
+ if (echo_test_string="`eval $cmd`") 2>/dev/null &&
+ echo_test_string="`eval $cmd`" &&
+ (test "X$echo_test_string" = "X$echo_test_string") 2>/dev/null
+ then
+ break
+ fi
+ done
+fi
+
+if test "X`($echo '\t') 2>/dev/null`" = 'X\t' &&
+ echo_testing_string=`($echo "$echo_test_string") 2>/dev/null` &&
+ test "X$echo_testing_string" = "X$echo_test_string"; then
+ :
+else
+ # The Solaris, AIX, and Digital Unix default echo programs unquote
+ # backslashes. This makes it impossible to quote backslashes using
+ # echo "$something" | sed 's/\\/\\\\/g'
+ #
+ # So, first we look for a working echo in the user's PATH.
+
+ IFS="${IFS= }"; save_ifs="$IFS"; IFS=$PATH_SEPARATOR
+ for dir in $PATH /usr/ucb; do
+ if (test -f $dir/echo || test -f $dir/echo$ac_exeext) &&
+ test "X`($dir/echo '\t') 2>/dev/null`" = 'X\t' &&
+ echo_testing_string=`($dir/echo "$echo_test_string") 2>/dev/null` &&
+ test "X$echo_testing_string" = "X$echo_test_string"; then
+ echo="$dir/echo"
+ break
+ fi
+ done
+ IFS="$save_ifs"
+
+ if test "X$echo" = Xecho; then
+ # We didn't find a better echo, so look for alternatives.
+ if test "X`(print -r '\t') 2>/dev/null`" = 'X\t' &&
+ echo_testing_string=`(print -r "$echo_test_string") 2>/dev/null` &&
+ test "X$echo_testing_string" = "X$echo_test_string"; then
+ # This shell has a builtin print -r that does the trick.
+ echo='print -r'
+ elif (test -f /bin/ksh || test -f /bin/ksh$ac_exeext) &&
+ test "X$CONFIG_SHELL" != X/bin/ksh; then
+ # If we have ksh, try running configure again with it.
+ ORIGINAL_CONFIG_SHELL=${CONFIG_SHELL-/bin/sh}
+ export ORIGINAL_CONFIG_SHELL
+ CONFIG_SHELL=/bin/ksh
+ export CONFIG_SHELL
+ exec $CONFIG_SHELL "$0" --no-reexec ${1+"$@"}
+ else
+ # Try using printf.
+ echo='printf %s\n'
+ if test "X`($echo '\t') 2>/dev/null`" = 'X\t' &&
+ echo_testing_string=`($echo "$echo_test_string") 2>/dev/null` &&
+ test "X$echo_testing_string" = "X$echo_test_string"; then
+ # Cool, printf works
+ :
+ elif echo_testing_string=`($ORIGINAL_CONFIG_SHELL "$0" --fallback-echo '\t') 2>/dev/null` &&
+ test "X$echo_testing_string" = 'X\t' &&
+ echo_testing_string=`($ORIGINAL_CONFIG_SHELL "$0" --fallback-echo "$echo_test_string") 2>/dev/null` &&
+ test "X$echo_testing_string" = "X$echo_test_string"; then
+ CONFIG_SHELL=$ORIGINAL_CONFIG_SHELL
+ export CONFIG_SHELL
+ SHELL="$CONFIG_SHELL"
+ export SHELL
+ echo="$CONFIG_SHELL $0 --fallback-echo"
+ elif echo_testing_string=`($CONFIG_SHELL "$0" --fallback-echo '\t') 2>/dev/null` &&
+ test "X$echo_testing_string" = 'X\t' &&
+ echo_testing_string=`($CONFIG_SHELL "$0" --fallback-echo "$echo_test_string") 2>/dev/null` &&
+ test "X$echo_testing_string" = "X$echo_test_string"; then
+ echo="$CONFIG_SHELL $0 --fallback-echo"
+ else
+ # maybe with a smaller string...
+ prev=:
+
+ for cmd in 'echo test' 'sed 2q "$0"' 'sed 10q "$0"' 'sed 20q "$0"' 'sed 50q "$0"'; do
+ if (test "X$echo_test_string" = "X`eval $cmd`") 2>/dev/null
+ then
+ break
+ fi
+ prev="$cmd"
+ done
+
+ if test "$prev" != 'sed 50q "$0"'; then
+ echo_test_string=`eval $prev`
+ export echo_test_string
+ exec ${ORIGINAL_CONFIG_SHELL-${CONFIG_SHELL-/bin/sh}} "$0" ${1+"$@"}
+ else
+ # Oops. We lost completely, so just stick with echo.
+ echo=echo
+ fi
+ fi
+ fi
+ fi
+fi
+fi
+
+# Copy echo and quote the copy suitably for passing to libtool from
+# the Makefile, instead of quoting the original, which is used later.
+ECHO=$echo
+if test "X$ECHO" = "X$CONFIG_SHELL $0 --fallback-echo"; then
+ ECHO="$CONFIG_SHELL \\\$\$0 --fallback-echo"
+fi
+
+
+ac_help="$ac_help
+ --disable-libtool-lock avoid locking (might break parallel builds)"
+ac_help="$ac_help
+ --with-pic try to use only PIC/non-PIC objects [default=use both]"
+ac_help="$ac_help
+ --with-control-dir=PATH Path for control programs [PREFIX/bin/control]"
+ac_help="$ac_help
+ --with-db-dir=PATH Path for news database files [PREFIX/db]"
+ac_help="$ac_help
+ --with-doc-dir=PATH Path for news documentation [PREFIX/doc]"
+ac_help="$ac_help
+ --with-etc-dir=PATH Path for news config files [PREFIX/etc]"
+ac_help="$ac_help
+ --with-filter-dir=PATH Path for embedded filters [PREFIX/bin/filter]"
+ac_help="$ac_help
+ --with-lib-dir=PATH Path for news library files [PREFIX/lib]"
+ac_help="$ac_help
+ --with-log-dir=PATH Path for news logs [PREFIX/log]"
+ac_help="$ac_help
+ --with-run-dir=PATH Path for news PID/runtime files [PREFIX/run]"
+ac_help="$ac_help
+ --with-spool-dir=PATH Path for news storage [PREFIX/spool]"
+ac_help="$ac_help
+ --with-tmp-dir=PATH Path for temporary files [PREFIX/tmp]"
+ac_help="$ac_help
+ --with-syslog-facility=LOG_FAC Syslog facility [LOG_NEWS or LOG_LOCAL1]"
+ac_help="$ac_help
+ --with-news-user=USER News user name [news]"
+ac_help="$ac_help
+ --with-news-group=GROUP News group name [news]"
+ac_help="$ac_help
+ --with-news-master=USER News master (address for reports) [usenet]"
+ac_help="$ac_help
+ --with-news-umask=UMASK umask for news files [002]"
+ac_help="$ac_help
+ --enable-setgid-inews Install inews setgid"
+ac_help="$ac_help
+ --enable-uucp-rnews Install rnews setuid, group uucp"
+ac_help="$ac_help
+ --with-log-compress=METHOD Log compression method [gzip]"
+ac_help="$ac_help
+ --with-innd-port=PORT Additional low-numbered port for inndstart"
+ac_help="$ac_help
+ --enable-ipv6 Enable IPv6 support"
+ac_help="$ac_help
+ --with-max-sockets=N Maximum number of listening sockets in innd"
+ac_help="$ac_help
+ --enable-tagged-hash Use tagged hash table for history"
+ac_help="$ac_help
+ --enable-keywords Automatic keyword generation support"
+ac_help="$ac_help
+ --enable-largefiles Support for files larger than 2GB [default=no]"
+ac_help="$ac_help
+ --with-sendmail=PATH Path to sendmail"
+ac_help="$ac_help
+ --with-kerberos=PATH Path to Kerberos v5 (for auth_krb5)"
+ac_help="$ac_help
+ --with-perl Embedded Perl script support [default=no]"
+ac_help="$ac_help
+ --with-python Embedded Python module support [default=no]"
+ac_help="$ac_help
+ --with-berkeleydb[=PATH] Enable BerkeleyDB (for ovdb overview method)"
+ac_help="$ac_help
+ --with-openssl=PATH Enable OpenSSL (for NNTP over SSL support)"
+ac_help="$ac_help
+ --with-sasl=PATH Enable SASL (for imapfeed authentication)"
+
+# Initialize some variables set by options.
+# The variables have the same names as the options, with
+# dashes changed to underlines.
+build=NONE
+cache_file=./config.cache
+exec_prefix=NONE
+host=NONE
+no_create=
+nonopt=NONE
+no_recursion=
+prefix=NONE
+program_prefix=NONE
+program_suffix=NONE
+program_transform_name=s,x,x,
+silent=
+site=
+srcdir=
+target=NONE
+verbose=
+x_includes=NONE
+x_libraries=NONE
+bindir='${exec_prefix}/bin'
+sbindir='${exec_prefix}/sbin'
+libexecdir='${exec_prefix}/libexec'
+datadir='${prefix}/share'
+sysconfdir='${prefix}/etc'
+sharedstatedir='${prefix}/com'
+localstatedir='${prefix}/var'
+libdir='${exec_prefix}/lib'
+includedir='${prefix}/include'
+oldincludedir='/usr/include'
+infodir='${prefix}/info'
+mandir='${prefix}/man'
+
+# Initialize some other variables.
+subdirs=
+MFLAGS= MAKEFLAGS=
+SHELL=${CONFIG_SHELL-/bin/sh}
+# Maximum number of lines to put in a shell here document.
+ac_max_here_lines=12
+
+ac_prev=
+for ac_option
+do
+
+ # If the previous option needs an argument, assign it.
+ if test -n "$ac_prev"; then
+ eval "$ac_prev=\$ac_option"
+ ac_prev=
+ continue
+ fi
+
+ case "$ac_option" in
+ -*=*) ac_optarg=`echo "$ac_option" | sed 's/[-_a-zA-Z0-9]*=//'` ;;
+ *) ac_optarg= ;;
+ esac
+
+ # Accept the important Cygnus configure options, so we can diagnose typos.
+
+ case "$ac_option" in
+
+ -bindir | --bindir | --bindi | --bind | --bin | --bi)
+ ac_prev=bindir ;;
+ -bindir=* | --bindir=* | --bindi=* | --bind=* | --bin=* | --bi=*)
+ bindir="$ac_optarg" ;;
+
+ -build | --build | --buil | --bui | --bu)
+ ac_prev=build ;;
+ -build=* | --build=* | --buil=* | --bui=* | --bu=*)
+ build="$ac_optarg" ;;
+
+ -cache-file | --cache-file | --cache-fil | --cache-fi \
+ | --cache-f | --cache- | --cache | --cach | --cac | --ca | --c)
+ ac_prev=cache_file ;;
+ -cache-file=* | --cache-file=* | --cache-fil=* | --cache-fi=* \
+ | --cache-f=* | --cache-=* | --cache=* | --cach=* | --cac=* | --ca=* | --c=*)
+ cache_file="$ac_optarg" ;;
+
+ -datadir | --datadir | --datadi | --datad | --data | --dat | --da)
+ ac_prev=datadir ;;
+ -datadir=* | --datadir=* | --datadi=* | --datad=* | --data=* | --dat=* \
+ | --da=*)
+ datadir="$ac_optarg" ;;
+
+ -disable-* | --disable-*)
+ ac_feature=`echo $ac_option|sed -e 's/-*disable-//'`
+ # Reject names that are not valid shell variable names.
+ if test -n "`echo $ac_feature| sed 's/[-a-zA-Z0-9_]//g'`"; then
+ { echo "configure: error: $ac_feature: invalid feature name" 1>&2; exit 1; }
+ fi
+ ac_feature=`echo $ac_feature| sed 's/-/_/g'`
+ eval "enable_${ac_feature}=no" ;;
+
+ -enable-* | --enable-*)
+ ac_feature=`echo $ac_option|sed -e 's/-*enable-//' -e 's/=.*//'`
+ # Reject names that are not valid shell variable names.
+ if test -n "`echo $ac_feature| sed 's/[-_a-zA-Z0-9]//g'`"; then
+ { echo "configure: error: $ac_feature: invalid feature name" 1>&2; exit 1; }
+ fi
+ ac_feature=`echo $ac_feature| sed 's/-/_/g'`
+ case "$ac_option" in
+ *=*) ;;
+ *) ac_optarg=yes ;;
+ esac
+ eval "enable_${ac_feature}='$ac_optarg'" ;;
+
+ -exec-prefix | --exec_prefix | --exec-prefix | --exec-prefi \
+ | --exec-pref | --exec-pre | --exec-pr | --exec-p | --exec- \
+ | --exec | --exe | --ex)
+ ac_prev=exec_prefix ;;
+ -exec-prefix=* | --exec_prefix=* | --exec-prefix=* | --exec-prefi=* \
+ | --exec-pref=* | --exec-pre=* | --exec-pr=* | --exec-p=* | --exec-=* \
+ | --exec=* | --exe=* | --ex=*)
+ exec_prefix="$ac_optarg" ;;
+
+ -gas | --gas | --ga | --g)
+ # Obsolete; use --with-gas.
+ with_gas=yes ;;
+
+ -help | --help | --hel | --he)
+ # Omit some internal or obsolete options to make the list less imposing.
+ # This message is too long to be a string in the A/UX 3.1 sh.
+ cat << EOF
+Usage: configure [options] [host]
+Options: [defaults in brackets after descriptions]
+Configuration:
+ --cache-file=FILE cache test results in FILE
+ --help print this message
+ --no-create do not create output files
+ --quiet, --silent do not print \`checking...' messages
+ --version print the version of autoconf that created configure
+Directory and file names:
+ --prefix=PREFIX install architecture-independent files in PREFIX
+ [$ac_default_prefix]
+ --exec-prefix=EPREFIX install architecture-dependent files in EPREFIX
+ [same as prefix]
+ --bindir=DIR user executables in DIR [EPREFIX/bin]
+ --sbindir=DIR system admin executables in DIR [EPREFIX/sbin]
+ --libexecdir=DIR program executables in DIR [EPREFIX/libexec]
+ --datadir=DIR read-only architecture-independent data in DIR
+ [PREFIX/share]
+ --sysconfdir=DIR read-only single-machine data in DIR [PREFIX/etc]
+ --sharedstatedir=DIR modifiable architecture-independent data in DIR
+ [PREFIX/com]
+ --localstatedir=DIR modifiable single-machine data in DIR [PREFIX/var]
+ --libdir=DIR object code libraries in DIR [EPREFIX/lib]
+ --includedir=DIR C header files in DIR [PREFIX/include]
+ --oldincludedir=DIR C header files for non-gcc in DIR [/usr/include]
+ --infodir=DIR info documentation in DIR [PREFIX/info]
+ --mandir=DIR man documentation in DIR [PREFIX/man]
+ --srcdir=DIR find the sources in DIR [configure dir or ..]
+ --program-prefix=PREFIX prepend PREFIX to installed program names
+ --program-suffix=SUFFIX append SUFFIX to installed program names
+ --program-transform-name=PROGRAM
+ run sed PROGRAM on installed program names
+EOF
+ cat << EOF
+Host type:
+ --build=BUILD configure for building on BUILD [BUILD=HOST]
+ --host=HOST configure for HOST [guessed]
+ --target=TARGET configure for TARGET [TARGET=HOST]
+Features and packages:
+ --disable-FEATURE do not include FEATURE (same as --enable-FEATURE=no)
+ --enable-FEATURE[=ARG] include FEATURE [ARG=yes]
+ --with-PACKAGE[=ARG] use PACKAGE [ARG=yes]
+ --without-PACKAGE do not use PACKAGE (same as --with-PACKAGE=no)
+ --x-includes=DIR X include files are in DIR
+ --x-libraries=DIR X library files are in DIR
+EOF
+ if test -n "$ac_help"; then
+ echo "--enable and --with options recognized:$ac_help"
+ fi
+ exit 0 ;;
+
+ -host | --host | --hos | --ho)
+ ac_prev=host ;;
+ -host=* | --host=* | --hos=* | --ho=*)
+ host="$ac_optarg" ;;
+
+ -includedir | --includedir | --includedi | --included | --include \
+ | --includ | --inclu | --incl | --inc)
+ ac_prev=includedir ;;
+ -includedir=* | --includedir=* | --includedi=* | --included=* | --include=* \
+ | --includ=* | --inclu=* | --incl=* | --inc=*)
+ includedir="$ac_optarg" ;;
+
+ -infodir | --infodir | --infodi | --infod | --info | --inf)
+ ac_prev=infodir ;;
+ -infodir=* | --infodir=* | --infodi=* | --infod=* | --info=* | --inf=*)
+ infodir="$ac_optarg" ;;
+
+ -libdir | --libdir | --libdi | --libd)
+ ac_prev=libdir ;;
+ -libdir=* | --libdir=* | --libdi=* | --libd=*)
+ libdir="$ac_optarg" ;;
+
+ -libexecdir | --libexecdir | --libexecdi | --libexecd | --libexec \
+ | --libexe | --libex | --libe)
+ ac_prev=libexecdir ;;
+ -libexecdir=* | --libexecdir=* | --libexecdi=* | --libexecd=* | --libexec=* \
+ | --libexe=* | --libex=* | --libe=*)
+ libexecdir="$ac_optarg" ;;
+
+ -localstatedir | --localstatedir | --localstatedi | --localstated \
+ | --localstate | --localstat | --localsta | --localst \
+ | --locals | --local | --loca | --loc | --lo)
+ ac_prev=localstatedir ;;
+ -localstatedir=* | --localstatedir=* | --localstatedi=* | --localstated=* \
+ | --localstate=* | --localstat=* | --localsta=* | --localst=* \
+ | --locals=* | --local=* | --loca=* | --loc=* | --lo=*)
+ localstatedir="$ac_optarg" ;;
+
+ -mandir | --mandir | --mandi | --mand | --man | --ma | --m)
+ ac_prev=mandir ;;
+ -mandir=* | --mandir=* | --mandi=* | --mand=* | --man=* | --ma=* | --m=*)
+ mandir="$ac_optarg" ;;
+
+ -nfp | --nfp | --nf)
+ # Obsolete; use --without-fp.
+ with_fp=no ;;
+
+ -no-create | --no-create | --no-creat | --no-crea | --no-cre \
+ | --no-cr | --no-c)
+ no_create=yes ;;
+
+ -no-recursion | --no-recursion | --no-recursio | --no-recursi \
+ | --no-recurs | --no-recur | --no-recu | --no-rec | --no-re | --no-r)
+ no_recursion=yes ;;
+
+ -oldincludedir | --oldincludedir | --oldincludedi | --oldincluded \
+ | --oldinclude | --oldinclud | --oldinclu | --oldincl | --oldinc \
+ | --oldin | --oldi | --old | --ol | --o)
+ ac_prev=oldincludedir ;;
+ -oldincludedir=* | --oldincludedir=* | --oldincludedi=* | --oldincluded=* \
+ | --oldinclude=* | --oldinclud=* | --oldinclu=* | --oldincl=* | --oldinc=* \
+ | --oldin=* | --oldi=* | --old=* | --ol=* | --o=*)
+ oldincludedir="$ac_optarg" ;;
+
+ -prefix | --prefix | --prefi | --pref | --pre | --pr | --p)
+ ac_prev=prefix ;;
+ -prefix=* | --prefix=* | --prefi=* | --pref=* | --pre=* | --pr=* | --p=*)
+ prefix="$ac_optarg" ;;
+
+ -program-prefix | --program-prefix | --program-prefi | --program-pref \
+ | --program-pre | --program-pr | --program-p)
+ ac_prev=program_prefix ;;
+ -program-prefix=* | --program-prefix=* | --program-prefi=* \
+ | --program-pref=* | --program-pre=* | --program-pr=* | --program-p=*)
+ program_prefix="$ac_optarg" ;;
+
+ -program-suffix | --program-suffix | --program-suffi | --program-suff \
+ | --program-suf | --program-su | --program-s)
+ ac_prev=program_suffix ;;
+ -program-suffix=* | --program-suffix=* | --program-suffi=* \
+ | --program-suff=* | --program-suf=* | --program-su=* | --program-s=*)
+ program_suffix="$ac_optarg" ;;
+
+ -program-transform-name | --program-transform-name \
+ | --program-transform-nam | --program-transform-na \
+ | --program-transform-n | --program-transform- \
+ | --program-transform | --program-transfor \
+ | --program-transfo | --program-transf \
+ | --program-trans | --program-tran \
+ | --progr-tra | --program-tr | --program-t)
+ ac_prev=program_transform_name ;;
+ -program-transform-name=* | --program-transform-name=* \
+ | --program-transform-nam=* | --program-transform-na=* \
+ | --program-transform-n=* | --program-transform-=* \
+ | --program-transform=* | --program-transfor=* \
+ | --program-transfo=* | --program-transf=* \
+ | --program-trans=* | --program-tran=* \
+ | --progr-tra=* | --program-tr=* | --program-t=*)
+ program_transform_name="$ac_optarg" ;;
+
+ -q | -quiet | --quiet | --quie | --qui | --qu | --q \
+ | -silent | --silent | --silen | --sile | --sil)
+ silent=yes ;;
+
+ -sbindir | --sbindir | --sbindi | --sbind | --sbin | --sbi | --sb)
+ ac_prev=sbindir ;;
+ -sbindir=* | --sbindir=* | --sbindi=* | --sbind=* | --sbin=* \
+ | --sbi=* | --sb=*)
+ sbindir="$ac_optarg" ;;
+
+ -sharedstatedir | --sharedstatedir | --sharedstatedi \
+ | --sharedstated | --sharedstate | --sharedstat | --sharedsta \
+ | --sharedst | --shareds | --shared | --share | --shar \
+ | --sha | --sh)
+ ac_prev=sharedstatedir ;;
+ -sharedstatedir=* | --sharedstatedir=* | --sharedstatedi=* \
+ | --sharedstated=* | --sharedstate=* | --sharedstat=* | --sharedsta=* \
+ | --sharedst=* | --shareds=* | --shared=* | --share=* | --shar=* \
+ | --sha=* | --sh=*)
+ sharedstatedir="$ac_optarg" ;;
+
+ -site | --site | --sit)
+ ac_prev=site ;;
+ -site=* | --site=* | --sit=*)
+ site="$ac_optarg" ;;
+
+ -srcdir | --srcdir | --srcdi | --srcd | --src | --sr)
+ ac_prev=srcdir ;;
+ -srcdir=* | --srcdir=* | --srcdi=* | --srcd=* | --src=* | --sr=*)
+ srcdir="$ac_optarg" ;;
+
+ -sysconfdir | --sysconfdir | --sysconfdi | --sysconfd | --sysconf \
+ | --syscon | --sysco | --sysc | --sys | --sy)
+ ac_prev=sysconfdir ;;
+ -sysconfdir=* | --sysconfdir=* | --sysconfdi=* | --sysconfd=* | --sysconf=* \
+ | --syscon=* | --sysco=* | --sysc=* | --sys=* | --sy=*)
+ sysconfdir="$ac_optarg" ;;
+
+ -target | --target | --targe | --targ | --tar | --ta | --t)
+ ac_prev=target ;;
+ -target=* | --target=* | --targe=* | --targ=* | --tar=* | --ta=* | --t=*)
+ target="$ac_optarg" ;;
+
+ -v | -verbose | --verbose | --verbos | --verbo | --verb)
+ verbose=yes ;;
+
+ -version | --version | --versio | --versi | --vers)
+ echo "configure generated by autoconf version 2.13"
+ exit 0 ;;
+
+ -with-* | --with-*)
+ ac_package=`echo $ac_option|sed -e 's/-*with-//' -e 's/=.*//'`
+ # Reject names that are not valid shell variable names.
+ if test -n "`echo $ac_package| sed 's/[-_a-zA-Z0-9]//g'`"; then
+ { echo "configure: error: $ac_package: invalid package name" 1>&2; exit 1; }
+ fi
+ ac_package=`echo $ac_package| sed 's/-/_/g'`
+ case "$ac_option" in
+ *=*) ;;
+ *) ac_optarg=yes ;;
+ esac
+ eval "with_${ac_package}='$ac_optarg'" ;;
+
+ -without-* | --without-*)
+ ac_package=`echo $ac_option|sed -e 's/-*without-//'`
+ # Reject names that are not valid shell variable names.
+ if test -n "`echo $ac_package| sed 's/[-a-zA-Z0-9_]//g'`"; then
+ { echo "configure: error: $ac_package: invalid package name" 1>&2; exit 1; }
+ fi
+ ac_package=`echo $ac_package| sed 's/-/_/g'`
+ eval "with_${ac_package}=no" ;;
+
+ --x)
+ # Obsolete; use --with-x.
+ with_x=yes ;;
+
+ -x-includes | --x-includes | --x-include | --x-includ | --x-inclu \
+ | --x-incl | --x-inc | --x-in | --x-i)
+ ac_prev=x_includes ;;
+ -x-includes=* | --x-includes=* | --x-include=* | --x-includ=* | --x-inclu=* \
+ | --x-incl=* | --x-inc=* | --x-in=* | --x-i=*)
+ x_includes="$ac_optarg" ;;
+
+ -x-libraries | --x-libraries | --x-librarie | --x-librari \
+ | --x-librar | --x-libra | --x-libr | --x-lib | --x-li | --x-l)
+ ac_prev=x_libraries ;;
+ -x-libraries=* | --x-libraries=* | --x-librarie=* | --x-librari=* \
+ | --x-librar=* | --x-libra=* | --x-libr=* | --x-lib=* | --x-li=* | --x-l=*)
+ x_libraries="$ac_optarg" ;;
+
+ -*) { echo "configure: error: $ac_option: invalid option; use --help to show usage" 1>&2; exit 1; }
+ ;;
+
+ *)
+ if test -n "`echo $ac_option| sed 's/[-a-z0-9.]//g'`"; then
+ echo "configure: warning: $ac_option: invalid host type" 1>&2
+ fi
+ if test "x$nonopt" != xNONE; then
+ { echo "configure: error: can only configure for one host and one target at a time" 1>&2; exit 1; }
+ fi
+ nonopt="$ac_option"
+ ;;
+
+ esac
+done
+
+if test -n "$ac_prev"; then
+ { echo "configure: error: missing argument to --`echo $ac_prev | sed 's/_/-/g'`" 1>&2; exit 1; }
+fi
+
+trap 'rm -fr conftest* confdefs* core core.* *.core $ac_clean_files; exit 1' 1 2 15
+
+# File descriptor usage:
+# 0 standard input
+# 1 file creation
+# 2 errors and warnings
+# 3 some systems may open it to /dev/tty
+# 4 used on the Kubota Titan
+# 6 checking for... messages and results
+# 5 compiler messages saved in config.log
+if test "$silent" = yes; then
+ exec 6>/dev/null
+else
+ exec 6>&1
+fi
+exec 5>./config.log
+
+echo "\
+This file contains any messages produced by compilers while
+running configure, to aid debugging if configure makes a mistake.
+" 1>&5
+
+# Strip out --no-create and --no-recursion so they do not pile up.
+# Also quote any args containing shell metacharacters.
+ac_configure_args=
+for ac_arg
+do
+ case "$ac_arg" in
+ -no-create | --no-create | --no-creat | --no-crea | --no-cre \
+ | --no-cr | --no-c) ;;
+ -no-recursion | --no-recursion | --no-recursio | --no-recursi \
+ | --no-recurs | --no-recur | --no-recu | --no-rec | --no-re | --no-r) ;;
+ *" "*|*" "*|*[\[\]\~\#\$\^\&\*\(\)\{\}\\\|\;\<\>\?]*)
+ ac_configure_args="$ac_configure_args '$ac_arg'" ;;
+ *) ac_configure_args="$ac_configure_args $ac_arg" ;;
+ esac
+done
+
+# NLS nuisances.
+# Only set these to C if already set. These must not be set unconditionally
+# because not all systems understand e.g. LANG=C (notably SCO).
+# Fixing LC_MESSAGES prevents Solaris sh from translating var values in `set'!
+# Non-C LC_CTYPE values break the ctype check.
+if test "${LANG+set}" = set; then LANG=C; export LANG; fi
+if test "${LC_ALL+set}" = set; then LC_ALL=C; export LC_ALL; fi
+if test "${LC_MESSAGES+set}" = set; then LC_MESSAGES=C; export LC_MESSAGES; fi
+if test "${LC_CTYPE+set}" = set; then LC_CTYPE=C; export LC_CTYPE; fi
+
+# confdefs.h avoids OS command line length limits that DEFS can exceed.
+rm -rf conftest* confdefs.h
+# AIX cpp loses on an empty file, so make sure it contains at least a newline.
+echo > confdefs.h
+
+# A filename unique to this package, relative to the directory that
+# configure is in, which we can look for to find out if srcdir is correct.
+ac_unique_file=Makefile.global.in
+
+# Find the source files, if location was not specified.
+if test -z "$srcdir"; then
+ ac_srcdir_defaulted=yes
+ # Try the directory containing this script, then its parent.
+ ac_prog=$0
+ ac_confdir=`echo $ac_prog|sed 's%/[^/][^/]*$%%'`
+ test "x$ac_confdir" = "x$ac_prog" && ac_confdir=.
+ srcdir=$ac_confdir
+ if test ! -r $srcdir/$ac_unique_file; then
+ srcdir=..
+ fi
+else
+ ac_srcdir_defaulted=no
+fi
+if test ! -r $srcdir/$ac_unique_file; then
+ if test "$ac_srcdir_defaulted" = yes; then
+ { echo "configure: error: can not find sources in $ac_confdir or .." 1>&2; exit 1; }
+ else
+ { echo "configure: error: can not find sources in $srcdir" 1>&2; exit 1; }
+ fi
+fi
+srcdir=`echo "${srcdir}" | sed 's%\([^/]\)/*$%\1%'`
+
+# Prefer explicitly selected file to automatically selected ones.
+if test -z "$CONFIG_SITE"; then
+ if test "x$prefix" != xNONE; then
+ CONFIG_SITE="$prefix/share/config.site $prefix/etc/config.site"
+ else
+ CONFIG_SITE="$ac_default_prefix/share/config.site $ac_default_prefix/etc/config.site"
+ fi
+fi
+for ac_site_file in $CONFIG_SITE; do
+ if test -r "$ac_site_file"; then
+ echo "loading site script $ac_site_file"
+ . "$ac_site_file"
+ fi
+done
+
+if test -r "$cache_file"; then
+ echo "loading cache $cache_file"
+ . $cache_file
+else
+ echo "creating cache $cache_file"
+ > $cache_file
+fi
+
+ac_ext=c
+# CFLAGS is not in ac_cpp because -g, -O, etc. are not valid cpp options.
+ac_cpp='$CPP $CPPFLAGS'
+ac_compile='${CC-cc} -c $CFLAGS $CPPFLAGS conftest.$ac_ext 1>&5'
+ac_link='${CC-cc} -o conftest${ac_exeext} $CFLAGS $CPPFLAGS $LDFLAGS conftest.$ac_ext $LIBS 1>&5'
+cross_compiling=$ac_cv_prog_cc_cross
+
+ac_exeext=
+ac_objext=o
+if (echo "testing\c"; echo 1,2,3) | grep c >/dev/null; then
+ # Stardent Vistra SVR4 grep lacks -e, says ghazi@caip.rutgers.edu.
+ if (echo -n testing; echo 1,2,3) | sed s/-n/xn/ | grep xn >/dev/null; then
+ ac_n= ac_c='
+' ac_t=' '
+ else
+ ac_n=-n ac_c= ac_t=
+ fi
+else
+ ac_n= ac_c='\c' ac_t=
+fi
+
+
+ac_aux_dir=
+for ac_dir in support $srcdir/support; do
+ if test -f $ac_dir/install-sh; then
+ ac_aux_dir=$ac_dir
+ ac_install_sh="$ac_aux_dir/install-sh -c"
+ break
+ elif test -f $ac_dir/install.sh; then
+ ac_aux_dir=$ac_dir
+ ac_install_sh="$ac_aux_dir/install.sh -c"
+ break
+ fi
+done
+if test -z "$ac_aux_dir"; then
+ { echo "configure: error: can not find install-sh or install.sh in support $srcdir/support" 1>&2; exit 1; }
+fi
+ac_config_guess=$ac_aux_dir/config.guess
+ac_config_sub=$ac_aux_dir/config.sub
+ac_configure=$ac_aux_dir/configure # This should be Cygnus configure.
+
+
+
+test x"$prefix" = xNONE && prefix="$ac_default_prefix"
+
+builddir=`pwd`
+
+
+if test x"$with_largefiles" != x ; then
+ { echo "configure: error: Use --enable-largefiles instead of --with-largefiles" 1>&2; exit 1; }
+fi
+
+
+
+# Extract the first word of "gcc", so it can be a program name with args.
+set dummy gcc; ac_word=$2
+echo $ac_n "checking for $ac_word""... $ac_c" 1>&6
+echo "configure:966: checking for $ac_word" >&5
+if eval "test \"`echo '$''{'ac_cv_prog_CC'+set}'`\" = set"; then
+ echo $ac_n "(cached) $ac_c" 1>&6
+else
+ if test -n "$CC"; then
+ ac_cv_prog_CC="$CC" # Let the user override the test.
+else
+ IFS="${IFS= }"; ac_save_ifs="$IFS"; IFS=":"
+ ac_dummy="$PATH"
+ for ac_dir in $ac_dummy; do
+ test -z "$ac_dir" && ac_dir=.
+ if test -f $ac_dir/$ac_word; then
+ ac_cv_prog_CC="gcc"
+ break
+ fi
+ done
+ IFS="$ac_save_ifs"
+fi
+fi
+CC="$ac_cv_prog_CC"
+if test -n "$CC"; then
+ echo "$ac_t""$CC" 1>&6
+else
+ echo "$ac_t""no" 1>&6
+fi
+
+if test -z "$CC"; then
+ # Extract the first word of "cc", so it can be a program name with args.
+set dummy cc; ac_word=$2
+echo $ac_n "checking for $ac_word""... $ac_c" 1>&6
+echo "configure:996: checking for $ac_word" >&5
+if eval "test \"`echo '$''{'ac_cv_prog_CC'+set}'`\" = set"; then
+ echo $ac_n "(cached) $ac_c" 1>&6
+else
+ if test -n "$CC"; then
+ ac_cv_prog_CC="$CC" # Let the user override the test.
+else
+ IFS="${IFS= }"; ac_save_ifs="$IFS"; IFS=":"
+ ac_prog_rejected=no
+ ac_dummy="$PATH"
+ for ac_dir in $ac_dummy; do
+ test -z "$ac_dir" && ac_dir=.
+ if test -f $ac_dir/$ac_word; then
+ if test "$ac_dir/$ac_word" = "/usr/ucb/cc"; then
+ ac_prog_rejected=yes
+ continue
+ fi
+ ac_cv_prog_CC="cc"
+ break
+ fi
+ done
+ IFS="$ac_save_ifs"
+if test $ac_prog_rejected = yes; then
+ # We found a bogon in the path, so make sure we never use it.
+ set dummy $ac_cv_prog_CC
+ shift
+ if test $# -gt 0; then
+ # We chose a different compiler from the bogus one.
+ # However, it has the same basename, so the bogon will be chosen
+ # first if we set CC to just the basename; use the full file name.
+ shift
+ set dummy "$ac_dir/$ac_word" "$@"
+ shift
+ ac_cv_prog_CC="$@"
+ fi
+fi
+fi
+fi
+CC="$ac_cv_prog_CC"
+if test -n "$CC"; then
+ echo "$ac_t""$CC" 1>&6
+else
+ echo "$ac_t""no" 1>&6
+fi
+
+ if test -z "$CC"; then
+ case "`uname -s`" in
+ *win32* | *WIN32*)
+ # Extract the first word of "cl", so it can be a program name with args.
+set dummy cl; ac_word=$2
+echo $ac_n "checking for $ac_word""... $ac_c" 1>&6
+echo "configure:1047: checking for $ac_word" >&5
+if eval "test \"`echo '$''{'ac_cv_prog_CC'+set}'`\" = set"; then
+ echo $ac_n "(cached) $ac_c" 1>&6
+else
+ if test -n "$CC"; then
+ ac_cv_prog_CC="$CC" # Let the user override the test.
+else
+ IFS="${IFS= }"; ac_save_ifs="$IFS"; IFS=":"
+ ac_dummy="$PATH"
+ for ac_dir in $ac_dummy; do
+ test -z "$ac_dir" && ac_dir=.
+ if test -f $ac_dir/$ac_word; then
+ ac_cv_prog_CC="cl"
+ break
+ fi
+ done
+ IFS="$ac_save_ifs"
+fi
+fi
+CC="$ac_cv_prog_CC"
+if test -n "$CC"; then
+ echo "$ac_t""$CC" 1>&6
+else
+ echo "$ac_t""no" 1>&6
+fi
+ ;;
+ esac
+ fi
+ test -z "$CC" && { echo "configure: error: no acceptable cc found in \$PATH" 1>&2; exit 1; }
+fi
+
+echo $ac_n "checking whether the C compiler ($CC $CFLAGS $LDFLAGS) works""... $ac_c" 1>&6
+echo "configure:1079: checking whether the C compiler ($CC $CFLAGS $LDFLAGS) works" >&5
+
+ac_ext=c
+# CFLAGS is not in ac_cpp because -g, -O, etc. are not valid cpp options.
+ac_cpp='$CPP $CPPFLAGS'
+ac_compile='${CC-cc} -c $CFLAGS $CPPFLAGS conftest.$ac_ext 1>&5'
+ac_link='${CC-cc} -o conftest${ac_exeext} $CFLAGS $CPPFLAGS $LDFLAGS conftest.$ac_ext $LIBS 1>&5'
+cross_compiling=$ac_cv_prog_cc_cross
+
+cat > conftest.$ac_ext << EOF
+
+#line 1090 "configure"
+#include "confdefs.h"
+
+main(){return(0);}
+EOF
+if { (eval echo configure:1095: \"$ac_link\") 1>&5; (eval $ac_link) 2>&5; } && test -s conftest${ac_exeext}; then
+ ac_cv_prog_cc_works=yes
+ # If we can't run a trivial program, we are probably using a cross compiler.
+ if (./conftest; exit) 2>/dev/null; then
+ ac_cv_prog_cc_cross=no
+ else
+ ac_cv_prog_cc_cross=yes
+ fi
+else
+ echo "configure: failed program was:" >&5
+ cat conftest.$ac_ext >&5
+ ac_cv_prog_cc_works=no
+fi
+rm -fr conftest*
+ac_ext=c
+# CFLAGS is not in ac_cpp because -g, -O, etc. are not valid cpp options.
+ac_cpp='$CPP $CPPFLAGS'
+ac_compile='${CC-cc} -c $CFLAGS $CPPFLAGS conftest.$ac_ext 1>&5'
+ac_link='${CC-cc} -o conftest${ac_exeext} $CFLAGS $CPPFLAGS $LDFLAGS conftest.$ac_ext $LIBS 1>&5'
+cross_compiling=$ac_cv_prog_cc_cross
+
+echo "$ac_t""$ac_cv_prog_cc_works" 1>&6
+if test $ac_cv_prog_cc_works = no; then
+ { echo "configure: error: installation or configuration problem: C compiler cannot create executables." 1>&2; exit 1; }
+fi
+echo $ac_n "checking whether the C compiler ($CC $CFLAGS $LDFLAGS) is a cross-compiler""... $ac_c" 1>&6
+echo "configure:1121: checking whether the C compiler ($CC $CFLAGS $LDFLAGS) is a cross-compiler" >&5
+echo "$ac_t""$ac_cv_prog_cc_cross" 1>&6
+cross_compiling=$ac_cv_prog_cc_cross
+
+echo $ac_n "checking whether we are using GNU C""... $ac_c" 1>&6
+echo "configure:1126: checking whether we are using GNU C" >&5
+if eval "test \"`echo '$''{'ac_cv_prog_gcc'+set}'`\" = set"; then
+ echo $ac_n "(cached) $ac_c" 1>&6
+else
+ cat > conftest.c <<EOF
+#ifdef __GNUC__
+ yes;
+#endif
+EOF
+if { ac_try='${CC-cc} -E conftest.c'; { (eval echo configure:1135: \"$ac_try\") 1>&5; (eval $ac_try) 2>&5; }; } | egrep yes >/dev/null 2>&1; then
+ ac_cv_prog_gcc=yes
+else
+ ac_cv_prog_gcc=no
+fi
+fi
+
+echo "$ac_t""$ac_cv_prog_gcc" 1>&6
+
+if test $ac_cv_prog_gcc = yes; then
+ GCC=yes
+else
+ GCC=
+fi
+
+ac_test_CFLAGS="${CFLAGS+set}"
+ac_save_CFLAGS="$CFLAGS"
+CFLAGS=
+echo $ac_n "checking whether ${CC-cc} accepts -g""... $ac_c" 1>&6
+echo "configure:1154: checking whether ${CC-cc} accepts -g" >&5
+if eval "test \"`echo '$''{'ac_cv_prog_cc_g'+set}'`\" = set"; then
+ echo $ac_n "(cached) $ac_c" 1>&6
+else
+ echo 'void f(){}' > conftest.c
+if test -z "`${CC-cc} -g -c conftest.c 2>&1`"; then
+ ac_cv_prog_cc_g=yes
+else
+ ac_cv_prog_cc_g=no
+fi
+rm -f conftest*
+
+fi
+
+echo "$ac_t""$ac_cv_prog_cc_g" 1>&6
+if test "$ac_test_CFLAGS" = set; then
+ CFLAGS="$ac_save_CFLAGS"
+elif test $ac_cv_prog_cc_g = yes; then
+ if test "$GCC" = yes; then
+ CFLAGS="-g -O2"
+ else
+ CFLAGS="-g"
+ fi
+else
+ if test "$GCC" = yes; then
+ CFLAGS="-O2"
+ else
+ CFLAGS=
+ fi
+fi
+
+echo $ac_n "checking how to run the C preprocessor""... $ac_c" 1>&6
+echo "configure:1186: checking how to run the C preprocessor" >&5
+# On Suns, sometimes $CPP names a directory.
+if test -n "$CPP" && test -d "$CPP"; then
+ CPP=
+fi
+if test -z "$CPP"; then
+if eval "test \"`echo '$''{'ac_cv_prog_CPP'+set}'`\" = set"; then
+ echo $ac_n "(cached) $ac_c" 1>&6
+else
+ # This must be in double quotes, not single quotes, because CPP may get
+ # substituted into the Makefile and "${CC-cc}" will confuse make.
+ CPP="${CC-cc} -E"
+ # On the NeXT, cc -E runs the code through the compiler's parser,
+ # not just through cpp.
+ cat > conftest.$ac_ext <<EOF
+#line 1201 "configure"
+#include "confdefs.h"
+#include <assert.h>
+Syntax Error
+EOF
+ac_try="$ac_cpp conftest.$ac_ext >/dev/null 2>conftest.out"
+{ (eval echo configure:1207: \"$ac_try\") 1>&5; (eval $ac_try) 2>&5; }
+ac_err=`grep -v '^ *+' conftest.out | grep -v "^conftest.${ac_ext}\$"`
+if test -z "$ac_err"; then
+ :
+else
+ echo "$ac_err" >&5
+ echo "configure: failed program was:" >&5
+ cat conftest.$ac_ext >&5
+ rm -rf conftest*
+ CPP="${CC-cc} -E -traditional-cpp"
+ cat > conftest.$ac_ext <<EOF
+#line 1218 "configure"
+#include "confdefs.h"
+#include <assert.h>
+Syntax Error
+EOF
+ac_try="$ac_cpp conftest.$ac_ext >/dev/null 2>conftest.out"
+{ (eval echo configure:1224: \"$ac_try\") 1>&5; (eval $ac_try) 2>&5; }
+ac_err=`grep -v '^ *+' conftest.out | grep -v "^conftest.${ac_ext}\$"`
+if test -z "$ac_err"; then
+ :
+else
+ echo "$ac_err" >&5
+ echo "configure: failed program was:" >&5
+ cat conftest.$ac_ext >&5
+ rm -rf conftest*
+ CPP="${CC-cc} -nologo -E"
+ cat > conftest.$ac_ext <<EOF
+#line 1235 "configure"
+#include "confdefs.h"
+#include <assert.h>
+Syntax Error
+EOF
+ac_try="$ac_cpp conftest.$ac_ext >/dev/null 2>conftest.out"
+{ (eval echo configure:1241: \"$ac_try\") 1>&5; (eval $ac_try) 2>&5; }
+ac_err=`grep -v '^ *+' conftest.out | grep -v "^conftest.${ac_ext}\$"`
+if test -z "$ac_err"; then
+ :
+else
+ echo "$ac_err" >&5
+ echo "configure: failed program was:" >&5
+ cat conftest.$ac_ext >&5
+ rm -rf conftest*
+ CPP=/lib/cpp
+fi
+rm -f conftest*
+fi
+rm -f conftest*
+fi
+rm -f conftest*
+ ac_cv_prog_CPP="$CPP"
+fi
+ CPP="$ac_cv_prog_CPP"
+else
+ ac_cv_prog_CPP="$CPP"
+fi
+echo "$ac_t""$CPP" 1>&6
+
+echo $ac_n "checking for AIX""... $ac_c" 1>&6
+echo "configure:1266: checking for AIX" >&5
+cat > conftest.$ac_ext <<EOF
+#line 1268 "configure"
+#include "confdefs.h"
+#ifdef _AIX
+ yes
+#endif
+
+EOF
+if (eval "$ac_cpp conftest.$ac_ext") 2>&5 |
+ egrep "yes" >/dev/null 2>&1; then
+ rm -rf conftest*
+ echo "$ac_t""yes" 1>&6; cat >> confdefs.h <<\EOF
+#define _ALL_SOURCE 1
+EOF
+
+else
+ rm -rf conftest*
+ echo "$ac_t""no" 1>&6
+fi
+rm -f conftest*
+
+
+echo $ac_n "checking for POSIXized ISC""... $ac_c" 1>&6
+echo "configure:1290: checking for POSIXized ISC" >&5
+if test -d /etc/conf/kconfig.d &&
+ grep _POSIX_VERSION /usr/include/sys/unistd.h >/dev/null 2>&1
+then
+ echo "$ac_t""yes" 1>&6
+ ISC=yes # If later tests want to check for ISC.
+ cat >> confdefs.h <<\EOF
+#define _POSIX_SOURCE 1
+EOF
+
+ if test "$GCC" = yes; then
+ CC="$CC -posix"
+ else
+ CC="$CC -Xp"
+ fi
+else
+ echo "$ac_t""no" 1>&6
+ ISC=
+fi
+
+echo $ac_n "checking for object suffix""... $ac_c" 1>&6
+echo "configure:1311: checking for object suffix" >&5
+if eval "test \"`echo '$''{'ac_cv_objext'+set}'`\" = set"; then
+ echo $ac_n "(cached) $ac_c" 1>&6
+else
+ rm -f conftest*
+echo 'int i = 1;' > conftest.$ac_ext
+if { (eval echo configure:1317: \"$ac_compile\") 1>&5; (eval $ac_compile) 2>&5; }; then
+ for ac_file in conftest.*; do
+ case $ac_file in
+ *.c) ;;
+ *) ac_cv_objext=`echo $ac_file | sed -e s/conftest.//` ;;
+ esac
+ done
+else
+ { echo "configure: error: installation or configuration problem; compiler does not work" 1>&2; exit 1; }
+fi
+rm -f conftest*
+fi
+
+echo "$ac_t""$ac_cv_objext" 1>&6
+OBJEXT=$ac_cv_objext
+ac_objext=$ac_cv_objext
+
+
+echo $ac_n "checking if $CC supports -c -o file.$ac_objext""... $ac_c" 1>&6
+echo "configure:1336: checking if $CC supports -c -o file.$ac_objext" >&5
+if eval "test \"`echo '$''{'inn_cv_compiler_c_o'+set}'`\" = set"; then
+ echo $ac_n "(cached) $ac_c" 1>&6
+else
+ rm -f -r conftest 2>/dev/null
+mkdir conftest
+cd conftest
+echo "int some_variable = 0;" > conftest.$ac_ext
+mkdir out
+# According to Tom Tromey, Ian Lance Taylor reported there are C compilers
+# that will create temporary files in the current directory regardless of
+# the output directory. Thus, making CWD read-only will cause this test
+# to fail, enabling locking or at least warning the user not to do parallel
+# builds.
+chmod -w .
+save_CFLAGS="$CFLAGS"
+CFLAGS="$CFLAGS -o out/conftest2.$ac_objext"
+compiler_c_o=no
+if { (eval $ac_compile) 2> out/conftest.err; } \
+ && test -s out/conftest2.$ac_objext; then
+ # The compiler can only warn and ignore the option if not recognized
+ # So say no if there are warnings
+ if test -s out/conftest.err; then
+ inn_cv_compiler_c_o=no
+ else
+ inn_cv_compiler_c_o=yes
+ fi
+else
+ # Append any errors to the config.log.
+ cat out/conftest.err 1>&5
+ inn_cv_compiler_c_o=no
+fi
+CFLAGS="$save_CFLAGS"
+chmod u+w .
+rm -f conftest* out/*
+rmdir out
+cd ..
+rmdir conftest
+rm -f -r conftest 2>/dev/null
+fi
+
+compiler_c_o=$inn_cv_compiler_c_o
+echo "$ac_t""$compiler_c_o" 1>&6
+
+inn_use_libtool=no
+# Check whether --enable-libtool or --disable-libtool was given.
+if test "${enable_libtool+set}" = set; then
+ enableval="$enable_libtool"
+ if test "$enableval" = yes ; then
+ inn_use_libtool=yes
+ fi
+fi
+
+if test x"$inn_use_libtool" = xyes ; then
+ # Find the correct PATH separator. Usually this is `:', but
+# DJGPP uses `;' like DOS.
+if test "X${PATH_SEPARATOR+set}" != Xset; then
+ UNAME=${UNAME-`uname 2>/dev/null`}
+ case X$UNAME in
+ *-DOS) lt_cv_sys_path_separator=';' ;;
+ *) lt_cv_sys_path_separator=':' ;;
+ esac
+ PATH_SEPARATOR=$lt_cv_sys_path_separator
+fi
+
+echo $ac_n "checking for Cygwin environment""... $ac_c" 1>&6
+echo "configure:1402: checking for Cygwin environment" >&5
+if eval "test \"`echo '$''{'ac_cv_cygwin'+set}'`\" = set"; then
+ echo $ac_n "(cached) $ac_c" 1>&6
+else
+ cat > conftest.$ac_ext <<EOF
+#line 1407 "configure"
+#include "confdefs.h"
+
+int main() {
+
+#ifndef __CYGWIN__
+#define __CYGWIN__ __CYGWIN32__
+#endif
+return __CYGWIN__;
+; return 0; }
+EOF
+if { (eval echo configure:1418: \"$ac_compile\") 1>&5; (eval $ac_compile) 2>&5; }; then
+ rm -rf conftest*
+ ac_cv_cygwin=yes
+else
+ echo "configure: failed program was:" >&5
+ cat conftest.$ac_ext >&5
+ rm -rf conftest*
+ ac_cv_cygwin=no
+fi
+rm -f conftest*
+rm -f conftest*
+fi
+
+echo "$ac_t""$ac_cv_cygwin" 1>&6
+CYGWIN=
+test "$ac_cv_cygwin" = yes && CYGWIN=yes
+echo $ac_n "checking for mingw32 environment""... $ac_c" 1>&6
+echo "configure:1435: checking for mingw32 environment" >&5
+if eval "test \"`echo '$''{'ac_cv_mingw32'+set}'`\" = set"; then
+ echo $ac_n "(cached) $ac_c" 1>&6
+else
+ cat > conftest.$ac_ext <<EOF
+#line 1440 "configure"
+#include "confdefs.h"
+
+int main() {
+return __MINGW32__;
+; return 0; }
+EOF
+if { (eval echo configure:1447: \"$ac_compile\") 1>&5; (eval $ac_compile) 2>&5; }; then
+ rm -rf conftest*
+ ac_cv_mingw32=yes
+else
+ echo "configure: failed program was:" >&5
+ cat conftest.$ac_ext >&5
+ rm -rf conftest*
+ ac_cv_mingw32=no
+fi
+rm -f conftest*
+rm -f conftest*
+fi
+
+echo "$ac_t""$ac_cv_mingw32" 1>&6
+MINGW32=
+test "$ac_cv_mingw32" = yes && MINGW32=yes
+# Check whether --enable-shared or --disable-shared was given.
+if test "${enable_shared+set}" = set; then
+ enableval="$enable_shared"
+ p=${PACKAGE-default}
+case $enableval in
+yes) enable_shared=yes ;;
+no) enable_shared=no ;;
+*)
+ enable_shared=no
+ # Look at the argument we got. We use all the common list separators.
+ IFS="${IFS= }"; ac_save_ifs="$IFS"; IFS="${IFS}:,"
+ for pkg in $enableval; do
+ if test "X$pkg" = "X$p"; then
+ enable_shared=yes
+ fi
+ done
+ IFS="$ac_save_ifs"
+ ;;
+esac
+else
+ enable_shared=yes
+fi
+
+# Check whether --enable-static or --disable-static was given.
+if test "${enable_static+set}" = set; then
+ enableval="$enable_static"
+ p=${PACKAGE-default}
+case $enableval in
+yes) enable_static=yes ;;
+no) enable_static=no ;;
+*)
+ enable_static=no
+ # Look at the argument we got. We use all the common list separators.
+ IFS="${IFS= }"; ac_save_ifs="$IFS"; IFS="${IFS}:,"
+ for pkg in $enableval; do
+ if test "X$pkg" = "X$p"; then
+ enable_static=yes
+ fi
+ done
+ IFS="$ac_save_ifs"
+ ;;
+esac
+else
+ enable_static=yes
+fi
+
+# Check whether --enable-fast-install or --disable-fast-install was given.
+if test "${enable_fast_install+set}" = set; then
+ enableval="$enable_fast_install"
+ p=${PACKAGE-default}
+case $enableval in
+yes) enable_fast_install=yes ;;
+no) enable_fast_install=no ;;
+*)
+ enable_fast_install=no
+ # Look at the argument we got. We use all the common list separators.
+ IFS="${IFS= }"; ac_save_ifs="$IFS"; IFS="${IFS}:,"
+ for pkg in $enableval; do
+ if test "X$pkg" = "X$p"; then
+ enable_fast_install=yes
+ fi
+ done
+ IFS="$ac_save_ifs"
+ ;;
+esac
+else
+ enable_fast_install=yes
+fi
+
+
+# Make sure we can run config.sub.
+if ${CONFIG_SHELL-/bin/sh} $ac_config_sub sun4 >/dev/null 2>&1; then :
+else { echo "configure: error: can not run $ac_config_sub" 1>&2; exit 1; }
+fi
+
+echo $ac_n "checking host system type""... $ac_c" 1>&6
+echo "configure:1539: checking host system type" >&5
+
+host_alias=$host
+case "$host_alias" in
+NONE)
+ case $nonopt in
+ NONE)
+ if host_alias=`${CONFIG_SHELL-/bin/sh} $ac_config_guess`; then :
+ else { echo "configure: error: can not guess host type; you must specify one" 1>&2; exit 1; }
+ fi ;;
+ *) host_alias=$nonopt ;;
+ esac ;;
+esac
+
+host=`${CONFIG_SHELL-/bin/sh} $ac_config_sub $host_alias`
+host_cpu=`echo $host | sed 's/^\([^-]*\)-\([^-]*\)-\(.*\)$/\1/'`
+host_vendor=`echo $host | sed 's/^\([^-]*\)-\([^-]*\)-\(.*\)$/\2/'`
+host_os=`echo $host | sed 's/^\([^-]*\)-\([^-]*\)-\(.*\)$/\3/'`
+echo "$ac_t""$host" 1>&6
+
+echo $ac_n "checking build system type""... $ac_c" 1>&6
+echo "configure:1560: checking build system type" >&5
+
+build_alias=$build
+case "$build_alias" in
+NONE)
+ case $nonopt in
+ NONE) build_alias=$host_alias ;;
+ *) build_alias=$nonopt ;;
+ esac ;;
+esac
+
+build=`${CONFIG_SHELL-/bin/sh} $ac_config_sub $build_alias`
+build_cpu=`echo $build | sed 's/^\([^-]*\)-\([^-]*\)-\(.*\)$/\1/'`
+build_vendor=`echo $build | sed 's/^\([^-]*\)-\([^-]*\)-\(.*\)$/\2/'`
+build_os=`echo $build | sed 's/^\([^-]*\)-\([^-]*\)-\(.*\)$/\3/'`
+echo "$ac_t""$build" 1>&6
+
+# Check whether --with-gnu-ld or --without-gnu-ld was given.
+if test "${with_gnu_ld+set}" = set; then
+ withval="$with_gnu_ld"
+ test "$withval" = no || with_gnu_ld=yes
+else
+ with_gnu_ld=no
+fi
+
+ac_prog=ld
+if test "$GCC" = yes; then
+ # Check if gcc -print-prog-name=ld gives a path.
+ echo $ac_n "checking for ld used by GCC""... $ac_c" 1>&6
+echo "configure:1589: checking for ld used by GCC" >&5
+ case $host in
+ *-*-mingw*)
+ # gcc leaves a trailing carriage return which upsets mingw
+ ac_prog=`($CC -print-prog-name=ld) 2>&5 | tr -d '\015'` ;;
+ *)
+ ac_prog=`($CC -print-prog-name=ld) 2>&5` ;;
+ esac
+ case $ac_prog in
+ # Accept absolute paths.
+ [\\/]* | [A-Za-z]:[\\/]*)
+ re_direlt='/[^/][^/]*/\.\./'
+ # Canonicalize the path of ld
+ ac_prog=`echo $ac_prog| sed 's%\\\\%/%g'`
+ while echo $ac_prog | grep "$re_direlt" > /dev/null 2>&1; do
+ ac_prog=`echo $ac_prog| sed "s%$re_direlt%/%"`
+ done
+ test -z "$LD" && LD="$ac_prog"
+ ;;
+ "")
+ # If it fails, then pretend we aren't using GCC.
+ ac_prog=ld
+ ;;
+ *)
+ # If it is relative, then search for the first ld in PATH.
+ with_gnu_ld=unknown
+ ;;
+ esac
+elif test "$with_gnu_ld" = yes; then
+ echo $ac_n "checking for GNU ld""... $ac_c" 1>&6
+echo "configure:1619: checking for GNU ld" >&5
+else
+ echo $ac_n "checking for non-GNU ld""... $ac_c" 1>&6
+echo "configure:1622: checking for non-GNU ld" >&5
+fi
+if eval "test \"`echo '$''{'lt_cv_path_LD'+set}'`\" = set"; then
+ echo $ac_n "(cached) $ac_c" 1>&6
+else
+ if test -z "$LD"; then
+ IFS="${IFS= }"; ac_save_ifs="$IFS"; IFS=$PATH_SEPARATOR
+ for ac_dir in $PATH; do
+ test -z "$ac_dir" && ac_dir=.
+ if test -f "$ac_dir/$ac_prog" || test -f "$ac_dir/$ac_prog$ac_exeext"; then
+ lt_cv_path_LD="$ac_dir/$ac_prog"
+ # Check to see if the program is GNU ld. I'd rather use --version,
+ # but apparently some GNU ld's only accept -v.
+ # Break only if it was the GNU/non-GNU ld that we prefer.
+ if "$lt_cv_path_LD" -v 2>&1 < /dev/null | egrep '(GNU|with BFD)' > /dev/null; then
+ test "$with_gnu_ld" != no && break
+ else
+ test "$with_gnu_ld" != yes && break
+ fi
+ fi
+ done
+ IFS="$ac_save_ifs"
+else
+ lt_cv_path_LD="$LD" # Let the user override the test with a path.
+fi
+fi
+
+LD="$lt_cv_path_LD"
+if test -n "$LD"; then
+ echo "$ac_t""$LD" 1>&6
+else
+ echo "$ac_t""no" 1>&6
+fi
+test -z "$LD" && { echo "configure: error: no acceptable ld found in \$PATH" 1>&2; exit 1; }
+echo $ac_n "checking if the linker ($LD) is GNU ld""... $ac_c" 1>&6
+echo "configure:1657: checking if the linker ($LD) is GNU ld" >&5
+if eval "test \"`echo '$''{'lt_cv_prog_gnu_ld'+set}'`\" = set"; then
+ echo $ac_n "(cached) $ac_c" 1>&6
+else
+ # I'd rather use --version here, but apparently some GNU ld's only accept -v.
+if $LD -v 2>&1 </dev/null | egrep '(GNU|with BFD)' 1>&5; then
+ lt_cv_prog_gnu_ld=yes
+else
+ lt_cv_prog_gnu_ld=no
+fi
+fi
+
+echo "$ac_t""$lt_cv_prog_gnu_ld" 1>&6
+with_gnu_ld=$lt_cv_prog_gnu_ld
+
+
+echo $ac_n "checking for $LD option to reload object files""... $ac_c" 1>&6
+echo "configure:1674: checking for $LD option to reload object files" >&5
+if eval "test \"`echo '$''{'lt_cv_ld_reload_flag'+set}'`\" = set"; then
+ echo $ac_n "(cached) $ac_c" 1>&6
+else
+ lt_cv_ld_reload_flag='-r'
+fi
+
+echo "$ac_t""$lt_cv_ld_reload_flag" 1>&6
+reload_flag=$lt_cv_ld_reload_flag
+test -n "$reload_flag" && reload_flag=" $reload_flag"
+
+echo $ac_n "checking for BSD-compatible nm""... $ac_c" 1>&6
+echo "configure:1686: checking for BSD-compatible nm" >&5
+if eval "test \"`echo '$''{'lt_cv_path_NM'+set}'`\" = set"; then
+ echo $ac_n "(cached) $ac_c" 1>&6
+else
+ if test -n "$NM"; then
+ # Let the user override the test.
+ lt_cv_path_NM="$NM"
+else
+ IFS="${IFS= }"; ac_save_ifs="$IFS"; IFS=$PATH_SEPARATOR
+ for ac_dir in $PATH /usr/ccs/bin /usr/ucb /bin; do
+ test -z "$ac_dir" && ac_dir=.
+ tmp_nm=$ac_dir/${ac_tool_prefix}nm
+ if test -f $tmp_nm || test -f $tmp_nm$ac_exeext ; then
+ # Check to see if the nm accepts a BSD-compat flag.
+ # Adding the `sed 1q' prevents false positives on HP-UX, which says:
+ # nm: unknown option "B" ignored
+ # Tru64's nm complains that /dev/null is an invalid object file
+ if ($tmp_nm -B /dev/null 2>&1 | sed '1q'; exit 0) | egrep '(/dev/null|Invalid file or object type)' >/dev/null; then
+ lt_cv_path_NM="$tmp_nm -B"
+ break
+ elif ($tmp_nm -p /dev/null 2>&1 | sed '1q'; exit 0) | egrep /dev/null >/dev/null; then
+ lt_cv_path_NM="$tmp_nm -p"
+ break
+ else
+ lt_cv_path_NM=${lt_cv_path_NM="$tmp_nm"} # keep the first match, but
+ continue # so that we can try to find one that supports BSD flags
+ fi
+ fi
+ done
+ IFS="$ac_save_ifs"
+ test -z "$lt_cv_path_NM" && lt_cv_path_NM=nm
+fi
+fi
+
+NM="$lt_cv_path_NM"
+echo "$ac_t""$NM" 1>&6
+
+echo $ac_n "checking whether ln -s works""... $ac_c" 1>&6
+echo "configure:1724: checking whether ln -s works" >&5
+if eval "test \"`echo '$''{'ac_cv_prog_LN_S'+set}'`\" = set"; then
+ echo $ac_n "(cached) $ac_c" 1>&6
+else
+ rm -f conftestdata
+if ln -s X conftestdata 2>/dev/null
+then
+ rm -f conftestdata
+ ac_cv_prog_LN_S="ln -s"
+else
+ ac_cv_prog_LN_S=ln
+fi
+fi
+LN_S="$ac_cv_prog_LN_S"
+if test "$ac_cv_prog_LN_S" = "ln -s"; then
+ echo "$ac_t""yes" 1>&6
+else
+ echo "$ac_t""no" 1>&6
+fi
+
+echo $ac_n "checking how to recognise dependant libraries""... $ac_c" 1>&6
+echo "configure:1745: checking how to recognise dependant libraries" >&5
+if eval "test \"`echo '$''{'lt_cv_deplibs_check_method'+set}'`\" = set"; then
+ echo $ac_n "(cached) $ac_c" 1>&6
+else
+ lt_cv_file_magic_cmd='$MAGIC_CMD'
+lt_cv_file_magic_test_file=
+lt_cv_deplibs_check_method='unknown'
+# Need to set the preceding variable on all platforms that support
+# interlibrary dependencies.
+# 'none' -- dependencies not supported.
+# `unknown' -- same as none, but documents that we really don't know.
+# 'pass_all' -- all dependencies passed with no checks.
+# 'test_compile' -- check by making test program.
+# 'file_magic [[regex]]' -- check by looking for files in library path
+# which responds to the $file_magic_cmd with a given egrep regex.
+# If you have `file' or equivalent on your system and you're not sure
+# whether `pass_all' will *always* work, you probably want this one.
+
+case $host_os in
+aix4* | aix5*)
+ lt_cv_deplibs_check_method=pass_all
+ ;;
+
+beos*)
+ lt_cv_deplibs_check_method=pass_all
+ ;;
+
+bsdi4*)
+ lt_cv_deplibs_check_method='file_magic ELF [0-9][0-9]*-bit [ML]SB (shared object|dynamic lib)'
+ lt_cv_file_magic_cmd='/usr/bin/file -L'
+ lt_cv_file_magic_test_file=/shlib/libc.so
+ ;;
+
+cygwin* | mingw* | pw32*)
+ lt_cv_deplibs_check_method='file_magic file format pei*-i386(.*architecture: i386)?'
+ lt_cv_file_magic_cmd='$OBJDUMP -f'
+ ;;
+
+darwin* | rhapsody*)
+ lt_cv_deplibs_check_method='file_magic Mach-O dynamically linked shared library'
+ lt_cv_file_magic_cmd='/usr/bin/file -L'
+ case "$host_os" in
+ rhapsody* | darwin1.[012])
+ lt_cv_file_magic_test_file=`echo /System/Library/Frameworks/System.framework/Versions/*/System | head -1`
+ ;;
+ *) # Darwin 1.3 on
+ lt_cv_file_magic_test_file='/usr/lib/libSystem.dylib'
+ ;;
+ esac
+ ;;
+
+freebsd*)
+ if echo __ELF__ | $CC -E - | grep __ELF__ > /dev/null; then
+ case $host_cpu in
+ i*86 )
+ # Not sure whether the presence of OpenBSD here was a mistake.
+ # Let's accept both of them until this is cleared up.
+ lt_cv_deplibs_check_method='file_magic (FreeBSD|OpenBSD)/i[3-9]86 (compact )?demand paged shared library'
+ lt_cv_file_magic_cmd=/usr/bin/file
+ lt_cv_file_magic_test_file=`echo /usr/lib/libc.so.*`
+ ;;
+ esac
+ else
+ lt_cv_deplibs_check_method=pass_all
+ fi
+ ;;
+
+gnu*)
+ lt_cv_deplibs_check_method=pass_all
+ ;;
+
+hpux10.20*|hpux11*)
+ lt_cv_deplibs_check_method='file_magic (s[0-9][0-9][0-9]|PA-RISC[0-9].[0-9]) shared library'
+ lt_cv_file_magic_cmd=/usr/bin/file
+ lt_cv_file_magic_test_file=/usr/lib/libc.sl
+ ;;
+
+irix5* | irix6*)
+ case $host_os in
+ irix5*)
+ # this will be overridden with pass_all, but let us keep it just in case
+ lt_cv_deplibs_check_method="file_magic ELF 32-bit MSB dynamic lib MIPS - version 1"
+ ;;
+ *)
+ case $LD in
+ *-32|*"-32 ") libmagic=32-bit;;
+ *-n32|*"-n32 ") libmagic=N32;;
+ *-64|*"-64 ") libmagic=64-bit;;
+ *) libmagic=never-match;;
+ esac
+ # this will be overridden with pass_all, but let us keep it just in case
+ lt_cv_deplibs_check_method="file_magic ELF ${libmagic} MSB mips-[1234] dynamic lib MIPS - version 1"
+ ;;
+ esac
+ lt_cv_file_magic_test_file=`echo /lib${libsuff}/libc.so*`
+ lt_cv_deplibs_check_method=pass_all
+ ;;
+
+# This must be Linux ELF.
+linux-gnu*)
+ case $host_cpu in
+ alpha* | hppa* | i*86 | powerpc* | sparc* | ia64* )
+ lt_cv_deplibs_check_method=pass_all ;;
+ *)
+ # glibc up to 2.1.1 does not perform some relocations on ARM
+ lt_cv_deplibs_check_method='file_magic ELF [0-9][0-9]*-bit [LM]SB (shared object|dynamic lib )' ;;
+ esac
+ lt_cv_file_magic_test_file=`echo /lib/libc.so* /lib/libc-*.so`
+ ;;
+
+netbsd*)
+ if echo __ELF__ | $CC -E - | grep __ELF__ > /dev/null; then
+ lt_cv_deplibs_check_method='match_pattern /lib[^/\.]+\.so\.[0-9]+\.[0-9]+$'
+ else
+ lt_cv_deplibs_check_method='match_pattern /lib[^/\.]+\.so$'
+ fi
+ ;;
+
+newos6*)
+ lt_cv_deplibs_check_method='file_magic ELF [0-9][0-9]*-bit [ML]SB (executable|dynamic lib)'
+ lt_cv_file_magic_cmd=/usr/bin/file
+ lt_cv_file_magic_test_file=/usr/lib/libnls.so
+ ;;
+
+openbsd*)
+ lt_cv_file_magic_cmd=/usr/bin/file
+ lt_cv_file_magic_test_file=`echo /usr/lib/libc.so.*`
+ if test -z "`echo __ELF__ | $CC -E - | grep __ELF__`" || test "$host_os-$host_cpu" = "openbsd2.8-powerpc"; then
+ lt_cv_deplibs_check_method='file_magic ELF [0-9][0-9]*-bit [LM]SB shared object'
+ else
+ lt_cv_deplibs_check_method='file_magic OpenBSD.* shared library'
+ fi
+ ;;
+
+osf3* | osf4* | osf5*)
+ # this will be overridden with pass_all, but let us keep it just in case
+ lt_cv_deplibs_check_method='file_magic COFF format alpha shared library'
+ lt_cv_file_magic_test_file=/shlib/libc.so
+ lt_cv_deplibs_check_method=pass_all
+ ;;
+
+sco3.2v5*)
+ lt_cv_deplibs_check_method=pass_all
+ ;;
+
+solaris*)
+ lt_cv_deplibs_check_method=pass_all
+ lt_cv_file_magic_test_file=/lib/libc.so
+ ;;
+
+sysv5uw[78]* | sysv4*uw2*)
+ lt_cv_deplibs_check_method=pass_all
+ ;;
+
+sysv4 | sysv4.2uw2* | sysv4.3* | sysv5*)
+ case $host_vendor in
+ motorola)
+ lt_cv_deplibs_check_method='file_magic ELF [0-9][0-9]*-bit [ML]SB (shared object|dynamic lib) M[0-9][0-9]* Version [0-9]'
+ lt_cv_file_magic_test_file=`echo /usr/lib/libc.so*`
+ ;;
+ ncr)
+ lt_cv_deplibs_check_method=pass_all
+ ;;
+ sequent)
+ lt_cv_file_magic_cmd='/bin/file'
+ lt_cv_deplibs_check_method='file_magic ELF [0-9][0-9]*-bit [LM]SB (shared object|dynamic lib )'
+ ;;
+ sni)
+ lt_cv_file_magic_cmd='/bin/file'
+ lt_cv_deplibs_check_method="file_magic ELF [0-9][0-9]*-bit [LM]SB dynamic lib"
+ lt_cv_file_magic_test_file=/lib/libc.so
+ ;;
+ esac
+ ;;
+esac
+
+fi
+
+echo "$ac_t""$lt_cv_deplibs_check_method" 1>&6
+file_magic_cmd=$lt_cv_file_magic_cmd
+deplibs_check_method=$lt_cv_deplibs_check_method
+
+
+
+echo $ac_n "checking for executable suffix""... $ac_c" 1>&6
+echo "configure:1930: checking for executable suffix" >&5
+if eval "test \"`echo '$''{'ac_cv_exeext'+set}'`\" = set"; then
+ echo $ac_n "(cached) $ac_c" 1>&6
+else
+ if test "$CYGWIN" = yes || test "$MINGW32" = yes; then
+ ac_cv_exeext=.exe
+else
+ rm -f conftest*
+ echo 'int main () { return 0; }' > conftest.$ac_ext
+ ac_cv_exeext=
+ if { (eval echo configure:1940: \"$ac_link\") 1>&5; (eval $ac_link) 2>&5; }; then
+ for file in conftest.*; do
+ case $file in
+ *.$ac_ext | *.c | *.o | *.obj) ;;
+ *) ac_cv_exeext=`echo $file | sed -e s/conftest//` ;;
+ esac
+ done
+ else
+ { echo "configure: error: installation or configuration problem: compiler cannot create executables." 1>&2; exit 1; }
+ fi
+ rm -f conftest*
+ test x"${ac_cv_exeext}" = x && ac_cv_exeext=no
+fi
+fi
+
+EXEEXT=""
+test x"${ac_cv_exeext}" != xno && EXEEXT=${ac_cv_exeext}
+echo "$ac_t""${ac_cv_exeext}" 1>&6
+ac_exeext=$EXEEXT
+
+if test $host != $build; then
+ ac_tool_prefix=${host_alias}-
+else
+ ac_tool_prefix=
+fi
+
+
+
+
+# Check for command to grab the raw symbol name followed by C symbol from nm.
+echo $ac_n "checking command to parse $NM output""... $ac_c" 1>&6
+echo "configure:1971: checking command to parse $NM output" >&5
+if eval "test \"`echo '$''{'lt_cv_sys_global_symbol_pipe'+set}'`\" = set"; then
+ echo $ac_n "(cached) $ac_c" 1>&6
+else
+
+# These are sane defaults that work on at least a few old systems.
+# [They come from Ultrix. What could be older than Ultrix?!! ;)]
+
+# Character class describing NM global symbol codes.
+symcode='[BCDEGRST]'
+
+# Regexp to match symbols that can be accessed directly from C.
+sympat='\([_A-Za-z][_A-Za-z0-9]*\)'
+
+# Transform the above into a raw symbol and a C symbol.
+symxfrm='\1 \2\3 \3'
+
+# Transform an extracted symbol line into a proper C declaration
+lt_cv_global_symbol_to_cdecl="sed -n -e 's/^. .* \(.*\)$/extern char \1;/p'"
+
+# Transform an extracted symbol line into symbol name and symbol address
+lt_cv_global_symbol_to_c_name_address="sed -n -e 's/^: \([^ ]*\) $/ {\\\"\1\\\", (lt_ptr) 0},/p' -e 's/^$symcode \([^ ]*\) \([^ ]*\)$/ {\"\2\", (lt_ptr) \&\2},/p'"
+
+# Define system-specific variables.
+case $host_os in
+aix*)
+ symcode='[BCDT]'
+ ;;
+cygwin* | mingw* | pw32*)
+ symcode='[ABCDGISTW]'
+ ;;
+hpux*) # Its linker distinguishes data from code symbols
+ lt_cv_global_symbol_to_cdecl="sed -n -e 's/^T .* \(.*\)$/extern char \1();/p' -e 's/^$symcode* .* \(.*\)$/extern char \1;/p'"
+ lt_cv_global_symbol_to_c_name_address="sed -n -e 's/^: \([^ ]*\) $/ {\\\"\1\\\", (lt_ptr) 0},/p' -e 's/^$symcode* \([^ ]*\) \([^ ]*\)$/ {\"\2\", (lt_ptr) \&\2},/p'"
+ ;;
+irix*)
+ symcode='[BCDEGRST]'
+ ;;
+solaris* | sysv5*)
+ symcode='[BDT]'
+ ;;
+sysv4)
+ symcode='[DFNSTU]'
+ ;;
+esac
+
+# Handle CRLF in mingw tool chain
+opt_cr=
+case $host_os in
+mingw*)
+ opt_cr=`echo 'x\{0,1\}' | tr x '\015'` # option cr in regexp
+ ;;
+esac
+
+# If we're using GNU nm, then use its standard symbol codes.
+if $NM -V 2>&1 | egrep '(GNU|with BFD)' > /dev/null; then
+ symcode='[ABCDGISTW]'
+fi
+
+# Try without a prefix undercore, then with it.
+for ac_symprfx in "" "_"; do
+
+ # Write the raw and C identifiers.
+lt_cv_sys_global_symbol_pipe="sed -n -e 's/^.*[ ]\($symcode$symcode*\)[ ][ ]*\($ac_symprfx\)$sympat$opt_cr$/$symxfrm/p'"
+
+ # Check to see that the pipe works correctly.
+ pipe_works=no
+ rm -f conftest*
+ cat > conftest.$ac_ext <<EOF
+#ifdef __cplusplus
+extern "C" {
+#endif
+char nm_test_var;
+void nm_test_func(){}
+#ifdef __cplusplus
+}
+#endif
+int main(){nm_test_var='a';nm_test_func();return(0);}
+EOF
+
+ if { (eval echo configure:2051: \"$ac_compile\") 1>&5; (eval $ac_compile) 2>&5; }; then
+ # Now try to grab the symbols.
+ nlist=conftest.nm
+ if { (eval echo configure:2054: \"$NM conftest.$ac_objext \| $lt_cv_sys_global_symbol_pipe \> $nlist\") 1>&5; (eval $NM conftest.$ac_objext \| $lt_cv_sys_global_symbol_pipe \> $nlist) 2>&5; } && test -s "$nlist"; then
+ # Try sorting and uniquifying the output.
+ if sort "$nlist" | uniq > "$nlist"T; then
+ mv -f "$nlist"T "$nlist"
+ else
+ rm -f "$nlist"T
+ fi
+
+ # Make sure that we snagged all the symbols we need.
+ if egrep ' nm_test_var$' "$nlist" >/dev/null; then
+ if egrep ' nm_test_func$' "$nlist" >/dev/null; then
+ cat <<EOF > conftest.$ac_ext
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+EOF
+ # Now generate the symbol file.
+ eval "$lt_cv_global_symbol_to_cdecl"' < "$nlist" >> conftest.$ac_ext'
+
+ cat <<EOF >> conftest.$ac_ext
+#if defined (__STDC__) && __STDC__
+# define lt_ptr void *
+#else
+# define lt_ptr char *
+# define const
+#endif
+
+/* The mapping between symbol names and symbols. */
+const struct {
+ const char *name;
+ lt_ptr address;
+}
+lt_preloaded_symbols[] =
+{
+EOF
+ sed "s/^$symcode$symcode* \(.*\) \(.*\)$/ {\"\2\", (lt_ptr) \&\2},/" < "$nlist" >> conftest.$ac_ext
+ cat <<\EOF >> conftest.$ac_ext
+ {0, (lt_ptr) 0}
+};
+
+#ifdef __cplusplus
+}
+#endif
+EOF
+ # Now try linking the two files.
+ mv conftest.$ac_objext conftstm.$ac_objext
+ save_LIBS="$LIBS"
+ save_CFLAGS="$CFLAGS"
+ LIBS="conftstm.$ac_objext"
+ CFLAGS="$CFLAGS$no_builtin_flag"
+ if { (eval echo configure:2105: \"$ac_link\") 1>&5; (eval $ac_link) 2>&5; } && test -s conftest; then
+ pipe_works=yes
+ fi
+ LIBS="$save_LIBS"
+ CFLAGS="$save_CFLAGS"
+ else
+ echo "cannot find nm_test_func in $nlist" >&5
+ fi
+ else
+ echo "cannot find nm_test_var in $nlist" >&5
+ fi
+ else
+ echo "cannot run $lt_cv_sys_global_symbol_pipe" >&5
+ fi
+ else
+ echo "$progname: failed program was:" >&5
+ cat conftest.$ac_ext >&5
+ fi
+ rm -f conftest* conftst*
+
+ # Do not use the global_symbol_pipe unless it works.
+ if test "$pipe_works" = yes; then
+ break
+ else
+ lt_cv_sys_global_symbol_pipe=
+ fi
+done
+
+fi
+
+global_symbol_pipe="$lt_cv_sys_global_symbol_pipe"
+if test -z "$lt_cv_sys_global_symbol_pipe"; then
+ global_symbol_to_cdecl=
+ global_symbol_to_c_name_address=
+else
+ global_symbol_to_cdecl="$lt_cv_global_symbol_to_cdecl"
+ global_symbol_to_c_name_address="$lt_cv_global_symbol_to_c_name_address"
+fi
+if test -z "$global_symbol_pipe$global_symbol_to_cdec$global_symbol_to_c_name_address";
+then
+ echo "$ac_t""failed" 1>&6
+else
+ echo "$ac_t""ok" 1>&6
+fi
+
+for ac_hdr in dlfcn.h
+do
+ac_safe=`echo "$ac_hdr" | sed 'y%./+-%__p_%'`
+echo $ac_n "checking for $ac_hdr""... $ac_c" 1>&6
+echo "configure:2154: checking for $ac_hdr" >&5
+if eval "test \"`echo '$''{'ac_cv_header_$ac_safe'+set}'`\" = set"; then
+ echo $ac_n "(cached) $ac_c" 1>&6
+else
+ cat > conftest.$ac_ext <<EOF
+#line 2159 "configure"
+#include "confdefs.h"
+#include <$ac_hdr>
+EOF
+ac_try="$ac_cpp conftest.$ac_ext >/dev/null 2>conftest.out"
+{ (eval echo configure:2164: \"$ac_try\") 1>&5; (eval $ac_try) 2>&5; }
+ac_err=`grep -v '^ *+' conftest.out | grep -v "^conftest.${ac_ext}\$"`
+if test -z "$ac_err"; then
+ rm -rf conftest*
+ eval "ac_cv_header_$ac_safe=yes"
+else
+ echo "$ac_err" >&5
+ echo "configure: failed program was:" >&5
+ cat conftest.$ac_ext >&5
+ rm -rf conftest*
+ eval "ac_cv_header_$ac_safe=no"
+fi
+rm -f conftest*
+fi
+if eval "test \"`echo '$ac_cv_header_'$ac_safe`\" = yes"; then
+ echo "$ac_t""yes" 1>&6
+ ac_tr_hdr=HAVE_`echo $ac_hdr | sed 'y%abcdefghijklmnopqrstuvwxyz./-%ABCDEFGHIJKLMNOPQRSTUVWXYZ___%'`
+ cat >> confdefs.h <<EOF
+#define $ac_tr_hdr 1
+EOF
+
+else
+ echo "$ac_t""no" 1>&6
+fi
+done
+
+
+
+
+
+# Only perform the check for file, if the check method requires it
+case $deplibs_check_method in
+file_magic*)
+ if test "$file_magic_cmd" = '$MAGIC_CMD'; then
+ echo $ac_n "checking for ${ac_tool_prefix}file""... $ac_c" 1>&6
+echo "configure:2199: checking for ${ac_tool_prefix}file" >&5
+if eval "test \"`echo '$''{'lt_cv_path_MAGIC_CMD'+set}'`\" = set"; then
+ echo $ac_n "(cached) $ac_c" 1>&6
+else
+ case $MAGIC_CMD in
+ /*)
+ lt_cv_path_MAGIC_CMD="$MAGIC_CMD" # Let the user override the test with a path.
+ ;;
+ ?:/*)
+ lt_cv_path_MAGIC_CMD="$MAGIC_CMD" # Let the user override the test with a dos path.
+ ;;
+ *)
+ ac_save_MAGIC_CMD="$MAGIC_CMD"
+ IFS="${IFS= }"; ac_save_ifs="$IFS"; IFS=":"
+ ac_dummy="/usr/bin:$PATH"
+ for ac_dir in $ac_dummy; do
+ test -z "$ac_dir" && ac_dir=.
+ if test -f $ac_dir/${ac_tool_prefix}file; then
+ lt_cv_path_MAGIC_CMD="$ac_dir/${ac_tool_prefix}file"
+ if test -n "$file_magic_test_file"; then
+ case $deplibs_check_method in
+ "file_magic "*)
+ file_magic_regex="`expr \"$deplibs_check_method\" : \"file_magic \(.*\)\"`"
+ MAGIC_CMD="$lt_cv_path_MAGIC_CMD"
+ if eval $file_magic_cmd \$file_magic_test_file 2> /dev/null |
+ egrep "$file_magic_regex" > /dev/null; then
+ :
+ else
+ cat <<EOF 1>&2
+
+*** Warning: the command libtool uses to detect shared libraries,
+*** $file_magic_cmd, produces output that libtool cannot recognize.
+*** The result is that libtool may fail to recognize shared libraries
+*** as such. This will affect the creation of libtool libraries that
+*** depend on shared libraries, but programs linked with such libtool
+*** libraries will work regardless of this problem. Nevertheless, you
+*** may want to report the problem to your system manager and/or to
+*** bug-libtool@gnu.org
+
+EOF
+ fi ;;
+ esac
+ fi
+ break
+ fi
+ done
+ IFS="$ac_save_ifs"
+ MAGIC_CMD="$ac_save_MAGIC_CMD"
+ ;;
+esac
+fi
+
+MAGIC_CMD="$lt_cv_path_MAGIC_CMD"
+if test -n "$MAGIC_CMD"; then
+ echo "$ac_t""$MAGIC_CMD" 1>&6
+else
+ echo "$ac_t""no" 1>&6
+fi
+
+if test -z "$lt_cv_path_MAGIC_CMD"; then
+ if test -n "$ac_tool_prefix"; then
+ echo $ac_n "checking for file""... $ac_c" 1>&6
+echo "configure:2261: checking for file" >&5
+if eval "test \"`echo '$''{'lt_cv_path_MAGIC_CMD'+set}'`\" = set"; then
+ echo $ac_n "(cached) $ac_c" 1>&6
+else
+ case $MAGIC_CMD in
+ /*)
+ lt_cv_path_MAGIC_CMD="$MAGIC_CMD" # Let the user override the test with a path.
+ ;;
+ ?:/*)
+ lt_cv_path_MAGIC_CMD="$MAGIC_CMD" # Let the user override the test with a dos path.
+ ;;
+ *)
+ ac_save_MAGIC_CMD="$MAGIC_CMD"
+ IFS="${IFS= }"; ac_save_ifs="$IFS"; IFS=":"
+ ac_dummy="/usr/bin:$PATH"
+ for ac_dir in $ac_dummy; do
+ test -z "$ac_dir" && ac_dir=.
+ if test -f $ac_dir/file; then
+ lt_cv_path_MAGIC_CMD="$ac_dir/file"
+ if test -n "$file_magic_test_file"; then
+ case $deplibs_check_method in
+ "file_magic "*)
+ file_magic_regex="`expr \"$deplibs_check_method\" : \"file_magic \(.*\)\"`"
+ MAGIC_CMD="$lt_cv_path_MAGIC_CMD"
+ if eval $file_magic_cmd \$file_magic_test_file 2> /dev/null |
+ egrep "$file_magic_regex" > /dev/null; then
+ :
+ else
+ cat <<EOF 1>&2
+
+*** Warning: the command libtool uses to detect shared libraries,
+*** $file_magic_cmd, produces output that libtool cannot recognize.
+*** The result is that libtool may fail to recognize shared libraries
+*** as such. This will affect the creation of libtool libraries that
+*** depend on shared libraries, but programs linked with such libtool
+*** libraries will work regardless of this problem. Nevertheless, you
+*** may want to report the problem to your system manager and/or to
+*** bug-libtool@gnu.org
+
+EOF
+ fi ;;
+ esac
+ fi
+ break
+ fi
+ done
+ IFS="$ac_save_ifs"
+ MAGIC_CMD="$ac_save_MAGIC_CMD"
+ ;;
+esac
+fi
+
+MAGIC_CMD="$lt_cv_path_MAGIC_CMD"
+if test -n "$MAGIC_CMD"; then
+ echo "$ac_t""$MAGIC_CMD" 1>&6
+else
+ echo "$ac_t""no" 1>&6
+fi
+
+ else
+ MAGIC_CMD=:
+ fi
+fi
+
+ fi
+ ;;
+esac
+
+# Extract the first word of "${ac_tool_prefix}ranlib", so it can be a program name with args.
+set dummy ${ac_tool_prefix}ranlib; ac_word=$2
+echo $ac_n "checking for $ac_word""... $ac_c" 1>&6
+echo "configure:2332: checking for $ac_word" >&5
+if eval "test \"`echo '$''{'ac_cv_prog_RANLIB'+set}'`\" = set"; then
+ echo $ac_n "(cached) $ac_c" 1>&6
+else
+ if test -n "$RANLIB"; then
+ ac_cv_prog_RANLIB="$RANLIB" # Let the user override the test.
+else
+ IFS="${IFS= }"; ac_save_ifs="$IFS"; IFS=":"
+ ac_dummy="$PATH"
+ for ac_dir in $ac_dummy; do
+ test -z "$ac_dir" && ac_dir=.
+ if test -f $ac_dir/$ac_word; then
+ ac_cv_prog_RANLIB="${ac_tool_prefix}ranlib"
+ break
+ fi
+ done
+ IFS="$ac_save_ifs"
+fi
+fi
+RANLIB="$ac_cv_prog_RANLIB"
+if test -n "$RANLIB"; then
+ echo "$ac_t""$RANLIB" 1>&6
+else
+ echo "$ac_t""no" 1>&6
+fi
+
+
+if test -z "$ac_cv_prog_RANLIB"; then
+if test -n "$ac_tool_prefix"; then
+ # Extract the first word of "ranlib", so it can be a program name with args.
+set dummy ranlib; ac_word=$2
+echo $ac_n "checking for $ac_word""... $ac_c" 1>&6
+echo "configure:2364: checking for $ac_word" >&5
+if eval "test \"`echo '$''{'ac_cv_prog_RANLIB'+set}'`\" = set"; then
+ echo $ac_n "(cached) $ac_c" 1>&6
+else
+ if test -n "$RANLIB"; then
+ ac_cv_prog_RANLIB="$RANLIB" # Let the user override the test.
+else
+ IFS="${IFS= }"; ac_save_ifs="$IFS"; IFS=":"
+ ac_dummy="$PATH"
+ for ac_dir in $ac_dummy; do
+ test -z "$ac_dir" && ac_dir=.
+ if test -f $ac_dir/$ac_word; then
+ ac_cv_prog_RANLIB="ranlib"
+ break
+ fi
+ done
+ IFS="$ac_save_ifs"
+ test -z "$ac_cv_prog_RANLIB" && ac_cv_prog_RANLIB=":"
+fi
+fi
+RANLIB="$ac_cv_prog_RANLIB"
+if test -n "$RANLIB"; then
+ echo "$ac_t""$RANLIB" 1>&6
+else
+ echo "$ac_t""no" 1>&6
+fi
+
+else
+ RANLIB=":"
+fi
+fi
+
+# Extract the first word of "${ac_tool_prefix}strip", so it can be a program name with args.
+set dummy ${ac_tool_prefix}strip; ac_word=$2
+echo $ac_n "checking for $ac_word""... $ac_c" 1>&6
+echo "configure:2399: checking for $ac_word" >&5
+if eval "test \"`echo '$''{'ac_cv_prog_STRIP'+set}'`\" = set"; then
+ echo $ac_n "(cached) $ac_c" 1>&6
+else
+ if test -n "$STRIP"; then
+ ac_cv_prog_STRIP="$STRIP" # Let the user override the test.
+else
+ IFS="${IFS= }"; ac_save_ifs="$IFS"; IFS=":"
+ ac_dummy="$PATH"
+ for ac_dir in $ac_dummy; do
+ test -z "$ac_dir" && ac_dir=.
+ if test -f $ac_dir/$ac_word; then
+ ac_cv_prog_STRIP="${ac_tool_prefix}strip"
+ break
+ fi
+ done
+ IFS="$ac_save_ifs"
+fi
+fi
+STRIP="$ac_cv_prog_STRIP"
+if test -n "$STRIP"; then
+ echo "$ac_t""$STRIP" 1>&6
+else
+ echo "$ac_t""no" 1>&6
+fi
+
+
+if test -z "$ac_cv_prog_STRIP"; then
+if test -n "$ac_tool_prefix"; then
+ # Extract the first word of "strip", so it can be a program name with args.
+set dummy strip; ac_word=$2
+echo $ac_n "checking for $ac_word""... $ac_c" 1>&6
+echo "configure:2431: checking for $ac_word" >&5
+if eval "test \"`echo '$''{'ac_cv_prog_STRIP'+set}'`\" = set"; then
+ echo $ac_n "(cached) $ac_c" 1>&6
+else
+ if test -n "$STRIP"; then
+ ac_cv_prog_STRIP="$STRIP" # Let the user override the test.
+else
+ IFS="${IFS= }"; ac_save_ifs="$IFS"; IFS=":"
+ ac_dummy="$PATH"
+ for ac_dir in $ac_dummy; do
+ test -z "$ac_dir" && ac_dir=.
+ if test -f $ac_dir/$ac_word; then
+ ac_cv_prog_STRIP="strip"
+ break
+ fi
+ done
+ IFS="$ac_save_ifs"
+ test -z "$ac_cv_prog_STRIP" && ac_cv_prog_STRIP=":"
+fi
+fi
+STRIP="$ac_cv_prog_STRIP"
+if test -n "$STRIP"; then
+ echo "$ac_t""$STRIP" 1>&6
+else
+ echo "$ac_t""no" 1>&6
+fi
+
+else
+ STRIP=":"
+fi
+fi
+
+
+enable_dlopen=no
+enable_win32_dll=no
+
+# Check whether --enable-libtool-lock or --disable-libtool-lock was given.
+if test "${enable_libtool_lock+set}" = set; then
+ enableval="$enable_libtool_lock"
+ :
+fi
+
+test "x$enable_libtool_lock" != xno && enable_libtool_lock=yes
+
+# Some flags need to be propagated to the compiler or linker for good
+# libtool support.
+case $host in
+*-*-irix6*)
+ # Find out which ABI we are using.
+ echo '#line 2480 "configure"' > conftest.$ac_ext
+ if { (eval echo configure:2481: \"$ac_compile\") 1>&5; (eval $ac_compile) 2>&5; }; then
+ case `/usr/bin/file conftest.$ac_objext` in
+ *32-bit*)
+ LD="${LD-ld} -32"
+ ;;
+ *N32*)
+ LD="${LD-ld} -n32"
+ ;;
+ *64-bit*)
+ LD="${LD-ld} -64"
+ ;;
+ esac
+ fi
+ rm -rf conftest*
+ ;;
+
+*-*-sco3.2v5*)
+ # On SCO OpenServer 5, we need -belf to get full-featured binaries.
+ SAVE_CFLAGS="$CFLAGS"
+ CFLAGS="$CFLAGS -belf"
+ echo $ac_n "checking whether the C compiler needs -belf""... $ac_c" 1>&6
+echo "configure:2502: checking whether the C compiler needs -belf" >&5
+if eval "test \"`echo '$''{'lt_cv_cc_needs_belf'+set}'`\" = set"; then
+ echo $ac_n "(cached) $ac_c" 1>&6
+else
+
+ ac_ext=c
+# CFLAGS is not in ac_cpp because -g, -O, etc. are not valid cpp options.
+ac_cpp='$CPP $CPPFLAGS'
+ac_compile='${CC-cc} -c $CFLAGS $CPPFLAGS conftest.$ac_ext 1>&5'
+ac_link='${CC-cc} -o conftest${ac_exeext} $CFLAGS $CPPFLAGS $LDFLAGS conftest.$ac_ext $LIBS 1>&5'
+cross_compiling=$ac_cv_prog_cc_cross
+
+ cat > conftest.$ac_ext <<EOF
+#line 2515 "configure"
+#include "confdefs.h"
+
+int main() {
+
+; return 0; }
+EOF
+if { (eval echo configure:2522: \"$ac_link\") 1>&5; (eval $ac_link) 2>&5; } && test -s conftest${ac_exeext}; then
+ rm -rf conftest*
+ lt_cv_cc_needs_belf=yes
+else
+ echo "configure: failed program was:" >&5
+ cat conftest.$ac_ext >&5
+ rm -rf conftest*
+ lt_cv_cc_needs_belf=no
+fi
+rm -f conftest*
+ ac_ext=c
+# CFLAGS is not in ac_cpp because -g, -O, etc. are not valid cpp options.
+ac_cpp='$CPP $CPPFLAGS'
+ac_compile='${CC-cc} -c $CFLAGS $CPPFLAGS conftest.$ac_ext 1>&5'
+ac_link='${CC-cc} -o conftest${ac_exeext} $CFLAGS $CPPFLAGS $LDFLAGS conftest.$ac_ext $LIBS 1>&5'
+cross_compiling=$ac_cv_prog_cc_cross
+
+fi
+
+echo "$ac_t""$lt_cv_cc_needs_belf" 1>&6
+ if test x"$lt_cv_cc_needs_belf" != x"yes"; then
+ # this is probably gcc 2.8.0, egcs 1.0 or newer; no need for -belf
+ CFLAGS="$SAVE_CFLAGS"
+ fi
+ ;;
+
+
+esac
+
+# Sed substitution that helps us do robust quoting. It backslashifies
+# metacharacters that are still active within double-quoted strings.
+Xsed='sed -e s/^X//'
+sed_quote_subst='s/\([\\"\\`$\\\\]\)/\\\1/g'
+
+# Same as above, but do not quote variable references.
+double_quote_subst='s/\([\\"\\`\\\\]\)/\\\1/g'
+
+# Sed substitution to delay expansion of an escaped shell variable in a
+# double_quote_subst'ed string.
+delay_variable_subst='s/\\\\\\\\\\\$/\\\\\\$/g'
+
+# Constants:
+rm="rm -f"
+
+# Global variables:
+default_ofile=libtool
+can_build_shared=yes
+
+# All known linkers require a `.a' archive for static linking (except M$VC,
+# which needs '.lib').
+libext=a
+ltmain="$ac_aux_dir/ltmain.sh"
+ofile="$default_ofile"
+with_gnu_ld="$lt_cv_prog_gnu_ld"
+need_locks="$enable_libtool_lock"
+
+old_CC="$CC"
+old_CFLAGS="$CFLAGS"
+
+# Set sane defaults for various variables
+test -z "$AR" && AR=ar
+test -z "$AR_FLAGS" && AR_FLAGS=cru
+test -z "$AS" && AS=as
+test -z "$CC" && CC=cc
+test -z "$DLLTOOL" && DLLTOOL=dlltool
+test -z "$LD" && LD=ld
+test -z "$LN_S" && LN_S="ln -s"
+test -z "$MAGIC_CMD" && MAGIC_CMD=file
+test -z "$NM" && NM=nm
+test -z "$OBJDUMP" && OBJDUMP=objdump
+test -z "$RANLIB" && RANLIB=:
+test -z "$STRIP" && STRIP=:
+test -z "$ac_objext" && ac_objext=o
+
+if test x"$host" != x"$build"; then
+ ac_tool_prefix=${host_alias}-
+else
+ ac_tool_prefix=
+fi
+
+# Transform linux* to *-*-linux-gnu*, to support old configure scripts.
+case $host_os in
+linux-gnu*) ;;
+linux*) host=`echo $host | sed 's/^\(.*-.*-linux\)\(.*\)$/\1-gnu\2/'`
+esac
+
+case $host_os in
+aix3*)
+ # AIX sometimes has problems with the GCC collect2 program. For some
+ # reason, if we set the COLLECT_NAMES environment variable, the problems
+ # vanish in a puff of smoke.
+ if test "X${COLLECT_NAMES+set}" != Xset; then
+ COLLECT_NAMES=
+ export COLLECT_NAMES
+ fi
+ ;;
+esac
+
+# Determine commands to create old-style static archives.
+old_archive_cmds='$AR $AR_FLAGS $oldlib$oldobjs$old_deplibs'
+old_postinstall_cmds='chmod 644 $oldlib'
+old_postuninstall_cmds=
+
+if test -n "$RANLIB"; then
+ case $host_os in
+ openbsd*)
+ old_postinstall_cmds="\$RANLIB -t \$oldlib~$old_postinstall_cmds"
+ ;;
+ *)
+ old_postinstall_cmds="\$RANLIB \$oldlib~$old_postinstall_cmds"
+ ;;
+ esac
+ old_archive_cmds="$old_archive_cmds~\$RANLIB \$oldlib"
+fi
+
+# Allow CC to be a program name with arguments.
+set dummy $CC
+compiler="$2"
+
+## FIXME: this should be a separate macro
+##
+echo $ac_n "checking for objdir""... $ac_c" 1>&6
+echo "configure:2644: checking for objdir" >&5
+rm -f .libs 2>/dev/null
+mkdir .libs 2>/dev/null
+if test -d .libs; then
+ objdir=.libs
+else
+ # MS-DOS does not allow filenames that begin with a dot.
+ objdir=_libs
+fi
+rmdir .libs 2>/dev/null
+echo "$ac_t""$objdir" 1>&6
+##
+## END FIXME
+
+
+## FIXME: this should be a separate macro
+##
+# Check whether --with-pic or --without-pic was given.
+if test "${with_pic+set}" = set; then
+ withval="$with_pic"
+ pic_mode="$withval"
+else
+ pic_mode=default
+fi
+
+test -z "$pic_mode" && pic_mode=default
+
+# We assume here that the value for lt_cv_prog_cc_pic will not be cached
+# in isolation, and that seeing it set (from the cache) indicates that
+# the associated values are set (in the cache) correctly too.
+echo $ac_n "checking for $compiler option to produce PIC""... $ac_c" 1>&6
+echo "configure:2675: checking for $compiler option to produce PIC" >&5
+if eval "test \"`echo '$''{'lt_cv_prog_cc_pic'+set}'`\" = set"; then
+ echo $ac_n "(cached) $ac_c" 1>&6
+else
+ lt_cv_prog_cc_pic=
+ lt_cv_prog_cc_shlib=
+ lt_cv_prog_cc_wl=
+ lt_cv_prog_cc_static=
+ lt_cv_prog_cc_no_builtin=
+ lt_cv_prog_cc_can_build_shared=$can_build_shared
+
+ if test "$GCC" = yes; then
+ lt_cv_prog_cc_wl='-Wl,'
+ lt_cv_prog_cc_static='-static'
+
+ case $host_os in
+ aix*)
+ # Below there is a dirty hack to force normal static linking with -ldl
+ # The problem is because libdl dynamically linked with both libc and
+ # libC (AIX C++ library), which obviously doesn't included in libraries
+ # list by gcc. This cause undefined symbols with -static flags.
+ # This hack allows C programs to be linked with "-static -ldl", but
+ # not sure about C++ programs.
+ lt_cv_prog_cc_static="$lt_cv_prog_cc_static ${lt_cv_prog_cc_wl}-lC"
+ ;;
+ amigaos*)
+ # FIXME: we need at least 68020 code to build shared libraries, but
+ # adding the `-m68020' flag to GCC prevents building anything better,
+ # like `-m68040'.
+ lt_cv_prog_cc_pic='-m68020 -resident32 -malways-restore-a4'
+ ;;
+ beos* | irix5* | irix6* | osf3* | osf4* | osf5*)
+ # PIC is the default for these OSes.
+ ;;
+ darwin* | rhapsody*)
+ # PIC is the default on this platform
+ # Common symbols not allowed in MH_DYLIB files
+ lt_cv_prog_cc_pic='-fno-common'
+ ;;
+ cygwin* | mingw* | pw32* | os2*)
+ # This hack is so that the source file can tell whether it is being
+ # built for inclusion in a dll (and should export symbols for example).
+ lt_cv_prog_cc_pic='-DDLL_EXPORT'
+ ;;
+ sysv4*MP*)
+ if test -d /usr/nec; then
+ lt_cv_prog_cc_pic=-Kconform_pic
+ fi
+ ;;
+ *)
+ lt_cv_prog_cc_pic='-fPIC'
+ ;;
+ esac
+ else
+ # PORTME Check for PIC flags for the system compiler.
+ case $host_os in
+ aix3* | aix4* | aix5*)
+ lt_cv_prog_cc_wl='-Wl,'
+ # All AIX code is PIC.
+ if test "$host_cpu" = ia64; then
+ # AIX 5 now supports IA64 processor
+ lt_cv_prog_cc_static='-Bstatic'
+ else
+ lt_cv_prog_cc_static='-bnso -bI:/lib/syscalls.exp'
+ fi
+ ;;
+
+ hpux9* | hpux10* | hpux11*)
+ # Is there a better lt_cv_prog_cc_static that works with the bundled CC?
+ lt_cv_prog_cc_wl='-Wl,'
+ lt_cv_prog_cc_static="${lt_cv_prog_cc_wl}-a ${lt_cv_prog_cc_wl}archive"
+ lt_cv_prog_cc_pic='+Z'
+ ;;
+
+ irix5* | irix6*)
+ lt_cv_prog_cc_wl='-Wl,'
+ lt_cv_prog_cc_static='-non_shared'
+ # PIC (with -KPIC) is the default.
+ ;;
+
+ cygwin* | mingw* | pw32* | os2*)
+ # This hack is so that the source file can tell whether it is being
+ # built for inclusion in a dll (and should export symbols for example).
+ lt_cv_prog_cc_pic='-DDLL_EXPORT'
+ ;;
+
+ newsos6)
+ lt_cv_prog_cc_pic='-KPIC'
+ lt_cv_prog_cc_static='-Bstatic'
+ ;;
+
+ osf3* | osf4* | osf5*)
+ # All OSF/1 code is PIC.
+ lt_cv_prog_cc_wl='-Wl,'
+ lt_cv_prog_cc_static='-non_shared'
+ ;;
+
+ sco3.2v5*)
+ lt_cv_prog_cc_pic='-Kpic'
+ lt_cv_prog_cc_static='-dn'
+ lt_cv_prog_cc_shlib='-belf'
+ ;;
+
+ solaris*)
+ lt_cv_prog_cc_pic='-KPIC'
+ lt_cv_prog_cc_static='-Bstatic'
+ lt_cv_prog_cc_wl='-Wl,'
+ ;;
+
+ sunos4*)
+ lt_cv_prog_cc_pic='-PIC'
+ lt_cv_prog_cc_static='-Bstatic'
+ lt_cv_prog_cc_wl='-Qoption ld '
+ ;;
+
+ sysv4 | sysv4.2uw2* | sysv4.3* | sysv5*)
+ lt_cv_prog_cc_pic='-KPIC'
+ lt_cv_prog_cc_static='-Bstatic'
+ if test "x$host_vendor" = xsni; then
+ lt_cv_prog_cc_wl='-LD'
+ else
+ lt_cv_prog_cc_wl='-Wl,'
+ fi
+ ;;
+
+ uts4*)
+ lt_cv_prog_cc_pic='-pic'
+ lt_cv_prog_cc_static='-Bstatic'
+ ;;
+
+ sysv4*MP*)
+ if test -d /usr/nec ;then
+ lt_cv_prog_cc_pic='-Kconform_pic'
+ lt_cv_prog_cc_static='-Bstatic'
+ fi
+ ;;
+
+ *)
+ lt_cv_prog_cc_can_build_shared=no
+ ;;
+ esac
+ fi
+
+fi
+
+if test -z "$lt_cv_prog_cc_pic"; then
+ echo "$ac_t""none" 1>&6
+else
+ echo "$ac_t""$lt_cv_prog_cc_pic" 1>&6
+
+ # Check to make sure the pic_flag actually works.
+ echo $ac_n "checking if $compiler PIC flag $lt_cv_prog_cc_pic works""... $ac_c" 1>&6
+echo "configure:2827: checking if $compiler PIC flag $lt_cv_prog_cc_pic works" >&5
+ if eval "test \"`echo '$''{'lt_cv_prog_cc_pic_works'+set}'`\" = set"; then
+ echo $ac_n "(cached) $ac_c" 1>&6
+else
+ save_CFLAGS="$CFLAGS"
+ CFLAGS="$CFLAGS $lt_cv_prog_cc_pic -DPIC"
+ cat > conftest.$ac_ext <<EOF
+#line 2834 "configure"
+#include "confdefs.h"
+
+int main() {
+
+; return 0; }
+EOF
+if { (eval echo configure:2841: \"$ac_compile\") 1>&5; (eval $ac_compile) 2>&5; }; then
+ rm -rf conftest*
+ case $host_os in
+ hpux9* | hpux10* | hpux11*)
+ # On HP-UX, both CC and GCC only warn that PIC is supported... then
+ # they create non-PIC objects. So, if there were any warnings, we
+ # assume that PIC is not supported.
+ if test -s conftest.err; then
+ lt_cv_prog_cc_pic_works=no
+ else
+ lt_cv_prog_cc_pic_works=yes
+ fi
+ ;;
+ *)
+ lt_cv_prog_cc_pic_works=yes
+ ;;
+ esac
+
+else
+ echo "configure: failed program was:" >&5
+ cat conftest.$ac_ext >&5
+ rm -rf conftest*
+ lt_cv_prog_cc_pic_works=no
+
+fi
+rm -f conftest*
+ CFLAGS="$save_CFLAGS"
+
+fi
+
+
+ if test "X$lt_cv_prog_cc_pic_works" = Xno; then
+ lt_cv_prog_cc_pic=
+ lt_cv_prog_cc_can_build_shared=no
+ else
+ lt_cv_prog_cc_pic=" $lt_cv_prog_cc_pic"
+ fi
+
+ echo "$ac_t""$lt_cv_prog_cc_pic_works" 1>&6
+fi
+##
+## END FIXME
+
+# Check for any special shared library compilation flags.
+if test -n "$lt_cv_prog_cc_shlib"; then
+ echo "configure: warning: \`$CC' requires \`$lt_cv_prog_cc_shlib' to build shared libraries" 1>&2
+ if echo "$old_CC $old_CFLAGS " | egrep -e "[ ]$lt_cv_prog_cc_shlib[ ]" >/dev/null; then :
+ else
+ echo "configure: warning: add \`$lt_cv_prog_cc_shlib' to the CC or CFLAGS env variable and reconfigure" 1>&2
+ lt_cv_prog_cc_can_build_shared=no
+ fi
+fi
+
+## FIXME: this should be a separate macro
+##
+echo $ac_n "checking if $compiler static flag $lt_cv_prog_cc_static works""... $ac_c" 1>&6
+echo "configure:2897: checking if $compiler static flag $lt_cv_prog_cc_static works" >&5
+if eval "test \"`echo '$''{'lt_cv_prog_cc_static_works'+set}'`\" = set"; then
+ echo $ac_n "(cached) $ac_c" 1>&6
+else
+ lt_cv_prog_cc_static_works=no
+ save_LDFLAGS="$LDFLAGS"
+ LDFLAGS="$LDFLAGS $lt_cv_prog_cc_static"
+ cat > conftest.$ac_ext <<EOF
+#line 2905 "configure"
+#include "confdefs.h"
+
+int main() {
+
+; return 0; }
+EOF
+if { (eval echo configure:2912: \"$ac_link\") 1>&5; (eval $ac_link) 2>&5; } && test -s conftest${ac_exeext}; then
+ rm -rf conftest*
+ lt_cv_prog_cc_static_works=yes
+else
+ echo "configure: failed program was:" >&5
+ cat conftest.$ac_ext >&5
+fi
+rm -f conftest*
+ LDFLAGS="$save_LDFLAGS"
+
+fi
+
+
+# Belt *and* braces to stop my trousers falling down:
+test "X$lt_cv_prog_cc_static_works" = Xno && lt_cv_prog_cc_static=
+echo "$ac_t""$lt_cv_prog_cc_static_works" 1>&6
+
+pic_flag="$lt_cv_prog_cc_pic"
+special_shlib_compile_flags="$lt_cv_prog_cc_shlib"
+wl="$lt_cv_prog_cc_wl"
+link_static_flag="$lt_cv_prog_cc_static"
+no_builtin_flag="$lt_cv_prog_cc_no_builtin"
+can_build_shared="$lt_cv_prog_cc_can_build_shared"
+##
+## END FIXME
+
+
+## FIXME: this should be a separate macro
+##
+# Check to see if options -o and -c are simultaneously supported by compiler
+echo $ac_n "checking if $compiler supports -c -o file.$ac_objext""... $ac_c" 1>&6
+echo "configure:2943: checking if $compiler supports -c -o file.$ac_objext" >&5
+if eval "test \"`echo '$''{'lt_cv_compiler_c_o'+set}'`\" = set"; then
+ echo $ac_n "(cached) $ac_c" 1>&6
+else
+
+$rm -r conftest 2>/dev/null
+mkdir conftest
+cd conftest
+echo "int some_variable = 0;" > conftest.$ac_ext
+mkdir out
+# According to Tom Tromey, Ian Lance Taylor reported there are C compilers
+# that will create temporary files in the current directory regardless of
+# the output directory. Thus, making CWD read-only will cause this test
+# to fail, enabling locking or at least warning the user not to do parallel
+# builds.
+chmod -w .
+save_CFLAGS="$CFLAGS"
+CFLAGS="$CFLAGS -o out/conftest2.$ac_objext"
+compiler_c_o=no
+if { (eval echo configure:2962: \"$ac_compile\") 1>&5; (eval $ac_compile) 2>out/conftest.err; } && test -s out/conftest2.$ac_objext; then
+ # The compiler can only warn and ignore the option if not recognized
+ # So say no if there are warnings
+ if test -s out/conftest.err; then
+ lt_cv_compiler_c_o=no
+ else
+ lt_cv_compiler_c_o=yes
+ fi
+else
+ # Append any errors to the config.log.
+ cat out/conftest.err 1>&5
+ lt_cv_compiler_c_o=no
+fi
+CFLAGS="$save_CFLAGS"
+chmod u+w .
+$rm conftest* out/*
+rmdir out
+cd ..
+rmdir conftest
+$rm -r conftest 2>/dev/null
+
+fi
+
+compiler_c_o=$lt_cv_compiler_c_o
+echo "$ac_t""$compiler_c_o" 1>&6
+
+if test x"$compiler_c_o" = x"yes"; then
+ # Check to see if we can write to a .lo
+ echo $ac_n "checking if $compiler supports -c -o file.lo""... $ac_c" 1>&6
+echo "configure:2991: checking if $compiler supports -c -o file.lo" >&5
+ if eval "test \"`echo '$''{'lt_cv_compiler_o_lo'+set}'`\" = set"; then
+ echo $ac_n "(cached) $ac_c" 1>&6
+else
+
+ lt_cv_compiler_o_lo=no
+ save_CFLAGS="$CFLAGS"
+ CFLAGS="$CFLAGS -c -o conftest.lo"
+ save_objext="$ac_objext"
+ ac_objext=lo
+ cat > conftest.$ac_ext <<EOF
+#line 3002 "configure"
+#include "confdefs.h"
+
+int main() {
+int some_variable = 0;
+; return 0; }
+EOF
+if { (eval echo configure:3009: \"$ac_compile\") 1>&5; (eval $ac_compile) 2>&5; }; then
+ rm -rf conftest*
+ # The compiler can only warn and ignore the option if not recognized
+ # So say no if there are warnings
+ if test -s conftest.err; then
+ lt_cv_compiler_o_lo=no
+ else
+ lt_cv_compiler_o_lo=yes
+ fi
+
+else
+ echo "configure: failed program was:" >&5
+ cat conftest.$ac_ext >&5
+fi
+rm -f conftest*
+ ac_objext="$save_objext"
+ CFLAGS="$save_CFLAGS"
+
+fi
+
+ compiler_o_lo=$lt_cv_compiler_o_lo
+ echo "$ac_t""$compiler_o_lo" 1>&6
+else
+ compiler_o_lo=no
+fi
+##
+## END FIXME
+
+## FIXME: this should be a separate macro
+##
+# Check to see if we can do hard links to lock some files if needed
+hard_links="nottested"
+if test "$compiler_c_o" = no && test "$need_locks" != no; then
+ # do not overwrite the value of need_locks provided by the user
+ echo $ac_n "checking if we can lock with hard links""... $ac_c" 1>&6
+echo "configure:3044: checking if we can lock with hard links" >&5
+ hard_links=yes
+ $rm conftest*
+ ln conftest.a conftest.b 2>/dev/null && hard_links=no
+ touch conftest.a
+ ln conftest.a conftest.b 2>&5 || hard_links=no
+ ln conftest.a conftest.b 2>/dev/null && hard_links=no
+ echo "$ac_t""$hard_links" 1>&6
+ if test "$hard_links" = no; then
+ echo "configure: warning: \`$CC' does not support \`-c -o', so \`make -j' may be unsafe" 1>&2
+ need_locks=warn
+ fi
+else
+ need_locks=no
+fi
+##
+## END FIXME
+
+## FIXME: this should be a separate macro
+##
+if test "$GCC" = yes; then
+ # Check to see if options -fno-rtti -fno-exceptions are supported by compiler
+ echo $ac_n "checking if $compiler supports -fno-rtti -fno-exceptions""... $ac_c" 1>&6
+echo "configure:3067: checking if $compiler supports -fno-rtti -fno-exceptions" >&5
+ echo "int some_variable = 0;" > conftest.$ac_ext
+ save_CFLAGS="$CFLAGS"
+ CFLAGS="$CFLAGS -fno-rtti -fno-exceptions -c conftest.$ac_ext"
+ compiler_rtti_exceptions=no
+ cat > conftest.$ac_ext <<EOF
+#line 3073 "configure"
+#include "confdefs.h"
+
+int main() {
+int some_variable = 0;
+; return 0; }
+EOF
+if { (eval echo configure:3080: \"$ac_compile\") 1>&5; (eval $ac_compile) 2>&5; }; then
+ rm -rf conftest*
+ # The compiler can only warn and ignore the option if not recognized
+ # So say no if there are warnings
+ if test -s conftest.err; then
+ compiler_rtti_exceptions=no
+ else
+ compiler_rtti_exceptions=yes
+ fi
+
+else
+ echo "configure: failed program was:" >&5
+ cat conftest.$ac_ext >&5
+fi
+rm -f conftest*
+ CFLAGS="$save_CFLAGS"
+ echo "$ac_t""$compiler_rtti_exceptions" 1>&6
+
+ if test "$compiler_rtti_exceptions" = "yes"; then
+ no_builtin_flag=' -fno-builtin -fno-rtti -fno-exceptions'
+ else
+ no_builtin_flag=' -fno-builtin'
+ fi
+fi
+##
+## END FIXME
+
+## FIXME: this should be a separate macro
+##
+# See if the linker supports building shared libraries.
+echo $ac_n "checking whether the linker ($LD) supports shared libraries""... $ac_c" 1>&6
+echo "configure:3111: checking whether the linker ($LD) supports shared libraries" >&5
+
+allow_undefined_flag=
+no_undefined_flag=
+need_lib_prefix=unknown
+need_version=unknown
+# when you set need_version to no, make sure it does not cause -set_version
+# flags to be left without arguments
+archive_cmds=
+archive_expsym_cmds=
+old_archive_from_new_cmds=
+old_archive_from_expsyms_cmds=
+export_dynamic_flag_spec=
+whole_archive_flag_spec=
+thread_safe_flag_spec=
+hardcode_into_libs=no
+hardcode_libdir_flag_spec=
+hardcode_libdir_separator=
+hardcode_direct=no
+hardcode_minus_L=no
+hardcode_shlibpath_var=unsupported
+runpath_var=
+link_all_deplibs=unknown
+always_export_symbols=no
+export_symbols_cmds='$NM $libobjs $convenience | $global_symbol_pipe | sed '\''s/.* //'\'' | sort | uniq > $export_symbols'
+# include_expsyms should be a list of space-separated symbols to be *always*
+# included in the symbol list
+include_expsyms=
+# exclude_expsyms can be an egrep regular expression of symbols to exclude
+# it will be wrapped by ` (' and `)$', so one must not match beginning or
+# end of line. Example: `a|bc|.*d.*' will exclude the symbols `a' and `bc',
+# as well as any symbol that contains `d'.
+exclude_expsyms="_GLOBAL_OFFSET_TABLE_"
+# Although _GLOBAL_OFFSET_TABLE_ is a valid symbol C name, most a.out
+# platforms (ab)use it in PIC code, but their linkers get confused if
+# the symbol is explicitly referenced. Since portable code cannot
+# rely on this symbol name, it's probably fine to never include it in
+# preloaded symbol tables.
+extract_expsyms_cmds=
+
+case $host_os in
+cygwin* | mingw* | pw32*)
+ # FIXME: the MSVC++ port hasn't been tested in a loooong time
+ # When not using gcc, we currently assume that we are using
+ # Microsoft Visual C++.
+ if test "$GCC" != yes; then
+ with_gnu_ld=no
+ fi
+ ;;
+openbsd*)
+ with_gnu_ld=no
+ ;;
+esac
+
+ld_shlibs=yes
+if test "$with_gnu_ld" = yes; then
+ # If archive_cmds runs LD, not CC, wlarc should be empty
+ wlarc='${wl}'
+
+ # See if GNU ld supports shared libraries.
+ case $host_os in
+ aix3* | aix4* | aix5*)
+ # On AIX, the GNU linker is very broken
+ # Note:Check GNU linker on AIX 5-IA64 when/if it becomes available.
+ ld_shlibs=no
+ cat <<EOF 1>&2
+
+*** Warning: the GNU linker, at least up to release 2.9.1, is reported
+*** to be unable to reliably create shared libraries on AIX.
+*** Therefore, libtool is disabling shared libraries support. If you
+*** really care for shared libraries, you may want to modify your PATH
+*** so that a non-GNU linker is found, and then restart.
+
+EOF
+ ;;
+
+ amigaos*)
+ archive_cmds='$rm $output_objdir/a2ixlibrary.data~$echo "#define NAME $libname" > $output_objdir/a2ixlibrary.data~$echo "#define LIBRARY_ID 1" >> $output_objdir/a2ixlibrary.data~$echo "#define VERSION $major" >> $output_objdir/a2ixlibrary.data~$echo "#define REVISION $revision" >> $output_objdir/a2ixlibrary.data~$AR $AR_FLAGS $lib $libobjs~$RANLIB $lib~(cd $output_objdir && a2ixlibrary -32)'
+ hardcode_libdir_flag_spec='-L$libdir'
+ hardcode_minus_L=yes
+
+ # Samuel A. Falvo II <kc5tja@dolphin.openprojects.net> reports
+ # that the semantics of dynamic libraries on AmigaOS, at least up
+ # to version 4, is to share data among multiple programs linked
+ # with the same dynamic library. Since this doesn't match the
+ # behavior of shared libraries on other platforms, we can use
+ # them.
+ ld_shlibs=no
+ ;;
+
+ beos*)
+ if $LD --help 2>&1 | egrep ': supported targets:.* elf' > /dev/null; then
+ allow_undefined_flag=unsupported
+ # Joseph Beckenbach <jrb3@best.com> says some releases of gcc
+ # support --undefined. This deserves some investigation. FIXME
+ archive_cmds='$CC -nostart $libobjs $deplibs $compiler_flags ${wl}-soname $wl$soname -o $lib'
+ else
+ ld_shlibs=no
+ fi
+ ;;
+
+ cygwin* | mingw* | pw32*)
+ # hardcode_libdir_flag_spec is actually meaningless, as there is
+ # no search path for DLLs.
+ hardcode_libdir_flag_spec='-L$libdir'
+ allow_undefined_flag=unsupported
+ always_export_symbols=yes
+
+ extract_expsyms_cmds='test -f $output_objdir/impgen.c || \
+ sed -e "/^# \/\* impgen\.c starts here \*\//,/^# \/\* impgen.c ends here \*\// { s/^# //;s/^# *$//; p; }" -e d < $''0 > $output_objdir/impgen.c~
+ test -f $output_objdir/impgen.exe || (cd $output_objdir && \
+ if test "x$HOST_CC" != "x" ; then $HOST_CC -o impgen impgen.c ; \
+ else $CC -o impgen impgen.c ; fi)~
+ $output_objdir/impgen $dir/$soroot > $output_objdir/$soname-def'
+
+ old_archive_from_expsyms_cmds='$DLLTOOL --as=$AS --dllname $soname --def $output_objdir/$soname-def --output-lib $output_objdir/$newlib'
+
+ # cygwin and mingw dlls have different entry points and sets of symbols
+ # to exclude.
+ # FIXME: what about values for MSVC?
+ dll_entry=__cygwin_dll_entry@12
+ dll_exclude_symbols=DllMain@12,_cygwin_dll_entry@12,_cygwin_noncygwin_dll_entry@12~
+ case $host_os in
+ mingw*)
+ # mingw values
+ dll_entry=_DllMainCRTStartup@12
+ dll_exclude_symbols=DllMain@12,DllMainCRTStartup@12,DllEntryPoint@12~
+ ;;
+ esac
+
+ # mingw and cygwin differ, and it's simplest to just exclude the union
+ # of the two symbol sets.
+ dll_exclude_symbols=DllMain@12,_cygwin_dll_entry@12,_cygwin_noncygwin_dll_entry@12,DllMainCRTStartup@12,DllEntryPoint@12
+
+ # recent cygwin and mingw systems supply a stub DllMain which the user
+ # can override, but on older systems we have to supply one (in ltdll.c)
+ if test "x$lt_cv_need_dllmain" = "xyes"; then
+ ltdll_obj='$output_objdir/$soname-ltdll.'"$ac_objext "
+ ltdll_cmds='test -f $output_objdir/$soname-ltdll.c || sed -e "/^# \/\* ltdll\.c starts here \*\//,/^# \/\* ltdll.c ends here \*\// { s/^# //; p; }" -e d < $''0 > $output_objdir/$soname-ltdll.c~
+ test -f $output_objdir/$soname-ltdll.$ac_objext || (cd $output_objdir && $CC -c $soname-ltdll.c)~'
+ else
+ ltdll_obj=
+ ltdll_cmds=
+ fi
+
+ # Extract the symbol export list from an `--export-all' def file,
+ # then regenerate the def file from the symbol export list, so that
+ # the compiled dll only exports the symbol export list.
+ # Be careful not to strip the DATA tag left be newer dlltools.
+ export_symbols_cmds="$ltdll_cmds"'
+ $DLLTOOL --export-all --exclude-symbols '$dll_exclude_symbols' --output-def $output_objdir/$soname-def '$ltdll_obj'$libobjs $convenience~
+ sed -e "1,/EXPORTS/d" -e "s/ @ [0-9]*//" -e "s/ *;.*$//" < $output_objdir/$soname-def > $export_symbols'
+
+ # If the export-symbols file already is a .def file (1st line
+ # is EXPORTS), use it as is.
+ # If DATA tags from a recent dlltool are present, honour them!
+ archive_expsym_cmds='if test "x`head -1 $export_symbols`" = xEXPORTS; then
+ cp $export_symbols $output_objdir/$soname-def;
+ else
+ echo EXPORTS > $output_objdir/$soname-def;
+ _lt_hint=1;
+ cat $export_symbols | while read symbol; do
+ set dummy \$symbol;
+ case \$# in
+ 2) echo " \$2 @ \$_lt_hint ; " >> $output_objdir/$soname-def;;
+ *) echo " \$2 @ \$_lt_hint \$3 ; " >> $output_objdir/$soname-def;;
+ esac;
+ _lt_hint=`expr 1 + \$_lt_hint`;
+ done;
+ fi~
+ '"$ltdll_cmds"'
+ $CC -Wl,--base-file,$output_objdir/$soname-base '$lt_cv_cc_dll_switch' -Wl,-e,'$dll_entry' -o $output_objdir/$soname '$ltdll_obj'$libobjs $deplibs $compiler_flags~
+ $DLLTOOL --as=$AS --dllname $soname --exclude-symbols '$dll_exclude_symbols' --def $output_objdir/$soname-def --base-file $output_objdir/$soname-base --output-exp $output_objdir/$soname-exp~
+ $CC -Wl,--base-file,$output_objdir/$soname-base $output_objdir/$soname-exp '$lt_cv_cc_dll_switch' -Wl,-e,'$dll_entry' -o $output_objdir/$soname '$ltdll_obj'$libobjs $deplibs $compiler_flags~
+ $DLLTOOL --as=$AS --dllname $soname --exclude-symbols '$dll_exclude_symbols' --def $output_objdir/$soname-def --base-file $output_objdir/$soname-base --output-exp $output_objdir/$soname-exp --output-lib $output_objdir/$libname.dll.a~
+ $CC $output_objdir/$soname-exp '$lt_cv_cc_dll_switch' -Wl,-e,'$dll_entry' -o $output_objdir/$soname '$ltdll_obj'$libobjs $deplibs $compiler_flags'
+ ;;
+
+ netbsd*)
+ if echo __ELF__ | $CC -E - | grep __ELF__ >/dev/null; then
+ archive_cmds='$LD -Bshareable $libobjs $deplibs $linker_flags -o $lib'
+ wlarc=
+ else
+ archive_cmds='$CC -shared -nodefaultlibs $libobjs $deplibs $compiler_flags ${wl}-soname $wl$soname -o $lib'
+ archive_expsym_cmds='$CC -shared -nodefaultlibs $libobjs $deplibs $compiler_flags ${wl}-soname $wl$soname ${wl}-retain-symbols-file $wl$export_symbols -o $lib'
+ fi
+ ;;
+
+ solaris* | sysv5*)
+ if $LD -v 2>&1 | egrep 'BFD 2\.8' > /dev/null; then
+ ld_shlibs=no
+ cat <<EOF 1>&2
+
+*** Warning: The releases 2.8.* of the GNU linker cannot reliably
+*** create shared libraries on Solaris systems. Therefore, libtool
+*** is disabling shared libraries support. We urge you to upgrade GNU
+*** binutils to release 2.9.1 or newer. Another option is to modify
+*** your PATH or compiler configuration so that the native linker is
+*** used, and then restart.
+
+EOF
+ elif $LD --help 2>&1 | egrep ': supported targets:.* elf' > /dev/null; then
+ archive_cmds='$CC -shared $libobjs $deplibs $compiler_flags ${wl}-soname $wl$soname -o $lib'
+ archive_expsym_cmds='$CC -shared $libobjs $deplibs $compiler_flags ${wl}-soname $wl$soname ${wl}-retain-symbols-file $wl$export_symbols -o $lib'
+ else
+ ld_shlibs=no
+ fi
+ ;;
+
+ sunos4*)
+ archive_cmds='$LD -assert pure-text -Bshareable -o $lib $libobjs $deplibs $linker_flags'
+ wlarc=
+ hardcode_direct=yes
+ hardcode_shlibpath_var=no
+ ;;
+
+ *)
+ if $LD --help 2>&1 | egrep ': supported targets:.* elf' > /dev/null; then
+ archive_cmds='$CC -shared $libobjs $deplibs $compiler_flags ${wl}-soname $wl$soname -o $lib'
+ archive_expsym_cmds='$CC -shared $libobjs $deplibs $compiler_flags ${wl}-soname $wl$soname ${wl}-retain-symbols-file $wl$export_symbols -o $lib'
+ else
+ ld_shlibs=no
+ fi
+ ;;
+ esac
+
+ if test "$ld_shlibs" = yes; then
+ runpath_var=LD_RUN_PATH
+ hardcode_libdir_flag_spec='${wl}--rpath ${wl}$libdir'
+ export_dynamic_flag_spec='${wl}--export-dynamic'
+ case $host_os in
+ cygwin* | mingw* | pw32*)
+ # dlltool doesn't understand --whole-archive et. al.
+ whole_archive_flag_spec=
+ ;;
+ *)
+ # ancient GNU ld didn't support --whole-archive et. al.
+ if $LD --help 2>&1 | egrep 'no-whole-archive' > /dev/null; then
+ whole_archive_flag_spec="$wlarc"'--whole-archive$convenience '"$wlarc"'--no-whole-archive'
+ else
+ whole_archive_flag_spec=
+ fi
+ ;;
+ esac
+ fi
+else
+ # PORTME fill in a description of your system's linker (not GNU ld)
+ case $host_os in
+ aix3*)
+ allow_undefined_flag=unsupported
+ always_export_symbols=yes
+ archive_expsym_cmds='$LD -o $output_objdir/$soname $libobjs $deplibs $linker_flags -bE:$export_symbols -T512 -H512 -bM:SRE~$AR $AR_FLAGS $lib $output_objdir/$soname'
+ # Note: this linker hardcodes the directories in LIBPATH if there
+ # are no directories specified by -L.
+ hardcode_minus_L=yes
+ if test "$GCC" = yes && test -z "$link_static_flag"; then
+ # Neither direct hardcoding nor static linking is supported with a
+ # broken collect2.
+ hardcode_direct=unsupported
+ fi
+ ;;
+
+ aix4* | aix5*)
+ if test "$host_cpu" = ia64; then
+ # On IA64, the linker does run time linking by default, so we don't
+ # have to do anything special.
+ aix_use_runtimelinking=no
+ exp_sym_flag='-Bexport'
+ no_entry_flag=""
+ else
+ aix_use_runtimelinking=no
+
+ # Test if we are trying to use run time linking or normal
+ # AIX style linking. If -brtl is somewhere in LDFLAGS, we
+ # need to do runtime linking.
+ case $host_os in aix4.[23]|aix4.[23].*|aix5*)
+ for ld_flag in $LDFLAGS; do
+ if (test $ld_flag = "-brtl" || test $ld_flag = "-Wl,-brtl"); then
+ aix_use_runtimelinking=yes
+ break
+ fi
+ done
+ esac
+
+ exp_sym_flag='-bexport'
+ no_entry_flag='-bnoentry'
+ fi
+
+ # When large executables or shared objects are built, AIX ld can
+ # have problems creating the table of contents. If linking a library
+ # or program results in "error TOC overflow" add -mminimal-toc to
+ # CXXFLAGS/CFLAGS for g++/gcc. In the cases where that is not
+ # enough to fix the problem, add -Wl,-bbigtoc to LDFLAGS.
+
+ hardcode_direct=yes
+ archive_cmds=''
+ hardcode_libdir_separator=':'
+ if test "$GCC" = yes; then
+ case $host_os in aix4.[012]|aix4.[012].*)
+ collect2name=`${CC} -print-prog-name=collect2`
+ if test -f "$collect2name" && \
+ strings "$collect2name" | grep resolve_lib_name >/dev/null
+ then
+ # We have reworked collect2
+ hardcode_direct=yes
+ else
+ # We have old collect2
+ hardcode_direct=unsupported
+ # It fails to find uninstalled libraries when the uninstalled
+ # path is not listed in the libpath. Setting hardcode_minus_L
+ # to unsupported forces relinking
+ hardcode_minus_L=yes
+ hardcode_libdir_flag_spec='-L$libdir'
+ hardcode_libdir_separator=
+ fi
+ esac
+
+ shared_flag='-shared'
+ else
+ # not using gcc
+ if test "$host_cpu" = ia64; then
+ shared_flag='${wl}-G'
+ else
+ if test "$aix_use_runtimelinking" = yes; then
+ shared_flag='${wl}-G'
+ else
+ shared_flag='${wl}-bM:SRE'
+ fi
+ fi
+ fi
+
+ # It seems that -bexpall can do strange things, so it is better to
+ # generate a list of symbols to export.
+ always_export_symbols=yes
+ if test "$aix_use_runtimelinking" = yes; then
+ # Warning - without using the other runtime loading flags (-brtl),
+ # -berok will link without error, but may produce a broken library.
+ allow_undefined_flag='-berok'
+ hardcode_libdir_flag_spec='${wl}-blibpath:$libdir:/usr/lib:/lib'
+ archive_expsym_cmds="\$CC"' -o $output_objdir/$soname $libobjs $deplibs $compiler_flags `if test "x${allow_undefined_flag}" != "x"; then echo "${wl}${allow_undefined_flag}"; else :; fi` '"\${wl}$no_entry_flag \${wl}$exp_sym_flag:\$export_symbols $shared_flag"
+ else
+ if test "$host_cpu" = ia64; then
+ hardcode_libdir_flag_spec='${wl}-R $libdir:/usr/lib:/lib'
+ allow_undefined_flag="-z nodefs"
+ archive_expsym_cmds="\$CC $shared_flag"' -o $output_objdir/$soname ${wl}-h$soname $libobjs $deplibs $compiler_flags ${wl}${allow_undefined_flag} '"\${wl}$no_entry_flag \${wl}$exp_sym_flag:\$export_symbols"
+ else
+ hardcode_libdir_flag_spec='${wl}-bnolibpath ${wl}-blibpath:$libdir:/usr/lib:/lib'
+ # Warning - without using the other run time loading flags,
+ # -berok will link without error, but may produce a broken library.
+ allow_undefined_flag='${wl}-berok'
+ # This is a bit strange, but is similar to how AIX traditionally builds
+ # it's shared libraries.
+ archive_expsym_cmds="\$CC $shared_flag"' -o $output_objdir/$soname $libobjs $deplibs $compiler_flags ${allow_undefined_flag} '"\${wl}$no_entry_flag \${wl}$exp_sym_flag:\$export_symbols"' ~$AR -crlo $objdir/$libname$release.a $objdir/$soname'
+ fi
+ fi
+ ;;
+
+ amigaos*)
+ archive_cmds='$rm $output_objdir/a2ixlibrary.data~$echo "#define NAME $libname" > $output_objdir/a2ixlibrary.data~$echo "#define LIBRARY_ID 1" >> $output_objdir/a2ixlibrary.data~$echo "#define VERSION $major" >> $output_objdir/a2ixlibrary.data~$echo "#define REVISION $revision" >> $output_objdir/a2ixlibrary.data~$AR $AR_FLAGS $lib $libobjs~$RANLIB $lib~(cd $output_objdir && a2ixlibrary -32)'
+ hardcode_libdir_flag_spec='-L$libdir'
+ hardcode_minus_L=yes
+ # see comment about different semantics on the GNU ld section
+ ld_shlibs=no
+ ;;
+
+ cygwin* | mingw* | pw32*)
+ # When not using gcc, we currently assume that we are using
+ # Microsoft Visual C++.
+ # hardcode_libdir_flag_spec is actually meaningless, as there is
+ # no search path for DLLs.
+ hardcode_libdir_flag_spec=' '
+ allow_undefined_flag=unsupported
+ # Tell ltmain to make .lib files, not .a files.
+ libext=lib
+ # FIXME: Setting linknames here is a bad hack.
+ archive_cmds='$CC -o $lib $libobjs $compiler_flags `echo "$deplibs" | sed -e '\''s/ -lc$//'\''` -link -dll~linknames='
+ # The linker will automatically build a .lib file if we build a DLL.
+ old_archive_from_new_cmds='true'
+ # FIXME: Should let the user specify the lib program.
+ old_archive_cmds='lib /OUT:$oldlib$oldobjs$old_deplibs'
+ fix_srcfile_path='`cygpath -w "$srcfile"`'
+ ;;
+
+ darwin* | rhapsody*)
+ case "$host_os" in
+ rhapsody* | darwin1.[012])
+ allow_undefined_flag='-undefined suppress'
+ ;;
+ *) # Darwin 1.3 on
+ allow_undefined_flag='-flat_namespace -undefined suppress'
+ ;;
+ esac
+ # FIXME: Relying on posixy $() will cause problems for
+ # cross-compilation, but unfortunately the echo tests do not
+ # yet detect zsh echo's removal of \ escapes.
+ archive_cmds='$nonopt $(test "x$module" = xyes && echo -bundle || echo -dynamiclib) $allow_undefined_flag -o $lib $libobjs $deplibs$linker_flags -install_name $rpath/$soname $verstring'
+ # We need to add '_' to the symbols in $export_symbols first
+ #archive_expsym_cmds="$archive_cmds"' && strip -s $export_symbols'
+ hardcode_direct=yes
+ hardcode_shlibpath_var=no
+ whole_archive_flag_spec='-all_load $convenience'
+ ;;
+
+ freebsd1*)
+ ld_shlibs=no
+ ;;
+
+ # FreeBSD 2.2.[012] allows us to include c++rt0.o to get C++ constructor
+ # support. Future versions do this automatically, but an explicit c++rt0.o
+ # does not break anything, and helps significantly (at the cost of a little
+ # extra space).
+ freebsd2.2*)
+ archive_cmds='$LD -Bshareable -o $lib $libobjs $deplibs $linker_flags /usr/lib/c++rt0.o'
+ hardcode_libdir_flag_spec='-R$libdir'
+ hardcode_direct=yes
+ hardcode_shlibpath_var=no
+ ;;
+
+ # Unfortunately, older versions of FreeBSD 2 do not have this feature.
+ freebsd2*)
+ archive_cmds='$LD -Bshareable -o $lib $libobjs $deplibs $linker_flags'
+ hardcode_direct=yes
+ hardcode_minus_L=yes
+ hardcode_shlibpath_var=no
+ ;;
+
+ # FreeBSD 3 and greater uses gcc -shared to do shared libraries.
+ freebsd*)
+ archive_cmds='$CC -shared -o $lib $libobjs $deplibs $compiler_flags'
+ hardcode_libdir_flag_spec='-R$libdir'
+ hardcode_direct=yes
+ hardcode_shlibpath_var=no
+ ;;
+
+ hpux9* | hpux10* | hpux11*)
+ case $host_os in
+ hpux9*) archive_cmds='$rm $output_objdir/$soname~$LD -b +b $install_libdir -o $output_objdir/$soname $libobjs $deplibs $linker_flags~test $output_objdir/$soname = $lib || mv $output_objdir/$soname $lib' ;;
+ *) archive_cmds='$LD -b +h $soname +b $install_libdir -o $lib $libobjs $deplibs $linker_flags' ;;
+ esac
+ hardcode_libdir_flag_spec='${wl}+b ${wl}$libdir'
+ hardcode_libdir_separator=:
+ hardcode_direct=yes
+ hardcode_minus_L=yes # Not in the search PATH, but as the default
+ # location of the library.
+ export_dynamic_flag_spec='${wl}-E'
+ ;;
+
+ irix5* | irix6*)
+ if test "$GCC" = yes; then
+ archive_cmds='$CC -shared $libobjs $deplibs $compiler_flags ${wl}-soname ${wl}$soname `test -n "$verstring" && echo ${wl}-set_version ${wl}$verstring` ${wl}-update_registry ${wl}${output_objdir}/so_locations -o $lib'
+ else
+ archive_cmds='$LD -shared $libobjs $deplibs $linker_flags -soname $soname `test -n "$verstring" && echo -set_version $verstring` -update_registry ${output_objdir}/so_locations -o $lib'
+ fi
+ hardcode_libdir_flag_spec='${wl}-rpath ${wl}$libdir'
+ hardcode_libdir_separator=:
+ link_all_deplibs=yes
+ ;;
+
+ netbsd*)
+ if echo __ELF__ | $CC -E - | grep __ELF__ >/dev/null; then
+ archive_cmds='$LD -Bshareable -o $lib $libobjs $deplibs $linker_flags' # a.out
+ else
+ archive_cmds='$LD -shared -o $lib $libobjs $deplibs $linker_flags' # ELF
+ fi
+ hardcode_libdir_flag_spec='-R$libdir'
+ hardcode_direct=yes
+ hardcode_shlibpath_var=no
+ ;;
+
+ newsos6)
+ archive_cmds='$LD -G -h $soname -o $lib $libobjs $deplibs $linker_flags'
+ hardcode_direct=yes
+ hardcode_libdir_flag_spec='${wl}-rpath ${wl}$libdir'
+ hardcode_libdir_separator=:
+ hardcode_shlibpath_var=no
+ ;;
+
+ openbsd*)
+ hardcode_direct=yes
+ hardcode_shlibpath_var=no
+ if test -z "`echo __ELF__ | $CC -E - | grep __ELF__`" || test "$host_os-$host_cpu" = "openbsd2.8-powerpc"; then
+ archive_cmds='$CC -shared $pic_flag -o $lib $libobjs $deplibs $linker_flags'
+ hardcode_libdir_flag_spec='${wl}-rpath,$libdir'
+ export_dynamic_flag_spec='${wl}-E'
+ else
+ case "$host_os" in
+ openbsd[01].* | openbsd2.[0-7] | openbsd2.[0-7].*)
+ archive_cmds='$LD -Bshareable -o $lib $libobjs $deplibs $linker_flags'
+ hardcode_libdir_flag_spec='-R$libdir'
+ ;;
+ *)
+ archive_cmds='$CC -shared $pic_flag -o $lib $libobjs $deplibs $linker_flags'
+ hardcode_libdir_flag_spec='${wl}-rpath,$libdir'
+ ;;
+ esac
+ fi
+ ;;
+
+ os2*)
+ hardcode_libdir_flag_spec='-L$libdir'
+ hardcode_minus_L=yes
+ allow_undefined_flag=unsupported
+ archive_cmds='$echo "LIBRARY $libname INITINSTANCE" > $output_objdir/$libname.def~$echo "DESCRIPTION \"$libname\"" >> $output_objdir/$libname.def~$echo DATA >> $output_objdir/$libname.def~$echo " SINGLE NONSHARED" >> $output_objdir/$libname.def~$echo EXPORTS >> $output_objdir/$libname.def~emxexp $libobjs >> $output_objdir/$libname.def~$CC -Zdll -Zcrtdll -o $lib $libobjs $deplibs $compiler_flags $output_objdir/$libname.def'
+ old_archive_from_new_cmds='emximp -o $output_objdir/$libname.a $output_objdir/$libname.def'
+ ;;
+
+ osf3*)
+ if test "$GCC" = yes; then
+ allow_undefined_flag=' ${wl}-expect_unresolved ${wl}\*'
+ archive_cmds='$CC -shared${allow_undefined_flag} $libobjs $deplibs $compiler_flags ${wl}-soname ${wl}$soname `test -n "$verstring" && echo ${wl}-set_version ${wl}$verstring` ${wl}-update_registry ${wl}${output_objdir}/so_locations -o $lib'
+ else
+ allow_undefined_flag=' -expect_unresolved \*'
+ archive_cmds='$LD -shared${allow_undefined_flag} $libobjs $deplibs $linker_flags -soname $soname `test -n "$verstring" && echo -set_version $verstring` -update_registry ${output_objdir}/so_locations -o $lib'
+ fi
+ hardcode_libdir_flag_spec='${wl}-rpath ${wl}$libdir'
+ hardcode_libdir_separator=:
+ ;;
+
+ osf4* | osf5*) # as osf3* with the addition of -msym flag
+ if test "$GCC" = yes; then
+ allow_undefined_flag=' ${wl}-expect_unresolved ${wl}\*'
+ archive_cmds='$CC -shared${allow_undefined_flag} $libobjs $deplibs $compiler_flags ${wl}-msym ${wl}-soname ${wl}$soname `test -n "$verstring" && echo ${wl}-set_version ${wl}$verstring` ${wl}-update_registry ${wl}${output_objdir}/so_locations -o $lib'
+ hardcode_libdir_flag_spec='${wl}-rpath ${wl}$libdir'
+ else
+ allow_undefined_flag=' -expect_unresolved \*'
+ archive_cmds='$LD -shared${allow_undefined_flag} $libobjs $deplibs $linker_flags -msym -soname $soname `test -n "$verstring" && echo -set_version $verstring` -update_registry ${output_objdir}/so_locations -o $lib'
+ archive_expsym_cmds='for i in `cat $export_symbols`; do printf "-exported_symbol " >> $lib.exp; echo "\$i" >> $lib.exp; done; echo "-hidden">> $lib.exp~
+ $LD -shared${allow_undefined_flag} -input $lib.exp $linker_flags $libobjs $deplibs -soname $soname `test -n "$verstring" && echo -set_version $verstring` -update_registry ${objdir}/so_locations -o $lib~$rm $lib.exp'
+
+ #Both c and cxx compiler support -rpath directly
+ hardcode_libdir_flag_spec='-rpath $libdir'
+ fi
+ hardcode_libdir_separator=:
+ ;;
+
+ sco3.2v5*)
+ archive_cmds='$LD -G -h $soname -o $lib $libobjs $deplibs $linker_flags'
+ hardcode_shlibpath_var=no
+ runpath_var=LD_RUN_PATH
+ hardcode_runpath_var=yes
+ export_dynamic_flag_spec='${wl}-Bexport'
+ ;;
+
+ solaris*)
+ # gcc --version < 3.0 without binutils cannot create self contained
+ # shared libraries reliably, requiring libgcc.a to resolve some of
+ # the object symbols generated in some cases. Libraries that use
+ # assert need libgcc.a to resolve __eprintf, for example. Linking
+ # a copy of libgcc.a into every shared library to guarantee resolving
+ # such symbols causes other problems: According to Tim Van Holder
+ # <tim.van.holder@pandora.be>, C++ libraries end up with a separate
+ # (to the application) exception stack for one thing.
+ no_undefined_flag=' -z defs'
+ if test "$GCC" = yes; then
+ case `$CC --version 2>/dev/null` in
+ [12].*)
+ cat <<EOF 1>&2
+
+*** Warning: Releases of GCC earlier than version 3.0 cannot reliably
+*** create self contained shared libraries on Solaris systems, without
+*** introducing a dependency on libgcc.a. Therefore, libtool is disabling
+*** -no-undefined support, which will at least allow you to build shared
+*** libraries. However, you may find that when you link such libraries
+*** into an application without using GCC, you have to manually add
+*** \`gcc --print-libgcc-file-name\` to the link command. We urge you to
+*** upgrade to a newer version of GCC. Another option is to rebuild your
+*** current GCC to use the GNU linker from GNU binutils 2.9.1 or newer.
+
+EOF
+ no_undefined_flag=
+ ;;
+ esac
+ fi
+ # $CC -shared without GNU ld will not create a library from C++
+ # object files and a static libstdc++, better avoid it by now
+ archive_cmds='$LD -G${allow_undefined_flag} -h $soname -o $lib $libobjs $deplibs $linker_flags'
+ archive_expsym_cmds='$echo "{ global:" > $lib.exp~cat $export_symbols | sed -e "s/\(.*\)/\1;/" >> $lib.exp~$echo "local: *; };" >> $lib.exp~
+ $LD -G${allow_undefined_flag} -M $lib.exp -h $soname -o $lib $libobjs $deplibs $linker_flags~$rm $lib.exp'
+ hardcode_libdir_flag_spec='-R$libdir'
+ hardcode_shlibpath_var=no
+ case $host_os in
+ solaris2.[0-5] | solaris2.[0-5].*) ;;
+ *) # Supported since Solaris 2.6 (maybe 2.5.1?)
+ whole_archive_flag_spec='-z allextract$convenience -z defaultextract' ;;
+ esac
+ link_all_deplibs=yes
+ ;;
+
+ sunos4*)
+ if test "x$host_vendor" = xsequent; then
+ # Use $CC to link under sequent, because it throws in some extra .o
+ # files that make .init and .fini sections work.
+ archive_cmds='$CC -G ${wl}-h $soname -o $lib $libobjs $deplibs $compiler_flags'
+ else
+ archive_cmds='$LD -assert pure-text -Bstatic -o $lib $libobjs $deplibs $linker_flags'
+ fi
+ hardcode_libdir_flag_spec='-L$libdir'
+ hardcode_direct=yes
+ hardcode_minus_L=yes
+ hardcode_shlibpath_var=no
+ ;;
+
+ sysv4)
+ if test "x$host_vendor" = xsno; then
+ archive_cmds='$LD -G -Bsymbolic -h $soname -o $lib $libobjs $deplibs $linker_flags'
+ hardcode_direct=yes # is this really true???
+ else
+ archive_cmds='$LD -G -h $soname -o $lib $libobjs $deplibs $linker_flags'
+ hardcode_direct=no #Motorola manual says yes, but my tests say they lie
+ fi
+ runpath_var='LD_RUN_PATH'
+ hardcode_shlibpath_var=no
+ ;;
+
+ sysv4.3*)
+ archive_cmds='$LD -G -h $soname -o $lib $libobjs $deplibs $linker_flags'
+ hardcode_shlibpath_var=no
+ export_dynamic_flag_spec='-Bexport'
+ ;;
+
+ sysv5*)
+ no_undefined_flag=' -z text'
+ # $CC -shared without GNU ld will not create a library from C++
+ # object files and a static libstdc++, better avoid it by now
+ archive_cmds='$LD -G${allow_undefined_flag} -h $soname -o $lib $libobjs $deplibs $linker_flags'
+ archive_expsym_cmds='$echo "{ global:" > $lib.exp~cat $export_symbols | sed -e "s/\(.*\)/\1;/" >> $lib.exp~$echo "local: *; };" >> $lib.exp~
+ $LD -G${allow_undefined_flag} -M $lib.exp -h $soname -o $lib $libobjs $deplibs $linker_flags~$rm $lib.exp'
+ hardcode_libdir_flag_spec=
+ hardcode_shlibpath_var=no
+ runpath_var='LD_RUN_PATH'
+ ;;
+
+ uts4*)
+ archive_cmds='$LD -G -h $soname -o $lib $libobjs $deplibs $linker_flags'
+ hardcode_libdir_flag_spec='-L$libdir'
+ hardcode_shlibpath_var=no
+ ;;
+
+ dgux*)
+ archive_cmds='$LD -G -h $soname -o $lib $libobjs $deplibs $linker_flags'
+ hardcode_libdir_flag_spec='-L$libdir'
+ hardcode_shlibpath_var=no
+ ;;
+
+ sysv4*MP*)
+ if test -d /usr/nec; then
+ archive_cmds='$LD -G -h $soname -o $lib $libobjs $deplibs $linker_flags'
+ hardcode_shlibpath_var=no
+ runpath_var=LD_RUN_PATH
+ hardcode_runpath_var=yes
+ ld_shlibs=yes
+ fi
+ ;;
+
+ sysv4.2uw2*)
+ archive_cmds='$LD -G -o $lib $libobjs $deplibs $linker_flags'
+ hardcode_direct=yes
+ hardcode_minus_L=no
+ hardcode_shlibpath_var=no
+ hardcode_runpath_var=yes
+ runpath_var=LD_RUN_PATH
+ ;;
+
+ sysv5uw7* | unixware7*)
+ no_undefined_flag='${wl}-z ${wl}text'
+ if test "$GCC" = yes; then
+ archive_cmds='$CC -shared ${wl}-h ${wl}$soname -o $lib $libobjs $deplibs $compiler_flags'
+ else
+ archive_cmds='$CC -G ${wl}-h ${wl}$soname -o $lib $libobjs $deplibs $compiler_flags'
+ fi
+ runpath_var='LD_RUN_PATH'
+ hardcode_shlibpath_var=no
+ ;;
+
+ *)
+ ld_shlibs=no
+ ;;
+ esac
+fi
+echo "$ac_t""$ld_shlibs" 1>&6
+test "$ld_shlibs" = no && can_build_shared=no
+##
+## END FIXME
+
+## FIXME: this should be a separate macro
+##
+# Check hardcoding attributes.
+echo $ac_n "checking how to hardcode library paths into programs""... $ac_c" 1>&6
+echo "configure:3799: checking how to hardcode library paths into programs" >&5
+hardcode_action=
+if test -n "$hardcode_libdir_flag_spec" || \
+ test -n "$runpath_var"; then
+
+ # We can hardcode non-existant directories.
+ if test "$hardcode_direct" != no &&
+ # If the only mechanism to avoid hardcoding is shlibpath_var, we
+ # have to relink, otherwise we might link with an installed library
+ # when we should be linking with a yet-to-be-installed one
+ ## test "$hardcode_shlibpath_var" != no &&
+ test "$hardcode_minus_L" != no; then
+ # Linking always hardcodes the temporary library directory.
+ hardcode_action=relink
+ else
+ # We can link without hardcoding, and we can hardcode nonexisting dirs.
+ hardcode_action=immediate
+ fi
+else
+ # We cannot hardcode anything, or else we can only hardcode existing
+ # directories.
+ hardcode_action=unsupported
+fi
+echo "$ac_t""$hardcode_action" 1>&6
+##
+## END FIXME
+
+## FIXME: this should be a separate macro
+##
+striplib=
+old_striplib=
+echo $ac_n "checking whether stripping libraries is possible""... $ac_c" 1>&6
+echo "configure:3831: checking whether stripping libraries is possible" >&5
+if test -n "$STRIP" && $STRIP -V 2>&1 | grep "GNU strip" >/dev/null; then
+ test -z "$old_striplib" && old_striplib="$STRIP --strip-debug"
+ test -z "$striplib" && striplib="$STRIP --strip-unneeded"
+ echo "$ac_t""yes" 1>&6
+else
+ echo "$ac_t""no" 1>&6
+fi
+##
+## END FIXME
+
+reload_cmds='$LD$reload_flag -o $output$reload_objs'
+test -z "$deplibs_check_method" && deplibs_check_method=unknown
+
+## FIXME: this should be a separate macro
+##
+# PORTME Fill in your ld.so characteristics
+echo $ac_n "checking dynamic linker characteristics""... $ac_c" 1>&6
+echo "configure:3849: checking dynamic linker characteristics" >&5
+library_names_spec=
+libname_spec='lib$name'
+soname_spec=
+postinstall_cmds=
+postuninstall_cmds=
+finish_cmds=
+finish_eval=
+shlibpath_var=
+shlibpath_overrides_runpath=unknown
+version_type=none
+dynamic_linker="$host_os ld.so"
+sys_lib_dlsearch_path_spec="/lib /usr/lib"
+sys_lib_search_path_spec="/lib /usr/lib /usr/local/lib"
+
+case $host_os in
+aix3*)
+ version_type=linux
+ library_names_spec='${libname}${release}.so$versuffix $libname.a'
+ shlibpath_var=LIBPATH
+
+ # AIX has no versioning support, so we append a major version to the name.
+ soname_spec='${libname}${release}.so$major'
+ ;;
+
+aix4* | aix5*)
+ version_type=linux
+ if test "$host_cpu" = ia64; then
+ # AIX 5 supports IA64
+ library_names_spec='${libname}${release}.so$major ${libname}${release}.so$versuffix $libname.so'
+ shlibpath_var=LD_LIBRARY_PATH
+ else
+ # With GCC up to 2.95.x, collect2 would create an import file
+ # for dependence libraries. The import file would start with
+ # the line `#! .'. This would cause the generated library to
+ # depend on `.', always an invalid library. This was fixed in
+ # development snapshots of GCC prior to 3.0.
+ case $host_os in
+ aix4 | aix4.[01] | aix4.[01].*)
+ if { echo '#if __GNUC__ > 2 || (__GNUC__ == 2 && __GNUC_MINOR__ >= 97)'
+ echo ' yes '
+ echo '#endif'; } | ${CC} -E - | grep yes > /dev/null; then
+ :
+ else
+ can_build_shared=no
+ fi
+ ;;
+ esac
+ # AIX (on Power*) has no versioning support, so currently we can
+ # not hardcode correct soname into executable. Probably we can
+ # add versioning support to collect2, so additional links can
+ # be useful in future.
+ if test "$aix_use_runtimelinking" = yes; then
+ # If using run time linking (on AIX 4.2 or later) use lib<name>.so
+ # instead of lib<name>.a to let people know that these are not
+ # typical AIX shared libraries.
+ library_names_spec='${libname}${release}.so$versuffix ${libname}${release}.so$major $libname.so'
+ else
+ # We preserve .a as extension for shared libraries through AIX4.2
+ # and later when we are not doing run time linking.
+ library_names_spec='${libname}${release}.a $libname.a'
+ soname_spec='${libname}${release}.so$major'
+ fi
+ shlibpath_var=LIBPATH
+ fi
+ ;;
+
+amigaos*)
+ library_names_spec='$libname.ixlibrary $libname.a'
+ # Create ${libname}_ixlibrary.a entries in /sys/libs.
+ finish_eval='for lib in `ls $libdir/*.ixlibrary 2>/dev/null`; do libname=`$echo "X$lib" | $Xsed -e '\''s%^.*/\([^/]*\)\.ixlibrary$%\1%'\''`; test $rm /sys/libs/${libname}_ixlibrary.a; $show "(cd /sys/libs && $LN_S $lib ${libname}_ixlibrary.a)"; (cd /sys/libs && $LN_S $lib ${libname}_ixlibrary.a) || exit 1; done'
+ ;;
+
+beos*)
+ library_names_spec='${libname}.so'
+ dynamic_linker="$host_os ld.so"
+ shlibpath_var=LIBRARY_PATH
+ ;;
+
+bsdi4*)
+ version_type=linux
+ need_version=no
+ library_names_spec='${libname}${release}.so$versuffix ${libname}${release}.so$major $libname.so'
+ soname_spec='${libname}${release}.so$major'
+ finish_cmds='PATH="\$PATH:/sbin" ldconfig $libdir'
+ shlibpath_var=LD_LIBRARY_PATH
+ sys_lib_search_path_spec="/shlib /usr/lib /usr/X11/lib /usr/contrib/lib /lib /usr/local/lib"
+ sys_lib_dlsearch_path_spec="/shlib /usr/lib /usr/local/lib"
+ export_dynamic_flag_spec=-rdynamic
+ # the default ld.so.conf also contains /usr/contrib/lib and
+ # /usr/X11R6/lib (/usr/X11 is a link to /usr/X11R6), but let us allow
+ # libtool to hard-code these into programs
+ ;;
+
+cygwin* | mingw* | pw32*)
+ version_type=windows
+ need_version=no
+ need_lib_prefix=no
+ case $GCC,$host_os in
+ yes,cygwin*)
+ library_names_spec='$libname.dll.a'
+ soname_spec='`echo ${libname} | sed -e 's/^lib/cyg/'``echo ${release} | sed -e 's/[.]/-/g'`${versuffix}.dll'
+ postinstall_cmds='dlpath=`bash 2>&1 -c '\''. $dir/${file}i;echo \$dlname'\''`~
+ dldir=$destdir/`dirname \$dlpath`~
+ test -d \$dldir || mkdir -p \$dldir~
+ $install_prog .libs/$dlname \$dldir/$dlname'
+ postuninstall_cmds='dldll=`bash 2>&1 -c '\''. $file; echo \$dlname'\''`~
+ dlpath=$dir/\$dldll~
+ $rm \$dlpath'
+ ;;
+ yes,mingw*)
+ library_names_spec='${libname}`echo ${release} | sed -e 's/[.]/-/g'`${versuffix}.dll'
+ sys_lib_search_path_spec=`$CC -print-search-dirs | grep "^libraries:" | sed -e "s/^libraries://" -e "s/;/ /g"`
+ ;;
+ yes,pw32*)
+ library_names_spec='`echo ${libname} | sed -e 's/^lib/pw/'``echo ${release} | sed -e 's/./-/g'`${versuffix}.dll'
+ ;;
+ *)
+ library_names_spec='${libname}`echo ${release} | sed -e 's/[.]/-/g'`${versuffix}.dll $libname.lib'
+ ;;
+ esac
+ dynamic_linker='Win32 ld.exe'
+ # FIXME: first we should search . and the directory the executable is in
+ shlibpath_var=PATH
+ ;;
+
+darwin* | rhapsody*)
+ dynamic_linker="$host_os dyld"
+ version_type=darwin
+ need_lib_prefix=no
+ need_version=no
+ # FIXME: Relying on posixy $() will cause problems for
+ # cross-compilation, but unfortunately the echo tests do not
+ # yet detect zsh echo's removal of \ escapes.
+ library_names_spec='${libname}${release}${versuffix}.$(test .$module = .yes && echo so || echo dylib) ${libname}${release}${major}.$(test .$module = .yes && echo so || echo dylib) ${libname}.$(test .$module = .yes && echo so || echo dylib)'
+ soname_spec='${libname}${release}${major}.$(test .$module = .yes && echo so || echo dylib)'
+ shlibpath_overrides_runpath=yes
+ shlibpath_var=DYLD_LIBRARY_PATH
+ ;;
+
+freebsd1*)
+ dynamic_linker=no
+ ;;
+
+freebsd*)
+ objformat=`test -x /usr/bin/objformat && /usr/bin/objformat || echo aout`
+ version_type=freebsd-$objformat
+ case $version_type in
+ freebsd-elf*)
+ library_names_spec='${libname}${release}.so$versuffix ${libname}${release}.so $libname.so'
+ need_version=no
+ need_lib_prefix=no
+ ;;
+ freebsd-*)
+ library_names_spec='${libname}${release}.so$versuffix $libname.so$versuffix'
+ need_version=yes
+ ;;
+ esac
+ shlibpath_var=LD_LIBRARY_PATH
+ case $host_os in
+ freebsd2*)
+ shlibpath_overrides_runpath=yes
+ ;;
+ *)
+ shlibpath_overrides_runpath=no
+ hardcode_into_libs=yes
+ ;;
+ esac
+ ;;
+
+gnu*)
+ version_type=linux
+ need_lib_prefix=no
+ need_version=no
+ library_names_spec='${libname}${release}.so$versuffix ${libname}${release}.so${major} ${libname}.so'
+ soname_spec='${libname}${release}.so$major'
+ shlibpath_var=LD_LIBRARY_PATH
+ hardcode_into_libs=yes
+ ;;
+
+hpux9* | hpux10* | hpux11*)
+ # Give a soname corresponding to the major version so that dld.sl refuses to
+ # link against other versions.
+ dynamic_linker="$host_os dld.sl"
+ version_type=sunos
+ need_lib_prefix=no
+ need_version=no
+ shlibpath_var=SHLIB_PATH
+ shlibpath_overrides_runpath=no # +s is required to enable SHLIB_PATH
+ library_names_spec='${libname}${release}.sl$versuffix ${libname}${release}.sl$major $libname.sl'
+ soname_spec='${libname}${release}.sl$major'
+ # HP-UX runs *really* slowly unless shared libraries are mode 555.
+ postinstall_cmds='chmod 555 $lib'
+ ;;
+
+irix5* | irix6*)
+ version_type=irix
+ need_lib_prefix=no
+ need_version=no
+ soname_spec='${libname}${release}.so$major'
+ library_names_spec='${libname}${release}.so$versuffix ${libname}${release}.so$major ${libname}${release}.so $libname.so'
+ case $host_os in
+ irix5*)
+ libsuff= shlibsuff=
+ ;;
+ *)
+ case $LD in # libtool.m4 will add one of these switches to LD
+ *-32|*"-32 ") libsuff= shlibsuff= libmagic=32-bit;;
+ *-n32|*"-n32 ") libsuff=32 shlibsuff=N32 libmagic=N32;;
+ *-64|*"-64 ") libsuff=64 shlibsuff=64 libmagic=64-bit;;
+ *) libsuff= shlibsuff= libmagic=never-match;;
+ esac
+ ;;
+ esac
+ shlibpath_var=LD_LIBRARY${shlibsuff}_PATH
+ shlibpath_overrides_runpath=no
+ sys_lib_search_path_spec="/usr/lib${libsuff} /lib${libsuff} /usr/local/lib${libsuff}"
+ sys_lib_dlsearch_path_spec="/usr/lib${libsuff} /lib${libsuff}"
+ ;;
+
+# No shared lib support for Linux oldld, aout, or coff.
+linux-gnuoldld* | linux-gnuaout* | linux-gnucoff*)
+ dynamic_linker=no
+ ;;
+
+# This must be Linux ELF.
+linux-gnu*)
+ version_type=linux
+ need_lib_prefix=no
+ need_version=no
+ library_names_spec='${libname}${release}.so$versuffix ${libname}${release}.so$major $libname.so'
+ soname_spec='${libname}${release}.so$major'
+ finish_cmds='PATH="\$PATH:/sbin" ldconfig -n $libdir'
+ shlibpath_var=LD_LIBRARY_PATH
+ shlibpath_overrides_runpath=no
+ # This implies no fast_install, which is unacceptable.
+ # Some rework will be needed to allow for fast_install
+ # before this can be enabled.
+ hardcode_into_libs=yes
+
+ # We used to test for /lib/ld.so.1 and disable shared libraries on
+ # powerpc, because MkLinux only supported shared libraries with the
+ # GNU dynamic linker. Since this was broken with cross compilers,
+ # most powerpc-linux boxes support dynamic linking these days and
+ # people can always --disable-shared, the test was removed, and we
+ # assume the GNU/Linux dynamic linker is in use.
+ dynamic_linker='GNU/Linux ld.so'
+ ;;
+
+netbsd*)
+ version_type=sunos
+ need_lib_prefix=no
+ need_version=no
+ if echo __ELF__ | $CC -E - | grep __ELF__ >/dev/null; then
+ library_names_spec='${libname}${release}.so$versuffix ${libname}.so$versuffix'
+ finish_cmds='PATH="\$PATH:/sbin" ldconfig -m $libdir'
+ dynamic_linker='NetBSD (a.out) ld.so'
+ else
+ library_names_spec='${libname}${release}.so$versuffix ${libname}${release}.so$major ${libname}${release}.so ${libname}.so'
+ soname_spec='${libname}${release}.so$major'
+ dynamic_linker='NetBSD ld.elf_so'
+ fi
+ shlibpath_var=LD_LIBRARY_PATH
+ shlibpath_overrides_runpath=yes
+ hardcode_into_libs=yes
+ ;;
+
+newsos6)
+ version_type=linux
+ library_names_spec='${libname}${release}.so$versuffix ${libname}${release}.so$major $libname.so'
+ shlibpath_var=LD_LIBRARY_PATH
+ shlibpath_overrides_runpath=yes
+ ;;
+
+openbsd*)
+ version_type=sunos
+ need_lib_prefix=no
+ need_version=no
+ if test -z "`echo __ELF__ | $CC -E - | grep __ELF__`" || test "$host_os-$host_cpu" = "openbsd2.8-powerpc"; then
+ case "$host_os" in
+ openbsd2.[89] | openbsd2.[89].*)
+ shlibpath_overrides_runpath=no
+ ;;
+ *)
+ shlibpath_overrides_runpath=yes
+ ;;
+ esac
+ else
+ shlibpath_overrides_runpath=yes
+ fi
+ library_names_spec='${libname}${release}.so$versuffix ${libname}.so$versuffix'
+ finish_cmds='PATH="\$PATH:/sbin" ldconfig -m $libdir'
+ shlibpath_var=LD_LIBRARY_PATH
+ ;;
+
+os2*)
+ libname_spec='$name'
+ need_lib_prefix=no
+ library_names_spec='$libname.dll $libname.a'
+ dynamic_linker='OS/2 ld.exe'
+ shlibpath_var=LIBPATH
+ ;;
+
+osf3* | osf4* | osf5*)
+ version_type=osf
+ need_version=no
+ soname_spec='${libname}${release}.so'
+ library_names_spec='${libname}${release}.so$versuffix ${libname}${release}.so $libname.so'
+ shlibpath_var=LD_LIBRARY_PATH
+ sys_lib_search_path_spec="/usr/shlib /usr/ccs/lib /usr/lib/cmplrs/cc /usr/lib /usr/local/lib /var/shlib"
+ sys_lib_dlsearch_path_spec="$sys_lib_search_path_spec"
+ ;;
+
+sco3.2v5*)
+ version_type=osf
+ soname_spec='${libname}${release}.so$major'
+ library_names_spec='${libname}${release}.so$versuffix ${libname}${release}.so$major $libname.so'
+ shlibpath_var=LD_LIBRARY_PATH
+ ;;
+
+solaris*)
+ version_type=linux
+ need_lib_prefix=no
+ need_version=no
+ library_names_spec='${libname}${release}.so$versuffix ${libname}${release}.so$major $libname.so'
+ soname_spec='${libname}${release}.so$major'
+ shlibpath_var=LD_LIBRARY_PATH
+ shlibpath_overrides_runpath=yes
+ hardcode_into_libs=yes
+ # ldd complains unless libraries are executable
+ postinstall_cmds='chmod +x $lib'
+ ;;
+
+sunos4*)
+ version_type=sunos
+ library_names_spec='${libname}${release}.so$versuffix ${libname}.so$versuffix'
+ finish_cmds='PATH="\$PATH:/usr/etc" ldconfig $libdir'
+ shlibpath_var=LD_LIBRARY_PATH
+ shlibpath_overrides_runpath=yes
+ if test "$with_gnu_ld" = yes; then
+ need_lib_prefix=no
+ fi
+ need_version=yes
+ ;;
+
+sysv4 | sysv4.2uw2* | sysv4.3* | sysv5*)
+ version_type=linux
+ library_names_spec='${libname}${release}.so$versuffix ${libname}${release}.so$major $libname.so'
+ soname_spec='${libname}${release}.so$major'
+ shlibpath_var=LD_LIBRARY_PATH
+ case $host_vendor in
+ sni)
+ shlibpath_overrides_runpath=no
+ ;;
+ motorola)
+ need_lib_prefix=no
+ need_version=no
+ shlibpath_overrides_runpath=no
+ sys_lib_search_path_spec='/lib /usr/lib /usr/ccs/lib'
+ ;;
+ esac
+ ;;
+
+uts4*)
+ version_type=linux
+ library_names_spec='${libname}${release}.so$versuffix ${libname}${release}.so$major $libname.so'
+ soname_spec='${libname}${release}.so$major'
+ shlibpath_var=LD_LIBRARY_PATH
+ ;;
+
+dgux*)
+ version_type=linux
+ need_lib_prefix=no
+ need_version=no
+ library_names_spec='${libname}${release}.so$versuffix ${libname}${release}.so$major $libname.so'
+ soname_spec='${libname}${release}.so$major'
+ shlibpath_var=LD_LIBRARY_PATH
+ ;;
+
+sysv4*MP*)
+ if test -d /usr/nec ;then
+ version_type=linux
+ library_names_spec='$libname.so.$versuffix $libname.so.$major $libname.so'
+ soname_spec='$libname.so.$major'
+ shlibpath_var=LD_LIBRARY_PATH
+ fi
+ ;;
+
+*)
+ dynamic_linker=no
+ ;;
+esac
+echo "$ac_t""$dynamic_linker" 1>&6
+test "$dynamic_linker" = no && can_build_shared=no
+##
+## END FIXME
+
+## FIXME: this should be a separate macro
+##
+# Report the final consequences.
+echo $ac_n "checking if libtool supports shared libraries""... $ac_c" 1>&6
+echo "configure:4250: checking if libtool supports shared libraries" >&5
+echo "$ac_t""$can_build_shared" 1>&6
+##
+## END FIXME
+
+## FIXME: this should be a separate macro
+##
+echo $ac_n "checking whether to build shared libraries""... $ac_c" 1>&6
+echo "configure:4258: checking whether to build shared libraries" >&5
+test "$can_build_shared" = "no" && enable_shared=no
+
+# On AIX, shared libraries and static libraries use the same namespace, and
+# are all built from PIC.
+case "$host_os" in
+aix3*)
+ test "$enable_shared" = yes && enable_static=no
+ if test -n "$RANLIB"; then
+ archive_cmds="$archive_cmds~\$RANLIB \$lib"
+ postinstall_cmds='$RANLIB $lib'
+ fi
+ ;;
+
+aix4*)
+ if test "$host_cpu" != ia64 && test "$aix_use_runtimelinking" = no ; then
+ test "$enable_shared" = yes && enable_static=no
+ fi
+ ;;
+esac
+echo "$ac_t""$enable_shared" 1>&6
+##
+## END FIXME
+
+## FIXME: this should be a separate macro
+##
+echo $ac_n "checking whether to build static libraries""... $ac_c" 1>&6
+echo "configure:4285: checking whether to build static libraries" >&5
+# Make sure either enable_shared or enable_static is yes.
+test "$enable_shared" = yes || enable_static=yes
+echo "$ac_t""$enable_static" 1>&6
+##
+## END FIXME
+
+if test "$hardcode_action" = relink; then
+ # Fast installation is not supported
+ enable_fast_install=no
+elif test "$shlibpath_overrides_runpath" = yes ||
+ test "$enable_shared" = no; then
+ # Fast installation is not necessary
+ enable_fast_install=needless
+fi
+
+variables_saved_for_relink="PATH $shlibpath_var $runpath_var"
+if test "$GCC" = yes; then
+ variables_saved_for_relink="$variables_saved_for_relink GCC_EXEC_PREFIX COMPILER_PATH LIBRARY_PATH"
+fi
+
+if test "x$enable_dlopen" != xyes; then
+ enable_dlopen=unknown
+ enable_dlopen_self=unknown
+ enable_dlopen_self_static=unknown
+else
+ lt_cv_dlopen=no
+ lt_cv_dlopen_libs=
+
+ case $host_os in
+ beos*)
+ lt_cv_dlopen="load_add_on"
+ lt_cv_dlopen_libs=
+ lt_cv_dlopen_self=yes
+ ;;
+
+ cygwin* | mingw* | pw32*)
+ lt_cv_dlopen="LoadLibrary"
+ lt_cv_dlopen_libs=
+ ;;
+
+ *)
+ echo $ac_n "checking for shl_load""... $ac_c" 1>&6
+echo "configure:4328: checking for shl_load" >&5
+if eval "test \"`echo '$''{'ac_cv_func_shl_load'+set}'`\" = set"; then
+ echo $ac_n "(cached) $ac_c" 1>&6
+else
+ cat > conftest.$ac_ext <<EOF
+#line 4333 "configure"
+#include "confdefs.h"
+/* System header to define __stub macros and hopefully few prototypes,
+ which can conflict with char shl_load(); below. */
+#include <assert.h>
+/* Override any gcc2 internal prototype to avoid an error. */
+/* We use char because int might match the return type of a gcc2
+ builtin and then its argument prototype would still apply. */
+char shl_load();
+
+int main() {
+
+/* The GNU C library defines this for functions which it implements
+ to always fail with ENOSYS. Some functions are actually named
+ something starting with __ and the normal name is an alias. */
+#if defined (__stub_shl_load) || defined (__stub___shl_load)
+choke me
+#else
+shl_load();
+#endif
+
+; return 0; }
+EOF
+if { (eval echo configure:4356: \"$ac_link\") 1>&5; (eval $ac_link) 2>&5; } && test -s conftest${ac_exeext}; then
+ rm -rf conftest*
+ eval "ac_cv_func_shl_load=yes"
+else
+ echo "configure: failed program was:" >&5
+ cat conftest.$ac_ext >&5
+ rm -rf conftest*
+ eval "ac_cv_func_shl_load=no"
+fi
+rm -f conftest*
+fi
+
+if eval "test \"`echo '$ac_cv_func_'shl_load`\" = yes"; then
+ echo "$ac_t""yes" 1>&6
+ lt_cv_dlopen="shl_load"
+else
+ echo "$ac_t""no" 1>&6
+echo $ac_n "checking for shl_load in -ldld""... $ac_c" 1>&6
+echo "configure:4374: checking for shl_load in -ldld" >&5
+ac_lib_var=`echo dld'_'shl_load | sed 'y%./+-%__p_%'`
+if eval "test \"`echo '$''{'ac_cv_lib_$ac_lib_var'+set}'`\" = set"; then
+ echo $ac_n "(cached) $ac_c" 1>&6
+else
+ ac_save_LIBS="$LIBS"
+LIBS="-ldld $LIBS"
+cat > conftest.$ac_ext <<EOF
+#line 4382 "configure"
+#include "confdefs.h"
+/* Override any gcc2 internal prototype to avoid an error. */
+/* We use char because int might match the return type of a gcc2
+ builtin and then its argument prototype would still apply. */
+char shl_load();
+
+int main() {
+shl_load()
+; return 0; }
+EOF
+if { (eval echo configure:4393: \"$ac_link\") 1>&5; (eval $ac_link) 2>&5; } && test -s conftest${ac_exeext}; then
+ rm -rf conftest*
+ eval "ac_cv_lib_$ac_lib_var=yes"
+else
+ echo "configure: failed program was:" >&5
+ cat conftest.$ac_ext >&5
+ rm -rf conftest*
+ eval "ac_cv_lib_$ac_lib_var=no"
+fi
+rm -f conftest*
+LIBS="$ac_save_LIBS"
+
+fi
+if eval "test \"`echo '$ac_cv_lib_'$ac_lib_var`\" = yes"; then
+ echo "$ac_t""yes" 1>&6
+ lt_cv_dlopen="shl_load" lt_cv_dlopen_libs="-dld"
+else
+ echo "$ac_t""no" 1>&6
+echo $ac_n "checking for dlopen""... $ac_c" 1>&6
+echo "configure:4412: checking for dlopen" >&5
+if eval "test \"`echo '$''{'ac_cv_func_dlopen'+set}'`\" = set"; then
+ echo $ac_n "(cached) $ac_c" 1>&6
+else
+ cat > conftest.$ac_ext <<EOF
+#line 4417 "configure"
+#include "confdefs.h"
+/* System header to define __stub macros and hopefully few prototypes,
+ which can conflict with char dlopen(); below. */
+#include <assert.h>
+/* Override any gcc2 internal prototype to avoid an error. */
+/* We use char because int might match the return type of a gcc2
+ builtin and then its argument prototype would still apply. */
+char dlopen();
+
+int main() {
+
+/* The GNU C library defines this for functions which it implements
+ to always fail with ENOSYS. Some functions are actually named
+ something starting with __ and the normal name is an alias. */
+#if defined (__stub_dlopen) || defined (__stub___dlopen)
+choke me
+#else
+dlopen();
+#endif
+
+; return 0; }
+EOF
+if { (eval echo configure:4440: \"$ac_link\") 1>&5; (eval $ac_link) 2>&5; } && test -s conftest${ac_exeext}; then
+ rm -rf conftest*
+ eval "ac_cv_func_dlopen=yes"
+else
+ echo "configure: failed program was:" >&5
+ cat conftest.$ac_ext >&5
+ rm -rf conftest*
+ eval "ac_cv_func_dlopen=no"
+fi
+rm -f conftest*
+fi
+
+if eval "test \"`echo '$ac_cv_func_'dlopen`\" = yes"; then
+ echo "$ac_t""yes" 1>&6
+ lt_cv_dlopen="dlopen"
+else
+ echo "$ac_t""no" 1>&6
+echo $ac_n "checking for dlopen in -ldl""... $ac_c" 1>&6
+echo "configure:4458: checking for dlopen in -ldl" >&5
+ac_lib_var=`echo dl'_'dlopen | sed 'y%./+-%__p_%'`
+if eval "test \"`echo '$''{'ac_cv_lib_$ac_lib_var'+set}'`\" = set"; then
+ echo $ac_n "(cached) $ac_c" 1>&6
+else
+ ac_save_LIBS="$LIBS"
+LIBS="-ldl $LIBS"
+cat > conftest.$ac_ext <<EOF
+#line 4466 "configure"
+#include "confdefs.h"
+/* Override any gcc2 internal prototype to avoid an error. */
+/* We use char because int might match the return type of a gcc2
+ builtin and then its argument prototype would still apply. */
+char dlopen();
+
+int main() {
+dlopen()
+; return 0; }
+EOF
+if { (eval echo configure:4477: \"$ac_link\") 1>&5; (eval $ac_link) 2>&5; } && test -s conftest${ac_exeext}; then
+ rm -rf conftest*
+ eval "ac_cv_lib_$ac_lib_var=yes"
+else
+ echo "configure: failed program was:" >&5
+ cat conftest.$ac_ext >&5
+ rm -rf conftest*
+ eval "ac_cv_lib_$ac_lib_var=no"
+fi
+rm -f conftest*
+LIBS="$ac_save_LIBS"
+
+fi
+if eval "test \"`echo '$ac_cv_lib_'$ac_lib_var`\" = yes"; then
+ echo "$ac_t""yes" 1>&6
+ lt_cv_dlopen="dlopen" lt_cv_dlopen_libs="-ldl"
+else
+ echo "$ac_t""no" 1>&6
+echo $ac_n "checking for dlopen in -lsvld""... $ac_c" 1>&6
+echo "configure:4496: checking for dlopen in -lsvld" >&5
+ac_lib_var=`echo svld'_'dlopen | sed 'y%./+-%__p_%'`
+if eval "test \"`echo '$''{'ac_cv_lib_$ac_lib_var'+set}'`\" = set"; then
+ echo $ac_n "(cached) $ac_c" 1>&6
+else
+ ac_save_LIBS="$LIBS"
+LIBS="-lsvld $LIBS"
+cat > conftest.$ac_ext <<EOF
+#line 4504 "configure"
+#include "confdefs.h"
+/* Override any gcc2 internal prototype to avoid an error. */
+/* We use char because int might match the return type of a gcc2
+ builtin and then its argument prototype would still apply. */
+char dlopen();
+
+int main() {
+dlopen()
+; return 0; }
+EOF
+if { (eval echo configure:4515: \"$ac_link\") 1>&5; (eval $ac_link) 2>&5; } && test -s conftest${ac_exeext}; then
+ rm -rf conftest*
+ eval "ac_cv_lib_$ac_lib_var=yes"
+else
+ echo "configure: failed program was:" >&5
+ cat conftest.$ac_ext >&5
+ rm -rf conftest*
+ eval "ac_cv_lib_$ac_lib_var=no"
+fi
+rm -f conftest*
+LIBS="$ac_save_LIBS"
+
+fi
+if eval "test \"`echo '$ac_cv_lib_'$ac_lib_var`\" = yes"; then
+ echo "$ac_t""yes" 1>&6
+ lt_cv_dlopen="dlopen" lt_cv_dlopen_libs="-lsvld"
+else
+ echo "$ac_t""no" 1>&6
+echo $ac_n "checking for dld_link in -ldld""... $ac_c" 1>&6
+echo "configure:4534: checking for dld_link in -ldld" >&5
+ac_lib_var=`echo dld'_'dld_link | sed 'y%./+-%__p_%'`
+if eval "test \"`echo '$''{'ac_cv_lib_$ac_lib_var'+set}'`\" = set"; then
+ echo $ac_n "(cached) $ac_c" 1>&6
+else
+ ac_save_LIBS="$LIBS"
+LIBS="-ldld $LIBS"
+cat > conftest.$ac_ext <<EOF
+#line 4542 "configure"
+#include "confdefs.h"
+/* Override any gcc2 internal prototype to avoid an error. */
+/* We use char because int might match the return type of a gcc2
+ builtin and then its argument prototype would still apply. */
+char dld_link();
+
+int main() {
+dld_link()
+; return 0; }
+EOF
+if { (eval echo configure:4553: \"$ac_link\") 1>&5; (eval $ac_link) 2>&5; } && test -s conftest${ac_exeext}; then
+ rm -rf conftest*
+ eval "ac_cv_lib_$ac_lib_var=yes"
+else
+ echo "configure: failed program was:" >&5
+ cat conftest.$ac_ext >&5
+ rm -rf conftest*
+ eval "ac_cv_lib_$ac_lib_var=no"
+fi
+rm -f conftest*
+LIBS="$ac_save_LIBS"
+
+fi
+if eval "test \"`echo '$ac_cv_lib_'$ac_lib_var`\" = yes"; then
+ echo "$ac_t""yes" 1>&6
+ lt_cv_dlopen="dld_link" lt_cv_dlopen_libs="-dld"
+else
+ echo "$ac_t""no" 1>&6
+fi
+
+
+fi
+
+
+fi
+
+
+fi
+
+
+fi
+
+
+fi
+
+ ;;
+ esac
+
+ if test "x$lt_cv_dlopen" != xno; then
+ enable_dlopen=yes
+ else
+ enable_dlopen=no
+ fi
+
+ case $lt_cv_dlopen in
+ dlopen)
+ save_CPPFLAGS="$CPPFLAGS"
+ test "x$ac_cv_header_dlfcn_h" = xyes && CPPFLAGS="$CPPFLAGS -DHAVE_DLFCN_H"
+
+ save_LDFLAGS="$LDFLAGS"
+ eval LDFLAGS=\"\$LDFLAGS $export_dynamic_flag_spec\"
+
+ save_LIBS="$LIBS"
+ LIBS="$lt_cv_dlopen_libs $LIBS"
+
+ echo $ac_n "checking whether a program can dlopen itself""... $ac_c" 1>&6
+echo "configure:4609: checking whether a program can dlopen itself" >&5
+if eval "test \"`echo '$''{'lt_cv_dlopen_self'+set}'`\" = set"; then
+ echo $ac_n "(cached) $ac_c" 1>&6
+else
+ if test "$cross_compiling" = yes; then :
+ lt_cv_dlopen_self=cross
+else
+ lt_dlunknown=0; lt_dlno_uscore=1; lt_dlneed_uscore=2
+ lt_status=$lt_dlunknown
+ cat > conftest.$ac_ext <<EOF
+#line 4619 "configure"
+#include "confdefs.h"
+
+#if HAVE_DLFCN_H
+#include <dlfcn.h>
+#endif
+
+#include <stdio.h>
+
+#ifdef RTLD_GLOBAL
+# define LT_DLGLOBAL RTLD_GLOBAL
+#else
+# ifdef DL_GLOBAL
+# define LT_DLGLOBAL DL_GLOBAL
+# else
+# define LT_DLGLOBAL 0
+# endif
+#endif
+
+/* We may have to define LT_DLLAZY_OR_NOW in the command line if we
+ find out it does not work in some platform. */
+#ifndef LT_DLLAZY_OR_NOW
+# ifdef RTLD_LAZY
+# define LT_DLLAZY_OR_NOW RTLD_LAZY
+# else
+# ifdef DL_LAZY
+# define LT_DLLAZY_OR_NOW DL_LAZY
+# else
+# ifdef RTLD_NOW
+# define LT_DLLAZY_OR_NOW RTLD_NOW
+# else
+# ifdef DL_NOW
+# define LT_DLLAZY_OR_NOW DL_NOW
+# else
+# define LT_DLLAZY_OR_NOW 0
+# endif
+# endif
+# endif
+# endif
+#endif
+
+#ifdef __cplusplus
+extern "C" void exit (int);
+#endif
+
+void fnord() { int i=42;}
+int main ()
+{
+ void *self = dlopen (0, LT_DLGLOBAL|LT_DLLAZY_OR_NOW);
+ int status = $lt_dlunknown;
+
+ if (self)
+ {
+ if (dlsym (self,"fnord")) status = $lt_dlno_uscore;
+ else if (dlsym( self,"_fnord")) status = $lt_dlneed_uscore;
+ /* dlclose (self); */
+ }
+
+ exit (status);
+}
+EOF
+ if { (eval echo configure:4680: \"$ac_link\") 1>&5; (eval $ac_link) 2>&5; } && test -s conftest${ac_exeext} 2>/dev/null; then
+ (./conftest; exit; ) 2>/dev/null
+ lt_status=$?
+ case x$lt_status in
+ x$lt_dlno_uscore) lt_cv_dlopen_self=yes ;;
+ x$lt_dlneed_uscore) lt_cv_dlopen_self=yes ;;
+ x$lt_unknown|x*) lt_cv_dlopen_self=no ;;
+ esac
+ else :
+ # compilation failed
+ lt_cv_dlopen_self=no
+ fi
+fi
+rm -fr conftest*
+
+
+fi
+
+echo "$ac_t""$lt_cv_dlopen_self" 1>&6
+
+ if test "x$lt_cv_dlopen_self" = xyes; then
+ LDFLAGS="$LDFLAGS $link_static_flag"
+ echo $ac_n "checking whether a statically linked program can dlopen itself""... $ac_c" 1>&6
+echo "configure:4703: checking whether a statically linked program can dlopen itself" >&5
+if eval "test \"`echo '$''{'lt_cv_dlopen_self_static'+set}'`\" = set"; then
+ echo $ac_n "(cached) $ac_c" 1>&6
+else
+ if test "$cross_compiling" = yes; then :
+ lt_cv_dlopen_self_static=cross
+else
+ lt_dlunknown=0; lt_dlno_uscore=1; lt_dlneed_uscore=2
+ lt_status=$lt_dlunknown
+ cat > conftest.$ac_ext <<EOF
+#line 4713 "configure"
+#include "confdefs.h"
+
+#if HAVE_DLFCN_H
+#include <dlfcn.h>
+#endif
+
+#include <stdio.h>
+
+#ifdef RTLD_GLOBAL
+# define LT_DLGLOBAL RTLD_GLOBAL
+#else
+# ifdef DL_GLOBAL
+# define LT_DLGLOBAL DL_GLOBAL
+# else
+# define LT_DLGLOBAL 0
+# endif
+#endif
+
+/* We may have to define LT_DLLAZY_OR_NOW in the command line if we
+ find out it does not work in some platform. */
+#ifndef LT_DLLAZY_OR_NOW
+# ifdef RTLD_LAZY
+# define LT_DLLAZY_OR_NOW RTLD_LAZY
+# else
+# ifdef DL_LAZY
+# define LT_DLLAZY_OR_NOW DL_LAZY
+# else
+# ifdef RTLD_NOW
+# define LT_DLLAZY_OR_NOW RTLD_NOW
+# else
+# ifdef DL_NOW
+# define LT_DLLAZY_OR_NOW DL_NOW
+# else
+# define LT_DLLAZY_OR_NOW 0
+# endif
+# endif
+# endif
+# endif
+#endif
+
+#ifdef __cplusplus
+extern "C" void exit (int);
+#endif
+
+void fnord() { int i=42;}
+int main ()
+{
+ void *self = dlopen (0, LT_DLGLOBAL|LT_DLLAZY_OR_NOW);
+ int status = $lt_dlunknown;
+
+ if (self)
+ {
+ if (dlsym (self,"fnord")) status = $lt_dlno_uscore;
+ else if (dlsym( self,"_fnord")) status = $lt_dlneed_uscore;
+ /* dlclose (self); */
+ }
+
+ exit (status);
+}
+EOF
+ if { (eval echo configure:4774: \"$ac_link\") 1>&5; (eval $ac_link) 2>&5; } && test -s conftest${ac_exeext} 2>/dev/null; then
+ (./conftest; exit; ) 2>/dev/null
+ lt_status=$?
+ case x$lt_status in
+ x$lt_dlno_uscore) lt_cv_dlopen_self_static=yes ;;
+ x$lt_dlneed_uscore) lt_cv_dlopen_self_static=yes ;;
+ x$lt_unknown|x*) lt_cv_dlopen_self_static=no ;;
+ esac
+ else :
+ # compilation failed
+ lt_cv_dlopen_self_static=no
+ fi
+fi
+rm -fr conftest*
+
+
+fi
+
+echo "$ac_t""$lt_cv_dlopen_self_static" 1>&6
+ fi
+
+ CPPFLAGS="$save_CPPFLAGS"
+ LDFLAGS="$save_LDFLAGS"
+ LIBS="$save_LIBS"
+ ;;
+ esac
+
+ case $lt_cv_dlopen_self in
+ yes|no) enable_dlopen_self=$lt_cv_dlopen_self ;;
+ *) enable_dlopen_self=unknown ;;
+ esac
+
+ case $lt_cv_dlopen_self_static in
+ yes|no) enable_dlopen_self_static=$lt_cv_dlopen_self_static ;;
+ *) enable_dlopen_self_static=unknown ;;
+ esac
+fi
+
+
+## FIXME: this should be a separate macro
+##
+if test "$enable_shared" = yes && test "$GCC" = yes; then
+ case $archive_cmds in
+ *'~'*)
+ # FIXME: we may have to deal with multi-command sequences.
+ ;;
+ '$CC '*)
+ # Test whether the compiler implicitly links with -lc since on some
+ # systems, -lgcc has to come before -lc. If gcc already passes -lc
+ # to ld, don't add -lc before -lgcc.
+ echo $ac_n "checking whether -lc should be explicitly linked in""... $ac_c" 1>&6
+echo "configure:4825: checking whether -lc should be explicitly linked in" >&5
+ if eval "test \"`echo '$''{'lt_cv_archive_cmds_need_lc'+set}'`\" = set"; then
+ echo $ac_n "(cached) $ac_c" 1>&6
+else
+ $rm conftest*
+ echo 'static int dummy;' > conftest.$ac_ext
+
+ if { (eval echo configure:4832: \"$ac_compile\") 1>&5; (eval $ac_compile) 2>&5; }; then
+ soname=conftest
+ lib=conftest
+ libobjs=conftest.$ac_objext
+ deplibs=
+ wl=$lt_cv_prog_cc_wl
+ compiler_flags=-v
+ linker_flags=-v
+ verstring=
+ output_objdir=.
+ libname=conftest
+ save_allow_undefined_flag=$allow_undefined_flag
+ allow_undefined_flag=
+ if { (eval echo configure:4845: \"$archive_cmds 2\>\&1 \| grep \" -lc \" \>/dev/null 2\>\&1\") 1>&5; (eval $archive_cmds 2\>\&1 \| grep \" -lc \" \>/dev/null 2\>\&1) 2>&5; }
+ then
+ lt_cv_archive_cmds_need_lc=no
+ else
+ lt_cv_archive_cmds_need_lc=yes
+ fi
+ allow_undefined_flag=$save_allow_undefined_flag
+ else
+ cat conftest.err 1>&5
+ fi
+fi
+
+ echo "$ac_t""$lt_cv_archive_cmds_need_lc" 1>&6
+ ;;
+ esac
+fi
+need_lc=${lt_cv_archive_cmds_need_lc-yes}
+##
+## END FIXME
+
+## FIXME: this should be a separate macro
+##
+# The second clause should only fire when bootstrapping the
+# libtool distribution, otherwise you forgot to ship ltmain.sh
+# with your package, and you will get complaints that there are
+# no rules to generate ltmain.sh.
+if test -f "$ltmain"; then
+ :
+else
+ # If there is no Makefile yet, we rely on a make rule to execute
+ # `config.status --recheck' to rerun these tests and create the
+ # libtool script then.
+ test -f Makefile && make "$ltmain"
+fi
+
+if test -f "$ltmain"; then
+ trap "$rm \"${ofile}T\"; exit 1" 1 2 15
+ $rm -f "${ofile}T"
+
+ echo creating $ofile
+
+ # Now quote all the things that may contain metacharacters while being
+ # careful not to overquote the AC_SUBSTed values. We take copies of the
+ # variables and quote the copies for generation of the libtool script.
+ for var in echo old_CC old_CFLAGS \
+ AR AR_FLAGS CC LD LN_S NM SHELL \
+ reload_flag reload_cmds wl \
+ pic_flag link_static_flag no_builtin_flag export_dynamic_flag_spec \
+ thread_safe_flag_spec whole_archive_flag_spec libname_spec \
+ library_names_spec soname_spec \
+ RANLIB old_archive_cmds old_archive_from_new_cmds old_postinstall_cmds \
+ old_postuninstall_cmds archive_cmds archive_expsym_cmds postinstall_cmds \
+ postuninstall_cmds extract_expsyms_cmds old_archive_from_expsyms_cmds \
+ old_striplib striplib file_magic_cmd export_symbols_cmds \
+ deplibs_check_method allow_undefined_flag no_undefined_flag \
+ finish_cmds finish_eval global_symbol_pipe global_symbol_to_cdecl \
+ global_symbol_to_c_name_address \
+ hardcode_libdir_flag_spec hardcode_libdir_separator \
+ sys_lib_search_path_spec sys_lib_dlsearch_path_spec \
+ compiler_c_o compiler_o_lo need_locks exclude_expsyms include_expsyms; do
+
+ case $var in
+ reload_cmds | old_archive_cmds | old_archive_from_new_cmds | \
+ old_postinstall_cmds | old_postuninstall_cmds | \
+ export_symbols_cmds | archive_cmds | archive_expsym_cmds | \
+ extract_expsyms_cmds | old_archive_from_expsyms_cmds | \
+ postinstall_cmds | postuninstall_cmds | \
+ finish_cmds | sys_lib_search_path_spec | sys_lib_dlsearch_path_spec)
+ # Double-quote double-evaled strings.
+ eval "lt_$var=\\\"\`\$echo \"X\$$var\" | \$Xsed -e \"\$double_quote_subst\" -e \"\$sed_quote_subst\" -e \"\$delay_variable_subst\"\`\\\""
+ ;;
+ *)
+ eval "lt_$var=\\\"\`\$echo \"X\$$var\" | \$Xsed -e \"\$sed_quote_subst\"\`\\\""
+ ;;
+ esac
+ done
+
+ cat <<__EOF__ > "${ofile}T"
+#! $SHELL
+
+# `$echo "$ofile" | sed 's%^.*/%%'` - Provide generalized library-building support services.
+# Generated automatically by $PROGRAM (GNU $PACKAGE $VERSION$TIMESTAMP)
+# NOTE: Changes made to this file will be lost: look at ltmain.sh.
+#
+# Copyright (C) 1996-2000 Free Software Foundation, Inc.
+# Originally by Gordon Matzigkeit <gord@gnu.ai.mit.edu>, 1996
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful, but
+# WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+# General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+#
+# As a special exception to the GNU General Public License, if you
+# distribute this file as part of a program that contains a
+# configuration script generated by Autoconf, you may include it under
+# the same distribution terms that you use for the rest of that program.
+
+# Sed that helps us avoid accidentally triggering echo(1) options like -n.
+Xsed="sed -e s/^X//"
+
+# The HP-UX ksh and POSIX shell print the target directory to stdout
+# if CDPATH is set.
+if test "X\${CDPATH+set}" = Xset; then CDPATH=:; export CDPATH; fi
+
+# ### BEGIN LIBTOOL CONFIG
+
+# Libtool was configured on host `(hostname || uname -n) 2>/dev/null | sed 1q`:
+
+# Shell to use when invoking shell scripts.
+SHELL=$lt_SHELL
+
+# Whether or not to build shared libraries.
+build_libtool_libs=$enable_shared
+
+# Whether or not to build static libraries.
+build_old_libs=$enable_static
+
+# Whether or not to add -lc for building shared libraries.
+build_libtool_need_lc=$need_lc
+
+# Whether or not to optimize for fast installation.
+fast_install=$enable_fast_install
+
+# The host system.
+host_alias=$host_alias
+host=$host
+
+# An echo program that does not interpret backslashes.
+echo=$lt_echo
+
+# The archiver.
+AR=$lt_AR
+AR_FLAGS=$lt_AR_FLAGS
+
+# The default C compiler.
+CC=$lt_CC
+
+# Is the compiler the GNU C compiler?
+with_gcc=$GCC
+
+# The linker used to build libraries.
+LD=$lt_LD
+
+# Whether we need hard or soft links.
+LN_S=$lt_LN_S
+
+# A BSD-compatible nm program.
+NM=$lt_NM
+
+# A symbol stripping program
+STRIP=$STRIP
+
+# Used to examine libraries when file_magic_cmd begins "file"
+MAGIC_CMD=$MAGIC_CMD
+
+# Used on cygwin: DLL creation program.
+DLLTOOL="$DLLTOOL"
+
+# Used on cygwin: object dumper.
+OBJDUMP="$OBJDUMP"
+
+# Used on cygwin: assembler.
+AS="$AS"
+
+# The name of the directory that contains temporary libtool files.
+objdir=$objdir
+
+# How to create reloadable object files.
+reload_flag=$lt_reload_flag
+reload_cmds=$lt_reload_cmds
+
+# How to pass a linker flag through the compiler.
+wl=$lt_wl
+
+# Object file suffix (normally "o").
+objext="$ac_objext"
+
+# Old archive suffix (normally "a").
+libext="$libext"
+
+# Executable file suffix (normally "").
+exeext="$exeext"
+
+# Additional compiler flags for building library objects.
+pic_flag=$lt_pic_flag
+pic_mode=$pic_mode
+
+# Does compiler simultaneously support -c and -o options?
+compiler_c_o=$lt_compiler_c_o
+
+# Can we write directly to a .lo ?
+compiler_o_lo=$lt_compiler_o_lo
+
+# Must we lock files when doing compilation ?
+need_locks=$lt_need_locks
+
+# Do we need the lib prefix for modules?
+need_lib_prefix=$need_lib_prefix
+
+# Do we need a version for libraries?
+need_version=$need_version
+
+# Whether dlopen is supported.
+dlopen_support=$enable_dlopen
+
+# Whether dlopen of programs is supported.
+dlopen_self=$enable_dlopen_self
+
+# Whether dlopen of statically linked programs is supported.
+dlopen_self_static=$enable_dlopen_self_static
+
+# Compiler flag to prevent dynamic linking.
+link_static_flag=$lt_link_static_flag
+
+# Compiler flag to turn off builtin functions.
+no_builtin_flag=$lt_no_builtin_flag
+
+# Compiler flag to allow reflexive dlopens.
+export_dynamic_flag_spec=$lt_export_dynamic_flag_spec
+
+# Compiler flag to generate shared objects directly from archives.
+whole_archive_flag_spec=$lt_whole_archive_flag_spec
+
+# Compiler flag to generate thread-safe objects.
+thread_safe_flag_spec=$lt_thread_safe_flag_spec
+
+# Library versioning type.
+version_type=$version_type
+
+# Format of library name prefix.
+libname_spec=$lt_libname_spec
+
+# List of archive names. First name is the real one, the rest are links.
+# The last name is the one that the linker finds with -lNAME.
+library_names_spec=$lt_library_names_spec
+
+# The coded name of the library, if different from the real name.
+soname_spec=$lt_soname_spec
+
+# Commands used to build and install an old-style archive.
+RANLIB=$lt_RANLIB
+old_archive_cmds=$lt_old_archive_cmds
+old_postinstall_cmds=$lt_old_postinstall_cmds
+old_postuninstall_cmds=$lt_old_postuninstall_cmds
+
+# Create an old-style archive from a shared archive.
+old_archive_from_new_cmds=$lt_old_archive_from_new_cmds
+
+# Create a temporary old-style archive to link instead of a shared archive.
+old_archive_from_expsyms_cmds=$lt_old_archive_from_expsyms_cmds
+
+# Commands used to build and install a shared archive.
+archive_cmds=$lt_archive_cmds
+archive_expsym_cmds=$lt_archive_expsym_cmds
+postinstall_cmds=$lt_postinstall_cmds
+postuninstall_cmds=$lt_postuninstall_cmds
+
+# Commands to strip libraries.
+old_striplib=$lt_old_striplib
+striplib=$lt_striplib
+
+# Method to check whether dependent libraries are shared objects.
+deplibs_check_method=$lt_deplibs_check_method
+
+# Command to use when deplibs_check_method == file_magic.
+file_magic_cmd=$lt_file_magic_cmd
+
+# Flag that allows shared libraries with undefined symbols to be built.
+allow_undefined_flag=$lt_allow_undefined_flag
+
+# Flag that forces no undefined symbols.
+no_undefined_flag=$lt_no_undefined_flag
+
+# Commands used to finish a libtool library installation in a directory.
+finish_cmds=$lt_finish_cmds
+
+# Same as above, but a single script fragment to be evaled but not shown.
+finish_eval=$lt_finish_eval
+
+# Take the output of nm and produce a listing of raw symbols and C names.
+global_symbol_pipe=$lt_global_symbol_pipe
+
+# Transform the output of nm in a proper C declaration
+global_symbol_to_cdecl=$lt_global_symbol_to_cdecl
+
+# Transform the output of nm in a C name address pair
+global_symbol_to_c_name_address=$lt_global_symbol_to_c_name_address
+
+# This is the shared library runtime path variable.
+runpath_var=$runpath_var
+
+# This is the shared library path variable.
+shlibpath_var=$shlibpath_var
+
+# Is shlibpath searched before the hard-coded library search path?
+shlibpath_overrides_runpath=$shlibpath_overrides_runpath
+
+# How to hardcode a shared library path into an executable.
+hardcode_action=$hardcode_action
+
+# Whether we should hardcode library paths into libraries.
+hardcode_into_libs=$hardcode_into_libs
+
+# Flag to hardcode \$libdir into a binary during linking.
+# This must work even if \$libdir does not exist.
+hardcode_libdir_flag_spec=$lt_hardcode_libdir_flag_spec
+
+# Whether we need a single -rpath flag with a separated argument.
+hardcode_libdir_separator=$lt_hardcode_libdir_separator
+
+# Set to yes if using DIR/libNAME.so during linking hardcodes DIR into the
+# resulting binary.
+hardcode_direct=$hardcode_direct
+
+# Set to yes if using the -LDIR flag during linking hardcodes DIR into the
+# resulting binary.
+hardcode_minus_L=$hardcode_minus_L
+
+# Set to yes if using SHLIBPATH_VAR=DIR during linking hardcodes DIR into
+# the resulting binary.
+hardcode_shlibpath_var=$hardcode_shlibpath_var
+
+# Variables whose values should be saved in libtool wrapper scripts and
+# restored at relink time.
+variables_saved_for_relink="$variables_saved_for_relink"
+
+# Whether libtool must link a program against all its dependency libraries.
+link_all_deplibs=$link_all_deplibs
+
+# Compile-time system search path for libraries
+sys_lib_search_path_spec=$lt_sys_lib_search_path_spec
+
+# Run-time system search path for libraries
+sys_lib_dlsearch_path_spec=$lt_sys_lib_dlsearch_path_spec
+
+# Fix the shell variable \$srcfile for the compiler.
+fix_srcfile_path="$fix_srcfile_path"
+
+# Set to yes if exported symbols are required.
+always_export_symbols=$always_export_symbols
+
+# The commands to list exported symbols.
+export_symbols_cmds=$lt_export_symbols_cmds
+
+# The commands to extract the exported symbol list from a shared archive.
+extract_expsyms_cmds=$lt_extract_expsyms_cmds
+
+# Symbols that should not be listed in the preloaded symbols.
+exclude_expsyms=$lt_exclude_expsyms
+
+# Symbols that must always be exported.
+include_expsyms=$lt_include_expsyms
+
+# ### END LIBTOOL CONFIG
+
+__EOF__
+
+ case $host_os in
+ aix3*)
+ cat <<\EOF >> "${ofile}T"
+
+# AIX sometimes has problems with the GCC collect2 program. For some
+# reason, if we set the COLLECT_NAMES environment variable, the problems
+# vanish in a puff of smoke.
+if test "X${COLLECT_NAMES+set}" != Xset; then
+ COLLECT_NAMES=
+ export COLLECT_NAMES
+fi
+EOF
+ ;;
+ esac
+
+ case $host_os in
+ cygwin* | mingw* | pw32* | os2*)
+ cat <<'EOF' >> "${ofile}T"
+ # This is a source program that is used to create dlls on Windows
+ # Don't remove nor modify the starting and closing comments
+# /* ltdll.c starts here */
+# #define WIN32_LEAN_AND_MEAN
+# #include <windows.h>
+# #undef WIN32_LEAN_AND_MEAN
+# #include <stdio.h>
+#
+# #ifndef __CYGWIN__
+# # ifdef __CYGWIN32__
+# # define __CYGWIN__ __CYGWIN32__
+# # endif
+# #endif
+#
+# #ifdef __cplusplus
+# extern "C" {
+# #endif
+# BOOL APIENTRY DllMain (HINSTANCE hInst, DWORD reason, LPVOID reserved);
+# #ifdef __cplusplus
+# }
+# #endif
+#
+# #ifdef __CYGWIN__
+# #include <cygwin/cygwin_dll.h>
+# DECLARE_CYGWIN_DLL( DllMain );
+# #endif
+# HINSTANCE __hDllInstance_base;
+#
+# BOOL APIENTRY
+# DllMain (HINSTANCE hInst, DWORD reason, LPVOID reserved)
+# {
+# __hDllInstance_base = hInst;
+# return TRUE;
+# }
+# /* ltdll.c ends here */
+ # This is a source program that is used to create import libraries
+ # on Windows for dlls which lack them. Don't remove nor modify the
+ # starting and closing comments
+# /* impgen.c starts here */
+# /* Copyright (C) 1999-2000 Free Software Foundation, Inc.
+#
+# This file is part of GNU libtool.
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+# */
+#
+# #include <stdio.h> /* for printf() */
+# #include <unistd.h> /* for open(), lseek(), read() */
+# #include <fcntl.h> /* for O_RDONLY, O_BINARY */
+# #include <string.h> /* for strdup() */
+#
+# /* O_BINARY isn't required (or even defined sometimes) under Unix */
+# #ifndef O_BINARY
+# #define O_BINARY 0
+# #endif
+#
+# static unsigned int
+# pe_get16 (fd, offset)
+# int fd;
+# int offset;
+# {
+# unsigned char b[2];
+# lseek (fd, offset, SEEK_SET);
+# read (fd, b, 2);
+# return b[0] + (b[1]<<8);
+# }
+#
+# static unsigned int
+# pe_get32 (fd, offset)
+# int fd;
+# int offset;
+# {
+# unsigned char b[4];
+# lseek (fd, offset, SEEK_SET);
+# read (fd, b, 4);
+# return b[0] + (b[1]<<8) + (b[2]<<16) + (b[3]<<24);
+# }
+#
+# static unsigned int
+# pe_as32 (ptr)
+# void *ptr;
+# {
+# unsigned char *b = ptr;
+# return b[0] + (b[1]<<8) + (b[2]<<16) + (b[3]<<24);
+# }
+#
+# int
+# main (argc, argv)
+# int argc;
+# char *argv[];
+# {
+# int dll;
+# unsigned long pe_header_offset, opthdr_ofs, num_entries, i;
+# unsigned long export_rva, export_size, nsections, secptr, expptr;
+# unsigned long name_rvas, nexp;
+# unsigned char *expdata, *erva;
+# char *filename, *dll_name;
+#
+# filename = argv[1];
+#
+# dll = open(filename, O_RDONLY|O_BINARY);
+# if (dll < 1)
+# return 1;
+#
+# dll_name = filename;
+#
+# for (i=0; filename[i]; i++)
+# if (filename[i] == '/' || filename[i] == '\\' || filename[i] == ':')
+# dll_name = filename + i +1;
+#
+# pe_header_offset = pe_get32 (dll, 0x3c);
+# opthdr_ofs = pe_header_offset + 4 + 20;
+# num_entries = pe_get32 (dll, opthdr_ofs + 92);
+#
+# if (num_entries < 1) /* no exports */
+# return 1;
+#
+# export_rva = pe_get32 (dll, opthdr_ofs + 96);
+# export_size = pe_get32 (dll, opthdr_ofs + 100);
+# nsections = pe_get16 (dll, pe_header_offset + 4 +2);
+# secptr = (pe_header_offset + 4 + 20 +
+# pe_get16 (dll, pe_header_offset + 4 + 16));
+#
+# expptr = 0;
+# for (i = 0; i < nsections; i++)
+# {
+# char sname[8];
+# unsigned long secptr1 = secptr + 40 * i;
+# unsigned long vaddr = pe_get32 (dll, secptr1 + 12);
+# unsigned long vsize = pe_get32 (dll, secptr1 + 16);
+# unsigned long fptr = pe_get32 (dll, secptr1 + 20);
+# lseek(dll, secptr1, SEEK_SET);
+# read(dll, sname, 8);
+# if (vaddr <= export_rva && vaddr+vsize > export_rva)
+# {
+# expptr = fptr + (export_rva - vaddr);
+# if (export_rva + export_size > vaddr + vsize)
+# export_size = vsize - (export_rva - vaddr);
+# break;
+# }
+# }
+#
+# expdata = (unsigned char*)malloc(export_size);
+# lseek (dll, expptr, SEEK_SET);
+# read (dll, expdata, export_size);
+# erva = expdata - export_rva;
+#
+# nexp = pe_as32 (expdata+24);
+# name_rvas = pe_as32 (expdata+32);
+#
+# printf ("EXPORTS\n");
+# for (i = 0; i<nexp; i++)
+# {
+# unsigned long name_rva = pe_as32 (erva+name_rvas+i*4);
+# printf ("\t%s @ %ld ;\n", erva+name_rva, 1+ i);
+# }
+#
+# return 0;
+# }
+# /* impgen.c ends here */
+
+EOF
+ ;;
+ esac
+
+ # We use sed instead of cat because bash on DJGPP gets confused if
+ # if finds mixed CR/LF and LF-only lines. Since sed operates in
+ # text mode, it properly converts lines to CR/LF. This bash problem
+ # is reportedly fixed, but why not run on old versions too?
+ sed '$q' "$ltmain" >> "${ofile}T" || (rm -f "${ofile}T"; exit 1)
+
+ mv -f "${ofile}T" "$ofile" || \
+ (rm -f "$ofile" && cp "${ofile}T" "$ofile" && rm -f "${ofile}T")
+ chmod +x "$ofile"
+fi
+##
+## END FIXME
+
+
+
+
+
+# This can be used to rebuild libtool when needed
+LIBTOOL_DEPS="$ac_aux_dir/ltmain.sh"
+
+# Always use our own libtool.
+LIBTOOL='$(SHELL) $(top_builddir)/libtool'
+
+# Prevent multiple expansion
+
+
+ EXTLIB='la'
+ EXTOBJ='lo'
+ LIBTOOL='$(top)/libtool'
+ LIBTOOLCC='$(top)/libtool --mode=compile'
+ LIBTOOLLD='$(top)/libtool --mode=link'
+ CCOUTPUT='-c -o $@ $<'
+else
+
+# Make sure we can run config.sub.
+if ${CONFIG_SHELL-/bin/sh} $ac_config_sub sun4 >/dev/null 2>&1; then :
+else { echo "configure: error: can not run $ac_config_sub" 1>&2; exit 1; }
+fi
+
+echo $ac_n "checking host system type""... $ac_c" 1>&6
+echo "configure:5446: checking host system type" >&5
+
+host_alias=$host
+case "$host_alias" in
+NONE)
+ case $nonopt in
+ NONE)
+ if host_alias=`${CONFIG_SHELL-/bin/sh} $ac_config_guess`; then :
+ else { echo "configure: error: can not guess host type; you must specify one" 1>&2; exit 1; }
+ fi ;;
+ *) host_alias=$nonopt ;;
+ esac ;;
+esac
+
+host=`${CONFIG_SHELL-/bin/sh} $ac_config_sub $host_alias`
+host_cpu=`echo $host | sed 's/^\([^-]*\)-\([^-]*\)-\(.*\)$/\1/'`
+host_vendor=`echo $host | sed 's/^\([^-]*\)-\([^-]*\)-\(.*\)$/\2/'`
+host_os=`echo $host | sed 's/^\([^-]*\)-\([^-]*\)-\(.*\)$/\3/'`
+echo "$ac_t""$host" 1>&6
+
+ EXTLIB='a'
+ EXTOBJ='o'
+ LIBTOOL=''
+ LIBTOOLCC=''
+ LIBTOOLLD=''
+ if test x"$compiler_c_o" = xyes ; then
+ CCOUTPUT='-c -o $@ $<'
+ else
+ CCOUTPUT='-c $< && if test x"$(@F)" != x"$@" ; then mv $(@F) $@ ; fi'
+ fi
+
+fi
+
+
+
+
+
+
+
+
+# Check whether --with-control-dir or --without-control-dir was given.
+if test "${with_control_dir+set}" = set; then
+ withval="$with_control_dir"
+ CONTROLDIR=$with_control_dir
+else
+ CONTROLDIR=$prefix/bin/control
+fi
+
+
+# Check whether --with-db-dir or --without-db-dir was given.
+if test "${with_db_dir+set}" = set; then
+ withval="$with_db_dir"
+ DBDIR=$with_db_dir
+else
+ DBDIR=$prefix/db
+fi
+
+
+# Check whether --with-doc-dir or --without-doc-dir was given.
+if test "${with_doc_dir+set}" = set; then
+ withval="$with_doc_dir"
+ DOCDIR=$with_doc_dir
+else
+ DOCDIR=$prefix/doc
+fi
+
+
+# Check whether --with-etc-dir or --without-etc-dir was given.
+if test "${with_etc_dir+set}" = set; then
+ withval="$with_etc_dir"
+ ETCDIR=$with_etc_dir
+else
+ ETCDIR=$prefix/etc
+fi
+
+
+# Check whether --with-filter-dir or --without-filter-dir was given.
+if test "${with_filter_dir+set}" = set; then
+ withval="$with_filter_dir"
+ FILTERDIR=$with_filter_dir
+else
+ FILTERDIR=$prefix/bin/filter
+fi
+
+
+# Check whether --with-lib-dir or --without-lib-dir was given.
+if test "${with_lib_dir+set}" = set; then
+ withval="$with_lib_dir"
+ LIBDIR=$with_lib_dir
+else
+ LIBDIR=$prefix/lib
+fi
+
+
+# Check whether --with-log-dir or --without-log-dir was given.
+if test "${with_log_dir+set}" = set; then
+ withval="$with_log_dir"
+ LOGDIR=$with_log_dir
+else
+ LOGDIR=$prefix/log
+fi
+
+
+# Check whether --with-run-dir or --without-run-dir was given.
+if test "${with_run_dir+set}" = set; then
+ withval="$with_run_dir"
+ RUNDIR=$with_run_dir
+else
+ RUNDIR=$prefix/run
+fi
+
+
+# Check whether --with-spool-dir or --without-spool-dir was given.
+if test "${with_spool_dir+set}" = set; then
+ withval="$with_spool_dir"
+ SPOOLDIR=$with_spool_dir
+else
+ SPOOLDIR=$prefix/spool
+fi
+
+
+# Check whether --with-tmp-dir or --without-tmp-dir was given.
+if test "${with_tmp_dir+set}" = set; then
+ withval="$with_tmp_dir"
+ tmpdir=$with_tmp_dir
+else
+ tmpdir=$prefix/tmp
+fi
+
+
+
+# Check whether --with-syslog-facility or --without-syslog-facility was given.
+if test "${with_syslog_facility+set}" = set; then
+ withval="$with_syslog_facility"
+ SYSLOG_FACILITY=$with_syslog_facility
+else
+ SYSLOG_FACILITY=none
+fi
+
+
+
+
+# Check whether --with-news-user or --without-news-user was given.
+if test "${with_news_user+set}" = set; then
+ withval="$with_news_user"
+ NEWSUSER=$with_news_user
+else
+ NEWSUSER=news
+fi
+
+
+cat >> confdefs.h <<EOF
+#define NEWSUSER "$NEWSUSER"
+EOF
+
+# Check whether --with-news-group or --without-news-group was given.
+if test "${with_news_group+set}" = set; then
+ withval="$with_news_group"
+ NEWSGRP=$with_news_group
+else
+ NEWSGRP=news
+fi
+
+
+cat >> confdefs.h <<EOF
+#define NEWSGRP "$NEWSGRP"
+EOF
+
+# Check whether --with-news-master or --without-news-master was given.
+if test "${with_news_master+set}" = set; then
+ withval="$with_news_master"
+ NEWSMASTER=$with_news_master
+else
+ NEWSMASTER=usenet
+fi
+
+
+cat >> confdefs.h <<EOF
+#define NEWSMASTER "$NEWSMASTER"
+EOF
+
+
+NEWSUMASK=02
+FILEMODE=0664
+DIRMODE=0775
+RUNDIRMODE=0770
+# Check whether --with-news-umask or --without-news-umask was given.
+if test "${with_news_umask+set}" = set; then
+ withval="$with_news_umask"
+ with_news_umask=`echo "$with_news_umask" | sed 's/^0*//'`
+ if test "x$with_news_umask" = x22 ; then
+ NEWSUMASK=022
+ FILEMODE=0644
+ DIRMODE=0755
+ RUNDIRMODE=0750
+ else
+ if test "x$with_news_umask" != x2 ; then
+ { echo "configure: error: Valid umasks are 02 or 022" 1>&2; exit 1; }
+ fi
+ fi
+fi
+
+
+
+
+
+cat >> confdefs.h <<EOF
+#define ARTFILE_MODE $FILEMODE
+EOF
+
+cat >> confdefs.h <<EOF
+#define BATCHFILE_MODE $FILEMODE
+EOF
+
+cat >> confdefs.h <<EOF
+#define GROUPDIR_MODE $DIRMODE
+EOF
+
+cat >> confdefs.h <<EOF
+#define NEWSUMASK $NEWSUMASK
+EOF
+
+
+INEWSMODE=0550
+# Check whether --enable-setgid-inews or --disable-setgid-inews was given.
+if test "${enable_setgid_inews+set}" = set; then
+ enableval="$enable_setgid_inews"
+ if test "x$enableval" = xyes ; then
+ INEWSMODE=02555
+ fi
+fi
+
+
+
+RNEWSGRP=$NEWSGRP
+RNEWSMODE=0500
+# Check whether --enable-uucp-rnews or --disable-uucp-rnews was given.
+if test "${enable_uucp_rnews+set}" = set; then
+ enableval="$enable_uucp_rnews"
+ if test "x$enableval" = xyes ; then
+ RNEWSGRP=uucp
+ RNEWSMODE=04550
+ fi
+fi
+
+
+
+
+# Check whether --with-log-compress or --without-log-compress was given.
+if test "${with_log_compress+set}" = set; then
+ withval="$with_log_compress"
+ LOG_COMPRESS=$with_log_compress
+else
+ LOG_COMPRESS=gzip
+fi
+
+case "$LOG_COMPRESS" in
+bzip2) LOG_COMPRESSEXT=".bz2" ;;
+gzip) LOG_COMPRESSEXT=".gz" ;;
+*) LOG_COMPRESSEXT=".Z" ;;
+esac
+
+
+
+# Check whether --with-innd-port or --without-innd-port was given.
+if test "${with_innd_port+set}" = set; then
+ withval="$with_innd_port"
+ cat >> confdefs.h <<EOF
+#define INND_PORT $with_innd_port
+EOF
+
+fi
+
+
+# Check whether --enable-ipv6 or --disable-ipv6 was given.
+if test "${enable_ipv6+set}" = set; then
+ enableval="$enable_ipv6"
+ if test "x$enableval" = xyes ; then
+ inn_enable_ipv6_tests=yes
+ cat >> confdefs.h <<\EOF
+#define HAVE_INET6 1
+EOF
+
+ fi
+fi
+
+
+# Check whether --with-max-sockets or --without-max-sockets was given.
+if test "${with_max_sockets+set}" = set; then
+ withval="$with_max_sockets"
+ :
+else
+ with_max_sockets=15
+fi
+
+cat >> confdefs.h <<EOF
+#define MAX_SOCKETS $with_max_sockets
+EOF
+
+
+# Check whether --enable-tagged-hash or --disable-tagged-hash was given.
+if test "${enable_tagged_hash+set}" = set; then
+ enableval="$enable_tagged_hash"
+ if test "x$enableval" = xyes ; then
+ DO_DBZ_TAGGED_HASH=DO
+ cat >> confdefs.h <<\EOF
+#define DO_TAGGED_HASH 1
+EOF
+
+ else
+ DO_DBZ_TAGGED_HASH=DONT
+ fi
+fi
+
+
+
+inn_enable_keywords=0
+# Check whether --enable-keywords or --disable-keywords was given.
+if test "${enable_keywords+set}" = set; then
+ enableval="$enable_keywords"
+ if test x"$enableval" = xyes ; then
+ inn_enable_keywords=1
+ fi
+fi
+
+cat >> confdefs.h <<EOF
+#define DO_KEYWORDS $inn_enable_keywords
+EOF
+
+
+# Check whether --enable-largefiles or --disable-largefiles was given.
+if test "${enable_largefiles+set}" = set; then
+ enableval="$enable_largefiles"
+ case "${enableval}" in
+ yes) inn_enable_largefiles=yes
+ if test x"$DO_DBZ_TAGGED_HASH" = xDO ; then
+{ echo "configure: error: --enable-tagged-hash conflicts with --enable-largefiles." 1>&2; exit 1; }
+ fi ;;
+ no) inn_enable_largefiles=no ;;
+ *) { echo "configure: error: invalid argument to --enable-largefiles" 1>&2; exit 1; } ;;
+ esac
+fi
+
+
+# Check whether --with-sendmail or --without-sendmail was given.
+if test "${with_sendmail+set}" = set; then
+ withval="$with_sendmail"
+ SENDMAIL=$with_sendmail
+fi
+
+
+# Check whether --with-kerberos or --without-kerberos was given.
+if test "${with_kerberos+set}" = set; then
+ withval="$with_kerberos"
+ if test x"$with_kerberos" != xno ; then
+ KRB5_LDFLAGS="-L$with_kerberos/lib"
+ KRB5_INC="-I$with_kerberos/include"
+ fi
+fi
+
+
+# Check whether --with-perl or --without-perl was given.
+if test "${with_perl+set}" = set; then
+ withval="$with_perl"
+ case "${withval}" in
+ yes) DO_PERL=DO
+ cat >> confdefs.h <<\EOF
+#define DO_PERL 1
+EOF
+
+ ;;
+ no) DO_PERL=DONT ;;
+ *) { echo "configure: error: invalid argument to --with-perl" 1>&2; exit 1; } ;;
+ esac
+else
+ DO_PERL=DONT
+fi
+
+
+# Check whether --with-python or --without-python was given.
+if test "${with_python+set}" = set; then
+ withval="$with_python"
+ case "${withval}" in
+ yes) DO_PYTHON=define
+ cat >> confdefs.h <<\EOF
+#define DO_PYTHON 1
+EOF
+
+ ;;
+ no) DO_PYTHON=DONT ;;
+ *) { echo "configure: error: invalid argument to --with-python" 1>&2; exit 1; } ;;
+ esac
+else
+ DO_PYTHON=DONT
+fi
+
+
+HOSTNAME=`hostname 2> /dev/null || uname -n`
+
+
+if test $ac_cv_prog_gcc = yes; then
+ echo $ac_n "checking whether ${CC-cc} needs -traditional""... $ac_c" 1>&6
+echo "configure:5848: checking whether ${CC-cc} needs -traditional" >&5
+if eval "test \"`echo '$''{'ac_cv_prog_gcc_traditional'+set}'`\" = set"; then
+ echo $ac_n "(cached) $ac_c" 1>&6
+else
+ ac_pattern="Autoconf.*'x'"
+ cat > conftest.$ac_ext <<EOF
+#line 5854 "configure"
+#include "confdefs.h"
+#include <sgtty.h>
+Autoconf TIOCGETP
+EOF
+if (eval "$ac_cpp conftest.$ac_ext") 2>&5 |
+ egrep "$ac_pattern" >/dev/null 2>&1; then
+ rm -rf conftest*
+ ac_cv_prog_gcc_traditional=yes
+else
+ rm -rf conftest*
+ ac_cv_prog_gcc_traditional=no
+fi
+rm -f conftest*
+
+
+ if test $ac_cv_prog_gcc_traditional = no; then
+ cat > conftest.$ac_ext <<EOF
+#line 5872 "configure"
+#include "confdefs.h"
+#include <termio.h>
+Autoconf TCGETA
+EOF
+if (eval "$ac_cpp conftest.$ac_ext") 2>&5 |
+ egrep "$ac_pattern" >/dev/null 2>&1; then
+ rm -rf conftest*
+ ac_cv_prog_gcc_traditional=yes
+fi
+rm -f conftest*
+
+ fi
+fi
+
+echo "$ac_t""$ac_cv_prog_gcc_traditional" 1>&6
+ if test $ac_cv_prog_gcc_traditional = yes; then
+ CC="$CC -traditional"
+ fi
+fi
+
+# Extract the first word of "flex", so it can be a program name with args.
+set dummy flex; ac_word=$2
+echo $ac_n "checking for $ac_word""... $ac_c" 1>&6
+echo "configure:5896: checking for $ac_word" >&5
+if eval "test \"`echo '$''{'ac_cv_prog_LEX'+set}'`\" = set"; then
+ echo $ac_n "(cached) $ac_c" 1>&6
+else
+ if test -n "$LEX"; then
+ ac_cv_prog_LEX="$LEX" # Let the user override the test.
+else
+ IFS="${IFS= }"; ac_save_ifs="$IFS"; IFS=":"
+ ac_dummy="$PATH"
+ for ac_dir in $ac_dummy; do
+ test -z "$ac_dir" && ac_dir=.
+ if test -f $ac_dir/$ac_word; then
+ ac_cv_prog_LEX="flex"
+ break
+ fi
+ done
+ IFS="$ac_save_ifs"
+ test -z "$ac_cv_prog_LEX" && ac_cv_prog_LEX="lex"
+fi
+fi
+LEX="$ac_cv_prog_LEX"
+if test -n "$LEX"; then
+ echo "$ac_t""$LEX" 1>&6
+else
+ echo "$ac_t""no" 1>&6
+fi
+
+if test -z "$LEXLIB"
+then
+ case "$LEX" in
+ flex*) ac_lib=fl ;;
+ *) ac_lib=l ;;
+ esac
+ echo $ac_n "checking for yywrap in -l$ac_lib""... $ac_c" 1>&6
+echo "configure:5930: checking for yywrap in -l$ac_lib" >&5
+ac_lib_var=`echo $ac_lib'_'yywrap | sed 'y%./+-%__p_%'`
+if eval "test \"`echo '$''{'ac_cv_lib_$ac_lib_var'+set}'`\" = set"; then
+ echo $ac_n "(cached) $ac_c" 1>&6
+else
+ ac_save_LIBS="$LIBS"
+LIBS="-l$ac_lib $LIBS"
+cat > conftest.$ac_ext <<EOF
+#line 5938 "configure"
+#include "confdefs.h"
+/* Override any gcc2 internal prototype to avoid an error. */
+/* We use char because int might match the return type of a gcc2
+ builtin and then its argument prototype would still apply. */
+char yywrap();
+
+int main() {
+yywrap()
+; return 0; }
+EOF
+if { (eval echo configure:5949: \"$ac_link\") 1>&5; (eval $ac_link) 2>&5; } && test -s conftest${ac_exeext}; then
+ rm -rf conftest*
+ eval "ac_cv_lib_$ac_lib_var=yes"
+else
+ echo "configure: failed program was:" >&5
+ cat conftest.$ac_ext >&5
+ rm -rf conftest*
+ eval "ac_cv_lib_$ac_lib_var=no"
+fi
+rm -f conftest*
+LIBS="$ac_save_LIBS"
+
+fi
+if eval "test \"`echo '$ac_cv_lib_'$ac_lib_var`\" = yes"; then
+ echo "$ac_t""yes" 1>&6
+ LEXLIB="-l$ac_lib"
+else
+ echo "$ac_t""no" 1>&6
+fi
+
+fi
+
+echo $ac_n "checking whether ${MAKE-make} sets \${MAKE}""... $ac_c" 1>&6
+echo "configure:5972: checking whether ${MAKE-make} sets \${MAKE}" >&5
+set dummy ${MAKE-make}; ac_make=`echo "$2" | sed 'y%./+-%__p_%'`
+if eval "test \"`echo '$''{'ac_cv_prog_make_${ac_make}_set'+set}'`\" = set"; then
+ echo $ac_n "(cached) $ac_c" 1>&6
+else
+ cat > conftestmake <<\EOF
+all:
+ @echo 'ac_maketemp="${MAKE}"'
+EOF
+# GNU make sometimes prints "make[1]: Entering...", which would confuse us.
+eval `${MAKE-make} -f conftestmake 2>/dev/null | grep temp=`
+if test -n "$ac_maketemp"; then
+ eval ac_cv_prog_make_${ac_make}_set=yes
+else
+ eval ac_cv_prog_make_${ac_make}_set=no
+fi
+rm -f conftestmake
+fi
+if eval "test \"`echo '$ac_cv_prog_make_'${ac_make}_set`\" = yes"; then
+ echo "$ac_t""yes" 1>&6
+ SET_MAKE=
+else
+ echo "$ac_t""no" 1>&6
+ SET_MAKE="MAKE=${MAKE-make}"
+fi
+
+# Extract the first word of "ranlib", so it can be a program name with args.
+set dummy ranlib; ac_word=$2
+echo $ac_n "checking for $ac_word""... $ac_c" 1>&6
+echo "configure:6001: checking for $ac_word" >&5
+if eval "test \"`echo '$''{'ac_cv_prog_RANLIB'+set}'`\" = set"; then
+ echo $ac_n "(cached) $ac_c" 1>&6
+else
+ if test -n "$RANLIB"; then
+ ac_cv_prog_RANLIB="$RANLIB" # Let the user override the test.
+else
+ IFS="${IFS= }"; ac_save_ifs="$IFS"; IFS=":"
+ ac_dummy="$PATH"
+ for ac_dir in $ac_dummy; do
+ test -z "$ac_dir" && ac_dir=.
+ if test -f $ac_dir/$ac_word; then
+ ac_cv_prog_RANLIB="ranlib"
+ break
+ fi
+ done
+ IFS="$ac_save_ifs"
+ test -z "$ac_cv_prog_RANLIB" && ac_cv_prog_RANLIB=":"
+fi
+fi
+RANLIB="$ac_cv_prog_RANLIB"
+if test -n "$RANLIB"; then
+ echo "$ac_t""$RANLIB" 1>&6
+else
+ echo "$ac_t""no" 1>&6
+fi
+
+for ac_prog in 'bison -y' byacc
+do
+# Extract the first word of "$ac_prog", so it can be a program name with args.
+set dummy $ac_prog; ac_word=$2
+echo $ac_n "checking for $ac_word""... $ac_c" 1>&6
+echo "configure:6033: checking for $ac_word" >&5
+if eval "test \"`echo '$''{'ac_cv_prog_YACC'+set}'`\" = set"; then
+ echo $ac_n "(cached) $ac_c" 1>&6
+else
+ if test -n "$YACC"; then
+ ac_cv_prog_YACC="$YACC" # Let the user override the test.
+else
+ IFS="${IFS= }"; ac_save_ifs="$IFS"; IFS=":"
+ ac_dummy="$PATH"
+ for ac_dir in $ac_dummy; do
+ test -z "$ac_dir" && ac_dir=.
+ if test -f $ac_dir/$ac_word; then
+ ac_cv_prog_YACC="$ac_prog"
+ break
+ fi
+ done
+ IFS="$ac_save_ifs"
+fi
+fi
+YACC="$ac_cv_prog_YACC"
+if test -n "$YACC"; then
+ echo "$ac_t""$YACC" 1>&6
+else
+ echo "$ac_t""no" 1>&6
+fi
+
+test -n "$YACC" && break
+done
+test -n "$YACC" || YACC="yacc"
+
+
+case "$CPP" in
+*-traditional-cpp*)
+ CFLAGS="-traditional-cpp $CFLAGS"
+ ;;
+esac
+
+case "$host" in
+
+*hpux*)
+ if test x"$GCC" != xyes ; then
+ CFLAGS="$CFLAGS -Ae"
+
+ case "$CFLAGS" in
+ *-g*)
+ LDFLAGS="$LDFLAGS -g"
+ ;;
+ esac
+ fi
+ ;;
+
+*darwin*)
+ LDFLAGS="$LDFLAGS -multiply_defined suppress"
+ ;;
+
+*UnixWare*|*unixware*|*-sco3*)
+ if test x"$GCC" != xyes ; then
+ CFLAGS="$CFLAGS -Kalloca"
+ fi
+esac
+
+
+# Extract the first word of "ctags", so it can be a program name with args.
+set dummy ctags; ac_word=$2
+echo $ac_n "checking for $ac_word""... $ac_c" 1>&6
+echo "configure:6098: checking for $ac_word" >&5
+if eval "test \"`echo '$''{'ac_cv_path_CTAGS'+set}'`\" = set"; then
+ echo $ac_n "(cached) $ac_c" 1>&6
+else
+ case "$CTAGS" in
+ /*)
+ ac_cv_path_CTAGS="$CTAGS" # Let the user override the test with a path.
+ ;;
+ ?:/*)
+ ac_cv_path_CTAGS="$CTAGS" # Let the user override the test with a dos path.
+ ;;
+ *)
+ IFS="${IFS= }"; ac_save_ifs="$IFS"; IFS=":"
+ ac_dummy="$PATH"
+ for ac_dir in $ac_dummy; do
+ test -z "$ac_dir" && ac_dir=.
+ if test -f $ac_dir/$ac_word; then
+ ac_cv_path_CTAGS="$ac_dir/$ac_word"
+ break
+ fi
+ done
+ IFS="$ac_save_ifs"
+ test -z "$ac_cv_path_CTAGS" && ac_cv_path_CTAGS="echo"
+ ;;
+esac
+fi
+CTAGS="$ac_cv_path_CTAGS"
+if test -n "$CTAGS"; then
+ echo "$ac_t""$CTAGS" 1>&6
+else
+ echo "$ac_t""no" 1>&6
+fi
+
+if test x"$CTAGS" != xecho ; then
+ CTAGS="$CTAGS -t -w"
+fi
+
+
+
+# Extract the first word of "awk", so it can be a program name with args.
+set dummy awk; ac_word=$2
+echo $ac_n "checking for $ac_word""... $ac_c" 1>&6
+echo "configure:6140: checking for $ac_word" >&5
+if eval "test \"`echo '$''{'ac_cv_path__PATH_AWK'+set}'`\" = set"; then
+ echo $ac_n "(cached) $ac_c" 1>&6
+else
+ case "$_PATH_AWK" in
+ /*)
+ ac_cv_path__PATH_AWK="$_PATH_AWK" # Let the user override the test with a path.
+ ;;
+ ?:/*)
+ ac_cv_path__PATH_AWK="$_PATH_AWK" # Let the user override the test with a dos path.
+ ;;
+ *)
+ IFS="${IFS= }"; ac_save_ifs="$IFS"; IFS=":"
+ ac_dummy="$PATH"
+ for ac_dir in $ac_dummy; do
+ test -z "$ac_dir" && ac_dir=.
+ if test -f $ac_dir/$ac_word; then
+ ac_cv_path__PATH_AWK="$ac_dir/$ac_word"
+ break
+ fi
+ done
+ IFS="$ac_save_ifs"
+ ;;
+esac
+fi
+_PATH_AWK="$ac_cv_path__PATH_AWK"
+if test -n "$_PATH_AWK"; then
+ echo "$ac_t""$_PATH_AWK" 1>&6
+else
+ echo "$ac_t""no" 1>&6
+fi
+
+if test -z "${_PATH_AWK}" ; then
+ { echo "configure: error: awk was not found in path and is required" 1>&2; exit 1; }
+fi
+# Extract the first word of "egrep", so it can be a program name with args.
+set dummy egrep; ac_word=$2
+echo $ac_n "checking for $ac_word""... $ac_c" 1>&6
+echo "configure:6178: checking for $ac_word" >&5
+if eval "test \"`echo '$''{'ac_cv_path__PATH_EGREP'+set}'`\" = set"; then
+ echo $ac_n "(cached) $ac_c" 1>&6
+else
+ case "$_PATH_EGREP" in
+ /*)
+ ac_cv_path__PATH_EGREP="$_PATH_EGREP" # Let the user override the test with a path.
+ ;;
+ ?:/*)
+ ac_cv_path__PATH_EGREP="$_PATH_EGREP" # Let the user override the test with a dos path.
+ ;;
+ *)
+ IFS="${IFS= }"; ac_save_ifs="$IFS"; IFS=":"
+ ac_dummy="$PATH"
+ for ac_dir in $ac_dummy; do
+ test -z "$ac_dir" && ac_dir=.
+ if test -f $ac_dir/$ac_word; then
+ ac_cv_path__PATH_EGREP="$ac_dir/$ac_word"
+ break
+ fi
+ done
+ IFS="$ac_save_ifs"
+ ;;
+esac
+fi
+_PATH_EGREP="$ac_cv_path__PATH_EGREP"
+if test -n "$_PATH_EGREP"; then
+ echo "$ac_t""$_PATH_EGREP" 1>&6
+else
+ echo "$ac_t""no" 1>&6
+fi
+
+if test -z "${_PATH_EGREP}" ; then
+ { echo "configure: error: egrep was not found in path and is required" 1>&2; exit 1; }
+fi
+# Extract the first word of "perl", so it can be a program name with args.
+set dummy perl; ac_word=$2
+echo $ac_n "checking for $ac_word""... $ac_c" 1>&6
+echo "configure:6216: checking for $ac_word" >&5
+if eval "test \"`echo '$''{'ac_cv_path__PATH_PERL'+set}'`\" = set"; then
+ echo $ac_n "(cached) $ac_c" 1>&6
+else
+ case "$_PATH_PERL" in
+ /*)
+ ac_cv_path__PATH_PERL="$_PATH_PERL" # Let the user override the test with a path.
+ ;;
+ ?:/*)
+ ac_cv_path__PATH_PERL="$_PATH_PERL" # Let the user override the test with a dos path.
+ ;;
+ *)
+ IFS="${IFS= }"; ac_save_ifs="$IFS"; IFS=":"
+ ac_dummy="$PATH"
+ for ac_dir in $ac_dummy; do
+ test -z "$ac_dir" && ac_dir=.
+ if test -f $ac_dir/$ac_word; then
+ ac_cv_path__PATH_PERL="$ac_dir/$ac_word"
+ break
+ fi
+ done
+ IFS="$ac_save_ifs"
+ ;;
+esac
+fi
+_PATH_PERL="$ac_cv_path__PATH_PERL"
+if test -n "$_PATH_PERL"; then
+ echo "$ac_t""$_PATH_PERL" 1>&6
+else
+ echo "$ac_t""no" 1>&6
+fi
+
+if test -z "${_PATH_PERL}" ; then
+ { echo "configure: error: perl was not found in path and is required" 1>&2; exit 1; }
+fi
+# Extract the first word of "sh", so it can be a program name with args.
+set dummy sh; ac_word=$2
+echo $ac_n "checking for $ac_word""... $ac_c" 1>&6
+echo "configure:6254: checking for $ac_word" >&5
+if eval "test \"`echo '$''{'ac_cv_path__PATH_SH'+set}'`\" = set"; then
+ echo $ac_n "(cached) $ac_c" 1>&6
+else
+ case "$_PATH_SH" in
+ /*)
+ ac_cv_path__PATH_SH="$_PATH_SH" # Let the user override the test with a path.
+ ;;
+ ?:/*)
+ ac_cv_path__PATH_SH="$_PATH_SH" # Let the user override the test with a dos path.
+ ;;
+ *)
+ IFS="${IFS= }"; ac_save_ifs="$IFS"; IFS=":"
+ ac_dummy="$PATH"
+ for ac_dir in $ac_dummy; do
+ test -z "$ac_dir" && ac_dir=.
+ if test -f $ac_dir/$ac_word; then
+ ac_cv_path__PATH_SH="$ac_dir/$ac_word"
+ break
+ fi
+ done
+ IFS="$ac_save_ifs"
+ ;;
+esac
+fi
+_PATH_SH="$ac_cv_path__PATH_SH"
+if test -n "$_PATH_SH"; then
+ echo "$ac_t""$_PATH_SH" 1>&6
+else
+ echo "$ac_t""no" 1>&6
+fi
+
+if test -z "${_PATH_SH}" ; then
+ { echo "configure: error: sh was not found in path and is required" 1>&2; exit 1; }
+fi
+# Extract the first word of "sed", so it can be a program name with args.
+set dummy sed; ac_word=$2
+echo $ac_n "checking for $ac_word""... $ac_c" 1>&6
+echo "configure:6292: checking for $ac_word" >&5
+if eval "test \"`echo '$''{'ac_cv_path__PATH_SED'+set}'`\" = set"; then
+ echo $ac_n "(cached) $ac_c" 1>&6
+else
+ case "$_PATH_SED" in
+ /*)
+ ac_cv_path__PATH_SED="$_PATH_SED" # Let the user override the test with a path.
+ ;;
+ ?:/*)
+ ac_cv_path__PATH_SED="$_PATH_SED" # Let the user override the test with a dos path.
+ ;;
+ *)
+ IFS="${IFS= }"; ac_save_ifs="$IFS"; IFS=":"
+ ac_dummy="$PATH"
+ for ac_dir in $ac_dummy; do
+ test -z "$ac_dir" && ac_dir=.
+ if test -f $ac_dir/$ac_word; then
+ ac_cv_path__PATH_SED="$ac_dir/$ac_word"
+ break
+ fi
+ done
+ IFS="$ac_save_ifs"
+ ;;
+esac
+fi
+_PATH_SED="$ac_cv_path__PATH_SED"
+if test -n "$_PATH_SED"; then
+ echo "$ac_t""$_PATH_SED" 1>&6
+else
+ echo "$ac_t""no" 1>&6
+fi
+
+if test -z "${_PATH_SED}" ; then
+ { echo "configure: error: sed was not found in path and is required" 1>&2; exit 1; }
+fi
+# Extract the first word of "sort", so it can be a program name with args.
+set dummy sort; ac_word=$2
+echo $ac_n "checking for $ac_word""... $ac_c" 1>&6
+echo "configure:6330: checking for $ac_word" >&5
+if eval "test \"`echo '$''{'ac_cv_path__PATH_SORT'+set}'`\" = set"; then
+ echo $ac_n "(cached) $ac_c" 1>&6
+else
+ case "$_PATH_SORT" in
+ /*)
+ ac_cv_path__PATH_SORT="$_PATH_SORT" # Let the user override the test with a path.
+ ;;
+ ?:/*)
+ ac_cv_path__PATH_SORT="$_PATH_SORT" # Let the user override the test with a dos path.
+ ;;
+ *)
+ IFS="${IFS= }"; ac_save_ifs="$IFS"; IFS=":"
+ ac_dummy="$PATH"
+ for ac_dir in $ac_dummy; do
+ test -z "$ac_dir" && ac_dir=.
+ if test -f $ac_dir/$ac_word; then
+ ac_cv_path__PATH_SORT="$ac_dir/$ac_word"
+ break
+ fi
+ done
+ IFS="$ac_save_ifs"
+ ;;
+esac
+fi
+_PATH_SORT="$ac_cv_path__PATH_SORT"
+if test -n "$_PATH_SORT"; then
+ echo "$ac_t""$_PATH_SORT" 1>&6
+else
+ echo "$ac_t""no" 1>&6
+fi
+
+if test -z "${_PATH_SORT}" ; then
+ { echo "configure: error: sort was not found in path and is required" 1>&2; exit 1; }
+fi
+for ac_prog in uux
+do
+# Extract the first word of "$ac_prog", so it can be a program name with args.
+set dummy $ac_prog; ac_word=$2
+echo $ac_n "checking for $ac_word""... $ac_c" 1>&6
+echo "configure:6370: checking for $ac_word" >&5
+if eval "test \"`echo '$''{'ac_cv_path__PATH_UUX'+set}'`\" = set"; then
+ echo $ac_n "(cached) $ac_c" 1>&6
+else
+ case "$_PATH_UUX" in
+ /*)
+ ac_cv_path__PATH_UUX="$_PATH_UUX" # Let the user override the test with a path.
+ ;;
+ ?:/*)
+ ac_cv_path__PATH_UUX="$_PATH_UUX" # Let the user override the test with a dos path.
+ ;;
+ *)
+ IFS="${IFS= }"; ac_save_ifs="$IFS"; IFS=":"
+ ac_dummy="$PATH"
+ for ac_dir in $ac_dummy; do
+ test -z "$ac_dir" && ac_dir=.
+ if test -f $ac_dir/$ac_word; then
+ ac_cv_path__PATH_UUX="$ac_dir/$ac_word"
+ break
+ fi
+ done
+ IFS="$ac_save_ifs"
+ ;;
+esac
+fi
+_PATH_UUX="$ac_cv_path__PATH_UUX"
+if test -n "$_PATH_UUX"; then
+ echo "$ac_t""$_PATH_UUX" 1>&6
+else
+ echo "$ac_t""no" 1>&6
+fi
+
+test -n "$_PATH_UUX" && break
+done
+test -n "$_PATH_UUX" || _PATH_UUX="uux"
+
+
+inn_perl_command='print $]'
+
+
+echo $ac_n "checking for Perl version""... $ac_c" 1>&6
+echo "configure:6411: checking for Perl version" >&5
+if eval "test \"`echo '$''{'inn_cv_perl_version'+set}'`\" = set"; then
+ echo $ac_n "(cached) $ac_c" 1>&6
+else
+ if $_PATH_PERL -e 'require 5.004_03;' > /dev/null 2>&1 ; then
+ inn_cv_perl_version=`$_PATH_PERL -e "$inn_perl_command"`
+else
+ { echo "configure: error: Perl 5.004_03 or greater is required" 1>&2; exit 1; }
+fi
+fi
+
+echo "$ac_t""$inn_cv_perl_version" 1>&6
+
+pgpverify=true
+for ac_prog in gpgv
+do
+# Extract the first word of "$ac_prog", so it can be a program name with args.
+set dummy $ac_prog; ac_word=$2
+echo $ac_n "checking for $ac_word""... $ac_c" 1>&6
+echo "configure:6430: checking for $ac_word" >&5
+if eval "test \"`echo '$''{'ac_cv_path_PATH_GPGV'+set}'`\" = set"; then
+ echo $ac_n "(cached) $ac_c" 1>&6
+else
+ case "$PATH_GPGV" in
+ /*)
+ ac_cv_path_PATH_GPGV="$PATH_GPGV" # Let the user override the test with a path.
+ ;;
+ ?:/*)
+ ac_cv_path_PATH_GPGV="$PATH_GPGV" # Let the user override the test with a dos path.
+ ;;
+ *)
+ IFS="${IFS= }"; ac_save_ifs="$IFS"; IFS=":"
+ ac_dummy="$PATH"
+ for ac_dir in $ac_dummy; do
+ test -z "$ac_dir" && ac_dir=.
+ if test -f $ac_dir/$ac_word; then
+ ac_cv_path_PATH_GPGV="$ac_dir/$ac_word"
+ break
+ fi
+ done
+ IFS="$ac_save_ifs"
+ ;;
+esac
+fi
+PATH_GPGV="$ac_cv_path_PATH_GPGV"
+if test -n "$PATH_GPGV"; then
+ echo "$ac_t""$PATH_GPGV" 1>&6
+else
+ echo "$ac_t""no" 1>&6
+fi
+
+test -n "$PATH_GPGV" && break
+done
+
+for ac_prog in pgpv pgp pgpgpg
+do
+# Extract the first word of "$ac_prog", so it can be a program name with args.
+set dummy $ac_prog; ac_word=$2
+echo $ac_n "checking for $ac_word""... $ac_c" 1>&6
+echo "configure:6470: checking for $ac_word" >&5
+if eval "test \"`echo '$''{'ac_cv_path__PATH_PGP'+set}'`\" = set"; then
+ echo $ac_n "(cached) $ac_c" 1>&6
+else
+ case "$_PATH_PGP" in
+ /*)
+ ac_cv_path__PATH_PGP="$_PATH_PGP" # Let the user override the test with a path.
+ ;;
+ ?:/*)
+ ac_cv_path__PATH_PGP="$_PATH_PGP" # Let the user override the test with a dos path.
+ ;;
+ *)
+ IFS="${IFS= }"; ac_save_ifs="$IFS"; IFS=":"
+ ac_dummy="$PATH"
+ for ac_dir in $ac_dummy; do
+ test -z "$ac_dir" && ac_dir=.
+ if test -f $ac_dir/$ac_word; then
+ ac_cv_path__PATH_PGP="$ac_dir/$ac_word"
+ break
+ fi
+ done
+ IFS="$ac_save_ifs"
+ ;;
+esac
+fi
+_PATH_PGP="$ac_cv_path__PATH_PGP"
+if test -n "$_PATH_PGP"; then
+ echo "$ac_t""$_PATH_PGP" 1>&6
+else
+ echo "$ac_t""no" 1>&6
+fi
+
+test -n "$_PATH_PGP" && break
+done
+
+if test -z "$_PATH_PGP" && test -z "$PATH_GPGV" ; then
+ pgpverify=false
+fi
+
+
+for ac_prog in wget ncftpget ncftp
+do
+# Extract the first word of "$ac_prog", so it can be a program name with args.
+set dummy $ac_prog; ac_word=$2
+echo $ac_n "checking for $ac_word""... $ac_c" 1>&6
+echo "configure:6515: checking for $ac_word" >&5
+if eval "test \"`echo '$''{'ac_cv_path_GETFTP'+set}'`\" = set"; then
+ echo $ac_n "(cached) $ac_c" 1>&6
+else
+ case "$GETFTP" in
+ /*)
+ ac_cv_path_GETFTP="$GETFTP" # Let the user override the test with a path.
+ ;;
+ ?:/*)
+ ac_cv_path_GETFTP="$GETFTP" # Let the user override the test with a dos path.
+ ;;
+ *)
+ IFS="${IFS= }"; ac_save_ifs="$IFS"; IFS=":"
+ ac_dummy="$PATH"
+ for ac_dir in $ac_dummy; do
+ test -z "$ac_dir" && ac_dir=.
+ if test -f $ac_dir/$ac_word; then
+ ac_cv_path_GETFTP="$ac_dir/$ac_word"
+ break
+ fi
+ done
+ IFS="$ac_save_ifs"
+ ;;
+esac
+fi
+GETFTP="$ac_cv_path_GETFTP"
+if test -n "$GETFTP"; then
+ echo "$ac_t""$GETFTP" 1>&6
+else
+ echo "$ac_t""no" 1>&6
+fi
+
+test -n "$GETFTP" && break
+done
+test -n "$GETFTP" || GETFTP="$prefix/bin/simpleftp"
+
+
+case "$LOG_COMPRESS" in
+compress|gzip) ;;
+*) # Extract the first word of ""$LOG_COMPRESS"", so it can be a program name with args.
+set dummy "$LOG_COMPRESS"; ac_word=$2
+echo $ac_n "checking for $ac_word""... $ac_c" 1>&6
+echo "configure:6557: checking for $ac_word" >&5
+if eval "test \"`echo '$''{'ac_cv_path_LOG_COMPRESS'+set}'`\" = set"; then
+ echo $ac_n "(cached) $ac_c" 1>&6
+else
+ case "$LOG_COMPRESS" in
+ /*)
+ ac_cv_path_LOG_COMPRESS="$LOG_COMPRESS" # Let the user override the test with a path.
+ ;;
+ ?:/*)
+ ac_cv_path_LOG_COMPRESS="$LOG_COMPRESS" # Let the user override the test with a dos path.
+ ;;
+ *)
+ IFS="${IFS= }"; ac_save_ifs="$IFS"; IFS=":"
+ ac_dummy="$PATH"
+ for ac_dir in $ac_dummy; do
+ test -z "$ac_dir" && ac_dir=.
+ if test -f $ac_dir/$ac_word; then
+ ac_cv_path_LOG_COMPRESS="$ac_dir/$ac_word"
+ break
+ fi
+ done
+ IFS="$ac_save_ifs"
+ ;;
+esac
+fi
+LOG_COMPRESS="$ac_cv_path_LOG_COMPRESS"
+if test -n "$LOG_COMPRESS"; then
+ echo "$ac_t""$LOG_COMPRESS" 1>&6
+else
+ echo "$ac_t""no" 1>&6
+fi
+
+if test -z "${LOG_COMPRESS}" ; then
+ { echo "configure: error: "$LOG_COMPRESS" was not found in path and is required" 1>&2; exit 1; }
+fi
+esac
+# Extract the first word of "compress", so it can be a program name with args.
+set dummy compress; ac_word=$2
+echo $ac_n "checking for $ac_word""... $ac_c" 1>&6
+echo "configure:6596: checking for $ac_word" >&5
+if eval "test \"`echo '$''{'ac_cv_path_COMPRESS'+set}'`\" = set"; then
+ echo $ac_n "(cached) $ac_c" 1>&6
+else
+ case "$COMPRESS" in
+ /*)
+ ac_cv_path_COMPRESS="$COMPRESS" # Let the user override the test with a path.
+ ;;
+ ?:/*)
+ ac_cv_path_COMPRESS="$COMPRESS" # Let the user override the test with a dos path.
+ ;;
+ *)
+ IFS="${IFS= }"; ac_save_ifs="$IFS"; IFS=":"
+ ac_dummy="$PATH"
+ for ac_dir in $ac_dummy; do
+ test -z "$ac_dir" && ac_dir=.
+ if test -f $ac_dir/$ac_word; then
+ ac_cv_path_COMPRESS="$ac_dir/$ac_word"
+ break
+ fi
+ done
+ IFS="$ac_save_ifs"
+ test -z "$ac_cv_path_COMPRESS" && ac_cv_path_COMPRESS="compress"
+ ;;
+esac
+fi
+COMPRESS="$ac_cv_path_COMPRESS"
+if test -n "$COMPRESS"; then
+ echo "$ac_t""$COMPRESS" 1>&6
+else
+ echo "$ac_t""no" 1>&6
+fi
+
+if test x"$LOG_COMPRESS" = xcompress ; then
+ if test x"$COMPRESS" = xcompress ; then
+ { echo "configure: error: compress not found but specified for log compression" 1>&2; exit 1; }
+ fi
+ LOG_COMPRESS="$COMPRESS"
+fi
+# Extract the first word of "gzip", so it can be a program name with args.
+set dummy gzip; ac_word=$2
+echo $ac_n "checking for $ac_word""... $ac_c" 1>&6
+echo "configure:6638: checking for $ac_word" >&5
+if eval "test \"`echo '$''{'ac_cv_path_GZIP'+set}'`\" = set"; then
+ echo $ac_n "(cached) $ac_c" 1>&6
+else
+ case "$GZIP" in
+ /*)
+ ac_cv_path_GZIP="$GZIP" # Let the user override the test with a path.
+ ;;
+ ?:/*)
+ ac_cv_path_GZIP="$GZIP" # Let the user override the test with a dos path.
+ ;;
+ *)
+ IFS="${IFS= }"; ac_save_ifs="$IFS"; IFS=":"
+ ac_dummy="$PATH"
+ for ac_dir in $ac_dummy; do
+ test -z "$ac_dir" && ac_dir=.
+ if test -f $ac_dir/$ac_word; then
+ ac_cv_path_GZIP="$ac_dir/$ac_word"
+ break
+ fi
+ done
+ IFS="$ac_save_ifs"
+ test -z "$ac_cv_path_GZIP" && ac_cv_path_GZIP="gzip"
+ ;;
+esac
+fi
+GZIP="$ac_cv_path_GZIP"
+if test -n "$GZIP"; then
+ echo "$ac_t""$GZIP" 1>&6
+else
+ echo "$ac_t""no" 1>&6
+fi
+
+if test x"$LOG_COMPRESS" = xgzip ; then
+ if test x"$GZIP" = xgzip ; then
+ { echo "configure: error: gzip not found but specified for log compression" 1>&2; exit 1; }
+ fi
+ LOG_COMPRESS="$GZIP"
+fi
+
+if test x"$COMPRESS" = xcompress && test x"$GZIP" != xgzip ; then
+ UNCOMPRESS="$GZIP -d"
+else
+ UNCOMPRESS="$COMPRESS -d"
+fi
+
+
+if test "${with_sendmail+set}" = set ; then
+ echo $ac_n "checking for sendmail""... $ac_c" 1>&6
+echo "configure:6687: checking for sendmail" >&5
+ echo "$ac_t""$SENDMAIL" 1>&6
+else
+ # Extract the first word of "sendmail", so it can be a program name with args.
+set dummy sendmail; ac_word=$2
+echo $ac_n "checking for $ac_word""... $ac_c" 1>&6
+echo "configure:6693: checking for $ac_word" >&5
+if eval "test \"`echo '$''{'ac_cv_path_SENDMAIL'+set}'`\" = set"; then
+ echo $ac_n "(cached) $ac_c" 1>&6
+else
+ case "$SENDMAIL" in
+ /*)
+ ac_cv_path_SENDMAIL="$SENDMAIL" # Let the user override the test with a path.
+ ;;
+ ?:/*)
+ ac_cv_path_SENDMAIL="$SENDMAIL" # Let the user override the test with a dos path.
+ ;;
+ *)
+ IFS="${IFS= }"; ac_save_ifs="$IFS"; IFS=":"
+ ac_dummy=""/usr/sbin:/usr/lib""
+ for ac_dir in $ac_dummy; do
+ test -z "$ac_dir" && ac_dir=.
+ if test -f $ac_dir/$ac_word; then
+ ac_cv_path_SENDMAIL="$ac_dir/$ac_word"
+ break
+ fi
+ done
+ IFS="$ac_save_ifs"
+ ;;
+esac
+fi
+SENDMAIL="$ac_cv_path_SENDMAIL"
+if test -n "$SENDMAIL"; then
+ echo "$ac_t""$SENDMAIL" 1>&6
+else
+ echo "$ac_t""no" 1>&6
+fi
+
+ if test -z "$SENDMAIL" ; then
+ { echo "configure: error: sendmail not found" 1>&2; exit 1; }
+ fi
+fi
+
+# Extract the first word of "uustat", so it can be a program name with args.
+set dummy uustat; ac_word=$2
+echo $ac_n "checking for $ac_word""... $ac_c" 1>&6
+echo "configure:6733: checking for $ac_word" >&5
+if eval "test \"`echo '$''{'ac_cv_prog_HAVE_UUSTAT'+set}'`\" = set"; then
+ echo $ac_n "(cached) $ac_c" 1>&6
+else
+ if test -n "$HAVE_UUSTAT"; then
+ ac_cv_prog_HAVE_UUSTAT="$HAVE_UUSTAT" # Let the user override the test.
+else
+ IFS="${IFS= }"; ac_save_ifs="$IFS"; IFS=":"
+ ac_dummy="$PATH"
+ for ac_dir in $ac_dummy; do
+ test -z "$ac_dir" && ac_dir=.
+ if test -f $ac_dir/$ac_word; then
+ ac_cv_prog_HAVE_UUSTAT="DO"
+ break
+ fi
+ done
+ IFS="$ac_save_ifs"
+ test -z "$ac_cv_prog_HAVE_UUSTAT" && ac_cv_prog_HAVE_UUSTAT="DONT"
+fi
+fi
+HAVE_UUSTAT="$ac_cv_prog_HAVE_UUSTAT"
+if test -n "$HAVE_UUSTAT"; then
+ echo "$ac_t""$HAVE_UUSTAT" 1>&6
+else
+ echo "$ac_t""no" 1>&6
+fi
+
+
+
+if test x"$DO_PYTHON" = xdefine ; then
+ # Extract the first word of "python", so it can be a program name with args.
+set dummy python; ac_word=$2
+echo $ac_n "checking for $ac_word""... $ac_c" 1>&6
+echo "configure:6766: checking for $ac_word" >&5
+if eval "test \"`echo '$''{'ac_cv_path__PATH_PYTHON'+set}'`\" = set"; then
+ echo $ac_n "(cached) $ac_c" 1>&6
+else
+ case "$_PATH_PYTHON" in
+ /*)
+ ac_cv_path__PATH_PYTHON="$_PATH_PYTHON" # Let the user override the test with a path.
+ ;;
+ ?:/*)
+ ac_cv_path__PATH_PYTHON="$_PATH_PYTHON" # Let the user override the test with a dos path.
+ ;;
+ *)
+ IFS="${IFS= }"; ac_save_ifs="$IFS"; IFS=":"
+ ac_dummy="$PATH"
+ for ac_dir in $ac_dummy; do
+ test -z "$ac_dir" && ac_dir=.
+ if test -f $ac_dir/$ac_word; then
+ ac_cv_path__PATH_PYTHON="$ac_dir/$ac_word"
+ break
+ fi
+ done
+ IFS="$ac_save_ifs"
+ ;;
+esac
+fi
+_PATH_PYTHON="$ac_cv_path__PATH_PYTHON"
+if test -n "$_PATH_PYTHON"; then
+ echo "$ac_t""$_PATH_PYTHON" 1>&6
+else
+ echo "$ac_t""no" 1>&6
+fi
+
+if test -z "${_PATH_PYTHON}" ; then
+ { echo "configure: error: python was not found in path and is required" 1>&2; exit 1; }
+fi
+fi
+
+
+
+
+
+echo $ac_n "checking for library containing setproctitle""... $ac_c" 1>&6
+echo "configure:6808: checking for library containing setproctitle" >&5
+if eval "test \"`echo '$''{'ac_cv_search_setproctitle'+set}'`\" = set"; then
+ echo $ac_n "(cached) $ac_c" 1>&6
+else
+ ac_func_search_save_LIBS="$LIBS"
+ac_cv_search_setproctitle="no"
+cat > conftest.$ac_ext <<EOF
+#line 6815 "configure"
+#include "confdefs.h"
+/* Override any gcc2 internal prototype to avoid an error. */
+/* We use char because int might match the return type of a gcc2
+ builtin and then its argument prototype would still apply. */
+char setproctitle();
+
+int main() {
+setproctitle()
+; return 0; }
+EOF
+if { (eval echo configure:6826: \"$ac_link\") 1>&5; (eval $ac_link) 2>&5; } && test -s conftest${ac_exeext}; then
+ rm -rf conftest*
+ ac_cv_search_setproctitle="none required"
+else
+ echo "configure: failed program was:" >&5
+ cat conftest.$ac_ext >&5
+fi
+rm -f conftest*
+test "$ac_cv_search_setproctitle" = "no" && for i in util; do
+LIBS="-l$i $ac_func_search_save_LIBS"
+cat > conftest.$ac_ext <<EOF
+#line 6837 "configure"
+#include "confdefs.h"
+/* Override any gcc2 internal prototype to avoid an error. */
+/* We use char because int might match the return type of a gcc2
+ builtin and then its argument prototype would still apply. */
+char setproctitle();
+
+int main() {
+setproctitle()
+; return 0; }
+EOF
+if { (eval echo configure:6848: \"$ac_link\") 1>&5; (eval $ac_link) 2>&5; } && test -s conftest${ac_exeext}; then
+ rm -rf conftest*
+ ac_cv_search_setproctitle="-l$i"
+break
+else
+ echo "configure: failed program was:" >&5
+ cat conftest.$ac_ext >&5
+fi
+rm -f conftest*
+done
+LIBS="$ac_func_search_save_LIBS"
+fi
+
+echo "$ac_t""$ac_cv_search_setproctitle" 1>&6
+if test "$ac_cv_search_setproctitle" != "no"; then
+ test "$ac_cv_search_setproctitle" = "none required" || LIBS="$ac_cv_search_setproctitle $LIBS"
+ cat >> confdefs.h <<\EOF
+#define HAVE_SETPROCTITLE 1
+EOF
+
+else :
+ LIBOBJS="$LIBOBJS setproctitle.o"
+ for ac_func in pstat
+do
+echo $ac_n "checking for $ac_func""... $ac_c" 1>&6
+echo "configure:6873: checking for $ac_func" >&5
+if eval "test \"`echo '$''{'ac_cv_func_$ac_func'+set}'`\" = set"; then
+ echo $ac_n "(cached) $ac_c" 1>&6
+else
+ cat > conftest.$ac_ext <<EOF
+#line 6878 "configure"
+#include "confdefs.h"
+/* System header to define __stub macros and hopefully few prototypes,
+ which can conflict with char $ac_func(); below. */
+#include <assert.h>
+/* Override any gcc2 internal prototype to avoid an error. */
+/* We use char because int might match the return type of a gcc2
+ builtin and then its argument prototype would still apply. */
+char $ac_func();
+
+int main() {
+
+/* The GNU C library defines this for functions which it implements
+ to always fail with ENOSYS. Some functions are actually named
+ something starting with __ and the normal name is an alias. */
+#if defined (__stub_$ac_func) || defined (__stub___$ac_func)
+choke me
+#else
+$ac_func();
+#endif
+
+; return 0; }
+EOF
+if { (eval echo configure:6901: \"$ac_link\") 1>&5; (eval $ac_link) 2>&5; } && test -s conftest${ac_exeext}; then
+ rm -rf conftest*
+ eval "ac_cv_func_$ac_func=yes"
+else
+ echo "configure: failed program was:" >&5
+ cat conftest.$ac_ext >&5
+ rm -rf conftest*
+ eval "ac_cv_func_$ac_func=no"
+fi
+rm -f conftest*
+fi
+
+if eval "test \"`echo '$ac_cv_func_'$ac_func`\" = yes"; then
+ echo "$ac_t""yes" 1>&6
+ ac_tr_func=HAVE_`echo $ac_func | tr 'abcdefghijklmnopqrstuvwxyz' 'ABCDEFGHIJKLMNOPQRSTUVWXYZ'`
+ cat >> confdefs.h <<EOF
+#define $ac_tr_func 1
+EOF
+
+else
+ echo "$ac_t""no" 1>&6
+fi
+done
+
+fi
+
+
+echo $ac_n "checking for library containing gethostbyname""... $ac_c" 1>&6
+echo "configure:6929: checking for library containing gethostbyname" >&5
+if eval "test \"`echo '$''{'ac_cv_search_gethostbyname'+set}'`\" = set"; then
+ echo $ac_n "(cached) $ac_c" 1>&6
+else
+ ac_func_search_save_LIBS="$LIBS"
+ac_cv_search_gethostbyname="no"
+cat > conftest.$ac_ext <<EOF
+#line 6936 "configure"
+#include "confdefs.h"
+/* Override any gcc2 internal prototype to avoid an error. */
+/* We use char because int might match the return type of a gcc2
+ builtin and then its argument prototype would still apply. */
+char gethostbyname();
+
+int main() {
+gethostbyname()
+; return 0; }
+EOF
+if { (eval echo configure:6947: \"$ac_link\") 1>&5; (eval $ac_link) 2>&5; } && test -s conftest${ac_exeext}; then
+ rm -rf conftest*
+ ac_cv_search_gethostbyname="none required"
+else
+ echo "configure: failed program was:" >&5
+ cat conftest.$ac_ext >&5
+fi
+rm -f conftest*
+test "$ac_cv_search_gethostbyname" = "no" && for i in nsl; do
+LIBS="-l$i $ac_func_search_save_LIBS"
+cat > conftest.$ac_ext <<EOF
+#line 6958 "configure"
+#include "confdefs.h"
+/* Override any gcc2 internal prototype to avoid an error. */
+/* We use char because int might match the return type of a gcc2
+ builtin and then its argument prototype would still apply. */
+char gethostbyname();
+
+int main() {
+gethostbyname()
+; return 0; }
+EOF
+if { (eval echo configure:6969: \"$ac_link\") 1>&5; (eval $ac_link) 2>&5; } && test -s conftest${ac_exeext}; then
+ rm -rf conftest*
+ ac_cv_search_gethostbyname="-l$i"
+break
+else
+ echo "configure: failed program was:" >&5
+ cat conftest.$ac_ext >&5
+fi
+rm -f conftest*
+done
+LIBS="$ac_func_search_save_LIBS"
+fi
+
+echo "$ac_t""$ac_cv_search_gethostbyname" 1>&6
+if test "$ac_cv_search_gethostbyname" != "no"; then
+ test "$ac_cv_search_gethostbyname" = "none required" || LIBS="$ac_cv_search_gethostbyname $LIBS"
+
+else :
+
+fi
+
+echo $ac_n "checking for library containing socket""... $ac_c" 1>&6
+echo "configure:6991: checking for library containing socket" >&5
+if eval "test \"`echo '$''{'ac_cv_search_socket'+set}'`\" = set"; then
+ echo $ac_n "(cached) $ac_c" 1>&6
+else
+ ac_func_search_save_LIBS="$LIBS"
+ac_cv_search_socket="no"
+cat > conftest.$ac_ext <<EOF
+#line 6998 "configure"
+#include "confdefs.h"
+/* Override any gcc2 internal prototype to avoid an error. */
+/* We use char because int might match the return type of a gcc2
+ builtin and then its argument prototype would still apply. */
+char socket();
+
+int main() {
+socket()
+; return 0; }
+EOF
+if { (eval echo configure:7009: \"$ac_link\") 1>&5; (eval $ac_link) 2>&5; } && test -s conftest${ac_exeext}; then
+ rm -rf conftest*
+ ac_cv_search_socket="none required"
+else
+ echo "configure: failed program was:" >&5
+ cat conftest.$ac_ext >&5
+fi
+rm -f conftest*
+test "$ac_cv_search_socket" = "no" && for i in socket; do
+LIBS="-l$i $ac_func_search_save_LIBS"
+cat > conftest.$ac_ext <<EOF
+#line 7020 "configure"
+#include "confdefs.h"
+/* Override any gcc2 internal prototype to avoid an error. */
+/* We use char because int might match the return type of a gcc2
+ builtin and then its argument prototype would still apply. */
+char socket();
+
+int main() {
+socket()
+; return 0; }
+EOF
+if { (eval echo configure:7031: \"$ac_link\") 1>&5; (eval $ac_link) 2>&5; } && test -s conftest${ac_exeext}; then
+ rm -rf conftest*
+ ac_cv_search_socket="-l$i"
+break
+else
+ echo "configure: failed program was:" >&5
+ cat conftest.$ac_ext >&5
+fi
+rm -f conftest*
+done
+LIBS="$ac_func_search_save_LIBS"
+fi
+
+echo "$ac_t""$ac_cv_search_socket" 1>&6
+if test "$ac_cv_search_socket" != "no"; then
+ test "$ac_cv_search_socket" = "none required" || LIBS="$ac_cv_search_socket $LIBS"
+
+else :
+ echo $ac_n "checking for socket in -lnsl""... $ac_c" 1>&6
+echo "configure:7050: checking for socket in -lnsl" >&5
+ac_lib_var=`echo nsl'_'socket | sed 'y%./+-%__p_%'`
+if eval "test \"`echo '$''{'ac_cv_lib_$ac_lib_var'+set}'`\" = set"; then
+ echo $ac_n "(cached) $ac_c" 1>&6
+else
+ ac_save_LIBS="$LIBS"
+LIBS="-lnsl -lsocket $LIBS"
+cat > conftest.$ac_ext <<EOF
+#line 7058 "configure"
+#include "confdefs.h"
+/* Override any gcc2 internal prototype to avoid an error. */
+/* We use char because int might match the return type of a gcc2
+ builtin and then its argument prototype would still apply. */
+char socket();
+
+int main() {
+socket()
+; return 0; }
+EOF
+if { (eval echo configure:7069: \"$ac_link\") 1>&5; (eval $ac_link) 2>&5; } && test -s conftest${ac_exeext}; then
+ rm -rf conftest*
+ eval "ac_cv_lib_$ac_lib_var=yes"
+else
+ echo "configure: failed program was:" >&5
+ cat conftest.$ac_ext >&5
+ rm -rf conftest*
+ eval "ac_cv_lib_$ac_lib_var=no"
+fi
+rm -f conftest*
+LIBS="$ac_save_LIBS"
+
+fi
+if eval "test \"`echo '$ac_cv_lib_'$ac_lib_var`\" = yes"; then
+ echo "$ac_t""yes" 1>&6
+ LIBS="$LIBS -lsocket -lnsl"
+else
+ echo "$ac_t""no" 1>&6
+fi
+
+fi
+
+
+echo $ac_n "checking for library containing inet_aton""... $ac_c" 1>&6
+echo "configure:7093: checking for library containing inet_aton" >&5
+if eval "test \"`echo '$''{'ac_cv_search_inet_aton'+set}'`\" = set"; then
+ echo $ac_n "(cached) $ac_c" 1>&6
+else
+ ac_func_search_save_LIBS="$LIBS"
+ac_cv_search_inet_aton="no"
+cat > conftest.$ac_ext <<EOF
+#line 7100 "configure"
+#include "confdefs.h"
+/* Override any gcc2 internal prototype to avoid an error. */
+/* We use char because int might match the return type of a gcc2
+ builtin and then its argument prototype would still apply. */
+char inet_aton();
+
+int main() {
+inet_aton()
+; return 0; }
+EOF
+if { (eval echo configure:7111: \"$ac_link\") 1>&5; (eval $ac_link) 2>&5; } && test -s conftest${ac_exeext}; then
+ rm -rf conftest*
+ ac_cv_search_inet_aton="none required"
+else
+ echo "configure: failed program was:" >&5
+ cat conftest.$ac_ext >&5
+fi
+rm -f conftest*
+test "$ac_cv_search_inet_aton" = "no" && for i in resolv; do
+LIBS="-l$i $ac_func_search_save_LIBS"
+cat > conftest.$ac_ext <<EOF
+#line 7122 "configure"
+#include "confdefs.h"
+/* Override any gcc2 internal prototype to avoid an error. */
+/* We use char because int might match the return type of a gcc2
+ builtin and then its argument prototype would still apply. */
+char inet_aton();
+
+int main() {
+inet_aton()
+; return 0; }
+EOF
+if { (eval echo configure:7133: \"$ac_link\") 1>&5; (eval $ac_link) 2>&5; } && test -s conftest${ac_exeext}; then
+ rm -rf conftest*
+ ac_cv_search_inet_aton="-l$i"
+break
+else
+ echo "configure: failed program was:" >&5
+ cat conftest.$ac_ext >&5
+fi
+rm -f conftest*
+done
+LIBS="$ac_func_search_save_LIBS"
+fi
+
+echo "$ac_t""$ac_cv_search_inet_aton" 1>&6
+if test "$ac_cv_search_inet_aton" != "no"; then
+ test "$ac_cv_search_inet_aton" = "none required" || LIBS="$ac_cv_search_inet_aton $LIBS"
+
+else :
+
+fi
+
+inn_save_LIBS=$LIBS
+LIBS=${CRYPT_LIB}
+
+echo $ac_n "checking for library containing crypt""... $ac_c" 1>&6
+echo "configure:7158: checking for library containing crypt" >&5
+if eval "test \"`echo '$''{'ac_cv_search_crypt'+set}'`\" = set"; then
+ echo $ac_n "(cached) $ac_c" 1>&6
+else
+ ac_func_search_save_LIBS="$LIBS"
+ac_cv_search_crypt="no"
+cat > conftest.$ac_ext <<EOF
+#line 7165 "configure"
+#include "confdefs.h"
+/* Override any gcc2 internal prototype to avoid an error. */
+/* We use char because int might match the return type of a gcc2
+ builtin and then its argument prototype would still apply. */
+char crypt();
+
+int main() {
+crypt()
+; return 0; }
+EOF
+if { (eval echo configure:7176: \"$ac_link\") 1>&5; (eval $ac_link) 2>&5; } && test -s conftest${ac_exeext}; then
+ rm -rf conftest*
+ ac_cv_search_crypt="none required"
+else
+ echo "configure: failed program was:" >&5
+ cat conftest.$ac_ext >&5
+fi
+rm -f conftest*
+test "$ac_cv_search_crypt" = "no" && for i in crypt; do
+LIBS="-l$i $ac_func_search_save_LIBS"
+cat > conftest.$ac_ext <<EOF
+#line 7187 "configure"
+#include "confdefs.h"
+/* Override any gcc2 internal prototype to avoid an error. */
+/* We use char because int might match the return type of a gcc2
+ builtin and then its argument prototype would still apply. */
+char crypt();
+
+int main() {
+crypt()
+; return 0; }
+EOF
+if { (eval echo configure:7198: \"$ac_link\") 1>&5; (eval $ac_link) 2>&5; } && test -s conftest${ac_exeext}; then
+ rm -rf conftest*
+ ac_cv_search_crypt="-l$i"
+break
+else
+ echo "configure: failed program was:" >&5
+ cat conftest.$ac_ext >&5
+fi
+rm -f conftest*
+done
+LIBS="$ac_func_search_save_LIBS"
+fi
+
+echo "$ac_t""$ac_cv_search_crypt" 1>&6
+if test "$ac_cv_search_crypt" != "no"; then
+ test "$ac_cv_search_crypt" = "none required" || LIBS="$ac_cv_search_crypt $LIBS"
+ CRYPT_LIB=$LIBS
+
+else :
+
+fi
+LIBS=$inn_save_LIBS
+
+inn_save_LIBS=$LIBS
+LIBS=${SHADOW_LIB}
+
+echo $ac_n "checking for library containing getspnam""... $ac_c" 1>&6
+echo "configure:7225: checking for library containing getspnam" >&5
+if eval "test \"`echo '$''{'ac_cv_search_getspnam'+set}'`\" = set"; then
+ echo $ac_n "(cached) $ac_c" 1>&6
+else
+ ac_func_search_save_LIBS="$LIBS"
+ac_cv_search_getspnam="no"
+cat > conftest.$ac_ext <<EOF
+#line 7232 "configure"
+#include "confdefs.h"
+/* Override any gcc2 internal prototype to avoid an error. */
+/* We use char because int might match the return type of a gcc2
+ builtin and then its argument prototype would still apply. */
+char getspnam();
+
+int main() {
+getspnam()
+; return 0; }
+EOF
+if { (eval echo configure:7243: \"$ac_link\") 1>&5; (eval $ac_link) 2>&5; } && test -s conftest${ac_exeext}; then
+ rm -rf conftest*
+ ac_cv_search_getspnam="none required"
+else
+ echo "configure: failed program was:" >&5
+ cat conftest.$ac_ext >&5
+fi
+rm -f conftest*
+test "$ac_cv_search_getspnam" = "no" && for i in shadow; do
+LIBS="-l$i $ac_func_search_save_LIBS"
+cat > conftest.$ac_ext <<EOF
+#line 7254 "configure"
+#include "confdefs.h"
+/* Override any gcc2 internal prototype to avoid an error. */
+/* We use char because int might match the return type of a gcc2
+ builtin and then its argument prototype would still apply. */
+char getspnam();
+
+int main() {
+getspnam()
+; return 0; }
+EOF
+if { (eval echo configure:7265: \"$ac_link\") 1>&5; (eval $ac_link) 2>&5; } && test -s conftest${ac_exeext}; then
+ rm -rf conftest*
+ ac_cv_search_getspnam="-l$i"
+break
+else
+ echo "configure: failed program was:" >&5
+ cat conftest.$ac_ext >&5
+fi
+rm -f conftest*
+done
+LIBS="$ac_func_search_save_LIBS"
+fi
+
+echo "$ac_t""$ac_cv_search_getspnam" 1>&6
+if test "$ac_cv_search_getspnam" != "no"; then
+ test "$ac_cv_search_getspnam" = "none required" || LIBS="$ac_cv_search_getspnam $LIBS"
+ SHADOW_LIB=$LIBS
+
+else :
+
+fi
+LIBS=$inn_save_LIBS
+
+
+inn_check_pam=1
+for ac_hdr in pam/pam_appl.h
+do
+ac_safe=`echo "$ac_hdr" | sed 'y%./+-%__p_%'`
+echo $ac_n "checking for $ac_hdr""... $ac_c" 1>&6
+echo "configure:7294: checking for $ac_hdr" >&5
+if eval "test \"`echo '$''{'ac_cv_header_$ac_safe'+set}'`\" = set"; then
+ echo $ac_n "(cached) $ac_c" 1>&6
+else
+ cat > conftest.$ac_ext <<EOF
+#line 7299 "configure"
+#include "confdefs.h"
+#include <$ac_hdr>
+EOF
+ac_try="$ac_cpp conftest.$ac_ext >/dev/null 2>conftest.out"
+{ (eval echo configure:7304: \"$ac_try\") 1>&5; (eval $ac_try) 2>&5; }
+ac_err=`grep -v '^ *+' conftest.out | grep -v "^conftest.${ac_ext}\$"`
+if test -z "$ac_err"; then
+ rm -rf conftest*
+ eval "ac_cv_header_$ac_safe=yes"
+else
+ echo "$ac_err" >&5
+ echo "configure: failed program was:" >&5
+ cat conftest.$ac_ext >&5
+ rm -rf conftest*
+ eval "ac_cv_header_$ac_safe=no"
+fi
+rm -f conftest*
+fi
+if eval "test \"`echo '$ac_cv_header_'$ac_safe`\" = yes"; then
+ echo "$ac_t""yes" 1>&6
+ ac_tr_hdr=HAVE_`echo $ac_hdr | sed 'y%abcdefghijklmnopqrstuvwxyz./-%ABCDEFGHIJKLMNOPQRSTUVWXYZ___%'`
+ cat >> confdefs.h <<EOF
+#define $ac_tr_hdr 1
+EOF
+
+else
+ echo "$ac_t""no" 1>&6
+ac_safe=`echo "security/pam_appl.h" | sed 'y%./+-%__p_%'`
+echo $ac_n "checking for security/pam_appl.h""... $ac_c" 1>&6
+echo "configure:7329: checking for security/pam_appl.h" >&5
+if eval "test \"`echo '$''{'ac_cv_header_$ac_safe'+set}'`\" = set"; then
+ echo $ac_n "(cached) $ac_c" 1>&6
+else
+ cat > conftest.$ac_ext <<EOF
+#line 7334 "configure"
+#include "confdefs.h"
+#include <security/pam_appl.h>
+EOF
+ac_try="$ac_cpp conftest.$ac_ext >/dev/null 2>conftest.out"
+{ (eval echo configure:7339: \"$ac_try\") 1>&5; (eval $ac_try) 2>&5; }
+ac_err=`grep -v '^ *+' conftest.out | grep -v "^conftest.${ac_ext}\$"`
+if test -z "$ac_err"; then
+ rm -rf conftest*
+ eval "ac_cv_header_$ac_safe=yes"
+else
+ echo "$ac_err" >&5
+ echo "configure: failed program was:" >&5
+ cat conftest.$ac_ext >&5
+ rm -rf conftest*
+ eval "ac_cv_header_$ac_safe=no"
+fi
+rm -f conftest*
+fi
+if eval "test \"`echo '$ac_cv_header_'$ac_safe`\" = yes"; then
+ echo "$ac_t""yes" 1>&6
+ :
+else
+ echo "$ac_t""no" 1>&6
+inn_check_pam=0
+fi
+
+fi
+done
+
+if test x"$inn_check_pam" = x1; then
+ inn_save_LIBS=$LIBS
+LIBS=${PAM_LIB}
+
+echo $ac_n "checking for library containing pam_start""... $ac_c" 1>&6
+echo "configure:7369: checking for library containing pam_start" >&5
+if eval "test \"`echo '$''{'ac_cv_search_pam_start'+set}'`\" = set"; then
+ echo $ac_n "(cached) $ac_c" 1>&6
+else
+ ac_func_search_save_LIBS="$LIBS"
+ac_cv_search_pam_start="no"
+cat > conftest.$ac_ext <<EOF
+#line 7376 "configure"
+#include "confdefs.h"
+/* Override any gcc2 internal prototype to avoid an error. */
+/* We use char because int might match the return type of a gcc2
+ builtin and then its argument prototype would still apply. */
+char pam_start();
+
+int main() {
+pam_start()
+; return 0; }
+EOF
+if { (eval echo configure:7387: \"$ac_link\") 1>&5; (eval $ac_link) 2>&5; } && test -s conftest${ac_exeext}; then
+ rm -rf conftest*
+ ac_cv_search_pam_start="none required"
+else
+ echo "configure: failed program was:" >&5
+ cat conftest.$ac_ext >&5
+fi
+rm -f conftest*
+test "$ac_cv_search_pam_start" = "no" && for i in pam; do
+LIBS="-l$i $ac_func_search_save_LIBS"
+cat > conftest.$ac_ext <<EOF
+#line 7398 "configure"
+#include "confdefs.h"
+/* Override any gcc2 internal prototype to avoid an error. */
+/* We use char because int might match the return type of a gcc2
+ builtin and then its argument prototype would still apply. */
+char pam_start();
+
+int main() {
+pam_start()
+; return 0; }
+EOF
+if { (eval echo configure:7409: \"$ac_link\") 1>&5; (eval $ac_link) 2>&5; } && test -s conftest${ac_exeext}; then
+ rm -rf conftest*
+ ac_cv_search_pam_start="-l$i"
+break
+else
+ echo "configure: failed program was:" >&5
+ cat conftest.$ac_ext >&5
+fi
+rm -f conftest*
+done
+LIBS="$ac_func_search_save_LIBS"
+fi
+
+echo "$ac_t""$ac_cv_search_pam_start" 1>&6
+if test "$ac_cv_search_pam_start" != "no"; then
+ test "$ac_cv_search_pam_start" = "none required" || LIBS="$ac_cv_search_pam_start $LIBS"
+ PAM_LIB=$LIBS
+ cat >> confdefs.h <<\EOF
+#define HAVE_PAM 1
+EOF
+
+else :
+
+fi
+LIBS=$inn_save_LIBS
+
+fi
+
+if test x"$inn_enable_keywords" = x1 ; then
+ inn_save_LIBS=$LIBS
+LIBS=${REGEX_LIB}
+
+echo $ac_n "checking for library containing regexec""... $ac_c" 1>&6
+echo "configure:7442: checking for library containing regexec" >&5
+if eval "test \"`echo '$''{'ac_cv_search_regexec'+set}'`\" = set"; then
+ echo $ac_n "(cached) $ac_c" 1>&6
+else
+ ac_func_search_save_LIBS="$LIBS"
+ac_cv_search_regexec="no"
+cat > conftest.$ac_ext <<EOF
+#line 7449 "configure"
+#include "confdefs.h"
+/* Override any gcc2 internal prototype to avoid an error. */
+/* We use char because int might match the return type of a gcc2
+ builtin and then its argument prototype would still apply. */
+char regexec();
+
+int main() {
+regexec()
+; return 0; }
+EOF
+if { (eval echo configure:7460: \"$ac_link\") 1>&5; (eval $ac_link) 2>&5; } && test -s conftest${ac_exeext}; then
+ rm -rf conftest*
+ ac_cv_search_regexec="none required"
+else
+ echo "configure: failed program was:" >&5
+ cat conftest.$ac_ext >&5
+fi
+rm -f conftest*
+test "$ac_cv_search_regexec" = "no" && for i in regex; do
+LIBS="-l$i $ac_func_search_save_LIBS"
+cat > conftest.$ac_ext <<EOF
+#line 7471 "configure"
+#include "confdefs.h"
+/* Override any gcc2 internal prototype to avoid an error. */
+/* We use char because int might match the return type of a gcc2
+ builtin and then its argument prototype would still apply. */
+char regexec();
+
+int main() {
+regexec()
+; return 0; }
+EOF
+if { (eval echo configure:7482: \"$ac_link\") 1>&5; (eval $ac_link) 2>&5; } && test -s conftest${ac_exeext}; then
+ rm -rf conftest*
+ ac_cv_search_regexec="-l$i"
+break
+else
+ echo "configure: failed program was:" >&5
+ cat conftest.$ac_ext >&5
+fi
+rm -f conftest*
+done
+LIBS="$ac_func_search_save_LIBS"
+fi
+
+echo "$ac_t""$ac_cv_search_regexec" 1>&6
+if test "$ac_cv_search_regexec" != "no"; then
+ test "$ac_cv_search_regexec" = "none required" || LIBS="$ac_cv_search_regexec $LIBS"
+ REGEX_LIB=$LIBS
+
+else :
+ { echo "configure: error: no usable regular expression library found" 1>&2; exit 1; }
+fi
+LIBS=$inn_save_LIBS
+
+fi
+
+
+# Check whether --with-berkeleydb or --without-berkeleydb was given.
+if test "${with_berkeleydb+set}" = set; then
+ withval="$with_berkeleydb"
+ BERKELEY_DB_DIR=$with_berkeleydb
+else
+ BERKELEY_DB_DIR=no
+fi
+
+echo $ac_n "checking if BerkeleyDB is desired""... $ac_c" 1>&6
+echo "configure:7517: checking if BerkeleyDB is desired" >&5
+if test x"$BERKELEY_DB_DIR" = xno ; then
+ echo "$ac_t""no" 1>&6
+ BERKELEY_DB_LDFLAGS=
+ BERKELEY_DB_CFLAGS=
+ BERKELEY_DB_LIB=
+else
+ echo "$ac_t""yes" 1>&6
+ echo $ac_n "checking for BerkeleyDB location""... $ac_c" 1>&6
+echo "configure:7526: checking for BerkeleyDB location" >&5
+ if test x"$BERKELEY_DB_DIR" = xyes ; then
+ for v in BerkeleyDB BerkeleyDB.3.0 BerkeleyDB.3.1 BerkeleyDB.3.2 \
+ BerkeleyDB.3.3 BerkeleyDB.4.0 BerkeleyDB.4.1 BerkeleyDB.4.2 \
+ BerkeleyDB.4.3 BerkeleyDB.4.4 BerkeleyDB.4.5 BerkeleyDB.4.6; do
+ for d in $prefix /usr/local /opt /usr ; do
+ if test -d "$d/$v" ; then
+ BERKELEY_DB_DIR="$d/$v"
+ break
+ fi
+ done
+ done
+ fi
+ if test x"$BERKELEY_DB_DIR" = xyes ; then
+ for v in db46 db45 db44 db43 db42 db41 db4 db3 db2 ; do
+ if test -d "/usr/local/include/$v" ; then
+ BERKELEY_DB_LDFLAGS="-L/usr/local/lib"
+ BERKELEY_DB_CFLAGS="-I/usr/local/include/$v"
+ BERKELEY_DB_LIB="-l$v"
+ echo "$ac_t""FreeBSD locations" 1>&6
+ break
+ fi
+ done
+ if test x"$BERKELEY_DB_LIB" = x ; then
+ for v in db44 db43 db42 db41 db4 db3 db2 ; do
+ if test -d "/usr/include/$v" ; then
+ BERKELEY_DB_CFLAGS="-I/usr/include/$v"
+ BERKELEY_DB_LIB="-l$v"
+ echo "$ac_t""Linux locations" 1>&6
+ break
+ fi
+ done
+ if test x"$BERKELEY_DB_LIB" = x ; then
+ BERKELEY_DB_LIB=-ldb
+ echo "$ac_t""trying -ldb" 1>&6
+ fi
+ fi
+ else
+ BERKELEY_DB_LDFLAGS="-L$BERKELEY_DB_DIR/lib"
+ BERKELEY_DB_CFLAGS="-I$BERKELEY_DB_DIR/include"
+ BERKELEY_DB_LIB="-ldb"
+ echo "$ac_t""$BERKELEY_DB_DIR" 1>&6
+ fi
+ cat >> confdefs.h <<\EOF
+#define USE_BERKELEY_DB 1
+EOF
+
+fi
+
+
+
+
+if test x"$BERKELEY_DB_LIB" != x ; then
+ DBM_INC="$BERKELEY_DB_CFLAGS"
+ DBM_LIB="$BERKELEY_DB_LDFLAGS $BERKELEY_DB_LIB"
+
+ cat >> confdefs.h <<\EOF
+#define HAVE_BDB_DBM 1
+EOF
+
+else
+ inn_save_LIBS=$LIBS
+LIBS=${DBM_LIB}
+
+echo $ac_n "checking for library containing dbm_open""... $ac_c" 1>&6
+echo "configure:7591: checking for library containing dbm_open" >&5
+if eval "test \"`echo '$''{'ac_cv_search_dbm_open'+set}'`\" = set"; then
+ echo $ac_n "(cached) $ac_c" 1>&6
+else
+ ac_func_search_save_LIBS="$LIBS"
+ac_cv_search_dbm_open="no"
+cat > conftest.$ac_ext <<EOF
+#line 7598 "configure"
+#include "confdefs.h"
+/* Override any gcc2 internal prototype to avoid an error. */
+/* We use char because int might match the return type of a gcc2
+ builtin and then its argument prototype would still apply. */
+char dbm_open();
+
+int main() {
+dbm_open()
+; return 0; }
+EOF
+if { (eval echo configure:7609: \"$ac_link\") 1>&5; (eval $ac_link) 2>&5; } && test -s conftest${ac_exeext}; then
+ rm -rf conftest*
+ ac_cv_search_dbm_open="none required"
+else
+ echo "configure: failed program was:" >&5
+ cat conftest.$ac_ext >&5
+fi
+rm -f conftest*
+test "$ac_cv_search_dbm_open" = "no" && for i in ndbm dbm; do
+LIBS="-l$i $ac_func_search_save_LIBS"
+cat > conftest.$ac_ext <<EOF
+#line 7620 "configure"
+#include "confdefs.h"
+/* Override any gcc2 internal prototype to avoid an error. */
+/* We use char because int might match the return type of a gcc2
+ builtin and then its argument prototype would still apply. */
+char dbm_open();
+
+int main() {
+dbm_open()
+; return 0; }
+EOF
+if { (eval echo configure:7631: \"$ac_link\") 1>&5; (eval $ac_link) 2>&5; } && test -s conftest${ac_exeext}; then
+ rm -rf conftest*
+ ac_cv_search_dbm_open="-l$i"
+break
+else
+ echo "configure: failed program was:" >&5
+ cat conftest.$ac_ext >&5
+fi
+rm -f conftest*
+done
+LIBS="$ac_func_search_save_LIBS"
+fi
+
+echo "$ac_t""$ac_cv_search_dbm_open" 1>&6
+if test "$ac_cv_search_dbm_open" != "no"; then
+ test "$ac_cv_search_dbm_open" = "none required" || LIBS="$ac_cv_search_dbm_open $LIBS"
+ DBM_LIB=$LIBS
+ cat >> confdefs.h <<\EOF
+#define HAVE_DBM 1
+EOF
+
+else :
+
+fi
+LIBS=$inn_save_LIBS
+
+ DBM_INC=
+fi
+
+
+
+# Check whether --with-openssl or --without-openssl was given.
+if test "${with_openssl+set}" = set; then
+ withval="$with_openssl"
+ OPENSSL_DIR=$with_openssl
+else
+ OPENSSL_DIR=no
+fi
+
+echo $ac_n "checking if OpenSSL is desired""... $ac_c" 1>&6
+echo "configure:7671: checking if OpenSSL is desired" >&5
+if test x"$OPENSSL_DIR" = xno ; then
+ echo "$ac_t""no" 1>&6
+ SSL_BIN=
+ SSL_INC=
+ SSL_LIB=
+else
+ echo "$ac_t""yes" 1>&6
+ echo $ac_n "checking for OpenSSL location""... $ac_c" 1>&6
+echo "configure:7680: checking for OpenSSL location" >&5
+ if test x"$OPENSSL_DIR" = xyes ; then
+ for dir in $prefix /usr/local/ssl /usr/lib/ssl /usr/ssl /usr/pkg \
+ /usr/local /usr ; do
+ if test -f "$dir/include/openssl/ssl.h" ; then
+ OPENSSL_DIR=$dir
+ break
+ fi
+ done
+ fi
+ if test x"$OPENSSL_DIR" = xyes ; then
+ { echo "configure: error: Can not find OpenSSL" 1>&2; exit 1; }
+ else
+ echo "$ac_t""$OPENSSL_DIR" 1>&6
+ SSL_BIN="${OPENSSL_DIR}/bin"
+ SSL_INC="-I${OPENSSL_DIR}/include"
+
+ # This is mildly tricky. In order to satisfy most linkers, libraries
+ # have to be listed in the right order, which means that libraries
+ # with dependencies on other libraries need to be listed first. But
+ # the -L flag for the OpenSSL library directory needs to go first of
+ # all. So put the -L flag into LIBS and accumulate actual libraries
+ # into SSL_LIB, and then at the end, restore LIBS and move -L to the
+ # beginning of SSL_LIB.
+ inn_save_LIBS=$LIBS
+ LIBS="$LIBS -L${OPENSSL_DIR}/lib"
+ SSL_LIB=''
+ echo $ac_n "checking for RSAPublicEncrypt in -lrsaref""... $ac_c" 1>&6
+echo "configure:7708: checking for RSAPublicEncrypt in -lrsaref" >&5
+ac_lib_var=`echo rsaref'_'RSAPublicEncrypt | sed 'y%./+-%__p_%'`
+if eval "test \"`echo '$''{'ac_cv_lib_$ac_lib_var'+set}'`\" = set"; then
+ echo $ac_n "(cached) $ac_c" 1>&6
+else
+ ac_save_LIBS="$LIBS"
+LIBS="-lrsaref $LIBS"
+cat > conftest.$ac_ext <<EOF
+#line 7716 "configure"
+#include "confdefs.h"
+/* Override any gcc2 internal prototype to avoid an error. */
+/* We use char because int might match the return type of a gcc2
+ builtin and then its argument prototype would still apply. */
+char RSAPublicEncrypt();
+
+int main() {
+RSAPublicEncrypt()
+; return 0; }
+EOF
+if { (eval echo configure:7727: \"$ac_link\") 1>&5; (eval $ac_link) 2>&5; } && test -s conftest${ac_exeext}; then
+ rm -rf conftest*
+ eval "ac_cv_lib_$ac_lib_var=yes"
+else
+ echo "configure: failed program was:" >&5
+ cat conftest.$ac_ext >&5
+ rm -rf conftest*
+ eval "ac_cv_lib_$ac_lib_var=no"
+fi
+rm -f conftest*
+LIBS="$ac_save_LIBS"
+
+fi
+if eval "test \"`echo '$ac_cv_lib_'$ac_lib_var`\" = yes"; then
+ echo "$ac_t""yes" 1>&6
+ echo $ac_n "checking for RSAPublicEncrypt in -lRSAglue""... $ac_c" 1>&6
+echo "configure:7743: checking for RSAPublicEncrypt in -lRSAglue" >&5
+ac_lib_var=`echo RSAglue'_'RSAPublicEncrypt | sed 'y%./+-%__p_%'`
+if eval "test \"`echo '$''{'ac_cv_lib_$ac_lib_var'+set}'`\" = set"; then
+ echo $ac_n "(cached) $ac_c" 1>&6
+else
+ ac_save_LIBS="$LIBS"
+LIBS="-lRSAglue -lrsaref $LIBS"
+cat > conftest.$ac_ext <<EOF
+#line 7751 "configure"
+#include "confdefs.h"
+/* Override any gcc2 internal prototype to avoid an error. */
+/* We use char because int might match the return type of a gcc2
+ builtin and then its argument prototype would still apply. */
+char RSAPublicEncrypt();
+
+int main() {
+RSAPublicEncrypt()
+; return 0; }
+EOF
+if { (eval echo configure:7762: \"$ac_link\") 1>&5; (eval $ac_link) 2>&5; } && test -s conftest${ac_exeext}; then
+ rm -rf conftest*
+ eval "ac_cv_lib_$ac_lib_var=yes"
+else
+ echo "configure: failed program was:" >&5
+ cat conftest.$ac_ext >&5
+ rm -rf conftest*
+ eval "ac_cv_lib_$ac_lib_var=no"
+fi
+rm -f conftest*
+LIBS="$ac_save_LIBS"
+
+fi
+if eval "test \"`echo '$ac_cv_lib_'$ac_lib_var`\" = yes"; then
+ echo "$ac_t""yes" 1>&6
+ SSL_LIB="-lRSAglue -lrsaref"
+else
+ echo "$ac_t""no" 1>&6
+fi
+
+else
+ echo "$ac_t""no" 1>&6
+fi
+
+ echo $ac_n "checking for BIO_new in -lcrypto""... $ac_c" 1>&6
+echo "configure:7787: checking for BIO_new in -lcrypto" >&5
+ac_lib_var=`echo crypto'_'BIO_new | sed 'y%./+-%__p_%'`
+if eval "test \"`echo '$''{'ac_cv_lib_$ac_lib_var'+set}'`\" = set"; then
+ echo $ac_n "(cached) $ac_c" 1>&6
+else
+ ac_save_LIBS="$LIBS"
+LIBS="-lcrypto $SSL_LIB $LIBS"
+cat > conftest.$ac_ext <<EOF
+#line 7795 "configure"
+#include "confdefs.h"
+/* Override any gcc2 internal prototype to avoid an error. */
+/* We use char because int might match the return type of a gcc2
+ builtin and then its argument prototype would still apply. */
+char BIO_new();
+
+int main() {
+BIO_new()
+; return 0; }
+EOF
+if { (eval echo configure:7806: \"$ac_link\") 1>&5; (eval $ac_link) 2>&5; } && test -s conftest${ac_exeext}; then
+ rm -rf conftest*
+ eval "ac_cv_lib_$ac_lib_var=yes"
+else
+ echo "configure: failed program was:" >&5
+ cat conftest.$ac_ext >&5
+ rm -rf conftest*
+ eval "ac_cv_lib_$ac_lib_var=no"
+fi
+rm -f conftest*
+LIBS="$ac_save_LIBS"
+
+fi
+if eval "test \"`echo '$ac_cv_lib_'$ac_lib_var`\" = yes"; then
+ echo "$ac_t""yes" 1>&6
+ echo $ac_n "checking for DSO_load in -ldl""... $ac_c" 1>&6
+echo "configure:7822: checking for DSO_load in -ldl" >&5
+ac_lib_var=`echo dl'_'DSO_load | sed 'y%./+-%__p_%'`
+if eval "test \"`echo '$''{'ac_cv_lib_$ac_lib_var'+set}'`\" = set"; then
+ echo $ac_n "(cached) $ac_c" 1>&6
+else
+ ac_save_LIBS="$LIBS"
+LIBS="-ldl -lcrypto -ldl $SSL_LIB $LIBS"
+cat > conftest.$ac_ext <<EOF
+#line 7830 "configure"
+#include "confdefs.h"
+/* Override any gcc2 internal prototype to avoid an error. */
+/* We use char because int might match the return type of a gcc2
+ builtin and then its argument prototype would still apply. */
+char DSO_load();
+
+int main() {
+DSO_load()
+; return 0; }
+EOF
+if { (eval echo configure:7841: \"$ac_link\") 1>&5; (eval $ac_link) 2>&5; } && test -s conftest${ac_exeext}; then
+ rm -rf conftest*
+ eval "ac_cv_lib_$ac_lib_var=yes"
+else
+ echo "configure: failed program was:" >&5
+ cat conftest.$ac_ext >&5
+ rm -rf conftest*
+ eval "ac_cv_lib_$ac_lib_var=no"
+fi
+rm -f conftest*
+LIBS="$ac_save_LIBS"
+
+fi
+if eval "test \"`echo '$ac_cv_lib_'$ac_lib_var`\" = yes"; then
+ echo "$ac_t""yes" 1>&6
+ SSL_LIB="-lcrypto -ldl $SSL_LIB"
+else
+ echo "$ac_t""no" 1>&6
+SSL_LIB="-lcrypto $SSL_LIB"
+fi
+
+else
+ echo "$ac_t""no" 1>&6
+{ echo "configure: error: Can not find OpenSSL" 1>&2; exit 1; }
+fi
+
+ echo $ac_n "checking for SSL_library_init in -lssl""... $ac_c" 1>&6
+echo "configure:7868: checking for SSL_library_init in -lssl" >&5
+ac_lib_var=`echo ssl'_'SSL_library_init | sed 'y%./+-%__p_%'`
+if eval "test \"`echo '$''{'ac_cv_lib_$ac_lib_var'+set}'`\" = set"; then
+ echo $ac_n "(cached) $ac_c" 1>&6
+else
+ ac_save_LIBS="$LIBS"
+LIBS="-lssl $SSL_LIB $LIBS"
+cat > conftest.$ac_ext <<EOF
+#line 7876 "configure"
+#include "confdefs.h"
+/* Override any gcc2 internal prototype to avoid an error. */
+/* We use char because int might match the return type of a gcc2
+ builtin and then its argument prototype would still apply. */
+char SSL_library_init();
+
+int main() {
+SSL_library_init()
+; return 0; }
+EOF
+if { (eval echo configure:7887: \"$ac_link\") 1>&5; (eval $ac_link) 2>&5; } && test -s conftest${ac_exeext}; then
+ rm -rf conftest*
+ eval "ac_cv_lib_$ac_lib_var=yes"
+else
+ echo "configure: failed program was:" >&5
+ cat conftest.$ac_ext >&5
+ rm -rf conftest*
+ eval "ac_cv_lib_$ac_lib_var=no"
+fi
+rm -f conftest*
+LIBS="$ac_save_LIBS"
+
+fi
+if eval "test \"`echo '$ac_cv_lib_'$ac_lib_var`\" = yes"; then
+ echo "$ac_t""yes" 1>&6
+ SSL_LIB="-lssl $SSL_LIB"
+else
+ echo "$ac_t""no" 1>&6
+{ echo "configure: error: Can not find OpenSSL" 1>&2; exit 1; }
+fi
+
+ SSL_LIB="-L${OPENSSL_DIR}/lib $SSL_LIB"
+ LIBS=$inn_save_LIBS
+ cat >> confdefs.h <<\EOF
+#define HAVE_SSL 1
+EOF
+
+ fi
+fi
+
+
+
+
+
+# Check whether --with-sasl or --without-sasl was given.
+if test "${with_sasl+set}" = set; then
+ withval="$with_sasl"
+ SASL_DIR=$with_sasl
+else
+ SASL_DIR=no
+fi
+
+echo $ac_n "checking if SASL is desired""... $ac_c" 1>&6
+echo "configure:7930: checking if SASL is desired" >&5
+if test x"$SASL_DIR" = xno ; then
+ echo "$ac_t""no" 1>&6
+ SASL_INC=
+ SASL_LIB=
+else
+ echo "$ac_t""yes" 1>&6
+ echo $ac_n "checking for SASL location""... $ac_c" 1>&6
+echo "configure:7938: checking for SASL location" >&5
+ if test x"$SASL_DIR" = xyes ; then
+ for dir in $prefix /usr/local/sasl /usr/sasl /usr/pkg /usr/local ; do
+ if test -f "$dir/include/sasl/sasl.h" ; then
+ SASL_DIR=$dir
+ break
+ fi
+ done
+ fi
+ if test x"$SASL_DIR" = xyes ; then
+ if test -f "/usr/include/sasl/sasl.h" ; then
+ SASL_INC=-I/usr/include/sasl
+ SASL_DIR=/usr
+ echo "$ac_t""$SASL_DIR" 1>&6
+ inn_save_LIBS=$LIBS
+ echo $ac_n "checking for sasl_getprop in -lsasl2""... $ac_c" 1>&6
+echo "configure:7954: checking for sasl_getprop in -lsasl2" >&5
+ac_lib_var=`echo sasl2'_'sasl_getprop | sed 'y%./+-%__p_%'`
+if eval "test \"`echo '$''{'ac_cv_lib_$ac_lib_var'+set}'`\" = set"; then
+ echo $ac_n "(cached) $ac_c" 1>&6
+else
+ ac_save_LIBS="$LIBS"
+LIBS="-lsasl2 $LIBS"
+cat > conftest.$ac_ext <<EOF
+#line 7962 "configure"
+#include "confdefs.h"
+/* Override any gcc2 internal prototype to avoid an error. */
+/* We use char because int might match the return type of a gcc2
+ builtin and then its argument prototype would still apply. */
+char sasl_getprop();
+
+int main() {
+sasl_getprop()
+; return 0; }
+EOF
+if { (eval echo configure:7973: \"$ac_link\") 1>&5; (eval $ac_link) 2>&5; } && test -s conftest${ac_exeext}; then
+ rm -rf conftest*
+ eval "ac_cv_lib_$ac_lib_var=yes"
+else
+ echo "configure: failed program was:" >&5
+ cat conftest.$ac_ext >&5
+ rm -rf conftest*
+ eval "ac_cv_lib_$ac_lib_var=no"
+fi
+rm -f conftest*
+LIBS="$ac_save_LIBS"
+
+fi
+if eval "test \"`echo '$ac_cv_lib_'$ac_lib_var`\" = yes"; then
+ echo "$ac_t""yes" 1>&6
+ SASL_LIB=-lsasl2
+else
+ echo "$ac_t""no" 1>&6
+{ echo "configure: error: Can not find SASL" 1>&2; exit 1; }
+fi
+
+ LIBS=$inn_save_LIBS
+ cat >> confdefs.h <<\EOF
+#define HAVE_SASL 1
+EOF
+
+ else
+ { echo "configure: error: Can not find SASL" 1>&2; exit 1; }
+ fi
+ else
+ echo "$ac_t""$SASL_DIR" 1>&6
+ SASL_INC="-I${SASL_DIR}/include"
+
+ inn_save_LIBS=$LIBS
+ LIBS="$LIBS -L${SASL_DIR}/lib"
+ echo $ac_n "checking for sasl_getprop in -lsasl2""... $ac_c" 1>&6
+echo "configure:8009: checking for sasl_getprop in -lsasl2" >&5
+ac_lib_var=`echo sasl2'_'sasl_getprop | sed 'y%./+-%__p_%'`
+if eval "test \"`echo '$''{'ac_cv_lib_$ac_lib_var'+set}'`\" = set"; then
+ echo $ac_n "(cached) $ac_c" 1>&6
+else
+ ac_save_LIBS="$LIBS"
+LIBS="-lsasl2 $LIBS"
+cat > conftest.$ac_ext <<EOF
+#line 8017 "configure"
+#include "confdefs.h"
+/* Override any gcc2 internal prototype to avoid an error. */
+/* We use char because int might match the return type of a gcc2
+ builtin and then its argument prototype would still apply. */
+char sasl_getprop();
+
+int main() {
+sasl_getprop()
+; return 0; }
+EOF
+if { (eval echo configure:8028: \"$ac_link\") 1>&5; (eval $ac_link) 2>&5; } && test -s conftest${ac_exeext}; then
+ rm -rf conftest*
+ eval "ac_cv_lib_$ac_lib_var=yes"
+else
+ echo "configure: failed program was:" >&5
+ cat conftest.$ac_ext >&5
+ rm -rf conftest*
+ eval "ac_cv_lib_$ac_lib_var=no"
+fi
+rm -f conftest*
+LIBS="$ac_save_LIBS"
+
+fi
+if eval "test \"`echo '$ac_cv_lib_'$ac_lib_var`\" = yes"; then
+ echo "$ac_t""yes" 1>&6
+ SASL_LIB="-L${SASL_DIR}/lib -lsasl2"
+else
+ echo "$ac_t""no" 1>&6
+{ echo "configure: error: Can not find SASL" 1>&2; exit 1; }
+fi
+
+ LIBS=$inn_save_LIBS
+ cat >> confdefs.h <<\EOF
+#define HAVE_SASL 1
+EOF
+
+ fi
+fi
+
+
+if test x"${KRB5_INC}" != x; then
+inn_save_LIBS=$LIBS
+LIBS=${KRB5_LIB}
+
+echo $ac_n "checking for library containing krb5_parse_name""... $ac_c" 1>&6
+echo "configure:8063: checking for library containing krb5_parse_name" >&5
+if eval "test \"`echo '$''{'ac_cv_search_krb5_parse_name'+set}'`\" = set"; then
+ echo $ac_n "(cached) $ac_c" 1>&6
+else
+ ac_func_search_save_LIBS="$LIBS"
+ac_cv_search_krb5_parse_name="no"
+cat > conftest.$ac_ext <<EOF
+#line 8070 "configure"
+#include "confdefs.h"
+/* Override any gcc2 internal prototype to avoid an error. */
+/* We use char because int might match the return type of a gcc2
+ builtin and then its argument prototype would still apply. */
+char krb5_parse_name();
+
+int main() {
+krb5_parse_name()
+; return 0; }
+EOF
+if { (eval echo configure:8081: \"$ac_link\") 1>&5; (eval $ac_link) 2>&5; } && test -s conftest${ac_exeext}; then
+ rm -rf conftest*
+ ac_cv_search_krb5_parse_name="none required"
+else
+ echo "configure: failed program was:" >&5
+ cat conftest.$ac_ext >&5
+fi
+rm -f conftest*
+test "$ac_cv_search_krb5_parse_name" = "no" && for i in krb5; do
+LIBS="-l$i $LIBS -lk5crypto -lcom_err $ac_func_search_save_LIBS"
+cat > conftest.$ac_ext <<EOF
+#line 8092 "configure"
+#include "confdefs.h"
+/* Override any gcc2 internal prototype to avoid an error. */
+/* We use char because int might match the return type of a gcc2
+ builtin and then its argument prototype would still apply. */
+char krb5_parse_name();
+
+int main() {
+krb5_parse_name()
+; return 0; }
+EOF
+if { (eval echo configure:8103: \"$ac_link\") 1>&5; (eval $ac_link) 2>&5; } && test -s conftest${ac_exeext}; then
+ rm -rf conftest*
+ ac_cv_search_krb5_parse_name="-l$i"
+break
+else
+ echo "configure: failed program was:" >&5
+ cat conftest.$ac_ext >&5
+fi
+rm -f conftest*
+done
+LIBS="$ac_func_search_save_LIBS"
+fi
+
+echo "$ac_t""$ac_cv_search_krb5_parse_name" 1>&6
+if test "$ac_cv_search_krb5_parse_name" != "no"; then
+ test "$ac_cv_search_krb5_parse_name" = "none required" || LIBS="$ac_cv_search_krb5_parse_name $LIBS"
+ KRB5_LIB=$LIBS
+ KRB5_AUTH="auth_krb5"
+ KRB5_LIB="$KRB5_LDFLAGS $KRB5_LIB -lk5crypto -lcom_err"
+
+
+ for ac_hdr in et/com_err.h
+do
+ac_safe=`echo "$ac_hdr" | sed 'y%./+-%__p_%'`
+echo $ac_n "checking for $ac_hdr""... $ac_c" 1>&6
+echo "configure:8128: checking for $ac_hdr" >&5
+if eval "test \"`echo '$''{'ac_cv_header_$ac_safe'+set}'`\" = set"; then
+ echo $ac_n "(cached) $ac_c" 1>&6
+else
+ cat > conftest.$ac_ext <<EOF
+#line 8133 "configure"
+#include "confdefs.h"
+#include <$ac_hdr>
+EOF
+ac_try="$ac_cpp conftest.$ac_ext >/dev/null 2>conftest.out"
+{ (eval echo configure:8138: \"$ac_try\") 1>&5; (eval $ac_try) 2>&5; }
+ac_err=`grep -v '^ *+' conftest.out | grep -v "^conftest.${ac_ext}\$"`
+if test -z "$ac_err"; then
+ rm -rf conftest*
+ eval "ac_cv_header_$ac_safe=yes"
+else
+ echo "$ac_err" >&5
+ echo "configure: failed program was:" >&5
+ cat conftest.$ac_ext >&5
+ rm -rf conftest*
+ eval "ac_cv_header_$ac_safe=no"
+fi
+rm -f conftest*
+fi
+if eval "test \"`echo '$ac_cv_header_'$ac_safe`\" = yes"; then
+ echo "$ac_t""yes" 1>&6
+ ac_tr_hdr=HAVE_`echo $ac_hdr | sed 'y%abcdefghijklmnopqrstuvwxyz./-%ABCDEFGHIJKLMNOPQRSTUVWXYZ___%'`
+ cat >> confdefs.h <<EOF
+#define $ac_tr_hdr 1
+EOF
+
+else
+ echo "$ac_t""no" 1>&6
+fi
+done
+
+else :
+
+fi
+LIBS=$inn_save_LIBS
+
+
+inn_save_LIBS=$LIBS
+LIBS=$KRB5_LIB
+for ac_func in krb5_init_ets
+do
+echo $ac_n "checking for $ac_func""... $ac_c" 1>&6
+echo "configure:8175: checking for $ac_func" >&5
+if eval "test \"`echo '$''{'ac_cv_func_$ac_func'+set}'`\" = set"; then
+ echo $ac_n "(cached) $ac_c" 1>&6
+else
+ cat > conftest.$ac_ext <<EOF
+#line 8180 "configure"
+#include "confdefs.h"
+/* System header to define __stub macros and hopefully few prototypes,
+ which can conflict with char $ac_func(); below. */
+#include <assert.h>
+/* Override any gcc2 internal prototype to avoid an error. */
+/* We use char because int might match the return type of a gcc2
+ builtin and then its argument prototype would still apply. */
+char $ac_func();
+
+int main() {
+
+/* The GNU C library defines this for functions which it implements
+ to always fail with ENOSYS. Some functions are actually named
+ something starting with __ and the normal name is an alias. */
+#if defined (__stub_$ac_func) || defined (__stub___$ac_func)
+choke me
+#else
+$ac_func();
+#endif
+
+; return 0; }
+EOF
+if { (eval echo configure:8203: \"$ac_link\") 1>&5; (eval $ac_link) 2>&5; } && test -s conftest${ac_exeext}; then
+ rm -rf conftest*
+ eval "ac_cv_func_$ac_func=yes"
+else
+ echo "configure: failed program was:" >&5
+ cat conftest.$ac_ext >&5
+ rm -rf conftest*
+ eval "ac_cv_func_$ac_func=no"
+fi
+rm -f conftest*
+fi
+
+if eval "test \"`echo '$ac_cv_func_'$ac_func`\" = yes"; then
+ echo "$ac_t""yes" 1>&6
+ ac_tr_func=HAVE_`echo $ac_func | tr 'abcdefghijklmnopqrstuvwxyz' 'ABCDEFGHIJKLMNOPQRSTUVWXYZ'`
+ cat >> confdefs.h <<EOF
+#define $ac_tr_func 1
+EOF
+
+else
+ echo "$ac_t""no" 1>&6
+fi
+done
+fi # test x"${KRB5_INC}" != x;
+
+LIBS=$inn_save_LIBS
+
+if test x"$DO_PERL" = xDO ; then
+ echo $ac_n "checking for Perl linkage""... $ac_c" 1>&6
+echo "configure:8231: checking for Perl linkage" >&5
+ inn_perl_core_path=`$_PATH_PERL -MConfig -e 'print $Config{archlibexp}'`
+ inn_perl_core_flags=`$_PATH_PERL -MExtUtils::Embed -e ccopts`
+ inn_perl_core_libs=`$_PATH_PERL -MExtUtils::Embed -e ldopts 2>&1 | tail -1`
+ inn_perl_core_libs=" $inn_perl_core_libs "
+ inn_perl_core_libs=`echo "$inn_perl_core_libs" | sed 's/ -lc / /'`
+ for i in $LIBS ; do
+ inn_perl_core_libs=`echo "$inn_perl_core_libs" | sed "s/ $i / /"`
+ done
+ case $host in
+ *-linux*)
+ inn_perl_core_libs=`echo "$inn_perl_core_libs" | sed 's/ -lgdbm / /'`
+ ;;
+ *-cygwin*)
+ inn_perl_libname=`$_PATH_PERL -MConfig -e 'print $Config{libperl}'`
+ inn_perl_libname=`echo "$inn_perl_libname" | sed 's/^lib//; s/\.a$//'`
+ inn_perl_core_libs="${inn_perl_core_libs}-l$inn_perl_libname"
+ ;;
+ esac
+ inn_perl_core_libs=`echo "$inn_perl_core_libs" | sed 's/^ *//'`
+ inn_perl_core_libs=`echo "$inn_perl_core_libs" | sed 's/ *$//'`
+ inn_perl_core_flags=" $inn_perl_core_flags "
+ if test x"$inn_enable_largefiles" != xyes ; then
+ for f in -D_LARGEFILE_SOURCE -D_FILE_OFFSET_BITS=64 -D_LARGE_FILES ; do
+ inn_perl_core_flags=`echo "$inn_perl_core_flags" | sed "s/ $f / /"`
+ done
+ fi
+ inn_perl_core_flags=`echo "$inn_perl_core_flags" | sed 's/^ *//'`
+ inn_perl_core_flags=`echo "$inn_perl_core_flags" | sed 's/ *$//'`
+ PERL_INC="$inn_perl_core_flags"
+ PERL_LIB="$inn_perl_core_libs"
+ echo "$ac_t""$inn_perl_core_path" 1>&6
+else
+ PERL_INC=''
+ PERL_LIB=''
+fi
+
+
+
+if test x"$DO_PYTHON" = xdefine ; then
+ echo $ac_n "checking for Python linkage""... $ac_c" 1>&6
+echo "configure:8272: checking for Python linkage" >&5
+ py_prefix=`$_PATH_PYTHON -c 'import sys; print sys.prefix'`
+ py_ver=`$_PATH_PYTHON -c 'import sys; print sys.version[:3]'`
+ py_libdir="${py_prefix}/lib/python${py_ver}"
+ PYTHON_INC="-I${py_prefix}/include/python${py_ver}"
+ py_linkage=""
+ for py_linkpart in LIBS LIBC LIBM LOCALMODLIBS BASEMODLIBS \
+ LINKFORSHARED LDFLAGS ; do
+ py_linkage="$py_linkage "`grep "^${py_linkpart}=" \
+ $py_libdir/config/Makefile \
+ | sed -e 's/^.*=//'`
+ done
+ PYTHON_LIB="-L$py_libdir/config -lpython$py_ver $py_linkage"
+ PYTHON_LIB=`echo $PYTHON_LIB | sed -e 's/ \\t*/ /g'`
+ echo "$ac_t""$py_libdir" 1>&6
+else
+ PYTHON_LIB=""
+ PYTHON_INC=""
+fi
+
+
+
+if test x"$inn_enable_largefiles" = xyes ; then
+ echo $ac_n "checking for largefile linkage""... $ac_c" 1>&6
+echo "configure:8296: checking for largefile linkage" >&5
+ case "$host" in
+ *-aix4.01*)
+ echo "$ac_t""no" 1>&6
+ { echo "configure: error: AIX before 4.2 does not support large files" 1>&2; exit 1; }
+ ;;
+ *-aix4*)
+ echo "$ac_t""ok" 1>&6
+ LFS_CFLAGS="-D_LARGE_FILES"
+ LFS_LDFLAGS=""
+ LFS_LIBS=""
+ ;;
+ *-hpux*)
+ echo "$ac_t""ok" 1>&6
+ LFS_CFLAGS="-D_LARGEFILE_SOURCE -D_FILE_OFFSET_BITS=64"
+ LFS_LDFLAGS=""
+ LFS_LIBS=""
+ ;;
+ *-irix*)
+ echo "$ac_t""no" 1>&6
+ { echo "configure: error: Large files not supported on this platform" 1>&2; exit 1; }
+ ;;
+ *-linux*)
+ echo "$ac_t""maybe" 1>&6
+ LFS_CFLAGS="-D_LARGEFILE_SOURCE -D_FILE_OFFSET_BITS=64"
+ LFS_LDFLAGS=""
+ LFS_LIBS=""
+ cat >> confdefs.h <<\EOF
+#define _GNU_SOURCE 1
+EOF
+
+ ;;
+ *-solaris*)
+ echo "$ac_t""ok" 1>&6
+ # Extract the first word of "getconf", so it can be a program name with args.
+set dummy getconf; ac_word=$2
+echo $ac_n "checking for $ac_word""... $ac_c" 1>&6
+echo "configure:8333: checking for $ac_word" >&5
+if eval "test \"`echo '$''{'ac_cv_path_GETCONF'+set}'`\" = set"; then
+ echo $ac_n "(cached) $ac_c" 1>&6
+else
+ case "$GETCONF" in
+ /*)
+ ac_cv_path_GETCONF="$GETCONF" # Let the user override the test with a path.
+ ;;
+ ?:/*)
+ ac_cv_path_GETCONF="$GETCONF" # Let the user override the test with a dos path.
+ ;;
+ *)
+ IFS="${IFS= }"; ac_save_ifs="$IFS"; IFS=":"
+ ac_dummy="$PATH"
+ for ac_dir in $ac_dummy; do
+ test -z "$ac_dir" && ac_dir=.
+ if test -f $ac_dir/$ac_word; then
+ ac_cv_path_GETCONF="$ac_dir/$ac_word"
+ break
+ fi
+ done
+ IFS="$ac_save_ifs"
+ ;;
+esac
+fi
+GETCONF="$ac_cv_path_GETCONF"
+if test -n "$GETCONF"; then
+ echo "$ac_t""$GETCONF" 1>&6
+else
+ echo "$ac_t""no" 1>&6
+fi
+
+ if test -z "$GETCONF" ; then
+ { echo "configure: error: getconf required to configure large file support" 1>&2; exit 1; }
+ fi
+ LFS_CFLAGS=`$GETCONF LFS_CFLAGS`
+ LFS_LDFLAGS=`$GETCONF LFS_LDFLAGS`
+ LFS_LIBS=`$GETCONF LFS_LIBS`
+ ;;
+ *)
+ echo "$ac_t""maybe" 1>&6
+ LFS_CFLAGS="-D_LARGEFILE_SOURCE -D_FILE_OFFSET_BITS=64"
+ LFS_LDFLAGS=""
+ LFS_LIBS=""
+ ;;
+ esac
+
+
+
+fi
+
+echo $ac_n "checking for ANSI C header files""... $ac_c" 1>&6
+echo "configure:8385: checking for ANSI C header files" >&5
+if eval "test \"`echo '$''{'ac_cv_header_stdc'+set}'`\" = set"; then
+ echo $ac_n "(cached) $ac_c" 1>&6
+else
+ cat > conftest.$ac_ext <<EOF
+#line 8390 "configure"
+#include "confdefs.h"
+#include <stdlib.h>
+#include <stdarg.h>
+#include <string.h>
+#include <float.h>
+EOF
+ac_try="$ac_cpp conftest.$ac_ext >/dev/null 2>conftest.out"
+{ (eval echo configure:8398: \"$ac_try\") 1>&5; (eval $ac_try) 2>&5; }
+ac_err=`grep -v '^ *+' conftest.out | grep -v "^conftest.${ac_ext}\$"`
+if test -z "$ac_err"; then
+ rm -rf conftest*
+ ac_cv_header_stdc=yes
+else
+ echo "$ac_err" >&5
+ echo "configure: failed program was:" >&5
+ cat conftest.$ac_ext >&5
+ rm -rf conftest*
+ ac_cv_header_stdc=no
+fi
+rm -f conftest*
+
+if test $ac_cv_header_stdc = yes; then
+ # SunOS 4.x string.h does not declare mem*, contrary to ANSI.
+cat > conftest.$ac_ext <<EOF
+#line 8415 "configure"
+#include "confdefs.h"
+#include <string.h>
+EOF
+if (eval "$ac_cpp conftest.$ac_ext") 2>&5 |
+ egrep "memchr" >/dev/null 2>&1; then
+ :
+else
+ rm -rf conftest*
+ ac_cv_header_stdc=no
+fi
+rm -f conftest*
+
+fi
+
+if test $ac_cv_header_stdc = yes; then
+ # ISC 2.0.2 stdlib.h does not declare free, contrary to ANSI.
+cat > conftest.$ac_ext <<EOF
+#line 8433 "configure"
+#include "confdefs.h"
+#include <stdlib.h>
+EOF
+if (eval "$ac_cpp conftest.$ac_ext") 2>&5 |
+ egrep "free" >/dev/null 2>&1; then
+ :
+else
+ rm -rf conftest*
+ ac_cv_header_stdc=no
+fi
+rm -f conftest*
+
+fi
+
+if test $ac_cv_header_stdc = yes; then
+ # /bin/cc in Irix-4.0.5 gets non-ANSI ctype macros unless using -ansi.
+if test "$cross_compiling" = yes; then
+ :
+else
+ cat > conftest.$ac_ext <<EOF
+#line 8454 "configure"
+#include "confdefs.h"
+#include <ctype.h>
+#define ISLOWER(c) ('a' <= (c) && (c) <= 'z')
+#define TOUPPER(c) (ISLOWER(c) ? 'A' + ((c) - 'a') : (c))
+#define XOR(e, f) (((e) && !(f)) || (!(e) && (f)))
+int main () { int i; for (i = 0; i < 256; i++)
+if (XOR (islower (i), ISLOWER (i)) || toupper (i) != TOUPPER (i)) exit(2);
+exit (0); }
+
+EOF
+if { (eval echo configure:8465: \"$ac_link\") 1>&5; (eval $ac_link) 2>&5; } && test -s conftest${ac_exeext} && (./conftest; exit) 2>/dev/null
+then
+ :
+else
+ echo "configure: failed program was:" >&5
+ cat conftest.$ac_ext >&5
+ rm -fr conftest*
+ ac_cv_header_stdc=no
+fi
+rm -fr conftest*
+fi
+
+fi
+fi
+
+echo "$ac_t""$ac_cv_header_stdc" 1>&6
+if test $ac_cv_header_stdc = yes; then
+ cat >> confdefs.h <<\EOF
+#define STDC_HEADERS 1
+EOF
+
+fi
+
+
+if test x"$ac_cv_header_stdc" = xno ; then
+ for ac_hdr in memory.h stdlib.h strings.h
+do
+ac_safe=`echo "$ac_hdr" | sed 'y%./+-%__p_%'`
+echo $ac_n "checking for $ac_hdr""... $ac_c" 1>&6
+echo "configure:8494: checking for $ac_hdr" >&5
+if eval "test \"`echo '$''{'ac_cv_header_$ac_safe'+set}'`\" = set"; then
+ echo $ac_n "(cached) $ac_c" 1>&6
+else
+ cat > conftest.$ac_ext <<EOF
+#line 8499 "configure"
+#include "confdefs.h"
+#include <$ac_hdr>
+EOF
+ac_try="$ac_cpp conftest.$ac_ext >/dev/null 2>conftest.out"
+{ (eval echo configure:8504: \"$ac_try\") 1>&5; (eval $ac_try) 2>&5; }
+ac_err=`grep -v '^ *+' conftest.out | grep -v "^conftest.${ac_ext}\$"`
+if test -z "$ac_err"; then
+ rm -rf conftest*
+ eval "ac_cv_header_$ac_safe=yes"
+else
+ echo "$ac_err" >&5
+ echo "configure: failed program was:" >&5
+ cat conftest.$ac_ext >&5
+ rm -rf conftest*
+ eval "ac_cv_header_$ac_safe=no"
+fi
+rm -f conftest*
+fi
+if eval "test \"`echo '$ac_cv_header_'$ac_safe`\" = yes"; then
+ echo "$ac_t""yes" 1>&6
+ ac_tr_hdr=HAVE_`echo $ac_hdr | sed 'y%abcdefghijklmnopqrstuvwxyz./-%ABCDEFGHIJKLMNOPQRSTUVWXYZ___%'`
+ cat >> confdefs.h <<EOF
+#define $ac_tr_hdr 1
+EOF
+
+else
+ echo "$ac_t""no" 1>&6
+fi
+done
+
+ for ac_func in memcpy strchr
+do
+echo $ac_n "checking for $ac_func""... $ac_c" 1>&6
+echo "configure:8533: checking for $ac_func" >&5
+if eval "test \"`echo '$''{'ac_cv_func_$ac_func'+set}'`\" = set"; then
+ echo $ac_n "(cached) $ac_c" 1>&6
+else
+ cat > conftest.$ac_ext <<EOF
+#line 8538 "configure"
+#include "confdefs.h"
+/* System header to define __stub macros and hopefully few prototypes,
+ which can conflict with char $ac_func(); below. */
+#include <assert.h>
+/* Override any gcc2 internal prototype to avoid an error. */
+/* We use char because int might match the return type of a gcc2
+ builtin and then its argument prototype would still apply. */
+char $ac_func();
+
+int main() {
+
+/* The GNU C library defines this for functions which it implements
+ to always fail with ENOSYS. Some functions are actually named
+ something starting with __ and the normal name is an alias. */
+#if defined (__stub_$ac_func) || defined (__stub___$ac_func)
+choke me
+#else
+$ac_func();
+#endif
+
+; return 0; }
+EOF
+if { (eval echo configure:8561: \"$ac_link\") 1>&5; (eval $ac_link) 2>&5; } && test -s conftest${ac_exeext}; then
+ rm -rf conftest*
+ eval "ac_cv_func_$ac_func=yes"
+else
+ echo "configure: failed program was:" >&5
+ cat conftest.$ac_ext >&5
+ rm -rf conftest*
+ eval "ac_cv_func_$ac_func=no"
+fi
+rm -f conftest*
+fi
+
+if eval "test \"`echo '$ac_cv_func_'$ac_func`\" = yes"; then
+ echo "$ac_t""yes" 1>&6
+ ac_tr_func=HAVE_`echo $ac_func | tr 'abcdefghijklmnopqrstuvwxyz' 'ABCDEFGHIJKLMNOPQRSTUVWXYZ'`
+ cat >> confdefs.h <<EOF
+#define $ac_tr_func 1
+EOF
+
+else
+ echo "$ac_t""no" 1>&6
+fi
+done
+
+fi
+
+ac_header_dirent=no
+for ac_hdr in dirent.h sys/ndir.h sys/dir.h ndir.h
+do
+ac_safe=`echo "$ac_hdr" | sed 'y%./+-%__p_%'`
+echo $ac_n "checking for $ac_hdr that defines DIR""... $ac_c" 1>&6
+echo "configure:8592: checking for $ac_hdr that defines DIR" >&5
+if eval "test \"`echo '$''{'ac_cv_header_dirent_$ac_safe'+set}'`\" = set"; then
+ echo $ac_n "(cached) $ac_c" 1>&6
+else
+ cat > conftest.$ac_ext <<EOF
+#line 8597 "configure"
+#include "confdefs.h"
+#include <sys/types.h>
+#include <$ac_hdr>
+int main() {
+DIR *dirp = 0;
+; return 0; }
+EOF
+if { (eval echo configure:8605: \"$ac_compile\") 1>&5; (eval $ac_compile) 2>&5; }; then
+ rm -rf conftest*
+ eval "ac_cv_header_dirent_$ac_safe=yes"
+else
+ echo "configure: failed program was:" >&5
+ cat conftest.$ac_ext >&5
+ rm -rf conftest*
+ eval "ac_cv_header_dirent_$ac_safe=no"
+fi
+rm -f conftest*
+fi
+if eval "test \"`echo '$ac_cv_header_dirent_'$ac_safe`\" = yes"; then
+ echo "$ac_t""yes" 1>&6
+ ac_tr_hdr=HAVE_`echo $ac_hdr | sed 'y%abcdefghijklmnopqrstuvwxyz./-%ABCDEFGHIJKLMNOPQRSTUVWXYZ___%'`
+ cat >> confdefs.h <<EOF
+#define $ac_tr_hdr 1
+EOF
+ ac_header_dirent=$ac_hdr; break
+else
+ echo "$ac_t""no" 1>&6
+fi
+done
+# Two versions of opendir et al. are in -ldir and -lx on SCO Xenix.
+if test $ac_header_dirent = dirent.h; then
+echo $ac_n "checking for opendir in -ldir""... $ac_c" 1>&6
+echo "configure:8630: checking for opendir in -ldir" >&5
+ac_lib_var=`echo dir'_'opendir | sed 'y%./+-%__p_%'`
+if eval "test \"`echo '$''{'ac_cv_lib_$ac_lib_var'+set}'`\" = set"; then
+ echo $ac_n "(cached) $ac_c" 1>&6
+else
+ ac_save_LIBS="$LIBS"
+LIBS="-ldir $LIBS"
+cat > conftest.$ac_ext <<EOF
+#line 8638 "configure"
+#include "confdefs.h"
+/* Override any gcc2 internal prototype to avoid an error. */
+/* We use char because int might match the return type of a gcc2
+ builtin and then its argument prototype would still apply. */
+char opendir();
+
+int main() {
+opendir()
+; return 0; }
+EOF
+if { (eval echo configure:8649: \"$ac_link\") 1>&5; (eval $ac_link) 2>&5; } && test -s conftest${ac_exeext}; then
+ rm -rf conftest*
+ eval "ac_cv_lib_$ac_lib_var=yes"
+else
+ echo "configure: failed program was:" >&5
+ cat conftest.$ac_ext >&5
+ rm -rf conftest*
+ eval "ac_cv_lib_$ac_lib_var=no"
+fi
+rm -f conftest*
+LIBS="$ac_save_LIBS"
+
+fi
+if eval "test \"`echo '$ac_cv_lib_'$ac_lib_var`\" = yes"; then
+ echo "$ac_t""yes" 1>&6
+ LIBS="$LIBS -ldir"
+else
+ echo "$ac_t""no" 1>&6
+fi
+
+else
+echo $ac_n "checking for opendir in -lx""... $ac_c" 1>&6
+echo "configure:8671: checking for opendir in -lx" >&5
+ac_lib_var=`echo x'_'opendir | sed 'y%./+-%__p_%'`
+if eval "test \"`echo '$''{'ac_cv_lib_$ac_lib_var'+set}'`\" = set"; then
+ echo $ac_n "(cached) $ac_c" 1>&6
+else
+ ac_save_LIBS="$LIBS"
+LIBS="-lx $LIBS"
+cat > conftest.$ac_ext <<EOF
+#line 8679 "configure"
+#include "confdefs.h"
+/* Override any gcc2 internal prototype to avoid an error. */
+/* We use char because int might match the return type of a gcc2
+ builtin and then its argument prototype would still apply. */
+char opendir();
+
+int main() {
+opendir()
+; return 0; }
+EOF
+if { (eval echo configure:8690: \"$ac_link\") 1>&5; (eval $ac_link) 2>&5; } && test -s conftest${ac_exeext}; then
+ rm -rf conftest*
+ eval "ac_cv_lib_$ac_lib_var=yes"
+else
+ echo "configure: failed program was:" >&5
+ cat conftest.$ac_ext >&5
+ rm -rf conftest*
+ eval "ac_cv_lib_$ac_lib_var=no"
+fi
+rm -f conftest*
+LIBS="$ac_save_LIBS"
+
+fi
+if eval "test \"`echo '$ac_cv_lib_'$ac_lib_var`\" = yes"; then
+ echo "$ac_t""yes" 1>&6
+ LIBS="$LIBS -lx"
+else
+ echo "$ac_t""no" 1>&6
+fi
+
+fi
+
+echo $ac_n "checking whether time.h and sys/time.h may both be included""... $ac_c" 1>&6
+echo "configure:8713: checking whether time.h and sys/time.h may both be included" >&5
+if eval "test \"`echo '$''{'ac_cv_header_time'+set}'`\" = set"; then
+ echo $ac_n "(cached) $ac_c" 1>&6
+else
+ cat > conftest.$ac_ext <<EOF
+#line 8718 "configure"
+#include "confdefs.h"
+#include <sys/types.h>
+#include <sys/time.h>
+#include <time.h>
+int main() {
+struct tm *tp;
+; return 0; }
+EOF
+if { (eval echo configure:8727: \"$ac_compile\") 1>&5; (eval $ac_compile) 2>&5; }; then
+ rm -rf conftest*
+ ac_cv_header_time=yes
+else
+ echo "configure: failed program was:" >&5
+ cat conftest.$ac_ext >&5
+ rm -rf conftest*
+ ac_cv_header_time=no
+fi
+rm -f conftest*
+fi
+
+echo "$ac_t""$ac_cv_header_time" 1>&6
+if test $ac_cv_header_time = yes; then
+ cat >> confdefs.h <<\EOF
+#define TIME_WITH_SYS_TIME 1
+EOF
+
+fi
+
+echo $ac_n "checking for sys/wait.h that is POSIX.1 compatible""... $ac_c" 1>&6
+echo "configure:8748: checking for sys/wait.h that is POSIX.1 compatible" >&5
+if eval "test \"`echo '$''{'ac_cv_header_sys_wait_h'+set}'`\" = set"; then
+ echo $ac_n "(cached) $ac_c" 1>&6
+else
+ cat > conftest.$ac_ext <<EOF
+#line 8753 "configure"
+#include "confdefs.h"
+#include <sys/types.h>
+#include <sys/wait.h>
+#ifndef WEXITSTATUS
+#define WEXITSTATUS(stat_val) ((unsigned)(stat_val) >> 8)
+#endif
+#ifndef WIFEXITED
+#define WIFEXITED(stat_val) (((stat_val) & 255) == 0)
+#endif
+int main() {
+int s;
+wait (&s);
+s = WIFEXITED (s) ? WEXITSTATUS (s) : 1;
+; return 0; }
+EOF
+if { (eval echo configure:8769: \"$ac_compile\") 1>&5; (eval $ac_compile) 2>&5; }; then
+ rm -rf conftest*
+ ac_cv_header_sys_wait_h=yes
+else
+ echo "configure: failed program was:" >&5
+ cat conftest.$ac_ext >&5
+ rm -rf conftest*
+ ac_cv_header_sys_wait_h=no
+fi
+rm -f conftest*
+fi
+
+echo "$ac_t""$ac_cv_header_sys_wait_h" 1>&6
+if test $ac_cv_header_sys_wait_h = yes; then
+ cat >> confdefs.h <<\EOF
+#define HAVE_SYS_WAIT_H 1
+EOF
+
+fi
+
+
+for ac_hdr in crypt.h inttypes.h limits.h ndbm.h pam/pam_appl.h stdbool.h \
+ stddef.h stdint.h string.h sys/bitypes.h sys/filio.h \
+ sys/loadavg.h sys/param.h sys/select.h sys/sysinfo.h \
+ sys/time.h unistd.h
+do
+ac_safe=`echo "$ac_hdr" | sed 'y%./+-%__p_%'`
+echo $ac_n "checking for $ac_hdr""... $ac_c" 1>&6
+echo "configure:8797: checking for $ac_hdr" >&5
+if eval "test \"`echo '$''{'ac_cv_header_$ac_safe'+set}'`\" = set"; then
+ echo $ac_n "(cached) $ac_c" 1>&6
+else
+ cat > conftest.$ac_ext <<EOF
+#line 8802 "configure"
+#include "confdefs.h"
+#include <$ac_hdr>
+EOF
+ac_try="$ac_cpp conftest.$ac_ext >/dev/null 2>conftest.out"
+{ (eval echo configure:8807: \"$ac_try\") 1>&5; (eval $ac_try) 2>&5; }
+ac_err=`grep -v '^ *+' conftest.out | grep -v "^conftest.${ac_ext}\$"`
+if test -z "$ac_err"; then
+ rm -rf conftest*
+ eval "ac_cv_header_$ac_safe=yes"
+else
+ echo "$ac_err" >&5
+ echo "configure: failed program was:" >&5
+ cat conftest.$ac_ext >&5
+ rm -rf conftest*
+ eval "ac_cv_header_$ac_safe=no"
+fi
+rm -f conftest*
+fi
+if eval "test \"`echo '$ac_cv_header_'$ac_safe`\" = yes"; then
+ echo "$ac_t""yes" 1>&6
+ ac_tr_hdr=HAVE_`echo $ac_hdr | sed 'y%abcdefghijklmnopqrstuvwxyz./-%ABCDEFGHIJKLMNOPQRSTUVWXYZ___%'`
+ cat >> confdefs.h <<EOF
+#define $ac_tr_hdr 1
+EOF
+
+else
+ echo "$ac_t""no" 1>&6
+fi
+done
+
+
+if test x"$ac_cv_header_ndbm_h" = xno ; then
+ for ac_hdr in db1/ndbm.h gdbm-ndbm.h
+do
+ac_safe=`echo "$ac_hdr" | sed 'y%./+-%__p_%'`
+echo $ac_n "checking for $ac_hdr""... $ac_c" 1>&6
+echo "configure:8839: checking for $ac_hdr" >&5
+if eval "test \"`echo '$''{'ac_cv_header_$ac_safe'+set}'`\" = set"; then
+ echo $ac_n "(cached) $ac_c" 1>&6
+else
+ cat > conftest.$ac_ext <<EOF
+#line 8844 "configure"
+#include "confdefs.h"
+#include <$ac_hdr>
+EOF
+ac_try="$ac_cpp conftest.$ac_ext >/dev/null 2>conftest.out"
+{ (eval echo configure:8849: \"$ac_try\") 1>&5; (eval $ac_try) 2>&5; }
+ac_err=`grep -v '^ *+' conftest.out | grep -v "^conftest.${ac_ext}\$"`
+if test -z "$ac_err"; then
+ rm -rf conftest*
+ eval "ac_cv_header_$ac_safe=yes"
+else
+ echo "$ac_err" >&5
+ echo "configure: failed program was:" >&5
+ cat conftest.$ac_ext >&5
+ rm -rf conftest*
+ eval "ac_cv_header_$ac_safe=no"
+fi
+rm -f conftest*
+fi
+if eval "test \"`echo '$ac_cv_header_'$ac_safe`\" = yes"; then
+ echo "$ac_t""yes" 1>&6
+ ac_tr_hdr=HAVE_`echo $ac_hdr | sed 'y%abcdefghijklmnopqrstuvwxyz./-%ABCDEFGHIJKLMNOPQRSTUVWXYZ___%'`
+ cat >> confdefs.h <<EOF
+#define $ac_tr_hdr 1
+EOF
+
+else
+ echo "$ac_t""no" 1>&6
+fi
+done
+
+fi
+
+
+echo $ac_n "checking whether h_errno must be declared""... $ac_c" 1>&6
+echo "configure:8879: checking whether h_errno must be declared" >&5
+if eval "test \"`echo '$''{'inn_cv_herrno_need_decl'+set}'`\" = set"; then
+ echo $ac_n "(cached) $ac_c" 1>&6
+else
+ cat > conftest.$ac_ext <<EOF
+#line 8884 "configure"
+#include "confdefs.h"
+#include <netdb.h>
+int main() {
+h_errno = 0;
+; return 0; }
+EOF
+if { (eval echo configure:8891: \"$ac_compile\") 1>&5; (eval $ac_compile) 2>&5; }; then
+ rm -rf conftest*
+ inn_cv_herrno_need_decl=no
+else
+ echo "configure: failed program was:" >&5
+ cat conftest.$ac_ext >&5
+ rm -rf conftest*
+ inn_cv_herrno_need_decl=yes
+fi
+rm -f conftest*
+fi
+
+echo "$ac_t""$inn_cv_herrno_need_decl" 1>&6
+if test "$inn_cv_herrno_need_decl" = yes ; then
+ cat >> confdefs.h <<\EOF
+#define NEED_HERRNO_DECLARATION 1
+EOF
+
+fi
+
+
+
+
+echo $ac_n "checking whether inet_aton must be declared""... $ac_c" 1>&6
+echo "configure:8915: checking whether inet_aton must be declared" >&5
+if eval "test \"`echo '$''{'inn_cv_decl_needed_inet_aton'+set}'`\" = set"; then
+ echo $ac_n "(cached) $ac_c" 1>&6
+else
+ cat > conftest.$ac_ext <<EOF
+#line 8920 "configure"
+#include "confdefs.h"
+#include <stdio.h>
+#include <sys/types.h>
+#if STDC_HEADERS
+# include <stdlib.h>
+# include <stddef.h>
+#else
+# if HAVE_STDLIB_H
+# include <stdlib.h>
+# endif
+# if !HAVE_STRCHR
+# define strchr index
+# define strrchr rindex
+# endif
+#endif
+#if HAVE_STRING_H
+# if !STDC_HEADERS && HAVE_MEMORY_H
+# include <memory.h>
+# endif
+# include <string.h>
+#else
+# if HAVE_STRINGS_H
+# include <strings.h>
+# endif
+#endif
+#if HAVE_UNISTD_H
+# include <unistd.h>
+#endif
+#include <netinet/in.h>
+#include <arpa/inet.h>
+int main() {
+char *(*pfn) = (char *(*)) inet_aton
+; return 0; }
+EOF
+if { (eval echo configure:8955: \"$ac_compile\") 1>&5; (eval $ac_compile) 2>&5; }; then
+ rm -rf conftest*
+ inn_cv_decl_needed_inet_aton=no
+else
+ echo "configure: failed program was:" >&5
+ cat conftest.$ac_ext >&5
+ rm -rf conftest*
+ inn_cv_decl_needed_inet_aton=yes
+fi
+rm -f conftest*
+fi
+
+if test $inn_cv_decl_needed_inet_aton = yes ; then
+ echo "$ac_t""yes" 1>&6
+ cat >> confdefs.h <<\EOF
+#define NEED_DECLARATION_INET_ATON 1
+EOF
+
+else
+ echo "$ac_t""no" 1>&6
+fi
+echo $ac_n "checking whether inet_ntoa must be declared""... $ac_c" 1>&6
+echo "configure:8977: checking whether inet_ntoa must be declared" >&5
+if eval "test \"`echo '$''{'inn_cv_decl_needed_inet_ntoa'+set}'`\" = set"; then
+ echo $ac_n "(cached) $ac_c" 1>&6
+else
+ cat > conftest.$ac_ext <<EOF
+#line 8982 "configure"
+#include "confdefs.h"
+#include <stdio.h>
+#include <sys/types.h>
+#if STDC_HEADERS
+# include <stdlib.h>
+# include <stddef.h>
+#else
+# if HAVE_STDLIB_H
+# include <stdlib.h>
+# endif
+# if !HAVE_STRCHR
+# define strchr index
+# define strrchr rindex
+# endif
+#endif
+#if HAVE_STRING_H
+# if !STDC_HEADERS && HAVE_MEMORY_H
+# include <memory.h>
+# endif
+# include <string.h>
+#else
+# if HAVE_STRINGS_H
+# include <strings.h>
+# endif
+#endif
+#if HAVE_UNISTD_H
+# include <unistd.h>
+#endif
+#include <netinet/in.h>
+#include <arpa/inet.h>
+int main() {
+char *(*pfn) = (char *(*)) inet_ntoa
+; return 0; }
+EOF
+if { (eval echo configure:9017: \"$ac_compile\") 1>&5; (eval $ac_compile) 2>&5; }; then
+ rm -rf conftest*
+ inn_cv_decl_needed_inet_ntoa=no
+else
+ echo "configure: failed program was:" >&5
+ cat conftest.$ac_ext >&5
+ rm -rf conftest*
+ inn_cv_decl_needed_inet_ntoa=yes
+fi
+rm -f conftest*
+fi
+
+if test $inn_cv_decl_needed_inet_ntoa = yes ; then
+ echo "$ac_t""yes" 1>&6
+ cat >> confdefs.h <<\EOF
+#define NEED_DECLARATION_INET_NTOA 1
+EOF
+
+else
+ echo "$ac_t""no" 1>&6
+fi
+echo $ac_n "checking whether snprintf must be declared""... $ac_c" 1>&6
+echo "configure:9039: checking whether snprintf must be declared" >&5
+if eval "test \"`echo '$''{'inn_cv_decl_needed_snprintf'+set}'`\" = set"; then
+ echo $ac_n "(cached) $ac_c" 1>&6
+else
+ cat > conftest.$ac_ext <<EOF
+#line 9044 "configure"
+#include "confdefs.h"
+#include <stdio.h>
+#include <sys/types.h>
+#if STDC_HEADERS
+# include <stdlib.h>
+# include <stddef.h>
+#else
+# if HAVE_STDLIB_H
+# include <stdlib.h>
+# endif
+# if !HAVE_STRCHR
+# define strchr index
+# define strrchr rindex
+# endif
+#endif
+#if HAVE_STRING_H
+# if !STDC_HEADERS && HAVE_MEMORY_H
+# include <memory.h>
+# endif
+# include <string.h>
+#else
+# if HAVE_STRINGS_H
+# include <strings.h>
+# endif
+#endif
+#if HAVE_UNISTD_H
+# include <unistd.h>
+#endif
+
+int main() {
+char *(*pfn) = (char *(*)) snprintf
+; return 0; }
+EOF
+if { (eval echo configure:9078: \"$ac_compile\") 1>&5; (eval $ac_compile) 2>&5; }; then
+ rm -rf conftest*
+ inn_cv_decl_needed_snprintf=no
+else
+ echo "configure: failed program was:" >&5
+ cat conftest.$ac_ext >&5
+ rm -rf conftest*
+ inn_cv_decl_needed_snprintf=yes
+fi
+rm -f conftest*
+fi
+
+if test $inn_cv_decl_needed_snprintf = yes ; then
+ echo "$ac_t""yes" 1>&6
+ cat >> confdefs.h <<\EOF
+#define NEED_DECLARATION_SNPRINTF 1
+EOF
+
+else
+ echo "$ac_t""no" 1>&6
+fi
+echo $ac_n "checking whether vsnprintf must be declared""... $ac_c" 1>&6
+echo "configure:9100: checking whether vsnprintf must be declared" >&5
+if eval "test \"`echo '$''{'inn_cv_decl_needed_vsnprintf'+set}'`\" = set"; then
+ echo $ac_n "(cached) $ac_c" 1>&6
+else
+ cat > conftest.$ac_ext <<EOF
+#line 9105 "configure"
+#include "confdefs.h"
+#include <stdio.h>
+#include <sys/types.h>
+#if STDC_HEADERS
+# include <stdlib.h>
+# include <stddef.h>
+#else
+# if HAVE_STDLIB_H
+# include <stdlib.h>
+# endif
+# if !HAVE_STRCHR
+# define strchr index
+# define strrchr rindex
+# endif
+#endif
+#if HAVE_STRING_H
+# if !STDC_HEADERS && HAVE_MEMORY_H
+# include <memory.h>
+# endif
+# include <string.h>
+#else
+# if HAVE_STRINGS_H
+# include <strings.h>
+# endif
+#endif
+#if HAVE_UNISTD_H
+# include <unistd.h>
+#endif
+
+int main() {
+char *(*pfn) = (char *(*)) vsnprintf
+; return 0; }
+EOF
+if { (eval echo configure:9139: \"$ac_compile\") 1>&5; (eval $ac_compile) 2>&5; }; then
+ rm -rf conftest*
+ inn_cv_decl_needed_vsnprintf=no
+else
+ echo "configure: failed program was:" >&5
+ cat conftest.$ac_ext >&5
+ rm -rf conftest*
+ inn_cv_decl_needed_vsnprintf=yes
+fi
+rm -f conftest*
+fi
+
+if test $inn_cv_decl_needed_vsnprintf = yes ; then
+ echo "$ac_t""yes" 1>&6
+ cat >> confdefs.h <<\EOF
+#define NEED_DECLARATION_VSNPRINTF 1
+EOF
+
+else
+ echo "$ac_t""no" 1>&6
+fi
+
+echo $ac_n "checking whether byte ordering is bigendian""... $ac_c" 1>&6
+echo "configure:9162: checking whether byte ordering is bigendian" >&5
+if eval "test \"`echo '$''{'ac_cv_c_bigendian'+set}'`\" = set"; then
+ echo $ac_n "(cached) $ac_c" 1>&6
+else
+ ac_cv_c_bigendian=unknown
+# See if sys/param.h defines the BYTE_ORDER macro.
+cat > conftest.$ac_ext <<EOF
+#line 9169 "configure"
+#include "confdefs.h"
+#include <sys/types.h>
+#include <sys/param.h>
+int main() {
+
+#if !BYTE_ORDER || !BIG_ENDIAN || !LITTLE_ENDIAN
+ bogus endian macros
+#endif
+; return 0; }
+EOF
+if { (eval echo configure:9180: \"$ac_compile\") 1>&5; (eval $ac_compile) 2>&5; }; then
+ rm -rf conftest*
+ # It does; now see whether it defined to BIG_ENDIAN or not.
+cat > conftest.$ac_ext <<EOF
+#line 9184 "configure"
+#include "confdefs.h"
+#include <sys/types.h>
+#include <sys/param.h>
+int main() {
+
+#if BYTE_ORDER != BIG_ENDIAN
+ not big endian
+#endif
+; return 0; }
+EOF
+if { (eval echo configure:9195: \"$ac_compile\") 1>&5; (eval $ac_compile) 2>&5; }; then
+ rm -rf conftest*
+ ac_cv_c_bigendian=yes
+else
+ echo "configure: failed program was:" >&5
+ cat conftest.$ac_ext >&5
+ rm -rf conftest*
+ ac_cv_c_bigendian=no
+fi
+rm -f conftest*
+else
+ echo "configure: failed program was:" >&5
+ cat conftest.$ac_ext >&5
+fi
+rm -f conftest*
+if test $ac_cv_c_bigendian = unknown; then
+if test "$cross_compiling" = yes; then
+ { echo "configure: error: can not run test program while cross compiling" 1>&2; exit 1; }
+else
+ cat > conftest.$ac_ext <<EOF
+#line 9215 "configure"
+#include "confdefs.h"
+main () {
+ /* Are we little or big endian? From Harbison&Steele. */
+ union
+ {
+ long l;
+ char c[sizeof (long)];
+ } u;
+ u.l = 1;
+ exit (u.c[sizeof (long) - 1] == 1);
+}
+EOF
+if { (eval echo configure:9228: \"$ac_link\") 1>&5; (eval $ac_link) 2>&5; } && test -s conftest${ac_exeext} && (./conftest; exit) 2>/dev/null
+then
+ ac_cv_c_bigendian=no
+else
+ echo "configure: failed program was:" >&5
+ cat conftest.$ac_ext >&5
+ rm -fr conftest*
+ ac_cv_c_bigendian=yes
+fi
+rm -fr conftest*
+fi
+
+fi
+fi
+
+echo "$ac_t""$ac_cv_c_bigendian" 1>&6
+if test $ac_cv_c_bigendian = yes; then
+ cat >> confdefs.h <<\EOF
+#define WORDS_BIGENDIAN 1
+EOF
+
+fi
+
+echo $ac_n "checking for working const""... $ac_c" 1>&6
+echo "configure:9252: checking for working const" >&5
+if eval "test \"`echo '$''{'ac_cv_c_const'+set}'`\" = set"; then
+ echo $ac_n "(cached) $ac_c" 1>&6
+else
+ cat > conftest.$ac_ext <<EOF
+#line 9257 "configure"
+#include "confdefs.h"
+
+int main() {
+
+/* Ultrix mips cc rejects this. */
+typedef int charset[2]; const charset x;
+/* SunOS 4.1.1 cc rejects this. */
+char const *const *ccp;
+char **p;
+/* NEC SVR4.0.2 mips cc rejects this. */
+struct point {int x, y;};
+static struct point const zero = {0,0};
+/* AIX XL C 1.02.0.0 rejects this.
+ It does not let you subtract one const X* pointer from another in an arm
+ of an if-expression whose if-part is not a constant expression */
+const char *g = "string";
+ccp = &g + (g ? g-g : 0);
+/* HPUX 7.0 cc rejects these. */
+++ccp;
+p = (char**) ccp;
+ccp = (char const *const *) p;
+{ /* SCO 3.2v4 cc rejects this. */
+ char *t;
+ char const *s = 0 ? (char *) 0 : (char const *) 0;
+
+ *t++ = 0;
+}
+{ /* Someone thinks the Sun supposedly-ANSI compiler will reject this. */
+ int x[] = {25, 17};
+ const int *foo = &x[0];
+ ++foo;
+}
+{ /* Sun SC1.0 ANSI compiler rejects this -- but not the above. */
+ typedef const int *iptr;
+ iptr p = 0;
+ ++p;
+}
+{ /* AIX XL C 1.02.0.0 rejects this saying
+ "k.c", line 2.27: 1506-025 (S) Operand must be a modifiable lvalue. */
+ struct s { int j; const int *ap[3]; };
+ struct s *b; b->j = 5;
+}
+{ /* ULTRIX-32 V3.1 (Rev 9) vcc rejects this */
+ const int foo = 10;
+}
+
+; return 0; }
+EOF
+if { (eval echo configure:9306: \"$ac_compile\") 1>&5; (eval $ac_compile) 2>&5; }; then
+ rm -rf conftest*
+ ac_cv_c_const=yes
+else
+ echo "configure: failed program was:" >&5
+ cat conftest.$ac_ext >&5
+ rm -rf conftest*
+ ac_cv_c_const=no
+fi
+rm -f conftest*
+fi
+
+echo "$ac_t""$ac_cv_c_const" 1>&6
+if test $ac_cv_c_const = no; then
+ cat >> confdefs.h <<\EOF
+#define const
+EOF
+
+fi
+
+echo $ac_n "checking for st_blksize in struct stat""... $ac_c" 1>&6
+echo "configure:9327: checking for st_blksize in struct stat" >&5
+if eval "test \"`echo '$''{'ac_cv_struct_st_blksize'+set}'`\" = set"; then
+ echo $ac_n "(cached) $ac_c" 1>&6
+else
+ cat > conftest.$ac_ext <<EOF
+#line 9332 "configure"
+#include "confdefs.h"
+#include <sys/types.h>
+#include <sys/stat.h>
+int main() {
+struct stat s; s.st_blksize;
+; return 0; }
+EOF
+if { (eval echo configure:9340: \"$ac_compile\") 1>&5; (eval $ac_compile) 2>&5; }; then
+ rm -rf conftest*
+ ac_cv_struct_st_blksize=yes
+else
+ echo "configure: failed program was:" >&5
+ cat conftest.$ac_ext >&5
+ rm -rf conftest*
+ ac_cv_struct_st_blksize=no
+fi
+rm -f conftest*
+fi
+
+echo "$ac_t""$ac_cv_struct_st_blksize" 1>&6
+if test $ac_cv_struct_st_blksize = yes; then
+ cat >> confdefs.h <<\EOF
+#define HAVE_ST_BLKSIZE 1
+EOF
+
+fi
+
+echo $ac_n "checking whether struct tm is in sys/time.h or time.h""... $ac_c" 1>&6
+echo "configure:9361: checking whether struct tm is in sys/time.h or time.h" >&5
+if eval "test \"`echo '$''{'ac_cv_struct_tm'+set}'`\" = set"; then
+ echo $ac_n "(cached) $ac_c" 1>&6
+else
+ cat > conftest.$ac_ext <<EOF
+#line 9366 "configure"
+#include "confdefs.h"
+#include <sys/types.h>
+#include <time.h>
+int main() {
+struct tm *tp; tp->tm_sec;
+; return 0; }
+EOF
+if { (eval echo configure:9374: \"$ac_compile\") 1>&5; (eval $ac_compile) 2>&5; }; then
+ rm -rf conftest*
+ ac_cv_struct_tm=time.h
+else
+ echo "configure: failed program was:" >&5
+ cat conftest.$ac_ext >&5
+ rm -rf conftest*
+ ac_cv_struct_tm=sys/time.h
+fi
+rm -f conftest*
+fi
+
+echo "$ac_t""$ac_cv_struct_tm" 1>&6
+if test $ac_cv_struct_tm = sys/time.h; then
+ cat >> confdefs.h <<\EOF
+#define TM_IN_SYS_TIME 1
+EOF
+
+fi
+
+echo $ac_n "checking for size_t""... $ac_c" 1>&6
+echo "configure:9395: checking for size_t" >&5
+if eval "test \"`echo '$''{'ac_cv_type_size_t'+set}'`\" = set"; then
+ echo $ac_n "(cached) $ac_c" 1>&6
+else
+ cat > conftest.$ac_ext <<EOF
+#line 9400 "configure"
+#include "confdefs.h"
+#include <sys/types.h>
+#if STDC_HEADERS
+#include <stdlib.h>
+#include <stddef.h>
+#endif
+EOF
+if (eval "$ac_cpp conftest.$ac_ext") 2>&5 |
+ egrep "(^|[^a-zA-Z_0-9])size_t[^a-zA-Z_0-9]" >/dev/null 2>&1; then
+ rm -rf conftest*
+ ac_cv_type_size_t=yes
+else
+ rm -rf conftest*
+ ac_cv_type_size_t=no
+fi
+rm -f conftest*
+
+fi
+echo "$ac_t""$ac_cv_type_size_t" 1>&6
+if test $ac_cv_type_size_t = no; then
+ cat >> confdefs.h <<\EOF
+#define size_t unsigned
+EOF
+
+fi
+
+echo $ac_n "checking for uid_t in sys/types.h""... $ac_c" 1>&6
+echo "configure:9428: checking for uid_t in sys/types.h" >&5
+if eval "test \"`echo '$''{'ac_cv_type_uid_t'+set}'`\" = set"; then
+ echo $ac_n "(cached) $ac_c" 1>&6
+else
+ cat > conftest.$ac_ext <<EOF
+#line 9433 "configure"
+#include "confdefs.h"
+#include <sys/types.h>
+EOF
+if (eval "$ac_cpp conftest.$ac_ext") 2>&5 |
+ egrep "uid_t" >/dev/null 2>&1; then
+ rm -rf conftest*
+ ac_cv_type_uid_t=yes
+else
+ rm -rf conftest*
+ ac_cv_type_uid_t=no
+fi
+rm -f conftest*
+
+fi
+
+echo "$ac_t""$ac_cv_type_uid_t" 1>&6
+if test $ac_cv_type_uid_t = no; then
+ cat >> confdefs.h <<\EOF
+#define uid_t int
+EOF
+
+ cat >> confdefs.h <<\EOF
+#define gid_t int
+EOF
+
+fi
+
+echo $ac_n "checking for off_t""... $ac_c" 1>&6
+echo "configure:9462: checking for off_t" >&5
+if eval "test \"`echo '$''{'ac_cv_type_off_t'+set}'`\" = set"; then
+ echo $ac_n "(cached) $ac_c" 1>&6
+else
+ cat > conftest.$ac_ext <<EOF
+#line 9467 "configure"
+#include "confdefs.h"
+#include <sys/types.h>
+#if STDC_HEADERS
+#include <stdlib.h>
+#include <stddef.h>
+#endif
+EOF
+if (eval "$ac_cpp conftest.$ac_ext") 2>&5 |
+ egrep "(^|[^a-zA-Z_0-9])off_t[^a-zA-Z_0-9]" >/dev/null 2>&1; then
+ rm -rf conftest*
+ ac_cv_type_off_t=yes
+else
+ rm -rf conftest*
+ ac_cv_type_off_t=no
+fi
+rm -f conftest*
+
+fi
+echo "$ac_t""$ac_cv_type_off_t" 1>&6
+if test $ac_cv_type_off_t = no; then
+ cat >> confdefs.h <<\EOF
+#define off_t long
+EOF
+
+fi
+
+echo $ac_n "checking for pid_t""... $ac_c" 1>&6
+echo "configure:9495: checking for pid_t" >&5
+if eval "test \"`echo '$''{'ac_cv_type_pid_t'+set}'`\" = set"; then
+ echo $ac_n "(cached) $ac_c" 1>&6
+else
+ cat > conftest.$ac_ext <<EOF
+#line 9500 "configure"
+#include "confdefs.h"
+#include <sys/types.h>
+#if STDC_HEADERS
+#include <stdlib.h>
+#include <stddef.h>
+#endif
+EOF
+if (eval "$ac_cpp conftest.$ac_ext") 2>&5 |
+ egrep "(^|[^a-zA-Z_0-9])pid_t[^a-zA-Z_0-9]" >/dev/null 2>&1; then
+ rm -rf conftest*
+ ac_cv_type_pid_t=yes
+else
+ rm -rf conftest*
+ ac_cv_type_pid_t=no
+fi
+rm -f conftest*
+
+fi
+echo "$ac_t""$ac_cv_type_pid_t" 1>&6
+if test $ac_cv_type_pid_t = no; then
+ cat >> confdefs.h <<\EOF
+#define pid_t int
+EOF
+
+fi
+
+echo $ac_n "checking for ptrdiff_t""... $ac_c" 1>&6
+echo "configure:9528: checking for ptrdiff_t" >&5
+if eval "test \"`echo '$''{'ac_cv_type_ptrdiff_t'+set}'`\" = set"; then
+ echo $ac_n "(cached) $ac_c" 1>&6
+else
+ cat > conftest.$ac_ext <<EOF
+#line 9533 "configure"
+#include "confdefs.h"
+#include <sys/types.h>
+#if STDC_HEADERS
+#include <stdlib.h>
+#include <stddef.h>
+#endif
+EOF
+if (eval "$ac_cpp conftest.$ac_ext") 2>&5 |
+ egrep "(^|[^a-zA-Z_0-9])ptrdiff_t[^a-zA-Z_0-9]" >/dev/null 2>&1; then
+ rm -rf conftest*
+ ac_cv_type_ptrdiff_t=yes
+else
+ rm -rf conftest*
+ ac_cv_type_ptrdiff_t=no
+fi
+rm -f conftest*
+
+fi
+echo "$ac_t""$ac_cv_type_ptrdiff_t" 1>&6
+if test $ac_cv_type_ptrdiff_t = no; then
+ cat >> confdefs.h <<\EOF
+#define ptrdiff_t long
+EOF
+
+fi
+
+echo $ac_n "checking for ssize_t""... $ac_c" 1>&6
+echo "configure:9561: checking for ssize_t" >&5
+if eval "test \"`echo '$''{'ac_cv_type_ssize_t'+set}'`\" = set"; then
+ echo $ac_n "(cached) $ac_c" 1>&6
+else
+ cat > conftest.$ac_ext <<EOF
+#line 9566 "configure"
+#include "confdefs.h"
+#include <sys/types.h>
+#if STDC_HEADERS
+#include <stdlib.h>
+#include <stddef.h>
+#endif
+EOF
+if (eval "$ac_cpp conftest.$ac_ext") 2>&5 |
+ egrep "(^|[^a-zA-Z_0-9])ssize_t[^a-zA-Z_0-9]" >/dev/null 2>&1; then
+ rm -rf conftest*
+ ac_cv_type_ssize_t=yes
+else
+ rm -rf conftest*
+ ac_cv_type_ssize_t=no
+fi
+rm -f conftest*
+
+fi
+echo "$ac_t""$ac_cv_type_ssize_t" 1>&6
+if test $ac_cv_type_ssize_t = no; then
+ cat >> confdefs.h <<\EOF
+#define ssize_t int
+EOF
+
+fi
+
+
+
+echo $ac_n "checking for C99 variadic macros""... $ac_c" 1>&6
+echo "configure:9596: checking for C99 variadic macros" >&5
+if eval "test \"`echo '$''{'inn_cv_c_c99_vamacros'+set}'`\" = set"; then
+ echo $ac_n "(cached) $ac_c" 1>&6
+else
+ cat > conftest.$ac_ext <<EOF
+#line 9601 "configure"
+#include "confdefs.h"
+#include <stdio.h>
+#define error(...) fprintf(stderr, __VA_ARGS__)
+int main() {
+error("foo"); error("foo %d", 0); return 0;
+; return 0; }
+EOF
+if { (eval echo configure:9609: \"$ac_compile\") 1>&5; (eval $ac_compile) 2>&5; }; then
+ rm -rf conftest*
+ inn_cv_c_c99_vamacros=yes
+else
+ echo "configure: failed program was:" >&5
+ cat conftest.$ac_ext >&5
+ rm -rf conftest*
+ inn_cv_c_c99_vamacros=no
+fi
+rm -f conftest*
+fi
+
+echo "$ac_t""$inn_cv_c_c99_vamacros" 1>&6
+if test $inn_cv_c_c99_vamacros = yes ; then
+ cat >> confdefs.h <<\EOF
+#define HAVE_C99_VAMACROS 1
+EOF
+
+fi
+
+
+echo $ac_n "checking for GNU-style variadic macros""... $ac_c" 1>&6
+echo "configure:9631: checking for GNU-style variadic macros" >&5
+if eval "test \"`echo '$''{'inn_cv_c_gnu_vamacros'+set}'`\" = set"; then
+ echo $ac_n "(cached) $ac_c" 1>&6
+else
+ cat > conftest.$ac_ext <<EOF
+#line 9636 "configure"
+#include "confdefs.h"
+#include <stdio.h>
+#define error(args...) fprintf(stderr, args)
+int main() {
+error("foo"); error("foo %d", 0); return 0;
+; return 0; }
+EOF
+if { (eval echo configure:9644: \"$ac_compile\") 1>&5; (eval $ac_compile) 2>&5; }; then
+ rm -rf conftest*
+ inn_cv_c_gnu_vamacros=yes
+else
+ echo "configure: failed program was:" >&5
+ cat conftest.$ac_ext >&5
+ rm -rf conftest*
+ inn_cv_c_gnu_vamacros=no
+fi
+rm -f conftest*
+fi
+
+echo "$ac_t""$inn_cv_c_gnu_vamacros" 1>&6
+if test $inn_cv_c_gnu_vamacros = yes ; then
+ cat >> confdefs.h <<\EOF
+#define HAVE_GNU_VAMACROS 1
+EOF
+
+fi
+
+
+echo $ac_n "checking for long long int""... $ac_c" 1>&6
+echo "configure:9666: checking for long long int" >&5
+if eval "test \"`echo '$''{'inn_cv_c_long_long'+set}'`\" = set"; then
+ echo $ac_n "(cached) $ac_c" 1>&6
+else
+ cat > conftest.$ac_ext <<EOF
+#line 9671 "configure"
+#include "confdefs.h"
+
+int main() {
+long long int i;
+; return 0; }
+EOF
+if { (eval echo configure:9678: \"$ac_compile\") 1>&5; (eval $ac_compile) 2>&5; }; then
+ rm -rf conftest*
+ inn_cv_c_long_long=yes
+else
+ echo "configure: failed program was:" >&5
+ cat conftest.$ac_ext >&5
+ rm -rf conftest*
+ inn_cv_c_long_long=no
+fi
+rm -f conftest*
+fi
+
+echo "$ac_t""$inn_cv_c_long_long" 1>&6
+if test $inn_cv_c_long_long = yes ; then
+ cat >> confdefs.h <<\EOF
+#define HAVE_LONG_LONG 1
+EOF
+
+fi
+
+
+
+
+echo $ac_n "checking for sig_atomic_t""... $ac_c" 1>&6
+echo "configure:9702: checking for sig_atomic_t" >&5
+if eval "test \"`echo '$''{'ac_cv_type_sig_atomic_t'+set}'`\" = set"; then
+ echo $ac_n "(cached) $ac_c" 1>&6
+else
+ cat > conftest.$ac_ext <<EOF
+#line 9707 "configure"
+#include "confdefs.h"
+#include <sys/types.h>
+#ifdef STDC_HEADERS
+# include <stdlib.h>
+# include <stddef.h>
+#endif
+#include <signal.h>
+EOF
+if (eval "$ac_cpp conftest.$ac_ext") 2>&5 |
+ egrep "(^|[^a-zA-Z_0-9])sig_atomic_t[^a-zA-Z_0-9]" >/dev/null 2>&1; then
+ rm -rf conftest*
+ ac_cv_type_sig_atomic_t=yes
+else
+ rm -rf conftest*
+ ac_cv_type_sig_atomic_t=no
+
+fi
+rm -f conftest*
+
+fi
+
+echo "$ac_t""$ac_cv_type_sig_atomic_t" 1>&6
+if test x"$ac_cv_type_sig_atomic_t" = xno ; then
+ cat >> confdefs.h <<EOF
+#define sig_atomic_t int
+EOF
+
+fi
+
+echo $ac_n "checking for socklen_t""... $ac_c" 1>&6
+echo "configure:9738: checking for socklen_t" >&5
+if eval "test \"`echo '$''{'ac_cv_type_socklen_t'+set}'`\" = set"; then
+ echo $ac_n "(cached) $ac_c" 1>&6
+else
+ cat > conftest.$ac_ext <<EOF
+#line 9743 "configure"
+#include "confdefs.h"
+#include <sys/types.h>
+#ifdef STDC_HEADERS
+# include <stdlib.h>
+# include <stddef.h>
+#endif
+#include <sys/socket.h>
+EOF
+if (eval "$ac_cpp conftest.$ac_ext") 2>&5 |
+ egrep "(^|[^a-zA-Z_0-9])socklen_t[^a-zA-Z_0-9]" >/dev/null 2>&1; then
+ rm -rf conftest*
+ ac_cv_type_socklen_t=yes
+else
+ rm -rf conftest*
+ ac_cv_type_socklen_t=no
+
+fi
+rm -f conftest*
+
+fi
+
+echo "$ac_t""$ac_cv_type_socklen_t" 1>&6
+if test x"$ac_cv_type_socklen_t" = xno ; then
+ cat >> confdefs.h <<EOF
+#define socklen_t int
+EOF
+
+fi
+
+
+
+
+echo $ac_n "checking value of IOV_MAX""... $ac_c" 1>&6
+echo "configure:9777: checking value of IOV_MAX" >&5
+if eval "test \"`echo '$''{'inn_cv_macro_iov_max'+set}'`\" = set"; then
+ echo $ac_n "(cached) $ac_c" 1>&6
+else
+ if test "$cross_compiling" = yes; then
+ 16
+else
+ cat > conftest.$ac_ext <<EOF
+#line 9785 "configure"
+#include "confdefs.h"
+#include <sys/types.h>
+#include <stdio.h>
+#include <sys/uio.h>
+#include <errno.h>
+#include <fcntl.h>
+#ifdef HAVE_UNISTD_H
+# include <unistd.h>
+#endif
+#ifdef HAVE_LIMITS_H
+# include <limits.h>
+#endif
+
+int
+main ()
+{
+ int fd, size;
+ struct iovec array[1024];
+ char data;
+
+ FILE *f = fopen ("conftestval", "w");
+ if (!f) return 1;
+#ifdef IOV_MAX
+ fprintf (f, "set in limits.h\n");
+#else
+# ifdef UIO_MAXIOV
+ fprintf (f, "%d\n", UIO_MAXIOV);
+# else
+ fd = open ("/dev/null", O_WRONLY, 0666);
+ if (fd < 0) return 1;
+ for (size = 1; size <= 1024; size++)
+ {
+ array[size - 1].iov_base = &data;
+ array[size - 1].iov_len = sizeof data;
+ if (writev (fd, array, size) < 0)
+ {
+ if (errno != EINVAL) return 1;
+ fprintf(f, "%d\n", size - 1);
+ exit (0);
+ }
+ }
+ fprintf (f, "1024\n");
+# endif /* UIO_MAXIOV */
+#endif /* IOV_MAX */
+ return 0;
+}
+EOF
+if { (eval echo configure:9833: \"$ac_link\") 1>&5; (eval $ac_link) 2>&5; } && test -s conftest${ac_exeext} && (./conftest; exit) 2>/dev/null
+then
+ inn_cv_macro_iov_max=`cat conftestval`
+else
+ echo "configure: failed program was:" >&5
+ cat conftest.$ac_ext >&5
+ rm -fr conftest*
+ inn_cv_macro_iov_max=error
+fi
+rm -fr conftest*
+fi
+
+if test x"$inn_cv_macro_iov_max" = xerror ; then
+ echo "configure: warning: probe failure, assuming 16" 1>&2
+ inn_cv_macro_iov_max=16
+fi
+fi
+
+echo "$ac_t""$inn_cv_macro_iov_max" 1>&6
+if test x"$inn_cv_macro_iov_max" != x"set in limits.h" ; then
+ cat >> confdefs.h <<EOF
+#define IOV_MAX $inn_cv_macro_iov_max
+EOF
+
+fi
+
+
+echo $ac_n "checking for SUN_LEN""... $ac_c" 1>&6
+echo "configure:9861: checking for SUN_LEN" >&5
+if eval "test \"`echo '$''{'inn_cv_macro_sun_len'+set}'`\" = set"; then
+ echo $ac_n "(cached) $ac_c" 1>&6
+else
+ cat > conftest.$ac_ext <<EOF
+#line 9866 "configure"
+#include "confdefs.h"
+#include <sys/types.h>
+#include <sys/un.h>
+int main() {
+struct sockaddr_un sun;
+int i;
+
+i = SUN_LEN(&sun);
+; return 0; }
+EOF
+if { (eval echo configure:9877: \"$ac_link\") 1>&5; (eval $ac_link) 2>&5; } && test -s conftest${ac_exeext}; then
+ rm -rf conftest*
+ inn_cv_macro_sun_len=yes
+else
+ echo "configure: failed program was:" >&5
+ cat conftest.$ac_ext >&5
+ rm -rf conftest*
+ inn_cv_macro_sun_len=no
+fi
+rm -f conftest*
+fi
+
+echo "$ac_t""$inn_cv_macro_sun_len" 1>&6
+if test x"$inn_cv_macro_sun_len" = xyes ; then
+ cat >> confdefs.h <<\EOF
+#define HAVE_SUN_LEN 1
+EOF
+
+fi
+
+
+echo $ac_n "checking for tm_gmtoff in struct tm""... $ac_c" 1>&6
+echo "configure:9899: checking for tm_gmtoff in struct tm" >&5
+if eval "test \"`echo '$''{'inn_cv_struct_tm_gmtoff'+set}'`\" = set"; then
+ echo $ac_n "(cached) $ac_c" 1>&6
+else
+ cat > conftest.$ac_ext <<EOF
+#line 9904 "configure"
+#include "confdefs.h"
+#include <time.h>
+int main() {
+struct tm t; t.tm_gmtoff = 3600
+; return 0; }
+EOF
+if { (eval echo configure:9911: \"$ac_link\") 1>&5; (eval $ac_link) 2>&5; } && test -s conftest${ac_exeext}; then
+ rm -rf conftest*
+ inn_cv_struct_tm_gmtoff=yes
+else
+ echo "configure: failed program was:" >&5
+ cat conftest.$ac_ext >&5
+ rm -rf conftest*
+ inn_cv_struct_tm_gmtoff=no
+fi
+rm -f conftest*
+fi
+
+echo "$ac_t""$inn_cv_struct_tm_gmtoff" 1>&6
+if test x"$inn_cv_struct_tm_gmtoff" = xyes ; then
+ cat >> confdefs.h <<\EOF
+#define HAVE_TM_GMTOFF 1
+EOF
+
+fi
+
+
+echo $ac_n "checking for tm_zone in struct tm""... $ac_c" 1>&6
+echo "configure:9933: checking for tm_zone in struct tm" >&5
+if eval "test \"`echo '$''{'inn_cv_struct_tm_zone'+set}'`\" = set"; then
+ echo $ac_n "(cached) $ac_c" 1>&6
+else
+ cat > conftest.$ac_ext <<EOF
+#line 9938 "configure"
+#include "confdefs.h"
+#include <time.h>
+int main() {
+struct tm t; t.tm_zone = "UTC"
+; return 0; }
+EOF
+if { (eval echo configure:9945: \"$ac_link\") 1>&5; (eval $ac_link) 2>&5; } && test -s conftest${ac_exeext}; then
+ rm -rf conftest*
+ inn_cv_struct_tm_zone=yes
+else
+ echo "configure: failed program was:" >&5
+ cat conftest.$ac_ext >&5
+ rm -rf conftest*
+ inn_cv_struct_tm_zone=no
+fi
+rm -f conftest*
+fi
+
+echo "$ac_t""$inn_cv_struct_tm_zone" 1>&6
+if test x"$inn_cv_struct_tm_zone" = xyes ; then
+ cat >> confdefs.h <<\EOF
+#define HAVE_TM_ZONE 1
+EOF
+
+fi
+
+
+echo $ac_n "checking for timezone variable""... $ac_c" 1>&6
+echo "configure:9967: checking for timezone variable" >&5
+if eval "test \"`echo '$''{'inn_cv_var_timezone'+set}'`\" = set"; then
+ echo $ac_n "(cached) $ac_c" 1>&6
+else
+ cat > conftest.$ac_ext <<EOF
+#line 9972 "configure"
+#include "confdefs.h"
+#include <time.h>
+int main() {
+timezone = 3600; altzone = 7200
+; return 0; }
+EOF
+if { (eval echo configure:9979: \"$ac_link\") 1>&5; (eval $ac_link) 2>&5; } && test -s conftest${ac_exeext}; then
+ rm -rf conftest*
+ inn_cv_var_timezone=yes
+else
+ echo "configure: failed program was:" >&5
+ cat conftest.$ac_ext >&5
+ rm -rf conftest*
+ inn_cv_var_timezone=no
+fi
+rm -f conftest*
+fi
+
+echo "$ac_t""$inn_cv_var_timezone" 1>&6
+if test x"$inn_cv_var_timezone" = xyes ; then
+ cat >> confdefs.h <<\EOF
+#define HAVE_VAR_TIMEZONE 1
+EOF
+
+fi
+
+
+echo $ac_n "checking for tzname variable""... $ac_c" 1>&6
+echo "configure:10001: checking for tzname variable" >&5
+if eval "test \"`echo '$''{'inn_cv_var_tzname'+set}'`\" = set"; then
+ echo $ac_n "(cached) $ac_c" 1>&6
+else
+ cat > conftest.$ac_ext <<EOF
+#line 10006 "configure"
+#include "confdefs.h"
+#include <time.h>
+int main() {
+*tzname = "UTC"
+; return 0; }
+EOF
+if { (eval echo configure:10013: \"$ac_link\") 1>&5; (eval $ac_link) 2>&5; } && test -s conftest${ac_exeext}; then
+ rm -rf conftest*
+ inn_cv_var_tzname=yes
+else
+ echo "configure: failed program was:" >&5
+ cat conftest.$ac_ext >&5
+ rm -rf conftest*
+ inn_cv_var_tzname=no
+fi
+rm -f conftest*
+fi
+
+echo "$ac_t""$inn_cv_var_tzname" 1>&6
+if test x"$inn_cv_var_tzname" = xyes ; then
+ cat >> confdefs.h <<\EOF
+#define HAVE_VAR_TZNAME 1
+EOF
+
+fi
+
+
+
+echo $ac_n "checking size of int""... $ac_c" 1>&6
+echo "configure:10036: checking size of int" >&5
+if eval "test \"`echo '$''{'ac_cv_sizeof_int'+set}'`\" = set"; then
+ echo $ac_n "(cached) $ac_c" 1>&6
+else
+ if test "$cross_compiling" = yes; then
+ ac_cv_sizeof_int=4
+else
+ cat > conftest.$ac_ext <<EOF
+#line 10044 "configure"
+#include "confdefs.h"
+#include <stdio.h>
+main()
+{
+ FILE *f = fopen("conftestval", "w");
+ if (!f) exit(1);
+ fprintf(f, "%d\n", sizeof(int));
+ exit(0);
+}
+EOF
+if { (eval echo configure:10055: \"$ac_link\") 1>&5; (eval $ac_link) 2>&5; } && test -s conftest${ac_exeext} && (./conftest; exit) 2>/dev/null
+then
+ ac_cv_sizeof_int=`cat conftestval`
+else
+ echo "configure: failed program was:" >&5
+ cat conftest.$ac_ext >&5
+ rm -fr conftest*
+ ac_cv_sizeof_int=0
+fi
+rm -fr conftest*
+fi
+
+
+fi
+echo "$ac_t""$ac_cv_sizeof_int" 1>&6
+if test x"$ac_cv_sizeof_int" = x"4" ; then
+ INN_INT32=int
+else
+ echo $ac_n "checking size of long""... $ac_c" 1>&6
+echo "configure:10074: checking size of long" >&5
+if eval "test \"`echo '$''{'ac_cv_sizeof_long'+set}'`\" = set"; then
+ echo $ac_n "(cached) $ac_c" 1>&6
+else
+ if test "$cross_compiling" = yes; then
+ ac_cv_sizeof_long=4
+else
+ cat > conftest.$ac_ext <<EOF
+#line 10082 "configure"
+#include "confdefs.h"
+#include <stdio.h>
+main()
+{
+ FILE *f = fopen("conftestval", "w");
+ if (!f) exit(1);
+ fprintf(f, "%d\n", sizeof(long));
+ exit(0);
+}
+EOF
+if { (eval echo configure:10093: \"$ac_link\") 1>&5; (eval $ac_link) 2>&5; } && test -s conftest${ac_exeext} && (./conftest; exit) 2>/dev/null
+then
+ ac_cv_sizeof_long=`cat conftestval`
+else
+ echo "configure: failed program was:" >&5
+ cat conftest.$ac_ext >&5
+ rm -fr conftest*
+ ac_cv_sizeof_long=0
+fi
+rm -fr conftest*
+fi
+
+
+fi
+echo "$ac_t""$ac_cv_sizeof_long" 1>&6
+if test x"$ac_cv_sizeof_long" = x"4" ; then
+ INN_INT32=long
+else
+ echo $ac_n "checking size of short""... $ac_c" 1>&6
+echo "configure:10112: checking size of short" >&5
+if eval "test \"`echo '$''{'ac_cv_sizeof_short'+set}'`\" = set"; then
+ echo $ac_n "(cached) $ac_c" 1>&6
+else
+ if test "$cross_compiling" = yes; then
+ ac_cv_sizeof_short=2
+else
+ cat > conftest.$ac_ext <<EOF
+#line 10120 "configure"
+#include "confdefs.h"
+#include <stdio.h>
+main()
+{
+ FILE *f = fopen("conftestval", "w");
+ if (!f) exit(1);
+ fprintf(f, "%d\n", sizeof(short));
+ exit(0);
+}
+EOF
+if { (eval echo configure:10131: \"$ac_link\") 1>&5; (eval $ac_link) 2>&5; } && test -s conftest${ac_exeext} && (./conftest; exit) 2>/dev/null
+then
+ ac_cv_sizeof_short=`cat conftestval`
+else
+ echo "configure: failed program was:" >&5
+ cat conftest.$ac_ext >&5
+ rm -fr conftest*
+ ac_cv_sizeof_short=0
+fi
+rm -fr conftest*
+fi
+
+
+fi
+echo "$ac_t""$ac_cv_sizeof_short" 1>&6
+if test x"$ac_cv_sizeof_short" = x"4" ; then
+ INN_INT32=short
+else
+ :
+fi
+
+fi
+
+fi
+
+
+echo $ac_n "checking for int32_t""... $ac_c" 1>&6
+echo "configure:10158: checking for int32_t" >&5
+if eval "test \"`echo '$''{'ac_cv_type_int32_t'+set}'`\" = set"; then
+ echo $ac_n "(cached) $ac_c" 1>&6
+else
+ cat > conftest.$ac_ext <<EOF
+#line 10163 "configure"
+#include "confdefs.h"
+#include <sys/types.h>
+#ifdef STDC_HEADERS
+# include <stdlib.h>
+# include <stddef.h>
+#endif
+#ifdef HAVE_STDINT_H
+# include <stdint.h>
+#endif
+#ifdef HAVE_SYS_BITYPES_H
+# include <sys/bitypes.h>
+#endif
+
+EOF
+if (eval "$ac_cpp conftest.$ac_ext") 2>&5 |
+ egrep "(^|[^a-zA-Z_0-9])int32_t[^a-zA-Z_0-9]" >/dev/null 2>&1; then
+ rm -rf conftest*
+ ac_cv_type_int32_t=yes
+else
+ rm -rf conftest*
+ ac_cv_type_int32_t=no
+
+fi
+rm -f conftest*
+
+fi
+
+echo "$ac_t""$ac_cv_type_int32_t" 1>&6
+if test x"$ac_cv_type_int32_t" = xno ; then
+ cat >> confdefs.h <<EOF
+#define int32_t $INN_INT32
+EOF
+
+fi
+
+
+echo $ac_n "checking for uint32_t""... $ac_c" 1>&6
+echo "configure:10201: checking for uint32_t" >&5
+if eval "test \"`echo '$''{'ac_cv_type_uint32_t'+set}'`\" = set"; then
+ echo $ac_n "(cached) $ac_c" 1>&6
+else
+ cat > conftest.$ac_ext <<EOF
+#line 10206 "configure"
+#include "confdefs.h"
+#include <sys/types.h>
+#ifdef STDC_HEADERS
+# include <stdlib.h>
+# include <stddef.h>
+#endif
+#ifdef HAVE_STDINT_H
+# include <stdint.h>
+#endif
+#ifdef HAVE_SYS_BITYPES_H
+# include <sys/bitypes.h>
+#endif
+
+EOF
+if (eval "$ac_cpp conftest.$ac_ext") 2>&5 |
+ egrep "(^|[^a-zA-Z_0-9])uint32_t[^a-zA-Z_0-9]" >/dev/null 2>&1; then
+ rm -rf conftest*
+ ac_cv_type_uint32_t=yes
+else
+ rm -rf conftest*
+ ac_cv_type_uint32_t=no
+
+fi
+rm -f conftest*
+
+fi
+
+echo "$ac_t""$ac_cv_type_uint32_t" 1>&6
+if test x"$ac_cv_type_uint32_t" = xno ; then
+ cat >> confdefs.h <<EOF
+#define uint32_t unsigned $INN_INT32
+EOF
+
+fi
+
+echo $ac_n "checking for 8-bit clean memcmp""... $ac_c" 1>&6
+echo "configure:10243: checking for 8-bit clean memcmp" >&5
+if eval "test \"`echo '$''{'ac_cv_func_memcmp_clean'+set}'`\" = set"; then
+ echo $ac_n "(cached) $ac_c" 1>&6
+else
+ if test "$cross_compiling" = yes; then
+ ac_cv_func_memcmp_clean=no
+else
+ cat > conftest.$ac_ext <<EOF
+#line 10251 "configure"
+#include "confdefs.h"
+
+main()
+{
+ char c0 = 0x40, c1 = 0x80, c2 = 0x81;
+ exit(memcmp(&c0, &c2, 1) < 0 && memcmp(&c1, &c2, 1) < 0 ? 0 : 1);
+}
+
+EOF
+if { (eval echo configure:10261: \"$ac_link\") 1>&5; (eval $ac_link) 2>&5; } && test -s conftest${ac_exeext} && (./conftest; exit) 2>/dev/null
+then
+ ac_cv_func_memcmp_clean=yes
+else
+ echo "configure: failed program was:" >&5
+ cat conftest.$ac_ext >&5
+ rm -fr conftest*
+ ac_cv_func_memcmp_clean=no
+fi
+rm -fr conftest*
+fi
+
+fi
+
+echo "$ac_t""$ac_cv_func_memcmp_clean" 1>&6
+test $ac_cv_func_memcmp_clean = no && LIBOBJS="$LIBOBJS memcmp.${ac_objext}"
+
+echo $ac_n "checking return type of signal handlers""... $ac_c" 1>&6
+echo "configure:10279: checking return type of signal handlers" >&5
+if eval "test \"`echo '$''{'ac_cv_type_signal'+set}'`\" = set"; then
+ echo $ac_n "(cached) $ac_c" 1>&6
+else
+ cat > conftest.$ac_ext <<EOF
+#line 10284 "configure"
+#include "confdefs.h"
+#include <sys/types.h>
+#include <signal.h>
+#ifdef signal
+#undef signal
+#endif
+#ifdef __cplusplus
+extern "C" void (*signal (int, void (*)(int)))(int);
+#else
+void (*signal ()) ();
+#endif
+
+int main() {
+int i;
+; return 0; }
+EOF
+if { (eval echo configure:10301: \"$ac_compile\") 1>&5; (eval $ac_compile) 2>&5; }; then
+ rm -rf conftest*
+ ac_cv_type_signal=void
+else
+ echo "configure: failed program was:" >&5
+ cat conftest.$ac_ext >&5
+ rm -rf conftest*
+ ac_cv_type_signal=int
+fi
+rm -f conftest*
+fi
+
+echo "$ac_t""$ac_cv_type_signal" 1>&6
+cat >> confdefs.h <<EOF
+#define RETSIGTYPE $ac_cv_type_signal
+EOF
+
+
+
+
+
+
+echo $ac_n "checking for working inet_ntoa""... $ac_c" 1>&6
+echo "configure:10324: checking for working inet_ntoa" >&5
+if eval "test \"`echo '$''{'inn_cv_func_inet_ntoa_works'+set}'`\" = set"; then
+ echo $ac_n "(cached) $ac_c" 1>&6
+else
+ if test "$cross_compiling" = yes; then
+ inn_cv_func_inet_ntoa_works=no
+else
+ cat > conftest.$ac_ext <<EOF
+#line 10332 "configure"
+#include "confdefs.h"
+#include <sys/types.h>
+#include <sys/socket.h>
+#include <netinet/in.h>
+#include <arpa/inet.h>
+#if STDC_HEADERS || HAVE_STRING_H
+# include <string.h>
+#endif
+
+int
+main ()
+{
+ struct in_addr in;
+ in.s_addr = htonl (0x7f000000L);
+ return (!strcmp (inet_ntoa (in), "127.0.0.0") ? 0 : 1);
+}
+EOF
+if { (eval echo configure:10350: \"$ac_link\") 1>&5; (eval $ac_link) 2>&5; } && test -s conftest${ac_exeext} && (./conftest; exit) 2>/dev/null
+then
+ inn_cv_func_inet_ntoa_works=yes
+else
+ echo "configure: failed program was:" >&5
+ cat conftest.$ac_ext >&5
+ rm -fr conftest*
+ inn_cv_func_inet_ntoa_works=no
+fi
+rm -fr conftest*
+fi
+
+fi
+
+echo "$ac_t""$inn_cv_func_inet_ntoa_works" 1>&6
+if test "$inn_cv_func_inet_ntoa_works" = yes ; then
+ cat >> confdefs.h <<\EOF
+#define HAVE_INET_NTOA 1
+EOF
+
+else
+ LIBOBJS="$LIBOBJS inet_ntoa.${ac_objext}"
+fi
+
+
+echo $ac_n "checking whether struct sockaddr has sa_len""... $ac_c" 1>&6
+echo "configure:10376: checking whether struct sockaddr has sa_len" >&5
+if eval "test \"`echo '$''{'inn_cv_struct_sockaddr_sa_len'+set}'`\" = set"; then
+ echo $ac_n "(cached) $ac_c" 1>&6
+else
+ cat > conftest.$ac_ext <<EOF
+#line 10381 "configure"
+#include "confdefs.h"
+#include <sys/types.h>
+ #include <sys/socket.h>
+ #include <netinet/in.h>
+int main() {
+struct sockaddr sa; int x = sa.sa_len;
+; return 0; }
+EOF
+if { (eval echo configure:10390: \"$ac_compile\") 1>&5; (eval $ac_compile) 2>&5; }; then
+ rm -rf conftest*
+ inn_cv_struct_sockaddr_sa_len=yes
+else
+ echo "configure: failed program was:" >&5
+ cat conftest.$ac_ext >&5
+ rm -rf conftest*
+ inn_cv_struct_sockaddr_sa_len=no
+fi
+rm -f conftest*
+fi
+
+echo "$ac_t""$inn_cv_struct_sockaddr_sa_len" 1>&6
+if test "$inn_cv_struct_sockaddr_sa_len" = yes ; then
+ cat >> confdefs.h <<\EOF
+#define HAVE_SOCKADDR_LEN 1
+EOF
+
+fi
+
+
+echo $ac_n "checking for SA_LEN(s) macro""... $ac_c" 1>&6
+echo "configure:10412: checking for SA_LEN(s) macro" >&5
+if eval "test \"`echo '$''{'inn_cv_sa_len_macro'+set}'`\" = set"; then
+ echo $ac_n "(cached) $ac_c" 1>&6
+else
+ cat > conftest.$ac_ext <<EOF
+#line 10417 "configure"
+#include "confdefs.h"
+#include <sys/types.h>
+ #include <sys/socket.h>
+ #include <netinet/in.h>
+int main() {
+struct sockaddr sa; int x = SA_LEN(&sa);
+; return 0; }
+EOF
+if { (eval echo configure:10426: \"$ac_link\") 1>&5; (eval $ac_link) 2>&5; } && test -s conftest${ac_exeext}; then
+ rm -rf conftest*
+ inn_cv_sa_len_macro=yes
+else
+ echo "configure: failed program was:" >&5
+ cat conftest.$ac_ext >&5
+ rm -rf conftest*
+ inn_cv_sa_len_macro=no
+fi
+rm -f conftest*
+fi
+
+echo "$ac_t""$inn_cv_sa_len_macro" 1>&6
+if test "$inn_cv_sa_len_macro" = yes ; then
+ cat >> confdefs.h <<\EOF
+#define HAVE_SA_LEN_MACRO 1
+EOF
+
+fi
+
+
+
+
+echo $ac_n "checking for struct sockaddr_storage""... $ac_c" 1>&6
+echo "configure:10450: checking for struct sockaddr_storage" >&5
+if eval "test \"`echo '$''{'inn_cv_struct_sockaddr_storage'+set}'`\" = set"; then
+ echo $ac_n "(cached) $ac_c" 1>&6
+else
+ cat > conftest.$ac_ext <<EOF
+#line 10455 "configure"
+#include "confdefs.h"
+#include <sys/types.h>
+ #include <sys/socket.h>
+ #include <netinet/in.h>
+int main() {
+struct sockaddr_storage ss;
+; return 0; }
+EOF
+if { (eval echo configure:10464: \"$ac_compile\") 1>&5; (eval $ac_compile) 2>&5; }; then
+ rm -rf conftest*
+ inn_cv_struct_sockaddr_storage=yes
+else
+ echo "configure: failed program was:" >&5
+ cat conftest.$ac_ext >&5
+ rm -rf conftest*
+ inn_cv_struct_sockaddr_storage=no
+fi
+rm -f conftest*
+fi
+
+echo "$ac_t""$inn_cv_struct_sockaddr_storage" 1>&6
+if test "$inn_cv_struct_sockaddr_storage" = yes ; then
+ cat >> confdefs.h <<\EOF
+#define HAVE_SOCKADDR_STORAGE 1
+EOF
+
+ echo $ac_n "checking for RFC 2553 style sockaddr_storage member names""... $ac_c" 1>&6
+echo "configure:10483: checking for RFC 2553 style sockaddr_storage member names" >&5
+if eval "test \"`echo '$''{'inn_cv_2553_ss_family'+set}'`\" = set"; then
+ echo $ac_n "(cached) $ac_c" 1>&6
+else
+ cat > conftest.$ac_ext <<EOF
+#line 10488 "configure"
+#include "confdefs.h"
+#include <sys/types.h>
+ #include <sys/socket.h>
+ #include <netinet/in.h>
+int main() {
+struct sockaddr_storage ss; int x=ss.ss_family;
+; return 0; }
+EOF
+if { (eval echo configure:10497: \"$ac_compile\") 1>&5; (eval $ac_compile) 2>&5; }; then
+ rm -rf conftest*
+ inn_cv_2553_ss_family=no
+else
+ echo "configure: failed program was:" >&5
+ cat conftest.$ac_ext >&5
+ rm -rf conftest*
+ inn_cv_2553_ss_family=yes
+fi
+rm -f conftest*
+fi
+
+echo "$ac_t""$inn_cv_2553_ss_family" 1>&6
+if test "$inn_cv_2553_ss_family" = yes ; then
+ cat >> confdefs.h <<\EOF
+#define HAVE_2553_STYLE_SS_FAMILY 1
+EOF
+
+fi
+fi
+
+
+
+
+if test "$inn_enable_ipv6_tests" = yes ; then
+ echo $ac_n "checking whether IN6_ARE_ADDR_EQUAL macro is broken""... $ac_c" 1>&6
+echo "configure:10523: checking whether IN6_ARE_ADDR_EQUAL macro is broken" >&5
+if eval "test \"`echo '$''{'inn_cv_in6_are_addr_equal_broken'+set}'`\" = set"; then
+ echo $ac_n "(cached) $ac_c" 1>&6
+else
+ if test "$cross_compiling" = yes; then
+ inn_cv_in6_are_addr_equal_broken=no
+else
+ cat > conftest.$ac_ext <<EOF
+#line 10531 "configure"
+#include "confdefs.h"
+#include <sys/types.h>
+#include <sys/socket.h>
+#include <netinet/in.h>
+#include <arpa/inet.h>
+
+int
+main ()
+{
+ struct in6_addr a;
+ struct in6_addr b;
+
+ inet_pton(AF_INET6,"fe80::1234:5678:abcd",&a);
+ inet_pton(AF_INET6,"fe80::1234:5678:abcd",&b);
+ return IN6_ARE_ADDR_EQUAL(&a,&b) ? 0 : 1;
+}
+EOF
+if { (eval echo configure:10549: \"$ac_link\") 1>&5; (eval $ac_link) 2>&5; } && test -s conftest${ac_exeext} && (./conftest; exit) 2>/dev/null
+then
+ inn_cv_in6_are_addr_equal_broken=no
+else
+ echo "configure: failed program was:" >&5
+ cat conftest.$ac_ext >&5
+ rm -fr conftest*
+ inn_cv_in6_are_addr_equal_broken=yes
+fi
+rm -fr conftest*
+fi
+
+fi
+
+echo "$ac_t""$inn_cv_in6_are_addr_equal_broken" 1>&6
+if test "$inn_cv_in6_are_addr_equal_broken" = yes ; then
+ cat >> confdefs.h <<\EOF
+#define HAVE_BROKEN_IN6_ARE_ADDR_EQUAL 1
+EOF
+
+fi
+fi
+
+
+
+
+echo $ac_n "checking for working snprintf""... $ac_c" 1>&6
+echo "configure:10576: checking for working snprintf" >&5
+if eval "test \"`echo '$''{'inn_cv_func_snprintf_works'+set}'`\" = set"; then
+ echo $ac_n "(cached) $ac_c" 1>&6
+else
+ if test "$cross_compiling" = yes; then
+ inn_cv_func_snprintf_works=no
+else
+ cat > conftest.$ac_ext <<EOF
+#line 10584 "configure"
+#include "confdefs.h"
+#include <stdio.h>
+#include <stdarg.h>
+
+char buf[2];
+
+int
+test (char *format, ...)
+{
+ va_list args;
+ int count;
+
+ va_start (args, format);
+ count = vsnprintf (buf, sizeof buf, format, args);
+ va_end (args);
+ return count;
+}
+
+int
+main ()
+{
+ return ((test ("%s", "abcd") == 4 && buf[0] == 'a' && buf[1] == '\0'
+ && snprintf(NULL, 0, "%s", "abcd") == 4) ? 0 : 1);
+}
+EOF
+if { (eval echo configure:10610: \"$ac_link\") 1>&5; (eval $ac_link) 2>&5; } && test -s conftest${ac_exeext} && (./conftest; exit) 2>/dev/null
+then
+ inn_cv_func_snprintf_works=yes
+else
+ echo "configure: failed program was:" >&5
+ cat conftest.$ac_ext >&5
+ rm -fr conftest*
+ inn_cv_func_snprintf_works=no
+fi
+rm -fr conftest*
+fi
+
+fi
+
+echo "$ac_t""$inn_cv_func_snprintf_works" 1>&6
+if test "$inn_cv_func_snprintf_works" = yes ; then
+ cat >> confdefs.h <<\EOF
+#define HAVE_SNPRINTF 1
+EOF
+
+else
+ LIBOBJS="$LIBOBJS snprintf.${ac_objext}"
+fi
+
+for ac_func in atexit getloadavg getrlimit getrusage getspnam setbuffer \
+ sigaction setgroups setrlimit setsid socketpair statvfs \
+ strncasecmp strtoul symlink sysconf
+do
+echo $ac_n "checking for $ac_func""... $ac_c" 1>&6
+echo "configure:10639: checking for $ac_func" >&5
+if eval "test \"`echo '$''{'ac_cv_func_$ac_func'+set}'`\" = set"; then
+ echo $ac_n "(cached) $ac_c" 1>&6
+else
+ cat > conftest.$ac_ext <<EOF
+#line 10644 "configure"
+#include "confdefs.h"
+/* System header to define __stub macros and hopefully few prototypes,
+ which can conflict with char $ac_func(); below. */
+#include <assert.h>
+/* Override any gcc2 internal prototype to avoid an error. */
+/* We use char because int might match the return type of a gcc2
+ builtin and then its argument prototype would still apply. */
+char $ac_func();
+
+int main() {
+
+/* The GNU C library defines this for functions which it implements
+ to always fail with ENOSYS. Some functions are actually named
+ something starting with __ and the normal name is an alias. */
+#if defined (__stub_$ac_func) || defined (__stub___$ac_func)
+choke me
+#else
+$ac_func();
+#endif
+
+; return 0; }
+EOF
+if { (eval echo configure:10667: \"$ac_link\") 1>&5; (eval $ac_link) 2>&5; } && test -s conftest${ac_exeext}; then
+ rm -rf conftest*
+ eval "ac_cv_func_$ac_func=yes"
+else
+ echo "configure: failed program was:" >&5
+ cat conftest.$ac_ext >&5
+ rm -rf conftest*
+ eval "ac_cv_func_$ac_func=no"
+fi
+rm -f conftest*
+fi
+
+if eval "test \"`echo '$ac_cv_func_'$ac_func`\" = yes"; then
+ echo "$ac_t""yes" 1>&6
+ ac_tr_func=HAVE_`echo $ac_func | tr 'abcdefghijklmnopqrstuvwxyz' 'ABCDEFGHIJKLMNOPQRSTUVWXYZ'`
+ cat >> confdefs.h <<EOF
+#define $ac_tr_func 1
+EOF
+
+else
+ echo "$ac_t""no" 1>&6
+fi
+done
+
+
+if test x"$ac_cv_func_getrlimit" = xno ; then
+ for ac_func in getdtablesize ulimit
+do
+echo $ac_n "checking for $ac_func""... $ac_c" 1>&6
+echo "configure:10696: checking for $ac_func" >&5
+if eval "test \"`echo '$''{'ac_cv_func_$ac_func'+set}'`\" = set"; then
+ echo $ac_n "(cached) $ac_c" 1>&6
+else
+ cat > conftest.$ac_ext <<EOF
+#line 10701 "configure"
+#include "confdefs.h"
+/* System header to define __stub macros and hopefully few prototypes,
+ which can conflict with char $ac_func(); below. */
+#include <assert.h>
+/* Override any gcc2 internal prototype to avoid an error. */
+/* We use char because int might match the return type of a gcc2
+ builtin and then its argument prototype would still apply. */
+char $ac_func();
+
+int main() {
+
+/* The GNU C library defines this for functions which it implements
+ to always fail with ENOSYS. Some functions are actually named
+ something starting with __ and the normal name is an alias. */
+#if defined (__stub_$ac_func) || defined (__stub___$ac_func)
+choke me
+#else
+$ac_func();
+#endif
+
+; return 0; }
+EOF
+if { (eval echo configure:10724: \"$ac_link\") 1>&5; (eval $ac_link) 2>&5; } && test -s conftest${ac_exeext}; then
+ rm -rf conftest*
+ eval "ac_cv_func_$ac_func=yes"
+else
+ echo "configure: failed program was:" >&5
+ cat conftest.$ac_ext >&5
+ rm -rf conftest*
+ eval "ac_cv_func_$ac_func=no"
+fi
+rm -f conftest*
+fi
+
+if eval "test \"`echo '$ac_cv_func_'$ac_func`\" = yes"; then
+ echo "$ac_t""yes" 1>&6
+ ac_tr_func=HAVE_`echo $ac_func | tr 'abcdefghijklmnopqrstuvwxyz' 'ABCDEFGHIJKLMNOPQRSTUVWXYZ'`
+ cat >> confdefs.h <<EOF
+#define $ac_tr_func 1
+EOF
+ break
+else
+ echo "$ac_t""no" 1>&6
+fi
+done
+
+fi
+
+if test x"$ac_cv_func_statvfs" = xno ; then
+ for ac_func in statfs
+do
+echo $ac_n "checking for $ac_func""... $ac_c" 1>&6
+echo "configure:10754: checking for $ac_func" >&5
+if eval "test \"`echo '$''{'ac_cv_func_$ac_func'+set}'`\" = set"; then
+ echo $ac_n "(cached) $ac_c" 1>&6
+else
+ cat > conftest.$ac_ext <<EOF
+#line 10759 "configure"
+#include "confdefs.h"
+/* System header to define __stub macros and hopefully few prototypes,
+ which can conflict with char $ac_func(); below. */
+#include <assert.h>
+/* Override any gcc2 internal prototype to avoid an error. */
+/* We use char because int might match the return type of a gcc2
+ builtin and then its argument prototype would still apply. */
+char $ac_func();
+
+int main() {
+
+/* The GNU C library defines this for functions which it implements
+ to always fail with ENOSYS. Some functions are actually named
+ something starting with __ and the normal name is an alias. */
+#if defined (__stub_$ac_func) || defined (__stub___$ac_func)
+choke me
+#else
+$ac_func();
+#endif
+
+; return 0; }
+EOF
+if { (eval echo configure:10782: \"$ac_link\") 1>&5; (eval $ac_link) 2>&5; } && test -s conftest${ac_exeext}; then
+ rm -rf conftest*
+ eval "ac_cv_func_$ac_func=yes"
+else
+ echo "configure: failed program was:" >&5
+ cat conftest.$ac_ext >&5
+ rm -rf conftest*
+ eval "ac_cv_func_$ac_func=no"
+fi
+rm -f conftest*
+fi
+
+if eval "test \"`echo '$ac_cv_func_'$ac_func`\" = yes"; then
+ echo "$ac_t""yes" 1>&6
+ ac_tr_func=HAVE_`echo $ac_func | tr 'abcdefghijklmnopqrstuvwxyz' 'ABCDEFGHIJKLMNOPQRSTUVWXYZ'`
+ cat >> confdefs.h <<EOF
+#define $ac_tr_func 1
+EOF
+
+else
+ echo "$ac_t""no" 1>&6
+fi
+done
+
+ for ac_hdr in sys/vfs.h sys/mount.h
+do
+ac_safe=`echo "$ac_hdr" | sed 'y%./+-%__p_%'`
+echo $ac_n "checking for $ac_hdr""... $ac_c" 1>&6
+echo "configure:10810: checking for $ac_hdr" >&5
+if eval "test \"`echo '$''{'ac_cv_header_$ac_safe'+set}'`\" = set"; then
+ echo $ac_n "(cached) $ac_c" 1>&6
+else
+ cat > conftest.$ac_ext <<EOF
+#line 10815 "configure"
+#include "confdefs.h"
+#include <$ac_hdr>
+EOF
+ac_try="$ac_cpp conftest.$ac_ext >/dev/null 2>conftest.out"
+{ (eval echo configure:10820: \"$ac_try\") 1>&5; (eval $ac_try) 2>&5; }
+ac_err=`grep -v '^ *+' conftest.out | grep -v "^conftest.${ac_ext}\$"`
+if test -z "$ac_err"; then
+ rm -rf conftest*
+ eval "ac_cv_header_$ac_safe=yes"
+else
+ echo "$ac_err" >&5
+ echo "configure: failed program was:" >&5
+ cat conftest.$ac_ext >&5
+ rm -rf conftest*
+ eval "ac_cv_header_$ac_safe=no"
+fi
+rm -f conftest*
+fi
+if eval "test \"`echo '$ac_cv_header_'$ac_safe`\" = yes"; then
+ echo "$ac_t""yes" 1>&6
+ ac_tr_hdr=HAVE_`echo $ac_hdr | sed 'y%abcdefghijklmnopqrstuvwxyz./-%ABCDEFGHIJKLMNOPQRSTUVWXYZ___%'`
+ cat >> confdefs.h <<EOF
+#define $ac_tr_hdr 1
+EOF
+
+else
+ echo "$ac_t""no" 1>&6
+fi
+done
+
+fi
+
+for ac_func in fseeko ftello getpagesize hstrerror inet_aton mkstemp \
+ pread pwrite seteuid strcasecmp strerror strlcat strlcpy \
+ strspn setenv
+do
+echo $ac_n "checking for $ac_func""... $ac_c" 1>&6
+echo "configure:10853: checking for $ac_func" >&5
+if eval "test \"`echo '$''{'ac_cv_func_$ac_func'+set}'`\" = set"; then
+ echo $ac_n "(cached) $ac_c" 1>&6
+else
+ cat > conftest.$ac_ext <<EOF
+#line 10858 "configure"
+#include "confdefs.h"
+/* System header to define __stub macros and hopefully few prototypes,
+ which can conflict with char $ac_func(); below. */
+#include <assert.h>
+/* Override any gcc2 internal prototype to avoid an error. */
+/* We use char because int might match the return type of a gcc2
+ builtin and then its argument prototype would still apply. */
+char $ac_func();
+
+int main() {
+
+/* The GNU C library defines this for functions which it implements
+ to always fail with ENOSYS. Some functions are actually named
+ something starting with __ and the normal name is an alias. */
+#if defined (__stub_$ac_func) || defined (__stub___$ac_func)
+choke me
+#else
+$ac_func();
+#endif
+
+; return 0; }
+EOF
+if { (eval echo configure:10881: \"$ac_link\") 1>&5; (eval $ac_link) 2>&5; } && test -s conftest${ac_exeext}; then
+ rm -rf conftest*
+ eval "ac_cv_func_$ac_func=yes"
+else
+ echo "configure: failed program was:" >&5
+ cat conftest.$ac_ext >&5
+ rm -rf conftest*
+ eval "ac_cv_func_$ac_func=no"
+fi
+rm -f conftest*
+fi
+
+if eval "test \"`echo '$ac_cv_func_'$ac_func`\" = yes"; then
+ echo "$ac_t""yes" 1>&6
+ ac_tr_func=HAVE_`echo $ac_func | tr 'abcdefghijklmnopqrstuvwxyz' 'ABCDEFGHIJKLMNOPQRSTUVWXYZ'`
+ cat >> confdefs.h <<EOF
+#define $ac_tr_func 1
+EOF
+
+else
+ echo "$ac_t""no" 1>&6
+LIBOBJS="$LIBOBJS ${ac_func}.${ac_objext}"
+fi
+done
+
+
+
+
+
+
+
+if test "$ac_cv_func_fseeko" = no || test "$ac_cv_func_ftello" = no ; then
+ echo $ac_n "checking for off_t-compatible fpos_t""... $ac_c" 1>&6
+echo "configure:10914: checking for off_t-compatible fpos_t" >&5
+if eval "test \"`echo '$''{'inn_cv_type_fpos_t_large'+set}'`\" = set"; then
+ echo $ac_n "(cached) $ac_c" 1>&6
+else
+ if test "$cross_compiling" = yes; then
+ inn_cv_type_fpos_t_large=no
+else
+ cat > conftest.$ac_ext <<EOF
+#line 10922 "configure"
+#include "confdefs.h"
+#include <stdio.h>
+#include <sys/types.h>
+
+int
+main ()
+{
+ fpos_t fpos = 9223372036854775807ULL;
+ off_t off;
+ off = fpos;
+ exit(off == (off_t) 9223372036854775807ULL ? 0 : 1);
+}
+EOF
+if { (eval echo configure:10936: \"$ac_link\") 1>&5; (eval $ac_link) 2>&5; } && test -s conftest${ac_exeext} && (./conftest; exit) 2>/dev/null
+then
+ inn_cv_type_fpos_t_large=yes
+else
+ echo "configure: failed program was:" >&5
+ cat conftest.$ac_ext >&5
+ rm -fr conftest*
+ inn_cv_type_fpos_t_large=no
+fi
+rm -fr conftest*
+fi
+
+if test "$inn_cv_type_fpos_t_large" = yes ; then
+ cat >> confdefs.h <<\EOF
+#define HAVE_LARGE_FPOS_T 1
+EOF
+
+fi
+fi
+
+echo "$ac_t""$inn_cv_type_fpos_t_large" 1>&6
+fi
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+echo $ac_n "checking for working mmap""... $ac_c" 1>&6
+echo "configure:10975: checking for working mmap" >&5
+if eval "test \"`echo '$''{'inn_cv_func_mmap'+set}'`\" = set"; then
+ echo $ac_n "(cached) $ac_c" 1>&6
+else
+ if test "$cross_compiling" = yes; then
+ inn_cv_func_mmap=no
+else
+ cat > conftest.$ac_ext <<EOF
+#line 10983 "configure"
+#include "confdefs.h"
+#include <stdio.h>
+#include <sys/types.h>
+#if STDC_HEADERS
+# include <stdlib.h>
+# include <stddef.h>
+#else
+# if HAVE_STDLIB_H
+# include <stdlib.h>
+# endif
+# if !HAVE_STRCHR
+# define strchr index
+# define strrchr rindex
+# endif
+#endif
+#if HAVE_STRING_H
+# if !STDC_HEADERS && HAVE_MEMORY_H
+# include <memory.h>
+# endif
+# include <string.h>
+#else
+# if HAVE_STRINGS_H
+# include <strings.h>
+# endif
+#endif
+#if HAVE_UNISTD_H
+# include <unistd.h>
+#endif
+#include <fcntl.h>
+#include <sys/mman.h>
+
+int
+main()
+{
+ int *data, *data2;
+ int i, fd;
+
+ /* First, make a file with some known garbage in it. Use something
+ larger than one page but still an odd page size. */
+ data = malloc (20000);
+ if (!data) return 1;
+ for (i = 0; i < 20000 / sizeof (int); i++)
+ data[i] = rand();
+ umask (0);
+ fd = creat ("conftestmmaps", 0600);
+ if (fd < 0) return 1;
+ if (write (fd, data, 20000) != 20000) return 1;
+ close (fd);
+
+ /* Next, try to mmap the file and make sure we see the same garbage. */
+ fd = open ("conftestmmaps", O_RDWR);
+ if (fd < 0) return 1;
+ data2 = mmap (0, 20000, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);
+ if (data2 == (int *) -1) return 1;
+ for (i = 0; i < 20000 / sizeof (int); i++)
+ if (data[i] != data2[i])
+ return 1;
+
+ close (fd);
+ unlink ("conftestmmaps");
+ return 0;
+}
+EOF
+if { (eval echo configure:11047: \"$ac_link\") 1>&5; (eval $ac_link) 2>&5; } && test -s conftest${ac_exeext} && (./conftest; exit) 2>/dev/null
+then
+ inn_cv_func_mmap=yes
+else
+ echo "configure: failed program was:" >&5
+ cat conftest.$ac_ext >&5
+ rm -fr conftest*
+ inn_cv_func_mmap=no
+fi
+rm -fr conftest*
+fi
+
+fi
+
+echo "$ac_t""$inn_cv_func_mmap" 1>&6
+if test $inn_cv_func_mmap = yes ; then
+ cat >> confdefs.h <<\EOF
+#define HAVE_MMAP 1
+EOF
+
+fi
+if test x"$inn_cv_func_mmap" = xyes ; then
+ for ac_func in madvise
+do
+echo $ac_n "checking for $ac_func""... $ac_c" 1>&6
+echo "configure:11072: checking for $ac_func" >&5
+if eval "test \"`echo '$''{'ac_cv_func_$ac_func'+set}'`\" = set"; then
+ echo $ac_n "(cached) $ac_c" 1>&6
+else
+ cat > conftest.$ac_ext <<EOF
+#line 11077 "configure"
+#include "confdefs.h"
+/* System header to define __stub macros and hopefully few prototypes,
+ which can conflict with char $ac_func(); below. */
+#include <assert.h>
+/* Override any gcc2 internal prototype to avoid an error. */
+/* We use char because int might match the return type of a gcc2
+ builtin and then its argument prototype would still apply. */
+char $ac_func();
+
+int main() {
+
+/* The GNU C library defines this for functions which it implements
+ to always fail with ENOSYS. Some functions are actually named
+ something starting with __ and the normal name is an alias. */
+#if defined (__stub_$ac_func) || defined (__stub___$ac_func)
+choke me
+#else
+$ac_func();
+#endif
+
+; return 0; }
+EOF
+if { (eval echo configure:11100: \"$ac_link\") 1>&5; (eval $ac_link) 2>&5; } && test -s conftest${ac_exeext}; then
+ rm -rf conftest*
+ eval "ac_cv_func_$ac_func=yes"
+else
+ echo "configure: failed program was:" >&5
+ cat conftest.$ac_ext >&5
+ rm -rf conftest*
+ eval "ac_cv_func_$ac_func=no"
+fi
+rm -f conftest*
+fi
+
+if eval "test \"`echo '$ac_cv_func_'$ac_func`\" = yes"; then
+ echo "$ac_t""yes" 1>&6
+ ac_tr_func=HAVE_`echo $ac_func | tr 'abcdefghijklmnopqrstuvwxyz' 'ABCDEFGHIJKLMNOPQRSTUVWXYZ'`
+ cat >> confdefs.h <<EOF
+#define $ac_tr_func 1
+EOF
+
+else
+ echo "$ac_t""no" 1>&6
+fi
+done
+
+ echo $ac_n "checking whether mmap sees writes""... $ac_c" 1>&6
+echo "configure:11125: checking whether mmap sees writes" >&5
+if eval "test \"`echo '$''{'inn_cv_func_mmap_sees_writes'+set}'`\" = set"; then
+ echo $ac_n "(cached) $ac_c" 1>&6
+else
+ if test "$cross_compiling" = yes; then
+ inn_cv_func_mmap_sees_writes=no
+else
+ cat > conftest.$ac_ext <<EOF
+#line 11133 "configure"
+#include "confdefs.h"
+#include <stdlib.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <fcntl.h>
+#if HAVE_UNISTD_H
+# include <unistd.h>
+#endif
+#include <sys/mman.h>
+
+/* Fractional page is probably worst case. */
+static char zbuff[1024];
+static char fname[] = "conftestw";
+
+int
+main ()
+{
+ char *map;
+ int i, fd;
+
+ fd = open (fname, O_RDWR | O_CREAT, 0660);
+ if (fd < 0) return 1;
+ unlink (fname);
+ write (fd, zbuff, sizeof (zbuff));
+ lseek (fd, 0, SEEK_SET);
+ map = mmap (0, sizeof (zbuff), PROT_READ, MAP_SHARED, fd, 0);
+ if (map == (char *) -1) return 2;
+ for (i = 0; fname[i]; i++)
+ {
+ if (write (fd, &fname[i], 1) != 1) return 3;
+ if (map[i] != fname[i]) return 4;
+ }
+ return 0;
+}
+EOF
+if { (eval echo configure:11169: \"$ac_link\") 1>&5; (eval $ac_link) 2>&5; } && test -s conftest${ac_exeext} && (./conftest; exit) 2>/dev/null
+then
+ inn_cv_func_mmap_sees_writes=yes
+else
+ echo "configure: failed program was:" >&5
+ cat conftest.$ac_ext >&5
+ rm -fr conftest*
+ inn_cv_func_mmap_sees_writes=no
+fi
+rm -fr conftest*
+fi
+
+fi
+
+echo "$ac_t""$inn_cv_func_mmap_sees_writes" 1>&6
+if test $inn_cv_func_mmap_sees_writes = no ; then
+ cat >> confdefs.h <<\EOF
+#define MMAP_MISSES_WRITES 1
+EOF
+
+fi
+ echo $ac_n "checking whether msync is needed""... $ac_c" 1>&6
+echo "configure:11191: checking whether msync is needed" >&5
+if eval "test \"`echo '$''{'inn_cv_func_mmap_need_msync'+set}'`\" = set"; then
+ echo $ac_n "(cached) $ac_c" 1>&6
+else
+ if test "$cross_compiling" = yes; then
+ inn_cv_func_mmap_need_msync=yes
+else
+ cat > conftest.$ac_ext <<EOF
+#line 11199 "configure"
+#include "confdefs.h"
+#include <stdio.h>
+#include <sys/types.h>
+#if STDC_HEADERS
+# include <stdlib.h>
+# include <stddef.h>
+#else
+# if HAVE_STDLIB_H
+# include <stdlib.h>
+# endif
+# if !HAVE_STRCHR
+# define strchr index
+# define strrchr rindex
+# endif
+#endif
+#if HAVE_STRING_H
+# if !STDC_HEADERS && HAVE_MEMORY_H
+# include <memory.h>
+# endif
+# include <string.h>
+#else
+# if HAVE_STRINGS_H
+# include <strings.h>
+# endif
+#endif
+#if HAVE_UNISTD_H
+# include <unistd.h>
+#endif
+#include <sys/types.h>
+#include <fcntl.h>
+#include <sys/mman.h>
+
+int
+main()
+{
+ int *data, *data2;
+ int i, fd;
+
+ /* First, make a file with some known garbage in it. Use something
+ larger than one page but still an odd page size. */
+ data = malloc (20000);
+ if (!data) return 1;
+ for (i = 0; i < 20000 / sizeof (int); i++)
+ data[i] = rand();
+ umask (0);
+ fd = creat ("conftestmmaps", 0600);
+ if (fd < 0) return 1;
+ if (write (fd, data, 20000) != 20000) return 1;
+ close (fd);
+
+ /* Next, try to mmap the file and make sure we see the same garbage. */
+ fd = open ("conftestmmaps", O_RDWR);
+ if (fd < 0) return 1;
+ data2 = mmap (0, 20000, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);
+ if (data2 == (int *) -1) return 1;
+
+ /* Finally, see if changes made to the mmaped region propagate back to
+ the file as seen by read (meaning that msync isn't needed). */
+ for (i = 0; i < 20000 / sizeof (int); i++)
+ data2[i]++;
+ if (read (fd, data, 20000) != 20000) return 1;
+ for (i = 0; i < 20000 / sizeof (int); i++)
+ if (data[i] != data2[i])
+ return 1;
+
+ close (fd);
+ unlink ("conftestmmapm");
+ return 0;
+}
+EOF
+if { (eval echo configure:11270: \"$ac_link\") 1>&5; (eval $ac_link) 2>&5; } && test -s conftest${ac_exeext} && (./conftest; exit) 2>/dev/null
+then
+ inn_cv_func_mmap_need_msync=no
+else
+ echo "configure: failed program was:" >&5
+ cat conftest.$ac_ext >&5
+ rm -fr conftest*
+ inn_cv_func_mmap_need_msync=yes
+fi
+rm -fr conftest*
+fi
+
+fi
+
+echo "$ac_t""$inn_cv_func_mmap_need_msync" 1>&6
+if test $inn_cv_func_mmap_need_msync = yes ; then
+ cat >> confdefs.h <<\EOF
+#define MMAP_NEEDS_MSYNC 1
+EOF
+
+fi
+ echo $ac_n "checking how many arguments msync takes""... $ac_c" 1>&6
+echo "configure:11292: checking how many arguments msync takes" >&5
+if eval "test \"`echo '$''{'inn_cv_func_msync_args'+set}'`\" = set"; then
+ echo $ac_n "(cached) $ac_c" 1>&6
+else
+ cat > conftest.$ac_ext <<EOF
+#line 11297 "configure"
+#include "confdefs.h"
+#include <sys/types.h>
+#include <sys/mman.h>
+int main() {
+char *p; int psize; msync (p, psize, MS_ASYNC);
+; return 0; }
+EOF
+if { (eval echo configure:11305: \"$ac_compile\") 1>&5; (eval $ac_compile) 2>&5; }; then
+ rm -rf conftest*
+ inn_cv_func_msync_args=3
+else
+ echo "configure: failed program was:" >&5
+ cat conftest.$ac_ext >&5
+ rm -rf conftest*
+ inn_cv_func_msync_args=2
+fi
+rm -f conftest*
+fi
+
+echo "$ac_t""$inn_cv_func_msync_args" 1>&6
+if test $inn_cv_func_msync_args = 3 ; then
+ cat >> confdefs.h <<\EOF
+#define HAVE_MSYNC_3_ARG 1
+EOF
+
+fi
+fi
+
+
+echo $ac_n "checking for Unix domain sockets""... $ac_c" 1>&6
+echo "configure:11328: checking for Unix domain sockets" >&5
+if eval "test \"`echo '$''{'inn_cv_sys_unix_sockets'+set}'`\" = set"; then
+ echo $ac_n "(cached) $ac_c" 1>&6
+else
+ cat > conftest.$ac_ext <<EOF
+#line 11333 "configure"
+#include "confdefs.h"
+#include <sys/socket.h>
+#ifdef AF_UNIX
+yes
+#endif
+EOF
+if (eval "$ac_cpp conftest.$ac_ext") 2>&5 |
+ egrep "yes" >/dev/null 2>&1; then
+ rm -rf conftest*
+ inn_cv_sys_unix_sockets=yes
+else
+ rm -rf conftest*
+ inn_cv_sys_unix_sockets=no
+fi
+rm -f conftest*
+
+fi
+
+echo "$ac_t""$inn_cv_sys_unix_sockets" 1>&6
+if test $inn_cv_sys_unix_sockets = yes ; then
+ cat >> confdefs.h <<\EOF
+#define HAVE_UNIX_DOMAIN_SOCKETS 1
+EOF
+
+fi
+
+
+echo $ac_n "checking log facility for news""... $ac_c" 1>&6
+echo "configure:11362: checking log facility for news" >&5
+if eval "test \"`echo '$''{'inn_cv_log_facility'+set}'`\" = set"; then
+ echo $ac_n "(cached) $ac_c" 1>&6
+else
+ cat > conftest.$ac_ext <<EOF
+#line 11367 "configure"
+#include "confdefs.h"
+#include <syslog.h>
+#ifdef LOG_NEWS
+yes
+#endif
+EOF
+if (eval "$ac_cpp conftest.$ac_ext") 2>&5 |
+ egrep "yes" >/dev/null 2>&1; then
+ rm -rf conftest*
+ inn_cv_log_facility=LOG_NEWS
+else
+ rm -rf conftest*
+ inn_cv_log_facility=LOG_LOCAL1
+fi
+rm -f conftest*
+
+fi
+
+if test x"$SYSLOG_FACILITY" = xnone ; then
+ SYSLOG_FACILITY=$inn_cv_log_facility
+fi
+echo "$ac_t""$SYSLOG_FACILITY" 1>&6
+cat >> confdefs.h <<EOF
+#define LOG_INN_SERVER $SYSLOG_FACILITY
+EOF
+
+cat >> confdefs.h <<EOF
+#define LOG_INN_PROG $SYSLOG_FACILITY
+EOF
+
+
+
+LIBS=`echo "$LIBS" | sed 's/^ *//' | sed 's/ */ /g' | sed 's/ *$//'`
+
+
+trap '' 1 2 15
+cat > confcache <<\EOF
+# This file is a shell script that caches the results of configure
+# tests run on this system so they can be shared between configure
+# scripts and configure runs. It is not useful on other systems.
+# If it contains results you don't want to keep, you may remove or edit it.
+#
+# By default, configure uses ./config.cache as the cache file,
+# creating it if it does not exist already. You can give configure
+# the --cache-file=FILE option to use a different cache file; that is
+# what configure does when it calls configure scripts in
+# subdirectories, so they share the cache.
+# Giving --cache-file=/dev/null disables caching, for debugging configure.
+# config.status only pays attention to the cache file if you give it the
+# --recheck option to rerun configure.
+#
+EOF
+# The following way of writing the cache mishandles newlines in values,
+# but we know of no workaround that is simple, portable, and efficient.
+# So, don't put newlines in cache variables' values.
+# Ultrix sh set writes to stderr and can't be redirected directly,
+# and sets the high bit in the cache file unless we assign to the vars.
+(set) 2>&1 |
+ case `(ac_space=' '; set | grep ac_space) 2>&1` in
+ *ac_space=\ *)
+ # `set' does not quote correctly, so add quotes (double-quote substitution
+ # turns \\\\ into \\, and sed turns \\ into \).
+ sed -n \
+ -e "s/'/'\\\\''/g" \
+ -e "s/^\\([a-zA-Z0-9_]*_cv_[a-zA-Z0-9_]*\\)=\\(.*\\)/\\1=\${\\1='\\2'}/p"
+ ;;
+ *)
+ # `set' quotes correctly as required by POSIX, so do not add quotes.
+ sed -n -e 's/^\([a-zA-Z0-9_]*_cv_[a-zA-Z0-9_]*\)=\(.*\)/\1=${\1=\2}/p'
+ ;;
+ esac >> confcache
+if cmp -s $cache_file confcache; then
+ :
+else
+ if test -w $cache_file; then
+ echo "updating cache $cache_file"
+ cat confcache > $cache_file
+ else
+ echo "not updating unwritable cache $cache_file"
+ fi
+fi
+rm -f confcache
+
+trap 'rm -fr conftest* confdefs* core core.* *.core $ac_clean_files; exit 1' 1 2 15
+
+test "x$prefix" = xNONE && prefix=$ac_default_prefix
+# Let make expand exec_prefix.
+test "x$exec_prefix" = xNONE && exec_prefix='${prefix}'
+
+# Any assignment to VPATH causes Sun make to only execute
+# the first set of double-colon rules, so remove it if not needed.
+# If there is a colon in the path, we need to keep it.
+if test "x$srcdir" = x.; then
+ ac_vpsub='/^[ ]*VPATH[ ]*=[^:]*$/d'
+fi
+
+trap 'rm -f $CONFIG_STATUS conftest*; exit 1' 1 2 15
+
+DEFS=-DHAVE_CONFIG_H
+
+# Without the "./", some shells look in PATH for config.status.
+: ${CONFIG_STATUS=./config.status}
+
+echo creating $CONFIG_STATUS
+rm -f $CONFIG_STATUS
+cat > $CONFIG_STATUS <<EOF
+#! /bin/sh
+# Generated automatically by configure.
+# Run this file to recreate the current configuration.
+# This directory was configured as follows,
+# on host `(hostname || uname -n) 2>/dev/null | sed 1q`:
+#
+# $0 $ac_configure_args
+#
+# Compiler output produced by configure, useful for debugging
+# configure, is in ./config.log if it exists.
+
+ac_cs_usage="Usage: $CONFIG_STATUS [--recheck] [--version] [--help]"
+for ac_option
+do
+ case "\$ac_option" in
+ -recheck | --recheck | --rechec | --reche | --rech | --rec | --re | --r)
+ echo "running \${CONFIG_SHELL-/bin/sh} $0 $ac_configure_args --no-create --no-recursion"
+ exec \${CONFIG_SHELL-/bin/sh} $0 $ac_configure_args --no-create --no-recursion ;;
+ -version | --version | --versio | --versi | --vers | --ver | --ve | --v)
+ echo "$CONFIG_STATUS generated by autoconf version 2.13"
+ exit 0 ;;
+ -help | --help | --hel | --he | --h)
+ echo "\$ac_cs_usage"; exit 0 ;;
+ *) echo "\$ac_cs_usage"; exit 1 ;;
+ esac
+done
+
+ac_given_srcdir=$srcdir
+
+trap 'rm -fr `echo "Makefile.global
+ include/paths.h
+ samples/inn.conf
+ samples/innreport.conf
+ samples/newsfeeds
+ samples/sasl.conf
+ scripts/inncheck
+ scripts/innshellvars
+ scripts/innshellvars.pl
+ scripts/innshellvars.tcl
+ scripts/news.daily
+ support/fixscript
+ include/config.h" | sed "s/:[^ ]*//g"` conftest*; exit 1' 1 2 15
+EOF
+cat >> $CONFIG_STATUS <<EOF
+
+# Protect against being on the right side of a sed subst in config.status.
+sed 's/%@/@@/; s/@%/@@/; s/%g\$/@g/; /@g\$/s/[\\\\&%]/\\\\&/g;
+ s/@@/%@/; s/@@/@%/; s/@g\$/%g/' > conftest.subs <<\\CEOF
+$ac_vpsub
+$extrasub
+s%@SHELL@%$SHELL%g
+s%@CFLAGS@%$CFLAGS%g
+s%@CPPFLAGS@%$CPPFLAGS%g
+s%@CXXFLAGS@%$CXXFLAGS%g
+s%@FFLAGS@%$FFLAGS%g
+s%@DEFS@%$DEFS%g
+s%@LDFLAGS@%$LDFLAGS%g
+s%@LIBS@%$LIBS%g
+s%@exec_prefix@%$exec_prefix%g
+s%@prefix@%$prefix%g
+s%@program_transform_name@%$program_transform_name%g
+s%@bindir@%$bindir%g
+s%@sbindir@%$sbindir%g
+s%@libexecdir@%$libexecdir%g
+s%@datadir@%$datadir%g
+s%@sysconfdir@%$sysconfdir%g
+s%@sharedstatedir@%$sharedstatedir%g
+s%@localstatedir@%$localstatedir%g
+s%@libdir@%$libdir%g
+s%@includedir@%$includedir%g
+s%@oldincludedir@%$oldincludedir%g
+s%@infodir@%$infodir%g
+s%@mandir@%$mandir%g
+s%@builddir@%$builddir%g
+s%@CC@%$CC%g
+s%@CPP@%$CPP%g
+s%@OBJEXT@%$OBJEXT%g
+s%@host@%$host%g
+s%@host_alias@%$host_alias%g
+s%@host_cpu@%$host_cpu%g
+s%@host_vendor@%$host_vendor%g
+s%@host_os@%$host_os%g
+s%@build@%$build%g
+s%@build_alias@%$build_alias%g
+s%@build_cpu@%$build_cpu%g
+s%@build_vendor@%$build_vendor%g
+s%@build_os@%$build_os%g
+s%@LN_S@%$LN_S%g
+s%@EXEEXT@%$EXEEXT%g
+s%@ECHO@%$ECHO%g
+s%@RANLIB@%$RANLIB%g
+s%@STRIP@%$STRIP%g
+s%@LIBTOOL@%$LIBTOOL%g
+s%@EXTLIB@%$EXTLIB%g
+s%@EXTOBJ@%$EXTOBJ%g
+s%@LIBTOOLCC@%$LIBTOOLCC%g
+s%@LIBTOOLLD@%$LIBTOOLLD%g
+s%@CCOUTPUT@%$CCOUTPUT%g
+s%@CONTROLDIR@%$CONTROLDIR%g
+s%@DBDIR@%$DBDIR%g
+s%@DOCDIR@%$DOCDIR%g
+s%@ETCDIR@%$ETCDIR%g
+s%@FILTERDIR@%$FILTERDIR%g
+s%@LIBDIR@%$LIBDIR%g
+s%@LOGDIR@%$LOGDIR%g
+s%@RUNDIR@%$RUNDIR%g
+s%@SPOOLDIR@%$SPOOLDIR%g
+s%@tmpdir@%$tmpdir%g
+s%@NEWSUSER@%$NEWSUSER%g
+s%@NEWSGRP@%$NEWSGRP%g
+s%@NEWSMASTER@%$NEWSMASTER%g
+s%@NEWSUMASK@%$NEWSUMASK%g
+s%@FILEMODE@%$FILEMODE%g
+s%@DIRMODE@%$DIRMODE%g
+s%@RUNDIRMODE@%$RUNDIRMODE%g
+s%@INEWSMODE@%$INEWSMODE%g
+s%@RNEWSGRP@%$RNEWSGRP%g
+s%@RNEWSMODE@%$RNEWSMODE%g
+s%@LOG_COMPRESS@%$LOG_COMPRESS%g
+s%@LOG_COMPRESSEXT@%$LOG_COMPRESSEXT%g
+s%@DO_DBZ_TAGGED_HASH@%$DO_DBZ_TAGGED_HASH%g
+s%@HOSTNAME@%$HOSTNAME%g
+s%@LEX@%$LEX%g
+s%@LEXLIB@%$LEXLIB%g
+s%@SET_MAKE@%$SET_MAKE%g
+s%@YACC@%$YACC%g
+s%@CTAGS@%$CTAGS%g
+s%@_PATH_AWK@%$_PATH_AWK%g
+s%@_PATH_EGREP@%$_PATH_EGREP%g
+s%@_PATH_PERL@%$_PATH_PERL%g
+s%@_PATH_SH@%$_PATH_SH%g
+s%@_PATH_SED@%$_PATH_SED%g
+s%@_PATH_SORT@%$_PATH_SORT%g
+s%@_PATH_UUX@%$_PATH_UUX%g
+s%@PATH_GPGV@%$PATH_GPGV%g
+s%@_PATH_PGP@%$_PATH_PGP%g
+s%@pgpverify@%$pgpverify%g
+s%@GETFTP@%$GETFTP%g
+s%@COMPRESS@%$COMPRESS%g
+s%@GZIP@%$GZIP%g
+s%@UNCOMPRESS@%$UNCOMPRESS%g
+s%@SENDMAIL@%$SENDMAIL%g
+s%@HAVE_UUSTAT@%$HAVE_UUSTAT%g
+s%@_PATH_PYTHON@%$_PATH_PYTHON%g
+s%@CRYPT_LIB@%$CRYPT_LIB%g
+s%@SHADOW_LIB@%$SHADOW_LIB%g
+s%@PAM_LIB@%$PAM_LIB%g
+s%@REGEX_LIB@%$REGEX_LIB%g
+s%@BERKELEY_DB_LDFLAGS@%$BERKELEY_DB_LDFLAGS%g
+s%@BERKELEY_DB_CFLAGS@%$BERKELEY_DB_CFLAGS%g
+s%@BERKELEY_DB_LIB@%$BERKELEY_DB_LIB%g
+s%@DBM_LIB@%$DBM_LIB%g
+s%@DBM_INC@%$DBM_INC%g
+s%@SSL_BIN@%$SSL_BIN%g
+s%@SSL_INC@%$SSL_INC%g
+s%@SSL_LIB@%$SSL_LIB%g
+s%@SASL_INC@%$SASL_INC%g
+s%@SASL_LIB@%$SASL_LIB%g
+s%@KRB5_AUTH@%$KRB5_AUTH%g
+s%@KRB5_INC@%$KRB5_INC%g
+s%@KRB5_LIB@%$KRB5_LIB%g
+s%@PERL_INC@%$PERL_INC%g
+s%@PERL_LIB@%$PERL_LIB%g
+s%@PYTHON_LIB@%$PYTHON_LIB%g
+s%@PYTHON_INC@%$PYTHON_INC%g
+s%@GETCONF@%$GETCONF%g
+s%@LFS_CFLAGS@%$LFS_CFLAGS%g
+s%@LFS_LDFLAGS@%$LFS_LDFLAGS%g
+s%@LFS_LIBS@%$LFS_LIBS%g
+s%@LIBOBJS@%$LIBOBJS%g
+s%@SYSLOG_FACILITY@%$SYSLOG_FACILITY%g
+
+CEOF
+EOF
+
+cat >> $CONFIG_STATUS <<\EOF
+
+# Split the substitutions into bite-sized pieces for seds with
+# small command number limits, like on Digital OSF/1 and HP-UX.
+ac_max_sed_cmds=90 # Maximum number of lines to put in a sed script.
+ac_file=1 # Number of current file.
+ac_beg=1 # First line for current file.
+ac_end=$ac_max_sed_cmds # Line after last line for current file.
+ac_more_lines=:
+ac_sed_cmds=""
+while $ac_more_lines; do
+ if test $ac_beg -gt 1; then
+ sed "1,${ac_beg}d; ${ac_end}q" conftest.subs > conftest.s$ac_file
+ else
+ sed "${ac_end}q" conftest.subs > conftest.s$ac_file
+ fi
+ if test ! -s conftest.s$ac_file; then
+ ac_more_lines=false
+ rm -f conftest.s$ac_file
+ else
+ if test -z "$ac_sed_cmds"; then
+ ac_sed_cmds="sed -f conftest.s$ac_file"
+ else
+ ac_sed_cmds="$ac_sed_cmds | sed -f conftest.s$ac_file"
+ fi
+ ac_file=`expr $ac_file + 1`
+ ac_beg=$ac_end
+ ac_end=`expr $ac_end + $ac_max_sed_cmds`
+ fi
+done
+if test -z "$ac_sed_cmds"; then
+ ac_sed_cmds=cat
+fi
+EOF
+
+cat >> $CONFIG_STATUS <<EOF
+
+CONFIG_FILES=\${CONFIG_FILES-"Makefile.global
+ include/paths.h
+ samples/inn.conf
+ samples/innreport.conf
+ samples/newsfeeds
+ samples/sasl.conf
+ scripts/inncheck
+ scripts/innshellvars
+ scripts/innshellvars.pl
+ scripts/innshellvars.tcl
+ scripts/news.daily
+ support/fixscript
+ "}
+EOF
+cat >> $CONFIG_STATUS <<\EOF
+for ac_file in .. $CONFIG_FILES; do if test "x$ac_file" != x..; then
+ # Support "outfile[:infile[:infile...]]", defaulting infile="outfile.in".
+ case "$ac_file" in
+ *:*) ac_file_in=`echo "$ac_file"|sed 's%[^:]*:%%'`
+ ac_file=`echo "$ac_file"|sed 's%:.*%%'` ;;
+ *) ac_file_in="${ac_file}.in" ;;
+ esac
+
+ # Adjust a relative srcdir, top_srcdir, and INSTALL for subdirectories.
+
+ # Remove last slash and all that follows it. Not all systems have dirname.
+ ac_dir=`echo $ac_file|sed 's%/[^/][^/]*$%%'`
+ if test "$ac_dir" != "$ac_file" && test "$ac_dir" != .; then
+ # The file is in a subdirectory.
+ test ! -d "$ac_dir" && mkdir "$ac_dir"
+ ac_dir_suffix="/`echo $ac_dir|sed 's%^\./%%'`"
+ # A "../" for each directory in $ac_dir_suffix.
+ ac_dots=`echo $ac_dir_suffix|sed 's%/[^/]*%../%g'`
+ else
+ ac_dir_suffix= ac_dots=
+ fi
+
+ case "$ac_given_srcdir" in
+ .) srcdir=.
+ if test -z "$ac_dots"; then top_srcdir=.
+ else top_srcdir=`echo $ac_dots|sed 's%/$%%'`; fi ;;
+ /*) srcdir="$ac_given_srcdir$ac_dir_suffix"; top_srcdir="$ac_given_srcdir" ;;
+ *) # Relative path.
+ srcdir="$ac_dots$ac_given_srcdir$ac_dir_suffix"
+ top_srcdir="$ac_dots$ac_given_srcdir" ;;
+ esac
+
+
+ echo creating "$ac_file"
+ rm -f "$ac_file"
+ configure_input="Generated automatically from `echo $ac_file_in|sed 's%.*/%%'` by configure."
+ case "$ac_file" in
+ *Makefile*) ac_comsub="1i\\
+# $configure_input" ;;
+ *) ac_comsub= ;;
+ esac
+
+ ac_file_inputs=`echo $ac_file_in|sed -e "s%^%$ac_given_srcdir/%" -e "s%:% $ac_given_srcdir/%g"`
+ sed -e "$ac_comsub
+s%@configure_input@%$configure_input%g
+s%@srcdir@%$srcdir%g
+s%@top_srcdir@%$top_srcdir%g
+" $ac_file_inputs | (eval "$ac_sed_cmds") > $ac_file
+fi; done
+rm -f conftest.s*
+
+# These sed commands are passed to sed as "A NAME B NAME C VALUE D", where
+# NAME is the cpp macro being defined and VALUE is the value it is being given.
+#
+# ac_d sets the value in "#define NAME VALUE" lines.
+ac_dA='s%^\([ ]*\)#\([ ]*define[ ][ ]*\)'
+ac_dB='\([ ][ ]*\)[^ ]*%\1#\2'
+ac_dC='\3'
+ac_dD='%g'
+# ac_u turns "#undef NAME" with trailing blanks into "#define NAME VALUE".
+ac_uA='s%^\([ ]*\)#\([ ]*\)undef\([ ][ ]*\)'
+ac_uB='\([ ]\)%\1#\2define\3'
+ac_uC=' '
+ac_uD='\4%g'
+# ac_e turns "#undef NAME" without trailing blanks into "#define NAME VALUE".
+ac_eA='s%^\([ ]*\)#\([ ]*\)undef\([ ][ ]*\)'
+ac_eB='$%\1#\2define\3'
+ac_eC=' '
+ac_eD='%g'
+
+if test "${CONFIG_HEADERS+set}" != set; then
+EOF
+cat >> $CONFIG_STATUS <<EOF
+ CONFIG_HEADERS="include/config.h"
+EOF
+cat >> $CONFIG_STATUS <<\EOF
+fi
+for ac_file in .. $CONFIG_HEADERS; do if test "x$ac_file" != x..; then
+ # Support "outfile[:infile[:infile...]]", defaulting infile="outfile.in".
+ case "$ac_file" in
+ *:*) ac_file_in=`echo "$ac_file"|sed 's%[^:]*:%%'`
+ ac_file=`echo "$ac_file"|sed 's%:.*%%'` ;;
+ *) ac_file_in="${ac_file}.in" ;;
+ esac
+
+ echo creating $ac_file
+
+ rm -f conftest.frag conftest.in conftest.out
+ ac_file_inputs=`echo $ac_file_in|sed -e "s%^%$ac_given_srcdir/%" -e "s%:% $ac_given_srcdir/%g"`
+ cat $ac_file_inputs > conftest.in
+
+EOF
+
+# Transform confdefs.h into a sed script conftest.vals that substitutes
+# the proper values into config.h.in to produce config.h. And first:
+# Protect against being on the right side of a sed subst in config.status.
+# Protect against being in an unquoted here document in config.status.
+rm -f conftest.vals
+cat > conftest.hdr <<\EOF
+s/[\\&%]/\\&/g
+s%[\\$`]%\\&%g
+s%#define \([A-Za-z_][A-Za-z0-9_]*\) *\(.*\)%${ac_dA}\1${ac_dB}\1${ac_dC}\2${ac_dD}%gp
+s%ac_d%ac_u%gp
+s%ac_u%ac_e%gp
+EOF
+sed -n -f conftest.hdr confdefs.h > conftest.vals
+rm -f conftest.hdr
+
+# This sed command replaces #undef with comments. This is necessary, for
+# example, in the case of _POSIX_SOURCE, which is predefined and required
+# on some systems where configure will not decide to define it.
+cat >> conftest.vals <<\EOF
+s%^[ ]*#[ ]*undef[ ][ ]*[a-zA-Z_][a-zA-Z_0-9]*%/* & */%
+EOF
+
+# Break up conftest.vals because some shells have a limit on
+# the size of here documents, and old seds have small limits too.
+
+rm -f conftest.tail
+while :
+do
+ ac_lines=`grep -c . conftest.vals`
+ # grep -c gives empty output for an empty file on some AIX systems.
+ if test -z "$ac_lines" || test "$ac_lines" -eq 0; then break; fi
+ # Write a limited-size here document to conftest.frag.
+ echo ' cat > conftest.frag <<CEOF' >> $CONFIG_STATUS
+ sed ${ac_max_here_lines}q conftest.vals >> $CONFIG_STATUS
+ echo 'CEOF
+ sed -f conftest.frag conftest.in > conftest.out
+ rm -f conftest.in
+ mv conftest.out conftest.in
+' >> $CONFIG_STATUS
+ sed 1,${ac_max_here_lines}d conftest.vals > conftest.tail
+ rm -f conftest.vals
+ mv conftest.tail conftest.vals
+done
+rm -f conftest.vals
+
+cat >> $CONFIG_STATUS <<\EOF
+ rm -f conftest.frag conftest.h
+ echo "/* $ac_file. Generated automatically by configure. */" > conftest.h
+ cat conftest.in >> conftest.h
+ rm -f conftest.in
+ if cmp -s $ac_file conftest.h 2>/dev/null; then
+ echo "$ac_file is unchanged"
+ rm -f conftest.h
+ else
+ # Remove last slash and all that follows it. Not all systems have dirname.
+ ac_dir=`echo $ac_file|sed 's%/[^/][^/]*$%%'`
+ if test "$ac_dir" != "$ac_file" && test "$ac_dir" != .; then
+ # The file is in a subdirectory.
+ test ! -d "$ac_dir" && mkdir "$ac_dir"
+ fi
+ rm -f $ac_file
+ mv conftest.h $ac_file
+ fi
+fi; done
+
+EOF
+cat >> $CONFIG_STATUS <<EOF
+
+EOF
+cat >> $CONFIG_STATUS <<\EOF
+chmod +x support/fixscript
+
+exit 0
+EOF
+chmod +x $CONFIG_STATUS
+rm -fr confdefs* $ac_clean_files
+test "$no_create" = yes || ${CONFIG_SHELL-/bin/sh} $CONFIG_STATUS || exit 1
+
+
+cat <<EOM
+
+Please check the following files before running make, to ensure that
+everything was set correctly.
+
+ Makefile.global
+ include/config.h
+ include/paths.h
+ innfeed/innfeed.h
+
+EOM
+
+if $_PATH_PERL -e "exit((stat('$tmpdir'))[2] & 2)" > /dev/null ; then
+ :
+else
+ cat <<EOM
+
+WARNING WARNING WARNING WARNING WARNING WARNING WARNING WARNING WARNING WARNING
+
+ The temporary directory you have configured is world-writeable. It is
+ currently set to $tmpdir.
+
+ This directory is used by INN for temporary files and should only be
+ writeable by the news user. INN does not go to great lengths to prevent
+ symlink attacks and the like because it assumes this directory is not
+ world-writeable. By configuring INN in this fashion, you may be
+ introducing a locally exploitable security hole.
+
+ It is STRONGLY recommended that you use a temporary directory reserved for
+ INN's exclusive use, one which is not world-writeable. You can do this
+ either with --with-tmp-dir or by setting --prefix to something other than
+ /usr or /.
+
+EOM
+fi
--- /dev/null
+dnl Process this file with autoconf to produce a configure script.
+dnl $Id: configure.in 7811 2008-04-30 07:06:58Z iulius $
+dnl
+dnl Please try to follow GNU conventions and autoconf manual conventions as
+dnl much as possible in this file so that any macros we develop can be easily
+dnl contributed to the macro archive and possibly rolled into future versions
+dnl of autoconf so that we can stop supporting them. This means, for example,
+dnl that code excerpts should probably follow the GNU coding standards rather
+dnl than INN's.
+dnl
+dnl The macro archive is at <http://www.gnu.org/software/ac-archive/>. Due to
+dnl the submission format and significant changes to autoconf's internal
+dnl architecture and building-block macros, I'm waiting until INN is switched
+dnl to autoconf 2.52 or later and we can convert this file into a bunch of
+dnl separate files before submitting macros to that archive.
+dnl
+dnl If a check is any way non-trivial, please package it up in a macro with
+dnl AC_DEFUN. This will allow us to easily break up this (far too long) file
+dnl into a directory full of .m4 files for particular checks once we switch to
+dnl autoconf 2.52 or later. Please also put any long code blocks into a
+dnl separate macro rather than in-line in the test macro; this will make
+dnl quoting issues much easier. See the existing tests for details on how to
+dnl do this.
+dnl
+dnl Try to do as much with AC_DEFINE and as little with AC_SUBST as is
+dnl reasonable; obviously, makefile things like library paths and so forth and
+dnl paths to programs have to use AC_SUBST, but any compile-time parameters
+dnl are easier to handle with AC_DEFINE. (And AC_SUBST is slower.)
+dnl
+dnl And remember: If you don't have any alternative available if your check
+dnl for something fails, and there's no purpose served in aborting configure
+dnl instead of the compile if what you're checking for is missing, don't
+dnl bother checking for it. Compile-time errors often produce a lot more
+dnl useful information for someone debugging a problem than configure-time
+dnl errors.
+
+AC_REVISION($Revision: 7811 $)dnl
+AC_PREREQ(2.13)
+AC_INIT(Makefile.global.in)
+AC_CONFIG_AUX_DIR(support)
+AC_PREFIX_DEFAULT(/usr/local/news)
+
+dnl Make sure $prefix is set so that we can use it internally.
+test x"$prefix" = xNONE && prefix="$ac_default_prefix"
+
+dnl Linking against in-tree libraries need to know the current directory (the
+dnl top of the build directory, not the source directory).
+builddir=`pwd`
+AC_SUBST(builddir)
+
+dnl Earlier versions of INN used --with-largefiles, which was the wrong flag
+dnl from the perspective of what --with and --enable are supposed to mean.
+dnl Catch the old usage and error out.
+if test x"$with_largefiles" != x ; then
+ AC_MSG_ERROR([Use --enable-largefiles instead of --with-largefiles])
+fi
+
+dnl Used to check whether -o can be provided with -c with the chosen
+dnl compiler. We need this if we're not using libtool so that object files
+dnl can be built in subdirectories. This macro is stolen shamelessly from
+dnl the libtool macros; there's a better version in Autoconf that we should
+dnl eventually use that tests twice.
+AC_DEFUN([INN_PROG_CC_C_O],
+[AC_REQUIRE([AC_OBJEXT])
+AC_MSG_CHECKING([if $CC supports -c -o file.$ac_objext])
+AC_CACHE_VAL([inn_cv_compiler_c_o],
+[rm -f -r conftest 2>/dev/null
+mkdir conftest
+cd conftest
+echo "int some_variable = 0;" > conftest.$ac_ext
+mkdir out
+# According to Tom Tromey, Ian Lance Taylor reported there are C compilers
+# that will create temporary files in the current directory regardless of
+# the output directory. Thus, making CWD read-only will cause this test
+# to fail, enabling locking or at least warning the user not to do parallel
+# builds.
+chmod -w .
+save_CFLAGS="$CFLAGS"
+CFLAGS="$CFLAGS -o out/conftest2.$ac_objext"
+compiler_c_o=no
+if { (eval $ac_compile) 2> out/conftest.err; } \
+ && test -s out/conftest2.$ac_objext; then
+ # The compiler can only warn and ignore the option if not recognized
+ # So say no if there are warnings
+ if test -s out/conftest.err; then
+ inn_cv_compiler_c_o=no
+ else
+ inn_cv_compiler_c_o=yes
+ fi
+else
+ # Append any errors to the config.log.
+ cat out/conftest.err 1>&AC_FD_CC
+ inn_cv_compiler_c_o=no
+fi
+CFLAGS="$save_CFLAGS"
+chmod u+w .
+rm -f conftest* out/*
+rmdir out
+cd ..
+rmdir conftest
+rm -f -r conftest 2>/dev/null])
+compiler_c_o=$inn_cv_compiler_c_o
+AC_MSG_RESULT([$compiler_c_o])])
+
+dnl A few tests need to happen before any of the libtool tests in order to
+dnl avoid error messages. We therefore lift them up to the top of the file.
+AC_PROG_CC
+AC_AIX
+AC_ISC_POSIX
+INN_PROG_CC_C_O
+
+dnl Check to see if the user wants to use libtool. We only invoke the libtool
+dnl setup macros if they do. Keep this call together with the libtool setup
+dnl so that the arguments to configure will be together in configure --help.
+inn_use_libtool=no
+AC_ARG_ENABLE(libtool,
+ [ --enable-libtool Use libtool for lib generation [default=no]],
+ if test "$enableval" = yes ; then
+ inn_use_libtool=yes
+ fi)
+if test x"$inn_use_libtool" = xyes ; then
+ AC_PROG_LIBTOOL
+ EXTLIB='la'
+ EXTOBJ='lo'
+ LIBTOOL='$(top)/libtool'
+ LIBTOOLCC='$(top)/libtool --mode=compile'
+ LIBTOOLLD='$(top)/libtool --mode=link'
+ CCOUTPUT='-c -o $@ $<'
+else
+ AC_CANONICAL_HOST
+ EXTLIB='a'
+ EXTOBJ='o'
+ LIBTOOL=''
+ LIBTOOLCC=''
+ LIBTOOLLD=''
+ if test x"$compiler_c_o" = xyes ; then
+ CCOUTPUT='-c -o $@ $<'
+ else
+ CCOUTPUT='-c $< && if test x"$(@F)" != x"$@" ; then mv $(@F) $@ ; fi'
+ fi
+ AC_SUBST(LIBTOOL)
+fi
+AC_SUBST(EXTLIB)
+AC_SUBST(EXTOBJ)
+AC_SUBST(LIBTOOLCC)
+AC_SUBST(LIBTOOLLD)
+AC_SUBST(CCOUTPUT)
+
+dnl INN has quite a few more configurable paths than autoconf supports by
+dnl default. For right now, those additional paths are configured with
+dnl --with-*-dir options. This is the generic macro for those arguments; it
+dnl takes the name of the directory, the path relative to $prefix if none
+dnl given to configure, the variable to set, and the help string.
+AC_DEFUN([INN_ARG_DIR],
+[AC_ARG_WITH([$1-dir], [$4], [$3=$with_$1_dir], [$3=$prefix/$2])
+AC_SUBST($3)])
+
+dnl And here are all the paths.
+dnl
+dnl FIXME: We should honor bindir, libdir, includedir, and mandir at the
+dnl least, and we should use libdir over --with-lib-dir.
+INN_ARG_DIR(control, bin/control, CONTROLDIR,
+ [ --with-control-dir=PATH Path for control programs [PREFIX/bin/control]])
+INN_ARG_DIR(db, db, DBDIR,
+ [ --with-db-dir=PATH Path for news database files [PREFIX/db]])
+INN_ARG_DIR(doc, doc, DOCDIR,
+ [ --with-doc-dir=PATH Path for news documentation [PREFIX/doc]])
+INN_ARG_DIR(etc, etc, ETCDIR,
+ [ --with-etc-dir=PATH Path for news config files [PREFIX/etc]])
+INN_ARG_DIR(filter, bin/filter, FILTERDIR,
+ [ --with-filter-dir=PATH Path for embedded filters [PREFIX/bin/filter]])
+INN_ARG_DIR(lib, lib, LIBDIR,
+ [ --with-lib-dir=PATH Path for news library files [PREFIX/lib]])
+INN_ARG_DIR(log, log, LOGDIR,
+ [ --with-log-dir=PATH Path for news logs [PREFIX/log]])
+INN_ARG_DIR(run, run, RUNDIR,
+ [ --with-run-dir=PATH Path for news PID/runtime files [PREFIX/run]])
+INN_ARG_DIR(spool, spool, SPOOLDIR,
+ [ --with-spool-dir=PATH Path for news storage [PREFIX/spool]])
+INN_ARG_DIR(tmp, tmp, tmpdir,
+ [ --with-tmp-dir=PATH Path for temporary files [PREFIX/tmp]])
+
+dnl This is actually given to AC_SUBST later on when we check whether the
+dnl system has the LOG_NEWS facility.
+AC_ARG_WITH(syslog-facility,
+[ --with-syslog-facility=LOG_FAC Syslog facility [LOG_NEWS or LOG_LOCAL1]],
+ SYSLOG_FACILITY=$with_syslog_facility,
+ SYSLOG_FACILITY=none)
+
+dnl INN allows the user and group INN will run as to be specified, as well as
+dnl the user to receive nightly reports and the like. These are all fairly
+dnl similar, so factor the commonality into this macro. Takes the name of
+dnl what we're looking for, the default, the variable to set, the help string,
+dnl and the comment for config.h.
+AC_DEFUN([INN_ARG_USER],
+[AC_ARG_WITH([news-$1], [$4], [$3=$with_news_$1], [$3=$2])
+AC_SUBST($3)
+AC_DEFINE_UNQUOTED($3, "$[$3]", [$5])])
+
+dnl And here they are.
+INN_ARG_USER(user, news, NEWSUSER,
+ [ --with-news-user=USER News user name [news]],
+ [The user that INN should run as.])
+INN_ARG_USER(group, news, NEWSGRP,
+ [ --with-news-group=GROUP News group name [news]],
+ [The group that INN should run as.])
+INN_ARG_USER(master, usenet, NEWSMASTER,
+ [ --with-news-master=USER News master (address for reports) [usenet]],
+ [The user who gets all INN-related e-mail.])
+
+dnl INN defaults to a umask of 002 with the corresponding directory and file
+dnl permissions, mostly for historical reasons. If the user sets the umask to
+dnl something else, change all of the permissions.
+NEWSUMASK=02
+FILEMODE=0664
+DIRMODE=0775
+RUNDIRMODE=0770
+AC_ARG_WITH(news-umask,
+ [ --with-news-umask=UMASK umask for news files [002]],
+ with_news_umask=`echo "$with_news_umask" | sed 's/^0*//'`
+ if test "x$with_news_umask" = x22 ; then
+ NEWSUMASK=022
+ FILEMODE=0644
+ DIRMODE=0755
+ RUNDIRMODE=0750
+ else
+ if test "x$with_news_umask" != x2 ; then
+ AC_MSG_ERROR(Valid umasks are 02 or 022)
+ fi
+ fi)
+AC_SUBST(NEWSUMASK)
+AC_SUBST(FILEMODE)
+AC_SUBST(DIRMODE)
+AC_SUBST(RUNDIRMODE)
+AC_DEFINE_UNQUOTED(ARTFILE_MODE, $FILEMODE,
+ [Mode that incoming articles are created with.])
+AC_DEFINE_UNQUOTED(BATCHFILE_MODE, $FILEMODE,
+ [Mode that batch files are created with.])
+AC_DEFINE_UNQUOTED(GROUPDIR_MODE, $DIRMODE,
+ [Mode that directories are created with.])
+AC_DEFINE_UNQUOTED(NEWSUMASK, $NEWSUMASK,
+ [The umask used by all INN programs.])
+
+dnl inews used to be installed setgid, but may not be secure. Only do this if
+dnl it's explicitly requested at configure time.
+INEWSMODE=0550
+AC_ARG_ENABLE(setgid-inews,
+ [ --enable-setgid-inews Install inews setgid],
+ if test "x$enableval" = xyes ; then
+ INEWSMODE=02555
+ fi)
+AC_SUBST(INEWSMODE)
+
+dnl rnews used to be installed setuid root so that it could be run by the uucp
+dnl user for incoming batches, but this isn't necessary unless you're using
+dnl UUCP (which most people aren't) and only setuid news is required. Only do
+dnl this if it's explicitly requested at configure time.
+RNEWSGRP=$NEWSGRP
+RNEWSMODE=0500
+AC_ARG_ENABLE(uucp-rnews,
+ [ --enable-uucp-rnews Install rnews setuid, group uucp],
+ if test "x$enableval" = xyes ; then
+ RNEWSGRP=uucp
+ RNEWSMODE=04550
+ fi)
+AC_SUBST(RNEWSGRP)
+AC_SUBST(RNEWSMODE)
+
+dnl Choose the log compression method; the argument should not be a full path,
+dnl just the name of the compression type.
+AC_ARG_WITH(log-compress,
+ [ --with-log-compress=METHOD Log compression method [gzip]],
+ LOG_COMPRESS=$with_log_compress,
+ LOG_COMPRESS=gzip)
+case "$LOG_COMPRESS" in
+bzip2) LOG_COMPRESSEXT=".bz2" ;;
+gzip) LOG_COMPRESSEXT=".gz" ;;
+*) LOG_COMPRESSEXT=".Z" ;;
+esac
+AC_SUBST(LOG_COMPRESS)
+AC_SUBST(LOG_COMPRESSEXT)
+
+dnl inndstart by default only allows ports 119 and 433 below 1024; if the user
+dnl wants to use some other port as well, they must use this option.
+AC_ARG_WITH(innd-port,
+ [ --with-innd-port=PORT Additional low-numbered port for inndstart],
+ AC_DEFINE_UNQUOTED(INND_PORT, $with_innd_port,
+ [Additional valid low-numbered port for inndstart.]))
+
+dnl By default, we omit all IPv6 support. This option enables it.
+AC_ARG_ENABLE(ipv6,
+ [ --enable-ipv6 Enable IPv6 support],
+ if test "x$enableval" = xyes ; then
+ inn_enable_ipv6_tests=yes
+ AC_DEFINE(HAVE_INET6, 1, [Define to enable IPv6 support.])
+ fi)
+
+dnl Maximum number of sockets that can be listened on.
+AC_ARG_WITH(max-sockets,
+ [ --with-max-sockets=N Maximum number of listening sockets in innd],,
+ [with_max_sockets=15])
+AC_DEFINE_UNQUOTED(MAX_SOCKETS, $with_max_sockets,
+ [Maximum number of sockets that innd can listen on.])
+
+dnl This will eventually be a runtime option rather than a compile-time
+dnl option.
+AC_ARG_ENABLE(tagged-hash,
+ [ --enable-tagged-hash Use tagged hash table for history],
+ if test "x$enableval" = xyes ; then
+ DO_DBZ_TAGGED_HASH=DO
+ AC_DEFINE(DO_TAGGED_HASH, 1,
+ [Define to use tagged hash for the history file.])
+ else
+ DO_DBZ_TAGGED_HASH=DONT
+ fi)
+AC_SUBST(DO_DBZ_TAGGED_HASH)
+
+dnl Whether to enable the keyword generation code in innd. Use of this code
+dnl requires a regular expression library, which is checked for later on.
+inn_enable_keywords=0
+AC_ARG_ENABLE(keywords,
+ [ --enable-keywords Automatic keyword generation support],
+ if test x"$enableval" = xyes ; then
+ inn_enable_keywords=1
+ fi)
+AC_DEFINE_UNQUOTED(DO_KEYWORDS, $inn_enable_keywords,
+ [Define to 1 to compile in support for keyword generation code.])
+
+dnl Whether to use the OS flags to enable large file support. Ideally this
+dnl should just always be turned on if possible and the various parts of INN
+dnl that read off_t's from disk should adjust somehow to the size, but INN
+dnl isn't there yet. Currently tagged hash doesn't work with large file
+dnl support due to assumptions about the size of off_t.
+AC_ARG_ENABLE(largefiles,
+ [ --enable-largefiles Support for files larger than 2GB [default=no]],
+ [case "${enableval}" in
+ yes) inn_enable_largefiles=yes
+ if test x"$DO_DBZ_TAGGED_HASH" = xDO ; then
+AC_MSG_ERROR([--enable-tagged-hash conflicts with --enable-largefiles.])
+ fi ;;
+ no) inn_enable_largefiles=no ;;
+ *) AC_MSG_ERROR(invalid argument to --enable-largefiles) ;;
+ esac])
+
+dnl Override the automatically detected path to sendmail. Used later on.
+AC_ARG_WITH(sendmail,
+ [ --with-sendmail=PATH Path to sendmail],
+ SENDMAIL=$with_sendmail)
+
+dnl Specify the path to the Kerberos libraries if the user wants to build
+dnl auth_krb5. Note that we don't search far and wide for the libraries if
+dnl the user doesn't specify the path.
+AC_ARG_WITH(kerberos,
+ [ --with-kerberos=PATH Path to Kerberos v5 (for auth_krb5)],
+ [if test x"$with_kerberos" != xno ; then
+ KRB5_LDFLAGS="-L$with_kerberos/lib"
+ KRB5_INC="-I$with_kerberos/include"
+ fi])
+
+dnl Checks for embedded interpretors.
+dnl
+dnl FIXME: These should ideally be combined with the later logic to
+dnl determine the version, determine the compiler and linker flags, etc.
+AC_ARG_WITH(perl,
+ [ --with-perl Embedded Perl script support [default=no]],
+ [case "${withval}" in
+ yes) DO_PERL=DO
+ AC_DEFINE(DO_PERL, 1, [Define to compile in Perl script support.])
+ ;;
+ no) DO_PERL=DONT ;;
+ *) AC_MSG_ERROR(invalid argument to --with-perl) ;;
+ esac],
+ DO_PERL=DONT)
+
+AC_ARG_WITH(python,
+ [ --with-python Embedded Python module support [default=no]],
+ [case "${withval}" in
+ yes) DO_PYTHON=define
+ AC_DEFINE(DO_PYTHON, 1,
+ [Define to compile in Python module support.])
+ ;;
+ no) DO_PYTHON=DONT ;;
+ *) AC_MSG_ERROR(invalid argument to --with-python) ;;
+ esac],
+ DO_PYTHON=DONT)
+
+dnl Set some configuration file defaults from the machine hostname.
+HOSTNAME=`hostname 2> /dev/null || uname -n`
+AC_SUBST(HOSTNAME)
+
+dnl Checks for programs.
+AC_PROG_GCC_TRADITIONAL
+AC_PROG_LEX
+AC_PROG_MAKE_SET
+AC_PROG_RANLIB
+AC_PROG_YACC
+
+dnl On MacOS X Server, -traditional-cpp is needed for gcc for compiling as
+dnl well as preprocessing according to Miro Jurisic <meeroh@meeroh.org>.
+case "$CPP" in
+*-traditional-cpp*)
+ CFLAGS="-traditional-cpp $CFLAGS"
+ ;;
+esac
+
+case "$host" in
+
+dnl HP-UX's native compiler needs a special flag to turn on ANSI, and needs
+dnl -g on link as well as compile for debugging to work.
+*hpux*)
+ if test x"$GCC" != xyes ; then
+ dnl Need flag to turn on ANSI.
+ CFLAGS="$CFLAGS -Ae"
+
+ dnl Need -g on link command for debug to work properly.
+ case "$CFLAGS" in
+ *-g*)
+ LDFLAGS="$LDFLAGS -g"
+ ;;
+ esac
+ fi
+ ;;
+
+dnl OSX needs '-multiply_defined suppress'
+*darwin*)
+ LDFLAGS="$LDFLAGS -multiply_defined suppress"
+ ;;
+
+dnl From Boyd Gerber <gerberb@zenez.com>, needed in some cases to compile
+dnl the bison-generated parser for innfeed.conf.
+*UnixWare*|*unixware*|*-sco3*)
+ if test x"$GCC" != xyes ; then
+ CFLAGS="$CFLAGS -Kalloca"
+ fi
+esac
+
+dnl Checks for pathnames.
+
+dnl See if we have ctags; if so, set CTAGS to its full path plus the -t -w
+dnl options. Otherwise, set CTAGS to echo.
+AC_PATH_PROG(CTAGS, ctags, echo)
+if test x"$CTAGS" != xecho ; then
+ CTAGS="$CTAGS -t -w"
+fi
+
+dnl Use INN_PATH_PROG if it's an error not to find a program.
+AC_DEFUN([INN_ENSURE_PATH_PROG],
+[AC_PATH_PROG($1, $2)
+if test -z "${$1}" ; then
+ AC_MSG_ERROR($2 was not found in path and is required)
+fi])
+
+INN_ENSURE_PATH_PROG(_PATH_AWK,awk)
+INN_ENSURE_PATH_PROG(_PATH_EGREP,egrep)
+INN_ENSURE_PATH_PROG(_PATH_PERL,perl)
+INN_ENSURE_PATH_PROG(_PATH_SH,sh)
+INN_ENSURE_PATH_PROG(_PATH_SED,sed)
+INN_ENSURE_PATH_PROG(_PATH_SORT,sort)
+AC_PATH_PROGS(_PATH_UUX,uux,uux)
+
+dnl Check for a required version of Perl. The separate shell variable and
+dnl the changequotes are necessary for autoconf 2.13; autoconf 2.50 will
+dnl provide a different interface that will allow this to work correctly.
+changequote(<<,>>)dnl
+inn_perl_command='print $]'
+changequote([,])dnl
+AC_DEFUN([INN_PERL_VERSION],
+[AC_CACHE_CHECK(for Perl version, inn_cv_perl_version,
+[if $_PATH_PERL -e 'require $1;' > /dev/null 2>&1 ; then
+ inn_cv_perl_version=`$_PATH_PERL -e "$inn_perl_command"`
+else
+ AC_MSG_ERROR(Perl $1 or greater is required)
+fi])])
+
+dnl Embedded Perl requires 5.004. controlchan requires 5.004_03. Other
+dnl things may work with 5.003, but make 5.004_03 the minimum level; anyone
+dnl should really have at least that these days.
+INN_PERL_VERSION(5.004_03)
+
+dnl Look for PGP 5.0's pgpv, then pgp, then pgpgpg (not sure why anyone would
+dnl have pgpgpg and not gpgv, but it doesn't hurt). Separately look for
+dnl GnuPG (which we prefer).
+pgpverify=true
+AC_PATH_PROGS(PATH_GPGV, gpgv)
+AC_PATH_PROGS(_PATH_PGP, pgpv pgp pgpgpg)
+if test -z "$_PATH_PGP" && test -z "$PATH_GPGV" ; then
+ pgpverify=false
+fi
+AC_SUBST(pgpverify)
+
+dnl Look for a program that takes an ftp URL as a command line argument and
+dnl retrieves the file to the current directory. Shame we can't also use
+dnl lynx -source; it only writes to stdout. ncftp as of version 3 doesn't
+dnl support this any more (it comes with ncftpget instead), but if someone
+dnl has ncftp and not ncftpget they have an earlier version.
+AC_PATH_PROGS(GETFTP, wget ncftpget ncftp, $prefix/bin/simpleftp)
+
+dnl Look for both compress and gzip, since the UUCP batching scripts require
+dnl both. If we're using a log compression method other than compress or
+dnl gzip, check for it too and make sure whatever log compressor we're using
+dnl exists. If we don't find compress or gzip for the UUCP scripts, just
+dnl use the bare program names in the hope that the path will be better at
+dnl the time the script runs (or that the script will never run).
+case "$LOG_COMPRESS" in
+compress|gzip) ;;
+*) INN_ENSURE_PATH_PROG(LOG_COMPRESS, "$LOG_COMPRESS")
+esac
+AC_PATH_PROG(COMPRESS, compress, compress)
+if test x"$LOG_COMPRESS" = xcompress ; then
+ if test x"$COMPRESS" = xcompress ; then
+ AC_MSG_ERROR(compress not found but specified for log compression)
+ fi
+ LOG_COMPRESS="$COMPRESS"
+fi
+AC_PATH_PROG(GZIP, gzip, gzip)
+if test x"$LOG_COMPRESS" = xgzip ; then
+ if test x"$GZIP" = xgzip ; then
+ AC_MSG_ERROR(gzip not found but specified for log compression)
+ fi
+ LOG_COMPRESS="$GZIP"
+fi
+
+dnl Figure out what program to use to uncompress .Z files. On systems that
+dnl have gzip but don't have compress, we can use gzip for this purpose and
+dnl should rather than hoping compres will be found at runtime.
+if test x"$COMPRESS" = xcompress && test x"$GZIP" != xgzip ; then
+ UNCOMPRESS="$GZIP -d"
+else
+ UNCOMPRESS="$COMPRESS -d"
+fi
+AC_SUBST(UNCOMPRESS)
+
+dnl Search for sendmail, checking the path first and then some common
+dnl locations. If --with-sendmail was given, that path overrides.
+if test "${with_sendmail+set}" = set ; then
+ AC_MSG_CHECKING(for sendmail)
+ AC_MSG_RESULT($SENDMAIL)
+else
+ AC_PATH_PROG(SENDMAIL, sendmail, , "/usr/sbin:/usr/lib")
+ if test -z "$SENDMAIL" ; then
+ AC_MSG_ERROR(sendmail not found, re-run with --with-sendmail)
+ fi
+fi
+
+dnl FIXME: innshellvars* wants to know if we have uustat, send-uucp expects
+dnl it to be in the old subst DO/DONT format. Should take a path.
+AC_CHECK_PROG(HAVE_UUSTAT, uustat, DO, DONT)
+AC_SUBST(HAVE_UUSTAT)
+
+dnl If we're compiling with Python support, make sure Python is available.
+if test x"$DO_PYTHON" = xdefine ; then
+ INN_ENSURE_PATH_PROG(_PATH_PYTHON, python)
+fi
+
+dnl Search for a particular library, and if found, add that library to the
+dnl specified variable (the third argument) and run the commands given in the
+dnl fourth argument, if any. This is for libraries we don't want to pollute
+dnl LIBS with.
+AC_DEFUN([INN_SEARCH_AUX_LIBS],
+[inn_save_LIBS=$LIBS
+LIBS=${$3}
+AC_SEARCH_LIBS($1, $2,
+ [$3=$LIBS
+ $4], $5, $6)
+LIBS=$inn_save_LIBS
+AC_SUBST($3)])
+
+dnl Checks for libraries. Use AC_SEARCH_LIBS where possible to avoid
+dnl adding libraries when the function is found in libc. In several
+dnl cases, we explicitly just add the library to LIBS on success rather
+dnl than using default actions so as not to clutter config.h with defines
+dnl we never use.
+
+dnl Check for setproctitle in libc first, then libutil if not found there.
+dnl We have a replacement function if we can't find it, and then we also need
+dnl to check for pstat.
+AC_SEARCH_LIBS(setproctitle, util,
+ [AC_DEFINE(HAVE_SETPROCTITLE, 1,
+ [Define if you have the setproctitle function.])],
+ [LIBOBJS="$LIBOBJS setproctitle.o"
+ AC_CHECK_FUNCS(pstat)])
+
+dnl The rat's nest of networking libraries. The common cases are not to
+dnl need any extra libraries, or to need -lsocket -lnsl. We need to avoid
+dnl linking with libnsl unless we need it, though, since on some OSes where
+dnl it isn't necessary it will totally break networking. Unisys also
+dnl includes gethostbyname in libsocket but needs libnsl for socket().
+AC_SEARCH_LIBS(gethostbyname, nsl)
+AC_SEARCH_LIBS(socket, socket, ,
+ [AC_CHECK_LIB(nsl, socket, LIBS="$LIBS -lsocket -lnsl", , -lsocket)])
+
+dnl Check for inet_aton. We have our own, but on Solaris the version in
+dnl libresolv is more lenient in ways that Solaris's internal DNS resolution
+dnl code requires, so if we use our own *and* link with libresolv (which may
+dnl be forced by Perl) DNS resolution fails.
+AC_SEARCH_LIBS(inet_aton, resolv)
+
+dnl Search for various additional libraries used by portions of INN.
+INN_SEARCH_AUX_LIBS(crypt, crypt, CRYPT_LIB)
+INN_SEARCH_AUX_LIBS(getspnam, shadow, SHADOW_LIB)
+
+dnl IRIX has a PAM library with the right symbols but no header files suitable
+dnl for use with it, so we have to check the header files first and then only
+dnl if one is found do we check for the library.
+inn_check_pam=1
+AC_CHECK_HEADERS([pam/pam_appl.h], ,
+ [AC_CHECK_HEADER([security/pam_appl.h], , [inn_check_pam=0])])
+if test x"$inn_check_pam" = x1; then
+ INN_SEARCH_AUX_LIBS([pam_start], [pam], [PAM_LIB],
+ [AC_DEFINE([HAVE_PAM], 1, [Define if you have PAM.])])
+fi
+
+dnl If keyword generation support was requested, check for the appropriate
+dnl libraries.
+if test x"$inn_enable_keywords" = x1 ; then
+ INN_SEARCH_AUX_LIBS(regexec, regex, REGEX_LIB, ,
+ [AC_MSG_ERROR(no usable regular expression library found)])
+fi
+
+dnl Check for whether the user wants to compile with BerkeleyDB, and if so
+dnl what the path to the various components of it is.
+AC_DEFUN([INN_LIB_BERKELEYDB],
+[AC_ARG_WITH(berkeleydb,
+ [ --with-berkeleydb[=PATH] Enable BerkeleyDB (for ovdb overview method)],
+ BERKELEY_DB_DIR=$with_berkeleydb,
+ BERKELEY_DB_DIR=no)
+AC_MSG_CHECKING(if BerkeleyDB is desired)
+if test x"$BERKELEY_DB_DIR" = xno ; then
+ AC_MSG_RESULT(no)
+ BERKELEY_DB_LDFLAGS=
+ BERKELEY_DB_CFLAGS=
+ BERKELEY_DB_LIB=
+else
+ AC_MSG_RESULT(yes)
+ AC_MSG_CHECKING(for BerkeleyDB location)
+ if test x"$BERKELEY_DB_DIR" = xyes ; then
+ for v in BerkeleyDB BerkeleyDB.3.0 BerkeleyDB.3.1 BerkeleyDB.3.2 \
+ BerkeleyDB.3.3 BerkeleyDB.4.0 BerkeleyDB.4.1 BerkeleyDB.4.2 \
+ BerkeleyDB.4.3 BerkeleyDB.4.4 BerkeleyDB.4.5 BerkeleyDB.4.6; do
+ for d in $prefix /usr/local /opt /usr ; do
+ if test -d "$d/$v" ; then
+ BERKELEY_DB_DIR="$d/$v"
+ break
+ fi
+ done
+ done
+ fi
+ if test x"$BERKELEY_DB_DIR" = xyes ; then
+ for v in db46 db45 db44 db43 db42 db41 db4 db3 db2 ; do
+ if test -d "/usr/local/include/$v" ; then
+ BERKELEY_DB_LDFLAGS="-L/usr/local/lib"
+ BERKELEY_DB_CFLAGS="-I/usr/local/include/$v"
+ BERKELEY_DB_LIB="-l$v"
+ AC_MSG_RESULT(FreeBSD locations)
+ break
+ fi
+ done
+ if test x"$BERKELEY_DB_LIB" = x ; then
+ for v in db44 db43 db42 db41 db4 db3 db2 ; do
+ if test -d "/usr/include/$v" ; then
+ BERKELEY_DB_CFLAGS="-I/usr/include/$v"
+ BERKELEY_DB_LIB="-l$v"
+ AC_MSG_RESULT(Linux locations)
+ break
+ fi
+ done
+ if test x"$BERKELEY_DB_LIB" = x ; then
+ BERKELEY_DB_LIB=-ldb
+ AC_MSG_RESULT(trying -ldb)
+ fi
+ fi
+ else
+ BERKELEY_DB_LDFLAGS="-L$BERKELEY_DB_DIR/lib"
+ BERKELEY_DB_CFLAGS="-I$BERKELEY_DB_DIR/include"
+ BERKELEY_DB_LIB="-ldb"
+ AC_MSG_RESULT($BERKELEY_DB_DIR)
+ fi
+ AC_DEFINE(USE_BERKELEY_DB, 1, [Define if BerkeleyDB is available.])
+fi
+AC_SUBST(BERKELEY_DB_LDFLAGS)
+AC_SUBST(BERKELEY_DB_CFLAGS)
+AC_SUBST(BERKELEY_DB_LIB)])
+INN_LIB_BERKELEYDB
+
+dnl The dbm libraries are a special case. If we're building with BerkeleyDB,
+dnl just use the ndbm support provided by it.
+if test x"$BERKELEY_DB_LIB" != x ; then
+ DBM_INC="$BERKELEY_DB_CFLAGS"
+ DBM_LIB="$BERKELEY_DB_LDFLAGS $BERKELEY_DB_LIB"
+ AC_SUBST([DBM_LIB])
+ AC_DEFINE([HAVE_BDB_DBM], 1,
+ [Define if the BerkeleyDB dbm compatibility layer is available.])
+else
+ INN_SEARCH_AUX_LIBS([dbm_open], [ndbm dbm], [DBM_LIB],
+ [AC_DEFINE([HAVE_DBM], 1, [Define if you have a dbm library.])])
+ DBM_INC=
+fi
+AC_SUBST([DBM_INC])
+
+dnl Check for whether the user wants to compile with OpenSSL, and if so what
+dnl the path to the various components of it is.
+AC_DEFUN([INN_LIB_OPENSSL],
+[AC_ARG_WITH(openssl,
+ [ --with-openssl=PATH Enable OpenSSL (for NNTP over SSL support)],
+ OPENSSL_DIR=$with_openssl,
+ OPENSSL_DIR=no)
+AC_MSG_CHECKING(if OpenSSL is desired)
+if test x"$OPENSSL_DIR" = xno ; then
+ AC_MSG_RESULT(no)
+ SSL_BIN=
+ SSL_INC=
+ SSL_LIB=
+else
+ AC_MSG_RESULT(yes)
+ AC_MSG_CHECKING(for OpenSSL location)
+ if test x"$OPENSSL_DIR" = xyes ; then
+ for dir in $prefix /usr/local/ssl /usr/lib/ssl /usr/ssl /usr/pkg \
+ /usr/local /usr ; do
+ if test -f "$dir/include/openssl/ssl.h" ; then
+ OPENSSL_DIR=$dir
+ break
+ fi
+ done
+ fi
+ if test x"$OPENSSL_DIR" = xyes ; then
+ AC_MSG_ERROR(Can not find OpenSSL)
+ else
+ AC_MSG_RESULT($OPENSSL_DIR)
+ SSL_BIN="${OPENSSL_DIR}/bin"
+ SSL_INC="-I${OPENSSL_DIR}/include"
+
+ # This is mildly tricky. In order to satisfy most linkers, libraries
+ # have to be listed in the right order, which means that libraries
+ # with dependencies on other libraries need to be listed first. But
+ # the -L flag for the OpenSSL library directory needs to go first of
+ # all. So put the -L flag into LIBS and accumulate actual libraries
+ # into SSL_LIB, and then at the end, restore LIBS and move -L to the
+ # beginning of SSL_LIB.
+ inn_save_LIBS=$LIBS
+ LIBS="$LIBS -L${OPENSSL_DIR}/lib"
+ SSL_LIB=''
+ AC_CHECK_LIB(rsaref, RSAPublicEncrypt,
+ [AC_CHECK_LIB(RSAglue, RSAPublicEncrypt,
+ [SSL_LIB="-lRSAglue -lrsaref"], , -lrsaref)])
+ AC_CHECK_LIB(crypto, BIO_new,
+ [AC_CHECK_LIB(dl, DSO_load,
+ SSL_LIB="-lcrypto -ldl $SSL_LIB",
+ SSL_LIB="-lcrypto $SSL_LIB",
+ -lcrypto -ldl $SSL_LIB)],
+ [AC_MSG_ERROR(Can not find OpenSSL)],
+ $SSL_LIB)
+ AC_CHECK_LIB(ssl, SSL_library_init,
+ [SSL_LIB="-lssl $SSL_LIB"],
+ [AC_MSG_ERROR(Can not find OpenSSL)],
+ $SSL_LIB)
+ SSL_LIB="-L${OPENSSL_DIR}/lib $SSL_LIB"
+ LIBS=$inn_save_LIBS
+ AC_DEFINE(HAVE_SSL, 1, [Define if OpenSSL is available.])
+ fi
+fi
+AC_SUBST(SSL_BIN)
+AC_SUBST(SSL_INC)
+AC_SUBST(SSL_LIB)])
+INN_LIB_OPENSSL
+
+dnl Check for whether the user wants to compile with SASL, and if so what
+dnl the path to the various components of it is.
+AC_DEFUN([INN_LIB_SASL],
+[AC_ARG_WITH(sasl,
+ [ --with-sasl=PATH Enable SASL (for imapfeed authentication)],
+ SASL_DIR=$with_sasl,
+ SASL_DIR=no)
+AC_MSG_CHECKING(if SASL is desired)
+if test x"$SASL_DIR" = xno ; then
+ AC_MSG_RESULT(no)
+ SASL_INC=
+ SASL_LIB=
+else
+ AC_MSG_RESULT(yes)
+ AC_MSG_CHECKING(for SASL location)
+ if test x"$SASL_DIR" = xyes ; then
+ for dir in $prefix /usr/local/sasl /usr/sasl /usr/pkg /usr/local ; do
+ if test -f "$dir/include/sasl/sasl.h" ; then
+ SASL_DIR=$dir
+ break
+ fi
+ done
+ fi
+ if test x"$SASL_DIR" = xyes ; then
+ if test -f "/usr/include/sasl/sasl.h" ; then
+ SASL_INC=-I/usr/include/sasl
+ SASL_DIR=/usr
+ AC_MSG_RESULT($SASL_DIR)
+ inn_save_LIBS=$LIBS
+ AC_CHECK_LIB(sasl2, sasl_getprop,
+ [SASL_LIB=-lsasl2], [AC_MSG_ERROR(Can not find SASL)])
+ LIBS=$inn_save_LIBS
+ AC_DEFINE(HAVE_SASL, 1, [Define if SASL is available.])
+ else
+ AC_MSG_ERROR(Can not find SASL)
+ fi
+ else
+ AC_MSG_RESULT($SASL_DIR)
+ SASL_INC="-I${SASL_DIR}/include"
+
+ inn_save_LIBS=$LIBS
+ LIBS="$LIBS -L${SASL_DIR}/lib"
+ AC_CHECK_LIB(sasl2, sasl_getprop,
+ [SASL_LIB="-L${SASL_DIR}/lib -lsasl2"],
+ [AC_MSG_ERROR(Can not find SASL)],)
+ LIBS=$inn_save_LIBS
+ AC_DEFINE(HAVE_SASL, 1, [Define if SASL is available.])
+ fi
+fi
+AC_SUBST(SASL_INC)
+AC_SUBST(SASL_LIB)])
+INN_LIB_SASL
+
+dnl Check for Kerberos libraries for auth_krb5, and if found define KRB5_AUTH
+dnl to the relevant object file, which will enable compilation of it.
+if test x"$KRB5_INC" != x ; then
+ INN_SEARCH_AUX_LIBS(krb5_parse_name, krb5, KRB5_LIB,
+ [KRB5_AUTH="auth_krb5"
+ KRB5_LIB="$KRB5_LDFLAGS $KRB5_LIB -lk5crypto -lcom_err"
+ AC_SUBST(KRB5_AUTH)
+ AC_SUBST(KRB5_INC)
+ AC_CHECK_HEADERS([et/com_err.h])], , [$LIBS -lk5crypto -lcom_err])
+fi
+
+dnl Check for necessity of krb5_init_ets
+dnl OSX does not require this function
+if test x"$KRB5_INC" != x ; then
+ inn_save_LIBS=$LIBS
+ LIBS=$KRB5_LIB
+ AC_CHECK_FUNCS(krb5_init_ets)
+ LIBS=$inn_save_LIBS
+fi
+
+dnl Libraries and flags for embedded Perl. Some distributions of Linux have
+dnl Perl linked with gdbm but don't normally have gdbm installed, so on that
+dnl platform only strip -lgdbm out of the Perl libraries. Leave it in on
+dnl other platforms where it may be necessary (it isn't on Linux; Linux
+dnl shared libraries can manage their own dependencies). Strip -lc out, which
+dnl is added on some platforms, is unnecessary, and breaks compiles with
+dnl -pthread (which may be added by Python).
+dnl
+dnl If we aren't compiling with large-file support, strip out the large file
+dnl flags from inn_perl_core_flags; otherwise, innd/cc.c and lib/qio.c
+dnl disagree over the size of an off_t. Since none of our calls into Perl
+dnl use variables of type off_t, this should be harmless; in any event, it's
+dnl going to be better than the innd/cc.c breakage.
+if test x"$DO_PERL" = xDO ; then
+ AC_MSG_CHECKING(for Perl linkage)
+ inn_perl_core_path=`$_PATH_PERL -MConfig -e 'print $Config{archlibexp}'`
+ inn_perl_core_flags=`$_PATH_PERL -MExtUtils::Embed -e ccopts`
+ inn_perl_core_libs=`$_PATH_PERL -MExtUtils::Embed -e ldopts 2>&1 | tail -1`
+ inn_perl_core_libs=" $inn_perl_core_libs "
+ inn_perl_core_libs=`echo "$inn_perl_core_libs" | sed 's/ -lc / /'`
+ for i in $LIBS ; do
+ inn_perl_core_libs=`echo "$inn_perl_core_libs" | sed "s/ $i / /"`
+ done
+ case $host in
+ *-linux*)
+ inn_perl_core_libs=`echo "$inn_perl_core_libs" | sed 's/ -lgdbm / /'`
+ ;;
+ *-cygwin*)
+ inn_perl_libname=`$_PATH_PERL -MConfig -e 'print $Config{libperl}'`
+ inn_perl_libname=`echo "$inn_perl_libname" | sed 's/^lib//; s/\.a$//'`
+ inn_perl_core_libs="${inn_perl_core_libs}-l$inn_perl_libname"
+ ;;
+ esac
+ inn_perl_core_libs=`echo "$inn_perl_core_libs" | sed 's/^ *//'`
+ inn_perl_core_libs=`echo "$inn_perl_core_libs" | sed 's/ *$//'`
+ inn_perl_core_flags=" $inn_perl_core_flags "
+ if test x"$inn_enable_largefiles" != xyes ; then
+ for f in -D_LARGEFILE_SOURCE -D_FILE_OFFSET_BITS=64 -D_LARGE_FILES ; do
+ inn_perl_core_flags=`echo "$inn_perl_core_flags" | sed "s/ $f / /"`
+ done
+ fi
+ inn_perl_core_flags=`echo "$inn_perl_core_flags" | sed 's/^ *//'`
+ inn_perl_core_flags=`echo "$inn_perl_core_flags" | sed 's/ *$//'`
+ PERL_INC="$inn_perl_core_flags"
+ PERL_LIB="$inn_perl_core_libs"
+ AC_MSG_RESULT($inn_perl_core_path)
+else
+ PERL_INC=''
+ PERL_LIB=''
+fi
+AC_SUBST(PERL_INC)
+AC_SUBST(PERL_LIB)
+
+dnl Libraries and flags for embedded Python.
+dnl
+dnl FIXME: I wish there was a less icky way to get this.
+if test x"$DO_PYTHON" = xdefine ; then
+ AC_MSG_CHECKING(for Python linkage)
+ py_prefix=`$_PATH_PYTHON -c 'import sys; print sys.prefix'`
+ py_ver=`$_PATH_PYTHON -c 'import sys; print sys.version[[:3]]'`
+ py_libdir="${py_prefix}/lib/python${py_ver}"
+ PYTHON_INC="-I${py_prefix}/include/python${py_ver}"
+ py_linkage=""
+ for py_linkpart in LIBS LIBC LIBM LOCALMODLIBS BASEMODLIBS \
+ LINKFORSHARED LDFLAGS ; do
+ py_linkage="$py_linkage "`grep "^${py_linkpart}=" \
+ $py_libdir/config/Makefile \
+ | sed -e 's/^.*=//'`
+ done
+ PYTHON_LIB="-L$py_libdir/config -lpython$py_ver $py_linkage"
+ PYTHON_LIB=`echo $PYTHON_LIB | sed -e 's/[ \\t]*/ /g'`
+ AC_MSG_RESULT($py_libdir)
+else
+ PYTHON_LIB=""
+ PYTHON_INC=""
+fi
+AC_SUBST(PYTHON_LIB)
+AC_SUBST(PYTHON_INC)
+
+dnl If configuring with large file support, determine the right flags to
+dnl use based on the platform. This is the wrong approach; autoconf 2.50
+dnl comes with a macro that takes the right approach. But this works well
+dnl enough until we switch to autoconf 2.50 or later.
+if test x"$inn_enable_largefiles" = xyes ; then
+ AC_MSG_CHECKING(for largefile linkage)
+ case "$host" in
+ *-aix4.[01]*)
+ AC_MSG_RESULT(no)
+ AC_MSG_ERROR([AIX before 4.2 does not support large files])
+ ;;
+ *-aix4*)
+ AC_MSG_RESULT(ok)
+ LFS_CFLAGS="-D_LARGE_FILES"
+ LFS_LDFLAGS=""
+ LFS_LIBS=""
+ ;;
+ *-hpux*)
+ AC_MSG_RESULT(ok)
+ LFS_CFLAGS="-D_LARGEFILE_SOURCE -D_FILE_OFFSET_BITS=64"
+ LFS_LDFLAGS=""
+ LFS_LIBS=""
+ ;;
+ *-irix*)
+ AC_MSG_RESULT(no)
+ AC_MSG_ERROR([Large files not supported on this platform])
+ ;;
+ *-linux*)
+ AC_MSG_RESULT(maybe)
+ LFS_CFLAGS="-D_LARGEFILE_SOURCE -D_FILE_OFFSET_BITS=64"
+ LFS_LDFLAGS=""
+ LFS_LIBS=""
+ AC_DEFINE([_GNU_SOURCE], 1,
+ [Some versions of glibc need this defined for pread/pwrite.])
+ ;;
+ *-solaris*)
+ AC_MSG_RESULT(ok)
+ AC_PATH_PROG(GETCONF, getconf)
+ if test -z "$GETCONF" ; then
+ AC_MSG_ERROR([getconf required to configure large file support])
+ fi
+ LFS_CFLAGS=`$GETCONF LFS_CFLAGS`
+ LFS_LDFLAGS=`$GETCONF LFS_LDFLAGS`
+ LFS_LIBS=`$GETCONF LFS_LIBS`
+ ;;
+ *)
+ AC_MSG_RESULT(maybe)
+ LFS_CFLAGS="-D_LARGEFILE_SOURCE -D_FILE_OFFSET_BITS=64"
+ LFS_LDFLAGS=""
+ LFS_LIBS=""
+ ;;
+ esac
+ AC_SUBST(LFS_CFLAGS)
+ AC_SUBST(LFS_LDFLAGS)
+ AC_SUBST(LFS_LIBS)
+fi
+
+dnl Start by checking for standard C headers. AC_HEADER_STDC will set
+dnl STDC_HEADERS if stdlib.h, stdarg.h, string.h, and float.h all exist, if
+dnl memchr (and probably the other mem functions) is in string.h, if free (and
+dnl probably malloc and friends) are in stdlib.h, and if ctype.h will work on
+dnl high-bit characters.
+AC_HEADER_STDC
+
+dnl Only if that wasn't set do we need to go hunting for other headers to
+dnl include on non-ANSI systems and check for functions that all ANSI C
+dnl systems should have.
+if test x"$ac_cv_header_stdc" = xno ; then
+ AC_CHECK_HEADERS(memory.h stdlib.h strings.h)
+ AC_CHECK_FUNCS(memcpy strchr)
+fi
+
+dnl Special checks for header files.
+AC_HEADER_DIRENT
+AC_HEADER_TIME
+AC_HEADER_SYS_WAIT
+
+dnl Generic checks for header files.
+AC_CHECK_HEADERS(crypt.h inttypes.h limits.h ndbm.h pam/pam_appl.h stdbool.h \
+ stddef.h stdint.h string.h sys/bitypes.h sys/filio.h \
+ sys/loadavg.h sys/param.h sys/select.h sys/sysinfo.h \
+ sys/time.h unistd.h)
+
+dnl Some Linux systems have db1/ndbm.h instead of ndbm.h. Others have
+dnl gdbm-ndbm.h.
+if test x"$ac_cv_header_ndbm_h" = xno ; then
+ AC_CHECK_HEADERS(db1/ndbm.h gdbm-ndbm.h)
+fi
+
+dnl Check to see if herrno is declared.
+AC_DEFUN([INN_NEED_HERRNO_DECLARATION],
+[AC_CACHE_CHECK([whether h_errno must be declared], inn_cv_herrno_need_decl,
+[AC_TRY_COMPILE([#include <netdb.h>], [h_errno = 0;],
+ inn_cv_herrno_need_decl=no,
+ inn_cv_herrno_need_decl=yes)])
+if test "$inn_cv_herrno_need_decl" = yes ; then
+ AC_DEFINE([NEED_HERRNO_DECLARATION], 1,
+ [Define if <netdb.h> does not declare h_errno.])
+fi])
+INN_NEED_HERRNO_DECLARATION
+
+dnl The set of standard includes, used for checking if functions need to be
+dnl declared and for tests that need to use standard functions.
+define([_INN_HEADER_SOURCE],
+[#include <stdio.h>
+#include <sys/types.h>
+#if STDC_HEADERS
+# include <stdlib.h>
+# include <stddef.h>
+#else
+# if HAVE_STDLIB_H
+# include <stdlib.h>
+# endif
+# if !HAVE_STRCHR
+# define strchr index
+# define strrchr rindex
+# endif
+#endif
+#if HAVE_STRING_H
+# if !STDC_HEADERS && HAVE_MEMORY_H
+# include <memory.h>
+# endif
+# include <string.h>
+#else
+# if HAVE_STRINGS_H
+# include <strings.h>
+# endif
+#endif
+#if HAVE_UNISTD_H
+# include <unistd.h>
+#endif])
+
+dnl See if a given function needs a declaration by seeing if we can access a
+dnl function pointer for that function. This is done in a really ugly way
+dnl with hacks so that autoheader will pick up the defines properly; it's a
+dnl stop-gap solution until switching to autoconf 2.50.
+AC_DEFUN([INN_NEED_DECLARATION],
+[AC_MSG_CHECKING([whether $1 must be declared])
+AC_CACHE_VAL([inn_cv_decl_needed_$1],
+[AC_TRY_COMPILE(
+_INN_HEADER_SOURCE()
+[$3],
+[char *(*pfn) = (char *(*)) $1],
+[inn_cv_decl_needed_$1=no], [inn_cv_decl_needed_$1=yes])])
+if test $inn_cv_decl_needed_$1 = yes ; then
+ AC_MSG_RESULT(yes)
+ AC_DEFINE($2, 1, [Define if $1 isn't declared in the system headers.])
+else
+ AC_MSG_RESULT(no)
+fi])
+INN_NEED_DECLARATION(inet_aton, [NEED_DECLARATION_INET_ATON],
+[#include <netinet/in.h>
+#include <arpa/inet.h>])
+INN_NEED_DECLARATION(inet_ntoa, [NEED_DECLARATION_INET_NTOA],
+[#include <netinet/in.h>
+#include <arpa/inet.h>])
+INN_NEED_DECLARATION(snprintf, [NEED_DECLARATION_SNPRINTF])
+INN_NEED_DECLARATION(vsnprintf, [NEED_DECLARATION_VSNPRINTF])
+
+dnl Checks for typedefs, structures, and compiler characteristics.
+AC_C_BIGENDIAN
+AC_C_CONST
+AC_STRUCT_ST_BLKSIZE
+AC_STRUCT_TM
+AC_TYPE_SIZE_T
+AC_TYPE_UID_T
+AC_TYPE_OFF_T
+AC_TYPE_PID_T
+AC_CHECK_TYPE(ptrdiff_t, long)
+AC_CHECK_TYPE(ssize_t, int)
+
+dnl Check for ISO C99 variadic macro support in the compiler.
+AC_DEFUN([INN_C_C99_VAMACROS],
+[AC_CACHE_CHECK(for C99 variadic macros, inn_cv_c_c99_vamacros,
+[AC_TRY_COMPILE(
+[#include <stdio.h>
+#define error(...) fprintf(stderr, __VA_ARGS__)],
+[error("foo"); error("foo %d", 0); return 0;],
+[inn_cv_c_c99_vamacros=yes], [inn_cv_c_c99_vamacros=no])])
+if test $inn_cv_c_c99_vamacros = yes ; then
+ AC_DEFINE(HAVE_C99_VAMACROS, 1,
+ [Define if the compiler supports C99 variadic macros.])
+fi])
+INN_C_C99_VAMACROS
+
+dnl Check for GNU-style variadic macro support in the compiler.
+AC_DEFUN([INN_C_GNU_VAMACROS],
+[AC_CACHE_CHECK(for GNU-style variadic macros, inn_cv_c_gnu_vamacros,
+[AC_TRY_COMPILE(
+[#include <stdio.h>
+#define error(args...) fprintf(stderr, args)],
+[error("foo"); error("foo %d", 0); return 0;],
+[inn_cv_c_gnu_vamacros=yes], [inn_cv_c_gnu_vamacros=no])])
+if test $inn_cv_c_gnu_vamacros = yes ; then
+ AC_DEFINE(HAVE_GNU_VAMACROS, 1,
+ [Define if the compiler supports GNU-style variadic macros.])
+fi])
+INN_C_GNU_VAMACROS
+
+dnl Check for long long int, and define HAVE_LONG_LONG if the compiler
+dnl supports it.
+AC_DEFUN([INN_C_LONG_LONG],
+[AC_CACHE_CHECK(for long long int, inn_cv_c_long_long,
+[AC_TRY_COMPILE(, [long long int i;],
+ inn_cv_c_long_long=yes,
+ inn_cv_c_long_long=no)])
+if test $inn_cv_c_long_long = yes ; then
+ AC_DEFINE(HAVE_LONG_LONG, 1,
+ [Define if the compiler supports long long int.])
+fi])
+INN_C_LONG_LONG
+
+dnl From Paul D. Smith <psmith@baynetworks.com> on the autoconf mailing list,
+dnl this is a version of AC_CHECK_TYPE that allows specification of additional
+dnl headers. It's a modified version of the standard autoconf macro.
+AC_DEFUN([INN_CHECK_TYPE],
+[AC_REQUIRE([AC_HEADER_STDC])
+AC_MSG_CHECKING(for $1)
+AC_CACHE_VAL(ac_cv_type_$1,
+[AC_EGREP_CPP(dnl
+changequote(<<, >>)dnl
+<<(^|[^a-zA-Z_0-9])$1[^a-zA-Z_0-9]>>dnl
+changequote([, ]),
+[#include <sys/types.h>
+#ifdef STDC_HEADERS
+# include <stdlib.h>
+# include <stddef.h>
+#endif
+$3],
+ ac_cv_type_$1=yes,
+ ac_cv_type_$1=no
+)])
+AC_MSG_RESULT($ac_cv_type_$1)
+if test x"$ac_cv_type_$1" = xno ; then
+ AC_DEFINE_UNQUOTED($1, $2)
+fi])
+
+INN_CHECK_TYPE(sig_atomic_t, int, [#include <signal.h>])
+INN_CHECK_TYPE(socklen_t, int, [#include <sys/socket.h>])
+
+dnl Source used by INN_MACRO_IOV_MAX.
+define([_INN_MACRO_IOV_MAX_SOURCE],
+[[#include <sys/types.h>
+#include <stdio.h>
+#include <sys/uio.h>
+#include <errno.h>
+#include <fcntl.h>
+#ifdef HAVE_UNISTD_H
+# include <unistd.h>
+#endif
+#ifdef HAVE_LIMITS_H
+# include <limits.h>
+#endif
+
+int
+main ()
+{
+ int fd, size;
+ struct iovec array[1024];
+ char data;
+
+ FILE *f = fopen ("conftestval", "w");
+ if (!f) return 1;
+#ifdef IOV_MAX
+ fprintf (f, "set in limits.h\n");
+#else
+# ifdef UIO_MAXIOV
+ fprintf (f, "%d\n", UIO_MAXIOV);
+# else
+ fd = open ("/dev/null", O_WRONLY, 0666);
+ if (fd < 0) return 1;
+ for (size = 1; size <= 1024; size++)
+ {
+ array[size - 1].iov_base = &data;
+ array[size - 1].iov_len = sizeof data;
+ if (writev (fd, array, size) < 0)
+ {
+ if (errno != EINVAL) return 1;
+ fprintf(f, "%d\n", size - 1);
+ exit (0);
+ }
+ }
+ fprintf (f, "1024\n");
+# endif /* UIO_MAXIOV */
+#endif /* IOV_MAX */
+ return 0;
+}]])
+
+dnl Check for the number of elements in an iovec (IOV_MAX). SVr4 systems
+dnl appear to use that name for this limit (checked Solaris 2.6, IRIX 6.5, and
+dnl HP-UX 11.00). Linux doesn't have it, but instead has UIO_MAXIOV defined
+dnl in <iovec.h> included from <sys/uio.h>. The platforms that have IOV_MAX
+dnl appear to also offer it via sysconf(3), but it should be a constant for a
+dnl given implementation. Set IOV_MAX if it's not defined in <sys/uio.h> or
+dnl <limits.h>.
+AC_DEFUN([INN_MACRO_IOV_MAX],
+[AC_CACHE_CHECK([value of IOV_MAX], [inn_cv_macro_iov_max],
+[AC_TRY_RUN(_INN_MACRO_IOV_MAX_SOURCE(),
+ inn_cv_macro_iov_max=`cat conftestval`,
+ inn_cv_macro_iov_max=error, 16)
+if test x"$inn_cv_macro_iov_max" = xerror ; then
+ AC_MSG_WARN([probe failure, assuming 16])
+ inn_cv_macro_iov_max=16
+fi])
+if test x"$inn_cv_macro_iov_max" != x"set in limits.h" ; then
+ AC_DEFINE_UNQUOTED(IOV_MAX, $inn_cv_macro_iov_max,
+ [Define to the max vectors in an iovec.])
+fi])
+INN_MACRO_IOV_MAX
+
+dnl Check for SUN_LEN (size of a Unix domain socket struct, macro required
+dnl POSIX.1g but not that widespread yet).
+AC_DEFUN([INN_MACRO_SUN_LEN],
+[AC_CACHE_CHECK(for SUN_LEN, inn_cv_macro_sun_len,
+[AC_TRY_LINK(
+[#include <sys/types.h>
+#include <sys/un.h>],
+[struct sockaddr_un sun;
+int i;
+
+i = SUN_LEN(&sun);],
+ inn_cv_macro_sun_len=yes,
+ inn_cv_macro_sun_len=no)])
+if test x"$inn_cv_macro_sun_len" = xyes ; then
+ AC_DEFINE(HAVE_SUN_LEN, 1,
+ [Define if <sys/un.h> defines the SUN_LEN macro.])
+fi])
+INN_MACRO_SUN_LEN
+
+dnl BSD hosts have a tm_gmtoff element in struct tm containing the offset from
+dnl GMT/UTC for that time. This is the strongly preferred way of getting time
+dnl zone information.
+AC_DEFUN([INN_STRUCT_TM_GMTOFF],
+[AC_CACHE_CHECK(for tm_gmtoff in struct tm, inn_cv_struct_tm_gmtoff,
+[AC_TRY_LINK([#include <time.h>],
+ [struct tm t; t.tm_gmtoff = 3600],
+ inn_cv_struct_tm_gmtoff=yes,
+ inn_cv_struct_tm_gmtoff=no)])
+if test x"$inn_cv_struct_tm_gmtoff" = xyes ; then
+ AC_DEFINE([HAVE_TM_GMTOFF], 1,
+ [Define if your struct tm has a tm_gmtoff member.])
+fi])
+INN_STRUCT_TM_GMTOFF
+
+dnl BSD hosts have the name of the local time zone in struct tm, which is much
+dnl nicer to use than the tzname variable (and also potentially handles
+dnl renamings of the time zone in the past).
+AC_DEFUN([INN_STRUCT_TM_ZONE],
+[AC_CACHE_CHECK(for tm_zone in struct tm, inn_cv_struct_tm_zone,
+[AC_TRY_LINK([#include <time.h>],
+ [struct tm t; t.tm_zone = "UTC"],
+ inn_cv_struct_tm_zone=yes,
+ inn_cv_struct_tm_zone=no)])
+if test x"$inn_cv_struct_tm_zone" = xyes ; then
+ AC_DEFINE([HAVE_TM_ZONE], 1,
+ [Define if your struct tm has a tm_zone member.])
+fi])
+INN_STRUCT_TM_ZONE
+
+dnl Many System V hosts have an external variable timezone containing the
+dnl offset of local time from GMT/UTC. We can use this for the timezone
+dnl offset for current time, although it's not usable for anything else.
+dnl Unfortunately, some BSD varients have a function named timezone instead.
+dnl HP-UX has timezone but doesn't have altzone, which isn't good enough.
+AC_DEFUN([INN_VAR_TIMEZONE],
+[AC_CACHE_CHECK(for timezone variable, inn_cv_var_timezone,
+[AC_TRY_LINK([#include <time.h>], [timezone = 3600; altzone = 7200],
+ inn_cv_var_timezone=yes,
+ inn_cv_var_timezone=no)])
+if test x"$inn_cv_var_timezone" = xyes ; then
+ AC_DEFINE([HAVE_VAR_TIMEZONE], 1,
+ [Define if timezone is an external variable in <time.h>.])
+fi])
+INN_VAR_TIMEZONE
+
+dnl Many System V hosts and some BSD systems have an external variable tzname
+dnl containing the abbreviations of the main and alternate time zone. We can
+dnl use these as a reasonable approximation of the correct time zone names,
+dnl although they could be incorrect if the time zone name has changed in the
+dnl past.
+AC_DEFUN([INN_VAR_TZNAME],
+[AC_CACHE_CHECK(for tzname variable, inn_cv_var_tzname,
+[AC_TRY_LINK([#include <time.h>], [*tzname = "UTC"],
+ inn_cv_var_tzname=yes,
+ inn_cv_var_tzname=no)])
+if test x"$inn_cv_var_tzname" = xyes ; then
+ AC_DEFINE([HAVE_VAR_TZNAME], 1,
+ [Define if tzname is an external variable in <time.h>.])
+fi])
+INN_VAR_TZNAME
+
+dnl A modified version of AC_CHECK_SIZEOF that doesn't always AC_DEFINE, but
+dnl instead lets you execute shell code based on success or failure. This is
+dnl to avoid config.h clutter.
+AC_DEFUN(INN_IF_SIZEOF,
+[changequote(<<, >>)dnl
+dnl The name to #define.
+define(<<AC_TYPE_NAME>>, translit(sizeof_$1, [a-z *], [A-Z_P]))dnl
+dnl The cache variable name.
+define(<<AC_CV_NAME>>, translit(ac_cv_sizeof_$1, [ *], [_p]))dnl
+changequote([, ])dnl
+AC_MSG_CHECKING(size of $1)
+AC_CACHE_VAL(AC_CV_NAME,
+[AC_TRY_RUN([#include <stdio.h>
+main()
+{
+ FILE *f = fopen("conftestval", "w");
+ if (!f) exit(1);
+ fprintf(f, "%d\n", sizeof($1));
+ exit(0);
+}], AC_CV_NAME=`cat conftestval`, AC_CV_NAME=0,
+ifelse([$2], , , AC_CV_NAME=$2))
+])dnl
+AC_MSG_RESULT($AC_CV_NAME)
+if test x"$AC_CV_NAME" = x"$3" ; then
+ ifelse([$4], , :, [$4])
+else
+ ifelse([$5], , :, [$5])
+fi
+undefine([AC_TYPE_NAME])dnl
+undefine([AC_CV_NAME])dnl
+])
+
+dnl Find a 32 bit type, by trying likely candidates. First, check for the C9X
+dnl int32_t, then look for something else with a size of four bytes.
+INN_IF_SIZEOF(int, 4, 4, INN_INT32=int,
+ [INN_IF_SIZEOF(long, 4, 4, INN_INT32=long,
+ [INN_IF_SIZEOF(short, 2, 4, INN_INT32=short)])])
+INN_CHECK_TYPE(int32_t, $INN_INT32,
+[#ifdef HAVE_STDINT_H
+# include <stdint.h>
+#endif
+#ifdef HAVE_SYS_BITYPES_H
+# include <sys/bitypes.h>
+#endif
+])
+
+dnl Figure out the unsigned version.
+INN_CHECK_TYPE(uint32_t, unsigned $INN_INT32,
+[#ifdef HAVE_STDINT_H
+# include <stdint.h>
+#endif
+#ifdef HAVE_SYS_BITYPES_H
+# include <sys/bitypes.h>
+#endif
+])
+
+dnl Checks for library functions.
+AC_FUNC_MEMCMP
+AC_TYPE_SIGNAL
+
+dnl Source used by INN_FUNC_INET_NTOA
+define([_INN_FUNC_INET_NTOA_SOURCE],
+[#include <sys/types.h>
+#include <sys/socket.h>
+#include <netinet/in.h>
+#include <arpa/inet.h>
+#if STDC_HEADERS || HAVE_STRING_H
+# include <string.h>
+#endif
+
+int
+main ()
+{
+ struct in_addr in;
+ in.s_addr = htonl (0x7f000000L);
+ return (!strcmp (inet_ntoa (in), "127.0.0.0") ? 0 : 1);
+}])
+
+dnl Check whether inet_ntoa is present and working. Since calling inet_ntoa
+dnl involves passing small structs on the stack, present and working versions
+dnl may still not function with gcc on some platforms (such as IRIX).
+AC_DEFUN([INN_FUNC_INET_NTOA],
+[AC_CACHE_CHECK(for working inet_ntoa, inn_cv_func_inet_ntoa_works,
+[AC_TRY_RUN(_INN_FUNC_INET_NTOA_SOURCE(),
+ [inn_cv_func_inet_ntoa_works=yes],
+ [inn_cv_func_inet_ntoa_works=no],
+ [inn_cv_func_inet_ntoa_works=no])])
+if test "$inn_cv_func_inet_ntoa_works" = yes ; then
+ AC_DEFINE([HAVE_INET_NTOA], 1,
+ [Define if your system has a working inet_ntoa function.])
+else
+ LIBOBJS="$LIBOBJS inet_ntoa.${ac_objext}"
+fi])
+INN_FUNC_INET_NTOA
+
+dnl Check whether sockaddr structs have sa_len fields
+AC_DEFUN([INN_SOCKADDR_SA_LEN],
+[AC_CACHE_CHECK(whether struct sockaddr has sa_len,
+ inn_cv_struct_sockaddr_sa_len,
+ [AC_TRY_COMPILE(
+ [#include <sys/types.h>
+ #include <sys/socket.h>
+ #include <netinet/in.h>],
+ [struct sockaddr sa; int x = sa.sa_len;],
+ [inn_cv_struct_sockaddr_sa_len=yes],
+ [inn_cv_struct_sockaddr_sa_len=no])])
+if test "$inn_cv_struct_sockaddr_sa_len" = yes ; then
+ AC_DEFINE([HAVE_SOCKADDR_LEN],1,
+ [Define if your system has a sa_len field in struct sockaddr])
+fi])
+INN_SOCKADDR_SA_LEN
+
+dnl Check whether we have an SA_LEN macro available to us
+AC_DEFUN([INN_SA_LEN_MACRO],
+[AC_CACHE_CHECK(for SA_LEN(s) macro, inn_cv_sa_len_macro,
+ [AC_TRY_LINK(
+ [#include <sys/types.h>
+ #include <sys/socket.h>
+ #include <netinet/in.h>],
+ [struct sockaddr sa; int x = SA_LEN(&sa);],
+ [inn_cv_sa_len_macro=yes],
+ [inn_cv_sa_len_macro=no])])
+if test "$inn_cv_sa_len_macro" = yes ; then
+ AC_DEFINE([HAVE_SA_LEN_MACRO],1,
+ [Define if your system has a SA_LEN(s) macro])
+fi])
+INN_SA_LEN_MACRO
+
+dnl Check to see how struct sockaddr_storage members are named.
+dnl *** Called from INN_SOCKADDR_STORAGE
+AC_DEFUN([INN_2553_SS_FAMILY],
+[AC_CACHE_CHECK(for RFC 2553 style sockaddr_storage member names,
+ inn_cv_2553_ss_family,
+ [AC_TRY_COMPILE(
+ [#include <sys/types.h>
+ #include <sys/socket.h>
+ #include <netinet/in.h>],
+ [struct sockaddr_storage ss; int x=ss.ss_family;],
+ [inn_cv_2553_ss_family=no],
+ [inn_cv_2553_ss_family=yes])])
+if test "$inn_cv_2553_ss_family" = yes ; then
+ AC_DEFINE([HAVE_2553_STYLE_SS_FAMILY],1,
+ [Define if your system has sockaddr_storage.__ss_family])
+fi])
+
+dnl Check whether we have struct sockaddr_storage as defined by RFC 2553,
+dnl or whether we should define it ourselves.
+AC_DEFUN([INN_SOCKADDR_STORAGE],
+[AC_CACHE_CHECK(for struct sockaddr_storage, inn_cv_struct_sockaddr_storage,
+ [AC_TRY_COMPILE(
+ [#include <sys/types.h>
+ #include <sys/socket.h>
+ #include <netinet/in.h>],
+ [struct sockaddr_storage ss;],
+ [inn_cv_struct_sockaddr_storage=yes],
+ [inn_cv_struct_sockaddr_storage=no])])
+if test "$inn_cv_struct_sockaddr_storage" = yes ; then
+ AC_DEFINE([HAVE_SOCKADDR_STORAGE],1,
+ [Define if your system has struct sockaddr_storage])
+ INN_2553_SS_FAMILY
+fi])
+INN_SOCKADDR_STORAGE
+
+dnl Source used by INN_IN6_EQ_BROKEN
+dnl Test borrowed from a bug report by tmoestl@gmx.net for glibc
+define([_INN_IN6_EQ_BROKEN_SOURCE],
+[#include <sys/types.h>
+#include <sys/socket.h>
+#include <netinet/in.h>
+#include <arpa/inet.h>
+
+int
+main ()
+{
+ struct in6_addr a;
+ struct in6_addr b;
+
+ inet_pton(AF_INET6,"fe80::1234:5678:abcd",&a);
+ inet_pton(AF_INET6,"fe80::1234:5678:abcd",&b);
+ return IN6_ARE_ADDR_EQUAL(&a,&b) ? 0 : 1;
+}])
+
+dnl Checks whether IN6_ARE_ADDR_EQUAL macro is broken (glibc 2.1.3 is)
+dnl *** only run if we're building for IPv6 (--enable-ipv6)
+AC_DEFUN([INN_IN6_EQ_BROKEN],
+[AC_CACHE_CHECK(whether IN6_ARE_ADDR_EQUAL macro is broken,
+ inn_cv_in6_are_addr_equal_broken,
+ [AC_TRY_RUN(_INN_IN6_EQ_BROKEN_SOURCE,
+ inn_cv_in6_are_addr_equal_broken=no,
+ inn_cv_in6_are_addr_equal_broken=yes,
+ inn_cv_in6_are_addr_equal_broken=no)])
+if test "$inn_cv_in6_are_addr_equal_broken" = yes ; then
+ AC_DEFINE([HAVE_BROKEN_IN6_ARE_ADDR_EQUAL],1,
+ [Define if your IN6_ARE_ADDR_EQUAL macro is broken])
+fi])
+if test "$inn_enable_ipv6_tests" = yes ; then
+ INN_IN6_EQ_BROKEN
+fi
+
+dnl Source used by INN_FUNC_SNPRINTF.
+define([_INN_FUNC_SNPRINTF_SOURCE],
+[[#include <stdio.h>
+#include <stdarg.h>
+
+char buf[2];
+
+int
+test (char *format, ...)
+{
+ va_list args;
+ int count;
+
+ va_start (args, format);
+ count = vsnprintf (buf, sizeof buf, format, args);
+ va_end (args);
+ return count;
+}
+
+int
+main ()
+{
+ return ((test ("%s", "abcd") == 4 && buf[0] == 'a' && buf[1] == '\0'
+ && snprintf(NULL, 0, "%s", "abcd") == 4) ? 0 : 1);
+}]])
+
+dnl Check for a working snprintf. Some systems have snprintf, but it doesn't
+dnl null-terminate if the buffer isn't large enough or it returns -1 if the
+dnl string doesn't fit instead of returning the number of characters that
+dnl would have been formatted.
+AC_DEFUN([INN_FUNC_SNPRINTF],
+[AC_CACHE_CHECK(for working snprintf, inn_cv_func_snprintf_works,
+[AC_TRY_RUN(_INN_FUNC_SNPRINTF_SOURCE(),
+ [inn_cv_func_snprintf_works=yes],
+ [inn_cv_func_snprintf_works=no],
+ [inn_cv_func_snprintf_works=no])])
+if test "$inn_cv_func_snprintf_works" = yes ; then
+ AC_DEFINE([HAVE_SNPRINTF], 1,
+ [Define if your system has a working snprintf function.])
+else
+ LIBOBJS="$LIBOBJS snprintf.${ac_objext}"
+fi])
+INN_FUNC_SNPRINTF
+
+dnl Check for various other functions.
+AC_CHECK_FUNCS(atexit getloadavg getrlimit getrusage getspnam setbuffer \
+ sigaction setgroups setrlimit setsid socketpair statvfs \
+ strncasecmp strtoul symlink sysconf)
+
+dnl Find a way to get the file descriptor limit.
+if test x"$ac_cv_func_getrlimit" = xno ; then
+ AC_CHECK_FUNCS(getdtablesize ulimit, break)
+fi
+
+dnl If we don't have statvfs, gather some more information for inndf.
+if test x"$ac_cv_func_statvfs" = xno ; then
+ AC_CHECK_FUNCS(statfs)
+ AC_CHECK_HEADERS(sys/vfs.h sys/mount.h)
+fi
+
+dnl If we can't find any of the following, we have replacements for them.
+AC_REPLACE_FUNCS(fseeko ftello getpagesize hstrerror inet_aton mkstemp \
+ pread pwrite seteuid strcasecmp strerror strlcat strlcpy \
+ strspn setenv)
+
+dnl Source used by INN_TYPE_FPOS_T_LARGE.
+define([_INN_TYPE_FPOS_T_LARGE_SOURCE],
+[#include <stdio.h>
+#include <sys/types.h>
+
+int
+main ()
+{
+ fpos_t fpos = 9223372036854775807ULL;
+ off_t off;
+ off = fpos;
+ exit(off == (off_t) 9223372036854775807ULL ? 0 : 1);
+}])
+
+dnl Check whether fpos_t is 64 bits and can be assigned to an off_t. If so,
+dnl sets HAVE_LARGE_FPOS_T; this means that a missing fseeko or ftello can be
+dnl emulated usint fgetpos and fsetpos.
+AC_DEFUN([INN_TYPE_FPOS_T_LARGE],
+[AC_CACHE_CHECK(for off_t-compatible fpos_t, inn_cv_type_fpos_t_large,
+[AC_TRY_RUN(_INN_TYPE_FPOS_T_LARGE_SOURCE(),
+ [inn_cv_type_fpos_t_large=yes],
+ [inn_cv_type_fpos_t_large=no],
+ [inn_cv_type_fpos_t_large=no])
+if test "$inn_cv_type_fpos_t_large" = yes ; then
+ AC_DEFINE([HAVE_LARGE_FPOS_T], 1,
+ [Define if fpos_t is at least 64 bits and compatible with off_t.])
+fi])])
+
+dnl If replacing fseeko or ftello, see if we can use fsetpos/fgetpos.
+if test "$ac_cv_func_fseeko" = no || test "$ac_cv_func_ftello" = no ; then
+ INN_TYPE_FPOS_T_LARGE
+fi
+
+dnl Source used by INN_FUNC_MMAP.
+define([_INN_FUNC_MMAP_SOURCE],
+[_INN_HEADER_SOURCE()]
+[[#include <fcntl.h>
+#include <sys/mman.h>
+
+int
+main()
+{
+ int *data, *data2;
+ int i, fd;
+
+ /* First, make a file with some known garbage in it. Use something
+ larger than one page but still an odd page size. */
+ data = malloc (20000);
+ if (!data) return 1;
+ for (i = 0; i < 20000 / sizeof (int); i++)
+ data[i] = rand();
+ umask (0);
+ fd = creat ("conftestmmaps", 0600);
+ if (fd < 0) return 1;
+ if (write (fd, data, 20000) != 20000) return 1;
+ close (fd);
+
+ /* Next, try to mmap the file and make sure we see the same garbage. */
+ fd = open ("conftestmmaps", O_RDWR);
+ if (fd < 0) return 1;
+ data2 = mmap (0, 20000, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);
+ if (data2 == (int *) -1) return 1;
+ for (i = 0; i < 20000 / sizeof (int); i++)
+ if (data[i] != data2[i])
+ return 1;
+
+ close (fd);
+ unlink ("conftestmmaps");
+ return 0;
+}]])
+
+
+dnl This portion is similar to what AC_FUNC_MMAP does, only it tests shared,
+dnl non-fixed mmaps.
+AC_DEFUN([INN_FUNC_MMAP],
+[AC_CACHE_CHECK(for working mmap, inn_cv_func_mmap,
+[AC_TRY_RUN(_INN_FUNC_MMAP_SOURCE(),
+ inn_cv_func_mmap=yes,
+ inn_cv_func_mmap=no,
+ inn_cv_func_mmap=no)])
+if test $inn_cv_func_mmap = yes ; then
+ AC_DEFINE(HAVE_MMAP)
+fi])
+
+dnl Source used by INN_FUNC_MMAP_NEEDS_MSYNC.
+define([_INN_FUNC_MMAP_NEEDS_MSYNC_SOURCE],
+[_INN_HEADER_SOURCE()]
+[[#include <sys/types.h>
+#include <fcntl.h>
+#include <sys/mman.h>
+
+int
+main()
+{
+ int *data, *data2;
+ int i, fd;
+
+ /* First, make a file with some known garbage in it. Use something
+ larger than one page but still an odd page size. */
+ data = malloc (20000);
+ if (!data) return 1;
+ for (i = 0; i < 20000 / sizeof (int); i++)
+ data[i] = rand();
+ umask (0);
+ fd = creat ("conftestmmaps", 0600);
+ if (fd < 0) return 1;
+ if (write (fd, data, 20000) != 20000) return 1;
+ close (fd);
+
+ /* Next, try to mmap the file and make sure we see the same garbage. */
+ fd = open ("conftestmmaps", O_RDWR);
+ if (fd < 0) return 1;
+ data2 = mmap (0, 20000, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);
+ if (data2 == (int *) -1) return 1;
+
+ /* Finally, see if changes made to the mmaped region propagate back to
+ the file as seen by read (meaning that msync isn't needed). */
+ for (i = 0; i < 20000 / sizeof (int); i++)
+ data2[i]++;
+ if (read (fd, data, 20000) != 20000) return 1;
+ for (i = 0; i < 20000 / sizeof (int); i++)
+ if (data[i] != data2[i])
+ return 1;
+
+ close (fd);
+ unlink ("conftestmmapm");
+ return 0;
+}]])
+
+dnl Check whether the data read from an open file sees the changes made to an
+dnl mmaped region, or if msync has to be called for other applications to see
+dnl those changes.
+AC_DEFUN([INN_FUNC_MMAP_NEEDS_MSYNC],
+[AC_CACHE_CHECK(whether msync is needed, inn_cv_func_mmap_need_msync,
+[AC_TRY_RUN(_INN_FUNC_MMAP_NEEDS_MSYNC_SOURCE(),
+ inn_cv_func_mmap_need_msync=no,
+ inn_cv_func_mmap_need_msync=yes,
+ inn_cv_func_mmap_need_msync=yes)])
+if test $inn_cv_func_mmap_need_msync = yes ; then
+ AC_DEFINE(MMAP_NEEDS_MSYNC, 1,
+ [Define if you need to call msync for calls to read to see changes.])
+fi])
+
+dnl Source used by INN_FUNC_MMAP_SEES_WRITES.
+define([_INN_FUNC_MMAP_SEES_WRITES_SOURCE],
+[[#include <stdlib.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <fcntl.h>
+#if HAVE_UNISTD_H
+# include <unistd.h>
+#endif
+#include <sys/mman.h>
+
+/* Fractional page is probably worst case. */
+static char zbuff[1024];
+static char fname[] = "conftestw";
+
+int
+main ()
+{
+ char *map;
+ int i, fd;
+
+ fd = open (fname, O_RDWR | O_CREAT, 0660);
+ if (fd < 0) return 1;
+ unlink (fname);
+ write (fd, zbuff, sizeof (zbuff));
+ lseek (fd, 0, SEEK_SET);
+ map = mmap (0, sizeof (zbuff), PROT_READ, MAP_SHARED, fd, 0);
+ if (map == (char *) -1) return 2;
+ for (i = 0; fname[i]; i++)
+ {
+ if (write (fd, &fname[i], 1) != 1) return 3;
+ if (map[i] != fname[i]) return 4;
+ }
+ return 0;
+}]])
+
+dnl Check if an mmaped region will see writes made to the underlying file
+dnl without an intervening msync.
+AC_DEFUN([INN_FUNC_MMAP_SEES_WRITES],
+[AC_CACHE_CHECK(whether mmap sees writes, inn_cv_func_mmap_sees_writes,
+[AC_TRY_RUN(_INN_FUNC_MMAP_SEES_WRITES_SOURCE(),
+ inn_cv_func_mmap_sees_writes=yes,
+ inn_cv_func_mmap_sees_writes=no,
+ inn_cv_func_mmap_sees_writes=no)])
+if test $inn_cv_func_mmap_sees_writes = no ; then
+ AC_DEFINE(MMAP_MISSES_WRITES, 1,
+ [Define if you need to call msync after writes.])
+fi])
+
+dnl Check whether msync takes three arguments. (It takes three arguments on
+dnl Solaris and Linux, two arguments on BSDI.)
+AC_DEFUN([INN_FUNC_MSYNC_ARGS],
+[AC_CACHE_CHECK(how many arguments msync takes, inn_cv_func_msync_args,
+[AC_TRY_COMPILE(
+[#include <sys/types.h>
+#include <sys/mman.h>],
+ [char *p; int psize; msync (p, psize, MS_ASYNC);],
+ inn_cv_func_msync_args=3,
+ inn_cv_func_msync_args=2)])
+if test $inn_cv_func_msync_args = 3 ; then
+ AC_DEFINE(HAVE_MSYNC_3_ARG, 1,
+ [Define if your msync function takes three arguments.])
+fi])
+
+dnl Now that all the tests are set up, do the work of the mmap tests.
+INN_FUNC_MMAP
+if test x"$inn_cv_func_mmap" = xyes ; then
+ AC_CHECK_FUNCS(madvise)
+ INN_FUNC_MMAP_SEES_WRITES
+ INN_FUNC_MMAP_NEEDS_MSYNC
+ INN_FUNC_MSYNC_ARGS
+fi
+
+dnl If AF_UNIX is set in <sys/socket.h>, assume we have Unix domain sockets.
+AC_DEFUN([INN_SYS_UNIX_SOCKETS],
+[AC_CACHE_CHECK([for Unix domain sockets], inn_cv_sys_unix_sockets,
+[AC_EGREP_CPP(yes,
+[#include <sys/socket.h>
+#ifdef AF_UNIX
+yes
+#endif],
+ inn_cv_sys_unix_sockets=yes,
+ inn_cv_sys_unix_sockets=no)])
+if test $inn_cv_sys_unix_sockets = yes ; then
+ AC_DEFINE(HAVE_UNIX_DOMAIN_SOCKETS, 1,
+ [Define if you have unix domain sockets.])
+fi])
+INN_SYS_UNIX_SOCKETS
+
+dnl Determine the facility for syslog messages. Default to LOG_NEWS for
+dnl syslog facility if it's available, but if it's not, fall back on
+dnl LOG_LOCAL1. --with-syslog-facility may have already set this.
+AC_DEFUN([INN_LOG_FACILITY],
+[AC_MSG_CHECKING(log facility for news)
+AC_CACHE_VAL(inn_cv_log_facility,
+[AC_EGREP_CPP(yes,
+[#include <syslog.h>
+#ifdef LOG_NEWS
+yes
+#endif],
+ inn_cv_log_facility=LOG_NEWS,
+ inn_cv_log_facility=LOG_LOCAL1)])
+if test x"$SYSLOG_FACILITY" = xnone ; then
+ SYSLOG_FACILITY=$inn_cv_log_facility
+fi
+AC_MSG_RESULT($SYSLOG_FACILITY)
+AC_DEFINE_UNQUOTED(LOG_INN_SERVER, $SYSLOG_FACILITY,
+ [Syslog facility to use for innd logs.])
+AC_DEFINE_UNQUOTED(LOG_INN_PROG, $SYSLOG_FACILITY,
+ [Syslog facility to use for INN program logs.])
+AC_SUBST(SYSLOG_FACILITY)])
+INN_LOG_FACILITY
+
+dnl Clean up our LIBS, just for grins.
+LIBS=`echo "$LIBS" | sed 's/^ *//' | sed 's/ */ /g' | sed 's/ *$//'`
+
+AC_CONFIG_HEADER(include/config.h)
+AC_OUTPUT(
+ Makefile.global
+ include/paths.h
+ samples/inn.conf
+ samples/innreport.conf
+ samples/newsfeeds
+ samples/sasl.conf
+ scripts/inncheck
+ scripts/innshellvars
+ scripts/innshellvars.pl
+ scripts/innshellvars.tcl
+ scripts/news.daily
+ support/fixscript
+ ,
+ chmod +x support/fixscript
+)
+
+dnl Print out some additional information on what to check.
+cat <<EOM
+
+Please check the following files before running make, to ensure that
+everything was set correctly.
+
+ Makefile.global
+ include/config.h
+ include/paths.h
+ innfeed/innfeed.h
+
+EOM
+
+dnl Finally, double-check the configured temporary directory. Some people
+dnl point this at the system temporary directories or at other world-writeable
+dnl directories, which can be a local security hole.
+if $_PATH_PERL -e "exit((stat('$tmpdir'))[[2]] & 2)" > /dev/null ; then
+ :
+else
+ cat <<EOM
+
+WARNING WARNING WARNING WARNING WARNING WARNING WARNING WARNING WARNING WARNING
+
+ The temporary directory you have configured is world-writeable. It is
+ currently set to $tmpdir.
+
+ This directory is used by INN for temporary files and should only be
+ writeable by the news user. INN does not go to great lengths to prevent
+ symlink attacks and the like because it assumes this directory is not
+ world-writeable. By configuring INN in this fashion, you may be
+ introducing a locally exploitable security hole.
+
+ It is STRONGLY recommended that you use a temporary directory reserved for
+ INN's exclusive use, one which is not world-writeable. You can do this
+ either with --with-tmp-dir or by setting --prefix to something other than
+ /usr or /.
+
+EOM
+fi
--- /dev/null
+## $Id: Makefile 6299 2003-04-20 19:04:14Z vinocur $
+##
+## There are no installation rules or other top-level rules for this
+## directory as it's not properly part of INN. Installation should be
+## done by the user by hand for those files that they're interested in.
+
+include ../Makefile.global
+
+top = ..
+CFLAGS = $(GCFLAGS)
+
+ALL = archivegz backlogstat backupfeed cleannewsgroups delayer \
+ findreadgroups makeexpctl makestorconf mlockfile newsresp \
+ pullart reset-cnfs respool showtoken stathist thdexpire \
+ tunefeed
+
+all: $(ALL)
+
+warnings:
+ $(MAKE) COPT='$(WARNINGS)' all
+
+clean clobber distclean:
+ rm -f *.o $(ALL)
+ rm -rf .libs
+
+$(FIXSCRIPT):
+ @echo Run configure before running make. See INSTALL for details.
+ @exit 1
+
+
+## Compilation rules.
+
+LINK = $(LIBLD) $(LDFLAGS) -o $@
+FIX = $(FIXSCRIPT)
+
+STORELIBS = $(LIBSTORAGE) $(LIBINN) $(EXTSTORAGELIBS) $(LIBS)
+
+expirectl: expirectl.o ; $(LINK) expirectl.o
+mlockfile: mlockfile.o ; $(LINK) mlockfile.o
+newsresp: newsresp.o ; $(LINK) newsresp.o $(LIBS)
+pullart: pullart.o ; $(LINK) pullart.o $(LIBINN)
+reset-cnfs: reset-cnfs.o ; $(LINK) reset-cnfs.o
+respool: respool.o ; $(LINK) respool.o $(STORELIBS)
+
+archivegz: archivegz.in $(FIX) ; $(FIX) -i archivegz.in
+backlogstat: backlogstat.in $(FIX) ; $(FIX) backlogstat.in
+backupfeed: backupfeed.in $(FIX) ; $(FIX) -i backupfeed.in
+cleannewsgroups: cleannewsgroups.in $(FIX) ; $(FIX) cleannewsgroups.in
+delayer: delayer.in $(FIX) ; $(FIX) -i delayer.in
+findreadgroups: findreadgroups.in $(FIX) ; $(FIX) findreadgroups.in
+makeexpctl: makeexpctl.in $(FIX) ; $(FIX) makeexpctl.in
+makestorconf: makestorconf.in $(FIX) ; $(FIX) makestorconf.in
+showtoken: showtoken.in $(FIX) ; $(FIX) -i showtoken.in
+stathist: stathist.in $(FIX) ; $(FIX) -i stathist.in
+thdexpire: thdexpire.in $(FIX) ; $(FIX) thdexpire.in
+tunefeed: tunefeed.in $(FIX) ; $(FIX) -i tunefeed.in
--- /dev/null
+This directory contains unsupported contributions to INN. Most of these
+programs are of interest to a limited set of sites, require some manual
+modifications to make work, and/or are separately maintained independent
+of INN. Programs in here may or may not have been tested on the latest
+version of INN, so keep that in mind when trying them out. The INN
+developers may not be able to answer bug reports for these utilities; it's
+best to send them to the original author.
+
+Volunteers who would like to take particularly useful applications in this
+directory and make them suitable for inclusion in INN proper are heartily
+encouraged, but discuss this on inn-workers@isc.org. Sometimes there's a
+reason why this hasn't already been done or something specific that's
+needed before they can be included.
+
+Type "make <program>" to build any of the following programs and then copy
+the binary to somewhere on your PATH to use it. For details on what each
+program does, see below, as well as the comments at the beginning of each
+file (if any).
+
+In addition to these files, also see the contrib section of the INN FTP
+site at <ftp://ftp.isc.org/isc/inn/contrib/> for more software designed
+to work with INN.
+
+ -------------------------
+
+archivegz
+
+ A compressing version of archive, writing out .gz files instead of
+ plain text files. May not work with the storage API without some
+ changes to use sm.
+
+backlogstat
+
+ Prints informations about the current state of innfeed's backlog, if
+ any.
+
+backupfeed
+
+ Another version of suck or pullnews that downloads posts from a remote
+ news server and offers them to the local news server.
+
+cleannewsgroups
+
+ Performs various cleanups on the newsgroups file.
+
+count_overview.pl
+
+ Counts the groups in a bunch of Xref records.
+
+delayer
+
+ Sits in a data stream and delays it by some constant period of time.
+ Mostly useful for delaying innfeed feeds to allow cancels a chance to
+ remove articles before innfeed sends them to your peers. See the
+ beginning of the file for an example of how to use it.
+
+expirectl
+
+ Automatically builds expire.ctl based on current available disk space
+ and a template, adjusting the expiration times of groups based on a
+ weight and the available space. Uses a template expire.ctl.ctl file;
+ see the end of expirectl.c for a sample.
+
+findreadgroups
+
+ Scans the news log files and generates a file giving readership counts
+ by newsgroup. Used by makeexpctl and makestorconf.
+
+fixhist
+
+ Performs various cleanups and sanity checks on the history database.
+
+innconfcheck
+
+ Merges your inn.conf settings with the inn.conf man page to make it
+ easier to be sure that your settings match what you want. Edit this
+ script to add the correct paths to the man page; see the comments at
+ the beginning of this script.
+
+makeexpctl
+
+ Generates an expire.ctl based on what newsgroups are actually read.
+ Uses data generated by findreadgroups. This script will require
+ editing before being usable for your server.
+
+makestorconf
+
+ Generates a storage.conf file putting frequently read newsgroups into
+ timecaf rather than CNFS. Uses data gefnerated by findreadgroups.
+ This script will require editing before being usable for your server.
+
+mkbuf
+
+ Creates a CNFS cycbuff; see the comments at the beginning of
+ this script.
+
+mlockfile
+
+ Locks files given on the command line into memory using mlock (only
+ tested on Solaris). Useful primarily for locking the history files
+ (history.hash and history.index) into memory on a system with
+ sufficient memory to speed history lookups in innd. This seems to
+ help some systems quite a lot and others not at all.
+
+newsresp
+
+ Opens an NNTP channel to a server and takes a peek at various response
+ times. Can check the round-trip time and the history lookup time.
+ See the comments at the beginning of the source for more details.
+
+pullart
+
+ Attempts to pull news articles out of CNFS cycbuffs. Useful for
+ emergency recoveries.
+
+reset-cnfs
+
+ Clears a CNFS cycbuff; see the comments at the beginning of
+ this script.
+
+respool
+
+ Takes a list of tokens on stdin and respools them, by retrieving the
+ article, storing it again, and then calling SMcancel on the previous
+ instance of the article. Note that after running this program, you'd
+ need to rebuild the history and overview, since it doesn't update
+ either.
+
+showtoken
+
+ Decodes storage API tokens.
+
+stathist
+
+ Parses and summarizes the log files created by the history profiling
+ code.
+
+thdexpire
+
+ A dynamic expire daemon for timehash and timecaf spools. It should
+ be started along with innd and periodically looks if news spool space
+ is getting tight, and then frees space by removing articles until
+ enough is free. It is an adjunct to (not a replacement for) INN's
+ expire program.
+
+tunefeed
+
+ Given two active files, attempts to produce a good set of wildmat
+ patterns for newsfeeds to minimize the number of rejects. For full
+ documentation, run "perldoc tunefeed".
--- /dev/null
+#!/usr/bin/perl
+# Copyright 1999 Stephen M. Benoit, Service Providers of America.
+# See notice at end of this file.
+#
+# Filename: archivegz.pl
+# Author: Stephen M. Benoit (benoits@servicepro.com)
+# Created: Wed Apr 14 13:56:01 1999
+# Version: $Id: archivegz.in 4329 2001-01-14 13:47:52Z rra $
+#
+$RCSID='$Id: archivegz.in 4329 2001-01-14 13:47:52Z rra $ ';
+
+# Specify command line options, and decode the command line.
+
+require 'newgetopt.pl';
+require 'newusage.pl';
+@opts =
+ (
+ "help|usage;;print this message",
+ "version;;print version",
+ "a=s;;directory to archive in instead of the default",
+ "f;;directory names will be flattened out",
+ "i=s;;append one line to the index file for each article (Destination name, Message ID, Subject)",
+ "m;; Files are copied by making a link. Not applicable, ignored",
+ "r;;Suppress stderr redirection to /var/log/news/errlog",
+ "n=s;;the news spool (source) directory (default=/var/spool/news/)",
+ "t=i;;timeout that separates batches (default 10 seconds)",
+ ";;input",
+ # Examples.
+ #
+ # "OPT;;Option without an argument",
+ # "OPT!;;Negatable option without an argument",
+ # "VAR=T;;Option with mandatory argumet T = s(tring),i(nteger), or f(loat).
+ # "VAR:T;;Option with optional argument.
+ # "OPT|AAA|BBB";;AAA and BBB are aliases for OPT",
+ # "VAR=T@";;Push option argument onto array @opt_VAR"
+ );
+$ignorecase = 0;
+$badopt = !&NGetOpt(&NMkOpts(@opts));
+# $badarg = (@ARGV != 0);
+if ($badarg || $badopt || $opt_help)
+ {
+ &NUsage($0,0,'',@opts);
+ exit ($badopt||$badarg);
+ }
+if ($opt_version) {print STDERR "$RCSID\n"; exit 0}
+
+# --------------------------------------------------------------------
+
+# --- constants and defaults ---
+$NEWS_ROOT = "/var/spool/news/";
+$NEWS_ERR = "/var/log/news/errlog";
+$NEWS_ARCHIVE = $NEWS_ROOT . "news.archive/";
+$timeout = 10;
+if ($opt_t)
+ { $timeout = $opt_t;}
+if ($timeout<1) {$timeout=1;}
+
+# --------------------------------------------------------------------
+
+sub regexp_escape
+ {
+ local($data)=@_;
+
+ $data =~ s+\\+\\\\+gi; # replace \ with \\
+ $data =~ s+\/+\\\/+gi; # replace / with \/
+
+ $data =~ s/([\+\*\?\[\]\(\)\{\}\.\|])/\\$1/gi; # replace +*?[](){}.|
+
+ return $data;
+ }
+
+sub fhbits {
+ local(@fhlist) = split(' ',$_[0]);
+ local($bits);
+ for (@fhlist) {
+ vec($bits,fileno($_),1) = 1;
+ }
+ $bits;
+}
+
+sub timed_getline
+ {
+ my ($fileh,$timeout)=@_;
+ my $filehandle = (ref($fileh)
+ ? (ref($fileh) eq 'GLOB'
+ || UNIVERSAL::isa($fileh, 'GLOB')
+ || UNIVERSAL::isa($fileh, 'IO::Handle'))
+ : (ref(\$fileh) eq 'GLOB'));
+ local(*FILEH) = *$fileh{FILEHANDLE};
+
+ local($rin,$win,$ein);
+ local($rout,$wout,$eout);
+ $rin = $win = $ein = '';
+ $rin = fhbits('FILEH');
+ $ein = $rin | $win;
+ local($nfound);
+ local($offset)=0;
+ local($accum)='';
+ local($done)=0;
+ local($result);
+
+ $nfound = select($rout=$rin, $wout=$win, $eout=$ein, $timeout);
+
+ if ($nfound>0)
+ {
+
+ # use sysread() to get characters up to end-of-line (incl.)
+ while (!$done)
+ {
+ $result = sysread(FILEH, $accum, 1, $offset);
+ if ($result<=0)
+ {
+ $done=1;
+ return undef;
+ }
+
+ if (substr($accum,$offset,1) eq "\n")
+ {
+ $done=1;
+ }
+ else
+ {
+ $offset+=$result;
+ }
+ }
+ }
+ return $accum;
+ }
+
+# --------------------------------------------------------------------
+
+# --- source spool directory ---
+if ($opt_n)
+ {
+ if ($opt_n !~ /^\//) # absolute path?
+ { $opt_n = $NEWS_ROOT . $opt_n; }
+ if ($opt_n !~ /\/$/) # must end with /
+ { $opt_n .= '/'; }
+ $NEWS_ROOT = $opt_n;
+ }
+
+# --- archive directory ---
+if ($opt_a)
+ {
+ if ($opt_a !~ /^\//) # absolute path?
+ { $opt_a = $NEWS_ROOT . $opt_a; }
+ if ($opt_a !~ /\/$/) # must end with /
+ { $opt_a .= '/'; }
+ $NEWS_ARCHIVE = $opt_a;
+ }
+
+# --- redirect stderr ---
+if (!$opt_r)
+ {
+ open(SAVEERR, ">&STDERR");
+ open(STDERR, ">>$NEWS_ERR") || die "Can't redirect stderr";
+ }
+
+# --- get input file opened ---
+if ($infilename=shift(@ARGV))
+ {
+ if ($infilename !~ /^\//) # absolute filename?
+ {
+ $infilename = $NEWS_ROOT . $infilename;
+ }
+
+ }
+else
+ {
+ $infilename="-";
+ }
+open(INFILE,"<$infilename");
+
+$done=0;
+while (!$done)
+ {
+ %sourcefile=();
+ %destfile=();
+ %destname=();
+
+
+ # --- loop over each line in infile ---
+ # comments start with '#', ignore blank lines, each line is a filename
+ while ($srcfile = &timed_getline(INFILE,$timeout))
+ {
+ if ($srcfile =~ /\#/) {$srcfile = $`;}
+ if ($srcfile =~ /^\s*/) {$srcfile = $';}
+ if ($srcfile =~ /\s*$/) {$srcfile = $`;}
+ if ($srcfile) # if a filename survived all that...
+ {
+ if ($srcfile !~ /^\//) # absolute filename?
+ {
+ $srcfile = $NEWS_ROOT . $srcfile;
+ }
+ # $srcfile is now a valid, absolute filename
+ # split filename into news directory, newsgroup and article number
+ $artnum=-1;
+ $remaining=$srcfile;
+ if ($remaining =~ /\/(\d*)$/) # remove / and article number
+ { $artnum = $1; $remaining=$`;}
+ $regex = ®exp_escape($NEWS_ROOT);
+ if ($remaining =~ /^$regex/) # split off news dir
+ { $newsdir = $&; $grpdir = $';}
+ else
+ { $newsdir = ''; $grpdir = $remaining; } # ... otherwise, grp = dir
+ $newsgrp = $grpdir;
+ $newsgrp =~ s/\//\./g; # replace slash (/) with dot (.)
+ if ($opt_f)
+ {
+ $grpdir = "$newsgrp.gz";
+ }
+ else
+ { $grpdir .= "/archive.gz"; }
+ $destfile = $NEWS_ARCHIVE . $grpdir;
+
+ # print STDERR "$srcfile --> $newsgrp --> $destfile\n";
+ if ($sourcefile{$newsgrp}) {$sourcefile{$newsgrp} .= " ";}
+ $sourcefile{$newsgrp} .= $srcfile;
+ $destfile{$newsgrp} = $destfile;
+ $destname{$newsgrp} = $grpdir;
+ }
+ }
+
+ # --- is there anything to do at this time? ---
+ if (%destfile)
+ {
+
+ # --- open INDEX ---
+ if ($opt_i)
+ {
+ # make sure directory exists
+ if ($opt_i =~ /\/[^\/]*$/)
+ {
+ $dirbase=$`;
+ system("mkdir -p $dirbase");
+ }
+ open(INDEX,">>$opt_i");
+ }
+
+ # --- make sure that archive file can be written (make parent dirs) ---
+ if ($destfile{$group} =~ /\/[^\/]*$/)
+ {
+ $dirbase=$`;
+ system("mkdir -p $dirbase");
+ }
+
+ # --- process each article ---
+ foreach $group (keys(%destfile))
+ {
+ # --- gzip the concatenated document, appending archive file ---
+ open(GZIP, "|gzip -c >> $destfile{$group}") || die "Can't open gzip";
+
+ # --- concatenate the articles, keeping header info if needed ---
+ @accum_headers=();
+ foreach $srcfile (split(/\s+/, $sourcefile{$group}))
+ {
+ # print STDERR "reading $srcfile...\n";
+ $this_doc='';
+ open(DOC, "<$srcfile");
+ while ($line=<DOC>)
+ {
+ $this_doc .= $line;
+ }
+ close(DOC);
+ print GZIP $this_doc;
+ if ($opt_i)
+ {
+ # --- get header information and store it in index
+ $subject=''; $mesageid=''; $destname='';
+ if ($this_doc =~ /Subject:\s*(.*)/)
+ { $subject = $1; }
+ if ($subject =~ /^\s*/) {$subject = $';}
+ if ($subject =~ /\s*$/) {$subject = $`;}
+ if ($this_doc =~ /Message-ID:\s*(.*)/)
+ {$messageid = $1; }
+ if ($messageid =~ /^\s*/) {$messageid = $';}
+ if ($messageid =~ /\s*$/) {$messageid = $`;}
+
+ print INDEX "$destname{$group} $messageid $subject\n";
+ }
+ }
+
+ close(GZIP);
+ }
+
+ # --- close index file ---
+ if ($opt_i)
+ {
+ close(INDEX);
+ }
+ }
+
+ if (!defined($srcfile)) # file was closed
+ {
+ $done=1;
+ last; # "break"
+ }
+
+ }
+
+# --- restore stderr ---
+if (!$opt_r)
+ {
+ close(STDERR);
+ open(STDERR,">>&SAVEERR");
+ }
+
+# --- close input file ---
+close(INFILE);
+
+
+__END__
+# Local Variables:
+# mode: perl
+# End:
+
+# Copyright 1999 Stephen M. Benoit, Service Providers of America (SPA).
+#
+# Permission to use, copy, modify, and distribute this software and its
+# documentation for any purpose without fee is hereby granted without fee,
+# provided that the above copyright notice appear in all copies and that both
+# that copyright notice and this permission notice appear in supporting
+# documentation, and that the name of SPA not be used in advertising or
+# publicity pertaining to distribution of the software without specific,
+# written prior permission. SPA makes no representations about the
+# suitability of this software for any purpose. It is provided "as is"
+# without express or implied warranty.
+#
+# SPA DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE, INCLUDING ALL
+# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL
+# SPA BE LIABLE FOR ANY SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY
+# DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN
+# AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+# OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
--- /dev/null
+This directory contains sample authorization programs for use with the
+'authinfo generic' command in nnrpd.
+
+The first program in here is from Doug Needham I have successfully
+tested this program when connecting to nnrpd by hand, but I've not
+taken the time to figure out how to get my newsreader to use
+'authinfo generic'. There is no Makefile here and no serious
+testing of it, so it's not integrated. If you have success using
+it and care to share what you've done. Please drop me a note
+(<inn@isc.org>). Thanks.
+
+
+---------------------------------------------------------------------------
+
+Replied: Fri, 26 Jul 1996 19:29:17 +0200
+Replied: Douglas Wade Needham <dneedham@dneedham.inhouse.compuserve.com>
+Received: by gw.home.vix.com id UAA05867; Thu, 25 Jul 1996 20:45:27 -0700 (PDT)
+Received: (from dneedham@localhost) by dneedham.inhouse.compuserve.com (8.7.4/8.6.9) id XAA21103; Thu, 25 Jul 1996 23:45:25 -0400 (EDT)
+From: Douglas Wade Needham <dneedham@dneedham.inhouse.compuserve.com>
+Message-Id: <199607260345.XAA21103@dneedham.inhouse.compuserve.com>
+Subject: A sample program for authinfo generic (for inn 1.5)
+To: inn-workers@vix.com (INN Gurus/Workers)
+Date: Thu, 25 Jul 1996 23:45:25 -0400 (EDT)
+Cc: inn@isc.org, brister@vix.com (James A. Brister)
+X-Mailer: ELM [version 2.4 PL25]
+MIME-Version: 1.0
+Content-Type: multipart/mixed; boundary=%#%record%#%
+Status: U
+
+--%#%record%#%
+Content-Type: text/plain; charset=US-ASCII
+Content-Transfer-Encoding: 7bit
+Content-Length: 1894
+
+Hi folks...
+
+Finally started to get some time to clear some things from my todo list...Here
+is a sample program which can be used by "authinfo generic" to validate a user
+against the password file on the news host. While not a great example, it does
+demonstrate how you can write an authentication program. All I ask is that
+credit be given.
+
+A couple of notes that I have found out about these programs for those of you
+who may be interested in writing your own...
+
+1) These programs have stdin and stdout connected all the way back to the
+ reader, so they can carry on a dialog in whatever fashion they want to
+ with the user's news reader. This can include passing Kerberos tickets,
+ encrypted or hashed passwords, or doing a challenge-response type session
+ for authenticating the user rather than passing the password in clear-text
+ across the network.
+
+2) Regardless of the outcome, the authentication program must send NNRPD a
+ record such as is found in nnrp.access by writing it to stderr.
+
+3) Successful authentication is indicated by a zero exit status, and
+ unsuccessful authentication is indicated by a non-zero exit status.
+
+4) Need I say it (again)...these programs can be a security hole unless care is
+ taken to avoid SUID programs and those that transmit/recieve passwords in
+ the clear (especially those that use login passwords). We should give some
+ thought to doing a similiar program for Kerberos authentication (what sort
+ of instance should we use???) and other authentication methods such as
+ Compuserve's Distributed Authentication (guess I should do this one once the
+ standard is finialized with the IETF 8) ).
+
+Also, a question for the list as a whole... what readers easily support
+authinfo generic (including running a program at the reader's end to do things
+like challenge-response)???
+
+Well...here it is...enjoy 8)...
+
+- doug
+
+#### See auth_pass.c #####
--- /dev/null
+/*
+ * auth_pass.c ( $Revision: 6141 $ )
+ *
+ * Abstract:
+ *
+ * This module is the complete source for a sample "authinfo generic"
+ * program. This program takes a user's login name and password
+ * (supplied either as arguments or as responses to prompts) and
+ * validates them against the contents of the password database.
+ *
+ * If the user properly authenticates themselves, a nnrp.auth style
+ * record indicating the user's authenticated login and permitting
+ * reading and posting to all groups is output on stderr (for reading by
+ * nnrpd) and the program exits with a 0 status. If the user fails to
+ * authenticate, then a record with the attempted login name and no
+ * access is output on stderr and a non-zero exit status is returned.
+ *
+ * Exit statuses:
+ * 0 Successfully authenticated.
+ * 1 getpeername() failed, returned a bad address family, or
+ * gethostbyaddr() failed.
+ * 2 Entry not found in password file.
+ * 3 No permission to read passwords, or password field is '*'.
+ * 4 Bad password match.
+ *
+ * Environment:
+ * Run by nnrpd with stdin/stdout connected to the reader and stderr
+ * connected back to nnrpd. This program will need to be run as suid
+ * root on systems where passwords are stored in a file readable only by
+ * root.
+ *
+ * Written 1996 July 6 by Douglas Wade Needham (dneedham@oucsace.cs.ohiou.edu).
+ *
+ */
+
+#include "config.h"
+#include "clibrary.h"
+#include "portable/socket.h"
+#include <netdb.h>
+#include <pwd.h>
+
+\f
+main(int argc, char** argv)
+/*+
+ * Abstract:
+ * Main routine of the program, implementing all prompting, validation,
+ * and status returns.
+ *
+ * Arguments:
+ * argc Argument count.
+ * argv Null terminated argument vector.
+ *
+ * Returns:
+ * Exits according to program status values.
+ *
+ * Variables:
+ * hp Pointer to host entry.
+ * length General integer variable
+ * password Password given by user.
+ * peername Hostname of the peer.
+ * pwd Pointer to entry from passwd file.
+ * sin Socket address structure.
+ * username User's login name.
+ */
+{
+ struct hostent * hp;
+ int length;
+ char password[256];
+ char peername[1024];
+ struct passwd * pwd;
+ struct sockaddr_in sin;
+ char username[32];
+
+ /*
+ * Get the user name and password if needed.
+ */
+ if (argc<2) {
+ fprintf(stdout, "Username: "); fflush(stdout);
+ fgets(username, sizeof(username), stdin);
+ } else {
+ strlcpy(username, argv[1], sizeof(username));
+ }
+ if (argc<3) {
+ fprintf(stdout, "Password: "); fflush(stdout);
+ fgets(password, sizeof(password), stdin);
+ } else {
+ strlcpy(password, argv[2], sizeof(password));
+ }
+
+ /*
+ * Strip CR's and NL's from the end.
+ */
+ length = strlen(username)-1;
+ while (username[length] == '\r' || username[length] == '\n') {
+ username[length--] = '\0';
+ }
+ length = strlen(password)-1;
+ while (password[length] == '\r' || password[length] == '\n') {
+ password[length--] = '\0';
+ }
+
+ /*
+ * Get the hostname of the peer.
+ */
+ length = sizeof(sin);
+ if (getpeername(0, (struct sockaddr *)&sin, &length) < 0) {
+ if (!isatty(0)) {
+ fprintf(stderr, "cant getpeername()::%s:+:!*\n", username);
+ exit(1);
+ }
+ strlcpy(peername, "stdin", sizeof(peername));
+ } else if (sin.sin_family != AF_INET) {
+ fprintf(stderr, "Bad address family %ld::%s:+:!*\n",
+ (long)sin.sin_family, username);
+ exit(1);
+ } else if ((hp = gethostbyaddr((char *)&sin.sin_addr, sizeof(sin.sin_addr), AF_INET)) == NULL) {
+ strlcpy(peername, inet_ntoa(sin.sin_addr), sizeof(peername));
+ } else {
+ strlcpy(peername, hp->h_name, sizeof(peername));
+ }
+
+ /*
+ * Get the user name in the passwd file.
+ */
+ if ((pwd = getpwnam(username)) == NULL) {
+
+ /*
+ * No entry in the passwd file.
+ */
+ fprintf(stderr, "%s::%s:+:!*\n", peername, username);
+ exit(2);
+ }
+
+ /*
+ * Make sure we managed to read in the password.
+ */
+ if (strcmp(pwd->pw_passwd, "*")==0) {
+
+ /*
+ * No permission to read passwords.
+ */
+ fprintf(stderr, "%s::%s:+:!*\n", peername, username);
+ exit(3);
+ }
+
+ /*
+ * Verify the password.
+ */
+ if (strcmp(pwd->pw_passwd, crypt(password, pwd->pw_passwd))!=0) {
+
+ /*
+ * Password was invalid.
+ */
+ fprintf(stderr, "%s::%s:+:!*\n", peername, username);
+ exit(4);
+ }
+
+ /*
+ * We managed to authenticate the user.
+ */
+ fprintf(stderr, "%s:RP:%s:+:*\n", peername, username);
+ exit(0);
+}
--- /dev/null
+#!/usr/bin/perl
+# fixscript will replace this line with require innshellvars.pl
+
+# backlogstat - display backlog to sites
+# based on bklog by bill davidsen <davidsen@tmr.com>
+
+# breaks if backlog-directory in innfeed.conf is not "innfeed"
+my $dir = "$inn::pathspool/innfeed";
+my $Revision = '1.8';
+
+use strict;
+use warnings;
+
+use Getopt::Std;
+use vars qw($opt_H $opt_h $opt_n $opt_t $opt_k $opt_S $opt_d);
+$| = 1;
+
+# option processing
+&getopts('HhntkS:d:') || &Usage;
+&Usage if $opt_h;
+
+# open the directory;
+$dir = $opt_d if $opt_d;
+print "$opt_d\n";
+chdir($dir) or die "Can't cd to $dir";
+opendir(DIR, ".") or die "Can't open dir";
+
+my %nodes;
+while (my $name = readdir(DIR)) {
+ # must be a file, correct name, non-zero size
+ my $size;
+ next unless -f $name;
+ next unless ($size = -s $name);
+ next unless $name =~ m/.*\.(in|out)put/;
+ my $io = $1;
+ (my $nodename = $name) =~ s/\..*//;
+
+ # check for only some sites wanted
+ next if ($opt_S && $nodename !~ /^${opt_S}.*/);
+ # here we do the counts if asked
+ if ($opt_n) {
+ # open the file and count lines
+ if (open(IN, "<$name")) {
+ if ($name =~ m/.*\.input/) {
+ my $offset = <IN> + 0;
+ seek(IN, $offset, 0);
+ }
+ $size = 0;
+ for ($size = 0; <IN> ; ++$size) {};
+ close IN;
+ }
+ } else {
+ # get the offset on .input files
+ if ($name =~ m/.*\.input/ && open(IN, "<$name")) {
+ my $offset = <IN> + 0;
+ $size -= $offset;
+ close IN;
+ }
+ }
+ $nodes{$nodename} = () unless defined $nodes{$nodename};
+ $nodes{$nodename}->{$io} = ( $opt_k ? $size / 1024 : $size );
+}
+closedir DIR;
+
+# output the data for each node
+if (my $numnodes = keys %nodes) {
+ if ($opt_H) {
+ if ($opt_n) {
+ print " <---------- posts ----------->\n";
+ } else {
+ print " <---------- bytes ----------->\n";
+ }
+ }
+ my $ofmt;
+ if ($opt_k) {
+ print " input(k) output(k) total(k) Feed Name\n" if $opt_H;
+ $ofmt = ( $opt_n ? "%10.2f" : "%10.1f" );
+ } else {
+ print " input output total Feed Name\n" if $opt_H;
+ $ofmt = "%10d";
+ }
+ for my $node (sort keys %nodes) {
+ my $hash = $nodes{$node};
+ my $size_in = $hash->{in} || 0;
+ my $size_out = $hash->{out} || 0;
+ my $size_tot = $size_in + $size_out;
+ printf "${ofmt} ${ofmt} ${ofmt} %s\n",
+ $size_in, $size_out, $size_tot, $node;
+ }
+} else {
+ print "NO backlog!\n";
+}
+
+exit 0;
+
+sub Usage
+{
+ print "\n"
+ . "bklog - print innfeed backlog info - v$Revision\n"
+ . "\n"
+ . "Format:\n"
+ . " bklog [ options ]\n"
+ . "\n"
+ . "Options:\n"
+ . " -H output a header at the top of the output\n"
+ . " -k scale all numbers in k (1024) units\n"
+ . " -n count number of arts, not bytes of backlog filesize\n"
+ . " Note: this may be SLOW for large files!\n"
+ . " -Sxx Display only site names starting with xx\n"
+ . " -d dir Use \"dir\" instead of \$pathspool/innfeed\n"
+ . "\n"
+ . " -h HELP - this is all, you got it!\n"
+ . "\n";
+
+ exit 1;
+}
+
+
--- /dev/null
+#! /usr/bin/perl -w
+#
+# Date: 26 Jun 1999 17:59:00 +0200
+# From: kaih=7Jbfpa7mw-B@khms.westfalen.de (Kai Henningsen)
+# Newsgroups: news.software.nntp
+# Message-ID: <7Jbfpa7mw-B@khms.westfalen.de>
+# Subject: Re: Version of pullnews that support authentication?
+#
+# [...]
+# I'm appending a script I wrote (called backupfeed.pl for some reason). Hmm
+# ... oh, I hereby put that into the public domain. Use as you see fit. If
+# it breaks, you get to keep all the parts.
+#
+# Needs the newer Net::NNTP versions for the MODE READER fix.
+#
+# This thing is both faster and uses far less memory than suck. And it
+# inserts a predictable Path: entry (in case the host you pull from
+# doesn't).
+#
+# It's in production use as a backup to regular feeds, so it specifically
+# fetches only old articles unless you say -p 1 (default is -p 0.6666...).
+
+use strict;
+use Net::NNTP;
+use DB_File;
+use Data::Dumper;
+use Getopt::Std;
+use vars qw($Group $Host $Pos $Rc %Rc $Starttime
+ $opt_S $opt_T $opt_d $opt_p $opt_s $opt_t);
+
+my ( @groups, $localhost, $remotehost, $accepted, $rejected, $lockf,
+ $history, $acc, $rej, $his, @parms, $from, $to, $art, %err );
+
+$| = 1;
+
+$opt_S = 10; # sleep between groups
+$opt_T = 10000; # max running time
+$opt_d = 0; # debugging
+$opt_p = 2/3; # how many articles to fetch
+$opt_s = 0; # sleep between articles
+$opt_t = 0; # timeout for NNTP connections
+getopts("dt:p:s:S:T:");
+
+die <<USAGE if @ARGV < 2;
+Usage: $0 hostname /groups/wanted [ userid password ]
+Options:
+ -d debugging
+ -t s NNTP timeout
+ -p nn how many articles (0.0 .. 1.0)
+ -s s sleep between articles
+ -S s sleep between groups
+ -T s max running time
+USAGE
+
+my ($GroupsWanted, $userid, $password);
+($Host, $GroupsWanted, $userid, $password) = @ARGV;
+
+chdir("/var/local/lib/backupfeed") or die "chdir: $!";
+$lockf = "/var/lock/lock-backupfeed-$Host";
+system("/usr/lib/news/bin/shlock -p $$ -f $lockf")==0 or exit 0;
+
+open LOG, ">> /var/log/news/backupfeed.$Host" or die "normal log: $!";
+autoflush LOG;
+
+open ERR, ">> /var/log/news/backupfeed.$Host.errors" or die "error log: $!";
+autoflush ERR;
+
+print LOG scalar(localtime), " $0 starting for $Host\n";
+print ERR scalar(localtime), " $0 starting for $Host\n";
+
+open GUP, $GroupsWanted or die "Groups Wanted: $GroupsWanted: $!";
+@groups = <GUP>;
+close GUP;
+
+$Starttime = time;
+
+$localhost = Net::NNTP->new("localhost", "Debug", $opt_d, "Timeout", $opt_t, "Reader", 0) or die "localhost: $!";
+
+$remotehost = Net::NNTP->new($Host, "Debug", $opt_d, "Timeout", $opt_t) or die "remotehost: $!";
+$remotehost->reader;
+&lifecheck($remotehost, $Host);
+$remotehost->authinfo($userid, $password) if ($userid);
+&lifecheck($remotehost, $Host);
+
+tie %Rc, "DB_File", "$Host.bfrc" or die "$Host.bfrc: $!";
+
+$SIG{HUP} = 'IGNORE';
+$SIG{INT} = \&sig;
+$SIG{TERM} = \&sig;
+
+my $restart = $Rc{'=restart='};
+$restart='' unless ($restart);
+
+my @before = grep $_ lt $restart, @groups;
+my @after = grep $_ ge $restart, @groups;
+@groups = ( @after, @before );
+
+($acc, $rej, $his) = (0, 0, 0);
+foreach $Group (@groups) {
+ chomp $Group;
+ (@parms = $remotehost->group($Group)) or next;
+ &lifecheck($remotehost, $Host);
+ next if ($#parms < 3);
+ $Rc{'=restart='} = $Group;
+ print LOG scalar(localtime), " \t<$Group>\n";
+ $Rc{$Group} = 0
+ if (!defined $Rc{$Group});
+ $Rc{$Group} = 0
+ if (!$Rc{$Group});
+ $from = $parms[1];
+ $to = $parms[2];
+ $to = $from + ($to - $from) * $opt_p;
+ if ($to < $Rc{$Group}) {
+ print LOG scalar(localtime), " \t watermark high, reset\n";
+ $Rc{$Group} = $from-1;
+ }
+ $Rc{$Group} = $from-1
+ if ($from > $Rc{$Group});
+# print LOG scalar(localtime), " \t\t",$Rc{$Group}+1,"-$to\n";
+ $remotehost->nntpstat($Rc{$Group}+1);
+# print LOG scalar(localtime), " \t\t",$remotehost->message,"\n";
+ &lifecheck($remotehost, $Host);
+ $art = $remotehost->nntpstat;
+ &lifecheck($remotehost, $Host);
+ $remotehost->message =~ /^(\d+)/;
+ $Pos = $1;
+ $accepted=0;
+ $rejected=0;
+ $history=0;
+ &offer($art)
+ if ($art);
+ while ($art = $remotehost->next) {
+ &lifecheck($remotehost, $Host);
+ $remotehost->message =~ /^(\d+)/;
+ $Pos = $1;
+ last
+ if ($Pos > $to);
+ &offer($art);
+ }
+ &lifecheck($remotehost, $Host);
+ print LOG scalar(localtime), " \taccepted=$accepted rejected=$rejected history=$history\n";
+ $acc+=$accepted;
+ $rej+=$rejected;
+ $his+=$history;
+ $accepted=0;
+ $rejected=0;
+ $history=0;
+ (tied %Rc)->sync;
+ sleep $opt_S if $opt_S;
+}
+
+untie %Rc;
+
+$localhost->quit;
+
+$remotehost->quit;
+
+&end0;
+
+sub offer
+{
+ system("echo $Host $Group $Pos > $Host.status");
+ if ($localhost->ihave($_[0])) {
+ &lifecheck($localhost, 'localhost');
+ my $article = $remotehost->article;
+ if (ref $article) {
+ #open ART1, "> art1";
+ #print ART1 @$article;
+ #close ART1;
+ my $i = 0;
+ while ($i <= @$article && !($$article[$i] =~ /^Path:/i)) {
+ $i++;
+ }
+ $$article[$i] =~ s/^(Path:\s*)/$1NNTP-from-$Host!/i;
+ #open ART2, "> art2";
+ #print ART2 @$article;
+ #close ART2;
+ #exit;
+ $localhost->datasend($article);
+ if ($localhost->dataend) {
+ $accepted++;
+ }
+ else {
+ $rejected++;
+ $err{" local " . $localhost->code . " " . $localhost->message} ++;
+ }
+ $Rc{$Group} = $Pos;
+ (tied %Rc)->sync;
+ }
+ else {
+ $err{" remote " . $remotehost->code . " " . $remotehost->message} ++;
+ }
+ sleep $opt_s if $opt_s;
+ }
+ else {
+ if ($localhost->status == 4) {
+ if ($localhost->code == 435) {
+ $err{" local " . $localhost->code . " " . $localhost->message} ++;
+ }
+ else {
+ $err{" local " . $localhost->code . " " . $localhost->message} ++;
+ print LOG scalar(localtime), " local ", $localhost->code, " ", $localhost->message, "\n";
+ &end;
+ }
+ }
+ &lifecheck($localhost, 'localhost');
+ $history++;
+ $Rc{$Group} = $Pos;
+ }
+}
+
+sub lifecheck
+{
+ unless (defined $_[0]->code and $_[0]->code > 0) {
+ print LOG scalar(localtime), " Connection to $_[1] dropped\n";
+ print ERR scalar(localtime), " Connection to $_[1] dropped\n";
+ &end;
+ }
+ #print "time=",time," starttime=$Starttime\n";
+ kill 'TERM', $$ if time-$Starttime > $opt_T;
+}
+
+sub sig
+{
+ print LOG scalar(localtime), " Caught sig: ", Data::Dumper::Dumper(@_), "\n";
+ print ERR scalar(localtime), " Caught sig: ", Data::Dumper::Dumper(@_), "\n";
+ &end;
+}
+
+sub end
+{
+ $acc+=$accepted;
+ $rej+=$rejected;
+ $his+=$history;
+ &end0;
+}
+
+sub end0
+{
+ print LOG scalar(localtime), " $0 $Host accepted=$acc rejected=$rej history=$his\n";
+ foreach my $e (sort keys %err) {
+ print ERR $err{$e}, $e, "\n";
+ }
+ print ERR scalar(localtime), " $0 $Host accepted=$acc rejected=$rej history=$his\n";
+ close LOG;
+ close ERR;
+ unlink $lockf;
+ exit 0;
+}
--- /dev/null
+#! /usr/bin/perl
+# fixscript will replace this line with require innshellvars.pl
+
+# This script cleans the newsgroups file:
+# * Groups no longer in the active file are removed.
+# * Duplicate entries are removed. The last of a set of duplicates
+# is the one retained. That way, you could simply append the
+# new/revised entries from a docheckgroups run and then this script
+# will remove the old ones.
+# * Groups with no description are removed.
+# * Groups matching the $remove regexp are removed.
+
+$remove='';
+# $remove='^alt\.';
+
+open ACT, $inn::active or die "Can't open $inn::active: $!\n";
+while(<ACT>) {
+ ($group) = split;
+ $act{$group} = 1 unless($remove ne "" && $group =~ /$remove/o);
+}
+close ACT;
+
+open NG, $inn::newsgroups or die "Can't open $inn::newsgroups: $!\n";
+while(<NG>) {
+ chomp;
+ ($group, $desc) = split /\s+/,$_,2;
+ next unless(defined $act{$group});
+
+ next if(!defined $desc);
+ next if($desc =~ /^[?\s]*$/);
+ next if($desc =~ /^no desc(ription)?(\.)?$/i);
+
+ $hist{$group} = $desc;
+}
+close NG;
+
+open NG, ">$inn::newsgroups.new" or die "Can't open $inn::newsgroups.new for write: $!\n";
+foreach $group (sort keys %act) {
+ if(defined $hist{$group}) {
+ print NG "$group\t$hist{$group}\n" or die "Can't write: $!\n";
+ }
+}
+close NG or die "Can't close: $!\n";
+
+rename "$inn::newsgroups.new", $inn::newsgroups or die "Can't rename $inn::newsgroups.new to $inn::newsgroups: $!\n";
--- /dev/null
+#!/usr/local/bin/perl
+#
+# count_overview.pl: Count the groups in a bunch of xref records.
+
+while (<>) {
+
+chop;
+@xreflist = split(/\t/); # split apart record
+
+$_ = $xreflist[$#xreflist]; # xref is last.
+
+@xreflist = reverse(split(/ /)); #break part xref line.
+
+pop @xreflist; # get rid xref header
+pop @xreflist;
+
+while ($current = pop @xreflist) {
+ ($current) = split(/:/,$current); #get newsgroup name
+ $groups{$current}++; #tally
+}
+
+}
+
+# display accumulated groups and counts.
+foreach $current (sort keys %groups) {
+ printf "%-50s\t%5d\n", $current, $groups{$current};
+}
--- /dev/null
+#!/usr/bin/perl
+# -*- perl -*-
+#
+# delay lines for N seconds.
+#
+# primarily meant to be used with INN to generate a delayed feed with innfeed.
+#
+# put it into your newsfeeds file like
+#
+# innfeed-delayed!\
+# :!*\
+# :Tc,Wnm*,S16384:/usr/local/news/bin/delayer 60 \
+# /usr/local/news/bin/startinnfeed -c innfeed-delayed.conf
+#
+#
+#
+# done by christian mock <cm@tahina.priv.at> sometime in july 1998,
+# and put into the public domain.
+#
+$delay = shift || die "usage: $0 delay prog-n-args\n";
+
+$timeout = $delay;
+$eof = 0;
+
+open(OUT, "|" . join(" ", @ARGV)) || die "open |prog-n-args: $!\n";
+
+#select(OUT);
+#$| = 1;
+#select(STDOUT);
+
+$rin = '';
+vec($rin,fileno(STDIN),1) = 1;
+
+while(!$eof || $#queue >= 0) {
+ if(!$eof) {
+ ($nfound,$timeleft) =
+ select($rout=$rin, undef, undef, $timeout);
+ } else {
+ sleep($timeout);
+ }
+ $now = time(); $exp = $now + $delay;
+
+ if(!$eof && vec($rout,fileno(STDIN),1)) {
+ $line = <STDIN>;
+ if(!defined $line) { # exit NOW!
+ foreach(@queue) {
+ s/^[^:]+://g;
+ print OUT;
+ }
+ close(OUT);
+ sleep(1);
+ exit;
+ }
+ push(@queue, "$exp:$line");
+ }
+
+ if($#queue < 0) {
+ undef $timeout;
+ next;
+ }
+
+ ($first, $line) = split(/:/, $queue[0], 2);
+ while($#queue >= 0 && $first <= $now) {
+ print OUT $line;
+ shift(@queue);
+ ($first, $line) = split(/:/, $queue[0], 2);
+ }
+ $timeout = $first - $now;
+
+}
+
--- /dev/null
+/*
+ * EXPIRECTL.C
+ *
+ * expirectl
+ *
+ * This program uses expire.ctl.ctl as input; please see the end of this
+ * file for an example of such a file.
+ */
+
+/*
+ * Date: Mon, 21 Nov 1994 12:29:52 -0801
+ * From: Matthew Dillon <dillon@apollo.west.oic.com>
+ * Message-Id: <199411212030.MAA21835@apollo.west.oic.com>
+ * To: rsalz@uunet.uu.net
+ * Subject: Re: INN is great, bug fix for BSDI
+ *
+ * [...]
+ * Oh, while I'm at it, I also wrote a cute program that builds the
+ * expire.ctl file dynamically based on available space. Feel free
+ * to include this in the dist (or not) as you please.
+ *
+ * Basically, the expirectl programs determines the amount of disk blocks
+ * and inodes free in the spool and creates a new expire.ctl file based
+ * on an expire.ctl.ctl template. The template specifies expiration times
+ * as a fraction of nominal. expirectl adjusts the nominal expiration
+ * up or down based on available disk space.
+ *
+ * The idea is to make expiration as hands off as possible. I tested
+ * it on a smaller spool and it appeared to work fine. Currently it
+ * only works for single-partition news spools tho. The above spool
+ * will not really exercise the program for another 14 days or so :-).
+ */
+
+
+#include <sys/types.h>
+#include <sys/mount.h>
+#include <errno.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#define EXPIRE_CTL_DIR "/home/news"
+#define NEWS_SPOOL "/home/news/spool/news/."
+
+#define EXPIRE_DAYS EXPIRE_CTL_DIR "/expire.days"
+#define EXPIRE_CTL EXPIRE_CTL_DIR "/expire.ctl"
+#define EXPIRE_CTL_CTL EXPIRE_CTL_DIR "/expire.ctl.ctl"
+
+void
+main(int ac, char **av)
+{
+ struct statfs sfs;
+ long minFree = 100 * 1024 * 1024;
+ long minIFree = 20 * 1024;
+ long expireDays = 2;
+ time_t expireIncTime = time(NULL) - 24 * 60 * 60;
+ int modified = 0;
+ int verbose = 0;
+
+ /*
+ * options
+ */
+
+ {
+ int i;
+
+ for (i = 1; i < ac; ++i) {
+ char *ptr = av[i];
+
+ if (*ptr == '-') {
+ ptr += 2;
+ switch(ptr[-1]) {
+ case 'v':
+ verbose = 1;
+ break;
+ case 'f':
+ modified = 1;
+ break;
+ case 'n':
+ modified = -1;
+ break;
+ case 'b':
+ minFree = strtol(((*ptr) ? ptr : av[++i]), &ptr, 0);
+ if (*ptr == 'k')
+ minFree *= 1024;
+ if (*ptr == 'm')
+ minFree *= 1024 * 1024;
+ break;
+ case 'i':
+ minIFree = strtol(((*ptr) ? ptr : av[++i]), NULL, 0);
+ if (*ptr == 'k')
+ minIFree *= 1024;
+ if (*ptr == 'm')
+ minIFree *= 1024 * 1024;
+ break;
+ default:
+ fprintf(stderr, "bad option: %s\n", ptr - 2);
+ exit(1);
+ }
+ } else {
+ fprintf(stderr, "bad option: %s\n", ptr);
+ exit(1);
+ }
+ }
+ }
+
+ if (statfs("/home/news/spool/news/.", &sfs) != 0) {
+ fprintf(stderr, "expirectl: couldn't fsstat /home/news/spool/news/.\n");
+ exit(1);
+ }
+
+ /*
+ * Load /home/news/expire.days
+ */
+
+ {
+ FILE *fi;
+ char buf[256];
+
+ if ((fi = fopen(EXPIRE_DAYS, "r")) != NULL) {
+ while (fgets(buf, sizeof(buf), fi) != NULL) {
+ if (strncmp(buf, "time", 4) == 0) {
+ expireIncTime = strtol(buf + 4, NULL, 0);
+ } else if (strncmp(buf, "days", 4) == 0) {
+ expireDays = strtol(buf + 4, NULL, 0);
+ }
+ }
+ fclose(fi);
+ } else {
+ if (modified >= 0)
+ modified = 1;
+ printf("creating %s\n", EXPIRE_DAYS);
+ }
+ }
+
+ /*
+ * print status
+ */
+
+ if (verbose) {
+ printf("spool: %4.2lfM / %3.2lfKinode free\n",
+ (double)sfs.f_fsize * (double)sfs.f_bavail / (1024.0 * 1024.0),
+ (double)sfs.f_ffree / 1024.0
+ );
+ printf("decrs: %4.2lfM / %3.2lfKinode\n",
+ (double)(minFree) / (double)(1024*1024),
+ (double)(minIFree) / (double)(1024)
+ );
+ printf("incrs: %4.2lfM / %3.2lfKinode\n",
+ (double)(minFree * 2) / (double)(1024*1024),
+ (double)(minIFree * 2) / (double)(1024)
+ );
+ }
+
+ /*
+ * Check limits, update as appropriate
+ */
+
+ {
+ double bytes;
+ long inodes;
+
+ bytes = (double)sfs.f_fsize * (double)sfs.f_bavail;
+ inodes = sfs.f_ffree;
+
+ if (bytes < (double)minFree || inodes < minIFree) {
+ if (--expireDays <= 0) {
+ expireDays = 1;
+ expireIncTime = time(NULL) - 24 * 60 * 60;
+ }
+ if (modified >= 0)
+ modified = 1;
+ printf("decrement expiration to %d days\n", expireDays);
+ } else if (bytes >= (double)minFree * 2.0 && inodes >= minIFree * 2) {
+ long dt = (long)(time(NULL) - expireIncTime);
+
+ if (dt >= 60 * 60 * 24 || dt < -60) {
+ ++expireDays;
+ expireIncTime = time(NULL);
+ if (modified >= 0)
+ modified = 1;
+ printf("increment expiration to %d days\n", expireDays);
+ } else {
+ printf("will increment expiration later\n");
+ }
+ } else if (verbose) {
+ printf("expiration unchanged: %d\n", expireDays);
+ }
+ }
+
+ /*
+ * Write EXPIRE_CTL file from EXPIRE_CTL_CTL template
+ */
+
+ if (modified > 0) {
+ FILE *fi;
+ FILE *fo;
+
+ if ((fi = fopen(EXPIRE_CTL_CTL, "r")) != NULL) {
+ if ((fo = fopen(EXPIRE_CTL ".tmp", "w")) != NULL) {
+ char sbuf[2048];
+ char dbuf[4096];
+
+ while (fgets(sbuf, sizeof(sbuf), fi) != NULL) {
+ char *base = sbuf;
+ char *sptr;
+ char *dptr = dbuf;
+
+ while ((sptr = strchr(base, '[')) != NULL) {
+ double d;
+ int m = 0;
+
+ bcopy(base, dptr, sptr - base);
+ dptr += sptr - base;
+ base = sptr;
+
+ d = strtod(sptr + 1, &sptr);
+ if (*sptr == '/')
+ m = strtol(sptr + 1, &sptr, 0);
+ if (*sptr == ']') {
+ long v = (long)((double)expireDays * d + 0.5);
+ if (v < 1)
+ v = 1;
+ if (v < m)
+ v = m;
+ sprintf(dptr, "%d", v);
+ dptr += strlen(dptr);
+ ++sptr;
+ }
+ base = sptr;
+ }
+ strcpy(dptr, base);
+ fputs(dbuf, fo);
+ }
+ fclose(fo);
+ if (rename(EXPIRE_CTL ".tmp", EXPIRE_CTL) != 0) {
+ fprintf(stderr, "rename(%s,%s): %s\n",
+ EXPIRE_CTL ".tmp",
+ EXPIRE_CTL,
+ strerror(errno)
+ );
+ }
+ }
+ fclose(fi);
+ }
+ }
+
+ /*
+ * Write EXPIRE_DAYS file
+ */
+
+ if (modified > 0) {
+ FILE *fo;
+
+ if ((fo = fopen(EXPIRE_DAYS, "w")) != NULL) {
+ fprintf(fo, "time 0x%08lx\n", expireIncTime);
+ fprintf(fo, "days %d\n", expireDays);
+ fclose(fo);
+ } else {
+ fprintf(stderr, "unable to create %s\n", EXPIRE_DAYS);
+ }
+ }
+ exit(0);
+}
+
+
+/*
+
+# Start of sample expire.ctl.ctl file.
+
+# EXPIRE.CTL.CTL (EXPIRE.CTL GENERATED FROM EXPIRE.CTL.CTL !!!)
+#
+# The expire.ctl file is generated by the expirectl program from the
+# expire.ctl.ctl file. The expirectl program calculates the proper
+# expiration based on the number of free inodes and free bytes available.
+#
+# This file is exactly expire.ctl but with the multiplier [N] replaced by
+# a calculated value, where a multiplier of '1' nominally fills the whole
+# disk.
+#
+# Any field [N] is substituted after being multiplied by the expiration
+# time (in days). A integer minimum can also be specified with a slash,
+# as in [N/minimum].
+#
+# expirectl is normally run just after expire is run. Note that expirectl
+# isn't very useful for the case where you are 'catching up' on news after
+# a long period of downtime UNLESS you use the -p option to expire.
+
+/remember/:[1.2/20]
+
+## Keep for 1-10 days, allow Expires headers to work.
+#
+*:A:1:[1.0]:[6.0]
+*.advocacy:A:1:[0.5]:[2.0]
+alt.binaries.pictures.erotica:A:1:[0.8]:[2.0]
+
+# permanent, semi-permanent
+#
+best.intro:A:never:never:never
+best.announce:A:5:60:120
+best.general:A:never:never:never
+best.bugs:A:never:never:never
+
+# End of sample expire.ctl.ctl file.
+
+*/
--- /dev/null
+#!/usr/local/bin/perl
+# fixscript will replace this line with require innshellvars.pl
+
+# Keep track of which groups are currently being read. Takes logfile input
+# on stdin.
+$readfile="$inn::newsetc/readgroups";
+
+$curtime = time;
+$oldtime = $curtime - 30 * 86400; # 30 days in the past
+
+if (open(RDF, $readfile)) {
+ while (<RDF>) {
+ chop;
+ @foo=split(/ /); # foo[0] should be group, foo[1] lastreadtime
+ if ($foo[1] < $oldtime) {
+ next; # skip entries that are too old.
+ }
+ $groups{$foo[0]} = $foo[1];
+ }
+ close(RDF);
+}
+
+# read input logs.
+while (<>) {
+ next unless /nnrpd/;
+ next unless / group /;
+ chop;
+ @foo = split(/ +/);
+ # group name is in the 8th field.
+ $groups{$foo[7]} = $curtime;
+}
+
+open(WRF, ">$readfile") || die "cannot open $readfile for write.\n";
+foreach $i (keys %groups) {
+ print WRF $i, " ", $groups{$i}, "\n";
+}
+
+exit(0);
--- /dev/null
+#!/usr/local/bin/perl
+#
+# history database sanity checker
+# David Barr <barr@math.psu.edu>
+# version 1.4
+# w/mods from: hucka@eecs.umich.edu
+# Katsuhiro Kondou <kondou@nec.co.jp>
+# version 1.1
+# Throw away history entries with:
+# malformed lines (too long, contain nulls or special characters)
+#
+# INN Usage:
+# ctlinnd throttle 'fixing history'
+# ./fixhist <history >history.n
+# makedbz -s `wc -l <history.n` -f history.n
+# or use instructions from fixhist to avoid the `wc -l <history.n`
+# mv history.n history
+# mv history.n.dir history.dir
+### if TAGGED_HASH is DO or before inn2.0
+# mv history.n.pag history.pag
+### if TAGGED_HASH is DONT
+# mv history.n.hash history.hash
+# mv history.n.index history.index
+### endif
+# ctlinnd reload history x
+# ctlinnd go 'fixing history'
+# any malformed entries will be output to stderr.
+
+
+$MAXKEYLEN=254;
+$count=0;
+
+while (<>) {
+ chop;
+ ($msgid,$dates,$arts,$xtra) = split('\t');
+ if ($xtra) {
+ &tossit(); # too many fields
+ next;
+ }
+ if (!($dates) && (($arts) || ($xtra))) {
+ &tossit(); # if not date field, then the rest
+ next; # should be empty
+ }
+ if (length($msgid) >= $MAXKEYLEN) {
+ &tossit(); # message-id too long
+ next;
+ }
+ if ($msgid !~ /^<[^<> ]*>$/) {
+ if ($msgid =~ /^\[[0-9A-F]{32}\]$/) {
+ if ($arts ne "") {
+ if ($arts =~ /^\@[0-9A-F]{56}\@$/) {
+ $arts =~ s/^\@([0-9A-F]{36})([0-9A-F]{20})\@$/\@${1}\@/;
+ print "$msgid\t$dates\t$arts\n";
+ next;
+ }
+ if ($arts !~ /^\@[0-9A-F]{36}\@$/) {
+ &tossit();
+ next;
+ }
+ }
+ } else {
+ &tossit(); # malformed msg-ids
+ next;
+ }
+ } else {
+ if ($arts ne "" && ($arts !~ /[^\/]*\/[0-9]*/)) {
+ &tossit(); # malformed articles list
+ next;
+ }
+ }
+ if (/[\000-\010\012-\037\177-\237]/) { # non-control chars except tab
+ &tossit(); # illegal chars
+ next;
+ }
+ if ($dates) {
+ if ($dates =~ /[^\d~\-]/) { # rudimentary check
+ &tossit(); # full check would be too slow
+ next;
+ }
+ }
+ print "$_\n";
+ $count++;
+ $0 = "history line $./$count" if $. % 50000 == 0;
+}
+print STDERR "Done. Now run:\nmakedbz -s $count -f history.n\n";
+
+sub tossit {
+ print STDERR "$_\n";
+}
--- /dev/null
+#!/bin/ksh
+
+### INNCONFcheck v1.1
+
+### Revision history:
+# v1.0 B. Galliart (designed to work with 2.3 inn.conf man page)
+# v1.1 B. Galliart (optional support for using inn.conf POD src instead)
+
+### Description:
+# This script is written to inner-mix the inn.conf settings with the
+# documentation from the inn.conf man page. The concept was shamelessly
+# ripped off of a CGI application provided at Mib Software's Usenet Rapid
+# Knowledge Transfer (http://www.mibsoftware.com/userkt/inn2.0/).
+
+# The idea is that a news administrator usually must go through the
+# task of reading the inn.conf man page in parallel with the inn.conf
+# inn.conf to confirm that the settings are set as desired. Manually
+# matching up the two files can become troublesome. This script should
+# make the task easier and hopefully reduce the chance a misconfiguration
+# is missed.
+
+### Known bugs:
+# - Is very dependent on the format of the man page. It is know NOT to
+# work with the inn.conf man pages written before INN 2.3 and may
+# require minor rewriting to address future revisions of inn.conf
+# Note: this known bug is addressed via the "EDITPOD" option below
+# but is not enabled by default (details explained below).
+#
+# - SECURITY! While taken from the concept of a CGI script, it is not
+# intended to be a CGI script itself. It is *assumed* that the
+# inn.conf file is provided by a "trusted" source.
+
+### License: this script is provided under the same terms as the majority
+# of INN 2.3.0 as stated in the file "inn-2.3.0/LICENSE"
+
+### Warrenty/Disclaimer: There is no warrenty provided. For details, please
+# refer to the file "inn-2.3.0/LICENSE" from the INN 2.3 package
+
+ ################
+
+### The User Modifiable Parameters/Settings:
+
+# INNCONF should be set to the actual location of the inn.conf file
+INNCONF=/usr/local/news/etc/inn.conf
+
+# INNCONFMAN should be set to the location of the inn.conf man page
+INNCONFMAN=/usr/local/news/man/man5/inn.conf.5
+
+# INNCONFPOD should be set to the location of the inn.conf POD source
+# INNCONFPOD=/usr/local/src/inn-2.3.0/doc/pod/inn.conf.pod
+INNCONFPOD=/usr/local/news/man/man5/inn.conf.pod
+
+# NROFF should be set to an approbate program for formating the man page
+# this could be the vendor provided nroff, the FSF's groff (which could be
+# used for producing PostScript output) or Earl Hood's man2html from
+# http://www.oac.uci.edu/indiv/ehood/man2html.html
+
+# NROFF=man2html
+NROFF="nroff -man"
+
+# Pager should be set to an approbate binary for making the output
+# readable in the user's desired method. Possible settings include
+# page, more, less, ghostview, lynx, mozilla, lpr, etc. If no pager
+# application is desire then by setting it to "cat" will cause the output
+# to continue on to stdout.
+PAGER=less
+
+# By default the script uses the inn.conf man page before being processed
+# by nroff to edit in the actual inn.conf settings. The problem with this
+# approach is that if the format of the inn.conf man page ever changes
+# assumptions about the format that this script makes will probably break.
+# Presently, the base/orginal format of the inn.conf man page is in perl
+# POD documentation. The formating of this file is less likely to change
+# in the future and is a cleaner format for automated editing. However,
+# their is some disadvantages to using this file. First disadvantage,
+# the POD file is not installed by INN 2.3.0 by default (see INNCONFPOD
+# enviromental variable for setting the script to find the file in the
+# correct location). Second disadvantage, pod2man does not appear to
+# support using stdin so the edited POD must be temporarily stored as a
+# file. Finally, the last disadvantage, the script is slower due to the
+# added processing time of pod2man. Weighing the advantages and
+# disadvantages to both approaches are left to the user. If you wish to
+# have innconfcheck edit the POD file then change the variable below to
+# a setting of "1", otherwise leave it with the setting of "0"
+EDITPOD=0
+
+ ################
+
+### The Script: (non-developers should not need to go beyond this point)
+
+# All variable settings in inn.conf should not contain a comment
+# character of "#" and should have a ":" in the line. These variable names
+# should then be matched up with the man page "items" in the inn.conf file.
+# In the INN 2.3 man page, these items appear in the following format:
+# .Ip "\fIvariable name\fR" 4
+# Hence, if there exists an entry in the inn.conf of "verifycancels: false"
+# then the awk script will produce:
+# s#^.Ip "\fIvarifycancels\f$" 4#.Ip "\verifycancels: false\f$" 4#
+# once piped to sed, this expression will replace the man page item to
+# include the setting from the inn.conf file. The nroff and pager
+# applications then polish the script off to provide a documented formated
+# in a way that is easier to find incorrect setting withen.
+
+if [ $EDITPOD -eq 0 ] ; then
+
+ grep -v "#" $INNCONF | grep ":" | \
+ awk 'BEGIN { FS = ":" } { print "s#^.Ip \042\\\\fI"$1"\\\\fR\042 4#.Ip \042\\\\fI"$0"\\\\fR\042 4#" }' | \
+ sed -f - $INNCONFMAN | $NROFF | $PAGER
+
+else
+
+# The next part is similar to above but provides working from the POD source
+# instead of from the resulting nroff/man page. This section is discussed
+# in more detail above with the "EDITPOD" setting.
+
+ grep -v "#" $INNCONF | grep ":" | \
+ awk 'BEGIN { FS = ":" } { print "s#=item I<"$1">#=item I<"$0">#" }' | \
+ sed -f - $INNCONFPOD > /tmp/innconfcheck-$$
+ pod2man /tmp/innconfcheck-$$ | $NROFF | $PAGER
+ rm -f /tmp/innconfcheck-$$
+
+fi
+
+# That's all.
+# EOF
--- /dev/null
+#!/usr/local/bin/perl
+# fixscript will replace this line with require innshellvars.pl
+
+# Create expire.ctl script based on recently read articles. Argument gives
+# scale factor to use to adjust expires.
+
+$readfile="$inn::pathdb/readgroups";
+
+$expirectl=$inn::expirectl;
+if (open(RDF, $readfile)) {
+ while (<RDF>) {
+ chop;
+ @foo=split(/ /); # foo[0] should be group, foo[1] lastreadtime
+ if ($foo[1] < $oldtime) {
+ next; # skip entries that are too old.
+ }
+ $groups{$foo[0]} = $foo[1];
+ }
+ close(RDF);
+}
+
+$scale = $ARGV[0];
+if ($scale <= 0) {
+ die "invalid scale parameter\n";
+}
+
+rename($expirectl, "$expirectl.OLD") || die "rename $expirectl failed!\n";
+open(OUTFILE, ">$expirectl") || die "open $expirectl for write failed!\n";
+
+print OUTFILE <<'EOF' ;
+## expire.ctl - expire control file
+## Format:
+## /remember/:<keep>
+## <patterns>:<modflag>:<keep>:<default>:<purge>
+## First line gives history retention; other lines specify expiration
+## for newsgroups. Must have a "*:A:..." line which is the default.
+## <patterns> wildmat-style patterns for the newsgroups
+## <modflag> Pick one of M U A -- modifies pattern to be only
+## moderated, unmoderated, or all groups
+## <keep> Mininum number of days to keep article
+## <default> Default number of days to keep the article
+## <purge> Flush article after this many days
+## <keep>, <default>, and <purge> can be floating-point numbers or the
+## word "never." Times are based on when received unless -p is used;
+## see expire.8
+
+# How long to remember old history entries for.
+/remember/:2
+#
+EOF
+
+# defaults for most groups.
+printline("*", "A", 1);
+printline("alt*,misc*,news*,rec*,sci*,soc*,talk*,vmsnet*","U",3);
+printline("alt*,misc*,news*,rec*,sci*,soc*,talk*,vmsnet*","M",5);
+printline("comp*,gnu*,info*,ok*,ecn*,uok*", "U", 5);
+printline("comp*,gnu*,info*,ok*,ecn*,uok*", "M", 7);
+# and now handle each group that's regularly read,
+# assinging them 3* normal max expire
+foreach $i (keys %groups) {
+ printline($i, "A", 21);
+}
+# and now put some overrides for groups which are too likely to fill spool if
+# we let them go to autoexpire.
+printline("*binaries*,*pictures*", "A", 0.5);
+printline("control*","A",1);
+printline("control.cancel","A",0.5);
+printline("news.lists.filters,alt.nocem.misc","A",1);
+
+close(OUTFILE);
+exit(1);
+
+sub printline {
+ local($grpstr, $mflag, $len) = @_;
+ print OUTFILE $grpstr,":",$mflag,":",$len*$scale,":",$len*$scale,":",$len*$scale,"\n";
+}
--- /dev/null
+#!/usr/local/bin/perl
+# fixscript will replace this line with require innshellvars.pl
+
+# Create storage.conf script based on recently read articles.
+
+$readfile="$inn::pathdb/readgroups";
+
+$outfile="$inn::pathdb/storage.conf";
+outloop:
+for ($level=9 ; $level >= 2; --$level) {
+ # clear groups hash.
+ foreach $i (keys %groups) {
+ delete $groups{$i};
+ }
+ if (open(RDF, "sort $readfile|")) {
+ while (<RDF>) {
+ chop;
+ next if (/^group/); # bogus
+ @foo=split(/ /); # foo[0] should be group, foo[1] lastreadtime
+ @bar=split(/\./,$foo[0]);
+ if ( $level >= scalar @bar) {
+ $grf = join(".", @bar);
+ } else {
+ $grf=join(".", @bar[0..($level-1)]) . ".*";
+ }
+ $groups{$grf} = 1;
+ }
+ close(RDF);
+ }
+ $grlist = join(",",keys(%groups));
+ last outloop if (length($grlist) < 2048);
+}
+
+open(OUT, ">$outfile") || die "cant open $outfile";
+#open(OUT, ">/dev/tty");
+
+print OUT <<"EOF" ;
+method cnfs {
+ newsgroups: control,control.*
+ class: 1
+ options: MINI
+}
+
+method timecaf {
+ newsgroups: $grlist
+ class: 1
+}
+
+method cnfs {
+ newsgroups: *
+ options: MONGO
+ class: 0
+}
+EOF
+close(OUT);
+exit(0);
--- /dev/null
+#!/usr/bin/perl
+
+sub usage {
+ print STDERR "Usage: $0 <size in KB> <filename>\n";
+ exit 1;
+}
+
+usage if(@ARGV != 2);
+
+$buf1k = "\0"x1024;
+$buf1m = "$buf1k"x1024;
+
+$kb = $ARGV[0] * 1;
+&usage if($kb == 0);
+
+if($ARGV[1] eq '-') {
+ open(FILE, "|cat") or die;
+} else {
+ open(FILE, ">$ARGV[1]") or die;
+}
+
+for($i = 0; $i+1024 <= $kb; $i+=1024) {
+ print FILE $buf1m or die;
+}
+if($i < $kb) {
+ print FILE "$buf1k"x($kb-$i) or die;
+}
+
+close FILE;
--- /dev/null
+/* $Id: mlockfile.c 6014 2002-12-16 11:28:07Z alexk $ */
+
+/* Locks the files given on the command line into memory using mlock.
+ This code has only been tested on Solaris and may not work on other
+ platforms.
+
+ Contributed by Alex Kiernan <alexk@demon.net>. */
+
+#include <sys/types.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <poll.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sysexits.h>
+#include <unistd.h>
+#include <sys/mman.h>
+#include <sys/stat.h>
+#include <sys/stropts.h>
+
+struct mlock {
+ const char *path;
+ struct stat st;
+ void *base;
+ off_t offset;
+ size_t length;
+};
+
+char *progname;
+
+int flush = 0;
+int interval = 60000;
+
+void
+inn_lock_files(struct mlock *ml)
+{
+ for (; ml->path != NULL; ++ml) {
+ int fd;
+
+ fd = open(ml->path, O_RDONLY);
+ if (fd == -1) {
+ fprintf(stderr, "%s: can't open `%s' - %s\n",
+ progname, ml->path, strerror(errno));
+ } else {
+ struct stat st;
+
+ /* check if size, inode or device of the path have
+ * changed, if so unlock the previous file & lock the new
+ * one */
+ if (fstat(fd, &st) != 0) {
+ fprintf(stderr, "%s: can't stat `%s' - %s\n",
+ progname, ml->path, strerror(errno));
+ } else if (ml->st.st_ino != st.st_ino ||
+ ml->st.st_dev != st.st_dev ||
+ ml->st.st_size != st.st_size) {
+ if (ml->base != MAP_FAILED)
+ munmap(ml->base,
+ ml->length ? ml->length : ml->st.st_size);
+
+ /* free everything here, so in case of failure we try
+ * again next time */
+ ml->st.st_ino = 0;
+ ml->st.st_dev = 0;
+ ml->st.st_size = 0;
+
+ ml->base = mmap(NULL,
+ ml->length ? ml->length : st.st_size,
+ PROT_READ,
+ MAP_SHARED, fd, ml->offset);
+
+ if (ml->base == MAP_FAILED) {
+ fprintf(stderr, "%s: can't mmap `%s' - %s\n",
+ progname, ml->path, strerror(errno));
+ } else {
+ if (mlock(ml->base,
+ ml->length ? ml->length : st.st_size) != 0) {
+ fprintf(stderr, "%s: can't mlock `%s' - %s\n",
+ progname, ml->path, strerror(errno));
+ } else {
+ ml->st = st;
+ }
+ }
+ } else if (flush) {
+ msync(ml->base, ml->length ? ml->length : st.st_size, MS_SYNC);
+ }
+ }
+ close (fd);
+ }
+}
+
+static void
+usage(void)
+{
+ fprintf(stderr,
+ "usage: %s [-f] [-i interval] file[@offset[:length]] ...\n",
+ progname);
+ fprintf(stderr, " -f\tflush locked bitmaps at interval\n");
+ fprintf(stderr, " -i interval\n\tset interval between checks/flushes\n");
+}
+
+int
+main(int argc, char *argv[])
+{
+ struct mlock *ml;
+ int i;
+
+ progname = *argv;
+ while ((i = getopt(argc, argv, "fi:")) != EOF) {
+ switch (i) {
+ case 'i':
+ interval = 1000 * atoi(optarg);
+ break;
+
+ case 'f':
+ flush = 1;
+ break;
+
+ default:
+ usage();
+ return EX_USAGE;
+ }
+ }
+ argc -= optind;
+ argv += optind;
+
+ /* construct list of pathnames which we're to operate on, zero out
+ * the "cookies" so we lock it in core first time through */
+ ml = malloc((1 + argc) * sizeof ml);
+ for (i = 0; argc--; ++i, ++argv) {
+ char *at;
+ off_t offset = 0;
+ size_t length = 0;
+
+ ml[i].path = *argv;
+ ml[i].st.st_ino = 0;
+ ml[i].st.st_dev = 0;
+ ml[i].st.st_size = 0;
+ ml[i].base = MAP_FAILED;
+
+ /* if we have a filename of the form ...@offset:length, only
+ * map in that portion of the file */
+ at = strchr(*argv, '@');
+ if (at != NULL) {
+ char *end;
+
+ *at++ = '\0';
+ errno = 0;
+ offset = strtoull(at, &end, 0);
+ if (errno != 0) {
+ fprintf(stderr, "%s: can't parse offset `%s' - %s\n",
+ progname, at, strerror(errno));
+ return EX_USAGE;
+ }
+ if (*end == ':') {
+ at = end + 1;
+ errno = 0;
+ length = strtoul(at, &end, 0);
+ if (errno != 0) {
+ fprintf(stderr, "%s: can't parse length `%s' - %s\n",
+ progname, at, strerror(errno));
+ return EX_USAGE;
+ }
+ }
+ if (*end != '\0') {
+ fprintf(stderr, "%s: unrecognised separator `%c'\n",
+ progname, *end);
+ return EX_USAGE;
+ }
+ }
+ ml[i].offset = offset;
+ ml[i].length = length;
+ }
+ ml[i].path = NULL;
+
+ /* loop over the list of paths, sleeping 60s between iterations */
+ for (;;) {
+ inn_lock_files(ml);
+ poll(NULL, 0, interval);
+ }
+ return EX_OSERR;
+}
--- /dev/null
+/* newsresp.c - EUnet - bilse */
+
+/*
+ * From: Koen De Vleeschauwer <koen@eu.net>
+ * Subject: Re: innfeed-users: innfeed: measuring server response time
+ * To: jeff.garzik@spinne.com (Jeff Garzik)
+ * Date: Tue, 13 May 1997 16:33:27 +0200 (MET DST)
+ * Cc: innfeed-users@vix.com
+ *
+ * > Is there an easy way to measure server response time, and print it out
+ * > on the innfeed status page? Cyclone's nntpTime measures login banner
+ * > response time and an article add and lookup operation.
+ * >
+ * > It seems to me that innfeed could do something very similar. It could
+ * > very easily sample gettimeofday() or Time.Now to determine a remote
+ * > server's average response time for lookups, lookup failures, article
+ * > send throughput, whatever.
+ * >
+ * > These statistics might be invaluable to developers creating advanced
+ * > connection and article delivery algorithms. If I knew, for example,
+ * > that a site's article send/save throughput was really fast, but history
+ * > lookups were really slow, my algorithm could reserve a channel or two
+ * > for TAKETHIS-only use.
+ *
+ * We use a stand-alone program which opens up an additional nntp channel
+ * from time to time and takes a peek at the various response times.
+ * It's also interesting to tune one's own box.
+ * I've included the source code; please consider this supplied 'as is';
+ * bugs and features alike. SunOS, Solaris and Irix ought to be ok;
+ * eg. gcc -traditional -o newsresp ./newsresp.c -lnsl -lsocket on S0laris.
+ * If a host has an uncommonly long banner you may have to change a constant
+ * somewhere; forget. Please note one has to interpret the output;
+ * eg. whether one is measuring rtt or history lookup time.
+ *
+ * Basic usage is:
+ * news 1 % newsresp -n 5 news.eu.net
+ * ---------------------------------
+ * news.eu.net is 134.222.90.2 port 119
+ * elap diff
+ * 0.0 0.0 Connecting ...
+ * 0.0 0.0 OK, waiting for prompt
+ * 0.0 0.0 <<< 200 EU.net InterNetNews server INN 1.5.1 17-Dec-1996 re [...]
+ * 0.0 0.0 >>> ihave <244796399@a>
+ * 0.0 0.0 <<< 335
+ * 0.0 0.0 >>> .
+ * 0.0 0.0 <<< 437 Empty article
+ * 0.0 0.0 >>> ihave <244796398@a>
+ * 0.0 0.0 <<< 335
+ * 0.0 0.0 >>> .
+ * 0.0 0.0 <<< 437 Empty article
+ * 0.0 0.0 >>> ihave <244796397@a>
+ * 0.0 0.0 <<< 335
+ * 0.0 0.0 >>> .
+ * 0.0 0.0 <<< 437 Empty article
+ * 0.0 0.0 >>> ihave <244796396@a>
+ * 0.1 0.0 <<< 335
+ * 0.1 0.0 >>> .
+ * 0.1 0.0 <<< 437 Empty article
+ * 0.1 0.0 >>> ihave <244796395@a>
+ * 0.1 0.0 <<< 335
+ * 0.1 0.0 >>> .
+ * 0.1 0.0 <<< 437 Empty article
+ * 0.1 0.0 >>> quit
+ * 0.1 0.0 <<< 205 .
+ */
+
+#include <stdio.h>
+#include <sys/types.h>
+#include <sys/time.h>
+#include <sys/socket.h>
+#include <netinet/in.h>
+#include <netdb.h>
+#include <errno.h>
+
+#define NNTPPORT 119
+struct sockaddr_in sock_in;
+int sock;
+char buf[1024];
+
+main(argc,argv)
+int argc;
+char *argv[];
+{
+ int errflg = 0, c;
+ extern char *optarg;
+ extern int optind;
+ struct hostent *host;
+ unsigned long temp;
+ unsigned numart = 1;
+ struct protoent *tcp_proto;
+ char **whoP;
+
+ while ( (c = getopt(argc,argv,"n:")) != -1 )
+ switch ( c ) {
+ case 'n': sscanf(optarg,"%u",&numart); break;
+ default : errflg++;
+ }
+ if ( numart == 0 || optind == argc )
+ errflg++;
+ if ( errflg ) {
+ fprintf(stderr,"Usage: %s [-n articles] host ...\n",argv[0]);
+ exit(1);
+ }
+
+ if ( (tcp_proto = getprotobyname("tcp")) == 0 )
+ fatal("getprotobyname");
+ for ( whoP = argv+optind; *whoP != 0; whoP++ ) {
+ if ( (sock = socket(PF_INET,SOCK_STREAM,tcp_proto->p_proto)) < 0 )
+ fatal("socket");
+ temp = inet_addr(*whoP);
+ if ( temp != (unsigned long) -1 ) {
+ sock_in.sin_addr.s_addr = temp;
+ sock_in.sin_family = AF_INET;
+ }
+ else {
+ host = gethostbyname(*whoP);
+ if ( host ) {
+ sock_in.sin_family = host->h_addrtype;
+ memcpy(&sock_in.sin_addr,host->h_addr,host->h_length);
+ }
+ else {
+ fprintf(stderr,"gethostbyname can't find %s\n",*whoP);
+ exit(1);
+ }
+ }
+ sock_in.sin_port = htons(NNTPPORT);
+ printf("---------------------------------\n%s is %s port %d\n",
+ *whoP,inet_ntoa(sock_in.sin_addr),ntohs(sock_in.sin_port));
+ punt(numart);
+ close(sock);
+ }
+}
+
+error(what)
+char *what;
+{
+ ptime(); fflush(stdout);
+ perror(what);
+}
+
+fatal(what)
+char *what;
+{
+ error(what);
+ exit(2);
+}
+
+ierror(how,what)
+char *how, *what;
+{
+ printf("Expected %s, bailing out.\n",how);
+}
+
+ifatal(how,what)
+char *how, *what;
+{
+ ierror(how,what);
+ exit(1);
+}
+
+unsigned do_time(start)
+unsigned start;
+{
+ struct timeval now;
+
+ gettimeofday(&now,(struct timezone *)0);
+ return ( now.tv_sec*1000 + now.tv_usec/1000 - start );
+}
+
+
+unsigned start, elapsed, diff;
+
+ptime()
+{
+ diff = elapsed;
+ elapsed = do_time(start);
+ diff = elapsed - diff;
+ printf("%5.1f %5.1f ",((float)elapsed)/1000.0,((float)diff)/1000.0);
+}
+
+massagebuff(bread,buf)
+int bread;
+char *buf;
+{
+ char *p;
+
+ if ( bread > 55 )
+ strcpy(buf+55," [...]\n");
+ else
+ buf[bread] = '\0';
+ for ( p = buf; *p != '\0'; )
+ if ( *p != '\r' ) /* We like to do it RISC style. */
+ p++;
+ else {
+ *p = ' ';
+ p++;
+ }
+}
+
+punt(numart)
+int numart;
+{
+ static char ihave[32],
+ dot[] = ".\r\n",
+ quit[] = "quit\r\n";
+ struct timeval start_tv;
+ int bread;
+
+ printf(" elap diff\n");
+ diff = elapsed = 0;
+ gettimeofday(&start_tv,(struct timezone *)0);
+ start = start_tv.tv_sec*1000 + start_tv.tv_usec/1000;
+
+ ptime();
+ printf("Connecting ...\n");
+ if ( connect(sock,(struct sockaddr*)&sock_in,sizeof(sock_in)) < 0 ) {
+ error("connect");
+ return(-1);
+ }
+ ptime();
+ printf("OK, waiting for prompt\n");
+
+ if ( (bread=read(sock,buf,sizeof(buf))) < 0 ) {
+ error("read socket");
+ return(-1);
+ }
+ massagebuff(bread,buf);
+ ptime();
+ printf("<<< %s",buf);
+ if ( strncmp(buf,"200",3) != 0 && strncmp(buf,"201",3) != 0 ) {
+ ierror("200 or 201",buf);
+ return(-1);
+ }
+
+ do {
+ snprintf(ihave,sizeof(ihave),"ihave <%u@a>\r\n",start+numart);
+ ptime();
+ printf(">>> %s",ihave);
+ if ( write(sock,ihave,strlen(ihave)) != strlen(ihave) ) {
+ error("write socket");
+ return(-1);
+ }
+
+ if ( (bread=read(sock,buf,sizeof(buf))) < 0 ) {
+ error("read socket");
+ return(-1);
+ }
+ massagebuff(bread,buf);
+ ptime();
+ printf("<<< %s",buf);
+ if ( strncmp(buf,"335",3) != 0 && strncmp(buf,"435",3) != 0 ) {
+ ierror("335 or 435 ",buf);
+ return(-1);
+ }
+
+ if ( strncmp(buf,"335",3) == 0 ) {
+ ptime();
+ printf(">>> %s",dot);
+ if ( write(sock,dot,sizeof(dot)-1) != sizeof(dot)-1 ) {
+ error("write socket");
+ return(-1);
+ }
+
+ if ( (bread=read(sock,buf,sizeof(buf))) < 0 ) {
+ error("read socket");
+ return(-1);
+ }
+ massagebuff(bread,buf);
+ ptime();
+ printf("<<< %s",buf);
+ if ( strncmp(buf,"437",3) != 0 && strncmp(buf,"235",3) != 0 ) {
+ ierror("437 or 235",buf);
+ return(-1);
+ }
+ }
+ } while ( --numart != 0 );
+
+ ptime();
+ printf(">>> %s",quit);
+ if ( write(sock,quit,sizeof(quit)-1) != sizeof(quit)-1 ) {
+ error("write socket");
+ return(-1);
+ }
+
+ if ( (bread=read(sock,buf,sizeof(buf))) < 0 ) {
+ error("read socket");
+ return(-1);
+ }
+ massagebuff(bread,buf);
+ ptime();
+ printf("<<< %s",buf);
+ if ( strncmp(buf,"205",3) != 0 ) {
+ ierror("205",buf);
+ return(-1);
+ }
+ return(0);
+}
--- /dev/null
+/*
+June 14, 1999
+
+Recover text articles from cyclic buffers
+Articles start with "\0Path:"
+and end with "\r\n.\r\n"
+
+Tested with INND 2.2 under AIX 4.2
+
+rifkin@uconn.edu
+*/
+/*
+(1) Pull 16 bytes at a time
+(2) Last 7 bytes must be \000\000\000Path
+(3) When found, print "\nPath";
+(4) print subsequent bytes until \r\n.\r\n found
+*/
+
+#include "config.h"
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#define INFILE 1
+#define FILEPREFIX 2
+#define HEADER 3
+#define STRING 4
+
+/* String buffer size */
+#define NBUFF 512
+
+#define MAX_ART_SIZE 2200000
+
+
+#define WRITEMSG printf ("File %s line %i\n", __FILE__, __LINE__); \
+ fflush(stdout);
+
+#define WRITEVAR(VAR_NAME,VAR_TYPE) \
+ { \
+ printf ("FILE %s LINE %i :", __FILE__, __LINE__); \
+ printf ("%s = ", #VAR_NAME); \
+ printf (#VAR_TYPE, (VAR_NAME) ); \
+ printf ("\n"); \
+ }
+
+#define WRITETXT(TEXT) \
+ printf ("FILE %s LINE %i \"%s\"\n", __FILE__, __LINE__, TEXT); \
+ fflush(stdout);
+
+#if 0
+#define WRITEMSG
+#define WRITEVAR(X,Y)
+#endif
+
+
+int WriteArticle (char *, int, char *, char *, char *, int);
+
+
+char ArtHead[7] = {0, 0, 0, 'P', 'a', 't', 'h'};
+char ArtTail[5] = {'\r', '\n', '.', '\r', '\n'};
+int LenTail = 5;
+
+int main (int argc, char *argv[])
+ {
+ FILE *Infile;
+ int NumTailCharFound;
+ bool ReadingArticle = false;
+ char buffer[32];
+ char *obuffer = NULL;
+ char *header = NULL;
+ char *string = NULL;
+ int osize = MAX_ART_SIZE;
+ int opos = 0;
+ int i;
+ int nchar;
+ int fileno = 0;
+ int artno = 0;
+
+ /* Check number of args */
+ if (argc<3)
+ {
+ printf ("Usage: pullart <cycbuff> <fileprefix> [<header> <string>]\n");
+ printf (" Read cycbuffer <cycbuff> and print all articles whose\n");
+ printf (" article header <header> contains <string>.\n");
+ printf (" Articles are written to files name <fileprefix>.nnnnnn\n");
+ printf (" where nnnnnn is numbered sequentially from 0.\n");
+ printf (" If <header> and <string> not specified, all articles\n");
+ printf (" are written.\n");
+ printf (" Examples:\n");
+ printf (" pullart /news3/cycbuff.3 alt.rec Newsgroup: alt.rec\n");
+ printf (" pullart /news3/cycbuff.3 all\n");
+ printf (" pullart firstbuff article Subject bluejay\n");
+ return 0;
+ }
+
+ /* Allocate output buffer */
+ obuffer = (char *) calloc (osize+1, sizeof(char));
+ if (obuffer==NULL)
+ {
+ printf ("Cannot allocate obuffer[]\n");
+ return 1;
+ }
+
+
+ /* Open input file */
+ Infile = fopen (argv[INFILE], "rb");
+ if (Infile==NULL)
+ {
+ printf ("Cannot open input file.\n");
+ return 1;
+ }
+
+
+if (argc>=4) header = argv[HEADER];
+if (argc>=5) string = argv[STRING];
+if (*header=='\0') header=NULL;
+if (*string=='\0') string=NULL;
+
+/*test*/
+printf ("filename <%s>\n", argv[INFILE]);
+printf ("fileprefix <%s>\n", argv[FILEPREFIX]);
+printf ("header <%s>\n", header);
+printf ("string <%s>\n", string);
+
+
+ /* Skip first 0x38000 16byte buffers */
+ i = fseek (Infile, 0x38000L, SEEK_SET);
+
+ /* Read following 16 byte buffers */
+ ReadingArticle = false;
+ NumTailCharFound = 0;
+ nchar=0;
+ artno=0;
+ while ( 0!=fread(buffer, 16, 1, Infile) )
+ {
+
+ nchar+=16;
+
+ /* Found start of article, start writing to obuffer */
+ if (0==memcmp(buffer+9, ArtHead, 7))
+ {
+ ReadingArticle = true;
+ memcpy (obuffer, "Path", 4);
+ opos = 4;
+ continue;
+ }
+
+ /* Currnetly reading article */
+ if (ReadingArticle)
+ {
+ for (i=0; i<16; i++)
+ {
+
+ /* Article too big, drop it and move on */
+ if (opos>=osize)
+ {
+ printf
+ ("article number %i bigger than buffer size %i.\n",
+ artno+1, osize);
+ artno++;
+ ReadingArticle=false;
+ break;
+ }
+
+ /* Add current character to output buffer, but remove \r */
+ if ('\r' != buffer[i])
+ obuffer[opos++] = buffer[i];
+
+ /* Check for article ending sequence */
+ if (buffer[i]==ArtTail[NumTailCharFound])
+ {
+ NumTailCharFound++;
+ }
+ else
+ NumTailCharFound=0;
+
+ /* End found, write article, reset for next */
+ if (NumTailCharFound==LenTail)
+ {
+ ReadingArticle = false;
+ NumTailCharFound = 0;
+
+ /* Add trailing \0 to buffer */
+ obuffer[opos+1] = '\0';
+
+ fileno += WriteArticle
+ (obuffer, opos, argv[FILEPREFIX],
+ header, string, fileno);
+ artno++;
+ break;
+ }
+ }
+
+ }
+
+ }
+
+ close (Infile);
+
+ return 0;
+ }
+
+
+
+/*
+Writes article stored in buff[] if it has a
+"Newsgroups:" header line which contains *newsgroup
+Write to a file named fileprefix.fileno
+*/
+int
+WriteArticle
+(char *buff, int n, char *fileprefix, char *headerin, char *string, int fileno)
+ {
+ char *begptr;
+ char *endptr;
+ char *newsptr;
+ char savechar;
+ char header[NBUFF];
+ char filename[NBUFF];
+ FILE *outfile;
+
+
+ /* Prevent buffer overflow due to fileprefix too long */
+ if (strlen(fileprefix)>384)
+ {
+ printf
+ ("program error: cannot have file prefix greater then 384 characters\n");
+ exit(1);
+ }
+
+ /*
+ Is header here? Search if header string requested, leave if not found
+ */
+ if (headerin!=NULL)
+ {
+ /* Find \nHEADER */
+ strlcpy(header, "\n", sizeof(header));
+ strlcat(header, headerin, sizeof(header));
+
+ begptr = strstr (buff, header);
+
+ /* return if Header name not found */
+ if (begptr==NULL)
+ {
+ return 0;
+ }
+
+ /*
+ Header found. What about string?
+ Search if string requested, leave if not found
+ */
+ if (string!=NULL)
+ {
+ /* Find end of header line */
+ begptr++;
+ endptr = strchr (begptr, '\n');
+
+ /* Something is wrong, end of header not found, do not write
+ * article
+ */
+ if (endptr==NULL)
+ return 0;
+
+ /* Temporarily make string end a null char */
+ savechar = *endptr;
+ *endptr = '\0';
+ newsptr = strstr (begptr, string);
+
+ /* Requested newsgroup not found */
+ if (newsptr==NULL)
+ return 0;
+
+ /* Restore character at end of header string */
+ *endptr = savechar;
+ }
+ /* No string specified */
+
+ }
+ /* No header specified */
+
+ /* Open file, write buffer, close file */
+ snprintf (filename, sizeof(filename), "%s.%06i", fileprefix, fileno);
+
+ outfile = fopen (filename, "wt");
+ if (outfile==NULL) {
+ printf ("Cannot open file name %s\n", filename);
+ exit(1);
+ }
+
+ while (n--)
+ fprintf (outfile, "%c", *buff++);
+
+ close (outfile);
+
+ /* Return number of files written */
+ return 1;
+ }
--- /dev/null
+/* Quick and Dirty Hack to reset a CNFS buffer without having to DD the
+ * Entire Thing from /dev/zero again. */
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <errno.h>
+#include <fcntl.h>
+
+#include <stdio.h>
+
+/* uncomment the below for LARGE_FILES support */
+/* #define LARGE_FILES */
+
+int main(int argc, char *argv[])
+{
+ int fd;
+ int i, j;
+ char buf[512];
+#ifdef LARGE_FILES
+ struct stat64 st;
+#else
+ struct stat st;
+#endif
+ int numwr;
+
+ bzero(buf, sizeof(buf));
+ for (i = 1; i < argc; i++) {
+#ifdef LARGE_FILES
+ if ((fd = open(argv[i], O_LARGEFILE | O_RDWR, 0664)) < 0)
+#else
+ if ((fd = open(argv[i], O_RDWR, 0664)) < 0)
+#endif
+ fprintf(stderr, "Could not open file %s: %s\n", argv[i], strerror(errno));
+ else {
+#ifdef LARGE_FILES
+ if (fstat64(fd, &st) < 0) {
+#else
+ if (fstat(fd, &st) < 0) {
+#endif
+ fprintf(stderr, "Could not stat file %s: %s\n", argv[i], strerror(errno));
+ } else {
+ /* each bit in the bitfield is 512 bytes of data. Each byte
+ * has 8 bits, so calculate as 512 * 8 bytes of data, plus
+ * fuzz. buf has 512 bytes in it, therefore containing data for
+ * (512 * 8) * 512 bytes of data. */
+ numwr = (st.st_size / (512*8) / sizeof(buf)) + 50;
+ printf("File %s: %u %u\n", argv[i], st.st_size, numwr);
+ for (j = 0; j < numwr; j++) {
+ if (!(j % 100))
+ printf("\t%d/%d\n", j, numwr);
+ write(fd, buf, sizeof(buf));
+ }
+ }
+ close(fd);
+ }
+ }
+}
--- /dev/null
+/*
+** Refile articles into the storage manager under the current storage.conf
+** rules, deleting articles from their old place in the spool.
+** Written 10-09-99 by rmtodd@servalan.servalan.com
+**
+** Note that history and overview will have to be rebuilt for the moved
+** articles to be visible after they're moved.
+*/
+
+/* include foo needed by libinn/storage manager */
+#include "config.h"
+#include "clibrary.h"
+#include <errno.h>
+
+#include "inn/innconf.h"
+#include "inn/qio.h"
+#include "libinn.h"
+#include "paths.h"
+#include "storage.h"
+
+char *ME;
+
+static void
+ProcessLine(char *line)
+{
+ char *tokenptr;
+ int len;
+ ARTHANDLE *art;
+ ARTHANDLE newart;
+ TOKEN token, newtoken;
+ char *arttmp;
+ time_t arrived;
+
+ tokenptr = line;
+
+ /* zap newline at end of tokenptr, if present. */
+ len = strlen(tokenptr);
+ if (tokenptr[len-1] == '\n') {
+ tokenptr[len-1] = '\0';
+ }
+
+ token = TextToToken(tokenptr);
+ if ((art = SMretrieve(token, RETR_ALL)) == NULL) return;
+
+ len = art->len;
+ arrived = art->arrived;
+ arttmp = xmalloc(len);
+ memcpy(arttmp, art->data, len);
+ SMfreearticle(art);
+ if (!SMcancel(token)) {
+ fprintf(stderr, "%s: cant cancel %s:%s\n", ME, tokenptr, SMerrorstr);
+ return;
+ }
+
+ newart.data = arttmp;
+ newart.len = len;
+ newart.arrived = (time_t) 0; /* set current time */
+ newart.token = (TOKEN *)NULL;
+
+ newtoken = SMstore(newart);
+ if (newtoken.type == TOKEN_EMPTY) {
+ fprintf(stderr, "%s: cant store article:%s\n", ME, SMerrorstr);
+ return;
+ }
+ free(arttmp);
+ printf("refiled %s ",TokenToText(token));
+ printf("to %s\n", TokenToText(newtoken));
+ return;
+}
+
+int
+main(int argc UNUSED, char *argv[])
+{
+ bool one = true;
+ char buff[SMBUF];
+
+ ME = argv[0];
+
+ if (!innconf_read(NULL))
+ exit(1);
+
+ if (!SMsetup(SM_PREOPEN, &one) || !SMsetup(SM_RDWR, (void *)&one)) {
+ fprintf(stderr, "can't init storage manager");
+ exit(1);
+ }
+ if (!SMinit()) {
+ fprintf(stderr, "Can't init storage manager: %s", SMerrorstr);
+ }
+ while (fgets(buff, SMBUF, stdin)) {
+ ProcessLine(buff);
+ }
+ printf("\nYou will now need to rebuild history and overview for the moved"
+ "\narticles to be visible again.\n");
+ exit(0);
+}
--- /dev/null
+#!/sbin/sh
+
+# This is a simple, bare-bones example of a SysV-style init.d script for INN.
+
+case $1 in
+
+start)
+ su news -c /usr/local/news/bin/rc.news
+ ;;
+
+stop)
+ su news -c '/usr/local/news/bin/rc.news stop'
+ ;;
+
+esac
+
+exit 0
+
--- /dev/null
+#!/usr/bin/perl -w
+# showtoken - decode SM tokens
+# Olaf Titz, 1999. Marco d'Itri, 2000. Public domain.
+# Takes tokens on stdin and write them along with a decoded form on stdout.
+
+use strict;
+
+my ($pathspool, %NG);
+
+my @types = ('trash', '', 'timehash', 'cnfs', 'timecaf', 'tradspool');
+
+if ($ARGV[0]) {
+ $pathspool = $ARGV[0];
+ if (open(MAP, "$pathspool/tradspool.map")) {
+ while (<MAP>) {
+ my ($ng, $gnum) = split;
+ $NG{$gnum} = $ng;
+ }
+ close MAP;
+ }
+}
+
+$| = 1;
+while (<STDIN>) {
+ chomp;
+ next if not /^@.+@/;
+ print "$_ ";
+ splittoken($_);
+}
+
+sub splittoken {
+ my $t = shift;
+
+ $t =~ tr/@//d;
+ $t = pack('H*', $t);
+ my ($type, $class, $token, $index, $offset, $overlen, $cancelled) =
+ unpack('C C a16 CLnc', $t);
+
+ if (not $types[$type]) {
+ print "type=$type unknown!\n";
+ next;
+ }
+ print "type=$types[$type] class=$class ";
+
+ if ($type == 0) { # trash
+ } elsif ($type == 2) { # timehash
+ my ($time, $seq) = unpack('Nn', $token);
+ my ($a, $b, $c, $d) = unpack('CCCC', $token);
+ printf 'time=%08lX seq=%04X file=time-%02x/%02x/%02x/%04x-%02x%02x',
+ $time, $seq, $class, $b, $c, $seq, $a, $d;
+ } elsif ($type == 3) { # cnfs
+ my ($buffn, $offset, $cnum) = unpack('A8NN', $token);
+ printf 'buffer=%s offset=%x cycnum=%x', $buffn, $offset * 512, $cnum;
+ } elsif ($type == 4) { # timecaf
+ my ($time, $seq) = unpack('Nn', $token);
+ my (undef, $b, $c, $d) = unpack('CCCC', $token);
+ printf 'time=%06lX seq=%04X caf=timecaf-%02x/%02x/%02x%02x.CF',
+ $time, $seq, $class, $c, $b, $d;
+ } elsif ($type == 5) { # tradspool
+ my ($gnum, $art) = unpack('NN', $token);
+ printf 'ng=%08X art=%d', $gnum, $art;
+ print "file=articles/$NG{$gnum}/$art" if $NG{$gnum};
+ } else {
+ die "invalid type $type";
+ }
+ print " over=$index offset=$offset overlen=$overlen cancelled=$cancelled"
+ if length $t > 36;
+ print "\n";
+}
+__END__
+# Format of a token:
+# 1 type
+# 1 class
+# 16 token
+# 1 index
+# 4 offset
+# 2 overlen
+# 2 cancelled
+# The fields "index" and following are not available with OV3 (INN 2.3 up)
+#
+# the "token" field is:
+# for type=0 (trash) ignored
+# for type=2 (timehash)
+# 4 time
+# 2 seqnum
+# for type=3 (cnfs)
+# 8 cycbuffname
+# 4 offset/512
+# 4 cycnum
+# for type=4 (timecaf)
+# 4 time
+# 2 seqnum
+# for type=5 (tradspool)
+# 4 ngnum
+# 4 artnum
--- /dev/null
+#!/usr/bin/perl -w
+
+# Parse log files created by innd history profiler
+# 2001/01/29 - Fabien Tassin
+
+use strict;
+use FileHandle;
+
+my $file = shift || "stathist.log";
+if ($file eq '-h' || $file eq '--help') {
+ print "Usage: stathist [logfile]\n";
+ exit 0;
+}
+
+sub parse {
+ my $file = shift;
+
+ my $f = new FileHandle $file;
+ unless (defined $f) {
+ print STDERR "Can't open file: $!\n";
+ return {};
+ }
+ my $data = {};
+ my $begin = 1;
+ my @stack = ();
+ while (defined (my $line = <$f>)) {
+ next if $begin && $line !~ / HIS(havearticle|write|setup) begin/;
+ $begin = 0;
+ chomp $line;
+ my @c = split /[\[\]\(\) ]+/, $line;
+ ($c[4] eq 'begin') && do {
+ push @stack, $c[3];
+ my $d = $data;
+ for my $l (@stack) {
+ unless (defined $$d{$l}) {
+ $$d{$l}{'min'} = 1E10;
+ $$d{$l}{'total'} = $$d{$l}{'count'} = $$d{$l}{'max'} = 0;
+ }
+ $d = $$d{$l}
+ }
+ } ||
+ ($c[4] eq 'end') && do {
+ my $d = $data;
+ for my $l (@stack) {
+ $d = $$d{$l};
+ }
+ $$d{'count'}++;
+ $$d{'total'} += $c[5];
+ $$d{'min'} = $c[5] if $$d{'min'} > $c[5];
+ $$d{'max'} = $c[5] if $$d{'max'} < $c[5];
+ pop @stack;
+ };
+ }
+ $f->close;
+ $data;
+}
+
+sub report {
+ my $data = shift;
+ my $inc = shift;
+
+ unless (defined $inc) {
+ printf "%-16s %10s %14s %10s %10s %10s\n\n", "Function", "Invoked",
+ "Total(s)", "Min(ms)", "Avg(ms)", "Max(ms)";
+ $inc = 0;
+ }
+
+ for my $key (sort keys %$data) {
+ next unless $key =~ m/^HIS/;
+ printf "%-16s %10d %14.6f %10.3f %10.3f %10.3f\n", (' ' x $inc) . $key,
+ $$data{$key}{'count'}, $$data{$key}{'total'}, $$data{$key}{'min'} * 1000,
+ $$data{$key}{'total'} / $$data{$key}{'count'} * 1000,
+ $$data{$key}{'max'} * 1000;
+ &report($$data{$key}, $inc + 1)
+ }
+}
+
+my $data = &parse($file);
+&report($data);
--- /dev/null
+#!/usr/bin/perl -w
+# fixscript will replace this line with require innshellvars.pl
+$ID='$Id: thdexpire.in 4572 2001-02-24 22:31:05Z rra $$';
+
+use POSIX ":fcntl_h";
+use SDBM_File;
+use Getopt::Std;
+
+# With the -M switch this program installs its own man page.
+#-----------------------------------------------------------------------------
+
+=head1 NAME
+
+thdexpire - dynamic expire daemon for timehash and timecaf storage
+
+=head1 SYNOPSIS
+
+B<thdexpire>
+[ B<-t> I<minutes> ]
+[ B<-f> I<kilobytes> ]
+[ B<-i> I<inodes> ]
+[ B<-m> I<mindays> ]
+[ B<-x> I<minseconds> ]
+[ B<-N> ]
+[ B<-v> I<level> ]
+
+B<thdexpire -r>
+
+=head1 DESCRIPTION
+
+This is a daemon, to be started along with B<innd>, which periodically
+looks if news spool space is getting tight, and frees space by removing
+articles until enough is free. It is an adjunct (not a replacement) to
+INNs B<expire> program.
+
+=head2 Setting Up
+
+=over 4
+
+=item 1.
+
+Configure your storage classes carefully. Let the default go in class
+100 and choose the storage classes as relative (percent) retention
+times. E.g. if you want to give C<alt.binaries.*> a fifth of the
+default time, put them in class 20. Storage classes above 200 are
+ignored by this program. 0 expires immediately. An example is given
+in L<"EXAMPLES">.
+
+=item 2.
+
+Set up your F<expire.ctl> in a way that it puts only a maximum cap on
+retention times. Run B<expire> from B<news.daily> as usual. However,
+it should only expire articles which have an Expires line or are in
+classes above 200. See L<"EXAMPLES">.
+
+=item 3.
+
+Ensure to start this daemon along with B<innd>.
+
+=item 4.
+
+To get information and statistics, run B<thdexpire -r> (in parallel to
+a running daemon). This will show you the current actual retention
+times.
+
+=back
+
+=head2 How It Works
+
+B<thdexpire> works directly on the spool. It assumes the layout
+described in the timehash and timecaf sections of L<storage.conf(5)> as of
+INN-2.x-CURRENT (Dec. 5, 1998). For every storage class associated
+with timehash/timecaf, B<thdexpire> keeps a I<work time> which is the
+modification time of the oldest article/CAF file in this class. This
+time is chosen so that the difference of the work time of class N to
+now (i.e. the I<retention time> for class N) will be N/100 of the
+retention time of class 100. The work time of all classes is
+continuously adjusted as time goes by. Articles and CAF files which
+are older than the work time are deleted.
+
+=head1 OPTIONS
+
+=over 8
+
+=item B<-t> I<minutes>
+
+Check for free space every I<minutes> minutes (default 30).
+
+=item B<-f> I<kilobytes>
+
+Leave I<kilobytes> kilobytes of free disk space on each spool
+filesystem (default 50000).
+
+=item B<-i> I<inodes>
+
+Leave I<inodes> inodes free on each spool filesystem (default 5000).
+
+=item B<-m> I<mindays>
+
+Set the minimum normal holding time for class 100 to I<mindays> days
+(default 7).
+
+=item B<-x> I<minseconds>
+
+Set the absolute minimum holding time for any article to I<minseconds>
+seconds (default 86400, i.e. 1 day).
+
+=item B<-N>
+
+Do not delete any articles, just print what would be done.
+
+=item B<-v> I<level>
+
+Set the verbosity level. Values from 1 to 3 are meaningful, where
+higher levels are mostly for debugging.
+
+=item B<-r>
+
+Do not run as a daemon, instead print a report from the database (see
+L<FILES>) on the available storage classes, current expire times and
+other stuff.
+
+=back
+
+=head1 EXAMPLES
+
+Here is an example F<storage.conf> file:
+
+ # Large postings in binary groups are expired fast:
+ # 20% retention time
+ method timehash {
+ newsgroups: *.binaries.*,*.binaer.*,*.dateien.*,alt.mag.*
+ size: 30000
+ class: 20
+ }
+
+ # Local groups and *.answers groups don't expire at all with
+ # thdexpire. These are handled by Expires lines and a cutoff
+ # in expire.ctl.
+ method timehash {
+ newsgroups: *.answers,news.announce.*,local.*
+ class: 201
+ }
+
+ # Expires lines are honored if they dont exceed 90 days.
+ # Exempt those postings from thdexpire handling.
+ method timehash {
+ newsgroups: *
+ expires: 1d,90d
+ class: 202
+ }
+
+ # Default: should be class 100 because thdexpire bases its
+ # calculations thereupon.
+ method timecaf {
+ newsgroups: *
+ class: 100
+ }
+
+And here is an F<expire.ctl> which fits:
+
+ # Our local groups are held 6 months
+ local.*:A:7:180:180
+ # Everything else is handled by thdexpire, or Expires lines
+ *:A:7:never:never
+
+Note that B<thdexpire> does not actually use these files, they just
+configure other parts of the news system in an appropriate way.
+
+=head1 FILES
+
+=over 4
+
+=item F<E<lt>inn::pathdbE<gt>/thdexpstat.{dir,pag}>
+
+Holds state information like classes, expire times, oldest articles.
+When this file is missing, it will be rebuilt the next time the daemon
+is started, which basically means scanning the spool directories to
+find the oldest articles. With the B<-r> option, the contents of this
+file are printed.
+
+=item F<E<lt>inn::innddirE<gt>/thdexpire.pid>
+
+Contains the PID of the running daemon.
+
+=back
+
+=head1 SIGNALS
+
+I<SIGINT> or I<SIGTERM> can be sent to the daemon at any time, causing
+it to gracefully exit immediately.
+
+=head1 SEE ALSO
+
+L<expire(8)>, L<news.daily(8)>, L<storage.conf(5)>
+
+=head1 NOTES
+
+This version needs the B<inndf> program supplied with newer releases of INN.
+
+The filenames for timecaf were wrong in older versions of the INN
+documentation. This program uses the true filenames, as found by
+reading the INN source.
+
+=head1 DIAGNOSTICS
+
+Any error messages are printed on standard error. Normal progress
+messages, as specified by the B<-v> option, are printed on standard
+output.
+
+=head1 BUGS
+
+Storage classes which are in I<storage.conf> but not on disk (i.e.
+which have never been filed into) when the daemon starts are ignored.
+
+The code is ugly and uses too many global variables.
+Should probably rewrite it in C.
+
+=head1 RESTRICTIONS
+
+Directories which are left empty are not removed.
+
+The overview database is not affected by B<thdexpire>, it has to be
+cleaned up by the daily regular B<news.daily> run. This may need a
+patch to B<expire>.
+
+=head1 AUTHOR
+
+Olaf Titz <olaf@bigred.inka.de>. Use and distribution of this work is
+permitted under the same terms as the B<INN> package.
+
+=head1 HISTORY
+
+Inspired by the old B<dexpire> program for the traditional spool.
+
+June 1998: wrote the first version for timehash.
+
+November 1998: added code for timecaf, works on multiple spool
+filesystems, PODed documentation.
+
+July 1999: bugfixes.
+
+=cut
+
+#-----------------------------------------------------------------------------
+
+chdir $inn::spool || die "chdir $inn::spool: $!";
+$opt_r=0; # make a report
+$opt_t=30; # check interval in minutes
+$opt_f=50000; # required space in kilobytes
+$opt_i=5000; # required space in inodes
+$opt_m=7; # minimum normal (class 100) time in days
+$opt_x=86400; # absolute minimum hold time in seconds
+$opt_N=0; # dont actually delete articles
+$opt_v=0; # verbosity level
+$opt_M=0; # install man page
+getopts("rt:f:i:m:x:Nv:M");
+
+$_=$inn::pathdb; $_=$inn::pathnews; # shut up warning
+$sfile="$inn::pathdb/thdexpstat";
+$ID=~/ ([^,]+,v [^ ]+)/; $ID=$1;
+
+if ($opt_M) {
+ print "Installing thdexpire(8) man page\n";
+ $0=~m:^(.*)/([^/]+)$:;
+ chdir $1 || die "chdir $1";
+ exec "pod2man --section=8 --center='Contributed News Software'" .
+ " --release='$ID' $2 >$inn::pathnews/man/man8/thdexpire.8";
+}
+
+if ($opt_r) {
+ tie(%S, SDBM_File, $sfile, O_RDONLY, 0664) || die "open $sfile: $!";
+ &report;
+ untie %S;
+ exit 0;
+}
+
+(system "shlock", "-p", $$, "-f", "$inn::innddir/thdexpire.pid")>>8==0
+ || die "Already running";
+tie(%S, SDBM_File, $sfile, O_RDWR|O_CREAT, 0664) || die "open $sfile: $!";
+$SIG{'TERM'}=$SIG{'INT'}='finish';
+$|=1;
+printf "%s starting at %s\n", $ID, &wtime(time) if ($opt_v>0);
+
+undef @c;
+$NOW=time; $ac=$cc=0;
+opendir(CD, ".") || &err("opendir $inn::spool: $!");
+while ($cd=readdir(CD), defined($cd)) {
+ $cd=~/^time(caf)?-([0-9a-f][0-9a-f])$/i || next;
+ $c{hex($2)}=1 unless hex($2)>200;
+}
+closedir CD;
+@classes=sort {$a<=>$b} keys %c;
+foreach $c (@classes) {
+ &initclass($c);
+ $S{"work$;$c"}=$S{"oldest$;$c"}&0xFFFFFF00;
+}
+
+$S{"classes"}=join(",", @classes);
+$S{"inittime"}=time;
+$S{"ID"}=$ID;
+printf "Checked %d articles, %d CAFs in %d seconds\n", $ac, $cc, time-$NOW
+ if ($ac+$cc>0 && $opt_v>0);
+
+chdir $inn::spool || die "chdir $inn::spool: $!";
+while (1) {
+ $S{"lastrun"}=$NOW=time;
+ printf "%s\n", &wtime($NOW) if ($opt_v>0);
+ $nt=0;
+ foreach $c (@classes) {
+ $t=($NOW-$S{"work$;$c"})*100/$c;
+ $nt=$t if ($nt<$t);
+ }
+ printf "Normal time (class 100): %s\n", &xtime($NOW-$nt)
+ if ($opt_v>0);
+ if ($nt<$opt_m*24*60*60) {
+ printf " capped at minimum %d days\n", $opt_m
+ if ($opt_v>0);
+ $nt=$opt_m*24*60*60;
+ }
+ if ($nt>180*24*60*60) {
+ print " capped at maximum 180 days\n"
+ if ($opt_v>0);
+ $nt=180*24*60*60;
+ }
+ $S{"normaltime"}=$nt;
+ $decrement=$opt_t*60;
+ $pass=$need=0;
+ $x="/";
+ undef %needk; undef %needi;
+ foreach $c (@classes) {
+ $Dart{$c}=$Dcaf{$c}=$Dkb{$c}=$Dino{$c}=0;
+ $y=sprintf("time-%02x", $c);
+ if (-d $y) {
+ @S=stat(_);
+ if ($#S>=0) {
+ $dev{$y}=$S[0];
+ unless (defined($needk{$S[0]})) {
+ $x.=" $y";
+ $needk{$S[0]}=$needi{$S[0]}=-1;
+ }
+ }
+ }
+ $y=sprintf("timecaf-%02x", $c);
+ if (-d $y) {
+ @S=stat(_);
+ if ($#S>=0) {
+ $dev{$y}=$S[0];
+ unless (defined($needk{$S[0]})) {
+ $x.=" $y";
+ $needk{$S[0]}=$needi{$S[0]}=-1;
+ }
+ }
+ }
+ }
+ if (open(D, "inndf $x |")) {
+ while (<D>) {
+ @S=split(/\s+/, $_);
+ $needk{$dev{$S[0]}}=$opt_f-$S[1] unless ($S[0] eq "/");
+ }
+ close D;
+ }
+ if (open(D, "inndf -i $x |")) {
+ while (<D>) {
+ @S=split(/\s+/, $_);
+ $needi{$dev{$S[0]}}=$opt_i-$S[1] unless ($S[0] eq "/");
+ }
+ close D;
+ }
+ foreach $c (keys %needk) {
+ printf "Device %d needs to free %d kilobytes, %d inodes\n",
+ $c, $needk{$c}<0?0:$needk{$c}, $needi{$c}<0?0:$needi{$c}
+ if ($opt_v>0 && ($needk{$c}>0 || $needi{$c}>0));
+ if ($needk{$c}>0 || $needi{$c}>0) {
+ ++$need;
+ }
+ }
+ if ($opt_v>0 && $need<=0) {
+ print " (nothing to do)\n";
+ $tt=0;
+ } else {
+ $error=0;
+ while (!$error && $need>0) {
+ if ($S{"normaltime"}-$decrement<$opt_m*24*60*60) {
+ print " Normal time hit minimum\n" if ($opt_v>0);
+ last;
+ }
+ $S{"normaltime"}-=$decrement;
+ printf " normal time (100) becomes %ld\n", $S{"normaltime"}
+ if ($opt_v>2);
+ ++$pass;
+ $Dart=$Dcaf=$Dkb=$Dino=$need=0;
+ foreach $c (keys %needk) {
+ if ($needk{$c}>0 || $needi{$c}>0) {
+ ++$need;
+ }
+ }
+ if ($need) {
+ foreach $c (@classes) {
+ &worktime($c, $NOW-($S{"normaltime"}*$c/100));
+ $Dart+=$dart; $Dcaf+=$dcaf; $Dkb+=$dbb>>10; $Dino+=$dino;
+ $Dart{$c}+=$dart; $Dcaf{$c}+=$dcaf;
+ $Dkb{$c}+=$dbb>>10; $Dino{$c}+=$dino;
+ last if ($error);
+ }
+ }
+ if ($Dart+$Dcaf) {
+ printf " pass %d deleted %d arts, %d CAFs, %d kb\n",
+ $pass, $Dart, $Dcaf, $Dkb if ($opt_v>1);
+ $decrement-=$decrement>>2 if ($decrement>10*60);
+ } else {
+ $decrement+=$decrement>>1 if ($decrement<4*60*60);
+ }
+ }
+ $Dkb=$Dart=$Dcaf=$Dino=0;
+ foreach $c (@classes) {
+ printf " class %3d: deleted %6d arts %6d CAFs %10d kb\n",
+ $c, $Dart{$c}, $Dcaf{$c}, $Dkb{$c} if ($opt_v>1);
+ $Dkb+=$Dkb{$c}; $Dart+=$Dart{$c}; $Dcaf+=$Dcaf{$c};
+ }
+ $tt=time-$NOW;
+ printf " deleted %d articles, %d CAFs, %d kb in %d seconds\n",
+ $Dart, $Dcaf, $Dkb, time-$NOW if ($opt_v>0);
+ if ($tt>$opt_t*60) {
+ printf STDERR "Round needed %d seconds, interval is %d\n",
+ $tt, $opt_t*60;
+ $tt=$opt_t*60;
+ }
+ }
+ sleep $opt_t*60-$tt;
+}
+&finish(0);
+
+
+sub initclass
+{
+ my $C=shift;
+ if (!$S{"blocksize$;$C$;CAF"}) {
+ # Determine filesystem blocksize
+ # unfortunately no way in perl to statfs
+ my $x=sprintf("%s/timecaf-%02x/test%d", $inn::spool, $C, $$);
+ if (open(A, ">$x")) {
+ print A "X" x 4096;
+ close A;
+ @S=stat $x;
+ $#S>=12 || die "stat: $!";
+ if ($S[12]) {
+ $S{"blocksize$;$C$;CAF"}=$S[7]/$S[12];
+ } else {
+ $S{"blocksize$;$C$;CAF"}=512;
+ warn "hack around broken stat blocksize";
+ }
+ unlink $x;
+ }
+ }
+ return if ($S{"oldest$;$C"});
+ my $oldest=time;
+ $S{"oldest$;$C"}=$oldest;
+ my $base=sprintf("%s/time-%02x", $inn::spool, $C);
+ my $count=0;
+ if (chdir $base) {
+ printf "Finding oldest in class %d (%s)\n", $C, $base if ($opt_v>0);
+ opendir(D0, ".");
+ while ($d1=readdir(D0), defined($d1)) {
+ $d1=~/^[0-9a-f][0-9a-f]$/ || next;
+ chdir $d1;
+ opendir(D1, ".") || next;
+ while ($d2=readdir(D1), defined($d2)) {
+ $d2=~/^[0-9a-f][0-9a-f]$/ || next;
+ chdir $d2;
+ opendir(D2, ".") || next;
+ while ($a=readdir(D2), defined($a)) {
+ $a=~/^\./ && next;
+ @S=stat($a);
+ $oldest=$S[9] if ($S[9]<$oldest);
+ ++$count;
+ }
+ closedir D2;
+ chdir "..";
+ }
+ closedir D1;
+ chdir "..";
+ }
+ closedir D0;
+ $ac+=$count;
+ }
+ $base=sprintf("%s/timecaf-%02x", $inn::spool, $C);
+ if (chdir $base) {
+ printf "Finding oldest in class %d (%s)\n", $C, $base if ($opt_v>0);
+ opendir(D0, ".");
+ while ($d1=readdir(D0), defined($d1)) {
+ $d1=~/^[0-9a-f][0-9a-f]$/ || next;
+ chdir $d1;
+ opendir(D1, ".") || next;
+ while ($a=readdir(D1), defined($a)) {
+ $a=~/^\./ && next;
+ @S=stat($a);
+ $oldest=$S[9] if ($S[9]<$oldest);
+ ++$count;
+ }
+ closedir D1;
+ chdir "..";
+ }
+ closedir D0;
+ $cc+=$count;
+ }
+ $S{"count$;$C"}=$count;
+ $S{"oldest$;$C"}=$oldest;
+}
+
+sub worktime
+{
+ my $C=shift;
+ my $goal=shift;
+ $goal&=0xFFFFFF00;
+ printf " goal for class %d becomes %s\n", $C, &xtime($goal)
+ if ($opt_v>2);
+ if ($goal>$NOW-$opt_x) {
+ printf " goal for class %d cut off\n", $C
+ if ($opt_v>1);
+ $error=1;
+ return;
+ }
+ $dart=$dcaf=$dbb=$dino=0;
+ $hdir=sprintf("time-%02x", $C);
+ $cdir=sprintf("timecaf-%02x", $C);
+ while (($_=$S{"work$;$C"})<$goal) {
+ printf " running: %08x\n", $_ if ($opt_v>2);
+ ($aa,$bb,$cc) = (($_>>24)&0xFF, ($_>>16)&0xFF, ($_>>8)&0xFF);
+ $dir=sprintf("%s/%02x/%02x", $hdir, $bb, $cc);
+ $pat=sprintf("[0-9a-f]{4}-%02x[0-9a-f]{2}", $aa);
+ if (opendir(D, $dir)) {
+ while ($_=readdir(D), defined($_)) {
+ /^$pat$/ || next;
+ $art="$dir/$_";
+ @S=stat($art);
+ if ($#S>=7) {
+ if ($opt_N) {
+ print " would delete $art" if ($opt_v>2);
+ } else {
+ print " deleting $art" if ($opt_v>2);
+ unlink $art;
+ }
+ ++$dart; ++$dino;
+ printf " %d kb\n", $S[7]>>10 if ($opt_v>2);
+ $dbb+=$S[7];
+ $needk{$dev{$hdir}}-=$S[7]>>10;
+ $needi{$dev{$hdir}}--;
+ }
+ }
+ } else {
+ printf " (no dir %s)\n", $dir if ($opt_v>2);
+ }
+ $caf=sprintf("%s/%02x/%02x%02x.CF", $cdir, $bb, $aa, $cc);
+ @S=stat($caf);
+ if ($#S>=12) {
+ if ($opt_N) {
+ print " would delete $caf" if ($opt_v>2);
+ } else {
+ print " deleting $caf" if ($opt_v>2);
+ unlink $caf;
+ }
+ $y=0;
+ if (open(C, $caf)) {
+ # try to find how much there is in the CAF
+ sysread(C, $_, 16);
+ @C=unpack("a4LLL", $_);
+ if ($C[0] eq "CRMT") {
+ $y=$C[3]-$C[1];
+ $dart+=$y;
+ }
+ close C;
+ }
+ ++$dcaf; ++$dino;
+ if ($S[12]) {
+ $x=$S[12]*$S{"blocksize$;$C$;CAF"};
+ } else {
+ $x=$S[7];
+ warn "hack around broken stat blocksize";
+ }
+ printf " %d arts %d kb\n", $y, $x>>10 if ($opt_v>2);
+ $dbb+=$x;
+ $needk{$dev{$cdir}}-=$x>>10;
+ $needi{$dev{$cdir}}--;
+ }
+ $S{"work$;$C"}+=0x100;
+ $S{"oldest$;$C"}=$S{"work$;$C"} unless ($opt_N);
+ }
+}
+
+sub report
+{
+ $NOW=time;
+ my $cc=$S{"classes"};
+ my $nt=$S{"normaltime"};
+ unless ($cc && $nt) {
+ print "Not initialized.\n";
+ return;
+ }
+ printf "Version: %s (this: %s)\n", $S{"ID"}, $ID;
+ printf "Started at: %s\n", &xtime($S{"inittime"}) if ($S{"inittime"});
+ printf "Last run: %s\n", &xtime($S{"lastrun"}) if ($S{"lastrun"});
+ printf "Classes: %s\n", $cc;
+ foreach $c (split(/,/, $cc)) {
+ printf "Class %d:\n", $c;
+ #printf " Initial count %d articles\n", $S{"count$;$c"};
+ printf " Oldest article: %s\n", &xtime($S{"oldest$;$c"});
+ printf " Expiring at: %s\n", &xtime($S{"work$;$c"});
+ printf " Normal time: %s\n", &xtime($NOW-$nt*$c/100);
+ printf " Filesystem block size (CAF): %d\n", $S{"blocksize$;$c$;CAF"};
+ }
+}
+
+sub wtime
+{
+ my $t=shift;
+ my @T=localtime($t);
+ sprintf("%04d-%02d-%02d %02d:%02d",
+ $T[5]+1900, $T[4]+1, $T[3], $T[2], $T[1]);
+}
+
+sub xtime
+{
+ my $t=shift;
+ if ($NOW-$t<0 || $NOW-$t>350*24*60*60) {
+ return &wtime($t);
+ }
+ my @T=localtime($t);
+ my @D=gmtime($NOW-$t);
+ sprintf("%04d-%02d-%02d %02d:%02d (%dd %dh %dm)",
+ $T[5]+1900, $T[4]+1, $T[3], $T[2], $T[1],
+ $D[7], $D[2], $D[1]);
+}
+
+sub err
+{
+ printf STDERR "%s\n", shift;
+ &finish(0);
+}
+
+sub finish
+{
+ untie(%S);
+ unlink "$inn::innddir/thdexpire.pid";
+ exit 0;
+}
+#-----------------------------------------------------------------------------
--- /dev/null
+#!/usr/bin/perl
+$version = q$Id: tunefeed.in 4329 2001-01-14 13:47:52Z rra $;
+#
+# tunefeed -- Compare active files with a remote site to tune a feed.
+# Copyright 1998 by Russ Allbery <rra@stanford.edu>
+#
+# This program is free software; you can redistribute it and/or modify it
+# under the same terms as Perl itself.
+
+############################################################################
+# Site configuration
+############################################################################
+
+# A list of hierarchies in the Big Eight.
+%big8 = map { $_ => 1 } qw(comp humanities misc news rec sci soc talk);
+
+# A list of hierarchies that are considered global and not language
+# hierarchies.
+%global = map { $_ => 1 } qw(bionet bit biz borland ddn gnu gov ieee info
+ linux k12 microsoft netscape tnn vmsnet);
+
+# The pattern matching local-only hierarchies (that we should disregard when
+# doing feed matching).
+%ignore = map { $_ => 1 } qw(clari control junk);
+
+
+############################################################################
+# Modules and declarations
+############################################################################
+
+require 5.003;
+
+use Getopt::Long qw(GetOptions);
+
+use strict;
+use vars qw(%big8 $days %global %ignore $threshold %traffic $version);
+
+
+############################################################################
+# Active file hashing and analysis
+############################################################################
+
+# Read in an active file, putting those groups into a hash where the key is
+# the name of the group and the value is always 1. If the optional third
+# argument is true, exclude any groups in the hierarchies listed in %local
+# and use this active file to store traffic information (in a rather
+# simple-minded fashion).
+sub hash {
+ my ($file, $hash, $local) = @_;
+ open (ACTIVE, $file) or die "$0: cannot open $file: $!\n";
+ local $_;
+ while (<ACTIVE>) {
+ my ($group, $high, $low, $flags) = split;
+ next if ($flags =~ /^=|^x/);
+ my $hierarchy = (split (/\./, $group, 2))[0];
+ next if ($local && $ignore{$hierarchy});
+ $$hash{$group} = 1;
+ $traffic{$group} = ($high - $low) / $days if $local;
+ }
+ close ACTIVE;
+}
+
+# Read in a file that gives traffic statistics. We assume it's in the form
+# group, whitespace, number of articles per day, and we just read it
+# directly into the %traffic hash.
+sub traffic {
+ my ($file) = @_;
+ open (TRAFFIC, $file) or die "$0: cannot open $file: $!\n";
+ local $_;
+ while (<TRAFFIC>) {
+ my ($group, $traffic) = split;
+ $traffic{$group} = $traffic;
+ }
+ close TRAFFIC;
+}
+
+# Pull off the first X nodes of a group name.
+sub prefix {
+ my ($group, $count) = @_;
+ my @group = split (/\./, $group);
+ splice (@group, $count);
+ join ('.', @group);
+}
+
+# Find the common hierarchical prefix of a list.
+sub common {
+ my (@list) = @_;
+ my @prefix = split (/\./, shift @list);
+ local $_;
+ while (defined ($_ = shift @list)) {
+ my @group = split /\./;
+ my $i;
+ $i++ while ($prefix[$i] && $prefix[$i] eq $group[$i]);
+ if ($i <= $#prefix) { splice (@prefix, $i) }
+ }
+ join ('.', @prefix);
+}
+
+# Given two lists, a list of groups that the remote site does have and a
+# list of groups that the remote site doesn't have, in a single hierarchy,
+# perform a smash. The object is to find the minimal pattern that expresses
+# just the groups they want. We're also given the common prefix of all the
+# groups in the have and exclude lists, and a flag indicating whether we're
+# coming in with a positive assumption (all groups sent unless excluded) or
+# a negative assumption (no groups sent unless added).
+sub smash {
+ my ($have, $exclude, $top, $positive) = @_;
+ my (@positive, @negative);
+ my $level = ($top =~ tr/././) + 1;
+
+ # Start with the positive assumption. We make copies of our @have and
+ # @exclude arrays since we're going to be needing the virgin ones again
+ # later for the negative assumption. If we're coming in with the
+ # negative assumption, we have to add a wildcarded entry to switch
+ # assumptions, and we also have to deal with the cases where there is a
+ # real group at the head of the hierarchy.
+ my @have = @$have;
+ my @exclude = @$exclude;
+ if ($top eq $have[0]) {
+ shift @have;
+ push (@positive, "$top*") unless $positive;
+ } else {
+ if ($top eq $exclude[0]) {
+ if ($positive && $traffic{$top} > $threshold) {
+ push (@positive, "!$top");
+ }
+ shift @exclude;
+ }
+ push (@positive, "$top.*") unless $positive;
+ }
+
+ # Now that we've got things started, keep in mind that we're set up so
+ # that every group will be sent *unless* it's excluded. So we step
+ # through the list of exclusions. The idea here is to pull together all
+ # of the exclusions with the same prefix (going one level deeper into
+ # the newsgroup names than we're currently at), and then find all the
+ # groups with the same prefix that the remote site *does* want. If
+ # there aren't any, then we can just exclude that whole prefix provided
+ # that we're saving enough traffic to make it worthwhile (checked
+ # against the threshold). If there are, and if the threshold still
+ # makes it worthwhile to worry about this, we call this sub recursively
+ # to compute the best pattern for that prefix.
+ while (defined ($_ = shift @exclude)) {
+ my ($prefix) = prefix ($_, $level + 1);
+ my @drop = ($_);
+ my @keep;
+ my $traffic = $traffic{$_};
+ while ($exclude[0] =~ /^\Q$prefix./) {
+ $traffic += $traffic{$exclude[0]};
+ push (@drop, shift @exclude);
+ }
+ $prefix = common (@drop);
+ my $saved = $traffic;
+ while (@have && $have[0] le $prefix) { shift @have }
+ while ($have[0] =~ /^\Q$prefix./) {
+ $traffic += $traffic{$have[0]};
+ push (@keep, shift @have);
+ }
+ next unless $saved > $threshold;
+ if (@keep) {
+ $traffic{"$prefix*"} = $traffic;
+ push (@positive, smash (\@keep, \@drop, $prefix, 1));
+ } elsif (@drop == 1) {
+ push (@positive, "!$_");
+ } elsif ($prefix eq $_) {
+ push (@positive, "!$prefix*");
+ } else {
+ push (@positive, "!$prefix.*");
+ }
+ }
+
+ # Now we do essentially the same thing, but from the negative
+ # perspective (adding a wildcard pattern as necessary to make sure that
+ # we're not sending all groups and then finding the groups we are
+ # sending and trying to smash them into minimal wildcard patterns).
+ @have = @$have;
+ @exclude = @$exclude;
+ if ($top eq $exclude[0]) {
+ shift @exclude;
+ push (@negative, "!$top*") if $positive;
+ } else {
+ if ($top eq $have[0]) {
+ push (@negative, $top) unless $positive;
+ shift @have;
+ }
+ push (@negative, "!$top.*") if $positive;
+ }
+
+ # This again looks pretty much the same as what we do for the positive
+ # case; the primary difference is that we have to make sure that we send
+ # them every group that they want, so we still err on the side of
+ # sending too much, rather than too little.
+ while (defined ($_ = shift @have)) {
+ my ($prefix) = prefix ($_, $level + 1);
+ my @keep = ($_);
+ my @drop;
+ my $traffic = $traffic{$_};
+ while ($have[0] =~ /^\Q$prefix./) {
+ $traffic += $traffic{$have[0]};
+ push (@keep, shift @have);
+ }
+ $prefix = common (@keep);
+ while (@exclude && $exclude[0] le $prefix) { shift @exclude }
+ my $saved = 0;
+ while ($exclude[0] =~ /^\Q$prefix./) {
+ $saved += $traffic{$exclude[0]};
+ push (@drop, shift @exclude);
+ }
+ if (@drop && $saved > $threshold) {
+ $traffic{"$prefix*"} = $traffic + $saved;
+ push (@negative, smash (\@keep, \@drop, $prefix, 0));
+ } elsif (@keep == 1) {
+ push (@negative, $_);
+ } elsif ($prefix eq $_) {
+ push (@negative, "$prefix*");
+ } else {
+ push (@negative, "$prefix.*");
+ }
+ }
+
+ # Now that we've built both the positive and negative case, we decide
+ # which to return. We want the one that's the most succinct, and if
+ # both descriptions are equally succinct, we return the negative case on
+ # the grounds that it's likely to send less of what they don't want.
+ (@positive < @negative) ? @positive : @negative;
+}
+
+
+############################################################################
+# Output
+############################################################################
+
+# We want to sort Big Eight ahead of alt.* ahead of global non-language
+# hierarchies ahead of regionals and language hierarchies.
+sub score {
+ my ($hierarchy) = @_;
+ if ($big8{$hierarchy}) { return 1 }
+ elsif ($hierarchy eq 'alt') { return 2 }
+ elsif ($global{$hierarchy}) { return 3 }
+ else { return 4 }
+}
+
+# Our special sort routine for hierarchies. It calls score to get a
+# hierarchy score and sorts on that first.
+sub by_hierarchy {
+ (score $a) <=> (score $b) || $a cmp $b;
+}
+
+# Given a reference to a list of patterns, output it in some reasonable
+# form. Currently, this is lines prefixed by a tab, with continuation lines
+# like INN likes to have in newsfeeds, 76 column margin, and with a line
+# break each time the hierarchy score changes.
+sub output {
+ my ($patterns) = @_;
+ my ($last, $line);
+ for (@$patterns) {
+ my ($hierarchy) = /^!?([^.]+)/;
+ my $score = score $hierarchy;
+ $line += 1 + length $_;
+ if (($last && $score > $last) || $line > 76) {
+ print ",\\\n\t";
+ $line = 8 + length $_;
+ } elsif ($last) {
+ print ',';
+ } else {
+ print "\t";
+ $line += 8;
+ }
+ print;
+ $last = $score;
+ }
+ print "\n";
+}
+
+
+############################################################################
+# Main routine
+############################################################################
+
+# Clean up the name of this program for error messages.
+my $fullpath = $0;
+$0 =~ s%.*/%%;
+
+# Parse the command line. Our argument is the path to an active file (we
+# tell the difference by seeing if it contains a /).
+my ($help, $print_version);
+Getopt::Long::config ('bundling');
+GetOptions ('help|h' => \$help,
+ 'days|d=i' => \$days,
+ 'threshold|t=i' => \$threshold,
+ 'version|v' => \$print_version) or exit 1;
+
+# Set a default for the minimum threshold traffic required to retain an
+# exclusion, and assume that active file differences represent one day of
+# traffic unless told otherwise.
+$threshold = (defined $threshold) ? $threshold : 250;
+$days ||= 1;
+
+# If they asked for our version number, abort and just print that.
+if ($print_version) {
+ my ($program, $ver) = (split (' ', $version))[1,2];
+ $program =~ s/,v$//;
+ die "$program $ver\n";
+}
+
+# If they asked for help, give them the documentation.
+if ($help) {
+ print "Feeding myself to perldoc, please wait....\n";
+ exec ('perldoc', '-t', $fullpath) or die "$0: can't fork: $!\n";
+}
+
+# Hash the active files, skipping groups we ignore in the local one. Make
+# sure we have our two files listed first.
+unless (@ARGV == 2 || @ARGV == 3) {
+ die "Usage: $0 [-hv] [-t <threshold>] <local> <remote> [<traffic>]\n";
+}
+my (%local, %remote);
+hash (shift, \%local, 1);
+hash (shift, \%remote);
+traffic (shift) if @ARGV;
+
+# Now, we analyze the differences between the two feeds. We're trying to
+# build a pattern of what *we* should send *them*, so stuff that's in
+# %remote and not in %local doesn't concern us. Rather, we're looking for
+# stuff that we carry that they don't, since that's what we'll want to
+# exclude from a full feed.
+my (%have, %exclude, %count, $have, $exclude, $positive);
+for (sort keys %local) {
+ my ($hierarchy) = (split /\./);
+ $count{$hierarchy}++;
+ $traffic{"$hierarchy*"} += $traffic{$_};
+ if ($remote{$_}) { push (@{$have{$hierarchy}}, $_); $have++ }
+ else { push (@{$exclude{$hierarchy}}, $_); $exclude++ }
+}
+my @patterns;
+if ($have > $exclude * 4) {
+ push (@patterns, "*");
+ $positive = 1;
+}
+for (sort by_hierarchy keys %count) {
+ if ($have{$_} && !$exclude{$_}) {
+ push (@patterns, "$_.*") unless $positive;
+ } elsif ($exclude{$_} && !$have{$_}) {
+ push (@patterns, "!$_.*") if $positive;
+ } else {
+ push (@patterns, smash ($have{$_}, $exclude{$_}, $_, $positive));
+ }
+}
+output (\@patterns);
+__END__
+
+
+############################################################################
+# Documentation
+############################################################################
+
+=head1 NAME
+
+tunefeed - Build a newsgroups pattern for a remote feed
+
+=head1 SYNOPSIS
+
+B<tunefeed> [B<-hv>] [B<-t> I<threshold>] [B<-d> I<days>] I<local>
+I<remote> [I<traffic>]
+
+=head1 DESCRIPTION
+
+Given two active files, B<tunefeed> generates an INN newsfeeds pattern for
+a feed from the first site to the second, that sends the second site
+everything in its active file carried by the first site but tries to
+minimize the number of rejected articles. It does this by noting
+differences between the two active files and then trying to generate
+wildcard patterns that cover the similarities without including much (or
+any) unwanted traffic.
+
+I<local> and I<remote> should be standard active files. You can probably
+get the active file of a site that you feed (provided they're running INN)
+by connecting to their NNTP port and typing C<LIST ACTIVE>.
+
+B<tunefeed> makes an effort to avoid complex patterns when they're of
+minimal gain. I<threshold> is the number of messages per day at which to
+worry about excluding a group; if a group the remote site doesn't want to
+receive gets below that number of messages per day, then that group is
+either sent or not sent depending on which choice results in the simplest
+(shortest) wildcard pattern. If you want a pattern that exactly matches
+what the remote site wants, use C<-t 0>.
+
+Ideally, B<tunefeed> likes to be given the optional third argument,
+I<traffic>, which points at a file listing traffic numbers for each group.
+The format of this file is a group name, whitespace, and then the number
+of messages per day it receives. Without such a file, B<tunefeed> will
+attempt to guess traffic by taking the difference between the high and low
+numbers in the active file as the amount of traffic in that group per day.
+This will almost always not be accurate, but it should at least be a
+ballpark figure. If you know approximately how many days of traffic the
+active file numbers represent, you can tell B<tunefeed> this information
+using the B<-d> flag.
+
+B<tunefeed>'s output will look something like:
+
+ comp.*,humanities.classics,misc.*,news.*,rec.*,sci.*,soc.*,talk.*,\
+ alt.*,!alt.atheism,!alt.binaries.*,!alt.nocem.misc,!alt.punk*,\
+ !alt.sex*,!alt.video.dvd,\
+ bionet.*,biz.*,gnu.*,vmsnet.*,\
+ ba.*,!ba.jobs.agency,ca.*,sbay.*
+
+(with each line prefixed by a tab, and with standard INN newsfeeds
+continuation syntax). Due to the preferences of the author, it will also
+be sorted as Big Eight, then alt.*, then global non-language hierarchies,
+then regional and language hierarchies.
+
+=head1 OPTIONS
+
+=over 4
+
+=item B<-h>, B<--help>
+
+Print out this documentation (which is done simply by feeding the script
+to C<perldoc -t>.
+
+=item B<-v>, B<--version>
+
+Print out the version of B<tunefeed> and exit.
+
+=item B<-d> I<days>, B<--days>=I<days>
+
+Assume that the difference between the high and low numbers in the active
+file represent I<days> days of traffic.
+
+=item B<-t> I<threshold>, B<--threshold>=I<threshold>
+
+Allow any group with less than I<threshold> articles per day in traffic to
+be either sent or not sent depending on which choice makes the wildcard
+patterns simpler. If a threshold isn't specified, the default value is
+250.
+
+=back
+
+=head1 BUGS
+
+This program takes a long time to run, not to mention being a nasty memory
+hog. The algorithm is thorough, but definitely not very optimized, and
+isn't all that friendly.
+
+Guessing traffic from active file numbers is going to produce very skewed
+results on sites with expiration policies that vary widely by group.
+
+There is no way to optimize for size in avoiding rejections, only quantity
+of articles.
+
+There should be a way to turn off the author's idiosyncratic ordering of
+hierarchies, or to specify a different ordering, without editing this
+script.
+
+This script should attempt to retrieve the active file from the remote
+site automatically if so desired.
+
+This script should be able to be given some existing wildcard patterns and
+take them into account when generating new ones.
+
+=head1 CAVEATS
+
+Please be aware that your neighbor's active file may not accurately
+represent the groups they wish to receive from you. As with everything,
+choices made by automated programs like this one should be reviewed by a
+human and the remote site should be notified, and if they have sent
+explicit patterns, those should be honored instead. I definitely do *not*
+recommend running this program on any sort of automated basis.
+
+=head1 AUTHOR
+
+Russ Allbery E<lt>rra@stanford.eduE<gt>
+
+=cut
--- /dev/null
+## $Id: Makefile 6806 2004-05-18 01:18:57Z rra $
+
+include ../Makefile.global
+
+top = ..
+
+ALL = controlbatch controlchan docheckgroups gpgverify perl-nocem \
+ pgpverify signcontrol
+
+MAN = ../doc/man/perl-nocem.8 ../doc/man/pgpverify.1
+
+all: $(ALL)
+
+install: all
+ for F in $(ALL) ; do \
+ $(CP_XPUB) $$F $D$(PATHBIN)/$$F ; \
+ done
+ for M in modules/*.pl ; do \
+ $(CP_RPUB) $$M $D$(PATHCONTROL)/`basename $$M` ; \
+ done
+
+man: $(MAN)
+
+clean clobber distclean:
+ rm -f $(ALL)
+
+profiled: all
+depend:
+
+$(FIXSCRIPT):
+ @echo Run configure before running make. See INSTALL for details.
+ @exit 1
+
+
+## Build rules.
+
+FIX = $(FIXSCRIPT)
+
+controlbatch: controlbatch.in $(FIX) ; $(FIX) controlbatch.in
+controlchan: controlchan.in $(FIX) ; $(FIX) controlchan.in
+docheckgroups: docheckgroups.in $(FIX) ; $(FIX) docheckgroups.in
+gpgverify: gpgverify.in $(FIX) ; $(FIX) gpgverify.in
+perl-nocem: perl-nocem.in $(FIX) ; $(FIX) perl-nocem.in
+pgpverify: pgpverify.in $(FIX) ; $(FIX) pgpverify.in
+signcontrol: signcontrol.in $(FIX) ; $(FIX) -i signcontrol.in
+
+../doc/man/perl-nocem.8: perl-nocem
+ $(POD2MAN) -s 8 $? > $@
+
+../doc/man/pgpverify.1: pgpverify
+ $(POD2MAN) -s 1 $? > $@
--- /dev/null
+#! /bin/sh
+# fixscript will replace this line with code to load innshellvars
+
+########################################################################
+# controlbatch - Run controlchan against a batch file.
+#
+# Command usage: controlbatch [feedsite batchfile]
+# Defaults are feedsite: controlchan!, batchfile: ${BATCH}/controlchan!
+########################################################################
+#
+# This script will run controlchan against a batch file. You can use
+# it to clear occasional backlogs while running controls from a
+# channel, or even skip the channel and run control messages as a file
+# feed.
+#
+########################################################################
+#
+# If you're doing the channel thing, you might want to put something
+# like this in your crontab to do a cleanup in the wee hours:
+#
+# 00 04 * * * @prefix@/bin/controlbatch
+#
+########################################################################
+#
+# If you would rather skip the channel and just process controls each
+# hour in a batch, use this newsfeeds entry instead of the "stock"
+# version:
+#
+# controlchan!\
+# :!*,control,control.*,!control.cancel\
+# :Tf,Wnsm:
+#
+# And, a crontab entry something like this:
+#
+# 30 * * * * @prefix@/bin/controlbatch
+#
+########################################################################
+
+batchlock="${LOCKS}/LOCK.controlbatch"
+mypid=$$
+
+# A concession to INN 1.x
+if [ me${PATHBIN}ow = meow ] ; then
+ PATHBIN=${NEWSBIN}
+ export PATHBIN
+fi
+
+# See if we have no arguments and should use the defaults. If there are
+# arguments, make sure we have enough to attempt something useful.
+if [ me${1}ow != meow ] ; then
+ if [ me${2}ow = meow ] ; then
+ echo "Usage: ${0} [feedsite batchfile]" >&2
+ exit 0
+ else
+ feedsite=${1}
+ batchfile=${2}
+ fi
+else
+ feedsite=controlchan\!
+ batchfile=controlchan\!
+fi
+
+# Check if any other copies of controlbatch are running. If we are not
+# alone, give up here and now.
+${PATHBIN}/shlock -p $mypid -f ${batchlock} || exit 0
+
+cd ${BATCH}
+
+if [ -s ${batchfile}.work ] ; then
+ cat ${batchfile}.work >>${batchfile}.doit
+ rm -f ${batchfile}.work
+fi
+
+if [ -s ${batchfile} ] ; then
+ mv ${batchfile} ${batchfile}.work
+ if ${PATHBIN}/ctlinnd -s -t30 flush ${feedsite} ; then
+ cat ${batchfile}.work >>${batchfile}.doit
+ rm -f ${batchfile}.work
+ fi
+fi
+
+if [ -s ${batchfile}.doit ] ; then
+ ${PATHBIN}/controlchan \
+ < ${batchfile}.doit >> ${MOST_LOGS}/controlbatch.log 2>&1
+ # if you want extra assurance that nothing gets lost...
+ # cat ${batchfile}.doit >> ${batchfile}.done
+ rm -f ${batchfile}.doit
+fi
+
+rm -f ${batchlock}
--- /dev/null
+#! /usr/bin/perl -w
+require "/usr/local/news/lib/innshellvars.pl";
+
+## $Id: controlchan.in 7591 2006-11-22 07:20:46Z eagle $
+##
+## Channel feed program to route control messages to an appropriate handler.
+##
+## Copyright 2001 by Marco d'Itri <md@linux.it>
+##
+## 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.
+##
+## Give this program its own newsfeed. Make sure that you've created
+## the newsgroup control.cancel so that you don't have to scan through
+## cancels, which this program won't process anyway.
+##
+## Make a newsfeeds entry like this:
+##
+## controlchan!\
+## :!*,control,control.*,!control.cancel\
+## :Tc,Wnsm\
+## :@prefix@/bin/controlchan
+
+require 5.004_03;
+use strict;
+
+delete @ENV{'IFS', 'CDPATH', 'ENV', 'BASH_ENV'};
+
+# globals
+my ($cachedctl, $curmsgid);
+my $lastctl = 0;
+my $use_syslog = 0;
+my $debug = 0;
+
+# setup logging ###########################################################
+# do not log to syslog if stderr is connected to a console
+if (not -t 2) {
+ eval { require INN::Syslog; import INN::Syslog; $use_syslog = 1; };
+ eval { require Sys::Syslog; import Sys::Syslog; $use_syslog = 1; }
+ unless $use_syslog;
+}
+
+if ($use_syslog) {
+ eval "sub Sys::Syslog::_PATH_LOG { '/dev/log' }" if $^O eq 'dec_osf';
+ Sys::Syslog::setlogsock('unix') if $^O =~ /linux|dec_osf|freebsd|darwin/;
+ openlog('controlchan', 'pid', $inn::syslog_facility);
+}
+logmsg('starting');
+
+# load modules from the control directory #################################
+opendir(CTL, $inn::controlprogs)
+ or logdie("Cannot open $inn::controlprogs: $!", 'crit');
+foreach (readdir CTL) {
+ next if not /^([a-z\.]+\.pl)$/ or not -f "$inn::controlprogs/$_";
+ eval { require "$inn::controlprogs/$1" };
+ if ($@) {
+ $@ =~ s/\n/ /g;
+ logdie($@, 'crit');
+ }
+ logmsg("loaded $inn::controlprogs/$1", 'debug');
+}
+closedir CTL;
+
+# main loop ###############################################################
+while (<STDIN>) {
+ chop;
+ my ($token, $sitepath, $msgid) = split(/\s+/, $_);
+ next if not defined $token;
+ $sitepath ||= '';
+ $curmsgid = $msgid || '';
+
+ my $artfh = open_article($token);
+ next if not defined $artfh;
+
+ # suck in headers and body, normalize the strange ones
+ my (@headers, @body, %hdr);
+ if (not parse_article($artfh, \@headers, \@body, \%hdr)) {
+ close $artfh;
+ next;
+ }
+ close $artfh or logdie('sm died with status ' . ($? >> 8));
+
+ next if not exists $hdr{control};
+
+ $curmsgid = $hdr{'message-id'};
+ my $sender = cleanaddr($hdr{sender} || $hdr{from});
+ my $replyto = cleanaddr($hdr{'reply-to'} || $hdr{from});
+
+ my (@progparams, $progname);
+ if ($hdr{control} =~ /\s/) {
+ $hdr{control} =~ /^(\S+)\s+(.+)?/;
+ $progname = lc $1;
+ @progparams = split(/\s+/, lc $2) if $2;
+ } else {
+ $progname = lc $hdr{control};
+ }
+
+ next if $progname eq 'cancel';
+
+ if ($progname !~ /^([a-z]+)$/) {
+ logmsg("Naughty control in article $curmsgid ($progname)");
+ next;
+ }
+ $progname = $1;
+
+ # Do we want to process the message? Let's check the permissions.
+ my ($action, $logname, $newsgrouppats) =
+ ctlperm($progname, $sender, $progparams[0],
+ $token, \@headers, \@body);
+
+ next if $action eq 'drop';
+
+ if ($action eq '_pgpfail') {
+ my $type = '';
+ if ($progname and $progname eq 'newgroup') {
+ if ($progparams[1] and $progparams[1] eq 'moderated') {
+ $type = 'm ';
+ } else {
+ $type = 'y ';
+ }
+ }
+ logmsg("skipping $progname $type$sender"
+ . "(pgpverify failed) in $curmsgid");
+ next;
+ }
+
+ # used by checkgroups. Convert from perl regexp to grep regexp.
+ if (local $_ = $newsgrouppats) {
+ s/\$\|/|/g;
+ s/[^\\]\.[^*]/?/g;
+ s/\$//;
+ s/\.\*/*/g;
+ s/\\([\$\+\.])/$1/g;
+ $progparams[0] = $_;
+ }
+
+ # find the appropriate module and call it
+ my $subname = "control_$progname";
+ my $subfind = \&$subname;
+ if (not defined &$subfind) {
+ if ($logname) {
+ logger($logname, "Unknown control message by $sender",
+ \@headers, \@body);
+ } else {
+ logmsg("Unknown \"$progname\" control by $sender");
+ }
+ next;
+ }
+
+ my $approved = $hdr{approved} ? 1 : 0;
+ logmsg("$subname, " . join(' ', @progparams)
+ . " $sender $replyto $token, $sitepath, $action"
+ . ($logname ? "=$logname" : '') .", $approved");
+
+ &$subfind(\@progparams, $sender, $replyto, $sitepath,
+ $action, $logname, $approved, \@headers, \@body);
+}
+
+closelog() if $use_syslog;
+exit 0;
+
+print $inn::most_logs.$inn::syslog_facility.$inn::mta.
+ $inn::newsmaster.$inn::locks; # lint food
+
+# misc functions ##########################################################
+sub parse_article {
+ my ($artfh, $headers, $body, $hdr) = @_;
+ my $h;
+ my %uniquehdr = map { $_ => 1 } qw(date followup-to from message-id
+ newsgroups path reply-to subject sender);
+
+ while (<$artfh>) {
+ s/\r?\n$//;
+ last if /^$/;
+ push @$headers, $_;
+ if (/^(\S+):\s+(.+)/) {
+ $h = lc $1;
+ if (exists $hdr->{$h}) {
+ if (exists $uniquehdr{$h}) {
+ logmsg("Multiple $1 headers in article $curmsgid");
+ return 0;
+ }
+ $hdr->{$h} .= ' ' . $2;
+ } else {
+ $hdr->{$h} = $2;
+ }
+ next;
+ } elsif (/^\s+(.+)/) {
+ if (defined $h) {
+ $hdr->{$h} .= ' ' . $1;
+ next;
+ }
+ }
+ logmsg("Broken headers in article $curmsgid");
+ return 0;
+ }
+
+ # article is empty or does not exist
+ return 0 if not @$headers;
+
+ chop (@$body = <$artfh>);
+ return 1;
+}
+
+# Strip a mail address, innd-style.
+sub cleanaddr {
+ local $_ = shift;
+ s/(\s+)?\(.*\)(\s+)?//g;
+ s/.*<(.*)>.*/$1/;
+ s/[^-a-zA-Z0-9+_.@%]/_/g; # protect MTA
+ s/^-/_/; # protect MTA
+ return $_;
+}
+
+# Read and cache control.ctl.
+sub readctlfile {
+ my $mtime = (stat($inn::ctlfile))[9];
+ return $cachedctl if $lastctl == $mtime; # mtime has not changed.
+ $lastctl = $mtime;
+
+ my @ctllist;
+ open(CTLFILE, $inn::ctlfile)
+ or logdie("Cannot open $inn::ctlfile: $!", 'crit');
+ while (<CTLFILE>) {
+ chop;
+ # Not a comment or blank? Convert wildmat to regex
+ next if not /^(\s+)?[^\#]/ or /^$/;
+ if (not /:(?:doit|doifarg|drop|log|mail|verify-.*)(?:=.*)?$/) {
+ s/.*://;
+ logmsg("$_ is not a valid action for control.ctl", 'err');
+ next;
+ }
+ # Convert to a : separated list of regexps
+ s/^all:/*:/i;
+ s/([\$\+\.])/\\$1/g;
+ s/\*/.*/g;
+ s/\?/./g;
+ s/(.*)/^$1\$/;
+ s/:/\$:^/g;
+ s/\|/\$|^/g;
+ push @ctllist, $_;
+ }
+ close CTLFILE;
+
+ logmsg('warning: control.ctl is empty!', 'err') if not @ctllist;
+ return $cachedctl = [ reverse @ctllist ];
+}
+
+# Parse a control message's permissions.
+sub ctlperm {
+ my ($type, $sender, $newsgroup, $token, $headers, $body) = @_;
+
+ my $action = 'drop'; # default
+ my ($logname, $hier);
+
+ # newgroup and rmgroup require newsgroup names; check explicitly for that
+ # here and return drop if the newsgroup is missing (to avoid a bunch of
+ # warnings from undefined values later on in permission checking).
+ if ($type eq 'newgroup' or $type eq 'rmgroup') {
+ unless ($newsgroup) {
+ return ('drop', undef, undef);
+ }
+ }
+
+ my $ctllist = readctlfile();
+ foreach (@$ctllist) {
+ my @ctlline = split /:/;
+ # 0: type 1: from@addr 2: group.* 3: action
+ if ($type =~ /$ctlline[0]/ and $sender =~ /$ctlline[1]/i and
+ ($type !~ /(?:new|rm)group/ or $newsgroup =~ /$ctlline[2]/)) {
+ $action = $ctlline[3];
+ $action =~ s/\^(.+)\$/$1/;
+ $action =~ s/\\//g;
+ $hier = $ctlline[2] if $type eq 'checkgroups';
+ last;
+ }
+ }
+
+ ($action, $logname) = split(/=/, $action);
+
+ if ($action =~ /^verify-(.+)/) {
+ my $keyowner = $1;
+ if ($inn::pgpverify and $inn::pgpverify =~ /^(?:true|on|yes)$/i) {
+ my $pgpresult = defined &local_pgpverify ?
+ local_pgpverify($token, $headers, $body) : pgpverify($token);
+ if ($keyowner eq $pgpresult) {
+ $action = 'doit';
+ } else {
+ $action = '_pgpfail';
+ }
+ } else {
+ $action = 'mail';
+ }
+ }
+
+ return ($action, $logname, $hier);
+}
+
+# Write stuff to a log or send mail to the news admin.
+sub logger {
+ my ($logfile, $message, $headers, $body) = @_;
+
+ if ($logfile eq 'mail') {
+ my $mail = sendmail($message);
+ print $mail map { s/^~/~~/; "$_\n" } @$headers;
+ print $mail "\n" . join ('', map { s/^~/~~/; "$_\n" } @$body)
+ if $body;
+ close $mail or logdie("Cannot send mail: $!");
+ return;
+ }
+
+ if ($logfile =~ /^([^.\/].*)/) {
+ $logfile = $1;
+ } else {
+ logmsg("Invalid log file: $logfile", 'err');
+ $logfile = 'control';
+ }
+
+ $logfile = "$inn::most_logs/$logfile.log" unless $logfile =~ /^\//;
+ my $lockfile = $logfile;
+ $lockfile =~ s#.*/##;
+ $lockfile = "$inn::locks/LOCK.$lockfile";
+ shlock($lockfile);
+
+ open(LOGFILE, ">>$logfile") or logdie("Cannot open $logfile: $!");
+ print LOGFILE "$message\n";
+ foreach (@$headers, '', @$body, '') {
+ print LOGFILE " $_\n";
+ }
+ close LOGFILE;
+ unlink $lockfile;
+}
+
+# write to syslog or errlog
+sub logmsg {
+ my ($msg, $lvl) = @_;
+
+ return if $lvl and $lvl eq 'debug' and not $debug;
+ if ($use_syslog) {
+ syslog($lvl || 'notice', '%s', $msg);
+ } else {
+ print STDERR (scalar localtime) . ": $msg\n";
+ }
+}
+
+# log a message and then die
+sub logdie {
+ my ($msg, $lvl) = @_;
+
+ $msg .= " ($curmsgid)" if $curmsgid;
+ logmsg($msg, $lvl || 'err');
+ exit 1;
+}
+
+# wrappers executing external programs ####################################
+
+# Open an article appropriately to our storage method (or lack thereof).
+sub open_article {
+ my $token = shift;
+
+ if ($token =~ /^\@.+\@$/) {
+ my $pid = open(ART, '-|');
+ logdie('Cannot fork: ' . $!) if $pid < 0;
+ if ($pid == 0) {
+ exec("$inn::newsbin/sm", '-q', $token) or
+ logdie("Cannot exec sm: $!");
+ }
+ return *ART;
+ } else {
+ return *ART if open(ART, $token);
+ logmsg("Cannot open article $token: $!");
+ }
+ return undef;
+}
+
+sub pgpverify {
+ my $token = shift;
+
+ if ($token =~ /^\@.+\@$/) {
+ open(PGPCHECK, "$inn::newsbin/sm -q $token "
+ . "| $inn::newsbin/pgpverify |") or goto ERROR;
+ } else {
+ open(PGPCHECK, "$inn::newsbin/pgpverify < $token |") or goto ERROR;
+ }
+ my $pgpresult = <PGPCHECK>;
+ close PGPCHECK or goto ERROR;
+ $pgpresult ||= '';
+ chop $pgpresult;
+ return $pgpresult;
+ERROR:
+ logmsg("pgpverify failed: $!", 'debug');
+ return '';
+}
+
+sub ctlinnd {
+ my ($cmd, @args) = @_;
+
+ my $st = system("$inn::newsbin/ctlinnd", '-s', $cmd, @args);
+ logdie('Cannot run ctlinnd: ' . $!) if $st == -1;
+ logdie('ctlinnd returned status ' . ($st & 255)) if $st > 0;
+}
+
+sub shlock {
+ my $lockfile = shift;
+
+ my $locktry = 0;
+ while ($locktry < 60) {
+ if (system("$inn::newsbin/shlock", '-p', $$, '-f', $lockfile) == 0) {
+ return 1;
+ }
+ $locktry++;
+ sleep 2;
+ }
+
+ my $lockreason;
+ if (open(LOCKFILE, $lockfile)) {
+ $lockreason = 'held by ' . (<LOCKFILE> || '?');
+ close LOCKFILE;
+ } else {
+ $lockreason = $!;
+ }
+ logdie("Cannot get lock $lockfile: $lockreason");
+ return undef;
+}
+
+# If $body is not defined, returns a file handle which must be closed.
+# Don't forget checking the return value of close().
+# $addresses may be a scalar or a reference to a list of addresses.
+# If not defined, $inn::newsmaster is the default.
+# parts of this code stolen from innmail.pl
+sub sendmail {
+ my ($subject, $addresses, $body) = @_;
+ $addresses = [ $addresses || $inn::newsmaster ] if not ref $addresses;
+ $subject ||= '(no subject)';
+
+ # fix up all addresses
+ my @addrs = map { s#[^-a-zA-Z0-9+_.@%]##g; $_ } @$addresses;
+
+ my $sm = $inn::mta;
+ if ($sm =~ /%s/) {
+ $sm = sprintf($sm, join(' ', @addrs));
+ } else {
+ $sm .= ' ' . join(' ', @addrs);
+ }
+
+ # fork and spawn the MTA whitout using the shell
+ my $pid = open(MTA, '|-');
+ logdie('Cannot fork: ' . $!) if $pid < 0;
+ if ($pid == 0) {
+ exec(split(/\s+/, $sm)) or logdie("Cannot exec $sm: $!");
+ }
+
+ print MTA 'To: ' . join(",\n\t", @addrs) . "\nSubject: $subject\n\n";
+ return *MTA if not defined $body;
+ $body = join("\n", @$body) if ref $body eq 'ARRAY';
+ print MTA $body . "\n";
+ close MTA or logdie("Execution of $sm failed: $!");
+ return 1;
+}
--- /dev/null
+#! /bin/sh
+# fixscript will replace this line with code to load innshellvars
+
+## $Revision: 7743 $
+## Script to execute checkgroups text; results to stdout.
+
+T=${TMPDIR}
+
+cat /dev/null >${T}/$$out
+
+## Copy the article without headers, append local newsgroups.
+cat >${T}/$$msg
+test -f ${LOCALGROUPS} && cat ${LOCALGROUPS} >>${T}/$$msg
+
+## Get the top-level newsgroup names from the message and turn it into
+## an egrep pattern.
+PATS=`${SED} <${T}/$$msg \
+ -e 's/[ ].*//' -e 's/\..*//' \
+ -e 's/^!//' -e '/^$/d' \
+ -e 's/^/^/' -e 's/$/[. ]/' \
+ | sort -u \
+ | (tr '\012' '|' ; echo '' )\
+ | ${SED} -e 's/|$//'`
+
+${EGREP} "${PATS}" ${ACTIVE} | ${EGREP} "${1:-.}" | ${SED} 's/ .*//' | sort >${T}/$$active
+${EGREP} "${PATS}" ${T}/$$msg | ${EGREP} "${1:-.}" | ${SED} 's/[ ].*//' | sort >${T}/$$newsgrps
+
+comm -13 ${T}/$$active ${T}/$$newsgrps >${T}/$$missing
+comm -23 ${T}/$$active ${T}/$$newsgrps >${T}/$$remove
+
+${EGREP} "${PATS}" ${ACTIVE} | ${EGREP} "${1:-.}" | ${SED} -n '/ m$/s/ .*//p' | sort >${T}/$$amod.all
+${EGREP} "${PATS}" ${T}/$$msg | ${EGREP} "${1:-.}" | ${SED} 's/\r\?$//' |
+${SED} -n '/(Moderated)$/s/[ ].*//p' | sort >${T}/$$ng.mod
+
+comm -12 ${T}/$$missing ${T}/$$ng.mod >${T}/$$add.mod
+comm -23 ${T}/$$missing ${T}/$$ng.mod >${T}/$$add.unmod
+cat ${T}/$$add.mod ${T}/$$add.unmod >>${T}/$$add
+
+comm -23 ${T}/$$amod.all ${T}/$$remove >${T}/$$amod
+comm -13 ${T}/$$ng.mod ${T}/$$amod >${T}/$$ismod
+comm -23 ${T}/$$ng.mod ${T}/$$amod >${T}/$$nm.all
+comm -23 ${T}/$$nm.all ${T}/$$add >${T}/$$notmod
+
+${EGREP} "${PATS}" ${NEWSGROUPS} | ${EGREP} "${1:-.}" | ${SED} 's/[ ]\+/ /' | sort >${T}/$$localdesc
+${EGREP} "${PATS}" ${T}/$$msg | ${EGREP} "${1:-.}" | ${SED} 's/\r\?$//' |
+${SED} 's/[ ]\+/ /' | sort >${T}/$$newdesc
+
+if ! (head -1 ${T}/$$newdesc | egrep " [[:digit:]]+ [[:digit:]]+ " > /dev/null) ; then
+ comm -13 ${T}/$$localdesc ${T}/$$newdesc >${T}/$$missingdesc
+ comm -23 ${T}/$$localdesc ${T}/$$newdesc >${T}/$$removedesc
+fi
+
+if [ -s ${T}/$$remove ] ; then
+ (
+ echo "# The following newsgroups are non-standard."
+ ${SED} "s/^/# /" ${T}/$$remove
+ echo "# You can remove them by executing the commands:"
+ for i in `cat ${T}/$$remove` ; do
+ echo " ${PATHBIN}/ctlinnd rmgroup $i"
+ ${EGREP} "^$i " ${NEWSGROUPS} >>${T}/$$ngdel
+ done
+ echo ''
+ ) >>${T}/$$out
+fi
+
+if [ -s ${T}/$$add ] ; then
+ (
+ echo "# The following newsgroups were missing and should be added."
+ ${SED} "s/^/# /" ${T}/$$add
+ echo "# You can do this by executing the command(s):"
+ for i in `cat ${T}/$$add.unmod` ; do
+ echo " ${PATHBIN}/ctlinnd newgroup $i y ${FROM}"
+ ${EGREP} "^$i " ${T}/$$msg >>${T}/$$ngadd
+ done
+ for i in `cat ${T}/$$add.mod` ; do
+ echo " ${PATHBIN}/ctlinnd newgroup $i m ${FROM}"
+ ${EGREP} "^$i " ${T}/$$msg >>${T}/$$ngadd
+ done
+ echo ''
+ ) >>${T}/$$out
+fi
+
+if [ -s ${T}/$$ismod ] ; then
+ (
+ echo "# The following groups are incorrectly marked as moderated:"
+ ${SED} "s/^/# /" ${T}/$$ismod
+ echo "# You can correct this by executing the following:"
+ for i in `cat ${T}/$$ismod` ; do
+ echo " ${PATHBIN}/ctlinnd changegroup $i y"
+ ${EGREP} "^$i " ${T}/$$msg >>${T}/$$ngchng
+ done
+ echo ''
+ ) >>${T}/$$out
+fi
+
+if [ -s ${T}/$$notmod ] ; then
+ (
+ echo "# The following groups are incorrectly marked as unmoderated:"
+ ${SED} "s/^/# /" ${T}/$$notmod
+ echo "# You can correct this by executing the following:"
+ for i in `cat ${T}/$$notmod` ;do
+ echo " ${PATHBIN}/ctlinnd changegroup $i m"
+ ${EGREP} "^$i " ${T}/$$msg >>${T}/$$ngchng
+ done
+ echo ''
+ ) >>${T}/$$out
+fi
+
+if [ -s ${T}/$$removedesc ] ; then
+ (
+ echo "# The following newsgroups descriptions are obsolete."
+ ${SED} "s/^/# /" ${T}/$$removedesc
+ echo "# You can remove them by editing ${NEWSGROUPS}."
+ echo ''
+ ) >>${T}/$$out
+fi
+
+if [ -s ${T}/$$missingdesc ] ; then
+ (
+ echo "# The following newsgroups descriptions were missing and should be added."
+ ${SED} "s/^/# /" ${T}/$$missingdesc
+ echo "# You can add them by editing ${NEWSGROUPS}."
+ echo ''
+ ) >>${T}/$$out
+fi
+
+
+test -s ${T}/$$out && {
+ cat ${T}/$$out
+ echo 'exit # so you can feed this message into the shell'
+ echo "# And remember to update ${NEWSGROUPS}."
+ test -s ${T}/$$ngdel && {
+ echo "# Remove these lines:"
+ ${SED} "s/^/# /" ${T}/$$ngdel
+ echo ''
+ }
+ test -s ${T}/$$ngadd && {
+ echo "# Add these lines:"
+ ${SED} "s/^/# /" ${T}/$$ngadd
+ echo ''
+ }
+ test -s ${T}/$$ngchng && {
+ echo "# Change these lines:"
+ ${SED} "s/^/# /" ${T}/$$ngchng
+ echo ''
+ }
+}
+
+rm -f ${T}/$$*
--- /dev/null
+#!/usr/bin/perl -w
+require '/etc/news/innshellvars.pl';
+
+# written April 1996, tale@isc.org (David C Lawrence)
+# mostly rewritten 2001-03-21 by Marco d'Itri <md@linux.it>
+#
+# requirements:
+# - GnuPG
+# - perl 5.004_03 and working Sys::Syslog
+# - syslog daemon
+#
+# There is no locking because gpg is supposed to not need it and controlchan
+# will serialize control messages processing anyway.
+
+require 5.004_03;
+use strict;
+
+# if you keep your keyring somewhere that is not the default used by gpg,
+# change the location below.
+my $keyring;
+if ($inn::newsetc && -d "$inn::newsetc/pgp") {
+ $keyring = $inn::newsetc . '/pgp/pubring.gpg';
+}
+
+# If you have INN and the script is able to successfully include your
+# innshellvars.pl file, the value of the next two variables will be
+# overridden.
+my $tmpdir = '/var/log/news/';
+my $syslog_facility = 'news';
+
+# 1: print PGP output
+my $debug = 0;
+#$debug = 1 if -t 1;
+
+### Exit value:
+### 0 good signature
+### 1 no signature
+### 2 unknown signature
+### 3 bad signature
+### 255 problem not directly related to gpg analysis of signature
+
+##############################################################################
+################ NO USER SERVICEABLE PARTS BELOW THIS COMMENT ################
+##############################################################################
+my $tmp = ($inn::pathtmp ? $inn::pathtmp : $tmpdir) . "/pgp$$";
+$syslog_facility = $inn::syslog_facility if $inn::syslog_facility;
+
+my $nntp_format = 0;
+$0 =~ s#^.*/##; # trim /path/to/prog to prog
+
+die "Usage: $0 < message\n" if $#ARGV != -1;
+
+# Path to gpg binary
+my $gpg;
+if ($inn::gpgv) {
+ $gpg = $inn::gpgv;
+} else {
+ foreach (split(/:/, $ENV{PATH}), qw(/usr/local/bin /opt/gnu/bin)) {
+ if (-x "$_/gpgv") {
+ $gpg = "$_/gpgv"; last;
+ }
+ }
+}
+fail('cannot find the gpgv binary') if not $gpg;
+
+# this is, by design, case-sensitive with regards to the headers it checks.
+# it's also insistent about the colon-space rule.
+my ($label, $value, %dup, %header);
+while (<STDIN>) {
+ # if a header line ends with \r\n, this article is in the encoding
+ # it would be in during an NNTP session. some article storage
+ # managers keep them this way for efficiency.
+ $nntp_format = /\r\n$/ if $. == 1;
+ s/\r?\n$//;
+
+ last if /^$/;
+ if (/^(\S+):[ \t](.+)/) {
+ ($label, $value) = ($1, $2);
+ $dup{$label} = 1 if $header{$label};
+ $header{$label} = $value;
+ } elsif (/^\s/) {
+ fail("non-header at line $.: $_") unless $label;
+ $header{$label} .= "\n$_";
+ } else {
+ fail("non-header at line $.: $_");
+ }
+}
+
+my $pgpheader = 'X-PGP-Sig';
+$_ = $header{$pgpheader};
+exit 1 if not $_; # no signature
+
+# the $sep value means the separator between the radix64 signature lines
+# can have any amount of spaces or tabs, but must have at least one space
+# or tab, if there is a newline then the space or tab has to follow the
+# newline. any number of newlines can appear as long as each is followed
+# by at least one space or tab. *phew*
+my $sep = "[ \t]*(\n?[ \t]+)+";
+# match all of the characters in a radix64 string
+my $r64 = '[a-zA-Z0-9+/]';
+fail("$pgpheader not in expected format")
+ unless /^(\S+)$sep(\S+)(($sep$r64{64})+$sep$r64+=?=?$sep=$r64{4})$/;
+
+my ($version, $signed_headers, $signature) = ($1, $3, $4);
+$signature =~ s/$sep/\n/g;
+
+my $message = "-----BEGIN PGP SIGNED MESSAGE-----\n\n"
+ . "X-Signed-Headers: $signed_headers\n";
+
+foreach $label (split(',', $signed_headers)) {
+ fail("duplicate signed $label header, can't verify") if $dup{$label};
+ $message .= "$label: ";
+ $message .= $header{$label} if $header{$label};
+ $message .= "\n";
+}
+$message .= "\n"; # end of headers
+
+while (<STDIN>) { # read body lines
+ if ($nntp_format) {
+ # check for end of article; some news servers (eg, Highwind's
+ # "Breeze") include the dot-CRLF of the NNTP protocol in the
+ # article data passed to this script
+ last if $_ eq ".\r\n";
+
+ # remove NNTP encoding
+ s/^\.\./\./;
+ s/\r\n$/\n/;
+ }
+
+ s/^-/- -/; # pgp quote ("ASCII armor") dashes
+ $message .= $_;
+}
+
+$message .=
+ "\n-----BEGIN PGP SIGNATURE-----\n" .
+ "Version: $version\n" .
+ $signature .
+ "\n-----END PGP SIGNATURE-----\n";
+
+open(TMP, ">$tmp") or fail("open $tmp: $!");
+print TMP $message;
+close TMP or errmsg("close $tmp: $!");
+
+my $opts = '--quiet --status-fd=1 --logger-fd=1';
+$opts .= " --keyring=$keyring" if $keyring;
+
+open(PGP, "$gpg $opts $tmp |") or fail("failed to execute $gpg: $!");
+
+undef $/;
+$_ = <PGP>;
+
+unlink $tmp or errmsg("unlink $tmp: $!");
+
+if (not close PGP) {
+ if ($? >> 8) {
+ my $status = $? >> 8;
+ errmsg("gpg exited status $status") if $status > 1;
+ } else {
+ errmsg('gpg died on signal ' . ($? & 255));
+ }
+}
+
+print STDERR $_ if $debug;
+
+my $ok = 255; # default exit status
+my $signer;
+if (/^\[GNUPG:\]\s+GOODSIG\s+\S+\s+(\S+)/m) {
+ $ok = 0;
+ $signer = $1;
+} elsif (/^\[GNUPG:\]\s+NODATA/m or /^\[GNUPG:\]\s+UNEXPECTED/m) {
+ $ok = 1;
+} elsif (/^\[GNUPG:\]\s+NO_PUBKEY/m) {
+ $ok = 2;
+} elsif (/^\[GNUPG:\]\s+BADSIG\s+/m) {
+ $ok = 3;
+}
+
+print "$signer\n" if $signer;
+exit $ok;
+
+sub errmsg {
+ my $msg = $_[0];
+
+ eval 'use Sys::Syslog qw(:DEFAULT setlogsock)';
+ die "$0: cannot use Sys::Syslog: $@ [$msg]\n" if $@;
+
+ die "$0: cannot set syslog method [$msg]\n"
+ if not (setlogsock('unix') or setlogsock('inet'));
+
+ $msg .= " processing $header{'Message-ID'}" if $header{'Message-ID'};
+
+ openlog($0, 'pid', $syslog_facility);
+ syslog('err', '%s', $msg);
+ closelog();
+}
+
+sub fail {
+ errmsg($_[0]);
+ unlink $tmp;
+ exit 255;
+}
+
+__END__
+
+# Copyright 2000 by Marco d'Itri
+
+# License of the original version distributed by David C. Lawrence:
+
+# Copyright (c) 1996 UUNET Technologies, Inc.
+# 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 UUNET Technologies, Inc.
+# 4. The name of UUNET Technologies ("UUNET") may not be used to endorse or
+# promote products derived from this software without specific prior
+# written permission.
+#
+# THIS SOFTWARE IS PROVIDED BY UUNET ``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 UUNET 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.
--- /dev/null
+## $Id: checkgroups.pl 7743 2008-04-06 10:04:43Z iulius $
+##
+## checkgroups control message handler.
+##
+## Copyright 2001 by Marco d'Itri <md@linux.it>
+##
+## 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.
+
+use strict;
+
+sub control_checkgroups {
+ my ($par, $sender, $replyto, $site, $action, $log, $approved,
+ $headers, $body) = @_;
+ my ($newsgrouppats) = @$par;
+
+ if ($action eq 'mail') {
+ my $mail = sendmail("checkgroups by $sender");
+ print $mail "$sender posted the following checkgroups message:\n";
+ print $mail map { s/^~/~~/; "$_\n" } @$headers;
+ print $mail <<END;
+
+If you want to process it, feed the body
+of the message to docheckgroups while logged
+in as user ID "$inn::newsuser":
+
+$inn::pathbin/docheckgroups '$newsgrouppats' <<zRbJ
+END
+ print $mail map { s/^~/~~/; "$_\n" } @$body;
+ print $mail "zRbJ\n";
+ close $mail or logdie("Cannot send mail: $!");
+ } elsif ($action eq 'log') {
+ if ($log) {
+ logger($log, "checkgroups by $sender", $headers, $body);
+ } else {
+ logmsg("checkgroups by $sender");
+ }
+ } elsif ($action eq 'doit') {
+ if (defined &local_docheckgroups) {
+ local_docheckgroups($body, $newsgrouppats, $log, $sender);
+ } else {
+ docheckgroups($body, $newsgrouppats, $log, $sender);
+ }
+ }
+}
+
+sub docheckgroups {
+ my ($body, $newsgrouppats, $log, $sender) = @_;
+
+ my $tempfile = "$inn::tmpdir/checkgroups.$$";
+ open(TEMPART, ">$tempfile.art")
+ or logdie("Cannot open $tempfile.art: $!");
+ print TEMPART map { s/^~/~~/; "$_\n" } @$body;
+ close TEMPART;
+
+ open(OLDIN, '<&STDIN') or die $!;
+ open(OLDOUT, '>&STDOUT') or die $!;
+ open(STDIN, "$tempfile.art") or die $!;
+ open(STDOUT, ">$tempfile") or die $!;
+ my $st = system("$inn::pathbin/docheckgroups", $newsgrouppats);
+ logdie('Cannot run docheckgroups: ' . $!) if $st == -1;
+ logdie('docheckgroups returned status ' . ($st & 255)) if $st > 0;
+ close(STDIN);
+ close(STDOUT);
+ open(STDIN, '<&OLDIN') or die $!;
+ open(STDOUT, '>&OLDOUT') or die $!;
+
+ open(TEMPFILE, $tempfile) or logdie("Cannot open $tempfile: $!");
+ my @output = <TEMPFILE>;
+ chop @output;
+ # There is no need to send an empty mail.
+ if ($#output > 0) {
+ logger($log || 'mail', "checkgroups by $sender", \@output);
+ } else {
+ logmsg("checkgroups by $sender processed (no change)");
+ }
+ close TEMPFILE;
+ unlink($tempfile, "$tempfile.art");
+}
+
+1;
--- /dev/null
+## $Id: ihave.pl 4932 2001-07-19 00:32:56Z rra $
+##
+## ihave control message handler.
+##
+## Copyright 2001 by Marco d'Itri <md@linux.it>
+##
+## 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.
+
+use strict;
+
+sub control_ihave {
+ my ($par, $sender, $replyto, $site, $action, $log, $approved,
+ $headers, $body) = @_;
+
+ if ($action eq 'mail') {
+ my $mail = sendmail("ihave by $sender");
+ print $mail map { s/^~/~~/; "$_\n" } @$body;
+ close $mail or logdie('Cannot send mail: ' . $!);
+ } elsif ($action eq 'log') {
+ if ($log) {
+ logger($log, "ihave $sender", $headers, $body);
+ } else {
+ logmsg("ihave $sender");
+ }
+ } elsif ($action eq 'doit') {
+ my $tempfile = "$inn::tmpdir/ihave.$$";
+ open(GREPHIST, "|grephistory -i > $tempfile")
+ or logdie('Cannot run grephistory: ' . $!);
+ foreach (@$body) {
+ print GREPHIST "$_\n";
+ }
+ close GREPHIST;
+
+ if (-s $tempfile) {
+ my $inews = open("$inn::inews -h")
+ or logdie('Cannot run inews: ' . $!);
+ print $inews "Newsgroups: to.$site\n"
+ . "Subject: cmsg sendme $inn::pathhost\n"
+ . "Control: sendme $inn::pathhost\n\n";
+ open(TEMPFILE, $tempfile) or logdie("Cannot open $tempfile: $!");
+ print $inews $_ while <TEMPFILE>;
+ close $inews or die $!;
+ close TEMPFILE;
+ }
+ unlink $tempfile;
+ }
+}
+
+1;
--- /dev/null
+## $Id: newgroup.pl 7849 2008-05-25 17:11:32Z iulius $
+##
+## newgroup control message handler.
+##
+## Copyright 2001 by Marco d'Itri <md@linux.it>
+##
+## 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.
+
+use strict;
+
+sub control_newgroup {
+ my ($par, $sender, $replyto, $site, $action, $log, $approved,
+ $headers, $body) = @_;
+ my ($groupname, $modflag) = @$par;
+
+ $modflag ||= '';
+ my $modcmd = $modflag eq 'moderated' ? 'm' : 'y';
+
+ my $errmsg;
+ $errmsg= local_checkgroupname($groupname) if defined &local_checkgroupname;
+ if ($errmsg) {
+ $errmsg = checkgroupname($groupname) if $errmsg eq 'DONE';
+
+ if ($log) {
+ logger($log, "skipping newgroup ($errmsg)", $headers, $body);
+ } else {
+ logmsg("skipping newgroup ($errmsg)");
+ }
+ return;
+ }
+
+ # Scan active to see what sort of change we are making.
+ open(ACTIVE, $inn::active) or logdie("Cannot open $inn::active: $!");
+ my @oldgroup;
+ while (<ACTIVE>) {
+ next unless /^(\Q$groupname\E)\s\d+\s\d+\s(\w)/;
+ @oldgroup = split /\s+/;
+ last;
+ }
+ close ACTIVE;
+
+ my $status;
+ my $ngdesc = 'No description.';
+ my $olddesc = '';
+ my $ngname = $groupname;
+
+ # If there is a tag line, search whether the description has changed.
+ my $found = 0;
+ my $ngline = '';
+ foreach (@$body) {
+ if ($found) {
+ # It is the line which contains the description.
+ $ngline = $_;
+ last;
+ }
+ $found = 1 if $_ =~ /^For your newsgroups file:\s*$/;
+ }
+
+ if ($found) {
+ ($ngname, $ngdesc) = split(/\s+/, $ngline, 2);
+ if ($ngdesc) {
+ $ngdesc =~ s/\s+$//;
+ $ngdesc =~ s/\s+\(moderated\)\s*$//i;
+ $ngdesc .= ' (Moderated)' if $modflag eq 'moderated';
+ }
+ # Scan newsgroups to see the previous description, if any.
+ open(NEWSGROUPS, $inn::newsgroups)
+ or logdie("Cannot open $inn::newsgroups: $!");
+ while (<NEWSGROUPS>) {
+ if (/^\Q$groupname\E\s+(.*)/) {
+ $olddesc = $1;
+ last;
+ }
+ }
+ close NEWSGROUPS;
+ }
+
+ if (@oldgroup) {
+ if ($oldgroup[3] eq 'm' and $modflag ne 'moderated') {
+ $status = 'be made unmoderated';
+ } elsif ($oldgroup[3] ne 'm' and $modflag eq 'moderated') {
+ $status = 'be made moderated';
+ } else {
+ if ($ngdesc eq $olddesc) {
+ $status = 'no change';
+ } else {
+ $status = 'have a new description';
+ }
+ }
+ } elsif (not $approved) {
+ $status = 'unapproved';
+ } else {
+ $status = 'be created';
+ }
+
+ if ($action eq 'mail' and $status !~ /(no change|unapproved)/) {
+ my $mail = sendmail("newgroup $groupname $modcmd $sender");
+ print $mail <<END;
+$sender asks for $groupname
+to $status.
+
+If this is acceptable, type:
+ $inn::newsbin/ctlinnd newgroup $groupname $modcmd $sender
+
+And do not forget to update the corresponding description in your
+newsgroups file.
+
+The control message follows:
+
+END
+ print $mail map { s/^~/~~/; "$_\n" } @$headers;
+ print $mail "\n";
+ print $mail map { s/^~/~~/; "$_\n" } @$body;
+ close $mail or logdie("Cannot send mail: $!");
+ } elsif ($action eq 'log') {
+ if ($log) {
+ logger($log, "skipping newgroup $groupname $modcmd"
+ . " $sender (would $status)", $headers, $body);
+ } else {
+ logmsg("skipping newgroup $groupname $modcmd $sender"
+ . " (would $status)");
+ }
+ } elsif ($action eq 'doit' and $status ne 'unapproved') {
+ if ($status ne 'no change') {
+ # The status 'be made (un)moderated' prevails over
+ # 'have a new description' so it is executed.
+ ctlinnd('newgroup', $groupname, $modcmd, $sender)
+ if $status ne 'have a new description';
+ # We know the description has changed.
+ update_desc($ngname, $ngdesc) if $ngdesc and $ngname eq $groupname;
+ }
+
+ if ($log) {
+ logger($log, "newgroup $groupname $modcmd $status $sender",
+ $headers, $body) if ($log ne 'mail' or $status ne 'no change');
+ }
+ }
+ return;
+}
+
+sub update_desc {
+ my ($name, $desc) = @_;
+ shlock("$inn::locks/LOCK.newsgroups");
+ my $tempfile = "$inn::newsgroups.$$";
+ open(NEWSGROUPS, $inn::newsgroups)
+ or logdie("Cannot open $inn::newsgroups: $!");
+ open(TEMPFILE, ">$tempfile") or logdie("Cannot open $tempfile: $!");
+ while (<NEWSGROUPS>) {
+ next if (/^\Q$name\E\s+(.*)/);
+ print TEMPFILE $_;
+ }
+ # We now write a pretty line for the description.
+ if (length $name < 8) {
+ print TEMPFILE "$name\t\t\t$desc\n";
+ } elsif (length $name < 16) {
+ print TEMPFILE "$name\t\t$desc\n";
+ } else {
+ print TEMPFILE "$name\t$desc\n";
+ }
+ close TEMPFILE;
+ close NEWSGROUPS;
+ rename($tempfile, $inn::newsgroups)
+ or logdie("Cannot rename $tempfile: $!");
+ unlink("$inn::locks/LOCK.newsgroups", $tempfile);
+}
+
+# Check the group name. This is partially derived from C News.
+# Some checks are commented out if I think they're too strict or
+# language-dependent. Your mileage may vary.
+sub checkgroupname {
+ local $_ = shift;
+
+ # whole-name checking
+ return 'Empty group name' if /^$/;
+ return 'Whitespace in group name' if /\s/;
+ return 'Unsafe group name' if /[\`\/:;]/;
+ return 'Bad dots in group name' if /^\./ or /\.$/ or /\.\./;
+# return 'Group name does not begin/end with alphanumeric'
+# if (/^[a-zA-Z0-9].+[a-zA-Z0-9]$/;
+ return 'Group name begins in control., junk. or to.' if /^(?:control|junk|to)\./;
+# return 'Group name too long' if length $_ > 128;
+
+ my @components = split(/\./);
+ # prevent alt.a.b.c.d.e.f.g.w.x.y.z...
+ return 'Too many components' if $#components > 9;
+
+ # per-component checking
+ for (my $i = 0; $i <= $#components; $i++) {
+ local $_ = $components[$i];
+ return 'all-numeric name component' if /^[0-9]+$/;
+# return 'name component starts with non-alphanumeric' if /^[a-zA-Z0-9]/;
+# return 'name component does not contain letter' if not /[a-zA-Z]/;
+ return "`all' or `ctl' used as name component" if /^(?:all|ctl)$/;
+# return 'name component longer than 30 characters' if length $_ > 30;
+# return 'uppercase letter(s) in name' if /[A-Z]/;
+ return 'illegal character(s) in name' if /[^a-z0-9+_\-.]/;
+ # sigh, c++ etc must be allowed
+ return 'repeated punctuation in name' if /--|__|\+\+./;
+# return 'repeated component(s) in name' if ($i + 2 <= $#components
+# and $_ eq $components[$i + 1] and $_ eq $components[$i + 2]);
+ }
+ return '';
+}
+
+1;
--- /dev/null
+## $Id: rmgroup.pl 7743 2008-04-06 10:04:43Z iulius $
+##
+## rmgroup control message handler.
+##
+## Copyright 2001 by Marco d'Itri <md@linux.it>
+##
+## 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.
+
+use strict;
+
+sub control_rmgroup {
+ my ($par, $sender, $replyto, $site, $action, $log, $approved,
+ $headers, $body) = @_;
+ my ($groupname) = @$par;
+
+ # Scan active to see what sort of change we are making.
+ open(ACTIVE, $inn::active) or logdie("Cannot open $inn::active: $!");
+ my @oldgroup;
+ while (<ACTIVE>) {
+ next unless /^(\Q$groupname\E)\s\d+\s\d+\s(\w)/;
+ @oldgroup = split /\s+/;
+ last;
+ }
+ close ACTIVE;
+ my $status;
+ if (not @oldgroup) {
+ $status = 'no change';
+ } elsif (not $approved) {
+ $status = 'unapproved';
+ } else {
+ $status = 'removed';
+ }
+
+ if ($action eq 'mail' and $status !~ /(no change|unapproved)/) {
+ my $mail = sendmail("rmgroup $groupname $sender");
+ print $mail <<END;
+$sender asks for $groupname
+to be $status.
+
+If this is acceptable, type:
+ $inn::newsbin/ctlinnd rmgroup $groupname
+
+And do not forget to remove the corresponding description, if any,
+from your newsgroups file.
+
+The control message follows:
+
+END
+ print $mail map { s/^~/~~/; "$_\n" } @$headers;
+ print $mail "\n";
+ print $mail map { s/^~/~~/; "$_\n" } @$body;
+ close $mail or logdie("Cannot send mail: $!");
+ } elsif ($action eq 'log') {
+ if ($log) {
+ logger($log, "skipping rmgroup $groupname"
+ . " $sender (would be $status)", $headers, $body);
+ } else {
+ logmsg("skipping rmgroup $groupname $sender (would be $status)");
+ }
+ } elsif ($action eq 'doit' and $status !~ /(no change|unapproved)/) {
+ ctlinnd('rmgroup', $groupname);
+ # Update newsgroups too.
+ shlock("$inn::locks/LOCK.newsgroups");
+ open(NEWSGROUPS, $inn::newsgroups)
+ or logdie("Cannot open $inn::newsgroups: $!");
+ my $tempfile = "$inn::newsgroups.$$";
+ open(TEMPFILE, ">$tempfile") or logdie("Cannot open $tempfile: $!");
+ while (<NEWSGROUPS>) {
+ print TEMPFILE $_ if not /^\Q$groupname\E\s/;
+ }
+ close TEMPFILE;
+ close NEWSGROUPS;
+ rename($tempfile, $inn::newsgroups)
+ or logdie("Cannot rename $tempfile: $!");
+ unlink "$inn::locks/LOCK.newsgroups";
+ unlink $tempfile;
+
+ logger($log, "rmgroup $groupname $status $sender", $headers, $body)
+ if $log;
+ }
+}
+
+1;
--- /dev/null
+## $Id: sendme.pl 4932 2001-07-19 00:32:56Z rra $
+##
+## sendme control message handler.
+##
+## Copyright 2001 by Marco d'Itri <md@linux.it>
+##
+## 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.
+
+use strict;
+
+sub control_sendme {
+ my ($par, $sender, $replyto, $site, $action, $log, $approved,
+ $headers, $body) = @_;
+
+ if ($action eq 'mail') {
+ my $mail = sendmail("sendme by $sender");
+ print $mail map { s/^~/~~/; "$_\n" } @$body;
+ close $mail or logdie('Cannot send mail: ' . $!);
+ } elsif ($action eq 'log') {
+ if ($log) {
+ logger($log, "sendme $sender", $headers, $body);
+ } else {
+ logmsg("sendme from $sender");
+ }
+ } elsif ($action eq 'doit') {
+ my $tempfile = "$inn::tmpdir/sendme.$$";
+ open(GREPHIST, "|grephistory -s > $tempfile")
+ or logdie("Cannot run grephistory: $!");
+ foreach (@$body) {
+ print GREPHIST "$_\n";
+ }
+ close GREPHIST or logdie("Cannot run grephistory: $!");
+
+ if (-s $tempfile and $site =~ /^[a-zA-Z0-9.-_]+$/) {
+ open(TEMPFILE, $tempfile) or logdie("Cannot open $tempfile: $!");
+ open(BATCH, ">>$inn::batch/$site.work")
+ or logdie("Cannot open $inn::batch/$site.work: $!");
+ print BATCH $_ while <TEMPFILE>;
+ close BATCH;
+ close TEMPFILE;
+ }
+ unlink $tempfile;
+ }
+}
+
+1;
--- /dev/null
+## $Id: sendsys.pl 4932 2001-07-19 00:32:56Z rra $
+##
+## sendsys control message handler.
+##
+## Copyright 2001 by Marco d'Itri <md@linux.it>
+##
+## 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.
+
+use strict;
+
+sub control_sendsys {
+ my ($par, $sender, $replyto, $site, $action, $log, $approved,
+ $headers, $body) = @_;
+ my ($where) = @$par;
+
+ if ($action eq 'mail') {
+ my $mail = sendmail("sendsys $sender");
+ print $mail <<END;
+$sender has requested that you send a copy
+of your newsgroups file.
+
+If this is acceptable, type:
+ $inn::mailcmd -s "sendsys reply from $inn::pathhost" $replyto < $inn::newsfeeds
+
+The control message follows:
+
+END
+ print $mail map { s/^~/~~/; "$_\n" } @$headers;
+ print $mail "\n";
+ print $mail map { s/^~/~~/; "$_\n" } @$body;
+ close $mail or logdie("Cannot send mail: $!");
+ } elsif ($action eq 'log') {
+ if ($log) {
+ logger($log, "sendsys $sender", $headers, $body);
+ } else {
+ logmsg("sendsys $sender");
+ }
+ } elsif ($action =~ /^(doit|doifarg)$/) {
+ if ($action eq 'doifarg' and $where ne $inn::pathhost) {
+ logmsg("skipped sendsys $sender");
+ return;
+ }
+ my $mail = sendmail("sendsys reply from $inn::pathhost", $replyto);
+ open(NEWSFEEDS, $inn::newsfeeds)
+ or logdie("Cannot open $inn::newsfeeds: $!");
+ print $mail $_ while <NEWSFEEDS>;
+ print $mail "\n";
+ close NEWSFEEDS;
+ close $mail or logdie("Cannot send mail: $!");
+
+ logger($log, "sendsys $sender to $replyto", $headers, $body) if $log;
+ }
+}
+
+1;
--- /dev/null
+## $Id: senduuname.pl 4932 2001-07-19 00:32:56Z rra $
+##
+## senduuname control message handler.
+##
+## Copyright 2001 by Marco d'Itri <md@linux.it>
+##
+## 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.
+
+use strict;
+
+sub control_senduuname {
+ my ($par, $sender, $replyto, $site, $action, $log, $approved,
+ $headers, $body) = @_;
+ my ($where) = @$par;
+
+ if ($action eq 'mail') {
+ my $mail = sendmail("senduuname $sender");
+ print $mail <<END;
+$sender has requested information about your UUCP name.
+
+If this is acceptable, type:
+ uuname | $inn::mailcmd -s "senduuname reply from $inn::pathhost" $replyto
+
+The control message follows:
+
+END
+ print $mail map { s/^~/~~/; "$_\n" } @$headers;
+ print $mail "\n";
+ print $mail map { s/^~/~~/; "$_\n" } @$body;
+ close $mail or logdie("Cannot send mail: $!");
+ } elsif ($action eq 'log') {
+ if ($log) {
+ logger($log, "senduuname $sender", $headers, $body);
+ } else {
+ logmsg("senduuname $sender");
+ }
+ } elsif ($action =~ /^(doit|doifarg)$/) {
+ if ($action eq 'doifarg' and $where ne $inn::pathhost) {
+ logmsg("skipped senduuname $sender");
+ return;
+ }
+ my $mail = sendmail("senduuname reply from $inn::pathhost", $replyto);
+ open(UUNAME, 'uuname|') or logdie("Cannot run uuname: $!");
+ print $mail $_ while <UUNAME>;
+ close UUNAME or logdie("Cannot run uuname: $!");
+ close $mail or logdie("Cannot send mail: $!");
+
+ logger($log, "senduuname $sender to $replyto", $headers, $body) if $log;
+ }
+}
+
+1;
--- /dev/null
+## $Id: version.pl 4932 2001-07-19 00:32:56Z rra $
+##
+## version control message handler.
+##
+## Copyright 2001 by Marco d'Itri <md@linux.it>
+##
+## 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.
+
+use strict;
+
+sub control_version {
+ my ($par, $sender, $replyto, $site, $action, $log, $approved,
+ $headers, $body) = @_;
+ my ($where) = @$par;
+
+ my $version = $inn::version || '(unknown version)';
+
+ if ($action eq 'mail') {
+ my $mail = sendmail("version $sender");
+ print $mail <<END;
+$sender has requested information about your
+news software version.
+
+If this is acceptable, type:
+ echo "InterNetNews $version" | $inn::mailcmd -s "version reply from $inn::pathhost" $replyto
+
+The control message follows:
+
+END
+ print $mail map { s/^~/~~/; "$_\n" } @$headers;
+ print $mail "\n";
+ print $mail map { s/^~/~~/; "$_\n" } @$body;
+ close $mail or logdie("Cannot send mail: $!");
+ } elsif ($action eq 'log') {
+ if ($log) {
+ logger($log, "version $sender", $headers, $body);
+ } else {
+ logmsg("version $sender");
+ }
+ } elsif ($action =~ /^(doit|doifarg)$/) {
+ if ($action eq 'doifarg' and $where ne $inn::pathhost) {
+ logmsg("skipped version $sender");
+ return;
+ }
+ sendmail("version reply from $inn::pathhost", $replyto,
+ [ "InterNetNews $version\n" ]);
+
+ logger($log, "version $sender to $replyto", $headers, $body) if $log;
+ }
+}
+
+1;
--- /dev/null
+#!/usr/bin/perl -w
+# fixscript will replace this line with require innshellvars.pl
+
+##############################################################################
+# perl-nocem - a NoCeM-on-spool implementation for INN 2.x.
+# Copyright 2000 by Miquel van Smoorenburg <miquels@cistron.nl>
+# Copyright 2001 by Marco d'Itri <md@linux.it>
+# This program is licensed under the terms of the GNU General Public License.
+#
+# List of changes:
+#
+# 2002: Patch by Steven M. Christey for untrusted printf input.
+# 2007: Patch by Christoph Biedl for checking a timeout.
+# Documentation improved by Jeffrey M. Vinocur (2002), Russ Allbery (2006)
+# and Julien Elie (2007).
+#
+##############################################################################
+
+require 5.00403;
+use strict;
+
+# XXX FIXME I haven't been able to load it only when installed.
+# If nobody can't fix it just ship the program with this line commented.
+#use Time::HiRes qw(time);
+
+my $keyring = $inn::pathetc . '/pgp/ncmring.gpg';
+
+# XXX To be moved to a config file.
+#sub local_want_cancel_id {
+# my ($group, $hdrs) = @_;
+#
+## Hippo has too many false positives to be useful outside of pr0n groups
+# if ($hdrs->{issuer} =~ /(?:Ultra|Spam)Hippo/) {
+# foreach (split(/,/, $group)) {
+# return 1 if /^alt\.(?:binar|sex)/;
+# }
+# return 0;
+# }
+# return 1;
+#}
+
+# no user serviceable parts below this line ###################################
+
+# global variables
+my ($working, $got_sighup, $got_sigterm, @ncmperm, $cancel);
+my $use_syslog = 0;
+my $log_open = 0;
+my $nntp_open = 0;
+my $last_cancel = 0;
+my $socket_timeout = $inn::peertimeout - 100;
+
+my $logfile = $inn::pathlog . '/perl-nocem.log';
+
+# initialization and main loop ###############################################
+
+eval { require Sys::Syslog; import Sys::Syslog; $use_syslog = 1; };
+
+if ($use_syslog) {
+ eval "sub Sys::Syslog::_PATH_LOG { '/dev/log' }" if $^O eq 'dec_osf';
+ Sys::Syslog::setlogsock('unix') if $^O =~ /linux|dec_osf/;
+ openlog('nocem', '', $inn::syslog_facility);
+}
+
+if (not $inn::gpgv) {
+ logmsg('cannot find the gpgv binary', 'err');
+ sleep 5;
+ exit 1;
+}
+
+if ($inn::version and not $inn::version =~ /^INN 2\.[0123]\./) {
+ $cancel = \&cancel_nntp;
+} else {
+ $cancel = \&cancel_ctlinnd;
+}
+
+$SIG{HUP} = \&hup_handler;
+$SIG{INT} = \&term_handler;
+$SIG{TERM} = \&term_handler;
+$SIG{PIPE} = \&term_handler;
+
+logmsg('starting up');
+
+unless (read_ctlfile()) {
+ sleep 5;
+ exit 1;
+}
+
+while (<STDIN>) {
+ chop;
+ $working = 1;
+ do_nocem($_);
+ $working = 0;
+ term_handler() if $got_sigterm;
+ hup_handler() if $got_sighup;
+}
+
+logmsg('exiting because of EOF', 'debug');
+exit 0;
+
+##############################################################################
+
+# Process one NoCeM notice.
+sub do_nocem {
+ my $token = shift;
+ my $start = time;
+
+ # open the article and verify the notice
+ my $artfh = open_article($token);
+ return if not defined $artfh;
+ my ($msgid, $nid, $issuer, $nocems) = read_nocem($artfh);
+ close $artfh;
+ return unless $nocems;
+
+ &$cancel($nocems);
+ logmsg("Articles cancelled: " . join(' ', @$nocems), 'debug');
+ my $diff = (time - $start) || 0.01;
+ my $nr = scalar @$nocems;
+ logmsg(sprintf("processed notice %s by %s (%d ids, %.5f s, %.1f/s)",
+ $nid, $issuer, $nr, $diff, $nr / $diff));
+}
+
+# - Check if it is a PGP signed NoCeM notice
+# - See if we want it
+# - Then check PGP signature
+sub read_nocem {
+ my $artfh = shift;
+
+ # Examine the first 200 lines to see if it is a PGP signed NoCeM.
+ my $ispgp = 0;
+ my $isncm = 0;
+ my $inhdr = 1;
+ my $i = 0;
+ my $body = '';
+ my ($from, $msgid);
+ while (<$artfh>) {
+ last if $i++ > 200;
+ s/\r\n$/\n/;
+ if ($inhdr) {
+ if (/^$/) {
+ $inhdr = 0;
+ } elsif (/^From:\s+(.*)\s*$/i) {
+ $from = $1;
+ } elsif (/^Message-ID:\s+(<.*>)/i) {
+ $msgid = $1;
+ }
+ } else {
+ $body .= $_;
+ $ispgp = 1 if /^-----BEGIN PGP SIGNED MESSAGE-----/;
+ if (/^\@\@BEGIN NCM HEADERS/) {
+ $isncm = 1;
+ last;
+ }
+ }
+ }
+
+ # must be a PGP signed NoCeM.
+ if (not $ispgp) {
+ logmsg("Article $msgid: not PGP signed", 'debug');
+ return;
+ }
+ if (not $isncm) {
+ logmsg("Article $msgid: not a NoCeM", 'debug');
+ return;
+ }
+
+ # read the headers of this NoCeM, and check if it's supported.
+ my %hdrs;
+ while (<$artfh>) {
+ s/\r\n/\n/;
+ $body .= $_;
+ last if /^\@\@BEGIN NCM BODY/;
+ my ($key, $val) = /^([^:]+)\s*:\s*(.*)$/;
+ $hdrs{lc $key} = $val;
+ }
+ foreach (qw(action issuer notice-id type version)) {
+ next if $hdrs{$_};
+ logmsg("Article $msgid: missing $_ pseudo header", 'debug');
+ return;
+ }
+ return if not supported_nocem($msgid, \%hdrs);
+
+ # decide if we want it.
+ if (not want_nocem(\%hdrs)) {
+ logmsg("Article $msgid: unwanted ($hdrs{issuer}/$hdrs{type})", 'debug');
+ return;
+ }
+# XXX want_hier() not implemented
+# if ($hdrs{hierarchies} and not want_hier($hdrs{hierarchies})) {
+# logmsg("Article $msgid: unwanted hierarchy ($hdrs{hierarchies})",
+# 'debug');
+# return;
+# }
+
+ # We do want it, so read the entire article. Also copy it to
+ # a temp file so that we can check the PGP signature when done.
+ my $tmpfile = "$inn::pathtmp/nocem.$$";
+ if (not open(OFD, ">$tmpfile")) {
+ logmsg("cannot open temp file $tmpfile: $!", 'err');
+ return;
+ }
+ print OFD $body;
+ undef $body;
+
+ # process NoCeM body.
+ my $inbody = 1;
+ my @nocems;
+ my ($lastid, $lastgrp);
+ while (<$artfh>) {
+ s/\r\n$/\n/;
+ print OFD;
+ $inbody = 0 if /^\@\@END NCM BODY/;
+ next if not $inbody or /^#/;
+
+ my ($id, $grp) = /^(\S*)\s+(\S+)/;
+ next if not $grp;
+ if ($id) {
+ push @nocems, $lastid
+ if $lastid and want_cancel_id($lastgrp, \%hdrs);
+ $lastid = $id;
+ $lastgrp = $grp;
+ } else {
+ $lastgrp .= ',' . $grp;
+ }
+ }
+ push @nocems, $lastid if $lastid and want_cancel_id($lastgrp, \%hdrs);
+ close OFD;
+
+ # at this point we need to verify the PGP signature.
+ return if not @nocems;
+ my $e = pgp_check($hdrs{issuer}, $msgid, $tmpfile);
+ unlink $tmpfile;
+ return if not $e;
+
+ return ($msgid, $hdrs{'notice-id'}, $hdrs{issuer}, \@nocems);
+}
+
+# XXX not implemented: code to discard notices for groups we don't carry
+sub want_cancel_id {
+ my ($group, $hdrs) = @_;
+
+ return local_want_cancel_id(@_) if defined &local_want_cancel_id;
+ 1;
+}
+
+# Do we actually want this NoCeM?
+sub want_nocem {
+ my $hdrs = shift;
+
+ foreach (@ncmperm) {
+ my ($issuer, $type) = split(/\001/);
+ if ($hdrs->{issuer} =~ /$issuer/i) {
+ return 1 if '*' eq $type or lc $hdrs->{type} eq $type;
+ }
+ }
+ return 0;
+}
+
+sub supported_nocem {
+ my ($msgid, $hdrs) = @_;
+
+ if ($hdrs->{version} !~ /^0\.9[0-9]?$/) {
+ logmsg("Article $msgid: version $hdrs->{version} not supported",
+ 'debug');
+ return 0;
+ }
+ if ($hdrs->{action} ne 'hide') {
+ logmsg("Article $msgid: action $hdrs->{action} not supported",
+ 'debug');
+ return 0;
+ }
+ return 1;
+}
+
+# Check the PGP signature on an article.
+sub pgp_check {
+ my ($issuer, $msgid, $art) = @_;
+
+ # fork and spawn a child
+ my $pid = open(PFD, '-|');
+ if (not defined $pid) {
+ logmsg("pgp_check: cannot fork: $!", 'err');
+ return 0;
+ }
+ if ($pid == 0) {
+ open(STDERR, '>&STDOUT');
+ exec($inn::gpgv, '--status-fd=1',
+ $keyring ? '--keyring=' . $keyring : '', $art);
+ exit 126;
+ }
+
+ # Read the result and check status code.
+ local $_ = join('', <PFD>);
+ my $status = 0;
+ if (not close PFD) {
+ if ($? >> 8) {
+ $status = $? >> 8;
+ } else {
+ logmsg("Article $msgid: $inn::gpgv killed by signal " . ($? & 255));
+ return 0;
+ }
+ }
+# logmsg("Command line was: $inn::gpgv --status-fd=1"
+# . ($keyring ? ' --keyring=' . $keyring : '') . " $art", 'debug');
+# logmsg("Full PGP output: >>>$_<<<", 'debug');
+
+ if (/^\[GNUPG:\]\s+GOODSIG\s+\S+\s+(.*)/m) {
+ return 1 if $1 =~ /\Q$issuer\E/;
+ logmsg("Article $msgid: signed by $1 instead of $issuer");
+ } elsif (/^\[GNUPG:\]\s+NO_PUBKEY\s+(\S+)/m) {
+ logmsg("Article $msgid: $issuer (ID $1) not in keyring");
+ } elsif (/^\[GNUPG:\]\s+BADSIG\s+\S+\s+(.*)/m) {
+ logmsg("Article $msgid: bad signature from $1");
+ } elsif (/^\[GNUPG:\]\s+BADARMOR/m or /^\[GNUPG:\]\s+UNEXPECTED/m) {
+ logmsg("Article $msgid: malformed signature");
+ } elsif (/^\[GNUPG:\]\s+ERRSIG\s+(\S+)/m) {
+ # safety net: we get there if we don't know about some token
+ logmsg("Article $msgid: unknown error (ID $1)");
+ } else {
+ # some other error we don't know about happened.
+ # 126 is returned by the child if exec fails.
+ s/ at \S+ line \d+\.\n$//; s/\n/_/;
+ logmsg("Article $msgid: $inn::gpgv exited "
+ . (($status == 126) ? "($_)" : "with status $status"), 'err');
+ }
+ return 0;
+}
+
+# Read article.
+sub open_article {
+ my $token = shift;
+
+ if ($token =~ /^\@.+\@$/) {
+ my $pid = open(ART, '-|');
+ if ($pid < 0) {
+ logmsg('Cannot fork: ' . $!, 'err');
+ return undef;
+ }
+ if ($pid == 0) {
+ exec("$inn::newsbin/sm", '-q', $token) or
+ logmsg("Cannot exec sm: $!", 'err');
+ return undef;
+ }
+ return *ART;
+ } else {
+ return *ART if open(ART, $token);
+ logmsg("Cannot open article $token: $!", 'err');
+ }
+ return undef;
+}
+
+# Cancel a number of Message-IDs. We use ctlinnd to do this,
+# and we run up to 15 of them at the same time (10 usually).
+sub cancel_ctlinnd {
+ my @ids = @{$_[0]};
+
+ while (@ids > 0) {
+ my $max = @ids <= 15 ? @ids : 10;
+ for (my $i = 1; $i <= $max; $i++) {
+ my $msgid = shift @ids;
+ my $pid;
+ sleep 5 until (defined ($pid = fork));
+ if ($pid == 0) {
+ exec "$inn::pathbin/ctlinnd", '-s', '-t', '180',
+ 'cancel', $msgid;
+ exit 126;
+ }
+# logmsg("cancelled: $msgid [$i/$max]", 'debug');
+ }
+ # Now wait for all children.
+ while ((my $pid = wait) > 0) {
+ next unless $?;
+ if ($? >> 8) {
+ logmsg("Child $pid died with status " . ($? >> 8), 'err');
+ } else {
+ logmsg("Child $pid killed by signal " . ($? & 255), 'err');
+ }
+ }
+ }
+}
+
+sub cancel_nntp {
+ my $ids = shift;
+ my $r;
+
+ if ($nntp_open and time - $socket_timeout > $last_cancel) {
+ logmsg('Close socket for timeout');
+ close (NNTP);
+ $nntp_open = 0;
+ }
+ if (not $nntp_open) {
+ use Socket;
+ if (not socket(NNTP, PF_UNIX, SOCK_STREAM, 0)) {
+ logmsg("socket: $!", 'err');
+ goto ERR;
+ }
+ if (not connect(NNTP, sockaddr_un($inn::pathrun . '/nntpin'))) {
+ logmsg("connect: $!", 'err');
+ goto ERR;
+ }
+ if (($r = <NNTP>) !~ /^200 /) {
+ $r =~ s/\r\n$//;
+ logmsg("bad reply from server: $r", 'err');
+ goto ERR;
+ }
+ select NNTP; $| = 1; select STDOUT;
+ print NNTP "MODE CANCEL\r\n";
+ if (($r = <NNTP>) !~ /^284 /) {
+ $r =~ s/\r\n$//;
+ logmsg("MODE CANCEL not supported: $r", 'err');
+ goto ERR;
+ }
+ $nntp_open = 1;
+ }
+ foreach (@$ids) {
+ print NNTP "$_\r\n";
+ if (($r = <NNTP>) !~ /^289/) {
+ $r =~ s/\r\n$//;
+ logmsg("cannot cancel $_: $r", 'err');
+ goto ERR;
+ }
+ }
+ $last_cancel = time;
+ return;
+
+ERR:
+ # discard unusable socket
+ close (NNTP);
+ logmsg('Switching to ctlinnd...', 'err');
+ cancel_ctlinnd($ids);
+ $cancel = \&cancel_ctlinnd;
+}
+
+sub read_ctlfile {
+ my $permfile = $inn::pathetc . '/nocem.ctl';
+
+ unless (open(CTLFILE, $permfile)) {
+ logmsg("Cannot open $permfile: $!", 'err');
+ return 0;
+ }
+ while (<CTLFILE>) {
+ chop;
+ s/^\s+//; s/\s+$//;
+ next if /^#/ or /^$/;
+ my ($issuer, $type) = split(/:/, lc $_);
+ logmsg("Cannot parse nocem.ctl line <<$_>>", 'err')
+ if not $issuer and $type;
+ $type =~ s/\s//g;
+ push @ncmperm, "$issuer\001$_" foreach split(/,/, $type);
+ }
+ close CTLFILE;
+ return 1;
+}
+
+sub logmsg {
+ my ($msg, $lvl) = @_;
+
+ if (not $use_syslog) {
+ if ($log_open == 0) {
+ open(LOG, ">>$logfile") or die "Cannot open log: $!";
+ $log_open = 1;
+ select LOG; $| = 1; select STDOUT;
+ }
+ $lvl ||= 'notice';
+ print LOG "$lvl: $msg\n";
+ return;
+ }
+ syslog($lvl || 'notice', '%s', $msg);
+}
+
+sub hup_handler {
+ $got_sighup = 1;
+ return if $working;
+ close LOG;
+ $log_open = 0;
+}
+
+sub term_handler {
+ $got_sigterm = 1;
+ return if $working;
+ logmsg('exiting because of signal');
+ exit 1;
+}
+
+# lint food
+print $inn::pathrun.$inn::pathlog.$inn::pathetc.$inn::newsbin.$inn::pathbin
+ .$inn::pathtmp.$inn::peertimeout.$inn::syslog_facility;
+
+__END__
+
+=head1 NAME
+
+perl-nocem - A NoCeM-on-spool implementation for S<INN 2.x>
+
+=head1 SYNOPSIS
+
+perl-nocem
+
+=head1 DESCRIPTION
+
+NoCeM, which is pronounced I<No See 'Em>, is a protocol enabling
+authenticated third-parties to issue notices which can be used
+to cancel unwanted articles (like spam and articles in moderated
+newsgroups which were not approved by their moderators). It can
+also be used by readers as a I<third-party killfile>. It is
+intended to eventually replace the protocol for third-party cancel
+messages.
+
+B<perl-nocem> processes third-party, PGP-signed article cancellation
+notices. It is possible not to honour all NoCeM notices but only those
+which are sent by people whom you trust (that is to say if you trust
+the PGP key they use to sign their NoCeM notices). Indeed, it is up
+to you to decide whether you wish to honour their notices, depending
+on the criteria they use.
+
+Processing NoCeM notices is easy to set up:
+
+=over 4
+
+=item 1.
+
+Import the keys of the NoCeM issuers you trust in order to check
+the authenticity of their notices. You can do:
+
+ gpg --no-default-keyring --primary-keyring <pathetc>/pgp/ncmring.gpg --import <key-file>
+
+where <pathetc> is the value of the I<pathetc> parameter set in F<inn.conf>
+and <key-file> the file containing the key(s) to import. The keyring
+must be located in I<pathetc>/pgp/ncmring.gpg (create the directory
+before using B<gpg>). For old PGP-generated keys, you may have to use
+B<--allow-non-selfsigned-uid> if they are not properly self-signed,
+but anyone creating a key really should self-sign the key. Current
+PGP implementations do this automatically.
+
+The keys of NoCeM issuers can be found in the web site of I<The NoCeM Registry>:
+L<http://www.xs4all.nl/~rosalind/nocemreg/nocemreg.html>. You can even
+download there a unique file which contains all the keys.
+
+=item 2.
+
+Create a F<nocem.ctl> config file in I<pathetc> indicating the NoCeM issuers
+and notices you want to follow. This permission file contains lines like:
+
+ annihilator-1:*
+ clewis@ferret.ocunix:mmf
+ stephane@asynchrone:mmf,openproxy,spam
+
+This will remove all articles for which the issuer (first part of the line,
+before the colon C<:>) has issued NoCeM notices corresponding to the
+criteria specified after the colon.
+
+You will also find information about that on the web site of
+I<The NoCeM Registry>.
+
+=item 3.
+
+Add to the F<newsfeeds> file an entry like this one in order to feed
+B<perl-nocem> the NoCeM notices posted to alt.nocem.misc and
+news.lists.filters:
+
+ nocem!\
+ :!*,alt.nocem.misc,news.lists.filters\
+ :Tc,Wf,Ap:<pathbin>/perl-nocem
+
+with the correct path to B<perl-nocem>, located in <pathbin>. Then, reload
+the F<newsfeeds> file (C<ctlinnd reload newsfeeds 'NoCeM channel feed'>).
+
+Note that you should at least carry news.lists.filters on your news
+server (or other newsgroups where NoCeM notices are sent) if you wish
+to process them.
+
+=item 4.
+
+Everything should now work. However, do not hesitate to manually test
+B<perl-nocem> with a NoCeM notice, using:
+
+ grephistory '<Message-ID>' | perl-nocem
+
+Indeed, B<perl-nocem> expects tokens on its standard input, and
+B<grephistory> can easily give it the token of a known article,
+thanks to its Message-ID.
+
+=back
+
+When you have verified that everything works, you can eventually turn
+off regular spam cancels, if you want, not processing any longer
+cancels containing C<cyberspam> in the Path: header (see the
+I<refusecybercancels> parameter in F<inn.conf>).
+
+=head1 FILES
+
+=over 4
+
+=item I<pathbin>/perl-nocem
+
+The Perl script itself used to process NoCeM notices.
+
+=item I<pathetc>/nocem.ctl
+
+The configuration file which specifies the NoCeM notices to be processed.
+
+=item I<pathetc>/pgp/ncmring.gpg
+
+The keyring which contains the public keys of trusted NoCeM issuers.
+
+=back
+
+=head1 BUGS
+
+The Subject: header is not checked for the @@NCM string and there is no
+check for the presence of the References: header.
+
+The Newsgroups: pseudo header is not checked, but this can be done in
+local_want_cancel_id().
+
+The Hierarchies: header is ignored.
+
+=head1 HISTORY
+
+Copyright 2000 by Miquel van Smoorenburg <miquels@cistron.nl>.
+
+Copyright 2001 by Marco d'Itri <md@linux.it>.
+
+$Id: perl-nocem.in 7733 2008-04-06 09:16:20Z iulius $
+
+=head1 SEE ALSO
+
+gpgv(1), grephistory(1), inn.conf(5), newsfeeds(5), pgp(1).
+
+=cut
--- /dev/null
+#! /usr/bin/perl -w
+# do '@LIBDIR@/innshellvars.pl';
+# If running inside INN, uncomment the above and point to innshellvars.pl.
+#
+# Written April 1996, <tale@isc.org> (David C Lawrence)
+# Currently maintained by Russ Allbery <rra@stanford.edu>
+# Version 1.27, 2005-07-02
+#
+# NOTICE TO INN MAINTAINERS: The version that is shipped with INN is the
+# same as the version that I make available to the rest of the world
+# (including non-INN sites), so please make all changes through me.
+#
+# This program requires Perl 5, probably at least about Perl 5.003 since
+# that's when FileHandle was introduced. If you want to use this program
+# and your Perl is too old, please contact me (rra@stanford.edu) and tell
+# me about it; I want to know what old versions of Perl are still used in
+# practice.
+#
+# Changes from 1.26 -> 1.27
+# -- Default to pubring.gpg when trustedkeys.gpg is not found in the
+# default key location, for backward compatibility.
+#
+# Changes from 1.25 -> 1.26
+# -- Return the correct status code when the message isn't verified
+# instead of always returning 255.
+#
+# Changes from 1.24 -> 1.25
+# -- Fix the -test switch to actually do something.
+# -- Improve date generation when logging to standard output.
+#
+# Changes from 1.23 -> 1.24
+# -- Fix bug in the recognition of wire-format articles.
+#
+# Changes from 1.15 -> 1.23
+# -- Bump version number to match CVS revision number.
+# -- Replaced all signature verification code with code that uses detached
+# signatures. Signatures generated by GnuPG couldn't be verified using
+# attached signatures without adding a Hash: header, and this was the
+# path of least resistance plus avoids munging problems in the future.
+# Code taken from PGP::Sign.
+#
+# Changes from 1.14 -> 1.15
+# -- Added POD documentation.
+# -- Fixed the -test switch so that it works again.
+# -- Dropped Perl 4 compatibility and reformatted. Now passes use strict.
+#
+# Changes from 1.13.1 -> 1.14
+# -- Native support for GnuPG without the pgpgpg wrapper, using GnuPG's
+# program interface by Marco d'Itri.
+# -- Always use Sys::Syslog without any setlogsock call for Perl 5.6.0 or
+# later, since Sys::Syslog in those versions of Perl uses the C library
+# interface and is now portable.
+# -- Default to expecting the key ring in $inn'newsetc/pgp if it exists.
+# -- Fix a portability problem for Perl 4 introduced in 1.12.
+#
+# Changes from 1.13 -> 1.13.1
+# -- Nothing functional, just moved the innshellvars.pl line to the head of
+# the script, to accomodate the build process of INN.
+#
+# Changes from 1.12 -> 1.13
+# -- Use INN's syslog_facility if available.
+#
+# Changes from 1.11 -> 1.12
+# -- Support for GnuPG.
+# -- Use /usr/ucb/logger, if present, instead of /usr/bin/logger (the latter
+# of which, on Solaris at least, is some sort of brain damaged POSIX.2
+# command which doesn't use syslog).
+# -- Made syslog work for dec_osf (version 4, at least).
+# -- Fixed up priority of '.' operator vs bitwise operators.
+#
+# Changes from 1.10 -> 1.11
+# -- Code to log error messages to syslog.
+# See $syslog and $syslog_method configurable variables.
+# -- Configurably allow date stamp on stderr error messages.
+# -- Added locking for multiple concurrent pgp instances.
+# -- More clear error message if pgp exits abnormally.
+# -- Identify PGP 5 "BAD signature" string.
+# -- Minor diddling for INN (path to innshellvars.pl changed).
+#
+# Changes from 1.9 -> 1.10
+# -- Minor diddling for INN 2.0: use $inn'pathtmp if it exists, and
+# work with the new subst method to find innshellvars.pl.
+# -- Do not truncate the tmp file when opening, in case it is really
+# linked to another file.
+#
+# Changes from 1.8 -> 1.9
+# -- Match 'Bad signature' pgp output to return exit status 3 by removing
+# '^' in regexp matched on multiline string.
+#
+# Changes from 1.7 -> 1.8
+# -- Ignore final dot-CRLF if article is in NNTP format.
+#
+# Changes from 1.6 -> 1.7
+# -- Parse PGP 5.0 'good signature' lines.
+# -- Allow -test switch; prints pgp input and output.
+# -- Look for pgp in INN's innshellvars.pl.
+# -- Changed regexp delimiters for stripping $0 to be compatible with old
+# Perl.
+#
+# Changes from 1.5 -> 1.6
+# -- Handle articles encoded in NNTP format ('.' starting line is doubled,
+# \r\n at line end) by stripping NNTP encoding.
+# -- Exit 255 with pointer to $HOME or $PGPPATH if pgp can't find key
+# ring. (It probably doesn't match the necessary error message with
+# ViaCrypt PGP.)
+# -- Failures also report Message-ID so the article can be looked up to
+# retry.
+#
+# Changes from 1.4 -> 1.5
+# -- Force English language for 'Good signature from user' by passing
+# +language=en on pgp command line, rather than setting the
+# environment variable LANGUAGE to 'en'.
+#
+# Changes from 1.3 -> 1.4
+# -- Now handles wrapped headers that have been unfolded.
+# (Though I do believe news software oughtn't be unfolding them.)
+# -- Checks to ensure that the temporary file is really a file, and
+# not a link or some other weirdness.
+
+# Path to the GnuPG gpgv binary, if you have GnuPG. If you do, this will
+# be used in preference to PGP. For most current control messages, you
+# need a version of GnuPG that can handle RSA signatures. If you have INN
+# and the script is able to successfully include your innshellvars.pl
+# file, the value of $inn::gpgv will override this.
+# $gpgv = '/usr/local/bin/gpgv';
+
+# Path to pgp binary; for PGP 5.0, set the path to the pgpv binary. If
+# you have INN and the script is able to successfully include your
+# innshellvars.pl file, the value of $inn::pgp will override this.
+$pgp = '/usr/local/bin/pgp';
+
+# If you keep your keyring somewhere that is not the default used by pgp,
+# uncomment the next line and set appropriately. If you have INN and the
+# script is able to successfully include your innshellvars.pl file, this
+# will be set to $inn::newsetc/pgp if that directory exists unless you set
+# it explicitly. GnuPG will use a file named pubring.gpg in this
+# directory.
+# $keyring = '/path/to/your/pgp/config';
+
+# If you have INN and the script is able to successfully include your
+# innshellvars.pl file, the value of $inn::pathtmp and $inn::locks will
+# override these.
+$tmpdir = "/tmp";
+$lockdir = $tmpdir;
+
+# How should syslog be accessed?
+#
+# As it turns out, syslogging is very hard to do portably in versions of
+# Perl prior to 5.6.0. Sys::Syslog should work without difficulty in
+# 5.6.0 or later and will be used automatically for those versions of Perl
+# (unless $syslog_method is ''). For earlier versions of Perl, 'inet' is
+# all that's available up to version 5.004_03. If your syslog does not
+# accept UDP log packets, such as when syslogd runs with the -l flag,
+# 'inet' will not work. A value of 'unix' will try to contact syslogd
+# directly over a Unix domain socket built entirely in Perl code (no
+# subprocesses). If that is not working for you, and you have the
+# 'logger' program on your system, set this variable to its full path name
+# to have a subprocess contact syslogd. If the method is just "logger",
+# the script will search some known directories for that program. If it
+# can't be found & used, everything falls back on stderr logging.
+#
+# You can test the script's syslogging by running "pgpverify <
+# /some/text/file" on a file that is not a valid news article. The
+# "non-header at line #" error should be syslogged.
+#
+# $syslog_method = 'unix'; # Unix doman socket, Perl 5.004_03 or higher.
+# $syslog_method = 'inet'; # UDP to port 514 of localhost.
+# $syslog_method = ''; # Don't ever try to do syslogging.
+$syslog_method = 'logger'; # Search for the logger program.
+
+# The next two variables are the values to be used for syslog's facility
+# and level to use, as would be found in syslog.conf. For various
+# reasons, it is impossible to economically have the script figure out how
+# to do syslogging correctly on the machine. If you have INN and the
+# script is able to successfully include you innshellvars.pl file, then
+# the value of $inn::syslog_facility will override this value of
+# $syslog_facility; $syslog_level is unaffected.
+$syslog_facility = 'news';
+$syslog_level = 'err';
+
+# Prepend the error message with a timestamp? This is only relevant if
+# not syslogging, when errors go to stderr.
+#
+# $log_date = 0; # Zero means don't do it.
+# $log_date = 1; # Non-zero means do it.
+$log_date = -t STDOUT; # Do it if STDOUT is to a terminal.
+
+# End of configuration section.
+
+
+require 5;
+
+use strict;
+use vars qw($gpgv $pgp $keyring $tmp $tmpdir $lockdir $syslog_method
+ $syslog_facility $syslog_level $log_date $test $messageid);
+
+use Fcntl qw(O_WRONLY O_CREAT O_EXCL);
+use FileHandle;
+use IPC::Open3 qw(open3);
+use POSIX qw(strftime);
+
+# Turn on test mode if the first argument is '-test'.
+if (@ARGV && $ARGV[0] eq '-test') {
+ shift @ARGV;
+ $test = 1;
+}
+
+# Not syslogged, such an error is almost certainly from someone running
+# the script manually.
+die "Usage: $0 < message\n" if @ARGV != 0;
+
+# Grab various defaults from innshellvars.pl if running inside INN.
+$pgp = $inn::pgp
+ if $inn::pgp && $inn::pgp ne "no-pgp-found-during-configure";
+$gpgv = $inn::gpgv if $inn::gpgv;
+$tmp = ($inn::pathtmp ? $inn::pathtmp : $tmpdir) . "/pgp$$";
+$lockdir = $inn::locks if $inn::locks;
+$syslog_facility = $inn::syslog_facility if $inn::syslog_facility;
+if (! $keyring && $inn::newsetc) {
+ $keyring = $inn::newsetc . '/pgp' if -d $inn::newsetc . '/pgp';
+}
+
+# Trim /path/to/prog to prog for error messages.
+$0 =~ s%^.*/%%;
+
+# Make sure that the signature verification program can be executed.
+if ($gpgv) {
+ if (! -x $gpgv) {
+ &fail("$0: $gpgv: " . (-e _ ? "cannot execute" : "no such file") . "\n");
+ }
+} elsif (! -x $pgp) {
+ &fail("$0: $pgp: " . (-e _ ? "cannot execute" : "no such file") . "\n");
+}
+
+# Parse the article headers and generate the PGP message.
+my ($nntp_format, $header, $dup) = &parse_header();
+exit 1 unless $$header{'X-PGP-Sig'};
+my ($message, $signature, $version)
+ = &generate_message($nntp_format, $header, $dup);
+if ($test) {
+ print "-----MESSAGE-----\n$message\n-----END MESSAGE-----\n\n";
+ print "-----SIGNATURE-----\n$signature\n-----SIGNATURE-----\n\n";
+}
+
+# The call to pgp needs to be locked because it tries to both read and
+# write a file named randseed.bin but doesn't do its own locking as it
+# should, and the consequences of a multiprocess conflict is failure to
+# verify.
+my $lock;
+unless ($gpgv) {
+ $lock = "$lockdir/LOCK.$0";
+ until (&shlock($lock) > 0) {
+ sleep(2);
+ }
+}
+
+# Verify the message.
+my ($ok, $signer) = pgp_verify($signature, $version, $message);
+unless ($gpgv) {
+ unlink ($lock) or &errmsg("$0: unlink $lock: $!\n");
+}
+print "$signer\n" if $signer;
+unless ($ok == 0) {
+ &errmsg("$0: verification failed\n");
+}
+exit $ok;
+
+
+# Parse the article headers and return a flag saying whether the message
+# is in NNTP format and then two references to hashes. The first hash
+# contains all the header/value pairs, and the second contains entries for
+# every header that's duplicated. This is, by design, case-sensitive with
+# regards to the headers it checks. It's also insistent about the
+# colon-space rule.
+sub parse_header {
+ my (%header, %dup, $label, $value, $nntp_format);
+ while (<>) {
+ # If the first header line ends with \r\n, this article is in the
+ # encoding it would be in during an NNTP session. Some article
+ # storage managers keep them this way for efficiency.
+ $nntp_format = /\r\n$/ if $. == 1;
+ s/\r?\n$//;
+
+ last if /^$/;
+ if (/^(\S+):[ \t](.+)/) {
+ ($label, $value) = ($1, $2);
+ $dup{$label} = 1 if $header{$label};
+ $header{$label} = $value;
+ } elsif (/^\s/) {
+ &fail("$0: non-header at line $.: $_\n") unless $label;
+ $header{$label} .= "\n$_";
+ } else {
+ &fail("$0: non-header at line $.: $_\n");
+ }
+ }
+ $messageid = $header{'Message-ID'};
+ return ($nntp_format, \%header, \%dup);
+}
+
+# Generate the PGP message to verify. Takes a flag indicating wire
+# format, the hash of headers and header duplicates returned by
+# parse_header and returns a list of three elements. The first is the
+# message to verify, the second is the signature, and the third is the
+# version number.
+sub generate_message {
+ my ($nntp_format, $header, $dup) = @_;
+
+ # The regexp below might be too strict about the structure of PGP
+ # signature lines.
+
+ # The $sep value means the separator between the radix64 signature lines
+ # can have any amount of spaces or tabs, but must have at least one
+ # space or tab; if there is a newline then the space or tab has to
+ # follow the newline. Any number of newlines can appear as long as each
+ # is followed by at least one space or tab. *phew*
+ my $sep = "[ \t]*(\n?[ \t]+)+";
+
+ # Match all of the characters in a radix64 string.
+ my $r64 = '[a-zA-Z0-9+/]';
+
+ local $_ = $$header{'X-PGP-Sig'};
+ &fail("$0: X-PGP-Sig not in expected format\n")
+ unless /^(\S+)$sep(\S+)(($sep$r64{64})+$sep$r64+=?=?$sep=$r64{4})$/;
+
+ my ($version, $signed_headers, $signature) = ($1, $3, $4);
+ $signature =~ s/$sep/\n/g;
+ $signature =~ s/^\s+//;
+
+ my $message = "X-Signed-Headers: $signed_headers\n";
+ my $label;
+ foreach $label (split(",", $signed_headers)) {
+ &fail("$0: duplicate signed $label header, can't verify\n")
+ if $$dup{$label};
+ $message .= "$label: ";
+ $message .= "$$header{$label}" if $$header{$label};
+ $message .= "\n";
+ }
+ $message .= "\n"; # end of headers
+
+ while (<>) { # read body lines
+ if ($nntp_format) {
+ # Check for end of article; some news servers (eg, Highwind's
+ # "Breeze") include the dot-CRLF of the NNTP protocol in the article
+ # data passed to this script.
+ last if $_ eq ".\r\n";
+
+ # Remove NNTP encoding.
+ s/^\.\./\./;
+ s/\r\n$/\n/;
+ }
+ $message .= $_;
+ }
+
+ # Strip off all trailing whitespaces for compatibility with the way that
+ # pgpverify used to work, using attached signatures.
+ $message =~ s/[ \t]+\n/\n/g;
+
+ return ($message, $signature, $version);
+}
+
+# Check a detached signature for given data. Takes a signature block (in
+# the form of an ASCII-armored string with embedded newlines), a version
+# number (which may be undef), and the message. We return an exit status
+# and the key id if the signature is verified. 0 means good signature, 1
+# means bad data, 2 means an unknown signer, and 3 means a bad signature.
+# In the event of an error, we report with errmsg.
+#
+# This code is taken almost verbatim from PGP::Sign except for the code to
+# figure out the PGP style.
+sub pgp_verify {
+ my ($signature, $version, $message) = @_;
+ chomp $signature;
+
+ # Ignore SIGPIPE, since we're going to be talking to PGP.
+ local $SIG{PIPE} = 'IGNORE';
+
+ # Set the PGP style based on whether $gpgv is set.
+ my $pgpstyle = ($gpgv ? 'GPG' : 'PGP2');
+
+ # Because this is a detached signature, we actually need to save both
+ # the signature and the data to files and then run PGP on the signature
+ # file to make it verify the signature. Because this is a detached
+ # signature, though, we don't have to do any data mangling, which makes
+ # our lives much easier. It would be nice to do this without having to
+ # use temporary files, but I don't see any way to do so without running
+ # into mangling problems.
+ #
+ # PGP v5 *requires* there be some subheader or another. *sigh*. So we
+ # supply one if Version isn't given. :)
+ my $umask = umask 077;
+ my $filename = $tmpdir . '/pgp' . time . '.' . $$;
+ my $sigfile = new FileHandle "$filename.asc", O_WRONLY|O_EXCL|O_CREAT;
+ unless ($sigfile) {
+ &errmsg ("Unable to open temp file $filename.asc: $!\n");
+ return (255, undef);
+ }
+ if ($pgpstyle eq 'PGP2') {
+ print $sigfile "-----BEGIN PGP MESSAGE-----\n";
+ } else {
+ print $sigfile "-----BEGIN PGP SIGNATURE-----\n";
+ }
+ if (defined $version) {
+ print $sigfile "Version: $version\n";
+ } elsif ($pgpstyle ne 'GPG') {
+ print $sigfile "Comment: Use GnuPG; it's better :)\n";
+ }
+ print $sigfile "\n", $signature;
+ if ($pgpstyle eq 'PGP2') {
+ print $sigfile "\n-----END PGP MESSAGE-----\n";
+ } else {
+ print $sigfile "\n-----END PGP SIGNATURE-----\n";
+ }
+ close $sigfile;
+
+ # Signature saved. Now save the actual message.
+ my $datafile = new FileHandle "$filename", O_WRONLY|O_EXCL|O_CREAT;
+ unless ($datafile) {
+ &errmsg ("Unable to open temp file $filename: $!\n");
+ unlink "$filename.asc";
+ return (255, undef);
+ }
+ print $datafile $message;
+ close $datafile;
+
+ # Figure out what command line we'll be using.
+ my @command;
+ if ($pgpstyle eq 'GPG') {
+ @command = ($gpgv, qw/--quiet --status-fd=1 --logger-fd=1/);
+ } else {
+ @command = ($pgp, '+batchmode', '+language=en');
+ }
+
+ # Now, call PGP to check the signature. Because we've written
+ # everything out to a file, this is actually fairly simple; all we need
+ # to do is grab stdout. PGP prints its banner information to stderr, so
+ # just ignore stderr. Set PGPPATH if desired.
+ #
+ # For GnuPG, use pubring.gpg if an explicit keyring was configured or
+ # found. Otherwise, use trustedkeys.gpg in the default keyring location
+ # if found and non-zero, or fall back on pubring.gpg. This is
+ # definitely not the logic that I would use if writing this from
+ # scratch, but it has the most backward compatibility.
+ local $ENV{PGPPATH} = $keyring if ($keyring && $pgpstyle ne 'GPG');
+ if ($pgpstyle eq 'GPG') {
+ if ($keyring) {
+ push (@command, "--keyring=$keyring/pubring.gpg");
+ } else {
+ my $home = $ENV{GNUPGHOME} || $ENV{HOME};
+ $home .= '/.gnupg' if $home;
+ if ($home && ! -s "$home/trustedkeys.gpg" && -f "$home/pubring.gpg") {
+ push (@command, "--keyring=pubring.gpg");
+ }
+ }
+ }
+ push (@command, "$filename.asc");
+ push (@command, $filename);
+ my $input = new FileHandle;
+ my $output = new FileHandle;
+ my $pid = eval { open3 ($input, $output, $output, @command) };
+ if ($@) {
+ &errmsg ($@);
+ &errmsg ("Execution of $command[0] failed.\n");
+ unlink ($filename, "$filename.asc");
+ return (255, undef);
+ }
+ close $input;
+
+ # Check for the message that gives us the key status and return the
+ # appropriate thing to our caller. This part is a zoo due to all of the
+ # different formats used. GPG has finally done the right thing and
+ # implemented a separate status stream with parseable data.
+ #
+ # MIT PGP 2.6.2 and PGP 6.5.2:
+ # Good signature from user "Russ Allbery <rra@stanford.edu>".
+ # ViaCrypt PGP 4.0:
+ # Good signature from user: Russ Allbery <rra@stanford.edu>
+ # PGP 5.0:
+ # Good signature made 1999-02-10 03:29 GMT by key:
+ # 1024 bits, Key ID 0AFC7476, Created 1999-02-10
+ # "Russ Allbery <rra@stanford.edu>"
+ #
+ # Also, PGP v2 prints out "Bad signature" while PGP v5 uses "BAD
+ # signature", and PGP v6 reverts back to "Bad signature".
+ local $_;
+ local $/ = '';
+ my $signer;
+ my $ok = 255;
+ while (<$output>) {
+ print if $test;
+ if ($pgpstyle eq 'GPG') {
+ if (/\[GNUPG:\]\s+GOODSIG\s+\S+\s+(\S+)/) {
+ $ok = 0;
+ $signer = $1;
+ } elsif (/\[GNUPG:\]\s+NODATA/ || /\[GNUPG:\]\s+UNEXPECTED/) {
+ $ok = 1;
+ } elsif (/\[GNUPG:\]\s+NO_PUBKEY/) {
+ $ok = 2;
+ } elsif (/\[GNUPG:\]\s+BADSIG\s+/) {
+ $ok = 3;
+ }
+ } else {
+ if (/^Good signature from user(?::\s+(.*)|\s+\"(.*)\"\.)$/m) {
+ $signer = $+;
+ $ok = 0;
+ last;
+ } elsif (/^Good signature made .* by key:\n.+\n\s+\"(.*)\"/m) {
+ $signer = $1;
+ $ok = 0;
+ last;
+ } elsif (/^\S+: Good signature from \"(.*)\"/m) {
+ $signer = $1;
+ $ok = 0;
+ last;
+ } elsif (/^(?:\S+: )?Bad signature /im) {
+ $ok = 3;
+ last;
+ }
+ }
+ }
+ close $input;
+ waitpid ($pid, 0);
+ unlink ($filename, "$filename.asc");
+ umask $umask;
+ return ($ok, $signer || '');
+}
+
+# Log an error message, attempting syslog first based on $syslog_method
+# and falling back on stderr.
+sub errmsg {
+ my ($message) = @_;
+ $message =~ s/\n$//;
+
+ my $date = '';
+ if ($log_date) {
+ $date = strftime ('%Y-%m-%d %T ', localtime);
+ }
+
+ if ($syslog_method && $] >= 5.006) {
+ eval "use Sys::Syslog";
+ $syslog_method = 'internal';
+ }
+
+ if ($syslog_method eq "logger") {
+ my @loggers = ('/usr/ucb/logger', '/usr/bin/logger',
+ '/usr/local/bin/logger');
+ my $try;
+ foreach $try (@loggers) {
+ if (-x $try) {
+ $syslog_method = $try;
+ last;
+ }
+ }
+ $syslog_method = '' if $syslog_method eq 'logger';
+ }
+
+ if ($syslog_method ne '' && $syslog_method !~ m%/logger$%) {
+ eval "use Sys::Syslog";
+ }
+
+ if ($@ || $syslog_method eq '') {
+ warn $date, "$0: trying to use Perl's syslog: $@\n" if $@;
+ warn $date, $message, "\n";
+ warn $date, "... while processing $messageid\n"
+ if $messageid;
+
+ } else {
+ $message .= " processing $messageid"
+ if $messageid;
+
+ if ($syslog_method =~ m%/logger$%) {
+ unless (system($syslog_method, "-i", "-p",
+ "$syslog_facility.$syslog_level", $message) == 0) {
+ if ($? >> 8) {
+ warn $date, "$0: $syslog_method exited status ", $? >> 8, "\n";
+ } else {
+ warn $date, "$0: $syslog_method died on signal ", $? & 255, "\n";
+ }
+ $syslog_method = '';
+ &errmsg($message);
+ }
+
+ } else {
+ # setlogsock arrived in Perl 5.004_03 to enable Sys::Syslog to use a
+ # Unix domain socket to talk to syslogd, which is the only way to do
+ # it when syslog runs with the -l switch.
+ if ($syslog_method eq "unix") {
+ if ($^O eq "dec_osf" && $] >= 5) {
+ eval 'sub Sys::Syslog::_PATH_LOG { "/dev/log" }';
+ }
+ if ($] <= 5.00403 || ! eval "setlogsock('unix')") {
+ warn $date, "$0: cannot use syslog_method 'unix' on this system\n";
+ $syslog_method = '';
+ &errmsg($message);
+ return;
+ }
+ }
+
+ # Unfortunately, there is no way to definitively know in this
+ # program if the message was logged. I wish there were a way to
+ # send a message to stderr if and only if the syslog attempt failed.
+ &openlog($0, 'pid', $syslog_facility);
+ &syslog($syslog_level, $_[0]);
+ &closelog();
+ }
+ }
+}
+
+sub fail {
+ &errmsg($_[0]);
+ exit 255;
+}
+
+# Get a lock in essentially the same fashion as INN's shlock. return 1 on
+# success, 0 for normal failure, -1 for abnormal failure. "normal
+# failure" is that a lock is apparently in use by someone else.
+sub shlock {
+ my ($file) = @_;
+ my ($ltmp, $pid);
+
+ unless (defined(&ENOENT)) {
+ eval "require POSIX qw(:errno_h)";
+ if ($@) {
+ # values taken from BSD/OS 3.1
+ sub ENOENT { 2 }
+ sub ESRCH { 3 }
+ sub EEXIST { 17 }
+ }
+ }
+
+ $ltmp = ($file =~ m%(.*/)%)[0] . "shlock$$";
+
+ # This should really attempt to use another temp name.
+ -e $ltmp && (unlink($ltmp) || return -1);
+
+ open(LTMP, ">$ltmp") || return -1;
+ print LTMP "$$\n" || (unlink($ltmp), return -1);
+ close(LTMP) || (unlink($ltmp), return -1);
+
+ if (!link($ltmp, $file)) {
+ if ($! == &EEXIST) {
+ if (open(LOCK, "<$file")) {
+ $pid = <LOCK>;
+ if ($pid =~ /^\d+$/ && (kill(0, $pid) == 1 || $! != &ESRCH)) {
+ unlink($ltmp);
+ return 0;
+ }
+
+ # OK, the pid in the lockfile is not a number or no longer exists.
+ close(LOCK); # silent failure is ok here
+
+ # Unlink failed.
+ if (unlink($file) != 1 && $! != &ENOENT) {
+ unlink($ltmp);
+ return 0;
+ }
+
+ # Check if open failed for reason other than file no longer present.
+ } elsif ($! != &ENOENT) {
+ unlink($ltmp);
+ return -1;
+ }
+
+ # Either this process unlinked the lockfile because it was bogus, or
+ # between this process's link() and open() the other process holding
+ # the lock unlinked it. This process can now try to acquire.
+ if (! link($ltmp, $file)) {
+ unlink($ltmp);
+ return $! == &EEXIST ? 0 : -1; # Maybe another proc grabbed the lock.
+ }
+
+ } else { # First attempt to link failed.
+ unlink($ltmp);
+ return 0;
+ }
+ }
+ unlink($ltmp);
+ return 1;
+}
+
+=head1 NAME
+
+pgpverify - Cryptographically verify Usenet control messages
+
+=head1 SYNOPSIS
+
+B<pgpverify> [B<-test>] < I<message>
+
+=head1 DESCRIPTION
+
+The B<pgpverify> program reads (on standard input) a Usenet control
+message that has been cryptographically signed using the B<signcontrol>
+program (or some other program that produces a compatible format).
+B<pgpverify> then uses a PGP implementation to determine who signed the
+control message. If the control message has a valid signature,
+B<pgpverify> prints (to stdout) the user ID of the key that signed the
+message. Otherwise, it exits with a non-zero exit status.
+
+If B<pgpverify> is installed as part of INN, it uses INN's configuration
+to determine what signature verification program to use, how to log
+errors, what temporary directory to use, and what keyring to use.
+Otherwise, all of those parameters can be set by editing the beginning of
+this script.
+
+By default, when running as part of INN, B<pgpverify> expects the PGP key
+ring to be found in I<pathetc>/pgp (as either F<pubring.pgp> or
+F<pubring.gpg> depending on whether PGP or GnuPG is used to verify
+signatures). If that directory doesn't exist, it will fall back on using
+the default key ring, which is in a F<.pgp> or F<.gnupg> subdirectory of
+the running user's home directory.
+
+INN, when using GnuPG, configures B<pgpverify> to use B<gpgv>, which by
+default expects keys to be in a keyring named F<trustedkeys.gpg>, since it
+doesn't implement trust checking directly. B<pgpverify> uses that file if
+present but falls back to F<pubring.gpg> if it's not found. This bypasses
+the trust model for checking keys, but is compatible with the way that
+B<pgpverify> used to behave. Of course, if a keyring is found in
+I<pathetc>/pgp or configured at the top of the script, that overrides all of
+this behavior.
+
+=head1 OPTIONS
+
+The B<-test> flag causes B<pgpverify> to print out the input that it is
+passing to PGP (which is a reconstructed version of the input that
+supposedly created the control message) as well as the output from PGP's
+analysis of the message.
+
+=head1 EXIT STATUS
+
+B<pgpverify> may exit with the following statuses:
+
+=over 4
+
+=item 0Z<>
+
+The control message had a good PGP signature.
+
+=item 1
+
+The control message had no PGP signature.
+
+=item 2
+
+The control message had an unknown PGP signature.
+
+=item 3
+
+The control message had a bad PGP signature.
+
+=item 255
+
+A problem occurred not directly related to PGP analysis of signature.
+
+=back
+
+=head1 ENVIRONMENT
+
+B<pgpverify> does not modify or otherwise alter the environment before
+invoking the B<pgp> or B<gpgv> program. It is the responsibility of the
+person who installs B<pgpverify> to ensure that when B<pgp> or B<gpgv>
+runs, it has the ability to locate and read a PGP key file that contains
+the PGP public keys for the appropriate Usenet hierarchy administrators.
+B<pgpverify> can be pointed to an appropriate key ring by editing
+variables at the beginning of this script.
+
+=head1 NOTES
+
+Historically, Usenet news server administrators have configured their news
+servers to automatically honor Usenet control messages based on the
+originator of the control messages and the hierarchies for which the
+control messages applied. For example, in the past, David Lawrence always
+issued control messages for the S<"Big 8"> hierarchies (comp, humanities,
+misc, news, rec, sci, soc, talk). Usenet news administrators would
+configure their news server software to automatically honor newgroup and
+rmgroup control messages that originated from David Lawrence and applied
+to any of the S<Big 8> hierarchies.
+
+Unfortunately, Usenet news articles (including control messages) are
+notoriously easy to forge. Soon, malicious users realized they could
+create or remove (at least temporarily) any S<Big 8> newsgroup they wanted by
+simply forging an appropriate control message in David Lawrence's name.
+As Usenet became more widely used, forgeries became more common.
+
+The B<pgpverify> program was designed to allow Usenet news administrators
+to configure their servers to cryptographically verify control messages
+before automatically acting on them. Under the B<pgpverify> system, a Usenet
+hierarchy maintainer creates a PGP public/private key pair and
+disseminates the public key. Whenever the hierarchy maintainer issues a
+control message, he uses the B<signcontrol> program to sign the control
+message with the PGP private key. Usenet news administrators configure
+their news servers to run the B<pgpverify> program on the appropriate
+control messages, and take action based on the PGP key User ID that signed
+the control message, not the name and address that appear in the control
+message's From: or Sender: headers.
+
+Thus, appropriate use of the B<signcontrol> and B<pgpverify> programs
+essentially eliminates the possibility of malicious users forging Usenet
+control messages that sites will act upon, as such users would have to
+obtain the PGP private key in order to forge a control message that would
+pass the cryptographic verification step. If the hierarchy administrators
+properly protect their PGP private keys, the only way a malicious user
+could forge a validly-signed control message would be by breaking the
+public key encryption algorithm, which (at least at this time) is believed
+to be prohibitively difficult for PGP keys of a sufficient bit length.
+
+=head1 HISTORY
+
+B<pgpverify> was written by David C Lawrence <tale@isc.org>. Manual page
+provided by James Ralston. It is currently maintained by Russ Allbery
+<rra@stanford.edu>.
+
+=head1 COPYRIGHT AND LICENSE
+
+David Lawrence wrote: "Our lawyer told me to include the following. The
+upshot of it is that you can use the software for free as much as you
+like."
+
+Copyright (c) 1996 UUNET Technologies, Inc.
+All rights reserved.
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are
+met:
+
+=over 4
+
+=item 1.
+
+Redistributions of source code must retain the above copyright notice,
+this list of conditions and the following disclaimer.
+
+=item 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.
+
+=item 3.
+
+All advertising materials mentioning features or use of this software must
+display the following acknowledgement:
+
+ This product includes software developed by UUNET Technologies, Inc.
+
+=item 4.
+
+The name of UUNET Technologies ("UUNET") may not be used to endorse or
+promote products derived from this software without specific prior written
+permission.
+
+=back
+
+THIS SOFTWARE IS PROVIDED BY UUNET "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 UUNET 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.
+
+=head1 SEE ALSO
+
+gpgv(1), pgp(1).
+
+L<ftp://ftp.isc.org/pub/pgpcontrol/> is where the most recent versions of
+B<signcontrol> and B<pgpverify> live, along with PGP public keys used for
+hierarchy administration.
+
+=cut
+
+# Local variables:
+# cperl-indent-level: 2
+# fill-column: 74
+# End:
--- /dev/null
+#! /usr/bin/perl -w
+# written April 1996, tale@isc.org (David C Lawrence)
+# Currently maintained by Russ Allbery <rra@stanford.edu>
+# Version 1.8, 2003-07-06
+#
+# Changes from 1.6 -> 1.8
+# -- Added support for GnuPG.
+# -- Replace signing code with code from PGP::Sign that generates detached
+# signatures instead. Otherwise, GnuPG signatures with DSA keys could
+# not be verified. Should still work the same as before with RSA keys.
+# -- Thanks to new signing code, no longer uses a temporary file.
+# -- Only lock when using PGP; GnuPG shouldn't need it.
+#
+# Changes from 1.5 -> 1.6
+# -- eliminated subprocess use (except pgp, of course).
+# -- interlock against competing signing processes.
+# -- allow optional headers; see $use_or_add.
+# -- added simple comments about why particular headers are signed.
+# -- made error messages a tad more helpful for situations when it is hard
+# to know what message was trying to be signed (such as via an "at"
+# job).
+# -- set $action, $group, $moderated to "" to prevent unusued variable
+# warnings in the event a Control header can't be parsed.
+# -- moved assignment of $pgpend out of loop.
+#
+# Changes from 1.4 -> 1.5
+# -- need to require Text::Tabs to get 'expand' for tabs in checkgroups.
+#
+# Changes from 1.3 -> 1.4
+# -- added checkgroups checking.
+# -- added group name in several error messages (for help w/batch
+# processing).
+# -- disabled moderator address checking.
+# -- adjusted newsgroups line (ie, tabbing fixed) now correctly
+# substituted into control message.
+#
+# Changes from 1.2.3 -> 1.3
+# -- skip minor pgp signature headers like "charset:" after "version:"
+# header and until the empty line that starts the base64 signature block.
+
+# CONFIGURATION
+
+# PGP variables.
+#
+# $pgp can be set to the path to GnuPG to use GnuPG instead. The program
+# name needs to end in gpg so that signcontrol knows GnuPG is being used.
+#
+# STORING YOUR PASS PHRASE IN A FILE IS A POTENTIAL SECURITY HOLE.
+# make sure you know what you're doing if you do it.
+# if you don't use pgppassfile, you can only use this script interactively.
+# if you DO use pgppassfile, it is possible that someone could steal
+# your passphrase either by gaining access to the file or by seeing
+# the environment of a running pgpverify program.
+#
+# $pgplock is used because pgp does not guard itself against concurrent
+# read/write access to its randseed.bin file. A writable file is needed;
+# The default value is to use the .pgp/config.txt file in the home
+# directory of the user running the program. Note that this will only
+# work to lock against other instances of signcontrol, not all pgp uses.
+# $pgplock is not used if $pgp ends in 'gpg' since GnuPG doesn't need
+# this.
+$pgpsigner = 'INSERT_YOUR_PGP_USERID';
+$pgppassfile = ''; # file with pass phrase for $pgpsigner
+$pgp = "/usr/local/bin/pgp";
+$pgpheader = "X-PGP-Sig";
+$pgplock = (getpwuid($<))[7] . '/.pgp/config.txt';
+
+# this program is strict about always wanting to be consistent about what
+# headers appear in the control messages. the defaults for the
+# @... arrays are reasonable, but you should edit the force values.
+
+# these headers are acceptable in input, but they will be overwritten with
+# these values. no sanity checking is done on what you put here. also,
+# Subject: is forced to be the Control header prepending by "cmsg". also,
+# Newsgroups: is forced to be just the group being added/removed.
+# (but is taken as-is for checkgroups)
+$force{'Path'} = 'bounce-back';
+$force{'From'} = 'YOUR_ADDRESS_AND_NAME';
+$force{'Approved'} = 'ADDRESS_FOR_Approved_HEADER';
+$force{'X-Info'}='ftp://ftp.isc.org/pub/pgpcontrol/README.html'
+ . "\n\t"
+ . 'ftp://ftp.isc.org/pub/pgpcontrol/README';
+
+# these headers are acceptable in input, or if not present then will be
+# created with the given value. None are enabled by default, because they
+# should not be necessary. Setting one to a null string will pass through
+# any instance of it found in the input, but not generate one if it is
+# missing. If you set any $default{} variables, you must also put it in
+# @orderheaders below.
+#
+# Note that Distribution nearly never works correctly, so use it only if
+# you are really sure the propagation of the article will be limited as
+# you intend. This normally means that you control all servers the
+# distribution will go to with an iron fist.
+#
+# $use_or_add{'Reply-To'} = 'YOUR_REPLY_ADDRESS';
+# $use_or_add{'Oranization'} = 'YOUR_ORGANIZATION';
+# $use_or_add{'Distribution'} = 'MESSAGE_DISTRIBUTION';
+
+# host for message-id; this could be determined automatically based on
+# where it is run, but consistency is the goal here
+$id_host = 'FULL_HOST_NAME';
+
+# headers to sign. Sender is included because non-PGP authentication uses
+# it. The following should always be signed:
+# Subject -- some older news systems use it to identify the control action.
+# Control -- most news systems use this to determine what to do.
+# Message-ID -- guards against replay attacks.
+# Date -- guards against replay attacks.
+# From -- used by news systems as part of authenticating the message.
+# Sender -- used by news systems as part of authenticating the message.
+@signheaders = ('Subject', 'Control', 'Message-ID', 'Date', 'From', 'Sender');
+
+# headers to remove from real headers of final message.
+# If it is a signed header, it is signed with an empty value.
+# set to () if you do not want any headers removed.
+@ignoreheaders = ('Sender');
+
+# headers that will appear in final message, and their order of
+# appearance. all _must_ be set, either in input or via the $force{} and
+# $use_or_add{} variables above.
+# (exceptions: Date, Lines, Message-ID are computed by this program)
+# if header is in use_or_add with a null value, it will not appear in output.
+# several are required by the news article format standard; if you remove
+# these, your article will not propagate:
+# Path, From, Newsgroups, Subject, Message-ID, Date
+# if you take out these, your control message is not very useful:
+# Control, Approved
+# any headers in @ignoreheaders also in @orderheaders are silently dropped.
+# any non-null header in the input but not in @orderheaders or @ignoreheaders
+# is an error.
+# null headers are silently dropped.
+@orderheaders =
+ ('Path', 'From', 'Newsgroups', 'Subject', 'Control', 'Approved',
+ 'Message-ID', 'Date', 'Lines', 'X-Info', $pgpheader);
+
+# this program tries to help you out by not letting you sign erroneous
+# names, especially ones that are so erroneous they run afoul of naming
+# standards.
+#
+# set to match only hierarchies you will use it on
+# include no '|' for a single hierarchy (eg, "$hierarchies = 'uk';").
+
+$hierarchies = 'HIERARCHIES';
+
+# the draft news article format standard says:
+# "subsequent components SHOULD begin with a letter"
+# where "SHOULD" means:
+# means that the item is a strong recommendation: there may be
+# valid reasons to ignore it in unusual circumstances, but
+# this should be done only after careful study of the full
+# implications and a firm conclusion that it is necessary,
+# because there are serious disadvantages to doing so.
+# as opposed to "MUST" which means:
+# means that the item is an absolute requirement of the specification
+# MUST is preferred, but might not be acceptable if you have legacy
+# newsgroups that have name components that begin with a letter, like
+# news.announce.newgroups does with comp.sys.3b1 and 17 other groups.
+
+$start_component_with_letter = 'MUST';
+
+## END CONFIGURATION
+
+use Fcntl qw(F_SETFD);
+use FileHandle;
+use IPC::Open3 qw(open3);
+use POSIX qw(setlocale strftime LC_TIME);
+use Text::Tabs; # to get 'expand' for tabs in checkgroups
+
+$0 =~ s#^.*/##;
+
+die "Usage: $0 < message\n" if @ARGV > 0;
+
+umask(0022); # flock needs a writable file, if we create it
+if ($pgp !~ /gpg$/) {
+ open(LOCK, ">>$pgplock") || die "$0: open $lock: $!, exiting\n";
+ flock(LOCK, 2); # block until locked
+}
+
+&setgrouppat;
+
+$die = '';
+
+&readhead;
+&readbody;
+
+if ($die) {
+ if ($group) {
+ die "$0: ERROR PROCESSING ${action}group $group:\n", $die;
+ } elsif ($action eq 'check') {
+ die "$0: ERROR PROCESSING checkgroups:\n", $die;
+ } elsif ($header{'Subject'}) {
+ die "$0: ERROR PROCESSING Subject: $header{'Subject'}\n", $die;
+ } else {
+ die $die;
+ }
+}
+
+&signit;
+
+if ($pgp !~ /gpg$/) {
+ close(LOCK) || warn "$0: close $lock: $!\n";
+}
+exit 0;
+
+sub
+setgrouppat
+
+{
+ my ($hierarchy, $plain_component, $no_component);
+ my ($must_start_letter, $should_start_letter);
+ my ($eval);
+
+ # newsgroup name checks based on RFC 1036bis (not including encodings) rules:
+ # "component MUST contain at least one letter"
+ # "[component] MUST not contain uppercase letters"
+ # "[component] MUST begin with a letter or digit"
+ # "[component] MUST not be longer than 14 characters"
+ # "sequences 'all' and 'ctl' MUST not be used as components"
+ # "first component MUST begin with a letter"
+ # and enforcing "subsequent components SHOULD begin with a letter" as MUST
+ # and enforcing at least a 2nd level group (can't use to newgroup "general")
+ #
+ # DO NOT COPY THIS PATTERN BLINDLY TO OTHER APPLICATIONS!
+ # It has special construction based on the pattern it is finally used in.
+
+ $plain_component = '[a-z][-+_a-z\d]{0,13}';
+ $no_component = '(.*\.)?(all|ctl)(\.|$)';
+ $must_start_letter = '(\.' . $plain_component . ')+';
+ $should_start_letter = '(\.(?=\d*[a-z])[a-z\d]+[-+_a-z\d]{0,13})+';
+
+ $grouppat = "(?!$no_component)($hierarchies)";
+ if ($start_component_with_letter eq 'SHOULD') {
+ $grouppat .= $should_start_letter;
+ } elsif ($start_component_with_letter eq 'MUST') {
+ $grouppat .= $must_start_letter;
+ } else {
+ die "$0: unknown value configured for \$start_component_with_letter\n";
+ }
+
+ foreach $hierarchy (split(/\|/, $hierarchies)) {
+ die "$0: hierarchy name $hierarchy not standards-compliant\n"
+ if $hierarchy !~ /^$plain_component$/o;
+ }
+
+ $eval = "\$_ = 'test'; /$grouppat/;";
+ eval $eval;
+ die "$0: bad regexp for matching group names:\n $@" if $@;
+}
+
+sub
+readhead
+
+{
+ my($head, $label, $value);
+ local($_, $/);
+
+ $/ = "";
+ $head = <STDIN>; # get the whole news header
+ $die .= "$0: continuation lines in headers not allowed\n"
+ if $head =~ s/\n[ \t]+/ /g; # rejoin continued lines
+
+ for (split(/\n/, $head)) {
+ if (/^(\S+): (.*)/) {
+ $label = $1;
+ $value = $2;
+
+ $die .= "$0: duplicate header $label\n" if $header{$label};
+
+ $header{$label} = $value;
+ $header{$label} =~ s/^\s+//;
+ $header{$label} =~ s/\s+$//;
+ } elsif (/^$/) {
+ ; # the empty line separator(s)
+ } else {
+ $die .= "$0: non-header line:\n $_\n";
+ }
+ }
+
+ $header{'Message-ID'} = '<' . time . ".$$\@$id_host>";
+
+ setlocale(LC_TIME, "C");
+ $header{'Date'} = strftime("%a, %d %h %Y %T -0000", gmtime);
+
+ for (@ignoreheaders) {
+ $die .= "ignored header $_ also has forced value set\n" if $force{$_};
+ $header{$_} = '';
+ }
+
+ for (@orderheaders) {
+ $header{$_} = $force{$_} if defined($force{$_});
+ next if /^(Lines|\Q$pgpheader\E)$/; # these are set later
+ unless ($header{$_}) {
+ if (defined($use_or_add{$_})) {
+ $header{$_} = $use_or_add{$_} if $use_or_add{$_} ne '';
+ } else {
+ $die .= "$0: missing $_ header\n";
+ }
+ }
+ }
+
+ $action = $group = $moderated = "";
+ if ($header{'Control'}) {
+ if ($header{'Control'} =~ /^(new)group (\S+)( moderated)?$/o ||
+ $header{'Control'} =~ /^(rm)group (\S+)()$/o ||
+ $header{'Control'} =~ /^(check)groups()()$/o) {
+ ($action, $group, $moderated) = ($1, $2, $3);
+ $die .= "$0: group name $group is not standards-compliant\n"
+ if $group !~ /^$grouppat$/ && $action eq 'new';
+ $die .= "$0: no group to rmgroup on Control: line\n"
+ if ! $group && $action eq 'rm';
+ $header{'Subject'} = "cmsg $header{'Control'}";
+ $header{'Newsgroups'} = $group unless $action eq 'check';
+ } else {
+ $die .= "$0: bad Control format: $header{'Control'}\n";
+ }
+ } else {
+ $die .= "$0: can't verify message content; missing Control header\n";
+ }
+}
+
+sub
+readbody
+
+{
+ local($_, $/);
+ local($status, $ngline, $fixline, $used, $desc, $mods);
+
+ undef $/;
+ $body = $_ = <STDIN>;
+ $header{'Lines'} = $body =~ tr/\n/\n/ if $body;
+
+ # the following tests are based on the structure of a
+ # news.announce.newgroups newgroup message; even if you comment out the
+ # "first line" test, please leave the newsgroups line and moderators
+ # checks
+ if ($action eq 'new') {
+ $status = $moderated ? 'a\smoderated' : 'an\sunmoderated';
+ $die .= "$0: nonstandard first line in body for $group\n"
+ if ! /^\Q$group\E\sis\s$status\snewsgroup\b/;
+
+ my $intro = "For your newsgroups file:\n";
+ $ngline =
+ (/^$intro\Q$group\E[ \t]+(.+)\n(\n|\Z(?!\n))/mi)[0];
+ if ($ngline) {
+ $_ = $group;
+ $desc = $1;
+ $fixline = $_;
+ $fixline .= "\t" x ((length) > 23 ? 1 : (4 - ((length) + 1) / 8));
+ $used = (length) < 24 ? 24 : (length) + (8 - (length) % 8);
+ $used--;
+ $desc =~ s/ \(Moderated\)//i;
+ $desc =~ s/\s+$//;
+ $desc =~ s/\w$/$&./;
+ $die .= "$0: $group description too long\n" if $used + length($desc) > 80;
+ $fixline .= $desc;
+ $fixline .= ' (Moderated)' if $moderated;
+ $body =~ s/^$intro(.+)/$intro$fixline/mi;
+ } else {
+ $die .= "$0: $group newsgroup line not formatted correctly\n";
+ }
+ # moderator checks are disabled; some sites were trying to
+ # automatically maintain aliases based on this, which is bad policy.
+ if (0 && $moderated) {
+ $die .= "$0: $group submission address not formatted correctly\n"
+ if $body !~ /\nGroup submission address: ?\S+@\S+\.\S+\n/m;
+ $mods = "( |\n[ \t]+)\\([^)]+\\)\n\n";
+ $die .= "$0: $group contact address not formatted correctly\n"
+ if $body !~ /\nModerator contact address: ?\S+@\S+\.\S+$mods/m;
+ }
+ }
+ # rmgroups have freeform bodies
+
+ # checkgroups have structured bodies
+ if ($action eq 'check') {
+ for (split(/\n/, $body)) {
+ my ($group, $description) = /^(\S+)\t+(.+)/;
+ $die .= "$0: no group:\n $_\n" unless $group;
+ $die .= "$0: no description:\n $_\n" unless $description;
+ $die .= "$0: bad group name \"$group\"\n" if $group !~ /^$grouppat$/;
+ $die .= "$0: tab in description\n" if $description =~ /\t/;
+ s/ \(Moderated\)$//;
+ $die .= "$0: $group line too long\n" if length(expand($_)) > 80;
+ }
+ }
+}
+
+# Create a detached signature for the given data. The first argument
+# should be a key id, the second argument the PGP passphrase (which may be
+# null, in which case PGP will prompt for it), and the third argument
+# should be the complete message to sign.
+#
+# In a scalar context, the signature is returned as an ASCII-armored block
+# with embedded newlines. In array context, a list consisting of the
+# signature and the PGP version number is returned. Returns undef in the
+# event of an error, and the error text is then stored in @ERROR.
+#
+# This function is taken almost verbatim from PGP::Sign except the PGP
+# style is determined from the name of the program used.
+sub pgp_sign {
+ my ($keyid, $passphrase, $message) = @_;
+
+ # Ignore SIGPIPE, since we're going to be talking to PGP.
+ local $SIG{PIPE} = 'IGNORE';
+
+ # Determine the PGP style.
+ my $pgpstyle = 'PGP2';
+ if ($pgp =~ /pgps$/) { $pgpstyle = 'PGP5' }
+ elsif ($pgp =~ /gpg$/) { $pgpstyle = 'GPG' }
+
+ # Figure out what command line we'll be using. PGP v6 and PGP v2 use
+ # compatible syntaxes for what we're trying to do. PGP v5 would have,
+ # except that the -s option isn't valid when you call pgps. *sigh*
+ my @command;
+ if ($pgpstyle eq 'PGP5') {
+ @command = ($pgp, qw/-baft -u/, $keyid);
+ } elsif ($pgpstyle eq 'GPG') {
+ @command = ($pgp, qw/--detach-sign --armor --textmode -u/, $keyid,
+ qw/--force-v3-sigs --pgp2/);
+ } else {
+ @command = ($pgp, qw/-sbaft -u/, $keyid);
+ }
+
+ # We need to send the password to PGP, but we don't want to use either
+ # the command line or an environment variable, since both may expose us
+ # to snoopers on the system. So we create a pipe, stick the password in
+ # it, and then pass the file descriptor to PGP. PGP wants to know about
+ # this in an environment variable; GPG uses a command-line flag.
+ # 5.005_03 started setting close-on-exec on file handles > $^F, so we
+ # need to clear that here (but ignore errors on platforms where fcntl or
+ # F_SETFD doesn't exist, if any).
+ #
+ # Make sure that the file handles are created outside of the if
+ # statement, since otherwise they leave scope at the end of the if
+ # statement and are automatically closed by Perl.
+ my $passfh = new FileHandle;
+ my $writefh = new FileHandle;
+ local $ENV{PGPPASSFD};
+ if ($passphrase) {
+ pipe ($passfh, $writefh);
+ eval { fcntl ($passfh, F_SETFD, 0) };
+ print $writefh $passphrase;
+ close $writefh;
+ if ($pgpstyle eq 'GPG') {
+ push (@command, '--batch', '--passphrase-fd', $passfh->fileno);
+ } else {
+ push (@command, '+batchmode');
+ $ENV{PGPPASSFD} = $passfh->fileno;
+ }
+ }
+
+ # Fork off a pgp process that we're going to be feeding data to, and tell
+ # it to just generate a signature using the given key id and pass phrase.
+ my $pgp = new FileHandle;
+ my $signature = new FileHandle;
+ my $errors = new FileHandle;
+ my $pid = eval { open3 ($pgp, $signature, $errors, @command) };
+ if ($@) {
+ @ERROR = ($@, "Execution of $command[0] failed.\n");
+ return undef;
+ }
+
+ # Write the message to the PGP process. Strip all trailing whitespace
+ # for compatibility with older pgpverify and attached signature
+ # verification.
+ $message =~ s/[ \t]+\n/\n/g;
+ print $pgp $message;
+
+ # All done. Close the pipe to PGP, clean up, and see if we succeeded.
+ # If not, save the error output and return undef.
+ close $pgp;
+ local $/ = "\n";
+ my @errors = <$errors>;
+ my @signature = <$signature>;
+ close $signature;
+ close $errors;
+ close $passfh if $passphrase;
+ waitpid ($pid, 0);
+ if ($? != 0) {
+ @ERROR = (@errors, "$command[0] returned exit status $?\n");
+ return undef;
+ }
+
+ # Now, clean up the returned signature and return it, along with the
+ # version number if desired. PGP v2 calls this a PGP MESSAGE, whereas
+ # PGP v5 and v6 and GPG both (more correctly) call it a PGP SIGNATURE,
+ # so accept either.
+ while ((shift @signature) !~ /-----BEGIN PGP \S+-----\n/) {
+ unless (@signature) {
+ @ERROR = ("No signature from PGP (command not found?)\n");
+ return undef;
+ }
+ }
+ my $version;
+ while ($signature[0] ne "\n" && @signature) {
+ $version = $1 if ((shift @signature) =~ /^Version:\s+(.*?)\s*$/);
+ }
+ shift @signature;
+ pop @signature;
+ $signature = join ('', @signature);
+ chomp $signature;
+ undef @ERROR;
+ return wantarray ? ($signature, $version) : $signature;
+}
+
+sub
+signit
+
+{
+ my($head, $header, $signheaders, $pgpflags, $pgpbegin, $pgpend);
+
+ # Form the message to be signed.
+ $signheaders = join(",", @signheaders);
+ $head = "X-Signed-Headers: $signheaders\n";
+ foreach $header (@signheaders) {
+ $head .= "$header: $header{$header}\n";
+ }
+ my $message = "$head\n$body";
+
+ # Get the passphrase if available.
+ my $passphrase;
+ if ($pgppassfile && -f $pgppassfile) {
+ $pgppassfile =~ s%^(\s)%./$1%;
+ if (open (PGPPASS, "< $pgppassfile\0")) {
+ $passphrase = <PGPPASS>;
+ close PGPPASS;
+ chomp $passphrase;
+ }
+ }
+
+ # Sign the message, getting the signature and PGP version number.
+ my ($signature, $version) = pgp_sign ($pgpsigner, $passphrase, $message);
+ unless ($signature) {
+ die "@ERROR\n$0: could not generate signature\n";
+ }
+
+ # GnuPG has version numbers containing spaces, which breaks our header
+ # format. Find just some portion that contains a digit.
+ ($version) = ($version =~ /(\S*\d\S*)/);
+
+ # Put the signature into the headers.
+ $signature =~ s/^/\t/mg;
+ $header{$pgpheader} = "$version $signheaders\n$signature";
+
+ for (@ignoreheaders) {
+ delete $header{$_} if defined $header{$_};
+ }
+
+ $head = '';
+ foreach $header (@orderheaders) {
+ $head .= "$header: $header{$header}\n" if $header{$header};
+ delete $header{$header};
+ }
+
+ foreach $header (keys %header) {
+ die "$0: unexpected header $header left in header array\n";
+ }
+
+ print STDOUT $head;
+ print STDOUT "\n";
+ print STDOUT $body;
+}
+
+# Our lawyer told me to include the following. The upshot of it is that
+# you can use the software for free as much as you like.
+
+# Copyright (c) 1996 UUNET Technologies, Inc.
+# 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 UUNET Technologies, Inc.
+# 4. The name of UUNET Technologies ("UUNET") may not be used to endorse or
+# promote products derived from this software without specific prior
+# written permission.
+#
+# THIS SOFTWARE IS PROVIDED BY UUNET ``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 UUNET 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.
+
+# Local variables:
+# cperl-indent-level: 2
+# fill-column: 74
+# End:
--- /dev/null
+inn2 (2.4.5-5) unstable; urgency=medium
+
+ * Added patches u_*: bug fixes from SVN chosen by the upstream maintainer:
+ - misc innreport bugs
+ - incorrect TLS error handling
+ - correctly initialize the status file IP address variables
+ - do not send a duplicate reply when TLS negotiation fails
+ - correct the permissions checking for XHDR and XPAT
+ - do not send a duplicate reply to XOVER/XHDR/XPAT in a empty group
+ * Install again our own sasl.conf with the correct paths.
+ * Document in README.Debian that STARTTLS and MODE READER do not work
+ together. (Closes: #503495)
+ * Added patch typo_inn_conf_man fixes a typo in inn.conf(5).
+ (Closes: #507256)
+ * Updated the md5.c license in debian/copyright.
+
+ -- Marco d'Itri <md@linux.it> Mon, 15 Dec 2008 00:50:17 +0100
+
+inn2 (2.4.5-4) unstable; urgency=low
+
+ * Backported fixes from SVN: honour the Ad newsfeeds flag and create a
+ valid SV for the article body which will correctly match regexps.
+
+ -- Marco d'Itri <md@linux.it> Wed, 10 Sep 2008 01:36:04 +0200
+
+inn2 (2.4.5-3) unstable; urgency=medium
+
+ * Do not FTBFS with old versions of find. (Closes: #495508)
+
+ -- Marco d'Itri <md@linux.it> Thu, 28 Aug 2008 04:21:48 +0200
+
+inn2 (2.4.5-2) unstable; urgency=medium
+
+ * Rebuilt with libdb4.6-dev.
+
+ -- Marco d'Itri <md@linux.it> Sun, 27 Jul 2008 19:23:55 +0200
+
+inn2 (2.4.5-1) unstable; urgency=low
+
+ * New upstream STABLE release.
+
+ -- Marco d'Itri <md@linux.it> Tue, 01 Jul 2008 01:18:29 +0200
+
+inn2 (2.4.4r-1) unstable; urgency=low
+
+ * New upstream STABLE release. (For real, this time.)
+ * On 32 bit architectures, build a new inn2-lfs package with Larges
+ Files Support enabled. (Closes: #433751)
+ * Enabled support for Kerberos. (Closes: #478775)
+ * Rebuilt with perl 5.10. (Closes: #479244)
+ * Removed usage of debconf.
+
+ -- Marco d'Itri <md@linux.it> Sun, 11 May 2008 12:31:56 +0200
+
+inn2 (2.4.4-1) unstable; urgency=low
+
+ * New upstream STABLE snapshot.
+ + Rotates innfeed.log. (Closes: #407752)
+ + Make inews not fail if MODE READER fails because the connection has
+ not been authenticated yet. (Closes: #475059)
+ * Removed S from Default-Stop in the init script. (Closes: #471081)
+ * Updated debconf translation: pt. (Closes: #444720)
+ * Fixed a typo in the name of debian/inn2.logcheck.violations.ignore.
+ * Stop overwriting active.times(5) with a symlink, inn now has it.
+ * Fixed many minor issues pointed out by Julien Élie. (Closes: #455882)
+ * Removed patches merged upstream: daemonize-ovdb_init,
+ fix-crash-on-reload, hashfeeds.
+ * Remove /var/lib/news/ on purge. (Closes: #455104)
+
+ -- Marco d'Itri <md@linux.it> Mon, 14 Apr 2008 22:01:48 +0200
+
+inn2 (2.4.3+20070806-1) unstable; urgency=low
+
+ * New upstream STABLE snapshot.
+ * Package converted to quilt.
+ * Added patch fix-crash-on-reload to fix segfaults when reloading
+ incoming.conf (Closes: #361073, #429802, #430190, #430191)
+ * Added patch daemonize-ovdb_init to make ovdb_init properly close
+ stdin/out/err when it becomes a daemon. (Closes: #433522)
+ * Added patch inndstart-sockets-v6only to suppress a startup warning
+ about an already opened socket.
+ * Fixed the bzip2 path in bunbatch. (Closes: #419429)
+ * Removed patches merged upstream: ckpasswd_use_libdb, fix_radius.conf,
+ innfeed-fix-getaddrinfo, innfeed-force-ipv4, libdb-4.4.
+ * Use --as-needed to not link superfluous libraries.
+ * New debconf translations: pt, nl. (Closes: #414921, #415511)
+ * Added a logcheck file. (Closes: #405536)
+
+ -- Marco d'Itri <md@linux.it> Tue, 07 Aug 2007 16:35:06 +0200
+
+inn2 (2.4.3-1) unstable; urgency=low
+
+ * New upstream release. (Closes: #381415)
+ + Fixes nnrpd when "localmaxartsize: 0". (Closes: #357370)
+ * Removed support for his64v6 and cnfs64, which do not work anyway.
+ ****** I am looking for a co-maintainer interested in adding ******
+ ****** support to build a inn2-lfs package. ******
+ * Switched to libdb4.4.
+ * New debconf translations: vi, cs, sv. (Closes: #314245, #315211, #339811)
+ * Pre-Depends on debconf-2.0 too. (Closes: #331859)
+ * Added to innfeed support for a "force-ipv4" configuration option.
+ Based on a patch contributed by Henning Makholm. (Closes: #336264)
+ * Added to innfeed support for hashed feeds.
+ * pgpverify: try harder to find the home directory. (Closes: #307765)
+ * Moved nnrpd-ssl to the main package.
+ * Added support for libdb to ckpasswd. (Closes: #380644)
+ * Use FHS paths in the perl-nocem documentation. (Closes: #365639)
+ * Create /var/run/news in the init script if it does not exist.
+
+ -- Marco d'Itri <md@linux.it> Fri, 18 Aug 2006 11:19:21 +0200
+
+inn2 (2.4.2-3) unstable; urgency=high
+
+ * Fixed upgrades on systems with a non-default pathdb. (Closes: #306765)
+ * Added the showtoken program. (Closes: #306837)
+
+ -- Marco d'Itri <md@linux.it> Sat, 14 May 2005 15:03:56 +0200
+
+inn2 (2.4.2-2) unstable; urgency=medium
+
+ * New upstream snapshot (20050407).
+ * Stop providing the inn package. (Closes: #288659)
+ * Made postinst continue when makehistory or makedbz fail. (Closes: #292167)
+ * Switched to libdb4.3.
+
+ -- Marco d'Itri <md@linux.it> Fri, 8 Apr 2005 14:51:22 +0200
+
+inn2 (2.4.2-1) unstable; urgency=low
+
+ * New upstream release.
+ + Removed patch innreport_nnrpd-ssl.
+ + Fixed news2mail, CNFS buffers reporting. (Closes: #282664, #276819)
+
+ -- Marco d'Itri <md@linux.it> Fri, 24 Dec 2004 17:05:33 +0100
+
+inn2 (2.4.1+20040820-2) unstable; urgency=medium
+
+ * New upstream snapshot (upstream/patches/20040820-to-20040929.diff).
+ + make Norbert Tretkowski happy. (Closes: #255324)
+ + fix inn2-ssl segfaults on ia64. (Closes: #270875)
+ * Conflict with inn and cnews instead of news-transport-system.
+ (Closes: #269874)
+
+ -- Marco d'Itri <md@linux.it> Wed, 29 Sep 2004 17:24:18 +0200
+
+inn2 (2.4.1+20040820-1) unstable; urgency=medium
+
+ * New upstream snapshot.
+ + Fixes headers folding in the overview. (Closes: #190207)
+ + Fixes headers for articles mailed to moderators. (Closes: #249151)
+ * Added a default CA file name to sasl.conf. (Closes: #250201)
+ * New patch innreport_nnrpd-ssl: makes innreport correctly parse the
+ nnrpd-ssl log entries. (Closes: #250252)
+ * New debconf translations: de, ja. (Closes: #263030, #251100)
+
+ -- Marco d'Itri <md@linux.it> Fri, 20 Aug 2004 19:32:20 +0200
+
+inn2 (2.4.1+20040403-1) unstable; urgency=medium
+
+ * New upstream snapshot. (Closes: #141750)
+ * Switched to db4.2. (Closes: #241584)
+ * Added catalan debconf template. (Closes: #236668)
+ * Removed the patches fix_bindaddress, default-storage.diff and
+ fix_reiserfs26.diff because they have been merged upstream.
+ * Removed the patch libdb41-fix.diff because it's not needed anymore.
+
+ -- Marco d'Itri <md@linux.it> Sat, 3 Apr 2004 21:00:31 +0200
+
+inn2 (2.4.1-2) unstable; urgency=medium
+
+ * Fix bindaddress. (Closes: #183812)
+ * Fix paths in inn2-ssl. (Closes: #229181)
+
+ -- Marco d'Itri <md@linux.it> Sat, 24 Jan 2004 17:13:43 +0100
+
+inn2 (2.4.1-1) unstable; urgency=high
+
+ * New upstream release.
+ + Fixes buffer overflow, maybe remotely exploitable. (Closes: #226772)
+ * Add workaround for 2.6.x reiserfs brokeness. (Closes: #225940)
+ * Use pgpverify from -CURRENT to add useless DSA support. (Closes: #222634)
+ * Source package converted to DBS.
+
+ -- Marco d'Itri <md@linux.it> Thu, 8 Jan 2004 20:30:49 +0100
+
+inn2 (2.4.0+20031130-1) unstable; urgency=low
+
+ * New upstream STABLE snapshot. (Closes: #213946)
+ * Added russian and spanish debconf messages. (Closes: #219235, #220884)
+ * Replaces: inn2-dev to improve upgrades from woody. (Closes: #217219)
+ * Added a new his64v6 history method with LFS support. Untested!
+ (Closes: #215877)
+
+ -- Marco d'Itri <md@linux.it> Sun, 30 Nov 2003 22:54:02 +0100
+
+inn2 (2.4.0+20030912-1) unstable; urgency=low
+
+ * New upstream STABLE snapshot.
+ * Add again a default storage method to storage.conf. (Closes: #205001)
+ * Fix the getlist command line in actsyncd. (Closes: #206283)
+ * Added a new cnfs64 method for large cycbufs. The on disk format is not
+ compatible with 32-bit cycbufs. The storage tokens are not compatible
+ with the tokens of a standard inn package built with --enable-largefiles
+ (but they could be converted, let me know if you want to try this).
+ This is basically untested and may trash the data you feed it. Please
+ let me know if this works for you or not. (Closes: #206828)
+
+ -- Marco d'Itri <md@linux.it> Fri, 12 Sep 2003 14:07:06 +0200
+
+inn2 (2.4.0+20030808-1) unstable; urgency=medium
+
+ * New upstream snapshot.
+ * Fix readers.conf(5) and ckpasswd(8). (Closes: #202098, #202300)
+ * Fix innupgrade invocation in postinst. (Closes: #202978)
+ * Misc debconf-related fixes courtesy of Christian Perrier
+ <bubulle@debian.org>. (Closes: #200517, #200518)
+ * Added polish, spanish and french debconf messages.
+ (Closes: #202155, #201627)
+
+ -- Marco d'Itri <md@linux.it> Fri, 8 Aug 2003 13:56:23 +0200
+
+inn2 (2.4.0-3) unstable; urgency=medium
+
+ * Add db_stop to postinst.
+ * Fixed inn.conf path in postinst. (Closes: #198578)
+
+ -- Marco d'Itri <md@linux.it> Wed, 25 Jun 2003 15:15:54 +0200
+
+inn2 (2.4.0-2) unstable; urgency=medium
+
+ * Install all headers in /usr/include/inn. (Closes: #198463, #198464)
+ * Added debconf support, patch by <arturcz@hell.pl>.
+
+ -- Marco d'Itri <md@linux.it> Mon, 23 Jun 2003 19:19:37 +0200
+
+inn2 (2.4.0-1) unstable; urgency=medium
+
+ * New upstream release. (Closes: #182751, #188740, #193967, #194273, #198395)
+ * send-uucp.pl is now send-uucp.
+ * Switched from db4.0 to db4.1.
+ * postinst should not fail if innd cannot start. (Closes: #189966)
+ * Depend on perlapi-5.8.0. (Closes: #187717, #192411)
+ * Depend on inn2-inews >= 2.3.999+20030227-1. (Closes: #196137)
+ * Do not scare admins with wrong postinsg messages. (Closes: #183103)
+ * Corrected typo in innupgrade. (Closes: #194444)
+ * Added fr.* to /etc/news/moderators. (Closes: #190202)
+
+ -- Marco d'Itri <md@linux.it> Fri, 20 Jun 2003 18:39:21 +0200
+
+inn2 (2.3.999+20030227-1) unstable; urgency=low
+
+ * New upstream snapshot:
+ * Fix expireover segfaults. (Closes: #180462, #179898)
+ * Create /var/log/news/path. (Closes: #180168, #180602)
+ * Build-Depends on libssl-dev. (Closes: #180662)
+ * Fix missing feed name in the log. (Closes: #178842, #181740)
+ * Fix news2mail. (Closes: #181086)
+ * Fix minor bugs in the init script. (Closes: #180866, #180867)
+
+ -- Marco d'Itri <md@linux.it> Thu, 27 Feb 2003 19:11:57 +0100
+
+inn2 (2.3.999+20030205-2) unstable; urgency=low
+
+ * New upstream snapshot. (Closes: #179294)
+ * Add a new inn2-ssl package. (Closes: #163672)
+ * Move wildmat(3) from inn2-dev to inn2. (Closes: #179441)
+ * Downgraded to extra priority.
+ Most people do not need a local news server, and definitely not INN 2.x.
+ * Create /var/{lib,run}/news in postinst.
+
+ -- Marco d'Itri <md@linux.it> Thu, 6 Feb 2003 15:18:02 +0100
+
+inn2 (2.3.999+20030125-3) unstable; urgency=low
+
+ * Fix rnews breakage. (Closes: #178673)
+ * Remove hardcoded paths of egrep, awk, sed, sort, wget. (Closes: #176749)
+
+ -- Marco d'Itri <md@linux.it> Tue, 28 Jan 2003 01:48:03 +0100
+
+inn2 (2.3.999+20030125-2) unstable; urgency=low
+
+ * Fix broken ctlinnd. (Closes: #178588)
+
+ -- Marco d'Itri <md@linux.it> Mon, 27 Jan 2003 19:41:03 +0100
+
+inn2 (2.3.999+20030125-1) unstable; urgency=low
+
+ * BEWARE: this is a -CURRENT snapshot. If it breaks you keep both pieces!
+ (Closes: #172212, #174938, #176336).
+ * Make innreport generate valid HTML. (Closes: #166372)
+ * Pre-Depends on inn2-inews. (Closes: #166804)
+ * Update gnu.* data in control.ctl. (Closes: #167581)
+ * Do not ship rnews suid root! (Closes: #171757)
+ * Install /usr/share/doc/inn2/INSTALL.gz (Closes: #174493)
+
+ -- Marco d'Itri <md@linux.it> Thu, 16 Jan 2003 01:12:53 +0100
+
+inn2 (2.3.3+20020922-5) unstable; urgency=medium
+
+ * Fixed pathtmp (Closes: #162686).
+ * Check if the usenet user exists before adding a mail alias
+ (Closes: #162731).
+ * Fixed a path in sendinpaths (Closes: #163022).
+
+ -- Marco d'Itri <md@linux.it> Mon, 7 Oct 2002 20:24:16 +0200
+
+inn2 (2.3.3+20020922-4) unstable; urgency=low
+
+ * Applied OVDB fixes, courtesy of Ian Hastie @clara.net (Closes: #162643).
+
+ -- Marco d'Itri <md@linux.it> Sat, 28 Sep 2002 16:46:57 +0200
+
+inn2 (2.3.3+20020922-3) unstable; urgency=low
+
+ * Fixed absolute path in Makefile (Closes: #162538).
+
+ -- Marco d'Itri <md@linux.it> Fri, 27 Sep 2002 19:12:10 +0200
+
+inn2 (2.3.3+20020922-1) unstable; urgency=low
+
+ * New STABLE CVS snapshot (Closes: #128725, #137175, #157808, #159105).
+ * Made some changes to make INN compile with perl 5.8.0. May be broken.
+ * Fix inndf to convert the "infinite" inodes of reiserfs to 2^31 - 1
+ (Closes: #124101).
+ * Suggests: gnupg instead of pgp.
+ * Brand new init script which uses ctlinnd.
+ * Removed debian changes to use mkstemp. INN uses a private temp
+ directory anyway.
+ * Conflicts+Replaces: ninpaths, added the scripts from the inpaths package.
+ * Do not depend anymore on libdb3-util, which is only needed by OVDB.
+ * Removed signcontrol.
+ * Changed control.* and junk groups to status n.
+ * Added gpgverify script (Closes: #131412).
+ * Added bunbatch script (Closes: #136860).
+ * Added /usr/share/doc/inn2/INSTALL.gz (Closes: #156685).
+ * Added buildinnkeyring script which downloads PGP keys from ftp.isc.org
+ (Closes: #86989).
+
+ -- Marco d'Itri <md@linux.it> Sun, 22 Sep 2002 21:05:18 +0200
--- /dev/null
+inn2 (2.3.999+20030114-1) unstable; urgency=low
+
+ * BEWARE: this is a -CURRENT snapshot. If it breaks you keep both pieces!
+ (Closes: #172212, #174938).
+ * Make innreport generate valid HTML. (Closes: #166372)
+ * Pre-Depends on inn2-inews. (Closes: #166804)
+ * Update gnu.* data in control.ctl. (Closes: #167581)
+ * Do not ship rnews suid root! (Closes: #171757)
+ * Install /usr/share/doc/inn2/INSTALL.gz (Closes: #174493)
+
+ -- Marco d'Itri <md@linux.it> Thu, 16 Jan 2003 01:12:53 +0100
+
+inn2 (2.3.3+20020922-5) unstable; urgency=medium
+
+ * Fixed pathtmp (Closes: #162686).
+ * Check if the usenet user exists before adding a mail alias
+ (Closes: #162731).
+ * Fixed a path in sendinpaths (Closes: #163022).
+
+ -- Marco d'Itri <md@linux.it> Mon, 7 Oct 2002 20:24:16 +0200
+
+inn2 (2.3.3+20020922-4) unstable; urgency=low
+
+ * Applied OVDB fixes, courtesy of Ian Hastie @clara.net (Closes: #162643).
+
+ -- Marco d'Itri <md@linux.it> Sat, 28 Sep 2002 16:46:57 +0200
+
+inn2 (2.3.3+20020922-3) unstable; urgency=low
+
+ * Fixed absolute path in Makefile (Closes: #162538).
+
+ -- Marco d'Itri <md@linux.it> Fri, 27 Sep 2002 19:12:10 +0200
+
+inn2 (2.3.3+20020922-1) unstable; urgency=low
+
+ * New STABLE CVS snapshot (Closes: #128725, #137175, #157808, #159105).
+ * Made some changes to make INN compile with perl 5.8.0. May be broken.
+ * Fix inndf to convert the "infinite" inodes of reiserfs to 2^31 - 1
+ (Closes: #124101).
+ * Suggests: gnupg instead of pgp.
+ * Brand new init script which uses ctlinnd.
+ * Removed debian changes to use mkstemp. INN uses a private temp
+ directory anyway.
+ * Conflicts+Replaces: ninpaths, added the scripts from the inpaths package.
+ * Do not depend anymore on libdb3-util, which is only needed by OVDB.
+ * Removed signcontrol.
+ * Changed control.* and junk groups to status n.
+ * Added gpgverify script (Closes: #131412).
+ * Added bunbatch script (Closes: #136860).
+ * Added /usr/share/doc/inn2/INSTALL.gz (Closes: #156685).
+ * Added buildinnkeyring script which downloads PGP keys from ftp.isc.org
+ (Closes: #86989).
+
+ -- Marco d'Itri <md@linux.it> Sun, 22 Sep 2002 21:05:18 +0200
+
+inn2 (2.3.3-1) unstable; urgency=low
+
+ * new upstream version
+ * use 'unset' not 'declare -x' GZIP to clear environment in innshellvars,
+ closes: #136156, #136495, #136557, #142464
+ * add a warning to inn.conf comments about avoiding tabs after values,
+ closes: #112657, #112665
+ * modify cron.d to test for presence of programs before running them,
+ closes: #136563
+ * modify init.d to redirect rc.news output to /var/log/news/rc.news so that
+ inn2 daemonizes properly when run manually with the positive side effect
+ that the startup messages now comply with Debian policy,
+ closes: #140794, #116716, #134459
+ * deliver more upstream doc files, closes: #141963
+ * procps is priority required, but not marked essential, so we need to
+ depend on it so innwatch can use 'uptime', closes: #146135
+ * add a clause to postinst to make sure /var/spool/news has appropriate
+ owner/group/perms
+
+ -- Bdale Garbee <bdale@gag.com> Fri, 24 May 2002 00:49:08 -0600
+
+inn2 (2.3.2-3) unstable; urgency=low
+
+ * apply patch from rene@seindal.dk to pullnews.in to keep a missing group
+ from killing a run, closes: #133571
+ * apply patch from falcon@wysocki.lodz.pdi.net to dbprocs.in so that ovdb
+ will work correctly with libdb3, and add a runtime dependency on
+ libdb3-util to the inn2 package, closes: #128855
+ * add manpage symlinks, closes: #99543, #99578
+ * ensure backoff directory exists during postinst, closes: #127050
+ * clean up some of the lintian warnings
+
+ -- Bdale Garbee <bdale@gag.com> Sat, 16 Feb 2002 15:06:20 -0700
+
+inn2 (2.3.2-2) unstable; urgency=low
+
+ * edit provided buffindexed.conf to reflect our path structure
+ * apply patch to mailpost.in provided by Paul Seelig to prevent message
+ posting failures by stripping Received lines, closes: #120267
+ * add remaining /etc files to conffiles, closes: #110647
+ * make sure /var/log/news/OLD is news.news in postinst, closes: #116715
+ * slightly tighten permissions on /var/run/news, closes: #117773
+ * fix missing quotes around command in init.d,closes: #120105
+ * explicitly unexport GZIP in innshellvars before defining it to avoid
+ clashes with GZIP set in external environment, closes: #120381
+ * eliminate the task-news-server binary package
+
+ -- Bdale Garbee <bdale@gag.com> Wed, 26 Dec 2001 16:02:44 -0700
+
+inn2 (2.3.2-1) unstable; urgency=low
+
+ * new upstream version, closes: #98247, #101601
+ * remove authprogs/pwcheck.c and modify authprogs/Makefile in orig.tar.gz
+ since we don't use it and license is non-DFSG-compliant! Closes: #103477
+ * make inn2-inews conflict with cnews, closes: #97662
+ * modify news.daily to use tempfile(1) for safe tempfile creation,
+ closes: #104517
+ * improve several instances of unsafe temp file handling using relevant
+ patches from the Debian security team, closes: #83734
+ * patch to fix expireover seg faults from Andrew Stribblehill, closes: #95096
+ * make 'server' in inn.conf be 'localhost' by default, closes: #90908
+ * add a note in the sample newsfeeds file indicating that nntplink is not
+ part of INN, closes: #88120
+ * depend on awk, closes: #87618 Note, I will *not* change the compress
+ definition in innshellvars, see README.Debian for details.
+ * only execute rnews in cron if it exists, so that removing but not purging
+ inn2 doesn't generate excessive email, closes: #89853
+ * postinst forces owner of default log files to be correct, closes: #98490
+ * enable support for ovdb, closes: #96612
+ * remove references to non-existent newslog(8) man page, clean up wildmat(5)
+ references, closes: #90993
+ * apply patches from Tollef Fog Heen for gnupg use in signcontrol and use
+ safer temp file handling, closes: #99021, #99242
+ * the inn2 package really can't do anything about the way conffiles are
+ handled by dpkg when moving from inn to inn2. inn and inn2 are distinct
+ packages to Debian, despite their similar heritage, closes: #97443
+
+ -- Bdale Garbee <bdale@gag.com> Fri, 10 Aug 2001 13:48:38 -0600
+
+inn2 (2.3.1-4) unstable; urgency=low
+
+ * make /etc/news/filter/* conffiles, closes: #85315
+ * update build-depends, changing perl5 to libperl-dev
+
+ -- Bdale Garbee <bdale@gag.com> Tue, 20 Feb 2001 15:30:56 -0700
+
+inn2 (2.3.1-3) unstable; urgency=low
+
+ * conflict with current and prior versions of suck, since they use innxmit
+ in a way that no longer works, resulting in data loss as per bug 83727.
+ Reassign that bug to suck for implementation of a real solution.
+ * update the init.d script to use rc.news as upstream intends for start and
+ stop operations, since it handles the current set of INN daemons better
+ than our previous attempt to use start-stop-daemon does, closes: #84438
+ * tag /etc/news/send-uucp.cf as a conffile, closes: #83282
+
+ -- Bdale Garbee <bdale@gag.com> Mon, 5 Feb 2001 16:04:12 -0700
+
+inn2 (2.3.1-2) unstable; urgency=low
+
+ * update send-uucp.pl's idea of where innshellvars.pl is, closes: #83194
+ * go back to symlinks instead of moving inews and rnews, closes: #83224
+ * have preinst clean up /usr/lib/news/bin/filter dregs, closes: #83515
+ * have inn2-inews conflict/replace inn2 prior to 2.3.1 to account for moving
+ some files, closes: #83622
+
+ -- Bdale Garbee <bdale@gag.com> Tue, 30 Jan 2001 17:32:17 -0700
+
+inn2 (2.3.1-1) unstable; urgency=low
+
+ * new upstream release. thank you Marco d'Itri <md@Linux.IT> for help with
+ this update
+ * revert send-uucp to the upstream version, deliver Perl version as
+ send-uucp.pl, closes: #81074
+ * add manual page for send-uucp.pl written by Mark Brown, closes: #81073
+ * in 2.3.0-1, we accidentally shipped active, active.times, and newsgroups
+ as real files in /var/lib/news. Hack the preinst and postinst to protect
+ the files in place, and fix the mess. While we're at it, fix a few other
+ details in the postinst. Closes: #81274
+ * update preinst warning and README.Debian to add an explicit pointer to the
+ NEWS file, which documents the 2.2 to 2.3 changes. Fix a few out of date
+ items in README.Debian. Closes: #81069
+ * fix path of required file in send-uucp.pl, closes: #81075
+ * move rnews and dependencies to the inn2-inews package, closes: #81268
+ * freshen PGPKEYS file from ftp.isc.org, fixes fr.* key and adds a new one,
+ closes: #81272
+ * pgpverify: use /etc/news/pgp as pgp/gnupg config dir and the syslog
+ socket instead of logger. (Md)
+ * innd/cc.c: added perl filter status patch (used by cleanfeed). (Md)
+ * debian/cron.d: added entry to reload incoming.conf and sample entries
+ for send-nntp and send-uucp.pl. (Md) closes: #81269
+ * debian/init.d: first try to gracefully shut down innd with ctlinnd. (Md)
+ * Changed /usr/lib/news/bin/filter to /etc/news/filter. (Md) closes: #81273
+ * Moved back the whole /etc/news/scripts to the standard location in
+ /usr/lib/news, none of these files is a conffile. (Md) This improves the
+ postinst questioning considerably, closes: #81072
+ * Fixed permissions of many binaries and config files. (Md) closes: #82002
+ * inews is not installed suid (suggested by upstream maintainer). (Md)
+ * Renamed send-uucp.pl.1 to send-uucp.pl.8. (Md)
+ * lose the "Recommends: trn | news-reader" on the inn2 package, it's not
+ particularly useful, and gets in the way for dedicated servers
+ * drop the "Suggests: inn2-dev" from inn2, the few who need it will find it,
+ and it confuses new users
+ * minor patch for actsyncd, closes: #80973
+ * inews now supports a -p option to set the port, closes: #22242, #68875
+ * touch the /var/lib/news/.news.daily file in the postinst to squelch the
+ email about it being missing before nightly cron runs begin. closes: #76195
+ * suidregister is obsolete. newer dpkg's include 'dpkg-statoverride', which
+ is a superior solution requiring nothing from the package. closes: #81310
+ * configure storage.conf for tradspool configuration by default
+ * modify configure/configure.in slightly so build hostname isn't embedded
+ in inn.conf, et al
+ * don't force alt.test to exist, not all servers want it
+ * add code to the preinst to clean up the scripts tagged as conffiles that
+ will still be around from 2.3.0-1. Sigh.
+
+ -- Bdale Garbee <bdale@gag.com> Mon, 22 Jan 2001 17:23:45 -0700
+
+inn2 (2.3.0-1) unstable; urgency=low
+
+ * new upstream version, closes: #69623
+ * sendbatch appears to be fixed now, closes: #69561
+ * innreport now appears to use png if gif isn't available, closes: #76169
+ * thanks to John Goerzen for help cleaning up this release
+ * hack around need to have pgp installed at build time, closes: #69745
+ * add sanity checks for syslog files in the postinst, closes: #74707
+ * move all the scripts in /usr/lib/news that must be conffiles into /etc,
+ backfilling symlinks. Closes: #57150
+ * built against perl-5.6, closes: #80703
+ * can't duplicate removal problem, closes: #77419
+ * update pgpverify's default notion of where to find pgp, closes: #78989
+ * ship the Perl send-uucp from Miquel van Smoorenburg, closes: #77836
+ * give inews more reasonable owner/group/perms, closes: #70856
+ * add another warning to the preinst since some file format changes defy
+ reasonable automation across the upgrade from pre-2.3.0 to 2.3.0, and some
+ manual actions will likely be required.
+ * as of 2.3.0, innshellvars now codes 'compress' as the path for the compress
+ program instead of an ugly token reporting that compress wasn't found if
+ there is no compress available at build time. This will work if the
+ non-free 'ncompress' package is installed. Since some news sites still
+ don't use gzip for uucp batches, this is probably the right default. Note
+ added to the README.Debian file.
+ Closes: #77030
+
+ -- Bdale Garbee <bdale@gag.com> Thu, 28 Dec 2000 16:17:47 -0700
+
+inn2 (2.2.3-3) unstable; urgency=low
+
+ * leave the real inews executable in /usr/lib/news/bin, and symlink to it
+ from /usr/bin instead of moving it, to reduce breakage. Closes: #68999
+ * do the same thing with rnews, for good measure
+
+ -- Bdale Garbee <bdale@gag.com> Mon, 14 Aug 2000 02:58:39 -0600
+
+inn2 (2.2.3-2) unstable; urgency=medium
+
+ * patch from upstream that fixes remote denial of service, closes: #66638
+ * provide /usr/sbin/ctlinnd as a symlink so ctlinnd is in root's path,
+ closes: #67730
+ * update the README.Debian file to explain the situation with 'compress'
+ and indicate willingness to receive patch suggestions for making inn2
+ work better with uucp article transport, closes: #64284, #67629
+
+ -- Bdale Garbee <bdale@gag.com> Sun, 13 Aug 2000 22:39:21 -0600
+
+inn2 (2.2.3-1) unstable; urgency=low
+
+ * new upstream release, closes: #67635, #65492, #59345, #64405
+ * ensure control.cancel exists since we like usecontrolchan=true,
+ closes: #57555
+ * add some verbage to the README.Debian about anacron, closes: #59664
+ * some of the code proposed by Raphael Bossek for the init.d is only
+ relevant for a 1.X to 2.X upgrade, and the rest could take quite a
+ while during boot. I therefore don't think this belongs in init.d.
+ I'm adding the interesting checks to the postinst, closes: #62045
+ * provide PGPKEYS and some text about it in the README.Debian file,
+ closes: #66756
+
+ -- Bdale Garbee <bdale@gag.com> Tue, 25 Jul 2000 01:23:14 -0600
+
+inn2 (2.2.2.2000.01.31-4) frozen unstable; urgency=low
+
+ * add code to the postinst that calls 'hostname --fqdn' to make sure we can
+ determine the FQDN before we try to start the daemon. Not doing this
+ caused installs to fail on poorly-configured systems. Closes: #64681
+ * target frozen since this was tagged important, and could indeed cause an
+ install or upgrade to fail in some (relatively rare?) cases.
+
+ -- Bdale Garbee <bdale@gag.com> Fri, 26 May 2000 21:32:01 -0600
+
+inn2 (2.2.2.2000.01.31-3) frozen unstable; urgency=low
+
+ * target frozen since these are release critical
+ * fix a variety of permission problems including /var/lib/news,
+ closes: #61077
+ * permit world execute of /usr/bin/rnews, closes: #61409
+
+ -- Bdale Garbee <bdale@gag.com> Thu, 6 Apr 2000 23:17:56 -0600
+
+inn2 (2.2.2.2000.01.31-2) frozen unstable; urgency=low
+
+ * target frozen since one of these is release critical
+ * fix owner, group, and permissions of /var/run/news on fresh installs,
+ closes: #61030
+ * minor tweak to default inn.conf so build host isn't the value of pathhost
+ on new installs, closes: #60779
+ * fix owner, group, and permissions of /usr/bin/rnews so that it actually
+ works, closes: #58964
+
+ -- Bdale Garbee <bdale@gag.com> Fri, 24 Mar 2000 01:01:57 -0700
+
+inn2 (2.2.2.2000.01.31-1) frozen unstable; urgency=low
+
+ * target frozen since some of the bug fixes here qualify as release critical
+ * roll to current stable CVS snapshot to acquire bug fixes (some significant)
+ since 2.2.2 release, closes: #55581
+ * tag many scripts in /usr/lib/news/ as conffiles, so changes aren't lost on
+ upgrades. This makes particularly good sense given the apparent upstream
+ attitude that whacking scripts to configure a system is reasonable. Add
+ lintian overrides since it calls conffiles under /usr errors.
+ Closes: #55723, #56385
+ * have inn2 "provide inn" so that other packages that depend on inn don't
+ get frustrated with us, closes: #56040
+ * add -L to innflags and turn controlchan on in default inn.conf (to match
+ what we're shipping in default newsfeeds file), closes: #56383, #56384
+ * don't remove /usr/lib/news explicitly in postrm, since other packages need
+ it, closes: #55467
+
+ -- Bdale Garbee <bdale@gag.com> Mon, 31 Jan 2000 23:48:44 -0700
+
+inn2 (2.2.2-4) frozen unstable; urgency=low
+
+ * change package names from inn to inn2 as part of Debian INN peace project,
+ which will reinstate 1.7.2 as 'inn'. Target frozen so we ship both 1.7.2
+ and 2.2.2 with potato!
+ * add suitable conflicts with inn 1.X packages, leaving check for old
+ versions in preinst along since it does no harm
+ * add task-news-server to help new installs target inn2 by default
+
+ -- Bdale Garbee <bdale@gag.com> Wed, 19 Jan 2000 11:07:16 -0700
+
+inn (2.2.2-3) frozen unstable; urgency=low
+
+ * target frozen since this fixes multiple release-critical bugs
+ * inewsinn needs to provide inews, closes: #55349
+ * fix rnews path in all innshellvars flavors, closes: #55307
+ * since rnews uses nnrpdpostport, inews should also, closes: #54975
+ * allow inews to work when talking to servers that require authentication
+ for the "mode reader" command, closes: #31145
+ * add some more information to README.Debian, and include an explicit
+ pointer to it from the upgrade check in the preinst
+ * remove needless leftover example maintainer scripts in debian/Examples
+
+ -- Bdale Garbee <bdale@gag.com> Sun, 16 Jan 2000 14:43:12 -0700
+
+inn (2.2.2-2) unstable; urgency=low
+
+ * move more config files and related man pages from package inn to inewsinn
+ so that inews works correctly, closes: #55159
+ * flag /etc/cron.d/inn as a conffile
+ * reviewing / closing bugs in inn reported against prior versions that are
+ fixed or no longer relevant in 2.2.2 ...
+ * innwatch startup is cleaner than it used to be, closes: #21586, #32416
+ * logging id different than it used to be, closes: #24504
+ * expire.ctl doesn't have sequence problem any more, closes: #37737
+ * Old hosts.nntp and hosts.nntp.nolimit are merged, closes: #48739
+ * nntpsend.ctl no longer specifies the path, closes: #49673
+ * startup works fine now, closes: #51944
+ * control.ctl template is new, and correct, closes: #54526
+ * crosspost and overview directories are correct, closes: #55062
+ * history corruption problem should be long since fixed, closes: #11614
+ * client timeout is set to 10 minutes in /etc/news/inn.conf file by default,
+ which seems pretty reasonable, and is easy to change. Closes: #12358
+ * the ancient problem with ctlinnd rmgroup appears fixed, closes: #12559
+ * the current GetFQDN code appears to be coded to work in more cases than
+ it once was, closes: #29695
+ * we use a cron.d script now, so send-uucp, et al, can be scheduled on any
+ desired interval. Closes: #43016
+
+ -- Bdale Garbee <bdale@gag.com> Sat, 15 Jan 2000 01:18:17 -0700
+
+inn (2.2.2-1) unstable; urgency=low
+
+ * New upstream release. Enough has changed since the 1.7.2 release that
+ this is repackaged entirely from scratch.
+ Closes: #25936, #26255, #43546, #52672, #43896, #54609, #54759
+ * patch lib/parsedate.y to include "y2k fix" relating to acceptance of
+ articles with year of 1900. Closes: #53813
+ * postinst no longer prompts on upgrades, closes: #26659, #44918, #37888
+ * much newer innfeed, now integrated with inn sources, closes: #14326
+ * install docs revised, formatted version provided, closes: #43898
+ * large warning in preinst about upgrades from prior revisions of Debian
+ INN package requiring manual intervention. The degree of assistance
+ will improve in future uploads, but may never be fully automatic.
+
+ -- Bdale Garbee <bdale@gag.com> Wed, 22 Dec 1999 02:22:33 -0700
+
+inn (1.7.2-12) unstable; urgency=low
+
+ * update to reflect current policy
+ * inndstart *is* provided setuid root, closes: #51944
+ * fix path in nntpsend.ctl.5, closes: #49673
+ * if we're upgrading, don't stop to ask user, just use existing config
+ information, closes: #44918
+ * deliver Install.txt instead of Install.ms into the doc directory,
+ closes: #43898
+
+ -- Bdale Garbee <bdale@gag.com> Sun, 5 Dec 1999 20:46:07 -0700
+
+inn (1.7.2-11) unstable; urgency=high
+
+ * patch to inews.c to fix buffer overrun problem from Martin Schulze
+
+ -- Bdale Garbee <bdale@gag.com> Mon, 6 Sep 1999 13:35:19 -0600
+
+inn (1.7.2-10) unstable; urgency=low
+
+ * rebuild to depend on perl 5.005, closes 41469, 41925, 41943.
+ * update postinst text to eliminate version bogosity, closes 41585.
+ * fix sample sendbatch, closes 41596
+ * fix source-archive clause in sample newsfeeds file, closes 37862.
+ * document nntpport, closes 28588.
+ * fix type of inet_addr to reflect current libc.
+
+ -- Bdale Garbee <bdale@gag.com> Mon, 2 Aug 1999 01:22:23 -0600
+
+inn (1.7.2-9) unstable; urgency=low
+
+ * fold in Roman Hodek's changes from his 6.1 NMU, closing 38621. This
+ fixes an ugly i386 dependency in the way inn calls Perl.
+ * update perl dependency managment to try and cope with new perl policy
+
+ -- Bdale Garbee <bdale@gag.com> Sat, 17 Jul 1999 17:13:05 -0600
+
+inn (1.7.2-6) unstable; urgency=low
+
+ * new maintainer
+ * clean up a few lintian complaints
+ * folding in changes from Christian Kurz that he called -5. We'll call this
+ -6 even though his changes were not widely distributed, just to avoid any
+ confusion:
+
+ Removed X-Server-Date-Patch as it's not needed.
+ default moderation address add to /etc/news/moderators (closes: #24549)
+ Inn now depends on perl (closes: #27754, #32313)
+ Added gunbatch for gzipped batches (closes: #29899)
+ Changed debian/rules so inncheck runs without errors.
+ Added Perl-Support to Inn (closes: #26254)
+ Changed the examples
+
+ -- Bdale Garbee <bdale@gag.com> Wed, 26 May 1999 15:18:53 -0600
+
+inn (1.7.2-4) frozen unstable; urgency=medium
+
+ * Fixes:
+ #21583: inn: inn must replace inewsinn
+ #20763: inn sends me `not running' and `now running' each night
+ #21342: inn: install probs
+ #21582: inn: incorrect prerm fail-upgrade action
+ #21584: inn: postinst doesn't know abort-upgrade
+ #20048: inn: poison and REMEMBER_TRASH patch
+ #21644: inn: a way to not receive certain groups
+ * Wrt bug #20763: the ctlinnd timeout in innwatch has been increased
+ to 300 seconds (5 minutes). Hopefully that is enough.. There is no
+ good alternative, the fact that INN is slow while renumbering is
+ a basic design flaw. (Though the abp-scheduler patch might help)
+
+ -- Miquel van Smoorenburg <miquels@cistron.nl> Fri, 22 May 1998 19:52:55 +0200
+
+inn (1.7.2-3) frozen unstable; urgency=medium
+
+ * Move moderators from inewsinn to inn. The server should keep the
+ moderators data, not inews.
+ * Fix lib/clientactive.c (still yucky, but should work..)
+ * Include latest pgpverify script, 1.9, and manpage
+ * Fix security hole (/tmp/pgp$$) in pgpverify script
+ * Fixes:
+ #18579: I can't uninstall INN package
+ #19776: inn.prerm buggy typos bah!
+ #18724: inn: /etc/init.d/inn contains sed that never terminates
+ #19206: inn: Crontab modifications not preserved
+ #20423: inn: error in removing
+ #20653: inn: Bug in send-uucp.pl, patch included
+ #20792: INN: Wrong sfnet maintainer
+ #20436: inn: on line 16 of the prerm script there is "fi" instead of "esac"
+
+ -- Miquel van Smoorenburg <miquels@cistron.nl> Wed, 15 Apr 1998 17:34:23 +0200
+
+inn (1.7.2-2) unstable; urgency=low
+
+ * Change over to new crontab -l method
+ * Fix (pre|post)(inst|rm) scripts in several ways
+ * Fix inewsinn inn.conf installation
+ * Set NNRP_DBZINCORE_DELAY to -1
+ * Fix lintian warnings
+ Fixes:
+ #18120: inn: Inn's crontab file should be a conffile
+
+ -- Miquel van Smoorenburg <miquels@cistron.nl> Thu, 19 Feb 1998 22:46:25 +0100
+
+inn (1.7.2-1) unstable; urgency=low
+
+ * New upstream version
+ * Fix crontab -l | tail +4
+ * Fixes bugs:
+ #15889: /etc/news/inn.conf missing
+ #16128: manpage uncompressed
+ #15103: egrep incorrectly searched in /bin by innshellvars*
+ #14404: /usr/doc/$(PACKAGE)/copyright should not be compressed
+
+ -- Miquel van Smoorenburg <miquels@cistron.nl> Thu, 5 Feb 1998 12:52:14 +0100
+
+inn (1.7-1) unstable; urgency=low
+
+ * New upstream version
+ * Fixed bugs:
+ #9264: Unresolved dependency report for inn
+ #9315: inn: /etc/news/innshellvars* add /usr/ucb to the PATH
+ #9832: INN 1.5.1-1 throttled rmgroup really shreds active file ?
+ #10196: inn: inews complains about missnig subject header when there is one
+ #10505: Moderated postings fail
+ #11042: error in /usr/doc/inn/inn-README
+ #11057: inn: Confusing/dangerous instructions
+ #11453: inn: max signature length
+ #11691: libc6
+ #11851: inn: Documentation for send-uucp.pl
+ #11852: inn: nntpsend looks for wrong config file
+ #11900: INN creates `local.*' by default
+ #11948: inn: nntpsend does not works
+ #12513: inewsinn should insert a linebreak
+ #13161: inn-makehistory - Bus error
+ #13425: inn: egrep moved to /bin
+ #13488: inewsinn: directs user to docs in a package it doesn't require
+ #13616: /etc/init.d/inn, /etc/news/hosts.nntp.nolimit are not conffiles
+ #13781: Can't feed by send-uucp.pl with ihave/sendme.
+ #13831: inn: scanlogs depends on hard coded path for egrep
+ #13899: inn: inn uses /usr/bin/egrep, grep doesn't provide that any longer
+ * Added BUFFSET fix
+
+ -- Miquel van Smoorenburg <miquels@cistron.nl> Wed, 22 Oct 1997 14:08:37 +0200
+
+inn (1.5.1-5) unstable; urgency=high
+
+ * Fixed sendbatch script (comment in between backtics is illegal)
+ * libc6 version
+
+ -- Miquel van Smoorenburg <miquels@cistron.nl> Wed, 10 Sep 1997 16:31:37 +0200
+
+inn (1.5.1-4) stable unstable; urgency=high
+
+ * Add new security patch (with fixed STRCPY): inn-1.5.1-bufferoverflow.patch4
+ * Applied null-pointer.patch from Michael Shields
+ * Upped SIG_MAXLINES in configdata.h to 8
+ * Fix inn-README (perl example). Fixes bug #11042
+ * Update inn-README and postinst to fix bug #11057
+ * Make ctlinnd addhist work in paused mode, and fail in throttled mode
+ * Change ID string
+
+ -- Miquel van Smoorenburg <miquels@cistron.nl> Thu, 21 Aug 1997 12:37:48 +0200
+
+inn (1.5.1-3) stable unstable; urgency=high
+
+ * Add changelogs to docdir
+ * innshellvars*: change /usr/ucb -> /usr/sbin (Bug#9315)
+ * Changed Recommends: pgp to Suggests: (Bug#9264)
+ * Fix inews to fallback on local moderators file (Bug#10505)
+ * Fix buffer overruns all over the place
+
+ -- Miquel van Smoorenburg <miquels@cistron.nl> Thu, 24 Jul 1997 18:29:33 +0200
+
+inn (1.5.1-2) frozen unstable; urgency=high
+
+ * Added security-patch.05 (mailx tilde exploit)
+ * inewsinn no longer conflicts: inn so installation should no
+ longer remove your original inn-1.4 package (and configuration).
+ Expect some dpkg trouble when upgrading from 1.4unoff4 to 1.5.1-2 though.
+ * Always create .incoming/.outgoing symlinks for backwards compat.
+ * Do not change ownerships/modes of existing directories
+ * Fix ownerships/modes of rnews, innd, inndstart, in.nnrpd
+ * Fix /etc/init.d/inn to comply with console messages standard
+ * Fix /usr/lib/news/bin/sendbatch
+ * Fix scanlogs not to nuke active file if log/news/OLD isn't there
+ * Console messages are a bit more standard now
+ * Use start-stop-daemon to kill innwatch in /etc/init.d/inn
+ * Fixed up inncheck - it almost doesn't complain anymore
+
+ -- Miquel van Smoorenburg <miquels@cistron.nl> Mon, 28 Apr 1997 13:58:16 +0200
+
+inn (1.5.1-1) unstable; urgency=low
+
+ * Upgraded to 1.5.1
+ * Fixed Bug#6387: expire-with-symlinks problem
+ * Fixed Bug#6246: inewsinn reconfigures on update
+ * Moved /var/spool/news,/var/lib/news back into package
+ * Saves removed conffiles in preinst, restores in postinst
+ * Set LIKE_PULLERS to DO
+ * Remove manpage stubs that are now real manpages
+ * Fix options to sendmail in _PATH_SENDMAIL
+ * Removed subdirectories from debian/
+ * create /var/log/news/OLD in postinst
+ * Fixed most if not all other outstanding bugs
+
+ -- Miquel van Smoorenburg <miquels@cistron.nl> Wed, 5 Feb 1997 10:58:16 +0100
+
+inn (1.5-1) unstable; urgency=low
+
+ * Upgraded to 1.5
+ * Undid most patches to 1.4unoff4 because they are in 1.5 proper.
+ * Added security patch
+ * Added X-Server-Date: patch
+ * inn now depends on inewsinn
+ * Fixed all other outstanding bugs (well, almost).
+
+ -- Miquel van Smoorenburg <miquels@cistron.nl> Tue, 17 Dec 1996 16:56:37 +0100
+
+inn (1.4unoff4-2) unstable; urgency=low
+
+ * Added inn-dev package for libinn.a and manpages.
+ * Increased hash table size in expire.c to 2048 (was 128)
+ * Moved ctlinnd to /usr/sbin
+ * Moved to new source packaging scheme
+
+ -- Miquel van Smoorenburg <miquels@cistron.nl> Wed, 06 Oct 1996 15:38:30 +0200
+
+INN (1.4unoff4-1) - Miquel van Smoorenburg <miquels@cistron.nl>
+
+ * Took out the Linux 1.2 patches I put in unoff3.
+ * added the 64 bits patch (for Linux/Alpha)
+ * There are some other minor patches for Linux/Alpha
+ * Added "xmode" as alias for "mode"
+ * Using MMAP and setsockopt() now - NEEDS 1.3 kernel !
+
+INN (1.4unoff3-1) - Miquel van Smoorenburg <miquels@cistron.nl>
+
+ * Took inn1.4sec-8 and 1.4unoff3, folded in some Linux and
+ other patches.
+ * Changed all makefiles to support a "prefix" variable for install
+ * Removed the hacks in debian.rules for installation
+ * Locks are in /var/run/innd
+ * Rewrote post install script.
+
+inn (1.4sec-8); priority=MEDIUM
+
+ * postinst configuration completely redone. It now sets up a minimal
+ local installation for you.
+ * prerm now exists and shuts the server down.
+ * init scripts changed to System V scheme.
+ * Descriptions in control files expanded.
+ * Package now contains /var/lock/news, and uses /var/log (not /var/adm).
+ * inewsinn postinst looks at and can write /etc/mailname.
+
+INN 1.4sec Debian 7 - iwj
+
+* libinn.a, <inn/*.h>, inn-sys2nf and inn-feedone installed
+ (in /usr/lib, /usr/include and /usr/bin).
+
+INN 1.4sec Debian 6 - iwj
+
+* innwatch now started by /etc/rc.misc/news.
+* inewsinn postinst minor typos fixed.
+* Leftover file `t' removed from source and diff distributions.
+
+INN 1.4sec Debian 5 - iwj
+
+* Added documentation about making active and history files.
+* Added monthly makehistory -ru crontab run.
+* Made postinst always do crontab -u news /etc/news/crontab .
+* Removed HAVE_UNIX_DOMAIN - AF_UNIX+SOCK_DGRAM still broken in Linux.
+* Fixed /usr/lib/news/bin/inncheck to conform to our permissions scheme.
+* Added manpage links for makeactive(8), makehistory(8), newsrequeue(8).
+* /var/adm/news now part of package.
+
+INN 1.4sec Debian 4 - iwj
+
+* Added $|=1 to inewsinn postinst script; a few cosmetic fixes.
+
+INN 1.4sec Debian 3 - iwj
+
+* Removed `inet' groups from distrib.pats.
+* Put more version number information in ../*.{deb,gz} filenames.
+* Added Package_Revision field to `control' file.
+* Rationalised debian.rules somewhat, and added `build' stamp file.
+* Permissions rationalised.
+* Changed /etc/rc.d/rc.news to /etc/rc.misc/news.
+* postinst calls Perl as /usr/bin/perl.
+* Added this Changelog.
+
+INN 1.4sec Debian 2 - iwj
+* inews moved to /usr/bin; rnews moved to /usr/sbin.
+* fixed nntpsend not to use PPID variable (it's a bash builtin).
+
+INN 1.4sec Debian 1 - iwj
+Initial release, completely untested.
--- /dev/null
+Source: inn2
+Section: news
+Priority: extra
+Maintainer: Marco d'Itri <md@linux.it>
+Build-Depends: bison, debhelper (>> 4.1.16), quilt (>= 0.40), groff-base, libperl-dev (>= 5.8.0), libdb4.6-dev, libpam0g-dev, libssl-dev (>= 0.9.7), libkrb5-dev
+Standards-Version: 3.8.0
+
+Package: inn2
+Architecture: any
+Depends: ${shlibs:Depends}, ${misc:Depends}, cron, exim4 | mail-transport-agent, time, procps, perl, ${PERLAPI}
+Pre-Depends: inn2-inews (>= 2.3.999+20030227-1)
+Suggests: gnupg, wget
+Replaces: inn, inewsinn, innfeed, ninpaths, inn2-dev
+Provides: news-transport-system
+Conflicts: inn2-lfs, cnews, inn, inewsinn, innfeed, ninpaths, suck (<= 4.2.5-2)
+Description: 'InterNetNews' news server
+ This package provides INN 2.x, which is a very complex news server
+ daemon useful for big sites. The 'inn' package still exists for smaller
+ sites which do not need the complexity of INN 2.x.
+ .
+ The news transport is the part of the system that stores the articles
+ and the lists of which groups are available and so on, and provides
+ those articles on request to users. It receives news (either posted
+ locally or from a newsfeed site), files it, and passes it on to any
+ downstream sites. Each article is kept for a period of time and then
+ deleted (this is known as 'expiry').
+ .
+ By default Debian's INN will install in a fairly simple 'local-only'
+ configuration.
+ .
+ In order to make use of the services provided by INN you'll have to
+ use a user-level newsreader program such as trn. The newsreader is
+ the program that fetches articles from the server and shows them to
+ the user, remembering which the user has seen so that they don't get
+ shown again. It also provides the posting interface for the user.
+Homepage: http://www.isc.org/products/INN/
+
+Package: inn2-lfs
+Architecture: any
+Depends: ${shlibs:Depends}, ${misc:Depends}, cron, exim4 | mail-transport-agent, time, procps, perl, ${PERLAPI}
+Pre-Depends: inn2-inews (>= 2.3.999+20030227-1)
+Suggests: gnupg, wget
+Replaces: inn, inewsinn, innfeed, ninpaths, inn2-dev
+Provides: news-transport-system, inn2
+Conflicts: inn2, cnews, inn, inewsinn, innfeed, ninpaths, suck (<= 4.2.5-2)
+Description: 'InterNetNews' news server (LFS version)
+ This package provides INN 2.x, which is a very complex news server
+ daemon useful for big sites. The 'inn' package still exists for smaller
+ sites which do not need the complexity of INN 2.x.
+ .
+ This version of the package is compiled with Large Files Support.
+ .
+ The news transport is the part of the system that stores the articles
+ and the lists of which groups are available and so on, and provides
+ those articles on request to users. It receives news (either posted
+ locally or from a newsfeed site), files it, and passes it on to any
+ downstream sites. Each article is kept for a period of time and then
+ deleted (this is known as 'expiry').
+ .
+ By default Debian's INN will install in a fairly simple 'local-only'
+ configuration.
+ .
+ In order to make use of the services provided by INN you'll have to
+ use a user-level newsreader program such as trn. The newsreader is
+ the program that fetches articles from the server and shows them to
+ the user, remembering which the user has seen so that they don't get
+ shown again. It also provides the posting interface for the user.
+Homepage: http://www.isc.org/products/INN/
+
+Package: inn2-inews
+Architecture: any
+Depends: ${shlibs:Depends}, ${misc:Depends}
+Provides: inews
+Conflicts: inewsinn, inn2 (<< 2.3.1), cnews
+Replaces: inewsinn, inn2 (<< 2.3.1)
+Description: NNTP client news injector, from InterNetNews (INN)
+ 'inews' is the program that newsreaders call when the user wishes to
+ post an article; it does a few elementary checks and passes the article
+ on to the news server for posting.
+ .
+ This version is the one from Rich Salz's InterNetNews news transport
+ system (which is also available as a Debian package).
+
+Package: inn2-dev
+Section: devel
+Architecture: any
+Conflicts: inn, inn-dev
+Description: The libinn.a library, headers and man pages
+ You will only need this if you are going to compile programs that
+ require the functions in libinn.a.
--- /dev/null
+This package was debianized by Bdale Garbee <bdale@gag.com> on
+Wed, 8 Dec 1999 16:30:09 -0700 and since 23 Sept 2002 has been
+maintained by Marco d'Itri <md@linux.it>.
+
+It was downloaded from ftp://ftp.isc.org/isc/inn/ .
+
+
+INN as a whole and all code contained in it not otherwise marked with
+different licenses and/or copyrights is covered by the following copyright
+and license:
+
+ Copyright (c) 2004, 2005, 2006, 2007, 2008
+ by Internet Systems Consortium, Inc. ("ISC")
+ Copyright (c) 1991, 1994, 1995, 1996, 1997, 1998, 1999, 2000, 2001,
+ 2002, 2003 by The Internet Software Consortium and Rich Salz
+
+ This code is derived from software contributed to the Internet Software
+ Consortium by Rich Salz.
+
+ Permission to use, copy, modify, and distribute this software for any
+ purpose with or without fee is hereby granted, provided that the above
+ copyright notice and this permission notice appear in all copies.
+
+ THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+ REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY
+ SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+
+Some specific portions of INN are covered by different licenses. Those
+licenses, if present, will be noted prominantly at the top of those source
+files. Specifically (but possibly not comprehensively):
+
+ authprogs/smbval/*, backends/send-uucp.in, and control/perl-nocem.in
+ are under the GNU General Public License. See doc/GPL for a copy of
+ this license.
+
+ backends/shrinkfile.c, frontends/scanspool.in, lib/concat.c,
+ lib/hstrerror.c, lib/inet_aton.c, lib/inet_ntoa.c, lib/memcmp.c,
+ lib/parsedate.y, lib/pread.c, lib/pwrite.c, lib/setenv.c, lib/seteuid.c,
+ lib/strerror.c, lib/strlcat.c and lib/strlcpy.c are in the public
+ domain.
+
+ lib/snprintf.c may be used for any purpose as long as the author's
+ notice remains intact in all source code distributions.
+
+ control/gpgverify.in, control/pgpverify.in and control/signcontrol.in
+ are under a BSD-style license (with the advertising clause) with UUNET
+ Technologies, Inc. as the copyright holder. See the end of those files
+ for details.
+
+ control/controlchan.in and control/modules/*.pl are covered by a
+ two-clause BSD-style license (no advertising clause). See the
+ beginning of those files for details.
+
+ lib/strcasecmp.c, lib/strspn.c, and lib/strtok.c are taken from BSD
+ sources and are covered by the standard BSD license. See those files
+ for more details.
+
+ lib/md5.c is covered under the standard free MD5 license from RSA Data
+ Security. See the file for more details. A clarification is also
+ provided here: <http://www.ietf.org/ietf/IPR/RSA-MD-all>.
+
+ "Implementations of these message-digest algorithms, including
+ implementations derived from the reference C code in RFC-1319,
+ RFC-1320, and RFC-1321, may be made, used, and sold without
+ license from RSA for any purpose."
+
+ history/his.c and history/hisv6/hisv6.c are under a license very
+ similar to the new BSD license (no advertising clause) but with Thus
+ plc as the copyright holder. See those files for details.
+
+ lib/tst.c, include/inn/tst.h and doc/pod/tst.pod are derived from
+ <http://www.octavian.org/cs/tst1.3.tar.gz> and are under the new BSD
+ license (no advertising clause), but with Peter A. Friend as the
+ copyright holder.
+
+ tests/runtests.c is covered under a license very similar to the MIT/X
+ Consortium license (less restrictive than INN's licese). See the
+ beginning of the file for details.
+
+
+On Debian GNU/Linux systems, the complete text of the GNU General
+Public License can be found in `/usr/share/common-licenses/GPL'.
+
--- /dev/null
+usr/include/inn/
+usr/share/man/man3/
+usr/lib/news/libinn.a
+usr/lib/news/libstorage.a
+usr/lib/news/libinnhist.a
--- /dev/null
+usr/share/man/man3/dbz.3 usr/share/man/man3/dbzclose.3
+usr/share/man/man3/dbz.3 usr/share/man/man3/dbzinit.3
+usr/share/man/man3/dbz.3 usr/share/man/man3/dbzfetch.3
+usr/share/man/man3/dbz.3 usr/share/man/man3/dbzstore.3
--- /dev/null
+etc/news/distrib.pats
+etc/news/inn.conf
+etc/news/moderators
+etc/news/passwd.nntp
+usr/lib/news/bin/inews
+usr/lib/news/bin/rnews
+usr/lib/news/bin/rnews.libexec
+usr/share/man/man1/inews.1
+usr/share/man/man1/rnews.1
+usr/share/man/man5/distrib.pats.5
+usr/share/man/man5/inn.conf.5
+usr/share/man/man5/moderators.5
+usr/share/man/man5/passwd.nntp.5
--- /dev/null
+usr/lib/news/bin/inews usr/bin/inews
+usr/lib/news/bin/rnews usr/bin/rnews
--- /dev/null
+Some random notes about the Debian INN 2.X package.
+
+If you are upgrading from a previous version, please review the information
+near the top of the NEWS file to learn what has changed, and what you may
+need to do to update your system.
+
+If you plan to use INN at home you should really consider running INN 1.x,
+which you can find in the inn package.
+
+INN 2.X is substantially different in terms of configuration file contents
+and filesystem layout than previous versions. The Debian INN package installs
+a minimal but functional local-only server configuration. Configuring feeds
+to/from other servers, and many other details, is up to you.
+
+You will want to review the information in /usr/share/doc/inn2 to get started
+on configuring the installation for your needs. All of the configuration files
+in /etc/news are flagged as 'conffiles' in the packaging system, so your work
+should not be overwritten without your permission if/when you upgrade the inn
+package in the future. In particular, make sure to update /etc/news/inn.conf
+to put in your organization name and related information before you establish
+any network connections if you don't want to be embarrassed.
+
+Also, if you are moving over from INN 1.X, please note that the directory
+structure under /var/spool/news has changed. At a minimum, you will need to
+move the article database subdirectories from /var/spool/news to
+/var/spool/news/articles. The set of directories that belong in
+/var/spool/news for 2.2.2 and later are:
+
+ archive articles incoming innfeed outgoing overview
+
+Anything else is left over from a previous version, and probably should be
+moved or removed.
+
+It has been pointed out that inn2's use of /etc/cron.d/inn2 instead of
+separate files in /etc/cron.daily and so forth poses a problem for users of
+anacron on boxes that are not run continuously. Since the primary target
+for an INN installation is a fully-connected system that might easily need
+a variety of cron entries with different intervals, I don't intend to change
+this default. However, if you're bothered by this, feel free to change the
+cron configuration to suite your needs.
+
+If you want to use pgpverify (and you do if you're getting a real feed!),
+you can use the /usr/lib/news/bin/buildinnkeyring program to download the
+keys for some hierarchies from ftp.isc.org and add them to the gnupg
+keyring used by pgpverify.
+This package does not support the non-free PGP program anymore.
+
+The program 'compress' is not a part of Debian GNU/Linux due to patent issues
+with the algorithm. By default, the innshellvars* files will try to call
+'compress' if you try to transport compressed batches over UUCP. This will
+work if you install the non-free 'ncompress' package. Since it's non-free,
+this might be as unacceptable to you as it is to me! If you know that all of
+your neighbors can handle gzip, a better solution might be to edit the
+innshellvars* files to use '/bin/gzip -9' for the COMPRESS variable. I do not
+intend to change this default to differ from the upstream source.
+
+Log files in /var/log/news need to be owned by user 'news' for the news
+scanlogs tool to be able to rotate them properly.
+
+If you want to use the ckpasswd program you need to install the libgdbm3
+package.
+
+
+SSL
+~~~
+To enable SSL you need to start /usr/lib/news/bin/nnrpd-ssl with the -S
+flag from inetd or the command line.
+See nnrpd(8) and sasl.conf(5) for details.
+
+You need a certificate authority (CA) certificate in
+/etc/news/nnrpd-ca-cert.pem. You will also need a certificate/key pair,
+named /etc/news/nnrpd-cert.pem and /etc/news/nnrpd-key.pem respectively.
+
+If you do not already have a PKI in place, you can create them with a
+command like:
+
+openssl req -new -x509 -nodes -days 1825 \
+ -keyout /etc/news/nnrpd-key.pem -out /etc/news/nnrpd-cert.pem
+
+The private key must have the correct permissions:
+
+chown root:news /etc/news/nnrpd-key.pem
+chmod 640 /etc/news/nnrpd-key.pem
+
+
+STARTTLS
+~~~~~~~~
+STARTTLS support will not work when nnrpd is started by innd using
+"MODE READER" unless the nnrpd binary is replaced by nnrpd-ssl (e.g.
+by using dpkg-divert(8)).
+The upstream maintainer recommends running nnrpd as a standalone process.
+
+
+Large Files Support
+~~~~~~~~~~~~~~~~~~~
+On 32 bit architectures, the inn2-lfs package is built.
+There is no transition procedure, so if you want to convert an existing
+installation (this may or may not be possible depending on your choice
+of storage and overview formats) then you are on your own.
+When attempting such conversion do not forget that the package will
+delete /var/{spool,lib,log}/news/ when removed so they should be renamed.
+
--- /dev/null
+SHELL=/bin/sh
+PATH=/usr/lib/news/bin:/sbin:/bin:/usr/sbin:/usr/bin
+
+# Expire old news and overview entries nightly, generate reports.
+
+15 4 * * * news test -x /usr/lib/news/bin/news.daily && news.daily expireover lowmark delayrm
+
+# Refresh the cached IP addresses every day.
+
+2 3 * * * news [ -x /usr/sbin/ctlinnd ] && ctlinnd -t 300 -s reload incoming.conf "flush cache"
+
+# Every hour, run an rnews -U. This is not only for UUCP sites, but
+# also to process queud up articles put there by in.nnrpd in case
+# innd wasn't accepting any articles.
+
+10 * * * * news [ -x /usr/bin/rnews ] && rnews -U
+
+# Enable this entry to send posted news back to your upstream provider.
+# Also edit /etc/news/nntpsend.ctl !
+# Not if you use innfeed, of course.
+
+#*/15 * * * * news nntpsend
+
+
+# Enable this if you want to send news by uucp to your provider.
+# Also edit /etc/news/send-uucp.cf !
+
+#22 * * * * news send-uucp.pl
+
+# NINPATHS ###################################################################
+# To enable ninpaths please add this line to /etc/news/newsfeeds:
+# inpaths!:*:Tc,WP:/usr/lib/news/bin/ginpaths2
+#
+#6 6 * * * news ctlinnd -s -t 60 flush inpaths!
+#8 6 1 * * news sendinpaths
+# NINPATHS ###################################################################
+
--- /dev/null
+CONTRIBUTORS
+INSTALL
+NEWS
+README
+doc/checklist
+doc/external-auth
+doc/history
+doc/hook-perl
+doc/IPv6-info
+doc/compliance-nntp
--- /dev/null
+extra/active
+extra/newsgroups
--- /dev/null
+#!/bin/sh -e
+### BEGIN INIT INFO
+# Provides: inn2
+# Required-Start: $local_fs $remote_fs $syslog
+# Required-Stop: $local_fs $remote_fs $syslog
+# Default-Start: 2 3 4 5
+# Default-Stop: 0 1 6
+# Short-Description: INN news server
+# Description: The InterNetNews news server.
+### END INIT INFO
+#
+# Start/stop the news server.
+#
+
+test -f /usr/lib/news/bin/rc.news || exit 0
+
+start () {
+ if [ ! -d /var/run/news ]; then
+ mkdir -p /var/run/news
+ chown news:news /var/run/news
+ chmod 775 /var/run/news
+ fi
+ su news -c /usr/lib/news/bin/rc.news > /var/log/news/rc.news 2>&1
+ # su news -c '/usr/lib/news/bin/nnrpd -D -c /etc/news/readers-ssl.conf -p 563 -S'
+}
+
+stop () {
+ su news -c '/usr/lib/news/bin/rc.news stop' >> /var/log/news/rc.news 2>&1
+ # start-stop-daemon --stop --name nnrpd --quiet --oknodo
+}
+
+case "$1" in
+ start)
+ echo -n "Starting news server: "
+ start
+ echo "done."
+ ;;
+ stop)
+ echo -n "Stopping news server: "
+ stop
+ echo "done."
+ ;;
+ reload|force-reload)
+ echo -n "Reloading most INN configuration files: "
+ ctlinnd -t 20 reload '' /etc/init.d/inn2
+ ;;
+ restart)
+ echo -n "Restarting innd: "
+ if [ -f /var/run/news/innd.pid ]; then
+ ctlinnd -t 20 throttle "init script" > /dev/null || true
+ ctlinnd -t 20 xexec inndstart > /dev/null || start
+ else
+ start
+ fi
+ echo "done."
+ ;;
+ *)
+ echo "Usage: /etc/init.d/inn start|stop|restart|reload">&2
+ exit 1
+ ;;
+esac
+
+exit 0
--- /dev/null
+usr/lib/news/bin/ctlinnd usr/sbin/ctlinnd
+usr/lib/news/bin/innstat usr/sbin/innstat
+usr/lib/news/bin/send-uucp usr/lib/news/bin/send-uucp.pl
+usr/share/man/man3/uwildmat.3 usr/share/man/man3/wildmat.3
+usr/share/man/man8/send-uucp.8 usr/share/man/man8/send-ihave.8
+usr/share/man/man8/send-uucp.8 usr/share/man/man8/send-nntp.8
--- /dev/null
+\w{3} [ :0-9]{11} [._[:alnum:]-]+ (rnews|innd|batcher): Reading config from /etc/news/inn\.conf$
+^\w{3} [ :0-9]{11} [._[:alnum:]-]+ (expire|expireover|ctlinnd|nnrpd)\[[0-9]+\]: Reading config from /etc/news/inn\.conf$
+^\w{3} [ :0-9]{11} [._[:alnum:]-]+ rnews: offered <[^[:space:]]+> [._[:alnum:]-]+$
+^\w{3} [ :0-9]{11} [._[:alnum:]-]+ innd: localhost connected [0-9]+$
+^\w{3} [ :0-9]{11} [._[:alnum:]-]+ innd: [[:alpha:]]$
+^\w{3} [ :0-9]{11} [._[:alnum:]-]+ innd: [[:alpha:]]:$
+^\w{3} [ :0-9]{11} [._[:alnum:]-]+ innd: [[:alpha:]]:[-[:alnum:]]+$
+^\w{3} [ :0-9]{11} [._[:alnum:]-]+ innd: [[:alpha:]]:Expiring process [0-9]+$
+^\w{3} [ :0-9]{11} [._[:alnum:]-]+ innd: [[:alpha:]]:Flushing log and syslog files$
+^\w{3} [ :0-9]{11} [._[:alnum:]-]+ innd: [[:alpha:]]:/var/log/news/expire\.lowmark$
+^\w{3} [ :0-9]{11} [._[:alnum:]-]+ innd: [._[:alnum:]-]+ flush$
+^\w{3} [ :0-9]{11} [._[:alnum:]-]+ innd: [._[:alnum:]-]+ opened [^[:space:]]+$
+^\w{3} [ :0-9]{11} [._[:alnum:]-]+ innd: [._[:alnum:]-]+ closed$
+^\w{3} [ :0-9]{11} [._[:alnum:]-]+ innd: [._[:alnum:]-]+:[0-9]+ readclose$
+^\w{3} [ :0-9]{11} [._[:alnum:]-]+ innd: [._[:alnum:]-]+:[0-9]+ inactive [0-9]+$
+^\w{3} [ :0-9]{11} [._[:alnum:]-]+ innd: [._[:alnum:]-]+:[0-9]+ NCmode \"mode stream\" received$
+^\w{3} [ :0-9]{11} [._[:alnum:]-]+ innd: [._[:alnum:]-]+ connected [0-9]+ streaming allowed$
+^\w{3} [ :0-9]{11} [._[:alnum:]-]+ innd: ME HISstats [0-9]+ hitpos [0-9]+ hitneg [0-9]+ missed [0-9]+ dne$
+^\w{3} [ :0-9]{11} [._[:alnum:]-]+ innd: ME time [0-9]+ hishave [0-9]+\([0-9]+\) hiswrite [0-9]+\([0-9]+\) hissync [0-9]+\([0-9]+\) idle [0-9]+\([0-9]+\) artclean [0-9]+\([0-9]+\) artwrite [0-9]+\([0-9]+\) artcncl [0-9]+\([0-9]+\) hishave/artcncl [0-9]+\([0-9]+\) his(grep|write)/artcncl [0-9]+\([0-9]+\) artlog/artcncl [0-9]+\([0-9]+\) his(write|grep)/artcncl [0-9]+\([0-9]+\) sitesend [0-9]+\([0-9]+\) overv [0-9]+\([0-9]+\) perl [0-9]+\([0-9]+\) nntpread [0-9]+\([0-9]+\) artparse [0-9]+\([0-9]+\)( artlog/artparse [0-9]+\([0-9]+\))? artlog [0-9]+\([0-9]+\) datamove [0-9]+\([0-9]+\)$
+^\w{3} [ :0-9]{11} [._[:alnum:]-]+ innd: SERVER (servermode|flushlogs) (running|paused)$
+^\w{3} [ :0-9]{11} [._[:alnum:]-]+ innd: SERVER paused Flushing log and syslog files$
+^\w{3} [ :0-9]{11} [._[:alnum:]-]+ innd: SERVER running$
+^\w{3} [ :0-9]{11} [._[:alnum:]-]+ innd: SERVER paused Expiring process [0-9]+$
+^\w{3} [ :0-9]{11} [._[:alnum:]-]+ batcher\[[0-9]+\]: batcher [[:alnum:]]+ times user [.0-9]+ system [.0-9]+ elapsed [.0-9]+$
+^\w{3} [ :0-9]{11} [._[:alnum:]-]+ batcher\[[0-9]+\]: batcher [[:alnum:]]+ stats batches [0-9]+ articles [0-9]+ bytes [0-9]+$
+^\w{3} [ :0-9]{11} [._[:alnum:]-]+ nnrpd\[[0-9]+\]: Reading access from /etc/news/readers\.conf$
+^\w{3} [ :0-9]{11} [._[:alnum:]-]+ nnrpd\[[0-9]+\]: SERVER perl filtering enabled$
+^\w{3} [ :0-9]{11} [._[:alnum:]-]+ nnrpd\[[0-9]+\]: [._[:alnum:]-]+ \([.0-9]+\) connect$
+^\w{3} [ :0-9]{11} [._[:alnum:]-]+ nnrpd\[[0-9]+\]: [._[:alnum:]-]+ timeout$
+^\w{3} [ :0-9]{11} [._[:alnum:]-]+ nnrpd\[[0-9]+\]: [._[:alnum:]-]+ group [.[:alnum:]+-]+ [0-9]+$
+^\w{3} [ :0-9]{11} [._[:alnum:]-]+ nnrpd\[[0-9]+\]: Auth strategy '[[:alnum:]]+' does not match client\. Removing\.$
+^\w{3} [ :0-9]{11} [._[:alnum:]-]+ nnrpd\[[0-9]+\]: [._[:alnum:]-]+ (no_)?match_user [<>_[:alnum:]-]+(@[._[:alnum:]-]+)? [<>,_,\*,\![:alnum:][:punct:]-]+$
+^\w{3} [ :0-9]{11} [._[:alnum:]-]+ nnrpd\[[0-9]+\]: [._[:alnum:]-]+ res <[_[:alnum:]-]+>(@[._[:alnum:]-]+)?$
+^\w{3} [ :0-9]{11} [._[:alnum:]-]+ nnrpd\[[0-9]+\]: [._[:alnum:]-]+ time [0-9]+ (hisgrep [0-9]+\([0-9]+\) )?idle [0-9]+\([0-9]+\) (readart [0-9]+\([0-9]+\) )?nntpwrite [0-9]+\([0-9]+\)$
+^\w{3} [ :0-9]{11} [._[:alnum:]-]+ nnrpd\[[0-9]+\]: [._[:alnum:]-]+ times user [.0-9]+ system [.0-9]+ idle [.0-9]+ elapsed [.0-9]+$
+^\w{3} [ :0-9]{11} [._[:alnum:]-]+ nnrpd\[[0-9]+\]: [._[:alnum:]-]+ exit articles [0-9]+ groups [0-9]+$
+^\w{3} [ :0-9]{11} [._[:alnum:]-]+ nnrpd\[[0-9]+\]: [._[:alnum:]-]+ artstats get [0-9]+ time [0-9]+ size [0-9]+$
+^\w{3} [ :0-9]{11} [._[:alnum:]-]+ nnrpd\[[0-9]+\]: [._[:alnum:]-]+ post ok <[[:graph:]]+@[._[:alnum:]-]+>$
+^\w{3} [ :0-9]{11} [._[:alnum:]-]+ nnrpd\[[0-9]+\]: [._[:alnum:]-]+ \(unknown\) posttrack ok [[:graph:]]+<[[:graph:]]+@[._[:alnum:]-]+>$
+^\w{3} [ :0-9]{11} [._[:alnum:]-]+ nnrpd\[[0-9]+\]: [._[:alnum:]-]+ user [[:alnum:][:punct:]]+$
+^\w{3} [ :0-9]{11} [._[:alnum:]-]+ nnrpd\[[0-9]+\]: [._[:alnum:]-]+ Tracking Disabled \(unknown\)$
+^\w{3} [ :0-9]{11} [._[:alnum:]-]+ nnrpd\[[0-9]+\]: [._[:alnum:]-]+ auth authenticator successful, user [[:alnum:][:punct:]]+$
+^\w{3} [ :0-9]{11} [._[:alnum:]-]+ nnrpd\[[0-9]+\]: [._[:alnum:]-]+ auth starting authenticator [[:alnum:][:space:][:punct:]]+$
+^\w{3} [ :0-9]{11} [._[:alnum:]-]+ nnrpd\[[0-9]+\]: [._[:alnum:]-]+ no_access_realm$
+^\w{3} [ :0-9]{11} [._[:alnum:]-]+ cnfsstat\[[0-9]+\]: Class [[:alnum:]]+ for groups matching \"[^[:space:]]+\" Buffer [[:alnum:]]+, len: [0-9]+ Mbytes, used: [0-9]+\.[0-9]+ Mbytes \([0-9 ]+\.[0-9]%\) [ 0-9]+ cycles$
+^\w{3} [ :0-9]{11} [._[:alnum:]-]+ send-uucp\[[0-9]+\]: checking site [^[:space:]]+$
+^\w{3} [ :0-9]{11} [._[:alnum:]-]+ send-uucp\[[0-9]+\]: no articles for [^[:space:]]+$
+^\w{3} [ :0-9]{11} [._[:alnum:]-]+ send-uucp\[[0-9]+\]: Flushing [^[:space:]]+ for site [^[:space:]]+$
+^\w{3} [ :0-9]{11} [._[:alnum:]-]+ send-uucp\[[0-9]+\]: batched articles for [^[:space:]]+$
+^\w{3} [ :0-9]{11} [._[:alnum:]-]+ innfeed\[[0-9]+\]: ME time [0-9]+ idle [0-9]+\([0-9]+\) blstats [0-9]+\([0-9]+\) stsfile [0-9]+\([0-9]+\) newart [0-9]+\([0-9]+\) readart [0-9]+\([0-9]+\) prepart [0-9]+\([0-9]+\) read [0-9]+\([0-9]+\) write [0-9]+\([0-9]+\) cb [0-9]+\([0-9]+\)$
+^\w{3} [ :0-9]{11} [._[:alnum:]-]+ innfeed\[[0-9]+\]: [._[:alnum:]-]+ spooling no active connections$
+^\w{3} [ :0-9]{11} [._[:alnum:]-]+ innfeed\[[0-9]+\]: [._[:alnum:]-]+:[0-9]+ connected$
+^\w{3} [ :0-9]{11} [._[:alnum:]-]+ innfeed\[[0-9]+\]: [._[:alnum:]-]+ remote MODE STREAM$
+^\w{3} [ :0-9]{11} [._[:alnum:]-]+ innfeed\[[0-9]+\]: [._[:alnum:]-]+ (final|checkpoint) seconds [0-9]+ spooled [0-9]+ on_close [0-9]+ sleeping [0-9]+$
+^\w{3} [ :0-9]{11} [._[:alnum:]-]+ innfeed\[[0-9]+\]: [._[:alnum:]-]+ hostChkCxns - maxConnections was [0-9]+ now [0-9]+$
+^\w{3} [ :0-9]{11} [._[:alnum:]-]+ innfeed\[[0-9]+\]: ME articles (active|total) [0-9]+ bytes [0-9]+$
+^\w{3} [ :0-9]{11} [._[:alnum:]-]+ innfeed\[[0-9]+\]: [._[:alnum:]-]+:[0-9]+ cxnsleep connect: Connection refused$
--- /dev/null
+^\w{3} [ :0-9]{11} [._[:alnum:]-]+ innd: [-[:alnum:].]+:[0-9]+ (closed|checkpoint) seconds [0-9]+ accepted [0-9]+ refused [0-9]+ rejected [0-9]+ duplicate [0-9]+ accepted size [0-9]+ duplicate size [0-9]+$
+^\w{3} [ :0-9]{11} [._[:alnum:]-]+ innd: rejecting\[perl\] <[[:alnum:][:punct:]]+@[.[:alnum:]-]+> [0-9]+ [[:alnum:] ]+( \([._[:alnum:]-]+\))?$
+^\w{3} [ :0-9]{11} [._[:alnum:]-]+ rnews: rejected [0-9]+ Unwanted (newsgroup|distribution) "[._,[:alnum:]-]+"$
+^\w{3} [ :0-9]{11} [._[:alnum:]-]+ rnews: rejected [0-9]+ Too old -- "\w{3}, [0-9 ]+ \w{3} [0-9]{4} [0-9:]{8} (\+|-)[0-9]{4}"$
+^\w{3} [ :0-9]{11} [._[:alnum:]-]+ rnews: rejected [0-9]+ Too old -- "[0-9]+ \w{3} [0-9]{4} [0-9:]{8} ([[:upper:]]+|(\+|-)[0-9]{4})"$
+^\w{3} [ :0-9]{11} [._[:alnum:]-]+ rnews: rejected [0-9]+ No colon-space in "("|x-no-archive:yes)" header$
+^\w{3} [ :0-9]{11} [._[:alnum:]-]+ rnews: offered <[^[:space:]]+> [._[:alnum:]-]+$
+^\w{3} [ :0-9]{11} [._[:alnum:]-]+ nnrpd\[[0-9]+\]: [^[:space:]]+ posts received [0-9]+ rejected [0-9]+$
+^\w{3} [ :0-9]{11} [._[:alnum:]-]+ nnrpd\[[0-9]+\]: \? reverse lookup for [0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3} failed: Unknown host -- using IP address for access$
+^\w{3} [ :0-9]{11} [._[:alnum:]-]+ innfeed\[[0-9]+\]: [._[:alnum:]-]+(:[0-9]+)? (final|global|checkpoint) seconds [0-9]+ offered [0-9]+ accepted [0-9]+ refused [0-9]+ rejected [0-9]+ (missing [0-9]+ )?accsize [0-9]+ rejsize [0-9]+( spooled [0-9]+ (on_close [0-9]+ )?unspooled [0-9]+)?( deferred [0-9]+/[0-9.]+ requeued [0-9]+ queue [0-9.]+/[0-9\:\,]+)?$
--- /dev/null
+#!/bin/sh -e
+
+init_inn_files() {
+ PATHDB=$(/usr/lib/news/bin/innconfval pathdb)
+ if [ -z "$PATHDB" ]; then
+ echo "Cannot determine the database path, aborting."
+ exit 1
+ fi
+ cd $PATHDB
+
+ local package='inn2'
+ if [ -e /usr/share/doc/inn2-lfs/ ]; then
+ package='inn2-lfs'
+ fi
+
+ for file in active newsgroups; do
+ if [ ! -f $file ]; then
+ echo "Installing initial content for $PATHDB/$file"
+ install -m 644 -o news -g news \
+ /usr/share/doc/$package/examples/$file .
+ fi
+ done
+
+ if [ ! -f history.dir ]; then
+ echo -n "Building history database in $PATHDB... "
+ if ! /usr/lib/news/bin/makehistory; then
+ echo "failed!"
+ return
+ fi
+ if ! /usr/lib/news/bin/makedbz -i -o -s 300000; then
+ echo "failed!"
+ return
+ fi
+ chown news:news history*
+ chmod 664 history*
+ echo "done."
+ fi
+
+ if [ ! -f active.times ]; then
+ touch active.times
+ chown news:news active.times
+ chmod 644 active.times
+ fi
+
+ # squelch initial noise in email if this isn't present
+ if [ ! -f .news.daily ]; then
+ touch .news.daily
+ chown news:news .news.daily
+ fi
+
+ # make sure typical log files exist, and can be rotated
+ if [ ! -d /var/log/news ]; then
+ install -d -m 775 -o news -g news /var/log/news
+ fi
+ cd /var/log/news
+ [ -f news.notice ] || touch news.crit news.err news.notice
+ chown news:news . OLD path news.crit news.err news.notice
+
+ if [ -x /etc/init.d/inn2 ]; then
+ update-rc.d inn2 defaults > /dev/null
+ fi
+}
+
+check_usenet_alias() {
+ # must have an alias for user usenet, point it to root by default
+ if [ -f /etc/aliases ] && ! grep -q '^usenet:' /etc/aliases \
+ && ! getent passwd usenet; then
+ echo "Adding alias for pseudo-user usenet to /etc/aliases."
+ echo "usenet: root" >> /etc/aliases
+ [ -x /usr/bin/newaliases ] && /usr/bin/newaliases
+ fi
+}
+
+upgrade_inn_conf() {
+ cd /etc/news
+ if [ "$2" ] && dpkg --compare-versions "$2" lt "2.3.999+20030125-1"; then
+ /usr/lib/news/bin/innupgrade -f inn.conf
+ fi
+}
+
+rebuild_history_index() {
+ [ -f /var/lib/news/must-rebuild-history-index ] || return 0
+
+ cd /var/lib/news
+ HLINES=$(tail -1 history.dir | awk '{ print $1 }')
+ [ "$HLINES" ] || HLINES=1000000
+ echo "Rebuilding the history index for $HLINES lines, please wait..."
+ rm history.hash history.index history.dir
+ su news -c "/usr/lib/news/bin/makedbz -s $HLINES -f history"
+
+ rm /var/lib/news/must-rebuild-history-index
+}
+
+rebuild_overview() {
+ [ -f /var/lib/news/must-rebuild-overview ] || return 0
+
+ OVENABLED=$(/usr/lib/news/bin/innconfval enableoverview)
+ if [ -z "$OVENABLED" ]; then
+ echo "Cannot determine the overview method used, stopping."
+ exit 1
+ fi
+ if [ $OVENABLED = no -o $OVENABLED = false ]; then
+ return 0
+ fi
+
+ OVMETHOD=$(/usr/lib/news/bin/innconfval ovmethod)
+ if [ -z "$OVMETHOD" ]; then
+ echo "Cannot determine the overview method used, stopping."
+ exit 1
+ elif [ $OVMETHOD = tradindexed -o $OVMETHOD = ovdb ]; then
+ OVPATH=$(/usr/lib/news/bin/innconfval pathoverview)
+ if [ -z "$OVPATH" ]; then
+ echo "Cannot determine the overview path, aborting."
+ exit 1
+ fi
+ echo "Deleting the old overview database, please wait..."
+ find $OVPATH -type f -not -name DB_CONFIG -print0 | xargs -0 -r rm -f
+ elif [ $OVMETHOD = buffindexed ]; then
+ echo "Deleting the old overview database, please wait..."
+ awk -F : '/^[0-9]/ { print $2 }' < /etc/news/buffindexed.conf | \
+ while read name size; do
+ dd if=/dev/zero of="$name" bs=1024 count="$size"
+ done
+ else
+ echo "Unknown overview method '$OVMETHOD', aborting."
+ exit 1
+ fi
+
+ echo "Rebuilding the overview database, please wait..."
+ su news -c "/usr/lib/news/bin/makehistory -F -O -x"
+
+ rm /var/lib/news/must-rebuild-overview
+}
+
+start_innd() {
+# make sure we can determine the FQDN, since innd won't launch if we can't
+if hostname --fqdn > /dev/null 2>&1; then
+ invoke-rc.d inn2 start || echo "Could not start INN!"
+else
+cat <<END
+
+
+Not starting innd. The daemon needs to be able to determine the name of
+this machine, and your /etc/hosts and/or DNS config is apparently not
+allowing this to happen. After you have fixed things so that 'hostname
+--fqdn' returns a reasonable value, you can start the daemon by running
+'/etc/init.d/inn2 start'.
+
+
+END
+fi
+}
+
+case "$1" in
+ configure)
+ init_inn_files
+ check_usenet_alias
+ upgrade_inn_conf "$@"
+ rebuild_history_index
+ rebuild_overview
+ if [ -z "$2" -o ! -e /var/run/news/innd.pid ]; then # first install
+ start_innd
+ else
+ ctlinnd -t 20 throttle "upgrade" > /dev/null || true
+ ctlinnd -t 20 xexec inndstart > /dev/null \
+ || echo "Could not restart INN!"
+ fi
+ ;;
+
+ abort-upgrade|abort-remove|abort-deconfigure)
+ ;;
+
+ *)
+ echo "postinst called with unknown argument '$1'" >&2
+ ;;
+esac
+
+#DEBHELPER#
+
+exit 0
+
--- /dev/null
+#!/bin/sh -e
+
+if [ "$1" = "purge" ]; then
+ update-rc.d inn2 remove >/dev/null
+ if [ -e /var/lib/news/ ]; then
+ rm -f /var/lib/news/.news.daily /var/lib/news/active* \
+ /var/lib/news/newsgroups /var/lib/news/history*
+ rmdir --ignore-fail-on-non-empty /var/lib/news/
+ fi
+fi
+
+#DEBHELPER#
+
+exit 0
--- /dev/null
+#!/bin/sh -e
+
+if [ "$2" ] && dpkg --compare-versions $2 gt 2.0.0 \
+ && dpkg --compare-versions $2 lt 2.3.0; then
+ echo "Some configuration files have changed in INN 2.4 and will need to"
+ echo "be adjusted, most notably nnrp.access has mutated into readers.conf."
+ echo "Also, note that you may need to rebuild the history database."
+ echo "For more information, read the /usr/share/doc/inn2/NEWS.gz file."
+fi
+
+if [ "$2" ] && dpkg --compare-versions $2 eq 2.3.0-1; then
+ echo 'Upgrade from 2.3.0-1 to >= 2.3.999+20030125-4 is not supported.'
+ echo 'Aborting inn upgrade.'
+ exit 1
+fi
+
+if [ "$2" ] && dpkg --compare-versions $2 lt 2.3.1-2; then
+ # remove any remaining symlinks under /usr/lib/news/bin/filter, then remove
+ # the directory if it's empty
+ if [ -d /usr/lib/news/bin/filter ]; then
+ find /usr/lib/news/bin/filter -type l -exec rm {} \;
+ rmdir /usr/lib/news/bin/filter 2> /dev/null || true
+ fi
+fi
+
+#DEBHELPER#
+
+exit 0
--- /dev/null
+#!/bin/sh -e
+
+kill_innd() {
+ if [ -x /etc/init.d/inn2 ]; then
+ invoke-rc.d inn2 stop
+ fi
+}
+
+case "$1" in
+ remove|deconfigure|failed-upgrade)
+ kill_innd
+ ;;
+
+ upgrade)
+ ;;
+
+ *)
+ echo "$0 called with unknown argument '$1'" >&2
+ exit 1
+ ;;
+esac
+
+#DEBHELPER#
+
+exit 0
--- /dev/null
+--- a/configure
++++ b/configure
+@@ -5839,7 +5839,7 @@ else
+ fi
+
+
+-HOSTNAME=`hostname 2> /dev/null || uname -n`
++HOSTNAME=server.example.net
+
+
+ if test $ac_cv_prog_gcc = yes; then
--- /dev/null
+--- a/samples/buffindexed.conf
++++ b/samples/buffindexed.conf
+@@ -7,5 +7,5 @@
+ # index(0-65535) : path to buffer file :
+ # length of buffer in kilobytes in decimal (1KB = 1024 bytes)
+
+-0:/var/news/spool/overview/OV1:1536000
+-1:/var/news/spool/overview/OV2:1536000
++0:/var/spool/news/overview/OV1:1536000
++1:/var/spool/news/overview/OV2:1536000
--- /dev/null
+honour the Ad flag in newsfeeds
+
+http://inn.eyrie.org/viewcvs/branches/2.4/innd/art.c?r1=7748&r2=7936&pathrev=7936&view=patch
+
+--- 2.4/innd/art.c 2008/04/06 13:49:56 7748
++++ 2.4/innd/art.c 2008/07/20 10:20:41 7936
+@@ -1725,7 +1725,7 @@
+ !DISTwantany(sp->Distributions, list))
+ /* Not in the site's desired list of distributions. */
+ continue;
+- if (sp->DistRequired && list == NULL)
++ if (sp->DistRequired && (list == NULL || *list == NULL))
+ /* Site requires Distribution header and there isn't one. */
+ continue;
+
--- /dev/null
+Fix the correct handling of bodies (Perl regexps were sometimes
+not properly working on SV * bodies). We now use a shared string.
+For Perl < 5.7.1, fall back to a copy of such bodies. At least,
+that method is reliable, even though it were 17% slower.
+
+http://inn.eyrie.org/viewcvs/branches/2.4/include/ppport.h?r1=7237&r2=7951&pathrev=7951&view=patch
+http://inn.eyrie.org/viewcvs/branches/2.4/innd/perl.c?r1=7815&r2=7951&pathrev=7951&view=patch
+
+--- 2.4/innd/perl.c 2008/05/05 08:43:58 7815
++++ 2.4/innd/perl.c 2008/08/05 19:41:17 7951
+@@ -69,7 +69,6 @@
+ CV * filter;
+ int i, rc;
+ char * p;
+- static SV * body = NULL;
+ static char buf[256];
+
+ if (!PerlFilterActive) return NULL;
+@@ -87,23 +86,19 @@
+ }
+
+ /* Store the article body. We don't want to make another copy of it,
+- since it could potentially be quite large. Instead, stash the
+- pointer in the static SV * body. We set LEN to 0 and inc the
+- refcount to tell Perl not to free it (either one should be enough).
+- Requires 5.004. In testing, this produced a 17% speed improvement
+- over making a copy of the article body for a fairly heavy filter. */
++ * since it could potentially be quite large. In testing, this produced
++ * a 17% speed improvement over making a copy of the article body
++ * for a fairly heavy filter.
++ * Available since Perl 5.7.1, newSVpvn_share allows to avoid such
++ * a copy (getting round its use for older versions of Perl leads
++ * to unreliable SV * bodies as for regexps). And for Perl not to
++ * compute a hash for artBody, we give it "42". */
+ if (artBody) {
+- if (!body) {
+- body = newSV(0);
+- (void) SvUPGRADE(body, SVt_PV);
+- }
+- SvPVX(body) = artBody;
+- SvCUR_set(body, artLen);
+- SvLEN_set(body, 0);
+- SvPOK_on(body);
+- (void) SvREADONLY_on(body);
+- (void) SvREFCNT_inc(body);
+- hv_store(hdr, "__BODY__", 8, body, 0);
++#if (PERL_REVISION == 5) && ((PERL_VERSION < 7) || ((PERL_VERSION == 7) && (PERL_SUBVERSION < 1)))
++ hv_store(hdr, "__BODY__", 8, newSVpv(artBody, artLen), 0);
++#else
++ hv_store(hdr, "__BODY__", 8, newSVpvn_share(artBody, artLen, 42), 0);
++#endif /* Perl < 5.7.1 */
+ }
+
+ hv_store(hdr, "__LINES__", 9, newSViv(lines), 0);
+--- 2.4/include/ppport.h 2005/06/05 21:57:50 7237
++++ 2.4/include/ppport.h 2008/08/05 19:41:17 7951
+@@ -150,6 +150,7 @@
+ # endif
+ #endif
+ #ifndef PERL_VERSION
++# define PERL_REVISION (5)
+ # ifdef PERL_PATCHLEVEL
+ # define PERL_VERSION PERL_PATCHLEVEL
+ # else
+@@ -162,7 +163,7 @@
+ # define ERRSV perl_get_sv("@",false)
+ #endif
+
+-#if (PERL_VERSION < 4) || ((PERL_VERSION == 4) && (PERL_SUBVERSION <= 4))
++#if (PERL_REVISION == 5) && ((PERL_VERSION < 4) || ((PERL_VERSION == 4) && (PERL_SUBVERSION <= 4)))
+ # define PL_sv_undef sv_undef
+ # define PL_sv_yes sv_yes
+ # define PL_sv_no sv_no
+@@ -174,7 +175,7 @@
+ # define PL_copline copline
+ #endif
+
+-#if (PERL_VERSION < 5)
++#if (PERL_REVISION == 5) && (PERL_VERSION < 5)
+ # undef dTHR
+ # ifdef WIN32
+ # define dTHR extern int Perl___notused
--- /dev/null
+--- a/site/Makefile
++++ b/site/Makefile
+@@ -116,7 +116,7 @@ config: $(ALL)
+ ## Don't use parallel rules -- we want this to be viewed carefully.
+ install: all $(PAUSE) install-config $(RELOAD_AND_GO)
+ reload-install: all pause install-config reload go
+-install-config: update $(REST_INSTALLED) $(SPECIAL)
++install-config: update $(REST_INSTALLED) #$(SPECIAL)
+
+ ## Install scripts, not per-host config files.
+ update: all $(MOST_INSTALLED)
--- /dev/null
+--- a/control/perl-nocem.in
++++ b/control/perl-nocem.in
+@@ -521,7 +521,9 @@ Processing NoCeM notices is easy to set
+ Import the keys of the NoCeM issuers you trust in order to check
+ the authenticity of their notices. You can do:
+
+- gpg --no-default-keyring --primary-keyring <pathetc>/pgp/ncmring.gpg --import <key-file>
++ gpg --no-default-keyring --primary-keyring <pathetc>/pgp/ncmring.gpg \
++ --no-options --allow-non-selfsigned-uid --no-permission-warning \
++ --batch --import <key-file>
+
+ where <pathetc> is the value of the I<pathetc> parameter set in F<inn.conf>
+ and <key-file> the file containing the key(s) to import. The keyring
+--- a/doc/man/perl-nocem.8
++++ b/doc/man/perl-nocem.8
+@@ -157,8 +157,10 @@ Processing NoCeM notices is easy to set
+ Import the keys of the NoCeM issuers you trust in order to check
+ the authenticity of their notices. You can do:
+ .Sp
+-.Vb 1
+-\& gpg \-\-no\-default\-keyring \-\-primary\-keyring <pathetc>/pgp/ncmring.gpg \-\-import <key\-file>
++.Vb 3
++\& gpg \-\-no\-default\-keyring \-\-primary\-keyring=/etc/news/pgp/ncmring.gpg \e
++\& \-\-no\-options \-\-allow\-non\-selfsigned\-uid \-\-no\-permission\-warning \e
++\& \-\-batch \-\-import <key\-file>
+ .Ve
+ .Sp
+ where <pathetc> is the value of the \fIpathetc\fR parameter set in \fIinn.conf\fR
--- /dev/null
+# backported fixes
+fix_ad_flag
+fix_body_regexps
+
+# waiting to be merged upstream
+
+# debian-specific
+nocem-gpg-import
+debian-paths
+
+# packaging-related
+configure-hostname
+no-makedbz-on-install
+u_innreport_misc
+u_right_length
+u_status_init_ip
+u_tls_duplicate_reply
+u_xhdr_permissions
+u_xover_duplicate_reply
+typo_inn_conf_man
--- /dev/null
+--- a/doc/man/inn.conf.5
++++ b/doc/man/inn.conf.5
+@@ -480,7 +480,7 @@ this parameter must be set if \fIenableo
+ .el .IP "\f(CWbuffindexed\fR" 4
+ .IX Item "buffindexed"
+ Stores overview data and index information into buffers, which are
+-preconfigured files defined in \fIbuffinedexed.conf\fR. \f(CW\*(C`buffindexed\*(C'\fR never
++preconfigured files defined in \fIbuffindexed.conf\fR. \f(CW\*(C`buffindexed\*(C'\fR never
+ consumes additional disk space beyond that allocated to these buffers.
+ .ie n .IP """tradindexed""" 4
+ .el .IP "\f(CWtradindexed\fR" 4
--- /dev/null
+Bug-fixes for innreport:
+ - Test for the existence of 'img_dir' instead of 'html_dir' in innreport;
+ - Trailing comma after %innfeed_spooled with "Outgoing feeds (innfeed)
+ by Articles";
+ - Column "Total" of "Outgoing feeds (innfeed) by Volume" tries to add
+ two hashes which evaluates to a constant 0;
+ - Gracefully handle undefined hash elements in "NNRP readership statistics
+ (by domain)";
+ - Also added two error messages generated by perl-nocem.
+
+http://inn.eyrie.org/viewcvs/branches/2.4/scripts/innreport.in?r1=8142&r2=8141&pathrev=8142&view=patch
+http://inn.eyrie.org/viewcvs/branches/2.4/samples/innreport.conf.in?r1=7945&r2=7944&pathrev=7945&view=patch
+http://inn.eyrie.org/viewcvs/branches/2.4/scripts/innreport_inn.pm?r1=7945&r2=7944&pathrev=7945&view=patch
+
+--- 2.4/scripts/innreport.in 2008/10/05 23:47:25 8141
++++ 2.4/scripts/innreport.in 2008/10/07 17:08:32 8142
+@@ -212,7 +212,7 @@
+ $IMG_pth = $ref{'webpath'} if defined $ref{'webpath'};
+
+ $IMG_dir = $HTML_dir . "/" . $IMG_pth
+- if (defined $output{'default'}{'html_dir'} ||
++ if (defined $output{'default'}{'img_dir'} ||
+ defined $ref{'w'} || defined $ref{'webpath'})
+ &&
+ (defined $output{'default'}{'html_dir'} ||
+--- 2.4/samples/innreport.conf.in 2008/08/03 07:30:03 7944
++++ 2.4/samples/innreport.conf.in 2008/08/03 07:47:10 7945
+@@ -1267,7 +1267,7 @@
+ data {
+ name "Spooled";
+ color "#AF00FF";
+- value "%innfeed_spooled,";
++ value "%innfeed_spooled";
+ };
+ };
+ };
+@@ -1347,12 +1347,6 @@
+ color "#FFAF00";
+ value "%innfeed_rejected_size";
+ };
+- data {
+- name "Total";
+- color "#00FF00";
+- value "%innfeed_accepted_size +
+- %innfeed_rejected_size";
+- };
+ };
+ };
+
+@@ -2116,8 +2110,8 @@
+ name "Rej";
+ format_name "%4s";
+ format "%4d";
+- value "$nnrpd_post_rej{$key} +
+- $nnrpd_post_error{$key}";
++ value "($nnrpd_post_rej{$key}||0) +
++ ($nnrpd_post_error{$key}||0)";
+ total "total(%nnrpd_post_rej) +
+ total(%nnrpd_post_error)";
+ };
+@@ -2179,8 +2173,8 @@
+ name "Rej";
+ format_name "%4s";
+ format "%4d";
+- value "$nnrpd_dom_post_rej{$key} +
+- $nnrpd_dom_post_error{$key}";
++ value "($nnrpd_dom_post_rej{$key}||0) +
++ ($nnrpd_dom_post_error{$key}||0)";
+ total "total(%nnrpd_dom_post_rej) +
+ total(%nnrpd_dom_post_error)";
+ };
+--- 2.4/scripts/innreport_inn.pm 2008/08/03 07:30:03 7944
++++ 2.4/scripts/innreport_inn.pm 2008/08/03 07:47:10 7945
+@@ -440,8 +440,8 @@
+ # The exact timers change from various versions of INN, so try to deal
+ # with this in a general fashion.
+ if ($left =~ m/^\S+\s+ # ME
+- time\ (\d+)\s+ # time
+- ((?:\S+\ \d+\(\d+\)\s*)+) # timer values
++ time\s(\d+)\s+ # time
++ ((?:\S+\s\d+\(\d+\)\s*)+) # timer values
+ $/ox) {
+ $innd_time_times += $1;
+ my $timers = $2;
+@@ -719,8 +719,8 @@
+ # ME time X nnnn X(X) [...]
+ return 1 if $left =~ m/backlogstats/;
+ if ($left =~ m/^\S+\s+ # ME
+- time\ (\d+)\s+ # time
+- ((?:\S+\ \d+\(\d+\)\s*)+) # timer values
++ time\s(\d+)\s+ # time
++ ((?:\S+\s\d+\(\d+\)\s*)+) # timer values
+ $/ox) {
+ $innfeed_time_times += $1;
+ my $timers = $2;
+@@ -1459,8 +1459,8 @@
+ # The exact timers change from various versions of INN, so try to deal
+ # with this in a general fashion.
+ if ($left =~ m/^\S+\s+ # ME
+- time\ (\d+)\s+ # time
+- ((?:\S+\ \d+\(\d+\)\s*)+) # timer values
++ time\s(\d+)\s+ # time
++ ((?:\S+\s\d+\(\d+\)\s*)+) # timer values
+ $/ox) {
+ $nnrpd_time_times += $1;
+ my $timers = $2;
+@@ -1683,13 +1683,28 @@
+ $nocem_totalids{$nocem_lastid} += $2;
+ return 1;
+ }
+- if ($left =~ /bad signature from (.*)/o) {
++ if ($left =~ /Article <[^>]*>: (.*) \(ID [[:xdigit:]]*\) not in keyring/o) {
++ $nocem_badsigs{$1}++;
++ $nocem_goodsigs{$1} = 0 unless ($nocem_goodsigs{$1});
++ $nocem_totalbad++;
++ $nocem_lastid = $1;
++ return 1;
++ }
++ if ($left =~ /Article <[^>]*>: bad signature from (.*)/o) {
+ $nocem_badsigs{$1}++;
+ $nocem_goodsigs{$1} = 0 unless ($nocem_goodsigs{$1});
+ $nocem_totalbad++;
+ $nocem_lastid = $1;
+ return 1;
+ }
++ if ($left =~ /Article <[^>]*>: malformed signature/o) {
++ $nocem_badsigs{'N/A'}++;
++ $nocem_goodsigs{'N/A'} = 0 unless ($nocem_goodsigs{'N/A'});
++ $nocem_totalbad++;
++ $nocem_lastid = 'N/A';
++ return 1;
++ }
++
+ return 1;
+ }
+
--- /dev/null
+Bug-fix for TLS: return 1 when length is right.
+
+http://inn.eyrie.org/viewcvs/branches/2.4/nnrpd/tls.c?r1=8058&r2=8057&pathrev=8058&view=patch
+
+--- 2.4/nnrpd/tls.c 2008/09/26 23:11:47 8057
++++ 2.4/nnrpd/tls.c 2008/09/26 23:12:49 8058
+@@ -257,7 +257,7 @@
+ X509_verify_cert_error_string(err));
+
+ if (verify_depth >= depth) {
+- ok = 0;
++ ok = 1;
+ verify_error = X509_V_OK;
+ } else {
+ ok = 0;
--- /dev/null
+Fix a bug in the IP address displayed for localhost in innd's status file.
+It was not correctly initialized (it is a local connection which does not
+use any IP address).
+
+http://inn.eyrie.org/viewcvs/branches/2.4/innd/status.c?r1=7947&r2=7946&pathrev=7947&view=patch
+
+--- 2.4/innd/status.c 2008/08/03 07:50:03 7946
++++ 2.4/innd/status.c 2008/08/03 07:55:20 7947
+@@ -153,9 +153,14 @@
+ status = xmalloc(sizeof(STATUS));
+ peers++; /* a new peer */
+ strlcpy(status->name, TempString, sizeof(status->name));
+- strlcpy(status->ip_addr,
+- sprint_sockaddr((struct sockaddr *)&cp->Address),
+- sizeof(status->ip_addr));
++ if (cp->Address.ss_family == 0) {
++ /* Connections from lc.c do not have an IP address. */
++ memset(&status->ip_addr, 0, sizeof(status->ip_addr));
++ } else {
++ strlcpy(status->ip_addr,
++ sprint_sockaddr((struct sockaddr *)&cp->Address),
++ sizeof(status->ip_addr));
++ }
+ status->can_stream = cp->Streaming;
+ status->seconds = status->Size = status->DuplicateSize = 0;
+ status->Ihave = status->Ihave_Duplicate =
--- /dev/null
+Do not send 580 when negotiation fails (382 has already been sent).
+
+http://inn.eyrie.org/viewcvs/branches/2.4/nnrpd/misc.c?r1=8057&r2=8056&pathrev=8057&view=patch
+
+--- 2.4/nnrpd/misc.c 2008/09/26 23:02:08 8056
++++ 2.4/nnrpd/misc.c 2008/09/26 23:11:47 8057
+@@ -544,7 +544,7 @@
+ result=tls_start_servertls(0, /* read */
+ 1); /* write */
+ if (result==-1) {
+- Reply("%d Starttls failed\r\n", NNTP_STARTTLS_BAD_VAL);
++ /* No reply because we have already sent NNTP_STARTTLS_NEXT_VAL. */
+ return;
+ }
+ nnrpd_starttls_done = 1;
--- /dev/null
+XHDR and XPAT were not checking the permissions the user has to read
+articles when using a message-ID. Now fixed, as well as calls to ARTclose().
+
+http://inn.eyrie.org/viewcvs/branches/2.4/nnrpd/article.c?r1=8004&r2=8003&pathrev=8004&view=patch
+
+--- 2.4/nnrpd/article.c 2008/09/05 19:13:28 8003
++++ 2.4/nnrpd/article.c 2008/09/06 08:49:55 8004
+@@ -688,6 +688,7 @@
+ if (ac > 1)
+ ARTnumber = tart;
+ if ((msgid = GetHeader("Message-ID")) == NULL) {
++ ARTclose();
+ Reply("%s\r\n", ARTnoartingroup);
+ return;
+ }
+@@ -745,9 +746,9 @@
+ if (!ARTopen(ARTnumber))
+ continue;
+ msgid = GetHeader("Message-ID");
++ ARTclose();
+ } while (msgid == NULL);
+
+- ARTclose();
+ Reply("%d %d %s Article retrieved; request text separately.\r\n",
+ NNTP_NOTHING_FOLLOWS_VAL, ARTnumber, msgid);
+ }
+@@ -1008,6 +1009,12 @@
+ Printf("%d No such article.\r\n", NNTP_DONTHAVEIT_VAL);
+ break;
+ }
++ if (!PERMartok()) {
++ ARTclose();
++ Printf("%s\r\n", NOACCESS);
++ break;
++ }
++
+ Printf("%d %s matches follow (ID)\r\n", NNTP_HEAD_FOLLOWS_VAL,
+ header);
+ if ((text = GetHeader(header)) != NULL
+@@ -1047,8 +1054,8 @@
+ SendIOb(buff, strlen(buff));
+ SendIOb(p, strlen(p));
+ SendIOb("\r\n", 2);
+- ARTclose();
+ }
++ ARTclose();
+ }
+ SendIOb(".\r\n", 3);
+ PushIOb();
--- /dev/null
+Fix a bug in the replies of XOVER/XHDR/XPAT when the group is empty.
+Two initial replies were sent.
+
+http://inn.eyrie.org/viewcvs/branches/2.4/nnrpd/article.c?r1=8000&r2=7999&pathrev=8000&view=patch
+
+--- 2.4/nnrpd/article.c 2008/09/03 05:41:27 7999
++++ 2.4/nnrpd/article.c 2008/09/04 17:06:51 8000
+@@ -854,9 +854,7 @@
+
+ /* Parse range. */
+ if (!CMDgetrange(ac, av, &range, &DidReply)) {
+- if (!DidReply) {
+- Reply("%d data follows\r\n", NNTP_OVERVIEW_FOLLOWS_VAL);
+- Printf(".\r\n");
++ if (DidReply) {
+ return;
+ }
+ }
+@@ -1028,10 +1026,7 @@
+
+ /* Range specified. */
+ if (!CMDgetrange(ac - 1, av + 1, &range, &DidReply)) {
+- if (!DidReply) {
+- Reply("%d %s no matches follow (range)\r\n",
+- NNTP_HEAD_FOLLOWS_VAL, header ? header : "\"\"");
+- Printf(".\r\n");
++ if (DidReply) {
+ break;
+ }
+ }
--- /dev/null
+#!/usr/bin/make -f
+SHELL+= -e
+
+QUILT_STAMPFN := .stamp-patched
+include /usr/share/quilt/quilt.make
+
+D-std := $(CURDIR)/debian/inn2
+D-lfs := $(CURDIR)/debian/inn2-lfs
+D = $(D-$*)
+B = $(CURDIR)/build-$*
+
+##############################################################################
+# this code deals with building a second inn2-lfs package from the same
+# source, but only on 32 bit architectures
+# Ideally new future 32 bit architectures should not bother with inn2-lfs
+# and just enable LFS by default.
+
+DEB_HOST_ARCH ?= $(shell dpkg-architecture -qDEB_HOST_ARCH)
+ifeq ($(DEB_HOST_ARCH),$(filter $(DEB_HOST_ARCH),amd64 ia64 ppc64 s390x))
+# 64 bit std package
+FLAVORS := std
+else ifeq ($(DEB_HOST_ARCH),$(filter $(DEB_HOST_ARCH),armel))
+# 32 bit LFS std package
+FLAVORS := std
+std_configure_flags = --enable-largefiles
+else
+# 32 bit std package and 32 bit LFS lfs package
+FLAVORS := std lfs
+lfs_configure_flags = --enable-largefiles
+endif
+
+std_dh_clean_opts = -pinn2 -pinn2-inews -p inn2-dev
+lfs_dh_clean_opts = -pinn2-lfs
+std_dh_movefiles_opts = -pinn2 -pinn2-inews -p inn2-dev
+lfs_dh_movefiles_opts = -pinn2-lfs -pinn2-lfs-inews -p inn2-lfs-dev
+
+ifeq ($(FLAVORS),std)
+no_package := --no-package=inn2-lfs
+endif
+
+# the upstream source needs to be copied in the flavor-specific build dirs
+src_files := $(shell find . -maxdepth 1 \
+ -not -name . -and -not -name debian -and -not -name .pc \
+ -and -not -name 'build-*' -and -not -name '.stamp-*')
+
+##############################################################################
+DEB_HOST_GNU_TYPE ?= $(shell dpkg-architecture -qDEB_HOST_GNU_TYPE)
+DEB_BUILD_GNU_TYPE ?= $(shell dpkg-architecture -qDEB_BUILD_GNU_TYPE)
+ifeq ($(DEB_BUILD_GNU_TYPE),$(DEB_HOST_GNU_TYPE))
+ configure_flags += --build $(DEB_HOST_GNU_TYPE)
+else
+ configure_flags += --build $(DEB_BUILD_GNU_TYPE) --host $(DEB_HOST_GNU_TYPE)
+endif
+
+clean: unpatch
+ rm -rf .stamp-* build-*
+ [ ! -f Makefile.global ] || $(MAKE) distclean
+ # delete packages which are not in control but are built anyway
+ rm -rf debian/inn2-lfs-dev/ debian/inn2-lfs-inews/
+ # delete the cloned debhelper configuration and logs
+ find debian -maxdepth 1 -name 'inn2-lfs*' -not -type d -print0 \
+ | xargs --no-run-if-empty -0 rm
+ dh_clean
+
+configure: $(addprefix .stamp-configure-, $(FLAVORS))
+.stamp-configure-%: $(QUILT_STAMPFN)
+ dh_testdir
+ mkdir -p $B
+ for dir in $(src_files); do cp -ldpR $$dir $B; done
+ cd $B && \
+ _PATH_PERL=/usr/bin/perl \
+ ac_cv_path__PATH_AWK=awk \
+ ac_cv_path__PATH_EGREP=egrep \
+ ac_cv_path__PATH_SED=sed \
+ ac_cv_path__PATH_SORT=sort \
+ ac_cv_path__PATH_UUX=uux \
+ ac_cv_path_PATH_GPGV=/usr/bin/gpgv \
+ ac_cv_path_GETFTP=wget \
+ ac_cv_search_dbm_open=-ldb \
+ LDFLAGS="-Wl,--as-needed $(LDFLAGS)" \
+ ./configure \
+ --with-perl \
+ --enable-ipv6 \
+ --prefix=/usr/lib/news \
+ --mandir=/usr/share/man \
+ --includedir=/usr/include/inn \
+ --with-db-dir=/var/lib/news \
+ --with-etc-dir=/etc/news \
+ --with-filter-dir=/etc/news/filter \
+ --with-lib-dir=/usr/lib/news \
+ --with-log-dir=/var/log/news \
+ --with-run-dir=/var/run/news \
+ --with-spool-dir=/var/spool/news \
+ --with-tmp-dir=/var/spool/news/incoming/tmp \
+ --with-berkeleydb=/usr \
+ --with-kerberos=/usr \
+ --with-sendmail=/usr/sbin/sendmail \
+ $($*_configure_flags) $(configure_flags)
+ cd $B && \
+ mkdir ssl/ ssl/nnrpd/ && \
+ cd ssl/ && \
+ ln -s ../Makefile.global ../include ../storage ../history . && \
+ cd nnrpd/ && ln -s ../../nnrpd/* .
+ touch $@
+
+build: $(addprefix .stamp-build-, $(FLAVORS))
+.stamp-build-%: .stamp-configure-%
+ dh_testdir
+ cd $B && $(MAKE)
+ cd $B/ssl/nnrpd/ && $(MAKE) \
+ SSLLIB='-L/usr/lib -lssl -lcrypto -ldl' SSLINC='-DHAVE_SSL=1'
+ touch $@
+
+install1-%: .stamp-build-%
+ dh_testdir
+ dh_testroot
+ dh_clean -k $($*_dh_clean_opts)
+
+ cd $B && $(MAKE) install DESTDIR=$D
+ sh -e extra/dh_cloneconf inn2 inn2-lfs
+
+ dh_movefiles $($*_dh_movefiles_opts) --sourcedir=$(subst $(CURDIR)/,,$D)
+
+# move back this one
+ mv $D-dev/usr/share/man/man3/uwildmat.3 $D/usr/share/man/man3/
+
+# remove assorted crap and
+# make sure we don't ship active, active.times, newsgroups in place!
+ cd $D/etc/news/filter && rm -f *.py *.tcl
+ rm -rf $D/usr/lib/news/bin/simpleftp $D/usr/share/man/man1/simpleftp.1\
+ $D/usr/lib/news/doc/ $D/var/lib/news/* \
+ $D/usr/include/
+
+ mv $D/usr/share/man/man1/startinnfeed.1 \
+ $D/usr/share/man/man8/startinnfeed.8
+
+ cp $B/ssl/nnrpd/nnrpd $D/usr/lib/news/bin/nnrpd-ssl
+ install -m 755 extra/buildinnkeyring extra/ginpaths2 \
+ $D/usr/lib/news/bin/
+ install -m 755 contrib/showtoken.in $D/usr/lib/news/bin/showtoken
+ install -m 755 extra/bunbatch $D-inews/usr/lib/news/bin/rnews.libexec/
+
+ install -m 644 extra/send-uucp.cf extra/sasl.conf $D/etc/news/
+
+ mkdir $D/var/log/news/path
+
+install2: $(addprefix install1-, $(FLAVORS))
+ dh_link
+ dh_installchangelogs NEWS
+ dh_installdocs
+ dh_installexamples
+ dh_installinit --noscripts --init-script=inn2
+ dh_installcron
+ dh_installlogcheck
+ dh_compress
+ dh_fixperms \
+ -Xusr/lib/news/bin/inndstart -Xusr/lib/news/bin/startinnfeed
+ # some files are not writeable when installed by make install
+ dh_strip
+
+install3-%: install2
+ chown root:news $D-inews/etc/news/passwd.nntp
+ chmod 640 $D-inews/etc/news/passwd.nntp
+
+ chmod -x $D/usr/lib/news/bin/control/*.pl
+ chmod +rw \
+ $D/usr/lib/news/bin/inndstart \
+ $D/usr/lib/news/bin/startinnfeed
+
+ chown news:uucp $D-inews/usr/lib/news/bin/rnews
+ chmod 4755 $D-inews/usr/lib/news/bin/rnews
+
+ chown -R news:news $D/var/spool/news/ $D/var/lib/news/ \
+ $D/var/run/news/ $D/var/log/news/
+ chmod -R g+w $D/var/spool/news/ $D/var/lib/news/ \
+ $D/var/run/news/ $D/var/log/news/
+
+install4-std: install3-std
+
+# lfs-specific: rename some files installed by debhelper
+install4-lfs: install3-lfs
+ for file in /etc/logcheck/ignore.d.server/inn2 /etc/logcheck/violations.ignore.d/inn2 /etc/cron.d/inn2; do \
+ mv $(D-lfs)$$file-lfs $(D-lfs)$$file; \
+ done
+
+install5: $(addprefix install4-, $(FLAVORS))
+ dh_installdeb
+ dh_md5sums
+ dh_shlibdeps
+ dh_gencontrol $(no_package) -- \
+ -VPERLAPI=$$(perl -MConfig -e 'print "perlapi-$$Config{version}"')
+ dh_builddeb $(no_package)
+
+binary-arch: install5
+
+binary: binary-arch
+
+.PHONY: clean configure build binary-arch binary install%
--- /dev/null
+version=3
+opts=dversionmangle=s/r$// \
+ftp://ftp.isc.org/isc/inn/inn-([\d\.]+)\.tar\.gz
--- /dev/null
+[ Please note that the only portions of INN covered by this license are
+ those files explicitly noted as being under the GPL in LICENSE. It is
+ a requirement of the GPL, however, that a copy of it be distributed
+ with software licensed under it, and some stand-alone programs that
+ are distributed with INN are covered under the GPL. ]
+
+
+ GNU GENERAL PUBLIC LICENSE
+ Version 2, June 1991
+
+ Copyright (C) 1989, 1991 Free Software Foundation, Inc.
+ 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ Everyone is permitted to copy and distribute verbatim copies
+ of this license document, but changing it is not allowed.
+
+ Preamble
+
+ The licenses for most software are designed to take away your
+freedom to share and change it. By contrast, the GNU General Public
+License is intended to guarantee your freedom to share and change free
+software--to make sure the software is free for all its users. This
+General Public License applies to most of the Free Software
+Foundation's software and to any other program whose authors commit to
+using it. (Some other Free Software Foundation software is covered by
+the GNU Library General Public License instead.) You can apply it to
+your programs, too.
+
+ When we speak of free software, we are referring to freedom, not
+price. Our General Public Licenses are designed to make sure that you
+have the freedom to distribute copies of free software (and charge for
+this service if you wish), that you receive source code or can get it
+if you want it, that you can change the software or use pieces of it
+in new free programs; and that you know you can do these things.
+
+ To protect your rights, we need to make restrictions that forbid
+anyone to deny you these rights or to ask you to surrender the rights.
+These restrictions translate to certain responsibilities for you if you
+distribute copies of the software, or if you modify it.
+
+ For example, if you distribute copies of such a program, whether
+gratis or for a fee, you must give the recipients all the rights that
+you have. You must make sure that they, too, receive or can get the
+source code. And you must show them these terms so they know their
+rights.
+
+ We protect your rights with two steps: (1) copyright the software, and
+(2) offer you this license which gives you legal permission to copy,
+distribute and/or modify the software.
+
+ Also, for each author's protection and ours, we want to make certain
+that everyone understands that there is no warranty for this free
+software. If the software is modified by someone else and passed on, we
+want its recipients to know that what they have is not the original, so
+that any problems introduced by others will not reflect on the original
+authors' reputations.
+
+ Finally, any free program is threatened constantly by software
+patents. We wish to avoid the danger that redistributors of a free
+program will individually obtain patent licenses, in effect making the
+program proprietary. To prevent this, we have made it clear that any
+patent must be licensed for everyone's free use or not licensed at all.
+
+ The precise terms and conditions for copying, distribution and
+modification follow.
+\f
+ GNU GENERAL PUBLIC LICENSE
+ TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
+
+ 0. This License applies to any program or other work which contains
+a notice placed by the copyright holder saying it may be distributed
+under the terms of this General Public License. The "Program", below,
+refers to any such program or work, and a "work based on the Program"
+means either the Program or any derivative work under copyright law:
+that is to say, a work containing the Program or a portion of it,
+either verbatim or with modifications and/or translated into another
+language. (Hereinafter, translation is included without limitation in
+the term "modification".) Each licensee is addressed as "you".
+
+Activities other than copying, distribution and modification are not
+covered by this License; they are outside its scope. The act of
+running the Program is not restricted, and the output from the Program
+is covered only if its contents constitute a work based on the
+Program (independent of having been made by running the Program).
+Whether that is true depends on what the Program does.
+
+ 1. You may copy and distribute verbatim copies of the Program's
+source code as you receive it, in any medium, provided that you
+conspicuously and appropriately publish on each copy an appropriate
+copyright notice and disclaimer of warranty; keep intact all the
+notices that refer to this License and to the absence of any warranty;
+and give any other recipients of the Program a copy of this License
+along with the Program.
+
+You may charge a fee for the physical act of transferring a copy, and
+you may at your option offer warranty protection in exchange for a fee.
+
+ 2. You may modify your copy or copies of the Program or any portion
+of it, thus forming a work based on the Program, and copy and
+distribute such modifications or work under the terms of Section 1
+above, provided that you also meet all of these conditions:
+
+ a) You must cause the modified files to carry prominent notices
+ stating that you changed the files and the date of any change.
+
+ b) You must cause any work that you distribute or publish, that in
+ whole or in part contains or is derived from the Program or any
+ part thereof, to be licensed as a whole at no charge to all third
+ parties under the terms of this License.
+
+ c) If the modified program normally reads commands interactively
+ when run, you must cause it, when started running for such
+ interactive use in the most ordinary way, to print or display an
+ announcement including an appropriate copyright notice and a
+ notice that there is no warranty (or else, saying that you provide
+ a warranty) and that users may redistribute the program under
+ these conditions, and telling the user how to view a copy of this
+ License. (Exception: if the Program itself is interactive but
+ does not normally print such an announcement, your work based on
+ the Program is not required to print an announcement.)
+\f
+These requirements apply to the modified work as a whole. If
+identifiable sections of that work are not derived from the Program,
+and can be reasonably considered independent and separate works in
+themselves, then this License, and its terms, do not apply to those
+sections when you distribute them as separate works. But when you
+distribute the same sections as part of a whole which is a work based
+on the Program, the distribution of the whole must be on the terms of
+this License, whose permissions for other licensees extend to the
+entire whole, and thus to each and every part regardless of who wrote it.
+
+Thus, it is not the intent of this section to claim rights or contest
+your rights to work written entirely by you; rather, the intent is to
+exercise the right to control the distribution of derivative or
+collective works based on the Program.
+
+In addition, mere aggregation of another work not based on the Program
+with the Program (or with a work based on the Program) on a volume of
+a storage or distribution medium does not bring the other work under
+the scope of this License.
+
+ 3. You may copy and distribute the Program (or a work based on it,
+under Section 2) in object code or executable form under the terms of
+Sections 1 and 2 above provided that you also do one of the following:
+
+ a) Accompany it with the complete corresponding machine-readable
+ source code, which must be distributed under the terms of Sections
+ 1 and 2 above on a medium customarily used for software interchange; or,
+
+ b) Accompany it with a written offer, valid for at least three
+ years, to give any third party, for a charge no more than your
+ cost of physically performing source distribution, a complete
+ machine-readable copy of the corresponding source code, to be
+ distributed under the terms of Sections 1 and 2 above on a medium
+ customarily used for software interchange; or,
+
+ c) Accompany it with the information you received as to the offer
+ to distribute corresponding source code. (This alternative is
+ allowed only for noncommercial distribution and only if you
+ received the program in object code or executable form with such
+ an offer, in accord with Subsection b above.)
+
+The source code for a work means the preferred form of the work for
+making modifications to it. For an executable work, complete source
+code means all the source code for all modules it contains, plus any
+associated interface definition files, plus the scripts used to
+control compilation and installation of the executable. However, as a
+special exception, the source code distributed need not include
+anything that is normally distributed (in either source or binary
+form) with the major components (compiler, kernel, and so on) of the
+operating system on which the executable runs, unless that component
+itself accompanies the executable.
+
+If distribution of executable or object code is made by offering
+access to copy from a designated place, then offering equivalent
+access to copy the source code from the same place counts as
+distribution of the source code, even though third parties are not
+compelled to copy the source along with the object code.
+\f
+ 4. You may not copy, modify, sublicense, or distribute the Program
+except as expressly provided under this License. Any attempt
+otherwise to copy, modify, sublicense or distribute the Program is
+void, and will automatically terminate your rights under this License.
+However, parties who have received copies, or rights, from you under
+this License will not have their licenses terminated so long as such
+parties remain in full compliance.
+
+ 5. You are not required to accept this License, since you have not
+signed it. However, nothing else grants you permission to modify or
+distribute the Program or its derivative works. These actions are
+prohibited by law if you do not accept this License. Therefore, by
+modifying or distributing the Program (or any work based on the
+Program), you indicate your acceptance of this License to do so, and
+all its terms and conditions for copying, distributing or modifying
+the Program or works based on it.
+
+ 6. Each time you redistribute the Program (or any work based on the
+Program), the recipient automatically receives a license from the
+original licensor to copy, distribute or modify the Program subject to
+these terms and conditions. You may not impose any further
+restrictions on the recipients' exercise of the rights granted herein.
+You are not responsible for enforcing compliance by third parties to
+this License.
+
+ 7. If, as a consequence of a court judgment or allegation of patent
+infringement or for any other reason (not limited to patent issues),
+conditions are imposed on you (whether by court order, agreement or
+otherwise) that contradict the conditions of this License, they do not
+excuse you from the conditions of this License. If you cannot
+distribute so as to satisfy simultaneously your obligations under this
+License and any other pertinent obligations, then as a consequence you
+may not distribute the Program at all. For example, if a patent
+license would not permit royalty-free redistribution of the Program by
+all those who receive copies directly or indirectly through you, then
+the only way you could satisfy both it and this License would be to
+refrain entirely from distribution of the Program.
+
+If any portion of this section is held invalid or unenforceable under
+any particular circumstance, the balance of the section is intended to
+apply and the section as a whole is intended to apply in other
+circumstances.
+
+It is not the purpose of this section to induce you to infringe any
+patents or other property right claims or to contest validity of any
+such claims; this section has the sole purpose of protecting the
+integrity of the free software distribution system, which is
+implemented by public license practices. Many people have made
+generous contributions to the wide range of software distributed
+through that system in reliance on consistent application of that
+system; it is up to the author/donor to decide if he or she is willing
+to distribute software through any other system and a licensee cannot
+impose that choice.
+
+This section is intended to make thoroughly clear what is believed to
+be a consequence of the rest of this License.
+\f
+ 8. If the distribution and/or use of the Program is restricted in
+certain countries either by patents or by copyrighted interfaces, the
+original copyright holder who places the Program under this License
+may add an explicit geographical distribution limitation excluding
+those countries, so that distribution is permitted only in or among
+countries not thus excluded. In such case, this License incorporates
+the limitation as if written in the body of this License.
+
+ 9. The Free Software Foundation may publish revised and/or new versions
+of the General Public License from time to time. Such new versions will
+be similar in spirit to the present version, but may differ in detail to
+address new problems or concerns.
+
+Each version is given a distinguishing version number. If the Program
+specifies a version number of this License which applies to it and "any
+later version", you have the option of following the terms and conditions
+either of that version or of any later version published by the Free
+Software Foundation. If the Program does not specify a version number of
+this License, you may choose any version ever published by the Free Software
+Foundation.
+
+ 10. If you wish to incorporate parts of the Program into other free
+programs whose distribution conditions are different, write to the author
+to ask for permission. For software which is copyrighted by the Free
+Software Foundation, write to the Free Software Foundation; we sometimes
+make exceptions for this. Our decision will be guided by the two goals
+of preserving the free status of all derivatives of our free software and
+of promoting the sharing and reuse of software generally.
+
+ NO WARRANTY
+
+ 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY
+FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN
+OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES
+PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED
+OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS
+TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE
+PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING,
+REPAIR OR CORRECTION.
+
+ 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
+WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR
+REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES,
+INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING
+OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED
+TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY
+YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER
+PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE
+POSSIBILITY OF SUCH DAMAGES.
+
+ END OF TERMS AND CONDITIONS
+\f
+ How to Apply These Terms to Your New Programs
+
+ If you develop a new program, and you want it to be of the greatest
+possible use to the public, the best way to achieve this is to make it
+free software which everyone can redistribute and change under these terms.
+
+ To do so, attach the following notices to the program. It is safest
+to attach them to the start of each source file to most effectively
+convey the exclusion of warranty; and each file should have at least
+the "copyright" line and a pointer to where the full notice is found.
+
+ <one line to give the program's name and a brief idea of what it does.>
+ Copyright (C) 19yy <name of author>
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 2 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program; if not, write to the Free Software
+ Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+
+
+Also add information on how to contact you by electronic and paper mail.
+
+If the program is interactive, make it output a short notice like this
+when it starts in an interactive mode:
+
+ Gnomovision version 69, Copyright (C) 19yy name of author
+ Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
+ This is free software, and you are welcome to redistribute it
+ under certain conditions; type `show c' for details.
+
+The hypothetical commands `show w' and `show c' should show the appropriate
+parts of the General Public License. Of course, the commands you use may
+be called something other than `show w' and `show c'; they could even be
+mouse-clicks or menu items--whatever suits your program.
+
+You should also get your employer (if you work as a programmer) or your
+school, if any, to sign a "copyright disclaimer" for the program, if
+necessary. Here is a sample; alter the names:
+
+ Yoyodyne, Inc., hereby disclaims all copyright interest in the program
+ `Gnomovision' (which makes passes at compilers) written by James Hacker.
+
+ <signature of Ty Coon>, 1 April 1989
+ Ty Coon, President of Vice
+
+This General Public License does not permit incorporating your program into
+proprietary programs. If your program is a subroutine library, you may
+consider it more useful to permit linking proprietary applications with the
+library. If this is what you want to do, use the GNU Library General
+Public License instead of this License.
--- /dev/null
+Notes about IPv6 support in INN:
+
+ This is $Revision: 5416 $, dated $Date: 2002-04-14 07:05:36 -0700 (Sun, 14 Apr 2002) $.
+
+ This document contains some notes about the status of IPv6 support in
+ INN (see also the parts of the code marked FIXME):
+
+
+Things that will break if you compile with --enable-ipv6:
+
+ * innd can only be started via inndstart
+ * IP_OPTIONS are not cleared for any incoming connections to innd even
+ over IPv4
+
+
+
+Some comments as of the completion of the original patch:
+
+ Date: Wed, 13 Feb 2002 00:10:59 -0500 (EST)
+ From: Nathan Lutchansky <lutchann@litech.org>
+ To: Jeffrey M. Vinocur <jeff@litech.org>
+ Subject: IPv6 patch notes
+
+ The IPv6 patch is based directly on Marco d'Itri's IPv6 patch of
+ 2001-03-01 that was posted last year to the inn-workers list. The
+ patch applied fairly cleanly to a working copy from 2002-02-04, and
+ the resulting tree was used as the basis for my work.
+
+ Modifications by Marco and myself were made so that if IPv6 support is
+ not explicitly enabled with the --enable-ipv6 flag to the configure
+ script, the old networking code will be used. Hopefully, nobody will
+ notice any problems with the default configuration, although some
+ changes have been made to data structures even when IPv6 is disabled.
+
+ The original patch added IPv6 support to innd and inndstart, and the
+ auth_pass program. I have added support to nnrpd, innfeed, and the
+ ident auth program. There is no IPv6 support for imapfeed and other
+ auxiliary programs like the radius auth backend.
+
+ Marco's patch made use of several preprocessor defines for
+ configuration but the defines were hand-coded, so I added the
+ corresponding tests the the configuration script. I make no
+ guarantees that the configure script will catch all possible
+ non-portable behavior; the IPv6 API standardization process has left
+ quite a wake of incompatible API implementations over the years.
+ -Nathan
+
--- /dev/null
+## $Id: Makefile 6017 2002-12-16 12:08:38Z alexk $
+##
+## The only target that this Makefile need support is install. Everything
+## else is a null target (and the top level Makefile shouldn't even attempt
+## them in this directory).
+
+include ../Makefile.global
+
+top = ..
+
+TOPDOCS = CONTRIBUTORS HACKING INSTALL LICENSE NEWS README TODO
+
+DOCS = GPL compliance-nntp config-design config-semantics config-syntax \
+ external-auth history hook-perl hook-python hook-tcl sample-control
+
+DIRS = man
+
+all:
+clobber clean distclean:
+tags ctags:
+profiled:
+depend:
+
+install: install-doc
+ @for D in $(DIRS) ; do \
+ cd $$D && $(MAKE) install || exit 1 ; cd .. ; \
+ done
+
+install-doc:
+ for F in $(TOPDOCS) ; do \
+ $(CP_RPUB) $(top)/$$F $D$(PATHDOC)/$$F ; \
+ done
+ for F in $(DOCS) ; do \
+ $(CP_RPUB) $$F $D$(PATHDOC)/$$F ; \
+ done
+ if [ -r $(top)/README.snapshot ] ; then \
+ $(CP_RPUB) $(top)/README.snapshot $D$(PATHDOC)/README.snapshot ; \
+ fi
--- /dev/null
+Introduction
+
+ $Id: checklist 5912 2002-12-03 05:31:11Z vinocur $
+
+ This is an installation checklist written by Rebecca Ore, intended to be
+ the beginning of a different presentation of the information in INSTALL,
+ since getting started with installing INN can be complex. Further
+ clarifications, updates, and expansion are welcome.
+
+Setup
+
+ * Make sure there is a "news" user (and a "news" group)
+
+ * Create a home directory for news (perhaps /usr/local/news/) and make
+ sure it (and subdirectories) are owned by "news", group "news".
+
+ You want to be careful that things in that directory stay owned by
+ "news" -- but you can't just "chown -R news.news" after the install,
+ because you may have binaries that are SUID root. You can do the
+ build as any user, because "make install" will set the permissions
+ correctly. After that point, though, you may want to "su news" to
+ avoid creating any files as root. (For routine maintenance once INN
+ is working, you can generally be root.)
+
+ * If necessary, add ~news/bin to the news user's path and ~news/man to
+ the news user's manpath in your shell config files. (You may want
+ to do this, especially the second part, on your regular account; the
+ manpages are very useful.)
+
+ You can do this now or later, but you will certainly want the
+ manpages to help with configuring INN.
+
+ For bash, try:
+
+ PATH=~news/bin:$PATH
+ export PATH
+ MANPATH=~news/man:$MANPATH
+ export MANPATH
+
+ or csh:
+
+ setenv PATH ~news/bin:$PATH
+ setenv MANPATH ~news/man:$MANPATH
+
+ although if you don't already have MANPATH set, the above may give
+ an error or override your defaults (making it so you can only read
+ the news manpages); if "echo $MANPATH" does not give some reasonable
+ path, you'll need to look up what the default is for your system
+ (such as /usr/man or /usr/share/man).
+
+Compile
+
+ * Download the INN tarball and unpack.
+
+ * Work out configure options ("./configure --help" for a list). If
+ you aren't working out of /usr/local/news, or want to put some files
+ on a different partition, you can set the directories now (or later
+ in inn.conf if you change your mind).
+
+ You probably want "--with-perl". If you're not using NetBSD with
+ cycbuffs or OpenBSD, perhaps "--with-tagged-hash". You might want
+ to compile in SSL and Berkeley DB, if your system supports them.
+
+ ./configure --with-perl ...
+ make
+
+ su
+ make install
+
+ (If you do the last step as root, all of the ownerships and
+ permissions will be correct.)
+
+Configure
+
+ * Find INSTALL and open a separate window for it. A printout is
+ probably a good idea -- it's long but very helpful. Any time the
+ instructions below ask you to make a decision, you can probably find
+ help in INSTALL.
+
+ * Now it's time to work on the files in ~news/etc/. Start with
+ inn.conf; you must fill in the default moderators address, your
+ fully qualified domain names and path. Fill in all the blanks.
+ Change the file descriptor limits to something like 500.
+
+ * If using cycbuffs (the CNFS storage method), open cycbuff.conf in
+ one window and a shell in another to create the cycbuff as described
+ in INSTALL. As you create them, record in cycbuff.conf the paths
+ and sizes. Save paths and sizes in a separate text file on another
+ machine in case you ever blow away the wrong file.
+
+ Name the metacycbuff, then configure storage.conf.
+
+ * In storage.conf, be sure that all sizes of articles can be
+ accomodated. If you want to throw away large articles, do it
+ explicitly by using the "trash" storage method.
+
+ * The default options in expire.ctl work fine if you have cycbuffs, if
+ not, configure to suit.
+
+ * Check over moderators and control.ctl.
+
+ * Run ~news/bin/inncheck and fix anything noted.
+
+ Inncheck gives a rough check on the appropriateness of the
+ configuration files as you go. (It's the equivalent of "perl -cw
+ yourfile.pl" for perl scripts.)
+
+ Note that inncheck is very conservative about permissions; there's
+ no reason most of the config files can't be world-readable if you
+ prefer that.
+
+ * Import an active file (~news/db/active) and run inncheck again.
+ Change where noted (there's a gotcha in the ISC's active list 000000
+ 000000 (whatever number of zeros) should be 0000000 00000001).
+
+ * Create empty initial db files. Be sure these end up owned by news.
+
+ cd ~news/db
+
+ touch newsgroups
+ touch active.times
+
+ touch history
+ ~news/bin/makedbz -i
+ mv history.n.hash history.hash
+ mv history.n.index history.index
+ mv history.n.dir history.dir
+
+ chmod 644 *
+
+ * Create the cron jobs and make the changes to your system's
+ syslog.conf as noted in INSTALL. Also create the cron job for
+ nntpsend if you've chosen that over innfeed.
+
+ Create the log files.
+
+ * For the time being, we can see if everything initially works without
+ worrying about feeds or reader access.
+
+Run
+
+ * Start inn by running ~news/bin/rc.news *as the news user*.
+
+ Check ~news/log/news.notice to see if everything went well, also use
+ "ps" to see if innd is running.
+
+ "telnet localhost 119" and you should see either a welcome banner or
+ a "no permission to talk" message. If not, investigate.
+
+ * "man ctlinnd" now; you'll use "ctlinnd reload" as you complete your
+ configuration.
+
+Feeds
+
+ All of this can be done while INN is running.
+
+ * To get your incoming feeds working, edit incoming.conf. When done,
+ "ctlinnd reload incoming.conf reason" (where "reason" is some text
+ that will show up in the logs, anything will do).
+
+ * To get your outgoing feeds working, decide whether to use innfeed or
+ nntpsend. Edit newsfeeds and either innfeed.conf or nntpsend.ctl.
+
+ In newsfeeds, if using innfeed, use the option which doens't require
+ you to do a separate innfeed configuration unless you know more than
+ I do.
+
+ Then "ctlinnd reload newsfeeds reason".
+
+ * In readers.conf, remember that auth and access can be separated.
+
+ Begin with auth. Your auth for password users could look like this:
+
+ auth "foreignokay" {
+ auth: "ckpasswd -d ~news/db/newsusers"
+ default: "<unauthenticated>"
+ }
+
+ There is a perl script in the ckpasswd man page if you want to do
+ authentications by password and have the appropriate libraries.
+ Copy it to ~news/bin, name the file something like makepasswd.pl and
+ change the internal paths to whatever you're using and wherever
+ you're putting the newsusers database. The standard Apache
+ "htpasswd" tool also works just fine to create INN password files.
+
+ Follow with the access stanzas. Something for people with
+ passwords:
+
+ access "generalpeople" {
+ users: "*"
+ newsgroups: "*,!junk,!control,!control.*"
+ }
+
+ And then something like one of the following two, depending on
+ whether unauthenticated users get any access:
+
+ access "restrictive" {
+ users: "<unauthenticated>"
+ newsgroups: "!*"
+ }
+
+ access "readonly" {
+ users: "<unauthenticated>"
+ read: "local.*"
+ post: "!*"
+ }
+
+ You don't need to reload anything after modifying readers.conf;
+ every time an nnrpd launches it reads its configuration from disk.
+
--- /dev/null
+$Id: compliance-nntp 6817 2004-05-18 09:25:55Z rra $
+
+The following are outstanding issues regarding INN's compliance with the
+NNTP standard. The reference documents used in this analysis are the
+current NNTP IETF Working Group draft (draft-ietf-nntpext-base-15.txt at
+the time of the last check of this audit) or RFC 2980, not RFC 977 (which
+is woefully out of date).
+
+This file documents only compliance issues with the latest version of the
+standard NNTP protocol. It does not cover INN's private extensions or
+INN's implementation of widely available extensions not documented in the
+NNTP standard. Specifically, it does not cover the extensions listed in
+RFC 2980.
+
+------------------------------
+
+ Summary: innd doesn't require whitespace between commands and arguments
+ Standard: draft-ietf-nntpext-base-15.txt, section 4
+ Version: 1.0 to CURRENT 2002-12-26
+Reference: innd/nc.c NCproc() and command handlers
+ Severity: Accepts invalid input
+
+The standard states:
+
+ Keywords and arguments MUST be each separated by one or more US-ASCII
+ SPACE or US-ASCII TAB characters.
+
+This is not checked in NCproc or in the individual command handlers in
+innd. Commands followed immediately by their argument will be accepted by
+innd. For example:
+
+ stat<9k6vjk.hg0@example.com>
+ 223 0 @0301543531000000000000079AAE0000006A@
+
+Impact: Should one command be a prefix of another, innd could dispatch
+the handling of the command to the wrong handler, treating the remainder
+of the command verb as an argument. This laxness also encourages sloppy
+client code. Internally, the lack of argument parsing in NCproc also
+results in code duplication in all of the command handlers.
+
+Suggested fix: Lift the argument parsing code into a function called from
+NCproc, breaking the command line into a vector of command and arguments.
+This will work for all commands implemented by innd and will simplify the
+implementation of command handlers, as well as fixing this problem. This
+is what nnrpd already does.
+
+Impact of fix: It's possible that some serving code is relying on this
+behavior and not sending spaces after commands. Fixing this problem would
+break interoperability with that code.
+
+------------------------------
+
+ Summary: INN doesn't check argument length
+ Standard: draft-ietf-nntpext-base-15.txt, section 4
+ Version: 1.0 to CURRENT 2002-12-26
+Reference: innd/nc.c and nnrpd/nnrpd.c
+ Severity: Accepts invalid input
+
+The standard says:
+
+ Arguments MUST NOT exceed 497 octets.
+
+This is not checked by either innd or nnrpd, although both do check that
+the command itself does not exceed 512 octets.
+
+Impact: Small. May accept invalid commands in extremely rare edge cases.
+
+Suggested fix: Probably not worth fixing separately, although if standard
+command parsing code is written to handle both innd and nnrpd, it wouldn't
+hurt to check this along with everything else.
+
+------------------------------
+
+ Summary: Reply codes other than x9x used for private extensions
+ Standard: draft-ietf-nntpext-base-15.txt, section 4.1
+ Version: 1.0 to CURRENT 2002-12-26
+Reference: include/nntp.h
+ Severity: Violates SHOULD
+
+The standard says:
+
+ Response codes not specified in this standard MAY be used for any
+ installation-specific additional commands also not specified. These
+ SHOULD be chosen to fit the pattern of x9x specified above.
+
+INN uses quite a few response codes that do not fit this pattern for
+various extensions. Some of these will likely later be standardized with
+the response codes that INN uses (the streaming commands, the
+authentication reply codes, and possibly the STARTTLS reply codes), but
+the rest (XGTITLE, MODE CANCEL, and XBATCH) should have used response
+codes in the x9x range.
+
+Impact: Additional ambiguity over the meaning of reply codes, as those
+reply codes could later be standardized as the reply codes for other
+commands.
+
+Suggested fix: For XGTITLE and probably XBATCH, there is no way to fix
+this now. Changing the reply codes would break all existing
+implementations. It may still be possible to change the reply codes for
+MODE CANCEL (which should probably be MODE XCANCEL), but it may not be
+worth the effort.
+
+------------------------------
+
+ Summary: innd may return 480 instead of 500 for unrecognized commands
+ Standard: draft-ietf-nntpext-base-15.txt, section 4.1.1
+ Version: 1.0 to CURRENT 2002-12-26
+Reference: innd/nc.c NCauthinfo()
+ Severity: Violates MUST
+
+The standard says:
+
+ If the command is not recognized, or it is an optional command or
+ extension that is not implemented by the server, the response code 500
+ MUST be returned.
+
+In innd, if the connection is determined to need authentication, all
+incoming commands other than MODE are handed off to NCauthinfo() rather
+than their normal command handlers. NCauthinfo() responds with a 480
+reply code to anything other than AUTHINFO USER, AUTHINFO PASS, or QUIT.
+
+Impact: Unlikely to cause problems in practice, but may confuse clients
+that don't understand the rarely used innd-level authentication
+mechanisms.
+
+Suggested fix: Restructure the command table so that each command also
+has a flag indicating whether it requires authentication for peers that
+are required to authenticate. (Some commands, like HELP and MODE READER,
+should be allowed without authentication.) Then eliminate the special
+casing of the state CSgetauth (it may be better to store whether the peer
+has authenticated in the channel rather than in the channel state) and the
+special handling in NCauthinfo. This should also simplify the code.
+
+------------------------------
+
+ Summary: innd always sends 200 for an accepted connection
+ Standard: draft-ietf-nntpext-base-15.txt, section 7.1
+ Version: 1.0 to CURRENT 2002-12-26
+Reference: innd/nc.c NCsetup() and rc.c RCreader()
+ Severity: Violates MUST
+
+The standard says:
+
+ If the server will accept further commands from the client including
+ POST, the server MUST present a 200 greeting code. If the server will
+ accept further commands from the client, but it is not authorized to
+ post articles using the POST command, the server MUST present a 201
+ greeting code.
+
+The implication is that the greeting code from innd (which doesn't
+implement POST and therefore is never going to allow it) should always be
+201, at least for the case where innd never spawns nnrpd. In the case
+where innd spawns nnrpd, it's unclear what the greeting code should be.
+
+The current implementation nevers send 201 unless one knows for certain
+that the connection will never be allowed to issue a POST command, which
+means that innd always sends 200.
+
+It's unknown whether there is any transit news software that would have
+difficulties with a 201 greeting. Both innxmit and innfeed handle it
+correctly in CURRENT 2001-07-04 and NNTPconnect() handles it correctly in
+INN 1.0, so it seems likely that if any such software exists, it's rare.
+
+Impact: It's almost certain that the current innd behavior isn't hurting
+anything. Even a confused client that thought 200 meant that it could
+send a POST command would then try and be rejected with no damage done.
+
+Suggested fix: The purpose of this return code is to give a hint to a
+reading client indicating whether it should even attempt POST, since
+attempting it may involve a lot of work by the user only to have the post
+rejected. It's only relevant to reading connections, not to transit
+connections.
+
+It's known that some clients, upon seeing a 201 response, will never
+attempt POST, even if MODE READER then returns 200. Therefore innd, when
+handing off connections to nnrpd, must return 200 to not confuse a client
+that will later send MODE READER. For connections where innd won't do
+that handoff, it makes sense to always send 201 if all transit feeds can
+handle that and won't interpret it as unwillingness to accept IHAVE or
+streaming feeds.
+
+RCreader() should therefore be modified to send 201 if noreader is set,
+and otherwise send 200.
+
+Impact of fix: Any feeding software that didn't consider 201 to be a
+valid greeting would be unable to feed a fixed innd unless that innd also
+allowed reading connections.
+
+------------------------------
+
+ Summary: innd doesn't support LIST EXTENSIONS
+ Standard: draft-ietf-nntpext-base-15.txt, section 8.1
+ Version: 1.0 to CURRENT 2002-12-26
+Reference: innd/nc.c NClist()
+ Severity: Not a violation
+
+Support for LIST EXTENSIONS is optional, and innd's current behavior
+(returning a 500 response) is permitted by the standard, but it means that
+innd cannot advertise any of the extensions that it supports. Since this
+will eventually include streaming, support should be added.
+
+Suggested fix: Add support for LIST EXTENSIONS to NClist() as soon as
+innd supports a registered extension or as soon as there is documentation
+for INN's extensions that specify an extension name (beginning with X).
+
+------------------------------
+
+ Summary: nnrpd doesn't return 423 errors when there is no overview info
+ Standard: draft-ietf-nntpext-base-17.txt, section 10.5.1.2
+ Version: 1.4 to CURRENT 2003-05-04
+Reference: nnrpd/article.c CMDxover()
+ Severity: Violates a MUST
+
+The standard says:
+
+ If there are no articles in the range specified, a 423 response MUST be
+ returned.
+
+nnrpd (from the beginning of the XOVER command) has always returned a 224
+response with an empty multiline response instead. INN doesn't support
+OVER yet so this isn't actually a bug in INN, but eventually the XOVER
+implementation will also be used for OVER.
+
+Impact: Less information is communicated to the client about why there
+are no overview records returned. An error response indicating there are
+no valid articles in that range is possibly more informative.
+
+Suggested fix: Don't print out the initial 224 message until at least one
+overview entry has been found, so that CMDxover() can print a 420 response
+instead if no overview records are found.
+
+Impact of fix: May confuse some clients that don't expect to get 420
+errors back from overview queries. It may be necessary to do something
+different for OVER (where clients should expect this behavior since OVER
+is a new command) than for XOVER (where clients may be relying on the
+existing behavior.
+
+------------------------------
+
+ Summary: HDR can return message IDs rather than article numbers
+ Standard: draft-ietf-nntpext-base-17.txt, section 10.6.1.2
+ Version: 1.0 to CURRENT 2003-05-04
+Reference: nnrpd/article.c CMDpat()
+ Severity: Violates a protocol description
+
+The standard says:
+
+ The line consists of the article number, a US-ASCII space, and then the
+ contents of the header (without the header name or the colon and space
+ that follow it) or metadata item. If the article is specified by
+ message-id rather than by article range, the article number is given as
+ "0".
+
+nnrpd instead returns the message ID as the first word of the line when
+HDR is given a message ID argument.
+
+Impact: A client may not be able to parse the output of HDR correctly,
+since the first word is not a number.
+
+Suggested fix: Change the code to return 0 as the first word instead of
+the message ID, per the standard.
+
+Impact of fix: Clients that are expecting the message ID may be
+confused.
+
+------------------------------
+
+ Summary: innd improperly caches DNS returns
+ Standard: draft-ietf-nntpext-base-15.txt, section 14.4
+ Version: 1.0 to CURRENT 2002-12-26
+Reference: innd/rc.c RCreadfile() and elsewhere
+ Severity: Violates a MUST
+
+The standard says:
+
+ If NNTP clients or servers cache the results of host name lookups in
+ order to achieve a performance improvement, they MUST observe the TTL
+ information reported by DNS.
+
+innd caches DNS lookups when reading incoming.conf and doesn't refresh its
+knowledge of DNS except when incoming.conf is reloaded.
+
+Impact: An explicit reload is required whenever the IP address of any
+peer changes, and in the presence of network renumbering innd is
+vulnerable to spoofing if DNS is the only authentication mechanism used.
+
+Suggested fix: This is hard to fix without unacceptable performance
+impact. The only good fix is to either fork a separate helper process to
+do DNS lookups (since gethostbyname may block for essentially an
+arbitrarily long period) or to use the direct resolver library so that one
+can get access to a file descriptor and throw it into the select loop.
+Either way, this requires keeping a DNS file descriptor in the main select
+loop and updating knowledge of DNS periodically, which is a substantial
+amount of additional complexity.
+
+------------------------------
+
+ Summary: innd doesn't diagnose repeated AUTHINFO USER commands
+ Standard: RFC 2980, section 3.1.1
+ Version: 1.0 to CURRENT 2002-12-26
+Reference: innd/nc.c NCauthinfo()
+ Severity: Violates a protocol description
+
+RFC 2980 says:
+
+ The 482 code will also be returned when the AUTHINFO commands are not
+ entered in the correct sequence (like two AUTHINFO USERs in a row, or
+ AUTHINFO PASS preceding AUTHINFO USER).
+
+innd ignores AUTHINFO USER and just always returns a 381 response, however,
+since it doesn't care about the username.
+
+Impact: Probably none.
+
+Suggested fix: A long-term solution would be to add real authentication
+to innd, in which case it would start caring about the authenticated
+identity (and perhaps use that identity to map to an incoming.conf entry).
+It's unclear if this would be worthwhile. Failing that, innd would need
+to keep internal state to know whether AUTHINFO USER had already been
+sent.
--- /dev/null
+$Id: config-design 4805 2001-06-21 10:52:27Z rra $
+
+This file is documentation of the design principles that went into INN's
+configuration file syntax, and some rationale for why those principles
+were chosen.
+
+ 1. All configuration files used by INN should have the same syntax.
+ This was the root reason why the project was taken on in the first
+ place; INN developed a proliferation of configuration files, all of
+ which had a slightly (or greatly) different syntax, forcing the
+ administrator to learn several different syntaxes and resulting in a
+ proliferation of parsers, all with their own little quirks.
+
+ 2. Adding a new configuration file or a new set of configuration options
+ should not require writing a single line of code for syntax parsing.
+ Code that analyzes the semantics of the configuration will of course
+ be necessary, but absolutely no additional code to read files, parse
+ files, build configuration trees, or the like should be required.
+ Ideally, INN should have a single configuration parser that
+ everything uses.
+
+ 3. The syntax should look basically like the syntax of readers.conf,
+ incoming.conf, and innfeed.conf in INN 2.3. After extensive
+ discussion on the inn-workers mailing list, this seemed to be the
+ most generally popular syntax of the ones already used in INN, and
+ inventing a completely new syntax didn't appear likely to have gains
+ outweighing the effort involved. This syntax seemed sufficiently
+ general to represent all of the configuration information that INN
+ needed.
+
+ 4. The parsing layer should *not* attempt to do semantic analysis of the
+ configuration; it should concern itself solely with syntax (or very
+ low-level semantics that are standard across all conceivable INN
+ configuration files). In particular, the parsing layer should not
+ know what parameters are valid, what groups are permitted, what types
+ the values for parameters should have, or what default values
+ parameters have.
+
+ This principle requires some additional explanation, since it is very
+ tempting to not do things this way. However, the more semantic
+ information the parser is aware of, the less general the parser is,
+ and it's very easy to paint oneself into a corner. In particular,
+ it's *not* a valid assumption that all clients of the parsing code
+ will want to reduce the configuration to a bunch of structs; this
+ happens to be true for most clients of inn.conf, for example, but
+ inndstart doesn't want the code needed to reduce everything to a
+ struct and set default values to necessarily be executed in a
+ security-critical context.
+
+ Additionally, making the parser know more semantic information either
+ complicates (significantly) the parser interface or means that the
+ parser has to be modified when the semantics change. The latter is
+ not acceptable, and the parser interface should be as straightforward
+ as possible (to encourage all parts of INN to use it).
+
+ 5. The result of a parse of the configuration file may be represented as
+ a tree of dictionaries, where each dictionary corresponds to a group
+ and each key corresponds to a parameter setting. (Note that this does
+ not assume that the underlying data structure is a hash table, just
+ that it has dictionary semantics, namely a collection of key/value
+ pairs with the keys presumed unique.)
+
+ 6. Parameter values inherit via group nesting. In other words, if a
+ group is nested inside another group, all parameters defined in the
+ enclosing group are inherited by the nested group unless they're
+ explicitly overriden within the nested group. (This point and point
+ 5 are to some degree just corollaries of point 3.)
+
+ 7. The parsing library must permit writing as well as reading. It must
+ be possible for a program to read in a configuration file, modify
+ parameters, add and delete groups, and otherwise change the
+ configuration, and then write back out to disk a configuration file
+ that preserves those changes and still remains as faithful to the
+ original (possibly human-written) configuration file as possible.
+ (Ideally, this would extend to preserving comments, but that may be
+ too difficult to do and therefore isn't required.)
+
+ 8. The parser must not limit the configuration arbitrarily. In
+ particular, unlimited length strings (within available memory) must
+ be supported for string values, and if allowable line length is
+ limited, line continuation must be supported everywhere that there's
+ any reasonable expectation that it might be necessary. One common
+ configuration parameter is a list of hosts or host wildmats that can
+ be almost arbitrarily long, and the syntax and parser must support
+ that.
+
+ 9. The parser should be reasonably efficient, enough so as to not cause
+ an annoying wait for command-line tools like sm and grephistory to
+ start. In general, though, efficiency in either time or memory is
+ not as high of a priority as readable, straightforward code; it's
+ safe to assume that configuration parsing is only done on startup and
+ at rare intervals and is not on any critical speed paths.
+
+10. Error reporting is a must. It must be possible to clearly report
+ errors in the configuration files, including at minimum the file name
+ and line number where the error occurred.
+
+11. The configuration parser should not trust its input, syntax-wise. It
+ must not segfault, infinitely loop, or otherwise explode on malformed
+ or broken input. And, as a related point, it's better to be
+ aggressively picky about syntax than to be lax and attempt to accept
+ minor violations. The intended configuration syntax is simple and
+ unambiguous, so it should be unnecessary to accept violations.
+
+12. It must be possible to do comprehensive semantic checks of a
+ configuration file, including verifying that all provided parameters
+ are known ones, all parameter values have the correct type, group
+ types that are not expected to be repeated are not, and only expected
+ group types are used. This must *not* be done by the parser, but the
+ parser must provide sufficient hooks that the client program can do
+ this if it chooses.
+
+13. The parser must be re-entrant and thread-safe.
+
+14. The grammar shouldn't require any lookahead to parse. This is in
+ order to keep the parser extremely simple and therefore maintainable.
+ (It's worth noting that this design principle leads to the
+ requirement that parameter keys end in a colon; the presence of the
+ colon allows parameter keys to be distinguished from other syntactic
+ elements allowed in the same scope, like the beginning of a nested
+ group.)
--- /dev/null
+$Id: config-semantics 4792 2001-06-21 08:59:39Z rra $
+
+Groups in a configuration file have a well-defined order, namely the order
+in which the groups would be encountered in a depth-first traversal of the
+parse tree.
+
+The supported operations on a configuration file parse tree for reading
+are:
+
+ * Search. Find the first group of a given type in a given tree. This is
+ done via depth-first search.
+
+ * Next. Find the next group of a given type, starting from some group.
+ This is done via depth-first search.
+
+ * Query. Look up the value of a given parameter in a given group (with
+ inheritance). Note that the expected type of the parameter value must
+ be provided by the caller; the parsing library doesn't know the types
+ of parameters.
+
+ * Prune. Limit one's view of the configuration file to only a given set
+ of group types and everything underneath them; any other group types
+ encountered won't be parsed (and therefore everything under them, even
+ groups of the wanted type, won't be seen).
+
+Therefore, the *only* significance of nested group structure is parameter
+inheritence and pruning. In the absence of pruning, it would always be
+possible, by duplicating parameter settings that were inherited and laying
+out the groups in depth-first traversal order, to transform any
+configuration file into an entirely equivalent one that contains no nested
+groups. This isn't true in the presence of pruning, but pruning is
+intended to be used primarily for performance (ignoring the parts of the
+configuration that don't apply to a given parsing library client).
+
+The expected way for clients to use the parsing library is to follow one
+of these two access patterns:
+
+ * Search for a particular configuration group and then query it for a set
+ of parameters (either one by one as they're used, or all at once to
+ collapse the parameters into a struct for faster access later). This
+ is expected to be the common pattern for finding and looking up
+ settings for a particular program. There will generally only be a
+ single group per group type for groups of this sort; it doesn't make
+ sense to have multiple groups setting general configuration options for
+ a program and have to iterate through them and merge them in some
+ fashion.
+
+ * Iterate through all groups of a given type, building a list of them (or
+ of the data they contain). This is the model used by, for example,
+ storage classes; each storage class has a set of parameters, and the
+ storage subsystem needs to know about the full list of classes.
+
+Note that neither of these operations directly reveal the tree structure;
+the tree structure is intended for the convenience of the user in setting
+defaults for various parameters so that they don't have to be repeated in
+each group, and to allow some top-level pruning. It's not intended to be
+semantically significant other than that.
+
+Here are some suggested general conventions:
+
+ * General options for a particular program should be separated out into a
+ their own group. For example, a group innwatch in inn.conf to set the
+ various options only used by innwatch. Note that pruning is inclusive
+ rather than exclusive, so programs should ideally only need to care
+ about a short list of groups.
+
+ * Groups used only for grouping and setting default parameters, ones that
+ won't be searched for explicitly by any program, should use the type
+ "group". This can be used uniformly in all configuration files so that
+ whenever a user sees a group of type "group", they know that it's just
+ syntactic convenience to avoid having to repeat a bunch of parameter
+ settings and isn't otherwise significant.
+
+ * Groups that are searched for or iterated through shouldn't be nested;
+ for example, if a configuration file defines a list of access groups,
+ nesting one access group inside another is discouraged (in favor of
+ putting both groups inside an enclosing group of type "group" that sets
+ the parameters they have in common). This is to cut down on user
+ confusion, since otherwise the nesting appears to be significant.
--- /dev/null
+$Id: config-syntax 5843 2002-11-19 00:08:18Z rra $
+
+This file documents the standardized syntax for INN configuration files.
+This is the syntax that the parsing code in libinn will understand and the
+syntax towards which all configuration files should move.
+
+The basic structure of a configuration file is a tree of groups. Each
+group has a type and an optional tag, and may contain zero or more
+parameter settings, an association of a name with a value. All parameter
+names and group types are simple case-sensitive strings composed of
+printable ASCII characters and not containing whitespace or any of the
+characters "\:;{}[]<>" or the double-quote. A group may contain another
+group (and in fact the top level of the file can be thought of as a
+top-level group that isn't allowed to contain parameter settings).
+
+Supported parameter values are booleans, integers, real numbers, strings,
+and lists of strings.
+
+The basic syntax looks like:
+
+ group-type tag {
+ parameter: value
+ parameter: [ string string ... ]
+ # ...
+
+ group-type tag {
+ # ...
+ }
+ }
+
+Tags are strings, with the same syntax as a string value for a parameter;
+they are optional and may be omitted. A tag can be thought of as the name
+of a particular group, whereas the <group-type> says what that group is
+intended to specify and there may be many groups with the same type.
+
+The second parameter example above has as its value a list. The square
+brackets are part of the syntax of the configuration file; lists are
+enclosed in square brackets and the elements are space-separated.
+
+As seen above, groups may be nested.
+
+Multiple occurances of the same parameter in the parameter section of a
+group is an error. In practice, the second parameter will take precedent,
+but an error will be reported when such a configuration file is parsed.
+
+Parameter values inherit. In other words, the structure:
+
+ first {
+ first-parameter: 1
+ second {
+ second-parameter: 1
+ third { third-parameter: 1 }
+ }
+
+ another "tag" { }
+ }
+
+is parsed into a tree that looks like:
+
+ +-------+ +--------+ +-------+
+ | first |-+-| second |---| third |
+ +-------+ | +--------+ +-------+
+ |
+ | +---------+
+ +-| another |
+ +---------+
+
+where each box is a group. The type of the group is given in the box;
+none of these groups have tags except for the only group of type
+"another", which has the tag "tag". The group of type "third" has three
+parameters set, namely "third-parameter" (set in the group itself),
+"second-parameter" (inherited from the group of type "second"), and
+"first-parameter" (inherited from "first" by "second" and then from
+"second" by "third").
+
+The practical meaning of this is that enclosing groups can be used to set
+default values for a set of subgroups. For example, consider the
+following configuration that defines three peers of a news server and
+newsgroups they're allowed to send:
+
+ peer news1.example.com { newsgroups: * }
+ peer news2.example.com { newsgroups: * }
+ peer news3.example.com { newsgroups: * }
+
+This could instead be written as:
+
+ group {
+ newsgroups: *
+
+ peer news1.example.com { }
+ peer news2.example.com { }
+ peer news3.example.com { }
+ }
+
+or as:
+
+ peer news1.example.com {
+ newsgroups: *
+
+ peer news2.example.com { }
+ peer news3.example.com { }
+ }
+
+and for a client program that only cares about the defined list of peers,
+these three structures would be entirely equivalent; all questions about
+what parameters are defined in the peer groups would have identical
+answers either way this configuration was written.
+
+Note that the second form above is preferred as a matter of style to the
+third, since otherwise it's tempting to derive some significance from the
+nesting structure of the peer groups. Also note that in the second
+example above, the enclosing group *must* have a type other than "peer";
+to see why, consider the program that asks the configuration parser for a
+list of all defined peer groups and uses the resulting list to build some
+internal data structures. If the enclosing group in the second example
+above had been of type peer, there would be four peer groups instead of
+three and one of them wouldn't have a tag, probably provoking an error
+message.
+
+Boolean values may be given as yes, true, or on, or as no, false, or off.
+Integers must be between -2,147,483,648 and +2,147,483,647 inclusive (the
+same as the minimums for a C99 signed long). Floating point numbers must
+be between 0 and 1e37 in absolute magnitude (the same as the minimums for
+a C99 double) and can safely expect eight digits of precision.
+
+Strings are optionally enclosed in double quotes, and must be quoted if
+they contain any whitespace, double-quote, or any characters in the set
+"\:;[]{}<>". Escape sequences in strings (sequences beginning with \) are
+parsed the same as they are in C. Strings can be continued on multiple
+lines by ending each line in a backslash, and the newline is not
+considered part of such a continued string (to embed a literal newline in
+a string, use \n).
+
+Lists of strings are delimited by [] and consist of whitespace-separated
+strings, which must follow the same quoting rules as all other strings.
+Group tags are also strings and follow the same quoting rules.
+
+There are two more bits of syntax. Normally, parameters must be separated
+by newlines, but for convenience it's possible to put multiple parameters
+on the same line separated by semicolons:
+
+ parameter: value; parameter: value
+
+Finally, the body of a group may be defined in a separate file. To do
+this, rather than writing the body of the group enclosed in {}, instead
+give the file name in <>:
+
+ group tag <filename>
+
+(The filename is also a string and may be double-quoted if necessary, but
+since file names rarely contain any of the excluded characters it's rarely
+necessary.)
+
+Here is the (almost) complete ABNF for the configuration file syntax.
+The syntax is per RFC 2234.
+
+First the basic syntax elements and possible parameter values:
+
+ newline = %d13 / %d10 / %d13.10
+ ; Any of CR, LF, or CRLF are interpreted
+ ; as a newline.
+
+ comment = *WSP "#" *(WSP / VCHAR / %x8A-FF) newline
+
+ WHITE = WSP / newline [comment]
+
+ boolean = "yes" / "on" / "true" / "no" / "off" / "false"
+
+ integer = ["-"] 1*DIGIT
+
+ real-number = ["-"] 1*DIGIT "." 1*DIGIT [ "e" ["-"] 1*DIGIT ]
+
+ non-special = %x21 / %x23-39 / %x3D / %x3F-5A / %x5E-7A
+ / %x7C / %x7E / %x8A-FF
+ ; All VCHAR except "\:;<>[]{}
+
+ quoted-string = DQUOTE 1*(WSP / VCHAR / %x8A-FF) DQUOTE
+ ; DQUOTE within the quoted string must be
+ ; written as 0x5C.22 (\"), and backslash
+ ; sequences are interpreted as in C
+ ; strings.
+
+ string = 1*non-special / quoted-string
+
+ list-body = string *( 1*WHITE string )
+
+ list = "[" *WHITE [ list-body ] *WHITE "]"
+
+Now the general structure:
+
+ parameter-name = 1*non-special
+
+ parameter-value = boolean / integer / real-number / string / list
+
+ parameter = parameter-name ":" 1*WSP parameter-value
+
+ parameter-list = parameter [ *WHITE (";" / newline) *WHITE parameter ]
+
+ group-list = group *( *WHITE group )
+
+ group-body = parameter-list [ *WHITE newline *WHITE group-list ]
+ / group-list
+
+ group-file = string
+
+ group-contents = "{" *WHITE [ group-body ] *WHITE "}"
+ / "<" group-file ">"
+
+ group-type = 1*non-special
+
+ group-tag = string
+
+ group-name = group-type [ 1*WHITE group-tag ]
+
+ group = group-name 1*WHITE group-contents
+
+ file = *WHITE *( group *WHITE )
+
+One implication of this grammar is that any line outside a quoted string
+that begins with "#", optionally preceded by whitespace, is regarded as a
+comment and discarded. The line must begin with "#" (and optional
+whitespace); comments at the end of lines aren't permitted. "#" has no
+special significance in quoted strings, even if it's at the beginning of a
+line. Note that comments cannot be continued to the next line in any way;
+each comment line must begin with "#".
+
+It's unclear the best thing to do with high-bit characters (both literal
+characters with value > 0x7F in a configuration file and characters with
+such values created in quoted strings with \<octal>, \x, \u, or \U). In
+the long term, INN should move towards assuming UTF-8 everywhere, as this
+is the direction that all of the news standards are heading, but in the
+interim various non-Unicode character sets are in widespread use and there
+must be some way of encoding those values in INN configuration files (so
+that things like the default Organization header value can be set
+appropriately).
+
+As a compromise, the configuration parser will pass unaltered any literal
+characters with value > 0x7F to the calling application, and \<octal> and
+\x escapes will generate eight-bit characters in the strings (and
+therefore cannot be used to generate UTF-8 strings containing code points
+greater than U+007F). \u and \U, in contrast, will generate characters
+encoded in UTF-8.
--- /dev/null
+NNRPD External Authentication Support
+
+ This is $Revision: 7880 $ dated $Date: 2005-03-17 12:42:46 +0100 (Thu,
+ 17 Mar 2005) $.
+
+ A fundamental part of the readers.conf(5)-based authorization mechanism
+ is the interface to external authenticator and resolver programs. This
+ interface is documented below.
+
+ INN ships with a number of such programs (all written in C, although any
+ language can be used). Code for them can be found in authprogs/ of the
+ source tree; the authenticators are installed to *pathbin*/auth/passwd,
+ and the resolvers are installed to *pathbin*/auth/resolv.
+
+Reading information from nnrpd
+
+ When nnrpd spawns an external auth program, it passes information on
+ standard input as a sequence of "key: value" lines. Each line ends with
+ CRLF, and a line consisting of only "." indicates the end of the input.
+ The order of the fields is not significant. Additional fields not
+ mentioned below may be included; this should not be cause for alarm.
+
+ (For robustness as well as ease of debugging, it is probably wise to
+ accept line endings consisting only of LF, and to treat EOF as
+ indicating the end of the input even if "." has not been received.)
+
+ Code which reads information in the format discussed below and parses it
+ into convenient structures is available authenticators and resolvers
+ written in C; see libauth(3) for details. Use of the libauth library
+ will make these programs substantially easier to write and more robust.
+
+ For authenticators
+
+ When nnrpd calls an authenticator, the lines it passes are
+
+ ClientAuthname: user\r\n
+ ClientPassword: pass\r\n
+
+ where *user* and *pass* are the username and password provided by the
+ client (e.g. using AUTHINFO). In addition, nnrpd generally also passes
+ the fields mentioned as intended for resolvers; it rare instances this
+ data may be useful for authenticators.
+
+ For resolvers
+
+ When nnrpd calls a resolver, the lines it passes are
+
+ ClientHost: hostname\r\n
+ ClientIP: IP-address\r\n
+ ClientPort: port\r\n
+ LocalIP: IP-address\r\n
+ LocalPort: port\r\n
+ .\r\n
+
+ where *hostname* indicates a string representing the hostname if
+ available, *IP-address* is a numeric IP address (dotted-quad for IPv4,
+ equivalent for IPv6 if appropriate), and *port* is a numeric port
+ number. (The *LocalIP* paramter may be useful for determining which
+ interface was used for the incoming connection.)
+
+ If information is not available, nnrpd will omit the corresponding
+ fields. In particular, this applies to the unusual situation of nnrpd
+ not being connected to a socket; TCP-related information is not
+ available for standard input.
+
+Returning information to nnrpd
+
+ Exit status and signals
+
+ The external auth program must exit with a status of 0 to indicate
+ success; any other exit status indicates failure. (The non-zero exit
+ value will be logged.)
+
+ If the program dies due to catching a signal (for example, a
+ segmentation fault occurs), this will be logged and treated as a
+ failure.
+
+ Returning a username and domain
+
+ If the program succeeds, it must return a username string (optionally
+ with a domain appended) by writing to standard output. The line it
+ should write is exactly:
+
+ user:username\r\n
+
+ where *username* is the string that nnrpd should use in matching
+ readers.conf access blocks.
+
+ There should be no extra spaces in lines sent from the hook to nnrpd;
+ "user:aidan" is read by nnrpd as a different username than "user:
+ aidan".
+
+Error messages
+
+ As mentioned above, errors can be indicated by a non-zero exit value, or
+ termination due to an unhandled signal; both cases are logged by nnrpd.
+ However, external auth programs may wish to log error messages
+ separately.
+
+ Although nnrpd will syslog() anything an external auth program writes to
+ standard error, it is generally better to use the messages.h functions,
+ such as warn() and die().
+
+ Please use the ckpasswd.c program as an example for any authenticators
+ you write, and ident.c as an example for any resolvers.
+
+HISTORY
+
+ Written by Aidan Cully for InterNetNews. This documentation rewritten
+ in POD by Jeffrey M. Vinocur <jeff@litech.org>.
+
--- /dev/null
+$Revision: 4165 $
+This file contains a few messages of historical interest. Some of the
+information in these messages is out of date (e.g., you don't need any
+other software, ihave/sendme is suported, etc); see the README and
+installation manual.
+
+The first is a mail message I sent as soon as I got the idea.
+
+Six months later I had something to beta, and I posted the second message
+to Usenet. My ship date was optimistic.
+
+The third message is the application that I required all beta sites to
+fill out.
+
+The fourth is a copy of the release notice.
+\f
+From: Rich Salz <rsalz@bbn.com>
+Date: Sat, 8 Dec 90 15:23:20 EST
+Message-Id: <9012082023.AA13441@litchi.bbn.com>
+To: newsgurus@ucsd.edu, nntp-managers@ucbarpa.Berkeley.EDU
+Subject: Speed idea.
+
+Suppose inews, nntp, "rnews -U", newsunbatch, etc., all just fed their
+articles to a single daemon?
+
+An idea I started kicking around yesterday. This is intended only for
+sites supporting BSD networking. I believe that anyone else who needs
+this kind of speed would find Cnews good enough.
+
+A multi-threaded server that used non-blocking IO to read all incoming
+articles on several sockets (don't forker a server, select on the
+connection socket will return READOK when a connection request comes in).
+All articles are read into memory, then written out to the filesystem
+using a single writev call (easy way to splice the path).
+
+Hash the active file and compile the sys file so as soon as an article was
+accepted we can write out the batchfile entries. As one special case,
+write entries to another socket for articles that should be fed out via
+NNTPLINK or something.
+
+Put the socket inside a group-access-only directory, so that only trusted
+front-ends like inews "rnews -U" etc can connect to it.
+
+Oh yeah, for things like nntp use sendmsg/recvmesg to hand off the
+feeding site to the demon once it's authenticated the incoming call and
+recognized it as an "xfer no" site.
+
+I've a few pages of notes and code fragments to type in.
+
+No locks of any kind. active file is mmap'd or periodically flushed.
+Keep it all in core and blat it out with a single write.
+
+When you want to expire, or add a group, you send a special message
+on a control port, or perhaps a sighup/sigusr1 to force it to resynch.
+
+Any feedback?
+ /r$
+\f
+Path: papaya.bbn.com!rsalz
+From: rsalz@bbn.com (Rich Salz)
+Newsgroups: news.software.nntp,news.admin,comp.org.usenix
+Subject: Seeking beta-testers for a new NNTP transfer system
+Message-ID: <3632@litchi.bbn.com>
+Date: 18 Jun 91 15:47:21 GMT
+Followup-To: poster
+Organization: Bolt, Beranek and Newman, Inc.
+Lines: 72
+Xref: papaya.bbn.com news.software.nntp:1550 news.admin:15565 comp.org.usenix:418
+
+InterNetNews, or INN, is a news transport system. The core part of the
+package is a single long-running daemon that handles all incoming NNTP
+connections. It files the articles and arranges for them to be forwarded
+to downstream sites. Because it is long-running, it can be directed to
+spawn other long-running processes, telling them exactly when an article
+should be sent to a feed. This can replace the "watch the logfile" mode
+of nntplink, for example, with a much cleaner mechanism: read the
+batchfile on standard input.
+
+InterNetNews assumes that memory is cheap and fast while disks are slow.
+No temporary files are used while incoming articles are being received,
+and once processed the entire article is written out using a single
+writev(2) call (this includes updating the Path and Xref headers). The
+active file is kept in memory (a compile-time option can be set to use
+mmap(2)), and the newsfeeds file is parsed once to build a complete matrix
+of which sites receive which newsgroups.
+
+InterNetNews uses many features of standard BSD sockets including
+non-blocking I/O and Unix-domain stream and datagram sockets. It is
+highly doubtful that the official version will ever provide support for
+TLI, DECNET, or other facilities.
+
+INN is fast. Not many hard numbers are available (that is one requirement
+of being a beta-site), but some preliminary tests show it to be at least
+twice as fast as the current standard NNTP/C News combination. For
+example, Jim Thompson at Sun has had 20 nntpxmits feeding into a 4/490,
+and was getting over 14 articles per second, with the CPU 11% utilized. I
+was getting 10 articles/second feeding into a DECstations 3100, with the
+program (running profiled!) 50% idle and the load average under .7. (It
+is a scary thing to see several articles filed with the same timestamp.)
+
+The sys file format is somewhat different, and has been renamed. The
+arcane "foo.all" syntax is gone, replaced with a set of order-dependant
+shell patterns. For example, instead of "comp,comp.sys.sun,!comp.sys" you
+would write "comp.*,!comp.sys.*,comp.sys.sun"; to not get any groups
+related to binaries or pictures, you write "!*pictures*,!*binaries*".
+
+There are other incompatibilities as well. For example, ihave/sendme
+control messages are not supported. Also the philosophy is that that
+invalid articles are dropped, rather than filed into "junk." (A log
+message is written with the reason, and also sent back to the upstream
+feed as part of the NNTP reject reply.) The active file is taken to be
+the definitive list of groups that an article wants to recieve, and if
+none of an article's newsgroups are mentioned in the active file, then the
+article is invalid, logged, and dropped.
+
+The history and log files are intended to be compatible with those created
+by C News. I want to thank Henry and Geoff for their kind permission to
+use DBZ and SUBST. You will need to be running C News expire or a B2.11
+expire that has been modified to use DBZ.
+
+The InterNetNews daemon does not implement all NNTP commands. If sites
+within your campus are going to post or read news via NNTP, you will need
+the standard NNTP distribution. The daemon will spawn the standard nntpd
+if any site not mentioned in its "hosts.nntp" file connects to the TCP
+port. InterNetNews includes a replacement for the "mini-inews" that comes
+with the standard NNTP distribution. This can be used on any machine that
+posts news and connects to an NNTP server somewhere; its use is not
+limited to INN. At some point I hope to have a replacement nntpd
+optimized for newsreaders, and an NNTP transmission program. These will
+remove the need for any external software beyond the C News expire program.
+
+If you would like to beta-test this version, please FTP the file
+pub/usenet/INN.BETA from cronus.bbn.com for directions. It will be a
+fairly tightly-screened beta: DO NOT ASK ME FOR COPIES! Once the system
+is stable, it will be freely redistributable. I hope to have the official
+release by August 7, so that schools can bring the system up before the
+semester starts.
+ /rich $alz
+--
+Please send comp.sources.unix-related mail to rsalz@uunet.uu.net.
+Use a domain-based address or give alternate paths, or you may lose out.
+\f
+Thanks for your interest in InterNetNews. I want to run a fairly
+tightly-controlled beta test of the software before I make it generally
+available. This means that I'm going to screen the sites which will be
+able to participate in the test. Please don't be offended or upset by
+this whole procedure. I want to make the final package as stable as soon
+as possible so that the entire net can benefit (it will be freely
+redistributable). I've set up this mechanism because I think it's the
+best way for me to get the best test results as quickly as possible.
+
+I would therefore appreciate your answers to the following questions.
+If you think the answers to some of them will be obvious to me (e.g.,
+"Describe your organization" --> "UUNET" :-) then feel free to leave it
+blank. If you have any other feedback or comments, please add them.
+
+Email your results to <rsalz@bbn.com>
+ /r$
+
+What software (transport, batching, readers, etc.) do you currently run?
+
+How much experience do you have with Usenet and NNTP?
+
+Describe your organization.
+
+How do you plan on testing InterNetNews? Be specific, describing the
+machine hardware, any test servers, etc. [The answers to this one
+won't be obvious to me -- you gotta write something.]
+
+What are the rough counts of the upstream and downstream feeds, and how do
+they break down by category (UUCP, NNTP, etc.)?
+
+What special news functions does your server perform (gatewaying,
+archiving, etc.)?
+
+Do you understand that by participating in the beta-test you agree not to
+redistribute the software outside of your administrative domain, and that
+you promise to upgrade to the official release in a timely manner?
+\f
+From: Rich Salz <rsalz@uunet.uu.net>
+Message-Id: <inn-announce@uunet.uu.net>
+Newsgroups: news.software.b,news.protocols.nntp
+Subject: Announcing the release of InterNetNews
+
+I am pleased to announce the official release of InterNetNews.
+
+InterNetNews, or INN, is a news transport system. The core part of the
+package is a single long-running daemon that handles all incoming NNTP
+connections. It files the articles and arranges for them to be forwarded
+to downstream sites. Because it is long-running, it can be directed to
+spawn other long-running processes, telling them exactly when an article
+should be sent to a feed.
+
+INN is a complete Usenet system. It provides article expiration and
+archiving, NNTP transport, and UUCP support. Nntplink works fine.
+
+INN does not include a newsreader. It does provide a version of the NNTP
+reference implementation "clientlib" routines so that rrn and other
+newsreaders compile with little trouble. The next release of xrn will
+include INN support.
+
+The spool directory is unchanged while the history database is
+upwardly-compatible with that of C News and the log file is very similar.
+All system configuration files are different.
+
+INN assumes that memory is cheap and fast while disks are slow. No
+temporary files are used while incoming articles are being received, and
+once processed the entire article is written out using a single system
+call (this includes updating the Path and Xref headers). The active file
+is kept in memory, and the newsfeeds file is parsed at start-up to build a
+complete matrix of which sites receive which newsgroups. A paper
+describing the implementation was presented at the June 1992 Usenix
+conference.
+
+INN uses many features of standard BSD sockets including non-blocking
+I/O. It is highly doubtful that the official version will ever provide
+support for TLI, DECNET, or other facilities. Among others, INN beta
+sites include ATT Unix System V Release 4, Apple A/UX, BSDI BSD/386 0.3.3,
+DEC Ultrix 3.x and 4.x, HP-UX s800 8.0, IBM AIX 3.1 and 3.2, Next NeXT-OS
+2.1, Pyramid OSx 5.1, SCO Xenix 2.3.4, SGI Irix 4.0, Sequent Dynix 3.0.4
+and 3.0.12, and Sun SunOS 3.5 and 4.x.
+
+Almost all of the beta-testers have reported faster performance and less
+load once they installed INN. Many people find it easy to maintain.
+
+A number of sites have graciously agreed to provide FTP access to this
+release. The machine names and directories are listed below. Within
+those directories you will find one or more of the following files:
+ README Intro and unpacking instructions;
+ -or- a copy appears at the end of this
+ README.INN article.
+ inn1.0.tar.Z The full distribution
+ inn.usenix.ps.Z The Usenix paper on INN
+
+The sites providing access are:
+ cs.utexas.edu /pub/inn
+ ftp.cs.widener.edu /pub/inn.tar.Z (or wherever).
+ ftp.germany.eu.net /pub/news/inn
+ ftp.ira.uka.de pub/network/news
+ ftp.msen.com /pub/packages/inn
+ ftp.uu.net /pub/news/nntp/inn
+ gatekeeper.dec.com /pub/news/inn
+ grasp1.univ-lyon1.fr /pub/unix/news/inn
+ munnari.oz.au /pub/news/inn
+ sparky.Sterling.COM /news/inn
+ src.doc.ic.ac.uk /computing/usenet/software/transport
+ stasys.sta.sub.org /pub/src/inn
+ (Stasys also has anonymous UUCP; contact <fkk@sta.sub.org>.
+ ucsd.edu /INN
+ usc.edu /pub/inn
+
+Discussion about INN should be posted to news.software.b and
+news.software.nntp. Email should be sent to <rsalz@uunet.uu.net>. Please
+do NOT send it to <rsalz@osf.org> -- it will only just delay your response
+since I will have to forward it to UUNET.
+
+The README follows after the formfeed.
+ /r$
--- /dev/null
+INN Perl Filtering and Authentication Support
+
+ This is $Revision: 7880 $ dated $Date: 2008-06-07 14:46:49 +0200 (Sat,
+ 07 Jun 2008) $.
+
+ This file documents INN's built-in support for Perl filtering and reader
+ authentication. The code is based very heavily on work by Christophe
+ Wolfhugel <wolf@pasteur.fr>, and his work was in turn inspired by the
+ existing TCL support. Please send any bug reports to inn-bugs@isc.org,
+ not to Christophe, as the code has been modified heavily since he
+ originally wrote it.
+
+ The Perl filtering support is described in more detail below.
+ Basically, it allows you to supply a Perl function that is invoked on
+ every article received by innd from a peer (the innd filter) or by nnrpd
+ from a reader (the nnrpd filter). This function can decide whether to
+ accept or reject the article, and can optionally do other, more
+ complicated processing (such as add history entries, cancel articles,
+ spool local posts into a holding area, or even modify the headers of
+ locally submitted posts). The Perl authentication hooks allow you to
+ replace or supplement the readers.conf mechanism used by nnrpd.
+
+ For Perl filtering support, you need to have Perl version 5.004 or
+ newer. Earlier versions of Perl will fail with a link error at
+ compilation time. http://language.perl.com/info/software.html should
+ have the latest Perl version.
+
+ To enable Perl support, you have to specify --with-perl when you run
+ configure. See INSTALL for more information.
+
+The innd Perl Filter
+
+ When innd starts, it first loads the file _PATH_PERL_STARTUP_INND
+ (defined in include/paths.h, by default startup_innd.pl) and then loads
+ the file _PATH_PERL_FILTER_INND (also defined in include/paths.h, by
+ default filter_innd.pl). Both of these files must be located in the
+ directory specified by pathfilter in inn.conf
+ (/usr/local/news/bin/filter by default). The default directory for
+ filter code can be specified at configure time by giving the flag
+ --with-filter-dir to configure.
+
+ INN doesn't care what Perl functions you define in which files. The
+ only thing that's different about the two files is when they're loaded.
+ startup_innd.pl is loaded only once, when innd first starts, and is
+ never reloaded as long as innd is running. Any modifications to that
+ file won't be noticed by innd; only stopping and restarting innd can
+ cause it to be reloaded.
+
+ filter_innd.pl, on the other hand, can be reloaded on command (with
+ "ctlinnd reload filter.perl 'reason'"). Whenever filter_innd.pl is
+ loaded, including the first time at innd startup, the Perl function
+ filter_before_reload() is called before it's reloaded and the function
+ filter_after_reload() is called after it's reloaded (if the functions
+ exist). Additionally, any code in either startup_innd.pl or
+ filter_innd.pl at the top level (in other words, not inside a sub { })
+ is automatically executed by Perl when the files are loaded.
+
+ This allows one to do things like write out filter statistics whenever
+ the filter is reloaded, load a cache into memory, flush cached data to
+ disk, or other similar operations that should only happen at particular
+ times or with manual intervention. Remember, any code not inside
+ functions in startup_innd.pl is executed when that file is loaded, and
+ it's loaded only once when innd first starts. That makes it the ideal
+ place to put initialization code that should only run once, or code to
+ load data that was preserved on disk across a stop and restart of innd
+ (perhaps using filter_mode() -- see below).
+
+ As mentioned above, "ctlinnd reload filter.perl 'reason'" (or "ctlinnd
+ reload all 'reason'") will cause filter_innd.pl to be reloaded. If the
+ function filter_art() is defined after the file has been reloaded,
+ filtering is turned on. Otherwise, filtering is turned off. (Note that
+ due to the way Perl stores functions, once you've defined filter_art(),
+ you can't undefine it just by deleting it from the file and reloading
+ the filter. You'll need to replace it with an empty sub.)
+
+ The Perl function filter_art() is the heart of a Perl filter. Whenever
+ an article is received from a peer, via either IHAVE or TAKETHIS,
+ filter_art() is called if Perl filtering is turned on. It receives no
+ arguments, and should return a single scalar value. That value should
+ be the empty string to indicate that INN should accept the article, or
+ some rejection message to indicate that the article should be rejected.
+
+ filter_art() has access to a global hash named %hdr, which contains all
+ of the standard headers present in the article and their values. The
+ standard headers are:
+
+ Also-Control, Approved, Bytes, Cancel-Key, Cancel-Lock,
+ Content-Base, Content-Disposition, Content-Transfer-Encoding,
+ Content-Type, Control, Date, Date-Received, Distribution, Expires,
+ Face, Followup-To, From, In-Reply-To, Injection-Date, Injection-Info,
+ Keywords, Lines, List-ID, Message-ID, MIME-Version, Newsgroups,
+ NNTP-Posting-Date, NNTP-Posting-Host, Organization, Originator,
+ Path, Posted, Posting-Version, Received, References, Relay-Version,
+ Reply-To, Sender, Subject, Supersedes, User-Agent,
+ X-Auth, X-Canceled-By, X-Cancelled-By, X-Complaints-To, X-Face,
+ X-HTTP-UserAgent, X-HTTP-Via, X-Mailer, X-Modbot, X-Modtrace,
+ X-Newsposter, X-Newsreader, X-No-Archive, X-Original-Message-ID,
+ X-Original-Trace, X-Originating-IP, X-PGP-Key, X-PGP-Sig,
+ X-Poster-Trace, X-Postfilter, X-Proxy-User, X-Submissions-To,
+ X-Trace, X-Usenet-Provider, Xref.
+
+ Note that all the above headers are as they arrived, not modified by
+ your INN (especially, the Xref: header, if present, is the one of the
+ remote site which sent you the article, and not yours).
+
+ For example, the Newsgroups: header of the article is accessible inside
+ the Perl filter as $hdr{'Newsgroups'}. In addition, $hdr{'__BODY__'}
+ will contain the full body of the article and $hdr{'__LINES__'} will
+ contain the number of lines in the body of the article.
+
+ The contents of the %hdr hash for a typical article may therefore look
+ something like this:
+
+ %hdr = (Subject => 'MAKE MONEY FAST!!',
+ From => 'Joe Spamer <him@example.com>',
+ Date => '10 Sep 1996 15:32:28 UTC',
+ Newsgroups => 'alt.test',
+ Path => 'news.example.com!not-for-mail',
+ Organization => 'Spammers Anonymous',
+ Lines => '5',
+ Distribution => 'usa',
+ 'Message-ID' => '<6.20232.842369548@example.com>',
+ __BODY__ => 'Send five dollars to the ISC, c/o ...',
+ __LINES__ => 5
+ );
+
+ Note that the value of $hdr{Lines} is the contents of the Lines: header
+ of the article and may bear no resemblence to the actual length of the
+ article. $hdr{__LINES__} is the line count calculated by INN, and is
+ guaranteed to be accurate.
+
+ The %hdr hash should not be modified inside filter_art(). Instead, if
+ any of the contents need to be modified temporarily during filtering
+ (smashing case, for example), copy them into a seperate variable first
+ and perform the modifications on the copy. Currently, $hdr{__BODY__} is
+ the only data that will cause your filter to die if you modify it, but
+ in the future other keys may also contain live data. Modifying live INN
+ data in Perl will hopefully only cause a fatal exception in your Perl
+ code that disables Perl filtering until you fix it, but it's possible
+ for it to cause article munging or even core dumps in INN. So always,
+ always make a copy first.
+
+ As mentioned above, if filter_art() returns the empty string (''), the
+ article is accepted. Note that this must be the empty string, not 0 or
+ undef. Otherwise, the article is rejected, and whatever scalar
+ filter_art() returns (typically a string) will be taken as the reason
+ why the article was rejected. This reason will be returned to the
+ remote peer as well as logged to the news logs. (innreport, in its
+ nightly report, will summarize the number of articles rejected by the
+ Perl filter and include a count of how many articles were rejected with
+ each reason string.)
+
+ One other type of filtering is also supported. If Perl filtering is
+ turned on and the Perl function filter_messageid() is defined, that
+ function will be called for each message ID received from a peer (via
+ either CHECK or IHAVE). The function receives a single argument, the
+ message ID, and like filter_art() should return an empty string to
+ accept the article or an error string to refuse the article. This
+ function is called before any history lookups and for every article
+ offered to innd with CHECK or IHAVE (before the actual article is sent).
+ Accordingly, the message ID is the only information it has about the
+ article (the %hdr hash will be empty). This code would sit in a
+ performance-critical hot path in a typical server, and therefore should
+ be as fast as possible, but it can do things like refuse articles from
+ certain hosts or cancels for already rejected articles (if they follow
+ the $alz convention) without having to take the network bandwidth hit of
+ accepting the entire article first.
+
+ Note that you cannot rely on filter_messageid() being called for every
+ incoming article; articles sent via TAKETHIS without an earlier CHECK
+ will never pass through filter_messageid() and will only go through
+ filter_art().
+
+ Finally, whenever ctlinnd throttle, ctlinnd pause, or ctlinnd go is run,
+ the Perl function filter_mode() is called if it exists. It receives no
+ arguments and returns no value, but it has access to a global hash %mode
+ that contains three values:
+
+ Mode The current server mode (throttled, paused, or running)
+ NewMode The new mode the server is going to
+ reason The reason that was given to ctlinnd
+
+ One possible use for this function is to save filter state across a
+ restart of innd. There isn't any Perl function which is called when INN
+ shuts down, but using filter_mode() the Perl filter can dump it's state
+ to disk whenever INN is throttled. Then, if the news administrator
+ follows the strongly recommended shutdown procedure of throttling the
+ server before shutting it down, the filter state will be safely saved to
+ disk and can be reloaded when innd restarts (possibly by
+ startup_innd.pl).
+
+ The state of the Perl interpretor in which all of these Perl functions
+ run is preserved over the lifetime of innd. In other words, it's
+ permissible for the Perl code to create its own global Perl variables,
+ data structures, saved state, and the like, and all of that will be
+ available to filter_art() and filter_messageid() each time they're
+ called. The only variable INN fiddles with (or pays any attention to at
+ all) is %hdr, which is cleared after each call to filter_art().
+
+ Perl filtering can be turned off with "ctlinnd perl n" and back on again
+ with "ctlinnd perl y". Perl filtering is turned off automatically if
+ loading of the filter fails or if the filter code returns any sort of a
+ fatal error (either due to Perl itself or due to a "die" in the Perl
+ code).
+
+Supported innd Callbacks
+
+ innd makes seven functions available to any of its embedded Perl code.
+ Those are:
+
+ INN::addhist(*messageid*, *arrival*, *articledate*, *expire*, *paths*)
+ Adds *messageid* to the history database. All of the arguments
+ except the first one are optional; the times default to the current
+ time and the paths field defaults to the empty string. (For those
+ unfamiliar with the fields of a history(5) database entry, the
+ *arrival* is normally the time at which the server accepts the
+ article, the *articledate* is from the Date header of the article,
+ the *expire* is from the Expires header of the article, and the
+ *paths* field is the storage API token. All three times as measured
+ as a time_t since the epoch.) Returns true on success, false
+ otherwise.
+
+ INN::article(*messageid*)
+ Returns the full article (as a simple string) identified by
+ *messageid*, or undef if it isn't found. Each line will end with a
+ simple \n, but leading periods may still be doubled if the article
+ is stored in wire format.
+
+ INN::cancel(*messageid*)
+ Cancels *messageid*. (This is equivalent to "ctlinnd cancel"; it
+ cancels the message on the local server, but doesn't post a cancel
+ message or do anything else that affects anything other than the
+ local server.) Returns true on success, false otherwise.
+
+ INN::filesfor(*messageid*)
+ Returns the *paths* field of the history entry for the given
+ *messageid*. This will be the storage API token for the message.
+ If *messageid* isn't found in the history database, returns undef.
+
+ INN::havehist(*messageid*)
+ Looks up *messageid* in the history database and returns true if
+ it's found, false otherwise.
+
+ INN::head(*messageid*)
+ Returns the header (as a simple string) of the article identified by
+ *messageid*, or undef if it isn't found. Each line will end with a
+ simple \n (in other words, regardless of the format of article
+ storage, the returned string won't be in wire format).
+
+ INN::newsgroup(*newsgroup*)
+ Returns the status of *newsgroup* (the last field of the active file
+ entry for that newsgroup). See active(5) for a description of the
+ possible values and their meanings (the most common are "y" for an
+ unmoderated group and "m" for a moderated group). If *newsgroup*
+ isn't in the active file, returns undef.
+
+ These functions can only be used from inside the innd Perl filter;
+ they're not available in the nnrpd filter.
+
+Common Callbacks
+
+ The following additional function is available from inside filters
+ embedded in innd, and is also available from filters embedded in nnrpd
+ (see below):
+
+ INN::syslog(level, message)
+ Logs a message via syslog(2). This is quite a bit more reliable and
+ portable than trying to use Sys::Syslog from inside the Perl filter.
+ Only the first character of the level argument matters; the valid
+ letters are the first letters of ALERT, CRIT, ERR, WARNING, NOTICE,
+ INFO, and DEBUG (case-insensitive) and specify the priority at which
+ the message is logged. If a level that doesn't match any of those
+ levels is given, the default priority level is LOG_NOTICE. The
+ second argument is the message to log; it will be prefixed by
+ "filter: " and logged to syslog with facility LOG_NEWS.
+
+The nnrpd Posting Filter
+
+ Whenever Perl support is needed in nnrpd, it first loads the file
+ _PATH_PERL_FILTER_NNRPD (defined in include/paths.h, by default
+ filter_nnrpd.pl). This file must be located in the directory specified
+ by pathfilter in inn.conf (/usr/local/news/bin/filter by default). The
+ default directory for filter code can be specified at configure time by
+ giving the flag --with-filter-dir to configure.
+
+ If filter_nnrpd.pl loads successfully and defines the Perl function
+ filter_post(), Perl filtering is turned on. Otherwise, it's turned off.
+ If filter_post() ever returns a fatal error (either from Perl or from a
+ "die" in the Perl code), Perl filtering is turned off for the life of
+ that nnrpd process and any further posts made during that session won't
+ go through the filter.
+
+ While Perl filtering is on, every article received by nnrpd via the POST
+ command is passed to the filter_post() Perl function before it is passed
+ to INN (or mailed to the moderator of a moderated newsgroup). If
+ filter_post() returns an empty string (''), the article is accepted and
+ normal processing of it continues. Otherwise, the article is rejected
+ and the string returned by filter_post() is returned to the client as
+ the error message (with some exceptions; see below).
+
+ filter_post() has access to a global hash %hdr, which contains all of
+ the headers of the article. (Unlike the innd Perl filter, %hdr for the
+ nnrpd Perl filter contains *all* of the headers, not just the standard
+ ones. If any of the headers are duplicated, though, %hdr will contain
+ only the value of the last occurance of the header. nnrpd will reject
+ the article before the filter runs if any of the standard headers are
+ duplicated.) It also has access to the full body of the article in the
+ variable $body, and if the poster authenticated via AUTHINFO (or if
+ either Perl authentication or a readers.conf authentication method is
+ used and produces user information), it has access to the authenticated
+ username of the poster in the variable $user.
+
+ Unlike the innd Perl filter, the nnrpd Perl filter can modify the %hdr
+ hash. In fact, if the Perl variable $modify_headers is set to true
+ after filter_post() returns, the contents of the %hdr hash will be
+ written back to the article replacing the original headers.
+ filter_post() can therefore make any modifications it wishes to the
+ headers and those modifications will be reflected in the article as it's
+ finally posted. The article body cannot be modified in this way; any
+ changes to $body will just be ignored.
+
+ Be careful when using the ability to modify headers. filter_post() runs
+ after all the normal consistency checks on the headers and after server
+ supplied headers (like Message-ID: and Date:) are filled in. Deleting
+ required headers or modifying headers that need to follow a strict
+ format can result in nnrpd trying to post nonsense articles (which will
+ probably then be rejected by innd). If $modify_headers is set,
+ *everything* in the %hdr hash is taken to be article headers and added
+ to the article.
+
+ If filter_post() returns something other than the empty string, this
+ message is normally returned to the client as an error. There are two
+ exceptions: If the string returned begins with "DROP", the post will be
+ silently discarded and success returned to the client. If the string
+ begins with "SPOOL", success is returned to the client, but the post is
+ saved in a directory named "spam" under the directory specified by
+ pathincoming in inn.conf (in a directory named "spam/mod" if the post is
+ to a moderated group). This is intended to allow manual inspection of
+ the suspect messages; if they should be posted, they can be manually
+ moved out of the subdirectory to the directory specified by pathincoming
+ in inn.conf, where they can be posted by running "rnews -U". If you use
+ this functionality, make sure those directories exist.
+
+Changes to Perl Authentication Support for nnrpd
+
+ The old authentication functionality has been combined with the new
+ readers.conf mechanism by Erik Klavon <erik@eriq.org>; bug reports
+ should however go to inn-bugs@isc.org, not Erik.
+
+ The remainder of this section is an introduction to the new mechanism
+ (which uses the perl_auth: and perl_access: readers.conf parameters)
+ with porting/migration suggestions for people familiar with the old
+ mechanism (identifiable by the nnrpperlauth: parameter in inn.conf).
+
+ Other people should skip this section.
+
+ The perl_auth parameter allows the use of Perl to authenticate a user.
+ Scripts (like those from the old mechanism) are listed in readers.conf
+ using perl_auth in the same manner other authenticators are using auth:
+
+ perl_auth: "/path/to/script/auth1.pl"
+
+ The file given as argument to perl_auth should contain the same
+ procedures as before. The global hash %attributes remains the same,
+ except for the removal of the "type" entry which is no longer needed in
+ this modification and the addition of several new entries (port,
+ intipaddr, intport) described below. The return array now only contains
+ either two or three elements, the first of which is the NNTP return
+ code. The second is an error string which is passed to the client if the
+ error code indicates that the authentication attempt has failed. This
+ allows a specific error message to be generated by the perl script in
+ place of "Authentication failed". An optional third return element if
+ present will be used to match the connection with the users: parameter
+ in access groups and will also be the username logged. If this element
+ is absent, the username supplied by the client during authentication
+ will be used as was the previous behavior.
+
+ The perl_access parameter (described below) is also new; it allows the
+ dynamic generation of an access group for an incoming connection using a
+ Perl script. If a connection matches an auth group which has a
+ perl_access parameter, all access groups in readers.conf are ignored;
+ instead the procedure described below is used to generate an access
+ group. This concept is due to Jeffrey M. Vinocur.
+
+ The new functionality should provide all of the existing capabilities of
+ the Perl hook, in combination with the flexibility of readers.conf and
+ the use of other authentication and resolving programs. To use Perl
+ authentication code that predates the readers.conf mechanism, you would
+ need to modify the code slightly (see below for the new specification)
+ and supply a simple readers.conf file. If you don't want to modify your
+ code, the samples directory has nnrpd_auth_wrapper.pl and
+ nnrpd_access_wrapper.pl which should allow you to use your old code
+ without needing to change it.
+
+ However, before trying to use your old Perl code, you may want to
+ consider replacing it entirely with non-Perl authentication. (With
+ readers.conf and the regular authenticator and resolver programs, much
+ of what once required Perl can be done directly.) Even if the
+ functionality is not available directly, you may wish to write a new
+ authenticator or resolver (which can be done in whatever language you
+ prefer to work in).
+
+Perl Authentication Support for nnrpd
+
+ Support for authentication via Perl is provided in nnrpd by the
+ inclusion of a perl_auth: parameter in a readers.conf auth group.
+ perl_auth: works exactly like the auth: parameter in readers.conf,
+ except that it calls the script given as argument using the Perl hook
+ rather then treating it as an external program.
+
+ If the processing of readers.conf requires that a perl_auth: statement
+ be used for authentication, Perl is loaded (if it has yet to be) and the
+ file given as argument to the perl_auth: parameter is loaded as well. If
+ a Perl function auth_init() is defined by that file, it is called
+ immediately after the file is loaded. It takes no arguments and returns
+ nothing.
+
+ Provided the file loads without errors, auth_init() (if present) runs
+ without fatal errors, and a Perl function authenticate() is defined,
+ authenticate() will then be called. authenticate() takes no arguments,
+ but it has access to a global hash %attributes which contains
+ information about the connection as follows: $attributes{hostname} will
+ contain the hostname (or the IP address if it doesn't resolve) of the
+ client machine, $attributes{ipaddress} will contain its IP address (as a
+ string), $attributes{port} will contain the client port (as an integer),
+ $attributes{interface} contains the hostname of the interface the client
+ connected on, $attributes{intipaddr} contains the IP address (as a
+ string) of the interface the client connected on, $attributes{intport}
+ contains the port (as an integer) on the interface the client connected
+ on, $attributes{username} will contain the provided username and
+ $attributes{password} the password.
+
+ authenticate() should return a two or three element array. The first
+ element is the NNTP response code to return to the client, the second
+ element is an error string which is passed to the client if the response
+ code indicates that the authentication attempt has failed. An optional
+ third return element if present will be used to match the connection
+ with the users: parameter in access groups and will also be the username
+ logged. If this element is absent, the username supplied by the client
+ during authentication will be used for matching and logging.
+
+ The NNTP response code should probably be either 281 (authentication
+ successful) or 502 (authentication unsuccessful). If the code returned
+ is anything other than 281, nnrpd will print an authentication error
+ message and drop the connection and exit.
+
+ If authenticate() dies (either due to a Perl error or due to calling
+ die), or if it returns anything other than the two or three element
+ array described above, an internal error will be reported to the client,
+ the exact error will be logged to syslog, and nnrpd will drop the
+ connection and exit.
+
+Dynamic Generation of Access Groups
+
+ A Perl script may be used to dynamically generate an access group which
+ is then used to determine the access rights of the client. This occurs
+ whenever the perl_access: is specified in an auth group which has
+ successfully matched the client. Only one perl_access: statement is
+ allowed in an auth group. This parameter should not be mixed with a
+ python_access: statement in the same auth group.
+
+ When a perl_access: parameter is encountered, Perl is loaded (if it has
+ yet to be) and the file given as argument is loaded as well. Provided
+ the file loads without errors, and a Perl function access() is defined,
+ access() will then be called. access() takes no arguments, but it has
+ access to a global hash %attributes which contains information about the
+ connection as follows: $attributes{hostname} will contain the hostname
+ (or the IP address if it doesn't resolve) of the client machine,
+ $attributes{ipaddress} will contain its IP address (as a string),
+ $attributes{port} will contain the client port (as an integer),
+ $attributes{interface} contains the hostname of the interface the client
+ connected on, $attributes{intipaddr} contains the IP address (as a
+ string) of the interface the client connected on, $attributes{intport}
+ contains the port (as an integer) on the interface the client connected
+ on, $attributes{username} will contain the provided username and domain
+ (in username@domain form).
+
+ access() returns a hash, containing the desired access parameters and
+ values. Here is an untested example showing how to dynamically generate
+ a list of newsgroups based on the client's username and domain.
+
+ my %hosts = ( "example.com" => "example.*", "isc.org" => "isc.*" );
+
+ sub access {
+ %return_hash = (
+ "max_rate" => "10000",
+ "addnntppostinghost" => "true",
+ # ...
+ );
+ if( defined $attributes{username} &&
+ $attributes{username} =~ /.*@(.*)/ )
+ {
+ $return_hash{"virtualhost"} = "true";
+ $return_hash{"path"} = $1;
+ $return_hash{"newsgroups"} = $hosts{$1};
+ } else {
+ $return_hash{"read"} = "*";
+ $return_hash{"post"} = "local.*"
+ }
+ return %return_hash;
+ }
+
+ Note that both the keys and values are quoted strings. These values are
+ to be returned to a C program and must be quoted strings. For values
+ containing one or more spaces, it is not necessary to include extra
+ quotes inside the string.
+
+ While you may include the users: parameter in a dynamically generated
+ access group, some care should be taken (unless your pattern is just *
+ which is equivalent to leaving the parameter out). The group created
+ with the values returned from the Perl script is the only one considered
+ when nnrpd attempts to find an access group matching the connection. If
+ a users: parameter is included and it doesn't match the connection, then
+ the client will be denied access since there are no other access groups
+ which could match the connection.
+
+ If access() dies (either due to a Perl error or due to calling die), or
+ if it returns anything other than a hash as described above, an internal
+ error will be reported to the client, the exact error will be logged to
+ syslog, and nnrpd will drop the connection and exit.
+
+Notes on Writing Embedded Perl
+
+ All Perl evaluation is done inside an implicit eval block, so calling
+ die in Perl code will not kill the innd or nnrpd process. Neither will
+ Perl errors (such as syntax errors). However, such errors will have
+ negative effects (fatal errors in the innd or nnrpd filter will cause
+ filtering to be disabled, and fatal errors in the nnrpd authentication
+ code will cause the client connection to be terminated).
+
+ Calling exit directly, however, *will* kill the innd or nnrpd process,
+ so don't do that. Similarly, you probably don't want to call fork (or
+ any other function that results in a fork such as system,
+ IPC::Open3::open3(), or any use of backticks) since there are possibly
+ unflushed buffers that could get flushed twice, lots of open state that
+ may not get closed properly, and innumerable other potential problems.
+ In general, be aware that all Perl code is running inside a large and
+ complicated C program, and Perl code that impacts the process as a whole
+ is best avoided.
+
+ You can use print and warn inside Perl code to send output to STDOUT or
+ STDERR, but you probably shouldn't. Instead, open a log file and print
+ to it instead (or, in the innd filter, use INN::syslog() to write
+ messages via syslog like the rest of INN). If you write to STDOUT or
+ STDERR, where that data will go depends on where the filter is running;
+ inside innd, it will go to the news log or the errlog, and inside nnrpd
+ it will probably go nowhere but could go to the client. The nnrpd
+ filter takes some steps to try to keep output from going across the
+ network connection to the client (which would probably result in a very
+ confused client), but best not to take the chance.
+
+ For similar reasons, try to make your Perl code -w clean, since Perl
+ warnings are written to STDERR. (INN won't run your code under -w, but
+ better safe than sorry, and some versions of Perl have some mandatory
+ warnings you can't turn off.)
+
+ You *can* use modules in your Perl code, just like you would in an
+ ordinary Perl script. You can even use modules that dynamically load C
+ code. Just make sure that none of the modules you use go off behind
+ your back to do any of the things above that are best avoided.
+
+ Whenever you make any modifications to the Perl code, and particularly
+ before starting INN or reloading filter.perl with new code, you should
+ run perl -wc on the file. This will at least make sure you don't have
+ any glaring syntax errors. Remember, if there are errors in your code,
+ filtering will be disabled, which could mean that posts you really
+ wanted to reject will leak through and authentication of readers may be
+ totally broken.
+
+ The samples directory has example startup_innd.pl, filter_innd.pl,
+ filter_nnrpd.pl, and nnrpd_auth.pl files that contain some simplistic
+ examples. Look them over as a starting point when writing your own.
+
+Available Packages
+
+ This is an unofficial list of known filtering packages at the time of
+ publication. This is not an endorsement of these filters by the ISC or
+ the INN developers, but is included as assistance in locating packages
+ which make use of this filter mechanism.
+
+ CleanFeed Jeremy Nixon <jeremy@exit109.com>
+ <URL:http://www.exit109.com/~jeremy/news/cleanfeed.html>
+ A spam filter catching excessive multi-posting and a host of
+ other things. Uses filter_innd.pl exclusively, requires the MD5
+ Perl module. Probably the most popular and widely-used Perl
+ filter around.
+
+ Usenet II Filter Edward S. Marshall <emarshal@xnet.com>
+ <URL:http://www.xnet.com/~emarshal/inn/filter_nnrpd.pl>
+ Checks for "soundness" according to Usenet II guidelines in the
+ net.* hierarchy. Designed to use filter_nnrpd.pl.
+
+ News Gizmo Aidan Cully <aidan@panix.com>
+ <URL:http://www.panix.com/gizmo/>
+ A posting filter for helping a site enforce Usenet-II soundness,
+ and for quotaing the number of messages any user can post to
+ Usenet daily.
--- /dev/null
+INN Python Filtering and Authentication Support
+
+ This file documents INN's built-in optional support for Python article
+ filtering. It is patterned after the Perl and (now obsolete) TCL hooks
+ previously added by Bob Heiney and Christophe Wolfhugel.
+
+ For this filter to work successfully, you will need to have at least
+ Python 1.5.2 installed. You can obtain it from
+ <http://www.python.org/>.
+
+ The innd Python interface and the original Python filtering
+ documentation were written by Greg Andruk (nee Fluffy)
+ <gerglery@usa.net>. The Python authentication and authorization support
+ for nnrpd as well as the original documentation for it were written by
+ Ilya Etingof <ilya@glas.net> in December 1999.
+
+Installation
+
+ Once you have built and installed Python, you can cause INN to use it by
+ adding the --with-python switch to your "configure" command. You will
+ need to have all the headers and libraries required for embedding Python
+ into INN; they can be found in Python development packages, which
+ include header files and static libraries.
+
+ You will then be able to use Python authentication, dynamic access group
+ generation and dynamic access control support in nnrpd along with
+ filtering support in innd.
+
+ See the ctlinnd(8) manual page to learn how to enable, disable and
+ reload Python filters on a running server (especially "ctlinnd mode",
+ "ctlinnd python y|n" and "ctlinnd reload filter.python 'reason'").
+
+ Also, see the filter_innd.py, nnrpd_auth.py, nnrpd_access.py and
+ nnrpd_dynamic.py samples in your filters directory for a demonstration
+ of how to get all this working.
+
+Writing an innd Filter
+
+ Introduction
+
+ You need to create a filter_innd.py module in INN's filter directory
+ (see the *pathfilter* setting in inn.conf). A heavily-commented sample
+ is provided; you can use it as a template for your own filter. There is
+ also an INN.py module there which is not actually used by INN; it is
+ there so you can test your module interactively.
+
+ First, define a class containing the methods you want to provide to
+ innd. Methods innd will use if present are:
+
+ __init__(*self*)
+ Not explicitly called by innd, but will run whenever the filter
+ module is (re)loaded. This is a good place to initialize constants
+ or pick up where "filter_before_reload" or "filter_close" left off.
+
+ filter_before_reload(*self*)
+ This will execute any time a "ctlinnd reload all 'reason'" or
+ "ctlinnd reload filter.python 'reason'" command is issued. You can
+ use it to save statistics or reports for use after reloading.
+
+ filter_close(*self*)
+ This will run when a "ctlinnd shutdown 'reason'" command is
+ received.
+
+ filter_art(*self*, *art*)
+ *art* is a dictionary containing an article's headers and body.
+ This method is called every time innd receives an article. The
+ following can be defined:
+
+ Also-Control, Approved, Bytes, Cancel-Key, Cancel-Lock,
+ Content-Base, Content-Disposition, Content-Transfer-Encoding,
+ Content-Type, Control, Date, Date-Received, Distribution, Expires,
+ Face, Followup-To, From, In-Reply-To, Injection-Date, Injection-Info,
+ Keywords, Lines, List-ID, Message-ID, MIME-Version, Newsgroups,
+ NNTP-Posting-Date, NNTP-Posting-Host, Organization, Originator,
+ Path, Posted, Posting-Version, Received, References, Relay-Version,
+ Reply-To, Sender, Subject, Supersedes, User-Agent,
+ X-Auth, X-Canceled-By, X-Cancelled-By, X-Complaints-To, X-Face,
+ X-HTTP-UserAgent, X-HTTP-Via, X-Mailer, X-Modbot, X-Modtrace,
+ X-Newsposter, X-Newsreader, X-No-Archive, X-Original-Message-ID,
+ X-Original-Trace, X-Originating-IP, X-PGP-Key, X-PGP-Sig,
+ X-Poster-Trace, X-Postfilter, X-Proxy-User, X-Submissions-To,
+ X-Trace, X-Usenet-Provider, Xref, __BODY__, __LINES__.
+
+ Note that all the above values are as they arrived, not modified by
+ your INN (especially, the Xref: header, if present, is the one of
+ the remote site which sent you the article, and not yours).
+
+ These values will be buffer objects holding the contents of the same
+ named article headers, except for the special "__BODY__" and
+ "__LINES__" items. Items not present in the article will contain
+ "None".
+
+ "art('__BODY__')" is a buffer object containing the article's entire
+ body, and "art('__LINES__')" is an int holding innd's reckoning of
+ the number of lines in the article. All the other elements will be
+ buffers with the contents of the same-named article headers.
+
+ The Newsgroups: header of the article is accessible inside the
+ Python filter as "art['Newsgroups']".
+
+ If you want to accept an article, return "None" or an empty string.
+ To reject, return a non-empty string. The rejection strings will be
+ shown to local clients and your peers, so keep that in mind when
+ phrasing your rejection responses.
+
+ filter_messageid(*self*, *msgid*)
+ *msgid* is a buffer object containing the ID of an article being
+ offered by IHAVE or CHECK. Like with "filter_art", the message will
+ be refused if you return a non-empty string. If you use this
+ feature, keep it light because it is called at a rather busy place
+ in innd's main loop. Also, do not rely on this function alone to
+ reject by ID; you should repeat the tests in "filter_art" to catch
+ articles sent with TAKETHIS but no CHECK.
+
+ filter_mode(*self*, *oldmode*, *newmode*, *reason*)
+ When the operator issues a ctlinnd "pause", "throttle", "go",
+ "shutdown" or "xexec" command, this function can be used to do
+ something sensible in accordance with the state change. Stamp a log
+ file, save your state on throttle, etc. *oldmode* and *newmode*
+ will be strings containing one of the values in ("running",
+ "throttled", "paused", "shutdown", "unknown"). *oldmode* is the
+ state innd was in before ctlinnd was run, *newmode* is the state
+ innd will be in after the command finishes. *reason* is the comment
+ string provided on the ctlinnd command line.
+
+ How to Use these Methods with innd
+
+ To register your methods with innd, you need to create an instance of
+ your class, import the built-in INN module, and pass the instance to
+ "INN.set_filter_hook". For example:
+
+ class Filter:
+ def filter_art(self, art):
+ ...
+ blah blah
+ ...
+
+ def filter_messageid(self, id):
+ ...
+ yadda yadda
+ ...
+
+ import INN
+ myfilter = Filter()
+ INN.set_filter_hook(myfilter)
+
+ When writing and testing your Python filter, don't be afraid to make use
+ of "try:"/"except:" and the provided "INN.syslog" function. stdout and
+ stderr will be disabled, so your filter will die silently otherwise.
+
+ Also, remember to try importing your module interactively before loading
+ it, to ensure there are no obvious errors. One typo can ruin your whole
+ filter. A dummy INN.py module is provided to facilitate testing outside
+ the server. To test, change into your filter directory and use a
+ command like:
+
+ python -ic 'import INN, filter_innd'
+
+ You can define as many or few of the methods listed above as you want in
+ your filter class (it is fine to define more methods for your own use;
+ innd will not be using them but your filter can). If you *do* define
+ the above methods, GET THE PARAMETER COUNTS RIGHT. There are checks in
+ innd to see whether the methods exist and are callable, but if you
+ define one and get the parameter counts wrong, innd WILL DIE. You have
+ been warned. Be careful with your return values, too. The "filter_art"
+ and "filter_messageid" methods have to return strings, or "None". If
+ you return something like an int, innd will *not* be happy.
+
+ A Note regarding Buffer Objects
+
+ Buffer objects are cousins of strings, new in Python 1.5.2. Using
+ buffer objects may take some getting used to, but we can create buffers
+ much faster and with less memory than strings.
+
+ For most of the operations you will perform in filters (like
+ "re.search", "string.find", "md5.digest") you can treat buffers just
+ like strings, but there are a few important differences you should know
+ about:
+
+ # Make a string and two buffers.
+ s = "abc"
+ b = buffer("def")
+ bs = buffer("abc")
+
+ s == bs # - This is false because the types differ...
+ buffer(s) == bs # - ...but this is true, the types now agree.
+ s == str(bs) # - This is also true, but buffer() is faster.
+ s[:2] == bs[:2] # - True. Buffer slices are strings.
+
+ # While most string methods will take either a buffer or a string,
+ # string.join (in the string module) insists on using only strings.
+ import string
+ string.join([str(b), s], '.') # Returns 'def.abc'.
+ '.'.join([str(b), s]) # Returns 'def.abc' too.
+ '.'.join([b, s]) # This raises a TypeError.
+
+ e = s + b # This raises a TypeError, but...
+
+ # ...these two both return the string 'abcdef'. The first one
+ # is faster -- choose buffer() over str() whenever you can.
+ e = buffer(s) + b
+ f = s + str(b)
+
+ g = b + '>' # This is legal, returns the string 'def>'.
+
+ Functions Supplied by the Built-in innd Module
+
+ Besides "INN.set_filter_hook" which is used to register your methods
+ with innd as it has already been explained above, the following
+ functions are available from Python scripts:
+
+ addhist(*message-id*)
+ article(*message-id*)
+ cancel(*message-id*)
+ havehist(*message-id*)
+ hashstring(*string*)
+ head(*message-id*)
+ newsgroup(*groupname*)
+ syslog(*level*, *message*)
+
+ Therefore, not only can innd use Python, but your filter can use some of
+ innd's features too. Here is some sample Python code to show what you
+ get with the previously listed functions.
+
+ import INN
+
+ # Python's native syslog module isn't compiled in by default,
+ # so the INN module provides a replacement. The first parameter
+ # tells the Unix syslogger what severity to use; you can
+ # abbreviate down to one letter and it's case insensitive.
+ # Available levels are (in increasing levels of seriousness)
+ # Debug, Info, Notice, Warning, Err, Crit, and Alert. (If you
+ # provide any other string, it will be defaulted to Notice.) The
+ # second parameter is the message text. The syslog entries will
+ # go to the same log files innd itself uses, with a 'python:'
+ # prefix.
+ syslog('warning', 'I will not buy this record. It is scratched.')
+ animals = 'eels'
+ vehicle = 'hovercraft'
+ syslog('N', 'My %s is full of %s.' % (vehicle, animals))
+
+ # Let's cancel an article! This only deletes the message on the
+ # local server; it doesn't send out a control message or anything
+ # scary like that. Returns 1 if successful, else 0.
+ if INN.cancel('<meow$123.456@solvangpastries.edu>'):
+ cancelled = "yup"
+ else:
+ cancelled = "nope"
+
+ # Check if a given message is in history. This doesn't
+ # necessarily mean the article is on your spool; cancelled and
+ # expired articles hang around in history for a while, and
+ # rejected articles will be in there if you have enabled
+ # remembertrash in inn.conf. Returns 1 if found, else 0.
+ if INN.havehist('<z456$789.abc@isc.org>'):
+ comment = "*yawn* I've already seen this article."
+ else:
+ comment = 'Mmm, fresh news.'
+
+ # Here we are running a local spam filter, so why eat all those
+ # cancels? We can add fake entries to history so they'll get
+ # refused. Returns 1 on success, 0 on failure.
+ cancelled_id = buffer('<meow$123.456@isc.org>')
+ if INN.addhist("<cancel." + cancelled_id[1:]):
+ thought = "Eat my dust, roadkill!"
+ else:
+ thought = "Darn, someone beat me to it."
+
+ # We can look at the header or all of an article already on spool,
+ # too. Might be useful for long-memory despamming or
+ # authentication things. Each is returned (if present) as a
+ # string object; otherwise you'll end up with an empty string.
+ artbody = INN.article('<foo$bar.baz@bungmunch.edu>')
+ artheader = INN.head('<foo$bar.baz@bungmunch.edu>')
+
+ # As we can compute a hash digest for a string, we can obtain one
+ # for artbody. It might be of help to detect spam.
+ digest = INN.hashstring(artbody)
+
+ # Finally, do you want to see if a given newsgroup is moderated or
+ # whatever? INN.newsgroup returns the last field of a group's
+ # entry in active as a string.
+ froupflag = INN.newsgroup('alt.fan.karl-malden.nose')
+ if froupflag == '':
+ moderated = 'no such newsgroup'
+ elif froupflag == 'y':
+ moderated = "nope"
+ elif froupflag == 'm':
+ moderated = "yep"
+ else:
+ moderated = "something else"
+
+Writing an nnrpd Filter
+
+ Changes to Python Authentication and Access Control Support for nnrpd
+
+ The old authentication and access control functionality has been
+ combined with the new readers.conf mechanism by Erik Klavon
+ <erik@eriq.org>; bug reports should however go to <inn-bugs@isc.org>,
+ not Erik.
+
+ The remainder of this section is an introduction to the new mechanism
+ (which uses the *python_auth*, *python_access*, and *python_dynamic*
+ readers.conf parameters) with porting/migration suggestions for people
+ familiar with the old mechanism (identifiable by the now deprecated
+ *nnrpperlauth* parameter in inn.conf).
+
+ Other people should skip this section.
+
+ The *python_auth* parameter allows the use of Python to authenticate a
+ user. Authentication scripts (like those from the old mechanism) are
+ listed in readers.conf using *python_auth* in the same manner other
+ authenticators are using *auth*:
+
+ python_auth: "nnrpd_auth"
+
+ It uses the script named nnrpd_auth.py (note that ".py" is not present
+ in the *python_auth* value).
+
+ Scripts should be placed as before in the filter directory (see the
+ *pathfilter* setting in inn.conf). The new hook method "authen_init"
+ takes no arguments and its return value is ignored; its purpose is to
+ provide a means for authentication specific initialization. The hook
+ method "authen_close" is the more specific analogue to the old "close"
+ method. These two method hooks are not required, contrary to
+ "authenticate", the main method.
+
+ The argument dictionary passed to "authenticate" remains the same,
+ except for the removal of the *type* entry which is no longer needed in
+ this modification and the addition of several new entries (*port*,
+ *intipaddr*, *intport*) described below. The return tuple now only
+ contains either two or three elements, the first of which is the NNTP
+ response code. The second is an error string which is passed to the
+ client if the response code indicates that the authentication attempt
+ has failed. This allows a specific error message to be generated by the
+ Python script in place of the generic message "Authentication failed".
+ An optional third return element, if present, will be used to match the
+ connection with the *user* parameter in access groups and will also be
+ the username logged. If this element is absent, the username supplied
+ by the client during authentication will be used, as was the previous
+ behaviour.
+
+ The *python_access* parameter (described below) is new; it allows the
+ dynamic generation of an access group of an incoming connection using a
+ Python script. If a connection matches an auth group which has a
+ *python_access* parameter, all access groups in readers.conf are
+ ignored; instead the procedure described below is used to generate an
+ access group. This concept is due to Jeffrey M. Vinocur and you can add
+ this line to readers.conf in order to use the nnrpd_access.py Python
+ script in *pathfilter*:
+
+ python_access: "nnrpd_access"
+
+ In the old implementation, the authorization method allowed for access
+ control on a per-group basis. That functionality is preserved in the
+ new implementation by the inclusion of the *python_dynamic* parameter in
+ readers.conf. The only change is the corresponding method name of
+ "dynamic" as opposed to "authorize". Additionally, the associated
+ optional housekeeping methods "dynamic_init" and "dynamic_close" may be
+ implemented if needed. In order to use nnrpd_dynamic.py in
+ *pathfilter*, you can add this line to readers.conf:
+
+ python_dynamic: "nnrpd_dynamic"
+
+ This new implementation should provide all of the previous capabilities
+ of the Python hooks, in combination with the flexibility of readers.conf
+ and the use of other authentication and resolving programs (including
+ the Perl hooks!). To use Python code that predates the new mechanism,
+ you would need to modify the code slightly (see below for the new
+ specification) and supply a simple readers.conf file. If you do not
+ want to modify your code, the sample directory has
+ nnrpd_auth_wrapper.py, nnrpd_access_wrapper.py and
+ nnrpd_dynamic_wrapper.py which should allow you to use your old code
+ without needing to change it.
+
+ However, before trying to use your old Python code, you may want to
+ consider replacing it entirely with non-Python authentication. (With
+ readers.conf and the regular authenticator and resolver programs, much
+ of what once required Python can be done directly.) Even if the
+ functionality is not available directly, you may wish to write a new
+ authenticator or resolver (which can be done in whatever language you
+ prefer).
+
+ Python Authentication Support for nnrpd
+
+ Support for authentication via Python is provided in nnrpd by the
+ inclusion of a *python_auth* parameter in a readers.conf auth group.
+ *python_auth* works exactly like the *auth* parameter in readers.conf,
+ except that it calls the script given as argument using the Python hook
+ rather then treating it as an external program. Multiple, mixed use of
+ *python_auth* with other *auth* statements including *perl_auth* is
+ permitted. Each *auth* statement will be tried in the order they appear
+ in the auth group until either one succeeds or all are exhausted.
+
+ If the processing of readers.conf requires that a *python_auth*
+ statement be used for authentication, Python is loaded (if it has yet to
+ be) and the file given as argument to the *python_auth* parameter is
+ loaded as well (do not include the ".py" extension of this file in the
+ value of *python_auth*). If a Python object with a method "authen_init"
+ is hooked in during the loading of that file, then that method is called
+ immediately after the file is loaded. If no errors have occurred, the
+ method "authenticate" is called. Depending on the NNTP response code
+ returned by "authenticate", the authentication hook either succeeds or
+ fails, after which the processing of the auth group continues as usual.
+ When the connection with the client is closed, the method "authen_close"
+ is called if it exists.
+
+ Dynamic Generation of Access Groups
+
+ A Python script may be used to dynamically generate an access group
+ which is then used to determine the access rights of the client. This
+ occurs whenever the *python_access* parameter is specified in an auth
+ group which has successfully matched the client. Only one
+ *python_access* statement is allowed in an auth group. This parameter
+ should not be mixed with a *perl_access* statement in the same auth
+ group.
+
+ When a *python_access* parameter is encountered, Python is loaded (if it
+ has yet to be) and the file given as argument is loaded as well (do not
+ include the ".py" extension of this file in the value of
+ *python_access*). If a Python object with a method "access_init" is
+ hooked in during the loading of that file, then that method is called
+ immediately after the file is loaded. If no errors have occurred, the
+ method "access" is called. The dictionary returned by "access" is used
+ to generate an access group that is then used to determine the access
+ rights of the client. When the connection with the client is closed,
+ the method "access_close" is called, if it exists.
+
+ While you may include the *users* parameter in a dynamically generated
+ access group, some care should be taken (unless your pattern is just "*"
+ which is equivalent to leaving the parameter out). The group created
+ with the values returned from the Python script is the only one
+ considered when nnrpd attempts to find an access group matching the
+ connection. If a *users* parameter is included and it does not match
+ the connection, then the client will be denied access since there are no
+ other access groups which could match the connection.
+
+ Dynamic Access Control
+
+ If you need to have access control rules applied immediately without
+ having to restart all the nnrpd processes, you may apply access control
+ on a per newsgroup basis using the Python dynamic hooks (as opposed to
+ readers.conf, which does the same on per user basis). These hooks are
+ activated through the inclusion of the *python_dynamic* parameter in a
+ readers.conf auth group. Only one *python_dynamic* statement is allowed
+ in an auth group.
+
+ When a *python_dynamic* parameter is encountered, Python is loaded (if
+ it has yet to be) and the file given as argument is loaded as well (do
+ not include the ".py" extension of this file in the value of
+ *python_dynamic*). If a Python object with a method "dynamic_init" is
+ hooked in during the loading of that file, then that method is called
+ immediately after the file is loaded. Every time a reader asks nnrpd to
+ read or post an article, the Python method "dynamic" is invoked before
+ proceeding with the requested operation. Based on the value returned by
+ "dynamic", the operation is either permitted or denied. When the
+ connection with the client is closed, the method "access_close" is
+ called if it exists.
+
+ Writing a Python 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 depending on which hooks you want to use.
+
+ Note that you will have to use different Python scripts for
+ authentication and access: the values of *python_auth*, *python_access*
+ and *python_dynamic* have to be distinct for your scripts to work.
+
+ The following methods are known to nnrpd:
+
+ __init__(*self*)
+ Not explicitly called by nnrpd, but will run whenever the auth
+ module is loaded. Use this method to initialize any general
+ variables or open a common database connection. This method may be
+ omitted.
+
+ authen_init(*self*)
+ Initialization function specific to authentication. This method may
+ be omitted.
+
+ authenticate(*self*, *attributes*)
+ Called when a *python_auth* statement is reached in the processing
+ of readers.conf. Connection attributes are passed in the
+ *attributes* dictionary. Returns a response code, an error string,
+ and an optional string to be used in place of the client-supplied
+ username (both for logging and for matching the connection with an
+ access group).
+
+ authen_close(*self*)
+ This method is invoked on nnrpd termination. You can use it to save
+ state information or close a database connection. This method may
+ be omitted.
+
+ access_init(*self*)
+ Initialization function specific to generation of an access group.
+ This method may be omitted.
+
+ access(*self*, *attributes*)
+ Called when a *python_access* statement is reached in the processing
+ of readers.conf. Connection attributes are passed in the
+ *attributes* dictionary. Returns a dictionary of values
+ representing statements to be included in an access group.
+
+ access_close(*self*)
+ This method is invoked on nnrpd termination. You can use it to save
+ state information or close a database connection. This method may
+ be omitted.
+
+ dynamic_init(*self*)
+ Initialization function specific to dynamic access control. This
+ method may be omitted.
+
+ dynamic(*self*, *attributes*)
+ Called when a client requests a newsgroup, an article or attempts to
+ post. Connection attributes are passed in the *attributes*
+ dictionary. Returns "None" to grant access, or a non-empty string
+ (which will be reported back to the client) otherwise.
+
+ dynamic_close(*self*)
+ This method is invoked on nnrpd termination. You can use it to save
+ state information or close a database connection. This method may
+ be omitted.
+
+ The *attributes* Dictionary
+
+ The keys and associated values of the *attributes* dictionary are
+ described below.
+
+ *type*
+ "read" or "post" values specify the authentication type; only valid
+ for the "dynamic" method.
+
+ *hostname*
+ It is the resolved hostname (or IP address if resolution fails) of
+ the connected reader.
+
+ *ipaddress*
+ The IP address of the connected reader.
+
+ *port*
+ The port of the connected reader.
+
+ *interface*
+ The hostname of the local endpoint of the NNTP connection.
+
+ *intipaddr*
+ The IP address of the local endpoint of the NNTP connection.
+
+ *intport*
+ The port of the local endpoint of the NNTP connection.
+
+ *user*
+ The username as passed with AUTHINFO command, or "None" if not
+ applicable.
+
+ *pass*
+ The password as passed with AUTHINFO command, or "None" if not
+ applicable.
+
+ *newsgroup*
+ The name of the newsgroup to which the reader requests read or post
+ access; only valid for the "dynamic" method.
+
+ All the above values are buffer objects (see the notes above on what
+ buffer objects are).
+
+ How to Use these Methods with nnrpd
+
+ 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 authen_init(self):
+ ...
+ blah blah
+ ...
+
+ def authenticate(self, attributes):
+ ...
+ yadda yadda
+ ...
+
+ import nnrpd
+ myauth = AUTH()
+ nnrpd.set_auth_hook(myauth)
+
+ When writing and testing your Python filter, don't be afraid to make use
+ of "try:"/"except:" and the provided "nnrpd.syslog" function. stdout
+ and stderr will be disabled, so your filter will die silently otherwise.
+
+ Also, remember to try importing your module interactively before loading
+ it, to ensure there are no obvious errors. One typo can ruin your whole
+ filter. A dummy nnrpd.py module is provided to facilitate testing
+ outside the server. It 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. To test, change into your filter
+ directory and use a command like:
+
+ python -ic 'import nnrpd, nnrpd_auth'
+
+ Functions Supplied by the Built-in nnrpd Module
+
+ Besides "nnrpd.set_auth_hook" used to pass a reference to the instance
+ of authentication and authorization class to nnrpd, the nnrpd built-in
+ module exports the following function:
+
+ syslog(*level*, *message*)
+ It is intended to be a replacement for a Python native syslog. It
+ works like "INN.syslog", seen above.
+
+ $Id: hook-python 7926 2008-06-29 08:27:41Z iulius $
+
--- /dev/null
+NOTE: The Tcl support described in this file is disabled. The code is
+all still there, but you have to define DO_TCL manually while compiling to
+enable it. Compiling in Tcl filtering was causing random innd segfaults
+even if no Tcl filters were defined, so it's been turned off to prevent
+confusion.
+
+The Tcl code will be removed in the next major release of INN since no one
+appears to be using it and the code is unmaintained and has no champion.
+If you want to resurrect it, it may be better to start from scratch, since
+a lot has changed about INN since the filters were originally written and
+the Perl and Python filters have far more capabilities.
+
+
+Note, you need tcl 7.4. Rumour has it that 7.5 won't work.
+---------------------------------------------------------------------------
+Subject: TCL-based Filtering for INN 1.5
+Date: Mon, 07 Feb 94 12:36:47 -0800
+From: Bob Heiney <heiney@pa.dec.com>
+
+
+Several times in the past few months, a site or two has started posting
+the same article over and over again, but with a different message id.
+Usually this is caused by broken software (e.g. mail <-> news gateways,
+which many have written, but few have written correctly).
+Occasionally, however, the reposting is intentional. A recent example
+would be the "Global Alert: Jesus Is Coming" message which was posted
+to over 2200 newsgroups (each copy with its own message id).
+
+I expect this to happen more often as the Internet continues its explosive
+growth. Although my site (decwrl) usually has enough excess capacity to
+weather these problems, many other sites cannot. One problem on
+comp.sys.sgi.misc several months ago spewed 40MB of duplicate articles
+before the offending sites were fixed, and this overflowed the spool at
+many sites. Even for sites with lots of resources, there's still no need
+to propagate erroneous or malicious duplicates.
+
+I wanted a way to protect my site that was highly specific, flexible, and
+quick.
+
+Examination of duplicated articles showed that although the message ids
+were different, it was usually easy for a news admin to come up with a
+few rules based on the headers of the article that could be used to
+differentiate the duplicates from other articles. (E.g. from
+John.Doe@foo.com to comp.sys.sgi.misc with 'foobar' in the subject".)
+I concluded that modifying innd to let me say "kill things that look
+like _this_" would solve my problem.
+
+I also wanted to allow enough flexibilty in the design that I could
+later work on automatic detection and elimination of excessive
+duplicates (using a body checksum instead of headers).
+
+Since I needed a fairly powerful language to do all this, and since the
+world doesn't need yet another special language, my solution was to add TCL
+support to INN. I then modified "ARTpost" to call a TCL procedure which
+could then accept or reject the article. The TCL code has access to an
+associative array called "Headers", which contains all of the articles
+headers. The TCL code may also call a 32-bit article-body checksum
+procedure (this is to aid in future automatic detection of duplicates).
+
+Here's what a sample TCL filter procedure looks like:
+
+proc filter_news {} {
+ global o Headers
+ set sum [checksum_article]
+ puts $o "$Headers(Message-ID) $sum"
+ set newsgroups [split $Headers(Newsgroups) ,]
+ foreach i $newsgroups {
+ if {$i=="alt.test" && [string match "*heiney@pa.dec.com*" $Headers(From)]} {
+ return "dont like alt.test from heiney"
+ }
+ }
+ return "accept"
+}
+
+The above TCL code does a few things. First it computes a 32-bit
+checksum and writes it and the message ID to a file. It then rejects
+articles from me to alt.test.
+
+The work I've done is totally integrated into the INN build and runtime
+environments. For example, to turn filtering off, you'd just type
+
+ ctlinnd filter n
+
+To reload the TCL code that does the filtering, you just say
+
+ ctlinnd reload filter.tcl 'your comment here'
+
+(You may specify TCL callbacks to be executed right before and/or right
+after reloading, in case your filter is doing fancy stuff.) See the
+ctlinnd man page for more info.
+
+Filtering capability that's this powerful can be used for many
+purposes, some benign and useful (excessive duplicate detections,
+on-the-fly statistics), others abusive. I would ask that news admins
+think carefully about any filtering they do.
+
+/Bob
+
+
--- /dev/null
+## $Id: Makefile 7458 2005-12-12 00:25:05Z eagle $
+
+include ../../Makefile.global
+
+top = ../..
+
+## Edit these if you need to.
+MANFLAGS = -c $(OWNER) -m 0444 -B .OLD
+
+SEC1 = convdate.1 fastrm.1 getlist.1 grephistory.1 inews.1 innconfval.1 \
+ innfeed.1 innmail.1 nntpget.1 pgpverify.1 pullnews.1 rnews.1 \
+ shlock.1 shrinkfile.1 simpleftp.1 sm.1 startinnfeed.1
+
+SEC3 = clientlib.3 dbz.3 inndcomm.3 libauth.3 libinn.3 libinnhist.3 \
+ libstorage.3 list.3 parsedate.3 qio.3 tst.3 uwildmat.3
+
+SEC5 = active.5 active.times.5 buffindexed.conf.5 control.ctl.5 \
+ cycbuff.conf.5 distrib.pats.5 expire.ctl.5 history.5 incoming.conf.5 \
+ inn.conf.5 innfeed.conf.5 innwatch.ctl.5 moderators.5 motd.news.5 \
+ newsfeeds.5 nnrpd.track.5 newslog.5 nntpsend.ctl.5 ovdb.5 \
+ overview.fmt.5 passwd.nntp.5 radius.conf.5 readers.conf.5 sasl.conf.5 \
+ storage.conf.5 subscriptions.5
+
+SEC8 = actsync.8 actsyncd.8 archive.8 auth_smb.8 batcher.8 buffchan.8 \
+ ckpasswd.8 cnfsheadconf.8 cnfsstat.8 controlchan.8 ctlinnd.8 \
+ cvtbatch.8 domain.8 expire.8 expireover.8 expirerm.8 filechan.8 \
+ ident.8 inncheck.8 innd.8 inndf.8 inndstart.8 innreport.8 innstat.8 \
+ innupgrade.8 innwatch.8 innxbatch.8 innxmit.8 mailpost.8 makedbz.8 \
+ makehistory.8 mod-active.8 news.daily.8 news2mail.8 ninpaths.8 \
+ nnrpd.8 nntpsend.8 ovdb_init.8 ovdb_monitor.8 ovdb_server.8 \
+ ovdb_stat.8 overchan.8 perl-nocem.8 prunehistory.8 radius.8 \
+ rc.news.8 scanlogs.8 send-nntp.8 send-uucp.8 sendinpaths.8 \
+ tally.control.8 tdx-util.8 writelog.8
+
+COPY = $(SHELL) ./putman.sh $(MANPAGESTYLE) "$(MANFLAGS)"
+
+all:
+clobber clean distclean:
+tags ctags:
+profiled:
+
+install: install-man1 install-man3 install-man5 install-man8
+
+install-man1:
+ for M in $(SEC1) ; do \
+ $(COPY) $$M $D$(MAN1)/$$M ; \
+ done
+
+install-man3:
+ for M in $(SEC3) ; do \
+ $(COPY) $$M $D$(MAN3)/$$M ; \
+ done
+
+install-man5:
+ for M in $(SEC5) ; do \
+ $(COPY) $$M $D$(MAN5)/$$M ; \
+ done
+
+# auth_krb5 is conditionally compiled, so handle it specially.
+install-man8:
+ for M in $(SEC8) ; do \
+ $(COPY) $$M $D$(MAN8)/$$M ; \
+ done
+ if [ x"$(KRB5_AUTH)" != x ] ; then \
+ $(COPY) auth_krb5.8 $D$(MAN8)/auth_krb5.8 ; \
+ fi
--- /dev/null
+.\" Automatically generated by Pod::Man v1.37, Pod::Parser v1.32
+.\"
+.\" Standard preamble:
+.\" ========================================================================
+.de Sh \" Subsection heading
+.br
+.if t .Sp
+.ne 5
+.PP
+\fB\\$1\fR
+.PP
+..
+.de Sp \" Vertical space (when we can't use .PP)
+.if t .sp .5v
+.if n .sp
+..
+.de Vb \" Begin verbatim text
+.ft CW
+.nf
+.ne \\$1
+..
+.de Ve \" End verbatim text
+.ft R
+.fi
+..
+.\" Set up some character translations and predefined strings. \*(-- will
+.\" give an unbreakable dash, \*(PI will give pi, \*(L" will give a left
+.\" double quote, and \*(R" will give a right double quote. \*(C+ will
+.\" give a nicer C++. Capital omega is used to do unbreakable dashes and
+.\" therefore won't be available. \*(C` and \*(C' expand to `' in nroff,
+.\" nothing in troff, for use with C<>.
+.tr \(*W-
+.ds C+ C\v'-.1v'\h'-1p'\s-2+\h'-1p'+\s0\v'.1v'\h'-1p'
+.ie n \{\
+. ds -- \(*W-
+. ds PI pi
+. if (\n(.H=4u)&(1m=24u) .ds -- \(*W\h'-12u'\(*W\h'-12u'-\" diablo 10 pitch
+. if (\n(.H=4u)&(1m=20u) .ds -- \(*W\h'-12u'\(*W\h'-8u'-\" diablo 12 pitch
+. ds L" ""
+. ds R" ""
+. ds C` ""
+. ds C' ""
+'br\}
+.el\{\
+. ds -- \|\(em\|
+. ds PI \(*p
+. ds L" ``
+. ds R" ''
+'br\}
+.\"
+.\" If the F register is turned on, we'll generate index entries on stderr for
+.\" titles (.TH), headers (.SH), subsections (.Sh), items (.Ip), and index
+.\" entries marked with X<> in POD. Of course, you'll have to process the
+.\" output yourself in some meaningful fashion.
+.if \nF \{\
+. de IX
+. tm Index:\\$1\t\\n%\t"\\$2"
+..
+. nr % 0
+. rr F
+.\}
+.\"
+.\" For nroff, turn off justification. Always turn off hyphenation; it makes
+.\" way too many mistakes in technical documents.
+.hy 0
+.if n .na
+.\"
+.\" Accent mark definitions (@(#)ms.acc 1.5 88/02/08 SMI; from UCB 4.2).
+.\" Fear. Run. Save yourself. No user-serviceable parts.
+. \" fudge factors for nroff and troff
+.if n \{\
+. ds #H 0
+. ds #V .8m
+. ds #F .3m
+. ds #[ \f1
+. ds #] \fP
+.\}
+.if t \{\
+. ds #H ((1u-(\\\\n(.fu%2u))*.13m)
+. ds #V .6m
+. ds #F 0
+. ds #[ \&
+. ds #] \&
+.\}
+. \" simple accents for nroff and troff
+.if n \{\
+. ds ' \&
+. ds ` \&
+. ds ^ \&
+. ds , \&
+. ds ~ ~
+. ds /
+.\}
+.if t \{\
+. ds ' \\k:\h'-(\\n(.wu*8/10-\*(#H)'\'\h"|\\n:u"
+. ds ` \\k:\h'-(\\n(.wu*8/10-\*(#H)'\`\h'|\\n:u'
+. ds ^ \\k:\h'-(\\n(.wu*10/11-\*(#H)'^\h'|\\n:u'
+. ds , \\k:\h'-(\\n(.wu*8/10)',\h'|\\n:u'
+. ds ~ \\k:\h'-(\\n(.wu-\*(#H-.1m)'~\h'|\\n:u'
+. ds / \\k:\h'-(\\n(.wu*8/10-\*(#H)'\z\(sl\h'|\\n:u'
+.\}
+. \" troff and (daisy-wheel) nroff accents
+.ds : \\k:\h'-(\\n(.wu*8/10-\*(#H+.1m+\*(#F)'\v'-\*(#V'\z.\h'.2m+\*(#F'.\h'|\\n:u'\v'\*(#V'
+.ds 8 \h'\*(#H'\(*b\h'-\*(#H'
+.ds o \\k:\h'-(\\n(.wu+\w'\(de'u-\*(#H)/2u'\v'-.3n'\*(#[\z\(de\v'.3n'\h'|\\n:u'\*(#]
+.ds d- \h'\*(#H'\(pd\h'-\w'~'u'\v'-.25m'\f2\(hy\fP\v'.25m'\h'-\*(#H'
+.ds D- D\\k:\h'-\w'D'u'\v'-.11m'\z\(hy\v'.11m'\h'|\\n:u'
+.ds th \*(#[\v'.3m'\s+1I\s-1\v'-.3m'\h'-(\w'I'u*2/3)'\s-1o\s+1\*(#]
+.ds Th \*(#[\s+2I\s-2\h'-\w'I'u*3/5'\v'-.3m'o\v'.3m'\*(#]
+.ds ae a\h'-(\w'a'u*4/10)'e
+.ds Ae A\h'-(\w'A'u*4/10)'E
+. \" corrections for vroff
+.if v .ds ~ \\k:\h'-(\\n(.wu*9/10-\*(#H)'\s-2\u~\d\s+2\h'|\\n:u'
+.if v .ds ^ \\k:\h'-(\\n(.wu*10/11-\*(#H)'\v'-.4m'^\v'.4m'\h'|\\n:u'
+. \" for low resolution devices (crt and lpr)
+.if \n(.H>23 .if \n(.V>19 \
+\{\
+. ds : e
+. ds 8 ss
+. ds o a
+. ds d- d\h'-1'\(ga
+. ds D- D\h'-1'\(hy
+. ds th \o'bp'
+. ds Th \o'LP'
+. ds ae ae
+. ds Ae AE
+.\}
+.rm #[ #] #H #V #F C
+.\" ========================================================================
+.\"
+.IX Title "ACTIVE 5"
+.TH ACTIVE 5 "2008-04-06" "INN 2.4.5" "InterNetNews Documentation"
+.SH "NAME"
+active \- List of newsgroups carried by the server
+.SH "DESCRIPTION"
+.IX Header "DESCRIPTION"
+The file \fIpathdb\fR/active lists the newsgroups carried by \s-1INN\s0. This file
+is generally maintained using \fIctlinnd\fR\|(8) to create and remove groups, or
+by letting \fIcontrolchan\fR\|(8) do so on the basis of received control messages.
+This file should not be edited directly without throttling \fBinnd\fR, and
+must be reloaded using \fBctlinnd\fR before \fBinnd\fR is unthrottled. Editing
+it directly even with those precautions may make it inconsistent with the
+overview database and won't update \fIactive.times\fR, so \fBctlinnd\fR should
+be used to make modifications whenever possible.
+.PP
+Each newsgroup should be listed only once. Each line specifies one group.
+The order of groups does not matter. Within each newsgroup, received
+articles for that group are assigned monotonically increasing numbers as
+unique names. If an article is posted to newsgroups not mentioned in this
+file, those newsgroups are ignored.
+.PP
+If none of the newsgroups listed in the Newsgroups header of an article
+are present in this file, the article is either rejected (if \fIwanttrash\fR
+is false in \fIinn.conf\fR), or is filed into the newsgroup \f(CW\*(C`junk\*(C'\fR and only
+propagated to sites that receive the \f(CW\*(C`junk\*(C'\fR newsgroup (if \fIwanttrash\fR is
+true).
+.PP
+Each line of this file consists of four fields separated by a space:
+.PP
+.Vb 1
+\& <name> <high> <low> <flag>
+.Ve
+.PP
+The first field is the name of the newsgroup. The newsgroup \f(CW\*(C`junk\*(C'\fR is
+special, as mentioned above. The newsgroup \f(CW\*(C`control\*(C'\fR and any newsgroups
+beginning with \f(CW\*(C`control.\*(C'\fR are also special; control messages are filed
+into a control.* newsgroup named after the type of control message if that
+group exists, and otherwise are filed into the newsgroup \f(CW\*(C`control\*(C'\fR
+(without regard to what newsgroups are listed in the Newsgroups header).
+If \fImergetogroups\fR is set to true in \fIinn.conf\fR, newsgroups that begin
+with \f(CW\*(C`to.\*(C'\fR are also treated specially; see \fIinnd\fR\|(8).
+.PP
+The second field is the highest article number that has been used in that
+newsgroup. The third field is the lowest article number in the group;
+this number is not guaranteed to be accurate, and should only be taken to
+be a hint. It is normally updated nightly as part of the expire process;
+see \fInews.daily\fR\|(8) and look for \f(CW\*(C`lowmark\*(C'\fR or \f(CW\*(C`renumber\*(C'\fR for more details.
+Note that because of article cancellations, there may be gaps in the
+numbering sequence. If the lowest article number is greater then the
+highest article number, then there are no articles in the newsgroup. In
+order to make it possible to update an entry in-place without rewriting
+the entire file, the second and third fields are padded out with leading
+zeros to make them a fixed width.
+.PP
+The fourth field contains one of the following flags:
+.PP
+.Vb 6
+\& y Local postings are allowed.
+\& m The group is moderated and all postings must be approved.
+\& n No local postings are allowed, only articles from peers.
+\& j Articles are filed in the junk group instead.
+\& x No local postings and ignored for articles from peers.
+\& =foo.bar Articles are filed in the group foo.bar instead.
+.Ve
+.PP
+If a newsgroup has the \f(CW\*(C`j\*(C'\fR flag, no articles will be filed in that
+newsgroup, and local postings to that group will be rejected. If an
+article for that newsgroup is received from a remote site, and it is not
+crossposted to some other valid group, it will be filed into the \f(CW\*(C`junk\*(C'\fR
+newsgroup instead. This is different than simply not listing the group,
+since the article will still be accepted and can be propagated to other
+sites, and the \f(CW\*(C`junk\*(C'\fR group can be made available to readers if wished.
+.PP
+If the <flag> field begins with an equal sign, the newsgroup is an alias.
+Articles cannot be posted to that newsgroup, but they can be received from
+other sites. Any articles received from peers for that newsgroup are
+treated as if they were actually posted to the group named after the equal
+sign. Note that the Newsgroups header of the articles are not modified.
+(Alias groups are typically used during a transition and are typically
+created manually with \fIctlinnd\fR\|(8).) An alias should not point to another
+alias.
+.SH "HISTORY"
+.IX Header "HISTORY"
+Written by Rich \f(CW$alz\fR <rsalz@uunet.uu.net> for InterNetNews. Converted to
+\&\s-1POD\s0 by Russ Allbery <rra@stanford.edu>.
+.PP
+$Id: active.5 7880 2008-06-16 20:37:13Z iulius $
+.SH "SEE ALSO"
+.IX Header "SEE ALSO"
+\&\fIactive.times\fR\|(5), \fIcontrolchan\fR\|(8), \fIctlinnd\fR\|(8), \fIinn.conf\fR\|(5), \fIinnd\fR\|(8),
+\&\fInews.daily\fR\|(8)
--- /dev/null
+.\" Automatically generated by Pod::Man v1.37, Pod::Parser v1.32
+.\"
+.\" Standard preamble:
+.\" ========================================================================
+.de Sh \" Subsection heading
+.br
+.if t .Sp
+.ne 5
+.PP
+\fB\\$1\fR
+.PP
+..
+.de Sp \" Vertical space (when we can't use .PP)
+.if t .sp .5v
+.if n .sp
+..
+.de Vb \" Begin verbatim text
+.ft CW
+.nf
+.ne \\$1
+..
+.de Ve \" End verbatim text
+.ft R
+.fi
+..
+.\" Set up some character translations and predefined strings. \*(-- will
+.\" give an unbreakable dash, \*(PI will give pi, \*(L" will give a left
+.\" double quote, and \*(R" will give a right double quote. \*(C+ will
+.\" give a nicer C++. Capital omega is used to do unbreakable dashes and
+.\" therefore won't be available. \*(C` and \*(C' expand to `' in nroff,
+.\" nothing in troff, for use with C<>.
+.tr \(*W-
+.ds C+ C\v'-.1v'\h'-1p'\s-2+\h'-1p'+\s0\v'.1v'\h'-1p'
+.ie n \{\
+. ds -- \(*W-
+. ds PI pi
+. if (\n(.H=4u)&(1m=24u) .ds -- \(*W\h'-12u'\(*W\h'-12u'-\" diablo 10 pitch
+. if (\n(.H=4u)&(1m=20u) .ds -- \(*W\h'-12u'\(*W\h'-8u'-\" diablo 12 pitch
+. ds L" ""
+. ds R" ""
+. ds C` ""
+. ds C' ""
+'br\}
+.el\{\
+. ds -- \|\(em\|
+. ds PI \(*p
+. ds L" ``
+. ds R" ''
+'br\}
+.\"
+.\" If the F register is turned on, we'll generate index entries on stderr for
+.\" titles (.TH), headers (.SH), subsections (.Sh), items (.Ip), and index
+.\" entries marked with X<> in POD. Of course, you'll have to process the
+.\" output yourself in some meaningful fashion.
+.if \nF \{\
+. de IX
+. tm Index:\\$1\t\\n%\t"\\$2"
+..
+. nr % 0
+. rr F
+.\}
+.\"
+.\" For nroff, turn off justification. Always turn off hyphenation; it makes
+.\" way too many mistakes in technical documents.
+.hy 0
+.if n .na
+.\"
+.\" Accent mark definitions (@(#)ms.acc 1.5 88/02/08 SMI; from UCB 4.2).
+.\" Fear. Run. Save yourself. No user-serviceable parts.
+. \" fudge factors for nroff and troff
+.if n \{\
+. ds #H 0
+. ds #V .8m
+. ds #F .3m
+. ds #[ \f1
+. ds #] \fP
+.\}
+.if t \{\
+. ds #H ((1u-(\\\\n(.fu%2u))*.13m)
+. ds #V .6m
+. ds #F 0
+. ds #[ \&
+. ds #] \&
+.\}
+. \" simple accents for nroff and troff
+.if n \{\
+. ds ' \&
+. ds ` \&
+. ds ^ \&
+. ds , \&
+. ds ~ ~
+. ds /
+.\}
+.if t \{\
+. ds ' \\k:\h'-(\\n(.wu*8/10-\*(#H)'\'\h"|\\n:u"
+. ds ` \\k:\h'-(\\n(.wu*8/10-\*(#H)'\`\h'|\\n:u'
+. ds ^ \\k:\h'-(\\n(.wu*10/11-\*(#H)'^\h'|\\n:u'
+. ds , \\k:\h'-(\\n(.wu*8/10)',\h'|\\n:u'
+. ds ~ \\k:\h'-(\\n(.wu-\*(#H-.1m)'~\h'|\\n:u'
+. ds / \\k:\h'-(\\n(.wu*8/10-\*(#H)'\z\(sl\h'|\\n:u'
+.\}
+. \" troff and (daisy-wheel) nroff accents
+.ds : \\k:\h'-(\\n(.wu*8/10-\*(#H+.1m+\*(#F)'\v'-\*(#V'\z.\h'.2m+\*(#F'.\h'|\\n:u'\v'\*(#V'
+.ds 8 \h'\*(#H'\(*b\h'-\*(#H'
+.ds o \\k:\h'-(\\n(.wu+\w'\(de'u-\*(#H)/2u'\v'-.3n'\*(#[\z\(de\v'.3n'\h'|\\n:u'\*(#]
+.ds d- \h'\*(#H'\(pd\h'-\w'~'u'\v'-.25m'\f2\(hy\fP\v'.25m'\h'-\*(#H'
+.ds D- D\\k:\h'-\w'D'u'\v'-.11m'\z\(hy\v'.11m'\h'|\\n:u'
+.ds th \*(#[\v'.3m'\s+1I\s-1\v'-.3m'\h'-(\w'I'u*2/3)'\s-1o\s+1\*(#]
+.ds Th \*(#[\s+2I\s-2\h'-\w'I'u*3/5'\v'-.3m'o\v'.3m'\*(#]
+.ds ae a\h'-(\w'a'u*4/10)'e
+.ds Ae A\h'-(\w'A'u*4/10)'E
+. \" corrections for vroff
+.if v .ds ~ \\k:\h'-(\\n(.wu*9/10-\*(#H)'\s-2\u~\d\s+2\h'|\\n:u'
+.if v .ds ^ \\k:\h'-(\\n(.wu*10/11-\*(#H)'\v'-.4m'^\v'.4m'\h'|\\n:u'
+. \" for low resolution devices (crt and lpr)
+.if \n(.H>23 .if \n(.V>19 \
+\{\
+. ds : e
+. ds 8 ss
+. ds o a
+. ds d- d\h'-1'\(ga
+. ds D- D\h'-1'\(hy
+. ds th \o'bp'
+. ds Th \o'LP'
+. ds ae ae
+. ds Ae AE
+.\}
+.rm #[ #] #H #V #F C
+.\" ========================================================================
+.\"
+.IX Title "ACTIVE.TIMES 5"
+.TH ACTIVE.TIMES 5 "2008-04-06" "INN 2.4.5" "InterNetNews Documentation"
+.SH "NAME"
+active.times \- List of local creation times of newsgroups
+.SH "DESCRIPTION"
+.IX Header "DESCRIPTION"
+The file \fIpathdb\fR/active.times provides a chronological record of when
+newsgruops were created on the local server. This file is normally
+updated by \fBinnd\fR whenever a newgroup control message is processed or a
+\&\f(CW\*(C`ctlinnd newgroup\*(C'\fR command is issued, and is used by \fBnnrpd\fR to answer
+\&\s-1NEWGROUPS\s0 requests.
+.PP
+Each line consists of three fields:
+.PP
+.Vb 1
+\& <name> <time> <creator>
+.Ve
+.PP
+The first field is the name of the newsgroup. The second field is the
+time it was created, expressed as the number of seconds since the epoch.
+The third field is the e\-mail addrses of the person who created the group,
+as specified in the control message or on the \fBctlinnd\fR command line, or
+the newsmaster specified at configure time if no creator argument was
+given to \fBctlinnd\fR.
+.SH "HISTORY"
+.IX Header "HISTORY"
+Written by Rich \f(CW$alz\fR <rsalz@uunet.uu.net> for InterNetNews. Converted to
+\&\s-1POD\s0 by Russ Allbery <rra@stanford.edu>
+.PP
+$Id: active.times.5 7880 2008-06-16 20:37:13Z iulius $
+.SH "SEE ALSO"
+.IX Header "SEE ALSO"
+\&\fIactive\fR\|(5), \fIctlinnd\fR\|(8), \fIinn.conf\fR\|(5), \fIinnd\fR\|(8), \fInnrpd\fR\|(8)
--- /dev/null
+.\" By: Landon Curt Noll chongo@toad.com (chongo was here /\../\)
+.\"
+.\" Copyright (c) Landon Curt Noll, 1993.
+.\" All rights reserved.
+.\"
+.\" Permission to use and modify is hereby granted so long as this
+.\" notice remains. Use at your own risk. No warranty is implied.
+.\"
+.\" @(#) $Id: actsync.8 6731 2004-05-16 22:00:46Z rra $
+.\" @(#) Under RCS control in /usr/local/news/src/inn/local/RCS/actsync.8,v
+.\"
+.TH ACTSYNC 8
+.SH NAME
+actsync, actsyncd \- synchronize newsgroups
+.SH SYNOPSIS
+.B actsync
+[\fB\-A\fP] [\fB\-b\fP\fI hostid\fP] [\fB\-d\fP\fI hostid\fP] [\fB\-g\fP\fI max\fP]
+.br
+ [\fB\-i\fP\fI ignore_file\fP] [\fB\-I\fP\fI hostid\fP] [\fB\-k\fP] [\fB\-l\fP\fI hostid\fP] [\fB\-m\fP]
+.br
+ [\fB\-n\fP\fI name\fP] [\fB\-o\fP\fI fmt\fP] [\fB\-p\fP\fI min_%_unchg\fP] [\fB\-q\fP\fI hostid\fP]
+.br
+ [\fB\-s\fP\fI size\fP] [\fB\-t\fP\fI hostid\fP] [\fB\-T\fP] [\fB\-v\fP\fI verbosity\fP]
+.br
+ [\fB\-z\fP\fI sec\fP] [\fIhost1\fP] \fIhost2\fP
+.sp 1
+.B actsyncd
+[\fB\-x\fP] \fIactsync.cfg\fP [\fIdebug_level\fP [\fIdebug_outfmt\fP] ]
+.SH DESCRIPTION
+.IR Actsync (8)
+permits one to synchronize, compare, or merge two
+.I active
+files.
+With this utility one may add, change, or remove newsgroups on the
+local news server to make it similar to the list the newsgroups
+found on another system or file.
+The synchronization need not be exact.
+Local differences in newsgroup lists may be maintained and preserved.
+Certain newsgroup errors may be detected and optionally corrected.
+.PP
+There are several reasons to run
+.IR actsync (8)
+(or
+.IR actsyncd (8)),
+on a periodic basis.
+Among the reasons are:
+.in +0.5i
+.sp 1
+A control message to add, change or remove a newsgroup
+may fail to reach your site.
+.sp 1
+Your
+.I control.ctl
+may be out of date or incomplete.
+.sp 1
+News articles for a new newsgroup can arrive ahead (sometimes days ahead)
+of the control message.
+.sp 1
+Control messages may be forged, thus bypassing the restrictions
+found in
+.I control.ctl .
+.sp 1
+Your
+.I active
+file may have been trashed.
+.sp 1
+.in -0.5i
+.PP
+If
+.I host1
+or
+.I host2
+begins with a
+.B ``.''
+or
+.BR ``/'' ,
+then it is assumed to be a name of a file containing information in the
+.IR active (5)
+format.
+The
+.IR getlist (1)
+utility may be used to obtain copy a remote system's active file
+via its NNTP server, or an FTP client program can retrieve such a
+file from an FTP archive (such as
+ftp://ftp.isc.org/pub/usenet/CONFIG/active; see more about this below).
+Newsgroup information from a file
+may be treated as if it was obtained from a host.
+In this man page
+.I host1
+and
+.I host2
+are called hosts, even though they may be file names.
+.PP
+If a host argument does not begin with
+.B ``.''
+or
+.BR ``/'' ,
+is assumed to be a
+hostname or Internet address.
+In this case,
+.IR actsync (8)
+will attempt to use the
+.B NNTP
+protocol to obtain a copy of the the specified system's active file.
+If the host argument contains a
+.B ``:'' ,
+the right side will be considerd the port to connect to on the remote system.
+If no port number is specified,
+.IR actsync (8)
+will connect to port 119.
+.PP
+Regardless how the active file information is obtained,
+the actions of
+.IR actsync (8)
+remain the same.
+.PP
+If only one host is specified, it is assumed to be
+.IR host2 ;
+if
+.IR host1
+is not specified, it assumed to be the default local
+NNTP server as specified by the
+.B NNTPSERVER
+environment variable, or by the
+.B server
+value found in
+.IR inn.conf .
+.PP
+The newsgroup synchronization, by default, involves all newsgroups
+found on both hosts.
+One may also synchronize a subset of newsgroups by directing
+.IR actsync (8)
+to ignore certain newsgroups from both systems. Only newsgroups with
+valid names will be synchronized. To be valid, a newsgroup name must
+consist only of alphanumeric characters, ``.'', ``+'', ``-'', and ``_''.
+One may not have two ``.''s in a row. The first character must be
+alphanumeric, as must any character following a ``.''. The name may not
+end in a ``.'' character.
+.PP
+The
+.IR actsyncd (8)
+daemon provides a convenient interface to configure and run
+.IR actsync (8).
+If a host is not initially reachable,
+the daemon will retry up to 9 additional times, waiting 6 minutes before
+each retry.
+This daemon runs in the foreground, sending output to standard output
+and standard error.
+.PP
+If the \fB\-x\fP flag is given to
+.IR actsyncd (8),
+then a
+.IR ctlinnd\ xexec
+will be used instead of a
+.IR ctlinnd\ reload
+to load the newly modified active file.
+.PP
+The configuration filename for the daemon is given as a
+commandline argument, usually
+.I <pathetc in inn.conf>/actsync.cfg
+The config file can contain the following options:
+.sp 1
+.in +0.5i
+.nf
+\fBhost=\fP\fIhost2\fP
+\fBftppath=\fP\fI/remote/path/to/active/file\fP
+\fBspool=\fP\fI<normally patharticles in inn.conf>\fP
+\fBignore_file=\fP\fIignore_file\fP
+\fBflags=\fP\fIactsyncd\fP(8) options
+.fi
+.in -0.5i
+.sp 1
+The \fBhost\fP, \fBignore_file\fP, and \fBflags\fP lines are mandatory.
+.sp 1
+The keyword must start at the beginning of the line, and there
+may be no whitespace before the
+.B ``=''
+character.
+Blank lines are ignored.
+Comment lines start with
+.B ``#''
+and are ignored.
+Any other lines may produce undefined results.
+.sp 1
+The \fBhost\fP config file line refers to the \fIhost2\fP parameter to
+.IR actsync (8).
+The \fBftppath\fP directive causes the machine named in the \fBhost\fP
+line to accessed as an ftp server, retrieving the file named. If
+the filename ends in \fB.gz\fP or \fB.Z\fP, then it will automatically
+be uncompressed after retrieval.
+The \fBspool\fP config file lines determines where the top of the
+news spool tree is to be found.
+The \fBignore_file\fP config file line names the ignore file to be
+used by
+.IR actsync (8).
+The \fBflags\fP config file line contains any flags that you wish to pass to
+.IR actsync (8).
+.sp 1
+Note that the \fB\-i ignore_file\fP option
+and the \fB-o format\fP option
+should not be given
+in the \fBflags=\fP line because they are automatically taken care of by
+.IR actsyncd (8).
+.sp 1
+INN is shipped with default values of \fIftp.isc.org\fP for \fBhost\fP
+and \fI/pub/usenet/CONFIG/active\fP for \fBftppath\fP. You can read
+about the policies used for maintaining that active file at
+\fIftp://ftp.isc.org/pub/usenet/CONFIG/README\fP. Consider
+sychronizing from this file on a daily basis by using
+.IR cron (8).
+.SH OPTIONS
+The options to
+.IR actsync (8)
+are as follows:
+.PP
+.TP
+.B \-A
+.IR actsync (8)
+tries to authenticate before issuing LIST command.
+.TP
+.BI \-b " hostid"
+This flag causes
+.IR actsync (8)
+to ignore newsgroups with ``bork.bork.bork'' style names.
+That is, newsgroups whose last 3 components are identical.
+For example, the following newsgroups have bork style names:
+.sp 1
+.in +0.5i
+.nf
+alt.helms.dork.dork.dork
+alt.auto.accident.sue.sue.sue
+alt.election.vote.vote.vote
+.fi
+.in -0.5i
+.sp 1
+The value
+.I hostid
+determines on which hosts this action is performed:
+.sp 1
+.in +0.5i
+.nf
+0 neither host
+1 local default server
+2 remove server
+12 both servers
+21 both servers
+.fi
+.in -0.5i
+.sp 1
+The default is
+.BR "\-b 0" ;
+no newsgroups are ignored because of bork-style names.
+.TP
+.BI \-d " hostid"
+This flag causes
+.IR actsync (8)
+to ignore newsgroups that have all numeric path components.
+The
+.B hostid
+value is interpreted the same as in
+.BR \-b .
+For example, the following newsgroups have numeric path components:
+.sp
+.in +0.5i
+.nf
+alt.prime.chongo.23209
+391581.times.2.to_the.216193.power.-1
+99.bottles.of.treacle.on.the.wall
+linfield.class.envio_bio.101.d
+.fi
+.in -0.5i
+.sp 1
+The newsgroups directory of a newsgroups with a all numeric component
+could conflict with an article from another group if stored using the
+``tradspool'' storage method; see
+.IR storage.conf (5).
+For example, the directory for the first newsgroup listed above
+is the same path as article number 23209 from the newsgroup:
+.sp
+.in +0.5i
+.nf
+alt.prime.chongo
+.fi
+.in -0.5i
+.sp 1
+The default is
+.BR "\-d 0" ;
+all numeric newsgroups from both hosts will be processed.
+.TP
+.BI \-g " max"
+Ignore any newsgroup with more than
+.B max
+levels. For example,
+.BI \-g " 6"
+would ignore:
+.sp 1
+.in +0.5i
+.nf
+alt.feinstien.votes.to.trash.freedom.of.speech
+alt.senator.exon.enemy.of.the.internet
+alt.crypto.export.laws.dumb.dumb.dumb
+.fi
+.in -0.5i
+.sp 1
+but would not ignore:
+.sp 1
+.in +0.5i
+.nf
+alt.feinstien.acts.like.a.republican
+alt.exon.amendment
+alt.crypto.export.laws
+.fi
+.in -0.5i
+.sp 1
+If
+.B max
+is 0, then the max level feature is disabled.
+.sp 1
+By default,
+the max level feature is disabled.
+.TP
+.BI \-i " ignore_file"
+The
+.I ignore_file ,
+usually
+.I <pathetc in inn.conf>/actsync.ign ,
+allows one to have a fine degree of control over which newsgroups are ignored.
+It contains a set of rules that specifies
+which newsgroups will be checked and which will be ignored.
+.sp 1
+By default, these rules apply to both hosts.
+This can be modified by using the
+.BI \-I " hostid"
+flag.
+.sp 1
+By default, all newsgroups are checked.
+If no
+.I ignore_file
+if specified, or if the ignore file contains no rule lines,
+all newsgroups will be checked.
+.sp 1
+Blank lines and text after a
+.B ``#''
+are considered comments and are ignored.
+.sp 1
+Rule lines consist of tokens separated by whitespace.
+Rule lines may be one of two forms:
+.sp 1
+.in +0.5i
+.nf
+\fBc newsgroup [type ...]\fP
+\fBi newsgroup [type ...]\fP
+.fi
+.in -0.5i
+.sp 1
+If the rule begins with a
+.B c
+then the rule requests certain newsgroups to be checked.
+If the rule begins with an
+.B i
+then the rule requests certain newsgroups to be ignored.
+The
+.B newsgroup
+field may be a specific newsgroup, or a
+.IR uwildmat (3)
+pattern.
+.sp 1
+If one or more
+.BR type s
+are specified, then the rule applies to the newsgroup only if
+is of the specified type.
+Types refer to the 4th field of the
+.I active
+file; that is, a type may be one of:
+.sp 1
+.in +0.5i
+.nf
+\fBy\fP
+\fBn\fP
+\fBm\fP
+\fBj\fP
+\fBx\fP
+\fB=group.name\fP
+.fi
+.in -0.5i
+.sp 1
+Unlike active files, the
+.B group.name
+in an alias type may be a newsgroup name or a
+.IR uwildmat (3)
+pattern.
+Also,
+.B ``=''
+is equivalent to
+.BR ``=*'' .
+.sp 1
+On each rule line, no pattern type may not be repeated.
+For example, one may not have more than one type that begins with
+.BR ``='' ,
+per line.
+However, one may achieve an effect equivalent to using multiple
+.B ``=''
+types by using multiple rule lines affecting the same newsgroup.
+.sp 1
+By default, all newsgroups are candidates to be checked.
+If an ignore file is used, each newsgroup in turn is checked
+against the ignore file.
+If multiple lines match a given newsgroup, the last line
+in the ignore file is used.
+.sp 1
+For example, consider the following ignore file lines:
+.sp 1
+.in +0.5i
+.nf
+i *.general
+c *.general m
+i nsa.general
+.fi
+.in -0.5i
+.sp 1
+The newsgroups
+.B ba.general
+and
+.B mod.general
+would be synchronized if moderated and ignored if not moderated.
+The newsgroup
+.B nsa.general
+would be ignored regardless of moderation status.
+All newsgroups not matching
+.B *.general
+would be synchronized by default.
+.TP
+.BI \-I " hostid"
+This flag restricts which hosts are affected by the ignore file.
+The
+.B hostid
+value is interpreted the same as in
+.BR \-b
+described above.
+.sp 1
+This flag may be useful in conjunction with the
+.B \-m
+merge flag.
+For example:
+.sp 1
+.in +0.5i
+actsync \-i actsync.ign \-I 2 \-m
+.I host1
+.I host2
+.in -0.5i
+.sp 1
+will keep all newsgroups currently on
+.I host1 .
+It will also will only compare
+.I host1
+groups with non-ignored newsgroups from
+.I host2 .
+.sp 1
+The default is
+.BR "\-I 12" ,
+newsgroups from both hosts to be ignored per the
+.I \-i " actsync.ign"
+file.
+.TP
+.B \-k
+By default, any newsgroup on
+.I host1
+that is in error will be considered for removal.
+This causes
+.IR actsync (8)
+simply ignore such newsgroups.
+This flag, used in combination with
+.I \-m ,
+will prevent any newsgroup from being scheduled for removal.
+.TP
+.BR \-l " hostid"
+This flag causes ``problem newsgroups'' of type
+.B ``=''
+from
+.B host1
+or
+.B host2
+to be considered as errors.
+The
+.B hostid
+value is interpreted the same as in
+.BR \-b .
+Newsgroups of type
+.B ``=''
+are newsgroups active entries that have 4th field
+that begins with
+.BR ``='' ,
+i.e. newsgroups that are equivalent to other newsgroups. A ``problem''
+newsgroup is one which is:
+.sp 1
+.in +0.5i
+.nf
+* equivalent to itself
+* in an equivalence chain that loops around
+ to itself
+* in an equivalence chain longer than 16 groups
+* equivalent to a non-existant newsgroup
+* equivalent to a newsgroup that has an error
+ of some kind
+.fi
+.in -0.5i
+.sp 1
+However, a newsgroup that is equivalent to an ignored newsgroup is
+not a problem.
+.sp 1
+By default, problem newsgroups from both hosts are
+marked as errors.
+.TP
+.B \-m
+Merge newsgroups instead of sync.
+By default, if a newsgroup exists on
+.B host1
+but not
+.BR host2 ,
+it will be scheduled to be removed.
+This flag disables this process, permitting newsgroups unique to
+.B host1
+to be kept.
+.TP
+.B \-n " name"
+The
+.IR ctlinnd (8)
+command is used to create newsgroups as necessary.
+By default, the creator name used is
+.BR "actsync" .
+This flag changes the creator name to
+.BR "name" .
+.TP
+.B \-o " fmt"
+Determine the output / action format of this utility.
+The
+.B "fmt"
+may one of:
+.sp 1
+.in +0.5i
+.nf
+\fBa\fP output in \fIactive\fP\fR(5)\fP\fR format\fP
+\fBa1\fP output in \fIactive\fP\fR(5)\fP\fR format,\fP
+ and output host1 non-error ignored groups
+\fBak\fP output in \fIactive\fP\fR(5)\fP\fR format, but use host2\fP
+ hi & low (2nd & 3rd active fields) values
+ for any newsgroup being created
+\fBaK\fP output in \fIactive\fP\fR(5)\fP\fR format, but use host2\fP
+ hi & low (2nd & 3rd active fields) values
+ for all newsgroups found in host2
+\fBa1k\fP output in \fIactive\fP\fR(5)\fP\fR format, but use host2\fP
+ hi & low (2nd & 3rd active fields) values
+ for any newsgroup being created,
+ and output host1 non-error ignored groups
+\fBa1K\fP output in \fIactive\fP\fR(5)\fP\fR format, but use host2\fP
+ hi & low (2nd & 3rd active fields) values
+ for all newsgroups found in host2,
+ and output host1 non-error ignored groups
+\fBak1\fP same as \fBa1k\fP
+\fBaK1\fP same as \fBa1K\fP
+\fBc\fP output in \fIctlinnd\fP\fR(8)\fP\fR format\fP
+\fBx\fP no output, directly exec \fIctlinnd\fP\fR(8)\fP\fR commands\fP
+\fBxi\fP no output, directly exec \fIctlinnd\fP\fR(8)\fP\fR commands,\fP
+ in an interactive mode
+.fi
+.in -0.5i
+.sp 1
+The \fBa\fP, \fBa1\fP, \fBak\fP, \fBaK\fP, \fBa1k\fP,
+\fBa1K\fP, \fBak1\fP and \fBaK1\fP style formats allow one to form
+a new active file instead of producing
+.IR ctlinnd (8)
+commands.
+They use hi & low values of
+.B 0000000000
+and
+.B 0000000001
+respectively for newsgroups that are created.
+The \fBak\fP and \fBaK\fP variants change the the hi & low (2nd & 3rd
+active fields).
+In the case of \fBak\fP, newsgroups created take their hi & low values from
+.BR host2 .
+In the case of \fBaK\fP, all newsgroups found on host2 take their
+hi & low values from
+.BR host2 .
+.sp 1
+The \fBc\fP format produces
+.IR ctlinnd (8)
+commands.
+No actions are taken because
+.IR actsync (8)
+simply prints
+.IR ctlinnd (8)
+commands on standard output.
+The sync (or merge) with
+.B host2
+may be accomplished by piping this output into
+.IR sh (1).
+A paranoid person might prefer to use \fBx\fP or \fBxi\fP
+in case a newsgroup name or type contains bogus characters
+that might be interpreted by
+.IR sh (1).
+Even so, this output format is useful to let you see how
+.B host1
+will be affected by the sync (or merge) with
+.BR host2 .
+.sp 1
+The sync (or merge) may be accomplished directly
+by use of the \fBx\fP format.
+With this format,
+.IR actsync (8)
+uses the
+.IR execl (2)
+system call to directly execute
+.IR ctlinnd (8)
+commands.
+Because of the exec, there is no risk
+of bogus newsgroups containing bogus characters causing
+a shell to do bogus (or dangerous) things.
+The output of such exec calls may be seen if the verbosity level
+is at least
+.BR 2 .
+.sp 1
+The
+.IR actsync (8)
+utility will pause for
+.B 4
+seconds before each command is executed if
+.BI \-o " x"
+is selected.
+See the
+.BR \-z " sec"
+flag below for discussion of this delay and how to customize it.
+.sp 1
+The \fBxi\fP format interactively prompts on standard output
+and reads directives on standard input.
+One may pick and choose changes using this format.
+.sp 1
+Care should be taken when producing
+\fIactive\fP\fR(5)\fP\fR formatted output\fP.
+One should check to be sure that
+.IR actsync (8)
+exited with a zero status prior to using such output.
+Also one should realize that such output will not
+contain lines ignored due to
+.BI \-i " ignore_file"
+even if
+.BI \-p " 100"
+is used.
+.sp 1
+By default,
+.BI \-o " c"
+is assumed.
+.TP
+.BI \-p " min_%_unchg"
+By default, the
+.IR actsync (8)
+utility has safeguards against performing massive changes.
+If fewer than
+.B min_%_unchg
+percent of the non-ignored lines from
+.B host1
+remain unchanged, no actions (output, execution, etc.)
+are performed and
+.IR actsync (8)
+exits with a non-zero exit status.
+The
+.B min_%_unchg
+may be a floating point value such as
+.BR 66.667 .
+.sp 1
+A change is considered a
+.B host1
+line that was removed, added, changed, or found to be in error.
+Changing the 2nd or 3rd active fields via
+.BI \-o "ak"
+or
+.BI \-o " aK"
+are not considered changes by
+.BR \-p .
+.sp 1
+To force
+.IR actsync (8)
+to accept any amount of change, use the
+.BI \-p " 0"
+option.
+To force
+.IR actsync (8)
+to reject any changes, use the
+.BI \-p " 100"
+option.
+.sp 1
+Care should be taken when producing
+\fIactive\fP\fR(5)\fP\fR-formatted output\fP;
+be sure to check that
+.IR actsync (8)
+exited with a zero status prior to using such output.
+Also one should realize that such output will not
+contain lines ignored by the
+.BI \-i " ignore_file"
+process even if
+.BI \-p " 100"
+is used.
+.sp 1
+By default, 96% of the lines not ignored in host1 must
+be unchanged.
+That is, by default,
+.BI \-p " 96"
+is assumed.
+.TP
+.BI \-q " hostid"
+By default, all newsgroup errors are reported on standard error.
+This flag quiets errors from
+.B host1
+or
+.BR host2 .
+The
+.B hostid
+value is interpreted the same as in
+.BR \-b .
+.TP
+.BR \-s " size"
+If
+.BR size\ >\ 0,
+then ignore newsgroups with names longer than
+.BR size ,
+and ignore newsgroups equivalent (by following
+.B ``=''
+chains) to names longer than
+.BR size .
+Length checking is performed on both the local and remote hosts.
+.sp 1
+By default,
+.B size
+is 0 and thus no length checking is performed.
+.TP
+.BR \-t " hostid"
+Ignore improper newsgroups consisting of only a top component
+from
+.B host1
+or
+.BR host2 .
+The
+.B hostid
+value is interpreted the same as in
+.BR \-b .
+The following newsgroups are considered proper newsgroups
+despite top only names and therefore are exempt from this flag:
+.sp 1
+.in +0.5i
+.nf
+control
+general
+junk
+test
+to
+.fi
+.in -0.5i
+.sp 1
+For example, the following newsgroup names are improper because they
+only contain a top level component:
+.sp 1
+.in +0.5i
+.nf
+dole_for_pres
+dos
+microsoft
+windows95
+.fi
+.in -0.5i
+.sp 1
+The default is
+.BR "\-t 2" ,
+that is, all improper top-level-only newsgroups from the remote
+are ignored.
+.TP
+.B \-T
+This flag causes
+.B host2
+newsgroups from new hierarchies to be ignored.
+Normally a newsgroup which only exists on
+.B host2 ,
+for example
+.B chongo.was.here ,
+will be created for
+.BR host1 .
+However, if this flag is given and
+.B host1
+does not have any other newsgroups in the same hierarchy,
+e.g. ``\fBchongo.*\fP'', then the newsgroup in question
+will be ignored and will not be created on
+.BR host1 .
+.TP
+.BI \-v " verbosity"
+By default,
+.IR actsync (8)
+is not verbose.
+This flag controls the verbosity level as follows:
+.sp 1
+.in +0.5i
+.nf
+\fB0\fP no debug or status reports (default)
+\fB1\fP print summary,
+ but only if work was needed or done
+\fB2\fP print actions, exec output, and summary,
+ but only if work was needed or done
+\fB3\fP print actions, exec output, and summary
+\fB4\fP full debug output
+.fi
+.TP
+.BI \-z " sec"
+If
+.BI \-o " x"
+is selected,
+.IR actsync (8)
+will pause for
+.B sec
+seconds before each command is executed.
+This helps prevent
+.IR innd (8)
+from being busied-out if a large number of
+.IR ctlinnd (8)
+commands are needed.
+One can entirely disable this sleeping by using
+.BI \-z " 0".
+.sp 1
+By default,
+.IR actsync (8)
+will pause for
+.B 4
+seconds before each command is executed if
+.BI \-o " x"
+is selected.
+.in -0.5i
+.SH EXAMPLES
+Determine the difference (but don't change anything) between your
+newsgroup set and uunet's set:
+.PP
+.in +0.5i
+actsync news.uu.net
+.in -0.5i
+.PP
+Same as above, with full debug and progress reports:
+.PP
+.in +0.5i
+actsync \-v 4 news.uu.net
+.in -0.5o
+.PP
+Force a site to have the same newsgroups some other site:
+.PP
+.in +0.5i
+actsync \-o x master
+.in -0.5i
+.PP
+This may be useful to sync a slave site to its master, or
+to sync internal site to a gateway.
+.PP
+Compare your site with uunet, disregarding local groups and
+certain local differences with uunet.
+Produce a report if
+any differences were encountered:
+.PP
+.in +0.5i
+actsync \-v 2 \-i actsync.ign news.uu.net
+.in -0.5i
+.PP
+where
+.B actsync.ign
+contains:
+.PP
+.in +0.5i
+.nf
+# Don't compare to.* groups as they will differ.
+#
+i to.*
+
+# These are our local groups that nobody else
+# (should) carry. So ignore them for the sake
+# of the compare.
+#
+i nsa.*
+
+# These groups are local favorites, so keep them
+# even if uunet does not carry them.
+#
+i ca.dump.bob.dorman
+i ca.keep.bob.dorman
+i alt.tv.dinosaurs.barney.die.die.die
+i alt.tv.dinosaurs.barney.love.love.love
+i alt.sounds.* =alt.binaries.sounds.*
+.PP
+.fi
+.in -0.5i
+.PP
+To interactively sync against news.uu.net, using the same
+ignore file:
+.PP
+.in +0.5i
+actsync \-o xi \-v 2 \-i actsync.ign news.uu.net
+.in -0.5i
+.PP
+Based on newsgroups that you decided to keep, one could
+make changes to the
+.B actsync.ign
+file:
+.PP
+.in +0.5i
+.nf
+# Don't compare to.* groups as they will differ.
+#
+i to.*
+
+# These are our local groups that nobody else
+# (should) carry. So ignore them for the sake
+# of the compare.
+#
+i nsa.*
+
+# These groups are local favorites, so keep them
+# even if uunet does not carry them.
+#
+i ca.dump.bob.dorman
+i alt.tv.dinosaurs.barney.die.die.die
+i alt.sounds.* =alt.binaries.sounds.*
+
+# Don't sync test groups, except for ones that are
+# moderated or that are under the gnu hierarchy.
+i *.test
+c *.test m # check moderated test groups
+c gnu.*.test
+c gnu.test # just in case it ever exists
+.PP
+.fi
+.in -0.5i
+.PP
+Automatic processing may be setup by using the following
+.B actsync.cfg
+file:
+.PP
+.in +0.5i
+.nf
+# host to sync off of (host2)
+host=news.uu.net
+
+# location of the ignore file
+ignore_file=<pathetc in inn.conf>/actsync.ign
+
+# where news articles are kept
+spool=<patharticles in inn.conf>
+
+# actsync(8) flags
+#
+# Automatic execs, report if something was done,
+# otherwise don't say anything, don't report
+# uunet active file problems, just ignore
+# the affected entries.
+flags=\-o x \-v 2 \-q 2
+.fi
+.in -0.5i
+.PP
+and then by running
+.IR actsyncd (8)
+with the path to the config
+file:
+.PP
+.in +0.5i
+actsyncd <pathetc in inn.conf>/actsync.cfg
+.in -0.5i
+.PP
+One may produce a trial
+.IR actsyncd (8)
+run without changing anything
+on the server by supplying the \fBdebug_level\fP arg:
+.sp 1
+.in +0.5i
+actsyncd <pathetc in inn.conf>/actsync.cfg 2
+.in -0.5i
+.PP
+The \fBdebug_level\fP causes
+.IR actsyncd (8)
+to run
+.IR actsync (8)
+with an \fB\-v debug_level\fP (overriding any \fB\-v\fP
+flag on the \fBflags\fP line),
+not make any changes to the
+.I active
+file, write a new active file to \fIstandard output\fP,
+and write debug messages to \fIstandard error\fP.
+.PP
+If the \fBdebug_outfmt\fP arg is also given to
+.IR actsyncd (8)
+then the data written to \fIstandard output\fP will
+be in \fB\-o debug_outfmt\fP instead of in \fB\-o a1\fP format.
+The /bin/sh command
+.sp 1
+.in +0.5i
+.nf
+actsyncd <pathetc in inn.conf>/actsync.cfg 4 \\
+ >cmd.log 2>dbg.log
+.fi
+.in -0.5i
+.PP
+will operate in debug mode,
+not change the
+.I active
+file, write
+.IR ctlinnd (8)
+style commands to \fBcmd.log\fP, and
+write debug statements to \fBdbg.log\fP.
+.PP
+To check only the major hierarchies against news.uu,net, use the following
+.B actsync.ign
+file:
+.PP
+.in +0.5i
+.nf
+# by default, ignore everything
+i *
+
+# check the major groups
+c comp.*
+c gnu.*
+c sci.*
+c alt.*
+c misc.*
+c news.*
+c rec.*
+c soc.*
+c talk.*
+.fi
+.in -0.5i
+.PP
+and the command:
+.PP
+.in +0.5i
+actsync \-i actsync.ign news.uu.net
+.in -0.5i
+.PP
+To determine the differences between your old active and
+your current default server:
+.PP
+.in +0.5i
+actsync <pathetc in inn.conf>/active.old \-
+.in -0.5i
+.PP
+To report but not fix any newsgroup problems with the current active file:
+.PP
+.in +0.5i
+actsync \- \-
+.in -0.5i
+.PP
+To detect any newsgroup errors on your local server, and
+to remove any
+.B *.bork.bork.bork
+style silly newsgroup names:
+.PP
+.in +0.5i
+actsync \-b 2 \- \-
+.in -0.5i
+.PP
+The active file produced by:
+.PP
+.in +0.5i
+actsync ...flags... \-o x erehwon.honey.edu
+.in -0.5i
+.PP
+or by:
+.PP
+.in +0.5i
+actsync ...flags... \-o c erehwon.honey.edu | sh
+.in -0.5i
+.PP
+is effectively the same as the active file produced by:
+.PP
+.nf
+.in +0.5i
+ctlinnd pause 'running actsync'
+rm -f active.new
+actsync ...flags... \-o a1 erehwon.honey.edu > active.new
+rm -f active.old
+ln active active.old
+mv active.new active
+ctlinnd reload active 'running actsync'
+ctlinnd go 'running actsync'
+.in -0.5i
+.fi
+.PP
+It should be noted that the final method above, pausing the server
+and simply replacing the
+.I active
+file, is faster.
+.PP
+.SH CAUTION
+Careless use of this tool may result in the unintended
+addition, change, or removal of newsgroups.
+You should avoid using the \fRx\fP output format until
+you are sure it will do what you want.
+.SH BUGS
+If a newsgroup appears multiple times,
+.IR actsync (8)
+will treat all copies as errors.
+However, if the group is marked for removal, only
+one rmgroup will be issued.
+.PP
+The timeout for
+.IR ctlinnd (8)
+commands is fixed at 30 seconds when
+running in ``\fRx\fP'' or ``\fRxi\fP'' output format.
+Perhaps the timeout value should be controlled via a command line option?
+.SH "SEE ALSO"
+.IR active (5),
+.br
+.IR simpleftp (1),
+.br
+.IR mod-active (8),
+.br
+.IR ctlinnd (8),
+.br
+.IR getlist (8),
+.br
+.IR inn.conf (5).
+.SH HISTORY
+Written by Landon Curt Noll <chongo@toad.com> for InterNetNews.
+Updated to support ftp fetching by David Lawrence <tale@isc.org>.
--- /dev/null
+.TH ACTSYNCD 8
+.SH NAME
+actsyncd \- run actsync to synchronize newsgroups
+.SH SYNOPSIS
+Please see the
+.IR actsync (8)
+manual page.
+.SH "SEE ALSO"
+actsync(8)
--- /dev/null
+.\" $Revision: 5909 $
+.TH ARCHIVE 8
+.SH NAME
+archive \- Usenet article archiver
+.SH SYNOPSIS
+.B archive
+[
+.BI \-a " archive"
+]
+[
+.B \-c
+]
+[
+.B \-f
+]
+[
+.BI \-i " index"
+]
+[
+.BI \-p " newsgroup-list"
+]
+[
+.B \-r
+]
+[
+.I input
+]
+.SH DESCRIPTION
+.I Archive
+makes copies of files specified on its standard input.
+It is normally run either as a channel feed under
+.IR innd (8),
+or by a script before
+.IR expire (8)
+is run.
+.PP
+.I Archive
+reads the named
+.I input
+file, or standard input if no file is given.
+The input is taken as a sequence of lines;
+blank lines and lines starting with a number sign (``#'') are ignored.
+All other lines should specify the token of an article to archive.
+Every article is retrieved from a token,
+and the Xref: header is used to determine the target file in the
+archive directory.
+You can limit the targets taken from the Xref: header with the ``\-p'' option.
+.PP
+Files are copied to a directory within the archive directory,
+.IR <patharchive\ in\ inn.conf> .
+The default is to create a hierarchy that mimics the input files;
+intermediate directories will be created as needed.
+For example, if the input token represents article 2211 in the newsgroup
+comp.sources.unix,
+.IR archive
+will generate a copy in
+.IR <patharchive\ in\ inn.conf>/comp/sources/unix/2211 .
+.SH OPTIONS
+.TP
+.B \-a\ archive
+If the ``\-a'' flag is used then its argument specifies the directory to
+archive in instead of
+.IR <patharchive\ in\ inn.conf> .
+.TP
+.B \-c
+If the ``\-c'' flag is used, then directory names will be flattened as if
+by the ``\-f'' flag; additionally, all posts will be concatenated into a
+.B single\ file ,
+appending if the file already exists, with the final component of the
+filename being YYYYMM based on the local execution time of
+.IR archive.
+In this case, on December 14, 1998, the file would be copied to
+.IR <patharchive\ in\ inn.conf>/comp.sources.unix/199812 .
+.TP
+.B \-f
+If the ``\-f'' flag is used, then all directory names will be
+flattened out, replacing the slashes with periods.
+In this case, the file would be copied to
+.IR <patharchive\ in\ inn.conf>/comp.sources.unix/2211 .
+.TP
+.B \-i
+If the ``\-i'' flag is used, then
+.I archive
+will append one line to the specified
+.I index
+file for each article that it copies.
+This line will contain the destination name as well as the Message-ID and
+Subject headers.
+.TP
+.B \-p newsgroup-list
+Limits the targets taken from the Xref: header to the groups specified in
+.I newsgroup-list.
+The
+.I newsgroup-list
+is a comma-separated
+.IR uwildmat (3)
+list of newsgroups you wish to have
+.IR archive
+handle.
+.TP
+.B \-r
+By default,
+.I archive
+sets its standard error to
+.IR <pathlog\ in\ inn.conf>/errlog .
+To suppress this redirection, use the ``\-r'' flag.
+.SH EXIT STATUS
+If the input is exhausted,
+.I archive
+will exit with a zero status.
+If an I/O error occures, it will try to spool its input, copying it to a file.
+If there was no input filename, the standard input will be copied to
+.I <pathoutgoing in inn.conf>/archive
+and the program will exit.
+If an input filename was given, a temporary file named
+.IR input .bch
+(if
+.I input
+is an absolute pathname)
+or
+.I <pathoutgoing in inn.conf>/input.bch
+(if the filename does not begin with a slash) is created.
+Once the input is copied,
+.I archive
+will try to rename this temporary file to be the name of the input file,
+and then exit.
+
+.SH EXAMPLES
+A typical
+.IR newsfeeds (5)
+entry to archive most source newsgroups is as follows:
+.PP
+.RS
+.nf
+source-archive\e
+ :!*,*sources*,!*wanted*,!*.d\e
+ :Tc,Wn\e
+ :<pathbin in inn.conf>/archive \-f \-i \e
+ <patharchive in inn.conf>/INDEX
+.fi
+.RE
+
+.SH HISTORY
+Written by Rich $alz <rsalz@uunet.uu.net> for InterNetNews.
+.de R$
+This is revision \\$3, dated \\$4.
+..
+.R$ $Id: archive.8 5909 2002-12-03 05:17:18Z vinocur $
+.SH "SEE ALSO"
+inn.conf(5),
+newsfeeds(5).
--- /dev/null
+.\" Automatically generated by Pod::Man v1.37, Pod::Parser v1.32
+.\"
+.\" Standard preamble:
+.\" ========================================================================
+.de Sh \" Subsection heading
+.br
+.if t .Sp
+.ne 5
+.PP
+\fB\\$1\fR
+.PP
+..
+.de Sp \" Vertical space (when we can't use .PP)
+.if t .sp .5v
+.if n .sp
+..
+.de Vb \" Begin verbatim text
+.ft CW
+.nf
+.ne \\$1
+..
+.de Ve \" End verbatim text
+.ft R
+.fi
+..
+.\" Set up some character translations and predefined strings. \*(-- will
+.\" give an unbreakable dash, \*(PI will give pi, \*(L" will give a left
+.\" double quote, and \*(R" will give a right double quote. \*(C+ will
+.\" give a nicer C++. Capital omega is used to do unbreakable dashes and
+.\" therefore won't be available. \*(C` and \*(C' expand to `' in nroff,
+.\" nothing in troff, for use with C<>.
+.tr \(*W-
+.ds C+ C\v'-.1v'\h'-1p'\s-2+\h'-1p'+\s0\v'.1v'\h'-1p'
+.ie n \{\
+. ds -- \(*W-
+. ds PI pi
+. if (\n(.H=4u)&(1m=24u) .ds -- \(*W\h'-12u'\(*W\h'-12u'-\" diablo 10 pitch
+. if (\n(.H=4u)&(1m=20u) .ds -- \(*W\h'-12u'\(*W\h'-8u'-\" diablo 12 pitch
+. ds L" ""
+. ds R" ""
+. ds C` ""
+. ds C' ""
+'br\}
+.el\{\
+. ds -- \|\(em\|
+. ds PI \(*p
+. ds L" ``
+. ds R" ''
+'br\}
+.\"
+.\" If the F register is turned on, we'll generate index entries on stderr for
+.\" titles (.TH), headers (.SH), subsections (.Sh), items (.Ip), and index
+.\" entries marked with X<> in POD. Of course, you'll have to process the
+.\" output yourself in some meaningful fashion.
+.if \nF \{\
+. de IX
+. tm Index:\\$1\t\\n%\t"\\$2"
+..
+. nr % 0
+. rr F
+.\}
+.\"
+.\" For nroff, turn off justification. Always turn off hyphenation; it makes
+.\" way too many mistakes in technical documents.
+.hy 0
+.if n .na
+.\"
+.\" Accent mark definitions (@(#)ms.acc 1.5 88/02/08 SMI; from UCB 4.2).
+.\" Fear. Run. Save yourself. No user-serviceable parts.
+. \" fudge factors for nroff and troff
+.if n \{\
+. ds #H 0
+. ds #V .8m
+. ds #F .3m
+. ds #[ \f1
+. ds #] \fP
+.\}
+.if t \{\
+. ds #H ((1u-(\\\\n(.fu%2u))*.13m)
+. ds #V .6m
+. ds #F 0
+. ds #[ \&
+. ds #] \&
+.\}
+. \" simple accents for nroff and troff
+.if n \{\
+. ds ' \&
+. ds ` \&
+. ds ^ \&
+. ds , \&
+. ds ~ ~
+. ds /
+.\}
+.if t \{\
+. ds ' \\k:\h'-(\\n(.wu*8/10-\*(#H)'\'\h"|\\n:u"
+. ds ` \\k:\h'-(\\n(.wu*8/10-\*(#H)'\`\h'|\\n:u'
+. ds ^ \\k:\h'-(\\n(.wu*10/11-\*(#H)'^\h'|\\n:u'
+. ds , \\k:\h'-(\\n(.wu*8/10)',\h'|\\n:u'
+. ds ~ \\k:\h'-(\\n(.wu-\*(#H-.1m)'~\h'|\\n:u'
+. ds / \\k:\h'-(\\n(.wu*8/10-\*(#H)'\z\(sl\h'|\\n:u'
+.\}
+. \" troff and (daisy-wheel) nroff accents
+.ds : \\k:\h'-(\\n(.wu*8/10-\*(#H+.1m+\*(#F)'\v'-\*(#V'\z.\h'.2m+\*(#F'.\h'|\\n:u'\v'\*(#V'
+.ds 8 \h'\*(#H'\(*b\h'-\*(#H'
+.ds o \\k:\h'-(\\n(.wu+\w'\(de'u-\*(#H)/2u'\v'-.3n'\*(#[\z\(de\v'.3n'\h'|\\n:u'\*(#]
+.ds d- \h'\*(#H'\(pd\h'-\w'~'u'\v'-.25m'\f2\(hy\fP\v'.25m'\h'-\*(#H'
+.ds D- D\\k:\h'-\w'D'u'\v'-.11m'\z\(hy\v'.11m'\h'|\\n:u'
+.ds th \*(#[\v'.3m'\s+1I\s-1\v'-.3m'\h'-(\w'I'u*2/3)'\s-1o\s+1\*(#]
+.ds Th \*(#[\s+2I\s-2\h'-\w'I'u*3/5'\v'-.3m'o\v'.3m'\*(#]
+.ds ae a\h'-(\w'a'u*4/10)'e
+.ds Ae A\h'-(\w'A'u*4/10)'E
+. \" corrections for vroff
+.if v .ds ~ \\k:\h'-(\\n(.wu*9/10-\*(#H)'\s-2\u~\d\s+2\h'|\\n:u'
+.if v .ds ^ \\k:\h'-(\\n(.wu*10/11-\*(#H)'\v'-.4m'^\v'.4m'\h'|\\n:u'
+. \" for low resolution devices (crt and lpr)
+.if \n(.H>23 .if \n(.V>19 \
+\{\
+. ds : e
+. ds 8 ss
+. ds o a
+. ds d- d\h'-1'\(ga
+. ds D- D\h'-1'\(hy
+. ds th \o'bp'
+. ds Th \o'LP'
+. ds ae ae
+. ds Ae AE
+.\}
+.rm #[ #] #H #V #F C
+.\" ========================================================================
+.\"
+.IX Title "AUTH_KRB5 8"
+.TH AUTH_KRB5 8 "2008-04-06" "INN 2.4.5" "InterNetNews Documentation"
+.SH "NAME"
+auth_krb5 \- nnrpd Kerberos v5 authenticator
+.SH "SYNOPSIS"
+.IX Header "SYNOPSIS"
+\&\fBauth_krb5\fR [\fB\-i\fR \fIinstance\fR]
+.SH "DESCRIPTION"
+.IX Header "DESCRIPTION"
+This program does authentication for \fBnnrpd\fR against a Kerberos v5 \s-1KDC\s0.
+This is \s-1NOT\s0 real Kerberos authentication using service tickets; instead, a
+username and password is used to attempt to obtain a Kerberos v5 \s-1TGT\s0 to
+confirm that they are valid. As such, this authenticator assumes that
+\&\fBnnrpd\fR has been given the user's username and password, and therefore is
+not as secure as real Kerberos authentication. It generally should only
+be used with \s-1NNTP\s0 over \s-1SSL\s0 to protect the password from sniffing.
+.SH "OPTIONS"
+.IX Header "OPTIONS"
+.IP "\fB\-i\fR \fIinstance\fR" 4
+.IX Item "-i instance"
+If this option is given, \fIinstance\fR will be used as the instance of the
+principal received from \fBnnrpd\fR and authentication will be done against
+that principal instead of the base principal. In other words, a principal
+like \f(CW\*(C`user\*(C'\fR, when passed to \fBauth_krb5\fR invoked with \f(CW\*(C`\-i nntp\*(C'\fR, will be
+transformed into \f(CW\*(C`user/nntp\*(C'\fR before attempting Kerberos authentication.
+.Sp
+Since giving one's password to \fBnnrpd\fR is not as secure as normal
+Kerberos authentication, this option supports a configuration where all
+users are given a separate instance just for news authentication with its
+own password, so their regular account password isn't exposed via \s-1NNTP\s0.
+.SH "EXAMPLE"
+.IX Header "EXAMPLE"
+The following \fIreaders.conf\fR\|(5) fragment tells nnrpd to authenticate users
+by attempting to obtain Kerberos v5 TGTs for them, appending an instance
+of \f(CW\*(C`nntp\*(C'\fR to usernames before doing so:
+.PP
+.Vb 3
+\& auth kerberos {
+\& auth: "auth_krb5 \-i nntp"
+\& }
+.Ve
+.PP
+.Vb 4
+\& access kerberos {
+\& users: "*/nntp"
+\& newsgroups: example.*
+\& }
+.Ve
+.PP
+Access is granted to the example.* groups for all users who successfully
+authenticate.
+.SH "BUGS"
+.IX Header "BUGS"
+Currently, any username containing realm information (containing \f(CW\*(C`@\*(C'\fR) is
+rejected. This is to prevent someone from passing in a username
+corresponding to a principal in another realm that they have access to and
+gaining access to the news server via it. However, this is also something
+that people may wish to do under some circumstances, so there should be a
+better way of handling it (such as, perhaps, a list of acceptable realms
+or a \-r flag specifying the realm in which to attempt authentication).
+.PP
+It's not clear the right thing to do when the username passed in contains
+a \f(CW\*(C`/\*(C'\fR and \fB\-i\fR was also given. Right now, \fBauth_krb5\fR will create a
+malformed Kerberos principal with multiple instances and attempt to
+authenticate against it, which will fail but perhaps not with the best
+error message.
+.SH "HISTORY"
+.IX Header "HISTORY"
+Originally written by Christopher P. Lindsey. This documentation was
+written by Russ Allbery <rra@stanford.edu> based on Christopher's original
+\&\s-1README\s0 file.
+.PP
+$Id: auth_krb5.8 7880 2008-06-16 20:37:13Z iulius $
+.SH "SEE ALSO"
+.IX Header "SEE ALSO"
+\&\fInnrpd\fR\|(8), \fIreaders.conf\fR\|(5)
+.PP
+The latest version of Christopher's original \fBnnrpkrb5auth\fR may be found
+on his web site at <http://www.mallorn.com/tools/>.
--- /dev/null
+.\" Automatically generated by Pod::Man v1.37, Pod::Parser v1.32
+.\"
+.\" Standard preamble:
+.\" ========================================================================
+.de Sh \" Subsection heading
+.br
+.if t .Sp
+.ne 5
+.PP
+\fB\\$1\fR
+.PP
+..
+.de Sp \" Vertical space (when we can't use .PP)
+.if t .sp .5v
+.if n .sp
+..
+.de Vb \" Begin verbatim text
+.ft CW
+.nf
+.ne \\$1
+..
+.de Ve \" End verbatim text
+.ft R
+.fi
+..
+.\" Set up some character translations and predefined strings. \*(-- will
+.\" give an unbreakable dash, \*(PI will give pi, \*(L" will give a left
+.\" double quote, and \*(R" will give a right double quote. \*(C+ will
+.\" give a nicer C++. Capital omega is used to do unbreakable dashes and
+.\" therefore won't be available. \*(C` and \*(C' expand to `' in nroff,
+.\" nothing in troff, for use with C<>.
+.tr \(*W-
+.ds C+ C\v'-.1v'\h'-1p'\s-2+\h'-1p'+\s0\v'.1v'\h'-1p'
+.ie n \{\
+. ds -- \(*W-
+. ds PI pi
+. if (\n(.H=4u)&(1m=24u) .ds -- \(*W\h'-12u'\(*W\h'-12u'-\" diablo 10 pitch
+. if (\n(.H=4u)&(1m=20u) .ds -- \(*W\h'-12u'\(*W\h'-8u'-\" diablo 12 pitch
+. ds L" ""
+. ds R" ""
+. ds C` ""
+. ds C' ""
+'br\}
+.el\{\
+. ds -- \|\(em\|
+. ds PI \(*p
+. ds L" ``
+. ds R" ''
+'br\}
+.\"
+.\" If the F register is turned on, we'll generate index entries on stderr for
+.\" titles (.TH), headers (.SH), subsections (.Sh), items (.Ip), and index
+.\" entries marked with X<> in POD. Of course, you'll have to process the
+.\" output yourself in some meaningful fashion.
+.if \nF \{\
+. de IX
+. tm Index:\\$1\t\\n%\t"\\$2"
+..
+. nr % 0
+. rr F
+.\}
+.\"
+.\" For nroff, turn off justification. Always turn off hyphenation; it makes
+.\" way too many mistakes in technical documents.
+.hy 0
+.if n .na
+.\"
+.\" Accent mark definitions (@(#)ms.acc 1.5 88/02/08 SMI; from UCB 4.2).
+.\" Fear. Run. Save yourself. No user-serviceable parts.
+. \" fudge factors for nroff and troff
+.if n \{\
+. ds #H 0
+. ds #V .8m
+. ds #F .3m
+. ds #[ \f1
+. ds #] \fP
+.\}
+.if t \{\
+. ds #H ((1u-(\\\\n(.fu%2u))*.13m)
+. ds #V .6m
+. ds #F 0
+. ds #[ \&
+. ds #] \&
+.\}
+. \" simple accents for nroff and troff
+.if n \{\
+. ds ' \&
+. ds ` \&
+. ds ^ \&
+. ds , \&
+. ds ~ ~
+. ds /
+.\}
+.if t \{\
+. ds ' \\k:\h'-(\\n(.wu*8/10-\*(#H)'\'\h"|\\n:u"
+. ds ` \\k:\h'-(\\n(.wu*8/10-\*(#H)'\`\h'|\\n:u'
+. ds ^ \\k:\h'-(\\n(.wu*10/11-\*(#H)'^\h'|\\n:u'
+. ds , \\k:\h'-(\\n(.wu*8/10)',\h'|\\n:u'
+. ds ~ \\k:\h'-(\\n(.wu-\*(#H-.1m)'~\h'|\\n:u'
+. ds / \\k:\h'-(\\n(.wu*8/10-\*(#H)'\z\(sl\h'|\\n:u'
+.\}
+. \" troff and (daisy-wheel) nroff accents
+.ds : \\k:\h'-(\\n(.wu*8/10-\*(#H+.1m+\*(#F)'\v'-\*(#V'\z.\h'.2m+\*(#F'.\h'|\\n:u'\v'\*(#V'
+.ds 8 \h'\*(#H'\(*b\h'-\*(#H'
+.ds o \\k:\h'-(\\n(.wu+\w'\(de'u-\*(#H)/2u'\v'-.3n'\*(#[\z\(de\v'.3n'\h'|\\n:u'\*(#]
+.ds d- \h'\*(#H'\(pd\h'-\w'~'u'\v'-.25m'\f2\(hy\fP\v'.25m'\h'-\*(#H'
+.ds D- D\\k:\h'-\w'D'u'\v'-.11m'\z\(hy\v'.11m'\h'|\\n:u'
+.ds th \*(#[\v'.3m'\s+1I\s-1\v'-.3m'\h'-(\w'I'u*2/3)'\s-1o\s+1\*(#]
+.ds Th \*(#[\s+2I\s-2\h'-\w'I'u*3/5'\v'-.3m'o\v'.3m'\*(#]
+.ds ae a\h'-(\w'a'u*4/10)'e
+.ds Ae A\h'-(\w'A'u*4/10)'E
+. \" corrections for vroff
+.if v .ds ~ \\k:\h'-(\\n(.wu*9/10-\*(#H)'\s-2\u~\d\s+2\h'|\\n:u'
+.if v .ds ^ \\k:\h'-(\\n(.wu*10/11-\*(#H)'\v'-.4m'^\v'.4m'\h'|\\n:u'
+. \" for low resolution devices (crt and lpr)
+.if \n(.H>23 .if \n(.V>19 \
+\{\
+. ds : e
+. ds 8 ss
+. ds o a
+. ds d- d\h'-1'\(ga
+. ds D- D\h'-1'\(hy
+. ds th \o'bp'
+. ds Th \o'LP'
+. ds ae ae
+. ds Ae AE
+.\}
+.rm #[ #] #H #V #F C
+.\" ========================================================================
+.\"
+.IX Title "AUTH_SMB 8"
+.TH AUTH_SMB 8 "2008-04-06" "INN 2.4.5" "InterNetNews Documentation"
+.SH "NAME"
+auth_smb \- nnrpd Samba authenticator
+.SH "SYNOPSIS"
+.IX Header "SYNOPSIS"
+\&\fBauth_smb\fR \fBserver\fR [\fBbackup_server\fR] \fBdomain\fR
+.SH "DESCRIPTION"
+.IX Header "DESCRIPTION"
+This program does authentication for \fBnnrpd\fR against an \s-1SMB\s0 server. It
+passes the received username and password to \fBserver\fR for validation in
+the specified \s-1SMB\s0 \fBdomain\fR. A backup server may optionally be
+supplied; if it is missing, only \fBserver\fR is used.
+.PP
+If authentication is successful, the original username is returned as
+the authentication identity. Brief errors, including incorrect password
+and failure contacting the server, are logged.
+.SH "EXAMPLE"
+.IX Header "EXAMPLE"
+The following \fIreaders.conf\fR\|(5) fragment grants access to users who can
+authenticate against an \s-1SMB\s0 server:
+.PP
+.Vb 4
+\& auth windows {
+\& auth: "auth_smb pdc.example.com bdc.example.com USERS"
+\& default\-domain: "users.example.com"
+\& }
+.Ve
+.PP
+.Vb 4
+\& access internal {
+\& users: "*@users.example.com"
+\& newsgroups: example.*
+\& }
+.Ve
+.PP
+Access is granted to the example.* groups after successful
+authentication.
+.SH "BUGS"
+.IX Header "BUGS"
+We should link against an external \s-1SMB\s0 library rather than maintain one
+within the \s-1INN\s0 source tree.
+.SH "HISTORY"
+.IX Header "HISTORY"
+Originally written October 2000 by Krischan Jodies <krischan@jodies.cx>,
+based heavily on pam_smb v1.1.6 by David Airlie <airlied@samba.org>.
+This documentation was written by Jeffrey M. Vinocur <jeff@litech.org>.
+.PP
+$Id: auth_smb.8 7880 2008-06-16 20:37:13Z iulius $
+.SH "SEE ALSO"
+.IX Header "SEE ALSO"
+\&\fInnrpd\fR\|(8), \fIreaders.conf\fR\|(5)
--- /dev/null
+.\" $Revision: 6491 $
+.TH BATCHER 8
+.SH NAME
+batcher \- article-batching backend for InterNetNews
+.SH SYNOPSIS
+.B batcher
+[
+.BI \-a " arts"
+]
+[
+.BI \-A " total_arts"
+]
+[
+.BI \-b " size"
+]
+[
+.BI \-B " total_size"
+]
+[
+.BI \-i " string"
+]
+[
+.BI \-N " num_batches"
+]
+[
+.BI \-p " process"
+]
+[
+.B \-r
+]
+[
+.BI \-s " separator"
+]
+[
+.BI \-S " alt_spool"
+]
+[
+.B \-v
+]
+.I host
+[
+.I input
+]
+.SH DESCRIPTION
+.I Batcher
+reads uses a list of files to prepare news batches for the specified
+.IR host .
+It is normally invoked by a script run out of
+.IR cron (8)
+that uses
+.IR shlock (1)
+to lock the host name, followed by a
+.IR ctlinnd (8)
+command to flush the batchfile.
+.PP
+.I Batcher
+reads the named
+.I input
+file, or standard input if no file is given.
+Relative pathnames are interpreted from the
+.I <pathoutgoing in inn.conf>
+directory.
+The input is taken as a sequence of lines;
+blank lines and lines starting with a number sign (``#'') are ignored.
+All other lines should consist of one or two fields separated by a single space.
+The first field is either the storage token of an article or the
+name of a file holding an article; if it is not an an absolute
+pathname or storage token, it is taken relative to
+.IR <patharticles\ in\ inn.conf> .
+The second field, if present, specifies the size of the article in bytes.
+.SH OPTIONS
+.TP
+.B \-S alt_spool
+The ``\-S'' flag may be used to specify an alternate spool directory to
+use if the article is not found; this would perhaps be an NFS-mounted
+spool directory of a master server with longer expiration times.
+.TP
+.B \-r
+By default, the program reports errors to
+.IR <pathlog\ in\ inn.conf>/errlog .
+To suppress this redirection and report errors to standard error,
+use the ``\-r'' flag.
+.TP
+.B \-v
+Upon exit,
+.I batcher
+reports statistics via
+.IR syslog (3).
+If the ``\-v'' flag is used, they will also be printed on the standard
+output.
+.TP
+.B \-b size
+.I Batcher
+collects the text of the named articles into batches.
+To limit the size of each batch, use the ``\-b'' flag.
+The default size is 60 kilobytes.
+Using ``\-b\ 0'' allows unlimited batch sizes.
+.TP
+.B \-a arts
+To limit the number of articles in each batch, use the ``\-a'' flag.
+The default is no limit.
+A new batch will be started when either the byte count or number of
+articles written exceeds the specified limits.
+.TP
+.B \-B total_size
+To limit the total number of bytes written for all batches, use the ``\-B''
+flag.
+.TP
+.B \-A total_arts
+To limit the total number of articles that can be batched use the ``\-A''
+flag.
+.TP
+.B \-N num_batches
+To limit the total number of batches that should be created use the ``\-N''
+flag.
+.IP
+In all three of the above cases, the default is zero, that is, no limit.
+.TP
+.B \-i string
+A batch starts with an identifying line to specify the unpacking method
+to be used on the receiving end.
+When the ``\-i'' flag is used, the initial string,
+.IR string ,
+followed by a newline, will be output at the start of every batch.
+The default is to have no initial string.
+.TP
+.B \-s separator
+Each article starts with a separator line to indicate the size of the article.
+To specify the separator use the ``\-s'' flag.
+This is a
+.IR sprintf (3)
+format string which can have a single ``%ld'' parameter which will be given
+the size of the article.
+If the separator is not empty, then the string and a newline will be output
+before every article.
+The default separator is ``#!\ rnews\ %ld''.
+.TP
+.B \-p process
+By default, batches are written to standard output, which
+is not useful when more than one output batch is created.
+Use the ``\-p'' flag to specify the shell command that should be
+created (via
+.IR popen (3))
+whenever a new batch is started.
+The process is a
+.IR sprintf (3)
+format string which can have a single ``%s'' parameter which will be given
+the host name.
+A common value is:
+.PP
+.RS
+.nf
+( echo '#! cunbatch' ; exec compress ) | uux \- \-r \-z %s!rnews
+.fi
+.RE
+.SH EXIT STATUS
+.PP
+If the input is exhausted,
+.I batcher
+will exit with a zero status.
+If any of the limits specified with the ``\-B'', ``\-A'', or ``\-N'' flags
+is reached, or if there is an error writing the batch, then
+.I batcher
+will try to spool the remaining input, copying it to a file.
+If there was no input filename, standard input will be copied to
+.I <pathoutgoing in inn.conf>/host
+and the program will exit.
+If an input filename was given, the input will be copied to
+a temporary file named
+.IR input .bch
+(if
+.I input
+is an absolute pathname)
+or
+.I <pathoutgoing in inn.conf>/input.bch
+(if the filename does not begin with a slash).
+Once the input is copied,
+.I batcher
+will try to rename this temporary file to be the name of the input file,
+and then exit.
+.PP
+Upon receipt of an interrupt or termination signal,
+.I batcher
+will finish sending the current article, close the batch, and then
+rewrite the batchfile according as described in the previous paragraph.
+.SH HISTORY
+Written by Rich $alz <rsalz@uunet.uu.net> for InterNetNews.
+.de R$
+This is revision \\$3, dated \\$4.
+..
+.R$ $Id: batcher.8 6491 2003-10-18 05:56:37Z rra $
+.SH "SEE ALSO"
+ctlinnd(8),
+inn.conf(5),
+newsfeeds(5),
+shlock(1).
--- /dev/null
+.\" $Revision: 5909 $
+.TH BUFFCHAN 8
+.SH NAME
+buffchan \- buffered file-writing backend for InterNetNews
+.SH SYNOPSIS
+.B buffchan
+[
+.B \-b
+]
+[
+.BI \-c " lines"
+]
+[
+.BI \-C " seconds"
+]
+[
+.BI \-d " directory"
+]
+[
+.BI \-f " num_fields"
+]
+[
+.BI \-m " map"
+]
+[
+.BI \-p " pidfile"
+]
+[
+.BI \-l " lines"
+]
+[
+.BI \-L " seconds"
+]
+[
+.B \-r
+]
+[
+.BI \-s " filename_format"
+]
+[
+.B \-u
+]
+.SH DESCRIPTION
+.I Buffchan
+reads lines from standard input and copies certain fields in
+each line into files named by other fields within the line.
+.I Buffchan
+is intended to be called by
+.IR innd (8)
+as an exploder feed.
+.SH OPTIONS
+.TP
+.B \-b
+Once
+.I buffchan
+opens a file it keeps it open.
+The input must therefore never specify more files than the
+number of available descriptors can keep open.
+If the ``\fB\-b\fP'' flag is used, the program will allocate a buffer and
+attach it to the file using
+.IR setbuf (3).
+.TP
+.B \-c lines
+If the ``\fB\-c\fP'' flag is used,
+.I buffchan
+will close, and re-open, a file after every
+.I lines
+lines are written to a file.
+.TP
+.B \-C seconds
+Similarly, the ``\fB\-C\fP'' flag may be used to specify that all files should
+be closed and re-opened every
+.I seconds
+seconds.
+.TP
+.B \-d directory
+The ``\fB\-d\fP'' flag may be used to specify a directory the program should
+change to before starting.
+If this flag is used, then the default for the ``\fB\-s\fP'' flag is changed to
+be a simple ``%s''.
+.TP
+.B \-f num_fields
+Buffchan
+input is interpreted as a sequence of lines.
+Each line contains a fixed number of initial fields, followed by a
+variable number of filename fields.
+All fields in a line are separated by whitespace.
+The default number of initial fields is one; the ``\fB\-f\fP''
+flag may be
+used to specify a different number of fields.
+.TP
+.B \-m map
+Map files specify short names as aliases for domain names; see
+.IR filechan (8)
+for details and an example.
+.TP
+.B \-p pidfile
+If the ``\fB\-p\fP'' flag is used, the program will write a line containing
+its process ID (in text) to the specified file.
+.TP
+.B \-l lines
+If the ``\fB\-l\fP'' flag is used,
+.I buffchan
+will call
+.IR fflush (3)
+after every
+.I lines
+lines are written to a file.
+.TP
+.B \-L seconds
+If the ``\fB\-L\fP'' flag is used,
+all files will be flushed every
+.I n
+seconds.
+.TP
+.B \-r
+By default, the program sends its error messages to
+.IR <pathlog\ in\ inn.conf>/errlog .
+To suppress this redirection and send error messages to standard error,
+use the ``\fB\-r\fP'' flag.
+.TP
+.B \-s filename_format
+After the initial fields, each remaining field names a file to
+write.
+The ``\fB\-s\fP'' flag may be used to specify a format string that maps
+the field to a file name.
+This is a
+.IR sprintf (3)
+format string which should have a single ``%s'' parameter which will be given
+the contents of a non-initial field.
+The default value is
+.IR <pathoutgoing\ in\ inn.conf>/%s .
+See the description of this flag in
+.IR filechan (8).
+.TP
+.B \-u
+If the ``\fB\-u\fP'' flag is used, the program will request unbuffered output.
+.PP
+.I Buffchan
+can be invoked as an exploder feed (see
+.IR newsfeeds (5)).
+As such, if a line starts with an exclamation point it will be treated
+as a command.
+There are three commands, described below:
+.TP
+.B flush
+The ``flush'' command closes and re-opens
+all open files; ``flush\ xxx'' which flushes only the specified site.
+These are analogous to the
+.IR ctlinnd (8)
+\&``flush'' command,
+and can be achieved by doing a ``send\ "flush\ xxx"'' command.
+Applications can tell that the ``flush'' has completed by renaming the
+file before issuing the command;
+.I buffchan
+has completed the command when the original filename re-appears.
+If
+.I <$ac_cv_func_fchmod in config.cache>
+is ``yes'', then
+.I buffchan
+also changes the access permissions of the file from read-only for
+everyone to read-write for owner and group as it flushes or closes each
+output file. It will change the modes back to read-only if it re-opens
+the same file.
+.TP
+.B drop
+The ``drop'' command is similar to the ``flush'' command except that no
+files are re-opened.
+If given an argument, then the specified site is dropped, otherwise all
+sites are dropped.
+(Note that the site will be restarted if the input stream mentions the
+site.)
+When a
+.I ctlinnd
+\&``drop site'' command is sent,
+.I innd
+will automatically forward the command to
+.I buffchan
+for sites listed as funnels feeding into this exploder.
+To drop all sites, use the
+.I ctlinnd
+\&``send buffchan-site drop'' command.
+.TP
+.B readmap
+The map file (specified with the ``\-m'' flag) is reloaded.
+.SH HISTORY
+Written by Rich $alz <rsalz@uunet.uu.net> for InterNetNews.
+.de R$
+This is revision \\$3, dated \\$4.
+..
+.R$ $Id: buffchan.8 5909 2002-12-03 05:17:18Z vinocur $
+.SH "SEE ALSO"
+ctlinnd(8),
+filechan(8),
+inn.conf(5),
+innd(8),
+newsfeeds(5).
--- /dev/null
+.\" $Revision: 5909 $
+.TH BUFFINDEXED.CONF 5
+.SH NAME
+buffindexed.conf \- configuration file for buffindexed ovmethod
+.SH DESCRIPTION
+The file
+.I <pathetc in inn.conf>/buffindexed.conf
+is required if buffindexed ovmethod is used.
+.PP
+Buffindexed is one of ovmethod which is specified in
+.IR inn.conf .
+It uses preconfigured buffer files to store overview data and index, and
+never needs more disk space other than those files. The files are divided
+into 8 KB blocks internally; a given block is allocated for either overview
+index or overview data. A block is never shared among multiple newsgroups.
+There is a database file:
+.I <pathdb in inn.conf>/group.index
+that includes information about each newsgroup: the pointer to the index
+block for the group, high mark, low mark, flag of the group, the number of
+articles, and etc. This file is created automatically when all buffers
+are initialized and must not be edited manually. If all buffers are filled up,
+.IR innd (8)
+throttles itself. Note that the buffer files are never rolled over and
+overwritten the way CNFS does. You need to append another buffer file in
+this event. You can see the buffer usage with
+.IR inndf (8)
+with ``-o'' option.
+.PP
+The file consists of a series of lines;
+blank lines and lines beginning with a number sign (``#'') are ignored.
+There is only one kind of configuration line.
+The order of lines in this file is not important.
+.PP
+.RS
+.nf
+index:file_name:buffer_size
+.fi
+.RE
+.PP
+\&``Index'' is an index of overview buffer.
+\&``Index'' must be between 0 and 65535.
+\&``File_name'' is the path to overview buffer file.
+The length of this path should be not more than 63 characters.
+\&``Buffer_size'' is the length of buffer file in kilobytes
+in decimal (1 KB = 1024 bytes). If the ``file_name'' is not a special
+device, the actual file size must be buffer_size * 1024 bytes.
+You can NOT use buffers over 2 GB even if you specify
+.IR <\-\-with\-largefiles\ at\ configure> ,
+or buffers will be broken. It'll be fixed in the future.
+.PP
+When creating new overview buffer, there are two different methods for
+creating the files.
+.TP
+.BR 1. " Create a big file on top of a standard filesystem."
+.sp 1
+Use "dd" to create the overview buffer
+files, such as "dd if=/dev/zero of=/path/to/ovbuff bs=1024 count=N"
+where N is the buffer_size.
+.TP
+.BR 2. " Use block disk devices directly."
+.sp 1
+If your operating system will allow you to
+.I mmap()
+block disk devices (Solaris does, FreeBSD does not), this is the
+recommended method. But note that Solaris (at least 2.6) seems to
+have a problem in regional locking of block disk devices, and should
+not be used as overview data will be corrupted.
+.sp 1
+Partition the disks to make each partition slightly larger (a few MB larger)
+than the intended size of each overview buffer.
+It is not recommend to use the block device files already located in
+``/dev''; instead, use "mknod" to create a new set of block device files.
+In order to do this, do an "ls -Ll" of the /dev/dsk partition.
+The major and minor device numbers are in the fifth and sixth columns (right
+before the date), respectively. This information should be fed to "mknod"
+to make a "block-type special file" (b).
+Here is a short script that accomplishes this when fed the name of the
+partition as listed in ``/dev/dsk/'':
+.sp 1
+.nf
+.in +0.5i
+#!/bin/sh
+disk=$1
+major=`ls -l /dev/dsk/$disk | awk '{print $5}' | tr -d ,`
+minor=`ls -l /dev/dsk/$disk | awk '{print $6}`
+mkdir /ovbuff
+mknod /ovbuff/$disk b $major $minor
+.in -0.5i
+.fi
+.sp 1
+The created device files themselves consume very little space.
+.PP
+In either case, make certain that each overview buffer file is owned by
+.IR <USER\ specified\ with\ \-\-with\-news\-user\ at\ configure> ,
+.IR <GROUP\ specified\ with\ \-\-with\-news\-group\ at\ configure> ,
+and has read/write modes for the owner and group (mode ``0664'' or ``0660'').
+.PP
+When you first start
+.IR innd (8)
+and everything is configured properly, you should see messages in
+.I <pathlog in inn.conf>/news.notice
+which look like:
+.sp 1
+.nf
+.in +0.5i
+Aug 27 00:00:00 kevlar innd: buffindexed: No magic cookie found for buffindexed 0, initializing
+.in -0.5i
+.fi
+.PP
+You MUST entirely recreate overview if you remove or relpace buffers.
+You need not recreate if you just append new buffers. And whenever you
+recreate the overview data base, you need to clean all the buffers.
+.SH HISTORY
+Written by Katsuhiro Kondou <kondou@nec.co.jp> for InterNetNews.
+.de R$
+This is revision \\$3, dated \\$4.
+..
+.R$ $Id: buffindexed.conf.5 5909 2002-12-03 05:17:18Z vinocur $
+.SH "SEE ALSO"
+inn.conf(5).
--- /dev/null
+.\" Automatically generated by Pod::Man v1.37, Pod::Parser v1.32
+.\"
+.\" Standard preamble:
+.\" ========================================================================
+.de Sh \" Subsection heading
+.br
+.if t .Sp
+.ne 5
+.PP
+\fB\\$1\fR
+.PP
+..
+.de Sp \" Vertical space (when we can't use .PP)
+.if t .sp .5v
+.if n .sp
+..
+.de Vb \" Begin verbatim text
+.ft CW
+.nf
+.ne \\$1
+..
+.de Ve \" End verbatim text
+.ft R
+.fi
+..
+.\" Set up some character translations and predefined strings. \*(-- will
+.\" give an unbreakable dash, \*(PI will give pi, \*(L" will give a left
+.\" double quote, and \*(R" will give a right double quote. \*(C+ will
+.\" give a nicer C++. Capital omega is used to do unbreakable dashes and
+.\" therefore won't be available. \*(C` and \*(C' expand to `' in nroff,
+.\" nothing in troff, for use with C<>.
+.tr \(*W-
+.ds C+ C\v'-.1v'\h'-1p'\s-2+\h'-1p'+\s0\v'.1v'\h'-1p'
+.ie n \{\
+. ds -- \(*W-
+. ds PI pi
+. if (\n(.H=4u)&(1m=24u) .ds -- \(*W\h'-12u'\(*W\h'-12u'-\" diablo 10 pitch
+. if (\n(.H=4u)&(1m=20u) .ds -- \(*W\h'-12u'\(*W\h'-8u'-\" diablo 12 pitch
+. ds L" ""
+. ds R" ""
+. ds C` ""
+. ds C' ""
+'br\}
+.el\{\
+. ds -- \|\(em\|
+. ds PI \(*p
+. ds L" ``
+. ds R" ''
+'br\}
+.\"
+.\" If the F register is turned on, we'll generate index entries on stderr for
+.\" titles (.TH), headers (.SH), subsections (.Sh), items (.Ip), and index
+.\" entries marked with X<> in POD. Of course, you'll have to process the
+.\" output yourself in some meaningful fashion.
+.if \nF \{\
+. de IX
+. tm Index:\\$1\t\\n%\t"\\$2"
+..
+. nr % 0
+. rr F
+.\}
+.\"
+.\" For nroff, turn off justification. Always turn off hyphenation; it makes
+.\" way too many mistakes in technical documents.
+.hy 0
+.if n .na
+.\"
+.\" Accent mark definitions (@(#)ms.acc 1.5 88/02/08 SMI; from UCB 4.2).
+.\" Fear. Run. Save yourself. No user-serviceable parts.
+. \" fudge factors for nroff and troff
+.if n \{\
+. ds #H 0
+. ds #V .8m
+. ds #F .3m
+. ds #[ \f1
+. ds #] \fP
+.\}
+.if t \{\
+. ds #H ((1u-(\\\\n(.fu%2u))*.13m)
+. ds #V .6m
+. ds #F 0
+. ds #[ \&
+. ds #] \&
+.\}
+. \" simple accents for nroff and troff
+.if n \{\
+. ds ' \&
+. ds ` \&
+. ds ^ \&
+. ds , \&
+. ds ~ ~
+. ds /
+.\}
+.if t \{\
+. ds ' \\k:\h'-(\\n(.wu*8/10-\*(#H)'\'\h"|\\n:u"
+. ds ` \\k:\h'-(\\n(.wu*8/10-\*(#H)'\`\h'|\\n:u'
+. ds ^ \\k:\h'-(\\n(.wu*10/11-\*(#H)'^\h'|\\n:u'
+. ds , \\k:\h'-(\\n(.wu*8/10)',\h'|\\n:u'
+. ds ~ \\k:\h'-(\\n(.wu-\*(#H-.1m)'~\h'|\\n:u'
+. ds / \\k:\h'-(\\n(.wu*8/10-\*(#H)'\z\(sl\h'|\\n:u'
+.\}
+. \" troff and (daisy-wheel) nroff accents
+.ds : \\k:\h'-(\\n(.wu*8/10-\*(#H+.1m+\*(#F)'\v'-\*(#V'\z.\h'.2m+\*(#F'.\h'|\\n:u'\v'\*(#V'
+.ds 8 \h'\*(#H'\(*b\h'-\*(#H'
+.ds o \\k:\h'-(\\n(.wu+\w'\(de'u-\*(#H)/2u'\v'-.3n'\*(#[\z\(de\v'.3n'\h'|\\n:u'\*(#]
+.ds d- \h'\*(#H'\(pd\h'-\w'~'u'\v'-.25m'\f2\(hy\fP\v'.25m'\h'-\*(#H'
+.ds D- D\\k:\h'-\w'D'u'\v'-.11m'\z\(hy\v'.11m'\h'|\\n:u'
+.ds th \*(#[\v'.3m'\s+1I\s-1\v'-.3m'\h'-(\w'I'u*2/3)'\s-1o\s+1\*(#]
+.ds Th \*(#[\s+2I\s-2\h'-\w'I'u*3/5'\v'-.3m'o\v'.3m'\*(#]
+.ds ae a\h'-(\w'a'u*4/10)'e
+.ds Ae A\h'-(\w'A'u*4/10)'E
+. \" corrections for vroff
+.if v .ds ~ \\k:\h'-(\\n(.wu*9/10-\*(#H)'\s-2\u~\d\s+2\h'|\\n:u'
+.if v .ds ^ \\k:\h'-(\\n(.wu*10/11-\*(#H)'\v'-.4m'^\v'.4m'\h'|\\n:u'
+. \" for low resolution devices (crt and lpr)
+.if \n(.H>23 .if \n(.V>19 \
+\{\
+. ds : e
+. ds 8 ss
+. ds o a
+. ds d- d\h'-1'\(ga
+. ds D- D\h'-1'\(hy
+. ds th \o'bp'
+. ds Th \o'LP'
+. ds ae ae
+. ds Ae AE
+.\}
+.rm #[ #] #H #V #F C
+.\" ========================================================================
+.\"
+.IX Title "CKPASSWD 8"
+.TH CKPASSWD 8 "2008-04-06" "INN 2.4.5" "InterNetNews Documentation"
+.SH "NAME"
+ckpasswd \- nnrpd password authenticator
+.SH "SYNOPSIS"
+.IX Header "SYNOPSIS"
+\&\fBckpasswd\fR [\fB\-gs\fR] [\fB\-d\fR \fIdatabase\fR] [\fB\-f\fR \fIfilename\fR]
+[\fB\-u\fR \fIusername\fR \fB\-p\fR \fIpassword\fR]
+.SH "DESCRIPTION"
+.IX Header "DESCRIPTION"
+\&\fBckpasswd\fR is the basic password authenticator for nnrpd, suitable for
+being run from an auth stanza in \fIreaders.conf\fR. See \fIreaders.conf\fR\|(5) for
+more information on how to configure an nnrpd authenticator.
+.PP
+\&\fBckpasswd\fR accepts a username and password from nnrpd and tells \fInnrpd\fR\|(8)
+whether that's the correct password for that username. By default, when
+given no arguments, it tries to check the password using \s-1PAM\s0 if support
+for \s-1PAM\s0 was found when \s-1INN\s0 was built. Failing that, it tries to check the
+password against the password field returned by \fIgetpwnam\fR\|(3). Note that
+these days most systems no longer make real passwords available via
+\&\fIgetpwnam\fR\|(3) (some still do if and only if the program calling \fIgetpwnam\fR\|(3)
+is running as root).
+.PP
+When using \s-1PAM\s0, \fBckpasswd\fR identifies itself as \f(CW\*(C`nnrpd\*(C'\fR, not as
+\&\f(CW\*(C`ckpasswd\*(C'\fR, and the \s-1PAM\s0 configuration must be set up accordingly. The
+details of \s-1PAM\s0 configuration are different on different operating systems
+(and even different Linux distributions); see \s-1EXAMPLES\s0 below for help
+getting started, and look for a \fIpam\fR\|(7) or \fIpam.conf\fR\|(4) manual page on your
+system.
+.PP
+When using any method other than \s-1PAM\s0, \fBckpasswd\fR expects all passwords to
+be stored encrypted by the system \fIcrypt\fR\|(3) function and calls \fIcrypt\fR\|(3) on
+the supplied password before comparing it to the expected password. \s-1IF\s0
+you're using a different password hash scheme (like \s-1MD5\s0), you must use
+\&\s-1PAM\s0.
+.SH "OPTIONS"
+.IX Header "OPTIONS"
+.IP "\fB\-d\fR \fIdatabase\fR" 4
+.IX Item "-d database"
+Read passwords from a database (ndbm or dbm format depending on what your
+system has) rather than by using \fIgetpwnam\fR\|(3). \fBckpasswd\fR expects
+\&\fIdatabase\fR.dir and \fIdatabase\fR.pag to exist and to be a database keyed by
+username with the encrypted passwords as the values.
+.Sp
+While \s-1INN\s0 doesn't come with a program intended specifically to create such
+databases, on most systems it's fairly easy to write a Perl script to do
+so. Something like:
+.Sp
+.Vb 16
+\& #!/usr/bin/perl
+\& use NDBM_File;
+\& use Fcntl;
+\& tie (%db, 'NDBM_File', '/path/to/database', O_RDWR|O_CREAT, 0640)
+\& or die "Cannot open /path/to/database: $!\en";
+\& $| = 1;
+\& print "Username: ";
+\& my $user = <STDIN>;
+\& chomp $user;
+\& print "Password: ";
+\& my $passwd = <STDIN>;
+\& chomp $passwd;
+\& my @alphabet = ('.', '/', 0..9, 'A'..'Z', 'a'..'z');
+\& my $salt = join '', @alphabet[rand 64, rand 64];
+\& $db{$user} = crypt ($passwd, $salt);
+\& untie %db;
+.Ve
+.Sp
+Note that this will echo back the password when typed; there are obvious
+improvements that could be made to this, but it should be a reasonable
+start. Sometimes a program like this will be available with the name
+\&\fBdbmpasswd\fR.
+.Sp
+This option will not be available on systems without dbm or ndbm
+libraries.
+.IP "\fB\-f\fR \fIfilename\fR" 4
+.IX Item "-f filename"
+Read passwords from the given file rather than using \fIgetpwnam\fR\|(3). The
+file is expected to be formatted like a system password file, at least
+vaguely. That means each line should look something like:
+.Sp
+.Vb 1
+\& username:pdIh9NCNslkq6
+.Ve
+.Sp
+(and each line may have an additional colon after the encrypted password
+and additional data; that data will be ignored by \fBckpasswd\fR). Lines
+starting with a number sign (`#') are ignored. \s-1INN\s0 does not come with a
+utility to create the encrypted passwords, but \fBhtpasswd\fR (which comes
+with Apache) can do so and it's a quick job with Perl (see the example
+script under \fB\-d\fR). If using Apache's \fBhtpasswd\fR program, be sure to
+give it the \fB\-d\fR option so that it will use \fIcrypt\fR\|(3).
+.IP "\fB\-g\fR" 4
+.IX Item "-g"
+Attempt to look up system group corresponding to username and return a
+string like \*(L"user@group\*(R" to be matched against in \fIreaders.conf\fR. This
+option is incompatible with the \fB\-d\fR and \fB\-f\fR options.
+.IP "\fB\-p\fR \fIpassword\fR" 4
+.IX Item "-p password"
+Use \fIpassword\fR as the password for authentication rather than reading a
+password using the nnrpd authenticator protocol. This option is useful
+only for testing your authentication system (particularly since it
+involves putting a password on the command line), and does not work when
+\&\fBckpasswd\fR is run by \fBnnrpd\fR. If this option is given, \fB\-u\fR must also
+be given.
+.IP "\fB\-s\fR" 4
+.IX Item "-s"
+Check passwords against the result of \fIgetspnam\fR\|(3) instead of \fIgetpwnam\fR\|(3).
+This function, on those systems that supports it, reads from /etc/shadow
+or similar more restricted files. If you want to check passwords supplied
+to \fInnrpd\fR\|(8) against system account passwords, you will probably have to
+use this option on most systems.
+.Sp
+Most systems require special privileges to call \fIgetspnam\fR\|(3), so in order
+to use this option you may need to make \fBckpasswd\fR setgid to some group
+(like group \*(L"shadow\*(R") or even setuid root. \fBckpasswd\fR has not been
+specifically audited for such uses! It is, however, a very small program
+that you should be able to check by hand for security.
+.Sp
+This configuration is not recommended if it can be avoided, for serious
+security reasons. See \*(L"\s-1SECURITY\s0 \s-1CONSIDERATIONS\s0\*(R" in readers.conf\&(5) for
+discussion.
+.IP "\fB\-u\fR \fIusername\fR" 4
+.IX Item "-u username"
+Authenticate as \fIusername\fR. This option is useful only for testing (so
+that you can test your authentication system easily) and does not work
+when \fBckpasswd\fR is run by \fBnnrpd\fR. If this option is given, \fB\-p\fR must
+also be given.
+.SH "EXAMPLES"
+.IX Header "EXAMPLES"
+See \fIreaders.conf\fR\|(5) for examples of \fInnrpd\fR\|(8) authentication configuration
+that uses \fBckpasswd\fR to check passwords.
+.PP
+An example \s-1PAM\s0 configuration for \fI/etc/pam.conf\fR that tells \fBckpasswd\fR
+to check usernames and passwords against system accounts is:
+.PP
+.Vb 2
+\& nnrpd auth required pam_unix.so
+\& nnrpd account required pam_unix.so
+.Ve
+.PP
+Your system may want you to instead create a file named \fInnrpd\fR in
+\&\fI/etc/pam.d\fR with lines like:
+.PP
+.Vb 2
+\& auth required pam_unix.so
+\& account required pam_unix.so
+.Ve
+.PP
+This is only the simplest configuration. You may be able to include
+common shared files, and you may want to stack other modules, either to
+allow different authentication methods or to apply restrictions like lists
+of users who can't authenticate using \fBckpasswd\fR. The best guide is the
+documentation for your system and the other \s-1PAM\s0 configurations you're
+already using.
+.PP
+To test to make sure that \fBckpasswd\fR is working correctly, you can run it
+manually and then give it the username (prefixed with \f(CW\*(C`ClientAuthname:\*(C'\fR)
+and password (prefixed with \f(CW\*(C`ClientPassword:\*(C'\fR) on standard input. For
+example:
+.PP
+.Vb 2
+\& (echo 'ClientAuthname: test' ; echo 'ClientPassword: testing') \e
+\& | ckpasswd \-f /path/to/passwd/file
+.Ve
+.PP
+will check a username of \f(CW\*(C`test\*(C'\fR and a password of \f(CW\*(C`testing\*(C'\fR against the
+username and passwords stored in \fI/path/to/passwd/file\fR. On success,
+\&\fBckpasswd\fR will print \f(CW\*(C`User:test\*(C'\fR and exit with status 0. On failure,
+it will print some sort of error message and exit a non-zero status.
+.SH "HISTORY"
+.IX Header "HISTORY"
+Written by Russ Allbery <rra@stanford.edu> for InterNetNews.
+.PP
+$Id: ckpasswd.8 7880 2008-06-16 20:37:13Z iulius $
+.SH "SEE ALSO"
+.IX Header "SEE ALSO"
+\&\fIreaders.conf\fR\|(5), \fInnrpd\fR\|(8)
+.PP
+Linux users who want to use \s-1PAM\s0 should read the Linux-PAM System
+Administrator's Guide at
+<http://www.kernel.org/pub/linux/libs/pam/Linux\-PAM\-html/Linux\-PAM_SAG.html>.
--- /dev/null
+.\" $Revision: 6312 $
+.TH CLIENTLIB 3
+.SH NAME
+clientlib \- NNTP clientlib part of InterNetNews library
+.SH SYNOPSIS
+.nf
+.ta \w' unsigned long 'u
+.B "extern FILE *ser_rd_fp;"
+.B "extern FILE *ser_wr_fp;"
+.B "extern char ser_line[];"
+
+.B "char *"
+.B "getserverbyfile(file)"
+.B " char *file;"
+
+.B "int"
+.B "server_init(host)"
+.B " char *host;"
+
+.B "int"
+.B "handle_server_response(response, host)"
+.B " int reponse;"
+.B " char *host;"
+
+.B "void"
+.B "put_server(text)"
+.B " char *text;"
+
+.B "int"
+.B "get_server(buff, buffsize)"
+.B " char *buff;"
+.B " int buffsize;"
+
+.B "void"
+.B "close_server()"
+.fi
+.SH DESCRIPTION
+The routines described in this manual page are part of the InterNetNews
+library,
+.IR libinn (3).
+They are replacements for the ``clientlib'' part of the NNTP distribution,
+and are intended to be used in building programs like
+.IR rrn .
+.PP
+.I Getserverbyfile
+calls
+.I GetConfigValue
+to get the name of the local NNTP server.
+It returns a pointer to static space.
+The
+.I file
+parameter is ignored.
+.PP
+.I Server_init
+opens a connect to the NNTP server at the specified
+.IR host .
+It returns the server's response code or \-1 on error.
+If a connection was made, then
+.I ser_rd_fp
+and
+.I ser_wr_fp
+can be used to read from and write to the server, respectively, and
+.I ser_line
+will contain the server's response.
+.I Ser_line
+can also be used in other routines.
+.PP
+.I Handle_server_response
+decodes the
+.IR response ,
+which comes from the server on
+.IR host.
+If the client is authorized, it returns 0.
+A client that is only allowed to read is authorized, but
+.I handle_server_response
+will print a message on the standard output.
+If the client is not authorized to talk to the server, then a message is
+printed and the routine returns \-1.
+.PP
+.I Put_server
+sends the text in
+.I buff
+to the server, adding the necessary NNTP line terminators, and flushing
+the I/O buffer.
+.PP
+.I Get_server
+reads a line of text from the server into
+.IR buff ,
+reading at most
+.I buffsize
+characters.
+Any trailing \er\en terminators are stripped off.
+.I Get_server
+returns \-1 on error.
+.PP
+.I Close_server
+sends a ``quit'' command to the server and closes the connection.
+.SH HISTORY
+Written by Rich $alz <rsalz@uunet.uu.net> for InterNetNews.
+.de R$
+This is revision \\$3, dated \\$4.
+..
+.R$ $Id: clientlib.3 6312 2003-05-04 21:40:11Z rra $
+.SH "SEE ALSO"
+libinn(3).
--- /dev/null
+.\" $Revision: 5909 $
+.TH CNFSHEADCONF 8
+.SH NAME
+cnfsheadconf \- set CNFS header
+.SH SYNOPSIS
+.B cnfsheadconf
+[
+.B \-c CLASS
+]
+[
+.B \-h
+]
+[
+.B \-w
+]
+.SH DESCRIPTION
+.I Cnfsheadconf
+reads
+.I <pathetc in inn.conf>/cycbuff.conf
+and
+.I <pathetc in inn.conf>/storage.conf
+to determine which cycbuffs are available, reads the specified cycbuff, and
+modifies the header as directed by the interactive user.
+.SH OPTIONS
+.TP
+.B \-c CLASS
+.I Cnfsheadconf
+prints status of (and modifies, if appropriate) the specified class.
+.TP
+.B \-h
+.I Cnfsheadconf
+prints usage information.
+.TP
+.B \-w
+.I Cnfsheadconf
+prompts for modifications to make to cycbuff header.
+.SH HISTORY
+Written by Katsuhiro Kondou <kondou@nec.co.jp> for InterNetNews.
+.de R$
+This is revision \\$3, dated \\$4.
+..
+.R$ $Id: cnfsheadconf.8 5909 2002-12-03 05:17:18Z vinocur $
+.SH "SEE ALSO"
+cycbuff.conf(5),
+inn.conf(5),
+storage.conf(5).
--- /dev/null
+.\" $Revision: 5909 $
+.TH CNFSSTAT 8
+.SH NAME
+cnfsstat \- show usage of cycbuffs
+.SH SYNOPSIS
+.B cnfsstat
+[
+.B \-a
+]
+[
+.B \-c CLASS
+]
+[
+.B \-h
+]
+[
+.B \-l
+[
+seconds
+]
+]
+[
+.B \-m BUFFER
+]
+[
+.B \-P
+]
+[
+.B \-p
+]
+[
+.B \-s
+]
+.SH DESCRIPTION
+.I Cnfsstat
+reads
+.I <pathetc in inn.conf>/cycbuff.conf
+and
+.I <pathetc in inn.conf>/storage.conf
+to determine which cycbuffs are available, read the specified cycbuffs, and
+shows their usage status.
+.PP
+.I Cnfsstat
+can be invoked from
+.IR rc.news (8),
+if
+.I <docnfsstat in inn.conf>
+is ``true'', and the result is written to
+.IR syslog (3).
+.SH OPTIONS
+.TP
+.B \-a
+.I Cnfsstat
+prints also the age of the oldest article in the cycbuff.
+.TP
+.B \-c CLASS
+.I Cnfsstat
+prints information only for the specified class.
+.TP
+.B \-h
+.I Cnfsstat
+prints usage information.
+.TP
+.B \-l [ seconds ]
+.I Cnfsstat
+prints a status snapshot every
+.IR seconds ,
+and only exits if there is an error.
+The default interval is 600 seconds.
+.TP
+.B \-m BUFFER
+.I Cnfsstat
+prints information about the specified buffer in a format suitable
+for mrtg.
+.TP
+.B \-P
+.I Cnfsstat
+writes PID into
+.IR <pathrun\ in\ inn.conf>/cnfsstat.pid .
+.TP
+.B \-p
+.I Cnfsstat
+prints an mrtg config file.
+.TP
+.B \-s
+.I Cnfsstat
+writes output to
+.IR syslog (3)
+instead of standard output.
+.SH HISTORY
+Written by Katsuhiro Kondou <kondou@nec.co.jp> for InterNetNews.
+.de R$
+This is revision \\$3, dated \\$4.
+..
+.R$ $Id: cnfsstat.8 5909 2002-12-03 05:17:18Z vinocur $
+.SH "SEE ALSO"
+cycbuff.conf(5),
+inn.conf(5),
+rc.news(8),
+storage.conf(5).
--- /dev/null
+.\" Automatically generated by Pod::Man v1.37, Pod::Parser v1.32
+.\"
+.\" Standard preamble:
+.\" ========================================================================
+.de Sh \" Subsection heading
+.br
+.if t .Sp
+.ne 5
+.PP
+\fB\\$1\fR
+.PP
+..
+.de Sp \" Vertical space (when we can't use .PP)
+.if t .sp .5v
+.if n .sp
+..
+.de Vb \" Begin verbatim text
+.ft CW
+.nf
+.ne \\$1
+..
+.de Ve \" End verbatim text
+.ft R
+.fi
+..
+.\" Set up some character translations and predefined strings. \*(-- will
+.\" give an unbreakable dash, \*(PI will give pi, \*(L" will give a left
+.\" double quote, and \*(R" will give a right double quote. \*(C+ will
+.\" give a nicer C++. Capital omega is used to do unbreakable dashes and
+.\" therefore won't be available. \*(C` and \*(C' expand to `' in nroff,
+.\" nothing in troff, for use with C<>.
+.tr \(*W-
+.ds C+ C\v'-.1v'\h'-1p'\s-2+\h'-1p'+\s0\v'.1v'\h'-1p'
+.ie n \{\
+. ds -- \(*W-
+. ds PI pi
+. if (\n(.H=4u)&(1m=24u) .ds -- \(*W\h'-12u'\(*W\h'-12u'-\" diablo 10 pitch
+. if (\n(.H=4u)&(1m=20u) .ds -- \(*W\h'-12u'\(*W\h'-8u'-\" diablo 12 pitch
+. ds L" ""
+. ds R" ""
+. ds C` ""
+. ds C' ""
+'br\}
+.el\{\
+. ds -- \|\(em\|
+. ds PI \(*p
+. ds L" ``
+. ds R" ''
+'br\}
+.\"
+.\" If the F register is turned on, we'll generate index entries on stderr for
+.\" titles (.TH), headers (.SH), subsections (.Sh), items (.Ip), and index
+.\" entries marked with X<> in POD. Of course, you'll have to process the
+.\" output yourself in some meaningful fashion.
+.if \nF \{\
+. de IX
+. tm Index:\\$1\t\\n%\t"\\$2"
+..
+. nr % 0
+. rr F
+.\}
+.\"
+.\" For nroff, turn off justification. Always turn off hyphenation; it makes
+.\" way too many mistakes in technical documents.
+.hy 0
+.if n .na
+.\"
+.\" Accent mark definitions (@(#)ms.acc 1.5 88/02/08 SMI; from UCB 4.2).
+.\" Fear. Run. Save yourself. No user-serviceable parts.
+. \" fudge factors for nroff and troff
+.if n \{\
+. ds #H 0
+. ds #V .8m
+. ds #F .3m
+. ds #[ \f1
+. ds #] \fP
+.\}
+.if t \{\
+. ds #H ((1u-(\\\\n(.fu%2u))*.13m)
+. ds #V .6m
+. ds #F 0
+. ds #[ \&
+. ds #] \&
+.\}
+. \" simple accents for nroff and troff
+.if n \{\
+. ds ' \&
+. ds ` \&
+. ds ^ \&
+. ds , \&
+. ds ~ ~
+. ds /
+.\}
+.if t \{\
+. ds ' \\k:\h'-(\\n(.wu*8/10-\*(#H)'\'\h"|\\n:u"
+. ds ` \\k:\h'-(\\n(.wu*8/10-\*(#H)'\`\h'|\\n:u'
+. ds ^ \\k:\h'-(\\n(.wu*10/11-\*(#H)'^\h'|\\n:u'
+. ds , \\k:\h'-(\\n(.wu*8/10)',\h'|\\n:u'
+. ds ~ \\k:\h'-(\\n(.wu-\*(#H-.1m)'~\h'|\\n:u'
+. ds / \\k:\h'-(\\n(.wu*8/10-\*(#H)'\z\(sl\h'|\\n:u'
+.\}
+. \" troff and (daisy-wheel) nroff accents
+.ds : \\k:\h'-(\\n(.wu*8/10-\*(#H+.1m+\*(#F)'\v'-\*(#V'\z.\h'.2m+\*(#F'.\h'|\\n:u'\v'\*(#V'
+.ds 8 \h'\*(#H'\(*b\h'-\*(#H'
+.ds o \\k:\h'-(\\n(.wu+\w'\(de'u-\*(#H)/2u'\v'-.3n'\*(#[\z\(de\v'.3n'\h'|\\n:u'\*(#]
+.ds d- \h'\*(#H'\(pd\h'-\w'~'u'\v'-.25m'\f2\(hy\fP\v'.25m'\h'-\*(#H'
+.ds D- D\\k:\h'-\w'D'u'\v'-.11m'\z\(hy\v'.11m'\h'|\\n:u'
+.ds th \*(#[\v'.3m'\s+1I\s-1\v'-.3m'\h'-(\w'I'u*2/3)'\s-1o\s+1\*(#]
+.ds Th \*(#[\s+2I\s-2\h'-\w'I'u*3/5'\v'-.3m'o\v'.3m'\*(#]
+.ds ae a\h'-(\w'a'u*4/10)'e
+.ds Ae A\h'-(\w'A'u*4/10)'E
+. \" corrections for vroff
+.if v .ds ~ \\k:\h'-(\\n(.wu*9/10-\*(#H)'\s-2\u~\d\s+2\h'|\\n:u'
+.if v .ds ^ \\k:\h'-(\\n(.wu*10/11-\*(#H)'\v'-.4m'^\v'.4m'\h'|\\n:u'
+. \" for low resolution devices (crt and lpr)
+.if \n(.H>23 .if \n(.V>19 \
+\{\
+. ds : e
+. ds 8 ss
+. ds o a
+. ds d- d\h'-1'\(ga
+. ds D- D\h'-1'\(hy
+. ds th \o'bp'
+. ds Th \o'LP'
+. ds ae ae
+. ds Ae AE
+.\}
+.rm #[ #] #H #V #F C
+.\" ========================================================================
+.\"
+.IX Title "CONTROL.CTL 5"
+.TH CONTROL.CTL 5 "2008-04-06" "INN 2.4.5" "InterNetNews Documentation"
+.SH "NAME"
+control.ctl \- Specify handling of Usenet control messages
+.SH "DESCRIPTION"
+.IX Header "DESCRIPTION"
+\&\fIcontrol.ctl\fR in \fIpathetc\fR is used to determine what action is taken
+when a control message is received. It is read by \fBcontrolchan\fR, which
+is normally invoked as a channel program by \fBinnd\fR. When \fIcontrol.ctl\fR
+is modified, \fBcontrolchan\fR notices this automatically and reloads it.
+.PP
+Blank lines and lines beginning with a number sign (\f(CW\*(C`#\*(C'\fR) are ignored.
+All other lines should consist of four fields separated by colons:
+.PP
+.Vb 1
+\& <type>:<from>:<newsgroups>:<action>
+.Ve
+.PP
+The first field, <type>, is the type of control message for which this
+line is valid. It should either be the name of a control message or the
+word \f(CW\*(C`all\*(C'\fR to indicate that it applies to all control messages.
+.PP
+The second field, <from>, is a shell-style pattern that matches the e\-mail
+address of the person posting the message (with the address first
+converted to lowercase). The matching is done with rules equivalent to
+those of the shell's \fIcase\fR statement; see \fIsh\fR\|(1) for more details.
+.PP
+If the control message is a newgroup or rmgroup, the third field,
+<newsgroups>, is a shell-style pattern matching the newsgroup affected by
+the control message. If the control message is a checkgroups, the third
+field is a shell-style pattern matching the newsgroups that should be
+processed for checking. If the control message is of any other type, the
+third field is ignored.
+.PP
+The fourth field, <action>, specifies what action to take with control
+messages that match this line. The following actions are understood:
+.IP "\fBdoit\fR" 4
+.IX Item "doit"
+The action requested by the control message should be performed. For
+checkgroups messages, this means that the shell commands that should
+be run will be mailed to the news administrator (the argument to
+\&\fB\-\-with\-news\-master\fR given at configure time, \f(CW\*(C`usenet\*(C'\fR by default); for
+other commands, this means that the change will be silently performed. If
+you always want notification of actions taken, use \f(CW\*(C`doit=mail\*(C'\fR instead (see
+below).
+.IP "\fBdoifarg\fR" 4
+.IX Item "doifarg"
+If the control message has an argument, this is equivalent to \fBdoit\fR. If
+it does not have an argument, this is equivalent to \fBmail\fR. This is only
+useful for entries for sendsys control messages, allowing a site to
+request its own \fInewsfeeds\fR entry by posting a \f(CW\*(C`sendsys mysite\*(C'\fR control
+message, but not allowing the entire \fInewsfeeds\fR file to be sent. This
+was intended to partially counter so-called \*(L"sendsys bombs,\*(R" where forged
+sendsys control messages were used to mailbomb people.
+.Sp
+Processing sendsys control messages is not recommended even with this
+work-around unless they are authenticated in some fashion. The risk of
+having news servers turned into anonymous mail bombing services is too
+high.
+.IP "\fBdoit\fR=\fIfile\fR" 4
+.IX Item "doit=file"
+The action is performed as in \fBdoit\fR, and additionally a log entry is
+written to the specified log file \fIfile\fR. If \fIfile\fR is the word
+\&\f(CW\*(C`mail\*(C'\fR, the log entry is mailed to the news administrator instead. An
+empty string is equivalent to \fI/dev/null\fR and says to log nothing.
+.Sp
+If \fIfile\fR starts with a slash, it is taken as the absolute filename to
+use for the log file. Otherwise, the filename is formed by prepending
+\&\fIpathlog\fR and a slash and appending \f(CW\*(C`.log\*(C'\fR. In other words, an action
+of \f(CW\*(C`doit=newgroup\*(C'\fR will log to \fIpathlog\fR/newgroup.log.
+.IP "\fBdrop\fR" 4
+.IX Item "drop"
+No action is taken and the message is ignored.
+.IP "\fBverify\-*\fR" 4
+.IX Item "verify-*"
+If the action starts with the string \f(CW\*(C`verify\-\*(C'\fR, as in:
+.Sp
+.Vb 1
+\& verify\-news.announce.newgroups
+.Ve
+.Sp
+then \s-1PGP\s0 verification of the control message will be done and the user \s-1ID\s0
+of the key of the authenticated signer will be checked against the
+expected identity defined by the rest of the string
+(\f(CW\*(C`news.announce.newgroups\*(C'\fR in the above example. This verification is
+done via \fBpgpverify\fR; see \fIpgpverify\fR\|(8) for more details.
+.Sp
+If no logging is specified (with =\fIfile\fR as mentioned below), logging will
+be done the same as with \fBdoit\fR as described above.
+.IP "\fBverify\-*\fR=\fBmail\fR" 4
+.IX Item "verify-*=mail"
+\&\s-1PGP\s0 verification is done as for the \fBverify\-*\fR action described above, and
+notification of successful newgroup and rmgroup control messages and the
+output of checkgroups messages will be mailed to the news administrator.
+(In the case of checkgroups messages, this means that the shell script that
+should be run will be mailed to the administrator.)
+.IP "\fBverify\-*\fR=\fIfile\fR" 4
+.IX Item "verify-*=file"
+\&\s-1PGP\s0 verification is done as for the \fBverify\-*\fR action described above,
+and a log entry is written to the specified file as described in
+\&\fBdoit\fR=\fIfile\fR above. (In the case of checkgroups messages, this means
+that the shell script output of the checkgroups message will be written to
+that file.)
+.IP "\fBlog\fR" 4
+.IX Item "log"
+A one-line log message is sent to standard error. \fBinnd\fR normally
+directs this to \fIpathlog\fR/errlog.
+.IP "\fBlog\fR=\fIfile\fR" 4
+.IX Item "log=file"
+A log entry is written to the specified log file, which is interpreted as
+in \fBdoit\fR=\fIfile\fR described above.
+.IP "\fBmail\fR" 4
+.IX Item "mail"
+A mail message is sent to the news administrator without taking any other
+action.
+.PP
+Processing of a checkgroups message will never actually change the
+\&\fIactive\fR file (the list of groups carried by the server). The difference
+between a \fBdoit\fR or \fBverify\fR action and a \fBmail\fR action for a
+checkgroups control message lies only in what e\-mail is sent; \fBdoit\fR or
+\&\fBverify\fR will mail the news administrator a shell script to create,
+delete, or modify newsgroups to match the checkgroups message, whereas
+\&\fBmail\fR will just mail the entire message. In either case, the news
+administrator will have to take action to implement the checkgroups
+message, and if that mail is ignored, nothing will be changed.
+.PP
+Lines are matched in order and the last matching line in the file will be
+used.
+.PP
+Use of the \fBverify\fR action for processing newgroup, rmgroup, and
+checkgroups messages is \s-1STRONGLY\s0 recommended. Abuse of control messages
+is rampant, and authentication via \s-1PGP\s0 signature is currently the only
+reliable way to be sure that a control message comes from who it claims to
+be from. Most major hierarchies are now issuing PGP-authenticated control
+messages.
+.PP
+In order to use \fBverify\fR actions, the \s-1PGP\s0 key ring of the news user must
+be populated with the \s-1PGP\s0 keys of the hierarchy maintainers whose control
+messages you want to honor. For more details on PGP-authenticated control
+messages and the \s-1URL\s0 for downloading the \s-1PGP\s0 keys of major hierarchies,
+see \fIpgpverify\fR\|(8).
+.PP
+Control messages of type cancel are handled internally by \fBinnd\fR and
+cannot be affected by any of the mechanisms described here.
+.SH "EXAMPLE"
+.IX Header "EXAMPLE"
+With the following three lines in \fIcontrol.ctl\fR:
+.PP
+.Vb 3
+\& newgroup:*:*:drop
+\& newgroup:group\-admin@isc.org:comp.*:verify\-news.announce.newgroups
+\& newgroup:kre@munnari.oz.au:aus.*:mail
+.Ve
+.PP
+a newgroup coming from \f(CW\*(C`group\-admin@isc.org\*(C'\fR will be honored if it is for
+a newsgroup in the comp.* hierarchy and if it has a valid signature
+corresponding to the \s-1PGP\s0 key with a user \s-1ID\s0 of \f(CW\*(C`news.announce.newgroups\*(C'\fR.
+If any newgroup claiming to be from \f(CW\*(C`kre@munnari.oz.au\*(C'\fR for a newsgroup
+in the aus.* hierarchy is received, it too will be honored. All other
+newgroup messages will be ignored.
+.SH "WARNINGS"
+.IX Header "WARNINGS"
+The third argument for a line affecting checkgroups does \fBnot\fR affect
+whether the line matches. It is only used after a matching line is found,
+to filter out which newsgroups listed in the checkgroups will be
+processed. This means that a line like:
+.PP
+.Vb 1
+\& checkgroups:*:*binaries*:drop
+.Ve
+.PP
+will cause \fBall\fR checkgroups control messages to be dropped unless they
+match a line after this one in \fIcontrol.ctl\fR, not just ignore newsgroups
+containing \f(CW\*(C`binaries\*(C'\fR in the name. The general rule is to never use \f(CW\*(C`*\*(C'\fR
+in the second field for a line matching checkgroups messages. There is
+unfortunately no way to do what the author of a line like the above
+probably intended to do (yet).
+.SH "HISTORY"
+.IX Header "HISTORY"
+Written by Rich \f(CW$alz\fR <rsalz@uunet.uu.net> for InterNetNews. Rewritten in
+\&\s-1POD\s0 by Russ Allbery <rra@stanford.edu>.
+.PP
+$Id: control.ctl.5 7880 2008-06-16 20:37:13Z iulius $
+.SH "SEE ALSO"
+.IX Header "SEE ALSO"
+\&\fIcontrolchan\fR\|(8), \fIinn.conf\fR\|(5), \fIinnd\fR\|(8), \fInewsfeeds\fR\|(5), \fIpgpverify\fR\|(8), \fIsh\fR\|(1).
--- /dev/null
+.\" $Revision: 5909 $
+.TH CONTROLCHAN 8
+.SH NAME
+controlchan \- channel\-fed control message handler
+.SH SYNOPSIS
+.B controlchan
+.SH DESCRIPTION
+.I Controlchan
+removes the responsibility for handling control messages
+(except cancels) from
+.IR innd (8)
+and instead processes them from a channel or file feed.
+To reduce load,
+.I controlchan
+keeps a copy of
+.I control.ctl
+in memory and checks permissions (including any required PGP headers) before any
+scripts are called. Also, the default (``bad message'') case is handled
+internally. The ``drop'' case is handled with far less fuss.
+.PP
+Normally,
+.I controlchan
+is invoked by
+.IR innd (8)
+as configured in
+.IR newsfeeds .
+An example entry is below. Make sure that you've created the newsgroup
+control.cancel so that
+.I controlchan
+doesn't have to scan through cancels, which it won't process anyway.
+.sp 1
+.in +0.5i
+.nf
+controlchan!\\
+ :!*,control,control.*,!control.cancel\\
+ :Tc,Wnsm\\
+ :<pathbin in inn.conf>/controlchan
+.fi
+.in -0.5i
+.sp 1
+Note that in the (very, very unlikely) event that you need to process
+ihave/sendme control messages, be sure that
+.I logipaddr
+is set to false in
+.IR inn.conf ,
+because in this case controlchan needs a site name, not an IP address.
+.sp 1
+.I Controlchan
+tries to report all log messages through
+.IR syslog (3),
+unless connected to an interactive terminal. To enable
+.IR syslog (3)'ing
+for versions of Perl prior to 5.6.0,
+you will need to have run ``h2ph'' on your
+system include files at some point (this is required to
+make ``Sys::Syslog'' work). If you have not done so, do this:
+.sp 1
+.nf
+.in +0.5i
+cd /usr/include
+h2ph * sys/*
+.in -0.5i
+.fi
+.sp 1
+If you run FreeBSD, you will need to run the following in addition:
+.sp 1
+.nf
+.in +0.5i
+h2ph machine/*
+.in -0.5i
+.fi
+.SH HISTORY
+Written by Katsuhiro Kondou <kondou@nec.co.jp> for InterNetNews.
+.de R$
+This is revision \\$3, dated \\$4.
+..
+.R$ $Id: controlchan.8 5909 2002-12-03 05:17:18Z vinocur $
+.SH "SEE ALSO"
+control.ctl(5),
+inn.conf(5).
--- /dev/null
+.\" Automatically generated by Pod::Man v1.37, Pod::Parser v1.32
+.\"
+.\" Standard preamble:
+.\" ========================================================================
+.de Sh \" Subsection heading
+.br
+.if t .Sp
+.ne 5
+.PP
+\fB\\$1\fR
+.PP
+..
+.de Sp \" Vertical space (when we can't use .PP)
+.if t .sp .5v
+.if n .sp
+..
+.de Vb \" Begin verbatim text
+.ft CW
+.nf
+.ne \\$1
+..
+.de Ve \" End verbatim text
+.ft R
+.fi
+..
+.\" Set up some character translations and predefined strings. \*(-- will
+.\" give an unbreakable dash, \*(PI will give pi, \*(L" will give a left
+.\" double quote, and \*(R" will give a right double quote. \*(C+ will
+.\" give a nicer C++. Capital omega is used to do unbreakable dashes and
+.\" therefore won't be available. \*(C` and \*(C' expand to `' in nroff,
+.\" nothing in troff, for use with C<>.
+.tr \(*W-
+.ds C+ C\v'-.1v'\h'-1p'\s-2+\h'-1p'+\s0\v'.1v'\h'-1p'
+.ie n \{\
+. ds -- \(*W-
+. ds PI pi
+. if (\n(.H=4u)&(1m=24u) .ds -- \(*W\h'-12u'\(*W\h'-12u'-\" diablo 10 pitch
+. if (\n(.H=4u)&(1m=20u) .ds -- \(*W\h'-12u'\(*W\h'-8u'-\" diablo 12 pitch
+. ds L" ""
+. ds R" ""
+. ds C` ""
+. ds C' ""
+'br\}
+.el\{\
+. ds -- \|\(em\|
+. ds PI \(*p
+. ds L" ``
+. ds R" ''
+'br\}
+.\"
+.\" If the F register is turned on, we'll generate index entries on stderr for
+.\" titles (.TH), headers (.SH), subsections (.Sh), items (.Ip), and index
+.\" entries marked with X<> in POD. Of course, you'll have to process the
+.\" output yourself in some meaningful fashion.
+.if \nF \{\
+. de IX
+. tm Index:\\$1\t\\n%\t"\\$2"
+..
+. nr % 0
+. rr F
+.\}
+.\"
+.\" For nroff, turn off justification. Always turn off hyphenation; it makes
+.\" way too many mistakes in technical documents.
+.hy 0
+.if n .na
+.\"
+.\" Accent mark definitions (@(#)ms.acc 1.5 88/02/08 SMI; from UCB 4.2).
+.\" Fear. Run. Save yourself. No user-serviceable parts.
+. \" fudge factors for nroff and troff
+.if n \{\
+. ds #H 0
+. ds #V .8m
+. ds #F .3m
+. ds #[ \f1
+. ds #] \fP
+.\}
+.if t \{\
+. ds #H ((1u-(\\\\n(.fu%2u))*.13m)
+. ds #V .6m
+. ds #F 0
+. ds #[ \&
+. ds #] \&
+.\}
+. \" simple accents for nroff and troff
+.if n \{\
+. ds ' \&
+. ds ` \&
+. ds ^ \&
+. ds , \&
+. ds ~ ~
+. ds /
+.\}
+.if t \{\
+. ds ' \\k:\h'-(\\n(.wu*8/10-\*(#H)'\'\h"|\\n:u"
+. ds ` \\k:\h'-(\\n(.wu*8/10-\*(#H)'\`\h'|\\n:u'
+. ds ^ \\k:\h'-(\\n(.wu*10/11-\*(#H)'^\h'|\\n:u'
+. ds , \\k:\h'-(\\n(.wu*8/10)',\h'|\\n:u'
+. ds ~ \\k:\h'-(\\n(.wu-\*(#H-.1m)'~\h'|\\n:u'
+. ds / \\k:\h'-(\\n(.wu*8/10-\*(#H)'\z\(sl\h'|\\n:u'
+.\}
+. \" troff and (daisy-wheel) nroff accents
+.ds : \\k:\h'-(\\n(.wu*8/10-\*(#H+.1m+\*(#F)'\v'-\*(#V'\z.\h'.2m+\*(#F'.\h'|\\n:u'\v'\*(#V'
+.ds 8 \h'\*(#H'\(*b\h'-\*(#H'
+.ds o \\k:\h'-(\\n(.wu+\w'\(de'u-\*(#H)/2u'\v'-.3n'\*(#[\z\(de\v'.3n'\h'|\\n:u'\*(#]
+.ds d- \h'\*(#H'\(pd\h'-\w'~'u'\v'-.25m'\f2\(hy\fP\v'.25m'\h'-\*(#H'
+.ds D- D\\k:\h'-\w'D'u'\v'-.11m'\z\(hy\v'.11m'\h'|\\n:u'
+.ds th \*(#[\v'.3m'\s+1I\s-1\v'-.3m'\h'-(\w'I'u*2/3)'\s-1o\s+1\*(#]
+.ds Th \*(#[\s+2I\s-2\h'-\w'I'u*3/5'\v'-.3m'o\v'.3m'\*(#]
+.ds ae a\h'-(\w'a'u*4/10)'e
+.ds Ae A\h'-(\w'A'u*4/10)'E
+. \" corrections for vroff
+.if v .ds ~ \\k:\h'-(\\n(.wu*9/10-\*(#H)'\s-2\u~\d\s+2\h'|\\n:u'
+.if v .ds ^ \\k:\h'-(\\n(.wu*10/11-\*(#H)'\v'-.4m'^\v'.4m'\h'|\\n:u'
+. \" for low resolution devices (crt and lpr)
+.if \n(.H>23 .if \n(.V>19 \
+\{\
+. ds : e
+. ds 8 ss
+. ds o a
+. ds d- d\h'-1'\(ga
+. ds D- D\h'-1'\(hy
+. ds th \o'bp'
+. ds Th \o'LP'
+. ds ae ae
+. ds Ae AE
+.\}
+.rm #[ #] #H #V #F C
+.\" ========================================================================
+.\"
+.IX Title "CONVDATE 1"
+.TH CONVDATE 1 "2008-04-06" "INN 2.4.5" "InterNetNews Documentation"
+.SH "NAME"
+convdate \- Convert time/date strings and numbers
+.SH "SYNOPSIS"
+.IX Header "SYNOPSIS"
+\&\fBconvdate\fR [\fB\-dhl\fR] [\fB\-c\fR | \fB\-n\fR | \fB\-s\fR] [\fIdate\fR ...]
+.SH "DESCRIPTION"
+.IX Header "DESCRIPTION"
+\&\fBconvdate\fR translates the date/time strings given on the command line,
+outputting the results one to a line. The input can either be a date in
+some format that \fIparsedate\fR\|(3) can parse or the number of seconds since
+epoch (if \fB\-c\fR is given). The output is either \fIctime\fR\|(3) results, the
+number of seconds since epoch, or a Usenet Date: header, depending on the
+options given.
+.SH "OPTIONS"
+.IX Header "OPTIONS"
+.IP "\fB\-c\fR" 4
+.IX Item "-c"
+Each argument is taken to be the number of seconds since epoch (a time_t)
+rather than a date.
+.IP "\fB\-d\fR" 4
+.IX Item "-d"
+Output a valid Usenet Date: header instead of the results of \fIctime\fR\|(3) for
+each date given on the command line. This is useful for testing the
+algorithm used to generate Date: headers for local posts. Normally, the
+date will be in \s-1UTC\s0, but see the \fB\-l\fR option.
+.IP "\fB\-h\fR" 4
+.IX Item "-h"
+Print usage information and exit.
+.IP "\fB\-l\fR" 4
+.IX Item "-l"
+Only makes sense in combination with \fB\-d\fR. If given, Date: headers
+generated will use the local time zone instead of \s-1UTC\s0.
+.IP "\fB\-n\fR" 4
+.IX Item "-n"
+Rather than outputting the results of \fIctime\fR\|(3) or a Date: header, output
+each date given as the number of seconds since epoch (a time_t). This
+option doesn't make sense in combination with \fB\-d\fR.
+.IP "\fB\-s\fR" 4
+.IX Item "-s"
+Pass each given date to \fIparsedate\fR\|(3) and print the results of \fIctime\fR\|(3) (or
+a Date: header if \fB\-d\fR is given). This is the default behavior.
+.SH "EXAMPLES"
+.IX Header "EXAMPLES"
+Note that relative times or times with partial information use the current
+time to fill in the rest of the date, so dates like \*(L"12pm\*(R" are taken to be
+12pm of the day when convdate is run. This is a property of \fIparsedate\fR\|(3);
+see the man page for more information. Most of these examples are from
+the original man page dating from 1991 and were run in the \-0400 time
+zone.
+.PP
+.Vb 2
+\& % convdate 'feb 10 10am'
+\& Sun Feb 10 10:00:00 1991
+.Ve
+.PP
+.Vb 3
+\& % convdate 12pm 5/4/90
+\& Fri Dec 13 00:00:00 1991
+\& Fri May 4 00:00:00 1990
+.Ve
+.PP
+Note that 12pm and 5/4/90 are two *separate* arguments and therefore
+result in two results. Note also that a date with no time is taken to be
+at midnight.
+.PP
+.Vb 3
+\& % convdate \-n 'feb 10 10am' '12pm 5/4/90'
+\& 666198000
+\& 641880000
+.Ve
+.PP
+.Vb 2
+\& % convdate \-c 666198000
+\& Sun Feb 10 10:00:00 1991
+.Ve
+.PP
+\&\fIctime\fR\|(3) results are in the local time zone. Compare to:
+.PP
+.Vb 2
+\& % convdate \-dc 666198000
+\& Sun, 10 Feb 1991 15:00:00 +0000 (UTC)
+.Ve
+.PP
+.Vb 2
+\& % env TZ=PST8PDT convdate \-dlc 666198000
+\& Sun, 10 Feb 1991 07:00:00 \-0800 (PST)
+.Ve
+.PP
+.Vb 2
+\& % env TZ=EST5EDT convdate \-dlc 666198000
+\& Sun, 10 Feb 1991 10:00:00 \-0500 (EST)
+.Ve
+.PP
+The system library functions generally use the environment variable \s-1TZ\s0 to
+determine (or at least override) the local time zone.
+.SH "HISTORY"
+.IX Header "HISTORY"
+Written by Rich \f(CW$alz\fR <rsalz@uunet.uu.net>, rewritten and updated by Russ
+Allbery <rra@stanford.edu> for the \fB\-d\fR and \fB\-l\fR flags.
+.PP
+$Id: convdate.1 7880 2008-06-16 20:37:13Z iulius $
+.SH "SEE ALSO"
+.IX Header "SEE ALSO"
+\&\fIparsedate\fR\|(3).
--- /dev/null
+.\" $Revision: 7062 $
+.TH CTLINND 8
+.SH NAME
+ctlinnd \- control the InterNetNews daemon
+.SH SYNOPSIS
+.B ctlinnd
+[
+.B \-h
+]
+[
+.B \-s
+]
+[
+.BI \-t " timeout"
+]
+.I command
+[
+.I argument...
+]
+.SH DESCRIPTION
+.I Ctlinnd
+sends a message to the control channel of
+.IR innd (8),
+the InterNetNews server.
+.PP
+In the normal mode of behavior, the message is sent to the server, which
+then performs the requested action and sends back a reply with a text
+message and the exit code for
+.IR ctlinnd .
+If the server successfully performed the command,
+.I ctlinnd
+will exit with a status of zero and print the reply on standard output.
+If the server could not perform the command (for example, it was told to
+remove a newsgroup that does not exist), it will direct
+.I ctlinnd
+to exit with a status of one. (Note that
+.I ctlinnd
+need not always exit immediately, see the ``\fB-t\fP'' flag.)
+The ``shutdown'', ``xabort'', and ``xexec'' commands do not generate a
+reply (because once
+.I innd
+has successfully exited, it is too late to send a reply to
+.IR ctlinnd );
+after these commands,
+.I ctlinnd
+will always exit silently with a status of zero.
+.SH OPTIONS
+.TP
+.B \-s
+If the ``\fB\-s\fP'' flag is
+used, then no message will be printed if the command was successful.
+.TP
+.B \-t timeout
+The ``\fB\-t\fP'' flag can be used to specify how long to wait for the reply
+from the server (for commands other than ``shutdown'', ``xabort'', and
+``xexec'').
+The timeout value specifies the number of seconds to wait.
+A value of zero waits forever, and a value less
+than zero indicates that no reply is needed (that is, exit immediately
+with status zero).
+When waiting for a reply,
+.I ctlinnd
+will try every two minutes to see if the server is still running, so it
+is unlikely that ``\fB\-t0\fP'' will hang.
+The default is set as
+.I <CTLINND_TIMEOUT in include/config.h>
+(typically
+.IR 0 ).
+.TP
+.B \-h
+To see a command summary, use the ``\fB\-h\fP'' flag.
+If a command is included when
+.I ctlinnd
+is invoked with the ``\fB\-h\fP'' flag, then only the usage for that command
+will be given.
+.PP
+The complete list of commands follows.
+Note that all commands have a fixed number of arguments.
+If a parameter can be an empty string, then it is necessary to
+specify it as two adjacent quotes, like "".
+.TP
+.BI addhist " <Message-ID> arr exp post token"
+Add an entry to the history database.
+This directs the server to create a history line for
+.IR Message-ID .
+The angle brackets are optional.
+.IR Arr ,
+.IR exp ,
+and
+.I post
+specify when the article arrived, what its expiration date is, and
+when it was posted.
+All three values are numbers indicating the number of seconds since the
+epoch.
+.I Exp
+being zero indicates the article does not have an Expires header.
+.I Token
+is the storage API token indicating where the article is stored.
+If the server is throttled manually, this command causes it to briefly
+open the history database.
+If the server is paused or throttled for any other reason, this command
+is rejected.
+.TP
+.BI allow " reason"
+Remote connections are allowed.
+The
+.I reason
+must be the same text given with an earlier ``reject'' command, or an
+empty string.
+.TP
+.BI begin " site"
+Begin feeding
+.IR site .
+This will cause the server to rescan the
+.I newsfeeds
+file to find the specified site and set up a newsfeed for it.
+If the site already exists, a ``drop'' is done first.
+This command is forwarded; see NOTES below.
+.TP
+.BI cancel " <Message-ID>"
+Remove the article with the specified Message-ID from the local system.
+This does
+.I not
+generate a cancel message.
+The angle brackets are optional.
+If the server is throttled manually, this command causes it to briefly
+open the history database.
+If the server is paused or throttled for any other reason, this command
+is rejected.
+.TP
+.BI changegroup " group rest"
+The newsgroup
+.I group
+is changed so that its fourth field in the
+.I active
+file becomes the value specified by the
+.I rest
+parameter.
+This may be used to make an existing group moderated or unmoderated,
+for example.
+This command can only be used while the server is running (not throttled),
+unlike
+.B newgroup
+or
+.BR rmgroup .
+.TP
+.B checkfile
+Check the syntax of the
+.I newsfeeds
+file, and display a message if any errors are found.
+The details of the errors are reported to
+.IR syslog (3).
+.TP
+.BI drop " site"
+Flush and drop
+.I site
+from the server's list of active feeds.
+This command is forwarded; see NOTES below.
+.TP
+.BI feedinfo " site"
+Print detailed information about the state of the
+feed to
+.I site
+or more brief status of all feeds if
+.I site
+is an empty string.
+.TP
+.BI flush " site"
+Flush the buffer for the specified site.
+The actions taken depend on the type of feed the site receives; see
+.IR newsfeeds (5).
+This is useful when the site is fed by a file and batching is about to start.
+If
+.I site
+is an empty string, then all sites are flushed and the
+.I active
+file and history databases are also written out.
+This command is forwarded; see NOTES below.
+.TP
+.B flushlogs
+Close the log and error log files and rename them to have a
+.I \&.old
+extension.
+The history database and
+.I active
+file are also written out.
+.TP
+.BI go " reason"
+Re-open the history database and start accepting articles after a ``pause''
+or ``throttle'' command.
+The
+.I reason
+must either be an empty string or match the text that was given
+in the earlier ``pause'' or ``throttle'' command.
+If a ``reject'' command was done, this will also do an ``allow'' command
+if the
+.I reason
+matches the text that was given in the ``reject.''
+If a ``reserve'' command was done, this will also clear the reservation if
+the
+.I reason
+matches the text that was given in the ``reserve.''
+Note that if only the history database has changed while the server is
+paused or throttled, it is not necessary to send it a ``reload'' command
+before sending it a ``go'' command.
+If the server throttled itself because it accumulated too many I/O
+errors, this command will reset the error count.
+If the server was not started with the ``\fB\-ny\fP'' flag, then this command also
+does a ``readers'' command with ``yes'' as the flag and
+.I reason
+as the text.
+.TP
+.BI hangup " channel"
+Close the socket on the specified incoming channel.
+This is useful when an incoming connection appears to be hung.
+.TP
+.BI help " [command]"
+Print a command summary for all commands, or just
+.I command
+if specified.
+.TP
+.BI kill " signal site"
+Signal
+.I signal
+is sent to the specified
+.IR site ,
+which must be a channel or exploder feed.
+.I Signal
+can be a numeric signal number or the word ``hup'', ``int'', or ``term'';
+case is not significant.
+.TP
+.BI lowmark " file"
+Reset the lowmarks in the
+.I active
+file based on the contents of the given
+file. Each line in the file must be of the form:
+.IP
+.RS
+.nf
+ group low-value
+.fi
+.RE
+.IP
+for example
+.IP
+.RS
+.nf
+ comp.lang.c++ 243
+.fi
+.RE
+.TP
+.BI logmode
+Cause the server to log its current operating mode to
+.IR syslog .
+.TP
+.BI mode
+Print the server's operating mode as a multi-line summary of the parameters
+and operating state.
+.TP
+.BI name " nnn"
+Print the name and relevant information of channel number
+.IR nnn ,
+or of all channels if it is an empty string. The response is formatted as:
+.sp 1
+.in +0.5i
+.nf
+f1:f2:f3:f4:f5
+.fi
+.in -0.5i
+.sp 1
+Where the meanings of the fields are:
+.sp 1
+.in +0.5i
+.nf
+f1 name of this channel
+f2 channel number
+f3 channel type
+f4 idle time for this channel (nntp type)
+ or process id (process type)
+f5 channel status (nntp type)
+.fi
+.in -0.5i
+.sp 1
+The channel type (f3) is one of following:
+.sp 1
+.in +0.5i
+.nf
+control control channel which is used
+ for ctlinnd
+file file channel which is used for
+ file feed
+localconn local channel which is used for
+ nnrpd or rnews
+nntp nntp channel which is used for
+ current remote connection
+proc process channel which is used
+ for process feed
+remconn remote channel which will be
+ used for nntp
+.fi
+.in -0.5i
+.sp 1
+Channel status indicates whether the channel is paused or not. Nothing is
+shown unless the channel is paused, in which case ``paused'' is shown.
+A channel is paused if the number of remote connection for that label in
+.I incoming.conf
+is beyond ``max-connections'' within ``hold-time'' seconds of connection.
+.TP
+.BI newgroup " group rest creator"
+Create the specified newsgroup.
+The
+.I rest
+parameter should be the fourth field as described in
+.IR active (5);
+if it is not an equal sign, only the first letter is used.
+The
+.I creator
+should be the identity of the person creating the group as described in
+.IR active (5).
+If the newsgroup already exists, this is equivalent to the ``changegroup''
+command.
+This is the only command that has defaults.
+The
+.I creator
+can be omitted and will default to the newsmaster (as specified at configure
+time, ``usenet'' by default), and the
+.I rest
+parameter can be omitted and will default to ``y''.
+This command can only be done while the server is throttled manually
+or running; it will
+update its internal state when a ``go'' command is sent.
+This command updates the
+.I active.times
+file (see
+.IR active.times (5)).
+This command is forwarded; see NOTES below.
+.TP
+.BI param " letter value"
+Change the command-line parameters of the server.
+The combination of defaults make it possible to use the text of the Control
+header directly.
+.I Letter
+is the
+.I innd
+command-line option to set, and
+.I value
+is the new value.
+For example, ``i 5'' directs the server to allow only five incoming
+connections.
+To enable or disable the action of the ``\fB\-n\fP'' flag, use the letter ``y''
+or ``n'', respectively, for the
+.IR value .
+.TP
+.BI pause " reason"
+Pause the server so that no incoming articles are accepted.
+No existing connections are closed, but the history database is closed.
+This command should be used for short-term locks, such as when replacing
+the history files.
+If the server was not started with the ``\fB\-ny\fP'' flag, then this command also
+does a ``readers'' command with ``no'' as the flag and
+.I reason
+as the text.
+.TP
+.BI perl " flag"
+Enable or disable perl news filtering, if
+.IR <\-\-with\-perl\ is\ specified\ at\ configure> .
+If
+.I flag
+starts with the letter ``y'' then filtering is enabled. If it starts with
+``n'', then filtering is disabled.
+.TP
+.BI python " flag"
+Enable or disable Python news filtering, if
+.IR <\-\-with\-python\ is\ specified\ at\ configure> .
+If
+.I flag
+starts with the letter ``y'' then filtering is enabled. If it starts with
+``n'', then filtering is disabled.
+.TP
+.BI readers " flag text"
+Allow or disallow newsreaders.
+If
+.I flag
+starts with the letter ``n'' then newsreading is disallowed, by
+causing the server to pass the
+.I text
+as the value of the
+.IR nnrpd (8)
+\&`\fB`\-r\fP'' flag.
+If
+.I flag
+starts with the letter ``y'' and
+.I text
+is either an empty string, or the same string that was used when newsreading
+was disallowed, then newsreading will be allowed.
+.\".TP
+.\".BI refile " path group"
+.\"The article specified by
+.\".I path
+.\"is refiled as if it were posted to the newsgroup
+.\".IR group .
+.TP
+.BI reject " reason"
+Remote connections (those that would not be handed off to
+.IR nnrpd )
+are rejected, with
+.I reason
+given as the explanation.
+.TP
+.BI reload " what reason"
+The server updates its in-memory copies of various configuration files.
+.I What
+identifies what should be reloaded.
+The
+.I reason
+is reported to
+.IR syslog .
+.sp 1
+There is no way to reload the
+.I inn.conf
+file; use
+.I "ctlinnd xexec innd"
+instead.
+.sp 1
+If
+.I what
+is an empty string or the word ``all'' then everything is reloaded;
+if it is the word ``history'' then the history database is closed and opened,
+if it is the word ``incoming.conf'' then the
+.I incoming.conf
+file is reloaded; if it is the word ``active'' or ``newsfeeds'' then both
+the
+.I active
+and
+.I newsfeeds
+files are reloaded; if it is the word ``overview.fmt'' then the
+.I overview.fmt
+file is reloaded.
+.sp 1
+If
+.I <\-\-with\-perl is specified at configure>
+and it is the word ``filter.perl'' then the
+.IR filter_innd.pl
+file is reloaded. If a Perl procedure named ``filter_before_reload'' exists,
+it will be called prior to rereading
+.IR filter_innd.pl .
+If a Perl procedure named ``filter_after_reload'' exists, it will be called
+after
+.IR filter_innd.pl .
+has been reloaded. Reloading the Perl filter does not enable filtering if
+it is disabled; use
+.I perl y
+to do this. The
+.I startup_innd.pl
+file cannot be reloaded.
+.sp 1
+If
+.I <\-\-with\-python is specified at configure>
+and it is the word ``filter.python'' then the
+.I filter_innd.py
+file is reloaded. If a Python method named ``filter_before_reload'' exists,
+it will be called prior to rereading
+.IR filter_innd.py .
+If a Python method named ``__init__'' exists, it will be called
+after
+.IR filter_innd.py .
+has been reloaded. Reloading the Python filter does not enable filtering if
+it is disabled; use
+.I python y
+to do this.
+If
+.I <\-\-with\-tcl is specified at configure>
+and it is the word ``filter.tcl'' then the
+.I filter.tcl
+file is reloaded. If a TCL procedure named ``filter_before_reload'' exists,
+it will be called prior to rereading
+.IR filter.tcl.
+If a TCL procedure named ``filter_after_reload'' exists, it will be called
+after
+.I filter.tcl
+has been reloaded. Reloading the Tcl filter does not enable filtering if
+it is disabled; use
+.IR filter
+to do this.
+The
+.I startup.tcl
+file cannot be reloaded.
+.TP
+.BI renumber " group"
+Scan overview database for the specified newsgroup and update the
+low-water mark and hi-water mark in the
+.I active
+file. Regardless of the content of the overview database, the hi-water
+mark will not be decreased (decreasing it may cause duplicate article
+numbers to be assigned after a crash, which can cause serious problems
+with the tradspool storage method).
+If
+.I group
+is an empty string then all newsgroups are scanned.
+Renumber only works if overview data has been created.
+(See the description of ``enableoverview'' in
+.IR inn.conf (5)
+for details about overview creation.)
+.TP
+.BI renumberlow " file"
+This command does same as ``lowmark'' command.
+.TP
+.BI reserve " reason"
+The next ``pause'' or ``throttle'' command must use
+.I reason
+as its reason.
+This ``reservation'' is cleared by giving an empty string for the
+.IR reason .
+This command is used by programs like
+.IR expire (8)
+that want to avoid running into other instances of each other.
+.TP
+.BI rmgroup " group"
+Remove the specified newsgroup.
+This is done by editing the
+.I active
+file.
+The spool directory is not touched, and any articles in the group will
+still be expired using the default expiration parameters.
+Unlike the ``newgroup'' command, this command does not update the
+.I active.times
+file.
+This command can be done while the server is only throttled manually or running.
+This command is forwarded; see NOTES below.
+.TP
+.BI send " feed text..."
+The specified
+.I text
+is sent as a control line to the exploder
+.IR feed .
+.TP
+.BI shutdown " reason"
+The server is shut down, with the specified reason recorded in the log
+and sent to all open connections.
+.sp 1
+It is a good idea to send a ``throttle'' command first.
+.sp 1
+If Perl, Python, or TCL filtering is compiled in and enabled, certain
+functions are called at ``throttle'' or ``shutdown'' (for example, to
+save filter state to disk), consult the embedded filter documentation
+for details.
+.TP
+.BI stathist " off|filename"
+Enable or disable generation of history performance statistics. If the
+parameter is ``off'', no statistics are gathered. Otherwise statistics
+are written to the specified file. The file can be parsed by
+.IR contrib/stathist.pl .
+.TP
+.BI status " off|interval"
+Adjust frequency in seconds at which
+.I innd
+reports status informatoin to syslog. Status reporting is turned off if
+``off'' or ``0'' is specified. See ``status'' in
+.IR inn.conf (5)
+for information on how to set the startup default.
+.TP
+.BI tcl " flag"
+Enable or disable Tcl news filtering, if
+.IR <\-\-with\-tcl\ is\ specified\ at\ configure> .
+If
+.I flag
+starts with the letter ``y'' then filtering is enabled. If it starts with
+``n'', then filtering is disabled.
+.TP
+.BI throttle " reason"
+Input is throttled so that all existing connections are closed and new
+connections are rejected.
+The history database is closed.
+This should be used for long-term locks, such as when
+.I expire
+is being run.
+If the server was not started with the ``\-ny'' flag, then this command also
+does a ``readers'' command with ``no'' as the flag and
+.I reason
+as the text.
+.TP
+.BI timer " off|interval"
+Performance monitoring is turned off if ``off'' or ``0'' is specified,
+otherwise, statistics will be reported every
+.I interval
+seconds to syslog. See ``timer'' in
+.IR inn.conf (5)
+for information on how to set the startup default.
+.TP
+.BI trace " item flag"
+Tracing is turned on or off for the specified
+.IR item .
+.I Flag
+should start with the letter ``y'' or ``n'' to turn tracing on or off.
+If
+.I item
+starts with a number, then tracing is set for the specified
+.I innd
+channel, which must be for an incoming NNTP feed.
+If it starts with the letter ``i'' then general
+.I innd
+tracing is turned on or off.
+If it starts with the letter ``n'' then future
+.IR nnrpd 's
+will or will not have the ``\-t'' flag enabled, as appropriate.
+The ``n'' flag does not affect
+.IR nnrpd 's
+already running or using ``-D'' (running as a daemon).
+.TP
+.BI xabort " reason"
+The server logs the specified
+.I reason
+and then invokes the
+.IR abort (3)
+routine.
+.TP
+.BI xexec " path"
+The server gets ready to shut itself down, but instead of exiting it
+.IR exec 's
+.I <pathbin in inn.conf>/inndstart
+with all of its original arguments except for ``\fB\-r\fP''.
+.I Path
+can be any of ``innd'', ``inndstart'', or an empty string, although all
+three valid parameters have exactly the same effect.
+Any other value is an error.
+.SH NOTES
+In addition to being acted upon within the server, certain commands can
+be forwarded to the appropriate child process.
+If the site receiving the command is an exploder (such as
+.IR buffchan (8)),
+or it is a funnel that feeds into an exploder, then the
+command can be forwarded.
+In this case, the server will send a command line to the exploder that
+consists of the
+.I ctlinnd
+command name.
+If the site funnels into an exploder that has an asterisk (``*'') in its ``W''
+flag (see
+.IR newsfeeds (5)),
+then the site name will be appended to the command; otherwise no argument
+is appended.
+.SH BUGS
+.I Ctlinnd
+uses the
+.IR inndcomm (3)
+library, and is therefore limited to server replies no larger than 4k.
+.SH HISTORY
+Written by Rich $alz <rsalz@uunet.uu.net> for InterNetNews.
+.de R$
+This is revision \\$3, dated \\$4.
+..
+.R$ $Id: ctlinnd.8 7062 2004-12-19 21:41:05Z rra $
+.SH "SEE ALSO"
+active(5),
+active.times(5),
+expire(8),
+innd(8),
+inndcomm(3),
+inn.conf(5),
+newsfeeds(5),
+overview.fmt(5).
--- /dev/null
+.\" $Revision: 5909 $
+.TH CVTBATCH 8
+.SH NAME
+cvtbatch \- convert Usenet batch file to INN format
+.SH SYNOPSIS
+.I cvtbatch
+[
+.BI \-w " items"
+]
+.SH DESCRIPTION
+.I Cvtbatch
+reads standard input as a sequence of lines, converts each line, and
+writes it to standard output.
+It is used to convert simple batchfiles that contain just the article
+name to INN batchfiles that contain additional information about each
+article.
+.PP
+Each line is taken as a storage API token indicating a Usenet article.
+(Only the first word of each line is parsed; anything following
+whitespace is ignored. Lines not starting with a valid token are also
+silently ignored.)
+.PP
+If the input file consists of a series of Message-ID's, then use
+.IR grephistory (1)
+with the `\fB`\-s\fP'' flag piped into
+.IR cvtbatch .
+.SH OPTIONS
+.TP
+.B \-w
+The `\fB`\-w\fP'' flag specifies how each output line should be written.
+The items for this flag should be chosen from the ``W'' flag items as
+specified in
+.IR newsfeeds (5).
+They may be chosen from the following set:
+.PP
+.RS
+.nf
+ b Size of article in bytes
+ f full pathname of article
+ m article message-id
+ n relative pathname of article
+.fi
+.RE
+.SH HISTORY
+Written by Rich $alz <rsalz@uunet.uu.net> for InterNetNews.
+.de R$
+This is revision \\$3, dated \\$4.
+..
+.R$ $Id: cvtbatch.8 5909 2002-12-03 05:17:18Z vinocur $
+.SH "SEE ALSO"
+grephistory(1),
+inn.conf(5),
+newsfeeds(5).
--- /dev/null
+.\" Automatically generated by Pod::Man v1.37, Pod::Parser v1.32
+.\"
+.\" Standard preamble:
+.\" ========================================================================
+.de Sh \" Subsection heading
+.br
+.if t .Sp
+.ne 5
+.PP
+\fB\\$1\fR
+.PP
+..
+.de Sp \" Vertical space (when we can't use .PP)
+.if t .sp .5v
+.if n .sp
+..
+.de Vb \" Begin verbatim text
+.ft CW
+.nf
+.ne \\$1
+..
+.de Ve \" End verbatim text
+.ft R
+.fi
+..
+.\" Set up some character translations and predefined strings. \*(-- will
+.\" give an unbreakable dash, \*(PI will give pi, \*(L" will give a left
+.\" double quote, and \*(R" will give a right double quote. \*(C+ will
+.\" give a nicer C++. Capital omega is used to do unbreakable dashes and
+.\" therefore won't be available. \*(C` and \*(C' expand to `' in nroff,
+.\" nothing in troff, for use with C<>.
+.tr \(*W-
+.ds C+ C\v'-.1v'\h'-1p'\s-2+\h'-1p'+\s0\v'.1v'\h'-1p'
+.ie n \{\
+. ds -- \(*W-
+. ds PI pi
+. if (\n(.H=4u)&(1m=24u) .ds -- \(*W\h'-12u'\(*W\h'-12u'-\" diablo 10 pitch
+. if (\n(.H=4u)&(1m=20u) .ds -- \(*W\h'-12u'\(*W\h'-8u'-\" diablo 12 pitch
+. ds L" ""
+. ds R" ""
+. ds C` ""
+. ds C' ""
+'br\}
+.el\{\
+. ds -- \|\(em\|
+. ds PI \(*p
+. ds L" ``
+. ds R" ''
+'br\}
+.\"
+.\" If the F register is turned on, we'll generate index entries on stderr for
+.\" titles (.TH), headers (.SH), subsections (.Sh), items (.Ip), and index
+.\" entries marked with X<> in POD. Of course, you'll have to process the
+.\" output yourself in some meaningful fashion.
+.if \nF \{\
+. de IX
+. tm Index:\\$1\t\\n%\t"\\$2"
+..
+. nr % 0
+. rr F
+.\}
+.\"
+.\" For nroff, turn off justification. Always turn off hyphenation; it makes
+.\" way too many mistakes in technical documents.
+.hy 0
+.if n .na
+.\"
+.\" Accent mark definitions (@(#)ms.acc 1.5 88/02/08 SMI; from UCB 4.2).
+.\" Fear. Run. Save yourself. No user-serviceable parts.
+. \" fudge factors for nroff and troff
+.if n \{\
+. ds #H 0
+. ds #V .8m
+. ds #F .3m
+. ds #[ \f1
+. ds #] \fP
+.\}
+.if t \{\
+. ds #H ((1u-(\\\\n(.fu%2u))*.13m)
+. ds #V .6m
+. ds #F 0
+. ds #[ \&
+. ds #] \&
+.\}
+. \" simple accents for nroff and troff
+.if n \{\
+. ds ' \&
+. ds ` \&
+. ds ^ \&
+. ds , \&
+. ds ~ ~
+. ds /
+.\}
+.if t \{\
+. ds ' \\k:\h'-(\\n(.wu*8/10-\*(#H)'\'\h"|\\n:u"
+. ds ` \\k:\h'-(\\n(.wu*8/10-\*(#H)'\`\h'|\\n:u'
+. ds ^ \\k:\h'-(\\n(.wu*10/11-\*(#H)'^\h'|\\n:u'
+. ds , \\k:\h'-(\\n(.wu*8/10)',\h'|\\n:u'
+. ds ~ \\k:\h'-(\\n(.wu-\*(#H-.1m)'~\h'|\\n:u'
+. ds / \\k:\h'-(\\n(.wu*8/10-\*(#H)'\z\(sl\h'|\\n:u'
+.\}
+. \" troff and (daisy-wheel) nroff accents
+.ds : \\k:\h'-(\\n(.wu*8/10-\*(#H+.1m+\*(#F)'\v'-\*(#V'\z.\h'.2m+\*(#F'.\h'|\\n:u'\v'\*(#V'
+.ds 8 \h'\*(#H'\(*b\h'-\*(#H'
+.ds o \\k:\h'-(\\n(.wu+\w'\(de'u-\*(#H)/2u'\v'-.3n'\*(#[\z\(de\v'.3n'\h'|\\n:u'\*(#]
+.ds d- \h'\*(#H'\(pd\h'-\w'~'u'\v'-.25m'\f2\(hy\fP\v'.25m'\h'-\*(#H'
+.ds D- D\\k:\h'-\w'D'u'\v'-.11m'\z\(hy\v'.11m'\h'|\\n:u'
+.ds th \*(#[\v'.3m'\s+1I\s-1\v'-.3m'\h'-(\w'I'u*2/3)'\s-1o\s+1\*(#]
+.ds Th \*(#[\s+2I\s-2\h'-\w'I'u*3/5'\v'-.3m'o\v'.3m'\*(#]
+.ds ae a\h'-(\w'a'u*4/10)'e
+.ds Ae A\h'-(\w'A'u*4/10)'E
+. \" corrections for vroff
+.if v .ds ~ \\k:\h'-(\\n(.wu*9/10-\*(#H)'\s-2\u~\d\s+2\h'|\\n:u'
+.if v .ds ^ \\k:\h'-(\\n(.wu*10/11-\*(#H)'\v'-.4m'^\v'.4m'\h'|\\n:u'
+. \" for low resolution devices (crt and lpr)
+.if \n(.H>23 .if \n(.V>19 \
+\{\
+. ds : e
+. ds 8 ss
+. ds o a
+. ds d- d\h'-1'\(ga
+. ds D- D\h'-1'\(hy
+. ds th \o'bp'
+. ds Th \o'LP'
+. ds ae ae
+. ds Ae AE
+.\}
+.rm #[ #] #H #V #F C
+.\" ========================================================================
+.\"
+.IX Title "CYCBUFF.CONF 5"
+.TH CYCBUFF.CONF 5 "2008-06-07" "INN 2.4.5" "InterNetNews Documentation"
+.SH "NAME"
+cycbuff.conf \- Configuration file for INN CNFS storage method
+.SH "DESCRIPTION"
+.IX Header "DESCRIPTION"
+This file defines the cyclical buffers that make up the storage pools for
+\&\s-1CNFS\s0 (Cyclic News File System). Some options controlling the behavior of
+the \s-1CNFS\s0 storage system can also be set here. \fIcycbuff.conf\fR is required
+if the \s-1CNFS\s0 (Cyclic News File System) storage method is used. \s-1INN\s0 will
+look for it in \fIpathetc\fR (as set in \fIinn.conf\fR).
+.PP
+For information about how to configure \s-1INN\s0 to use \s-1CNFS\s0, see
+\&\fIstorage.conf\fR\|(5).
+.PP
+Blank lines and lines beginning with a hash sign (\f(CW\*(C`#\*(C'\fR) are ignored. All
+other lines must be of one of the following forms:
+.PP
+.Vb 4
+\& cycbuffupdate:<interval>
+\& refreshinterval:<interval>
+\& cycbuff:<name>:<file>:<size>
+\& metacycbuff:<name>:<buffer>[,<buffer>,...][:<mode>]
+.Ve
+.PP
+(where items enclosed in [] are optional). Order is mostly not
+significant, but all \fIcycbuff\fR lines must occur before all \fImetacycbuff\fR
+lines. Long lines can be continued on the next line by ending the line
+with a backslash (\f(CW\*(C`\e\*(C'\fR).
+.IP "cycbuffupdate:<interval>" 4
+.IX Item "cycbuffupdate:<interval>"
+Sets the number of articles are written before the cycbuff header is
+written back to disk to <interval>. Under most operating systems, the
+header doesn't have to be written to disk for the updated data to be
+available to other processes on the same system that are reading articles
+out of \s-1CNFS\s0, but any accesses to the \s-1CNFS\s0 cycbuffs over \s-1NFS\s0 will only see
+the data present at the last write of the header. After a system crash,
+all updates since the last write of the \s-1CNFS\s0 header may be lost. The
+default value, if this line is omitted, is 25, meaning that the header is
+written to disk after every 25 articles stored in that cycbuff.
+.IP "refreshinterval:<interval>" 4
+.IX Item "refreshinterval:<interval>"
+Sets the interval (in seconds) between re-reads of the cycbuff header to
+<interval>. This primarily affects \fBnnrpd\fR and controls the frequency
+with which it updates its knowledge of the current contents of the \s-1CNFS\s0
+cycbuffs. The default value, if this line is omitted, is 30.
+.IP "cycbuff:<name>:<file>:<size>" 4
+.IX Item "cycbuff:<name>:<file>:<size>"
+Configures a particular \s-1CNFS\s0 cycbuff. <name> is a symbolic name for the
+buffer, to be used later in a metacycbuff line. It must be no longer than
+seven characters. <file> is the full path to the buffer file or block
+device, and must be no longer than 63 characters. <size> is the length of
+the buffer in kilobytes (1KB is 1024 bytes). If <file> is not a block
+device, it should be <size> * 1024 bytes long.
+.IP "metacycbuff:<name>:<buffer>[,<buffer>,...][:<mode>]" 4
+.IX Item "metacycbuff:<name>:<buffer>[,<buffer>,...][:<mode>]"
+Specifies a collection of \s-1CNFS\s0 buffers that make up a single logical
+storage location from the perspective of \s-1INN\s0. Metacycbuffs are referred
+to in \fIstorage.conf\fR as storage locations for articles, so in order to
+actually put articles in a cycbuff, it has to be listed as part of some
+metacycbuff which is then referenced in \fIstorage.conf\fR.
+.Sp
+<name> is the symbolic name of the metacycbuff, referred to in the options
+field of cnfs entries in \fIstorage.conf\fR. <buffer> is the name of a
+cycbuff (the <name> part of a cycbuff line), and any number of cycbuffs
+may be specified, separated by commas.
+.Sp
+If there is more than one cycbuff in a metacycbuff, there are two ways
+that \s-1INN\s0 can distribute articles between the cycbuffs. The default mode,
+\&\s-1INTERLEAVE\s0, stores the articles in each cycbuff in a round-robin fashion,
+one article per cycbuff in the order listed. If the cycbuffs are of
+wildly different sizes, this can cause some of them to roll over much
+faster than others, and it may not give the best performance depending on
+your disk layout. The other storage mode, \s-1SEQUENTIAL\s0, instead writes to
+each cycbuff in turn until that cycbuff is full and then moves on to the
+next one, returning to the first and starting a new cycle when the last
+one is full. To specify a mode rather than leaving it at the default, add
+a colon and the mode (\s-1INTERLEAVE\s0 or \s-1SEQUENTIAL\s0) at the end of the
+metacycbuff line.
+.PP
+\&\fBinnd\fR only reads \fIcycbuff.conf\fR on startup, so if you change anything
+in this file and want \fBinnd\fR to pick up the changes, you have to stop and
+restart it. \f(CW\*(C`ctlinnd reload all ''\*(C'\fR is not sufficient.
+.PP
+When articles are stored, the cycbuff into which they're stored is saved
+as part of the article token. In order for \s-1INN\s0 to retrieve articles from
+a cycbuff, that cycbuff must be listed in \fIcycbuff.conf\fR. However, if
+\&\s-1INN\s0 should not write to a cycbuff, it doesn't need to be (and shouldn't
+be) listed in a metacycbuff.
+.PP
+This provides an easy way to retire a cycbuff. Just remove it from its
+metacycbuff, leaving in the cycbuff line, and restart \fBinnd\fR (with, for
+example, \f(CW\*(C`ctlinnd xexec innd\*(C'\fR). No new articles will be put into the
+cycbuff, but neither will any articles expire from it. After you no
+longer need the articles in the cycbuff, just remove it entirely from
+\&\fIcycbuff.conf\fR. Then all of the articles will appear to have been
+deleted to \s-1INN\s0, and the next nightly expire run will clean up any
+remaining references to them.
+.PP
+Adding a new cycbuff just requires creating it (see below), adding a
+cycbuff line, adding it to a metacycbuff, and then restarting \fBinnd\fR.
+.SH "CREATING CYCBUFFS"
+.IX Header "CREATING CYCBUFFS"
+When creating a new cycbuff, there are two different methods for creating
+the buffers in which the articles will be stored.
+.IP "1." 4
+Create a large file on top of a regular file system. The easiest way to
+do this is probably with \fIdd\fR\|(1), using a command like:
+.Sp
+.Vb 1
+\& dd if=/dev/zero of=/path/to/cycbuff bs=1024 count=<size>
+.Ve
+.Sp
+where <size> is the size from the cycbuff line in \fIcycbuff.conf\fR.
+\&\fI\s-1INSTALL\s0\fR contains a script that will generate these commands for you
+from your \fIcycbuff.conf\fR file.
+.Sp
+This is the simplest method, but has the disadvantage that very large
+files on regular file systems can be fairly slow to access, particularly
+at the end of the file, and \s-1INN\s0 incurs unnecessary file system overhead
+when accessing the cycbuff.
+.IP "2." 4
+Use block devices directly. If your operating system allows you to call
+\&\fImmap()\fR on block devices (Solaris and recent versions of Linux do, FreeBSD
+at last report does not), this is the recommended method since you can
+avoid all of the native file system overhead. Note, however, that each
+cycbuff cannot be larger than 2GB with this method, so if you need a lot
+of spool space, you may have to go back to disk files.
+.Sp
+Partition the disk to make each partition equal to or smaller than 2GB.
+If you're using Solaris, set up your partitions to avoid the first
+cylinder of the disk (or otherwise the cycbuff header will overwrite the
+disk partition table and render the cycbuffs inaccessible). Then, create
+device files for each block device you're going to use.
+.Sp
+It's not recommended to use the block device files in \fI/dev\fR, since the
+news system doesn't have permission to write to them and changing the
+permissions of the system device files may affect something else.
+Instead, use \fImknod\fR\|(1) to create a new set of block devices (in somewhere
+like \fIpathspool\fR/cycbuffs that's only writable by the news user). To do
+this, run \f(CW\*(C`ls \-Ll\*(C'\fR on the devices in \fI/dev\fR that correspond to the block
+devices that you want to use. The major and minor device numbers are in
+the fifth and sixth columns (right before the date), respectively. Then
+run mknod like:
+.Sp
+.Vb 1
+\& mknod <file> b <major> <minor>
+.Ve
+.Sp
+where <file> is the path to the device to create (matching the <file> part
+of the cycbuff line) and <major> and <minor> are the major and minor
+device numbers as discovered above.
+.Sp
+Here's a short script to do this when given the path to the system device
+file as an argument:
+.Sp
+.Vb 8
+\& #!/bin/sh
+\& base=`echo "$1" | sed 's%.*/%%'`
+\& major=`ls \-Ll "$1" | awk '{print $5}' | tr \-d ,`
+\& minor=`ls \-Ll "$1" | awk '{print $6}`
+\& mkdir \-p /usr/local/news/spool/cycbuffs
+\& mknod /usr/local/news/spool/cycbuffs/"$base" b "$major" "$minor"
+\& chown news:news /usr/local/news/spool/cycbuffs/"$base"
+\& chmod 644 /usr/local/news/spool/cycbuffs/"$base"
+.Ve
+.Sp
+Make sure that the created files are owned by the news user and news
+group, as specified at configure time (the default being \f(CW\*(C`news\*(C'\fR for
+both). Also make sure that the permissions on the devices allow the news
+user to read and write, and if you want other users on the system to be
+able to use \fBsm\fR to retrieve articles, make sure they're world\-readable.
+.PP
+Once you have everything configured properly and you start \fBinnd\fR, you
+should see messages in \fInews.notice\fR that look like:
+.PP
+.Vb 1
+\& innd: CNFS\-sm No magic cookie found for cycbuff ONE, initializing
+.Ve
+.PP
+where \s-1ONE\s0 will be whatever you called your cycbuff.
+.SH "HISTORY"
+.IX Header "HISTORY"
+Written by Katsuhiro Kondou <kondou@nec.co.jp> for InterNetNews.
+Rewritten into \s-1POD\s0 by Russ Allbery <rra@stanford.edu>.
+.PP
+$Id: cycbuff.conf.5 7880 2008-06-16 20:37:13Z iulius $
+.SH "SEE ALSO"
+.IX Header "SEE ALSO"
+\&\fIctlinnd\fR\|(8), \fIinnd\fR\|(8), \fInnrpd\fR\|(8), \fIsm\fR\|(1), \fIstorage.conf\fR\|(5)
--- /dev/null
+.TH DBZ 3 "6 Sep 1997"
+.BY "INN"
+.SH NAME
+dbzinit, dbzfresh, dbzagain, dbzclose, dbzexists, dbzfetch, dbzstore, dbzsync, dbzsize, dbzgetoptions, dbzsetoptions, dbzdebug \- database routines
+.SH SYNOPSIS
+.nf
+.B #include <dbz.h>
+.PP
+.B "bool dbzinit(const char *base)"
+.PP
+.B "bool dbzclose(void)"
+.PP
+.B "bool dbzfresh(const char *base, long size)"
+.PP
+.B "bool dbzagain(const char *base, const char *oldbase)"
+.PP
+.B "bool dbzexists(const HASH key)"
+.PP
+.B "off_t dbzfetch(const HASH key)"
+.B "bool dbzfetch(const HASH key, void *ivalue)"
+.PP
+.B "bool dbzstore(const HASH key, off_t offset)"
+.B "bool dbzstore(const HASH key, void *ivalue)"
+.PP
+.B "bool dbzsync(void)"
+.PP
+.B "long dbzsize(long nentries)"
+.PP
+.B "void dbzgetoptions(dbzoptions *opt)"
+.PP
+.B "void dbzsetoptions(const dbzoptions opt)"
+.PP
+.SH DESCRIPTION
+These functions provide an indexing system for rapid random access to a
+text file (the
+.I base
+.IR file ).
+.PP
+.I Dbz
+stores offsets into the base text file for rapid retrieval. All retrievals
+are keyed on a hash value that is generated by the
+.I HashMessageID()
+function.
+.PP
+.I Dbzinit
+opens a database,
+an index into the base file
+.IR base ,
+consisting of files
+.IB base .dir
+,
+.IB base .index
+, and
+.IB base .hash
+which must already exist.
+(If the database is new, they should be zero-length files.)
+Subsequent accesses go to that database until
+.I dbzclose
+is called to close the database.
+.PP
+.I Dbzfetch
+searches the database for the specified
+.IR key ,
+returning the corresponding
+.I value
+if any, if
+.I <\-\-enable\-tagged\-hash at configure>
+is specified. If
+.I <\-\-enable\-tagged\-hash at configure>
+is not specified, it returns true and content of
+.I ivalue
+is set.
+.I Dbzstore
+stores the
+.I key - value
+pair in the database, if
+.I <\-\-enable\-tagged\-hash at configure>
+is specified. If
+.I <\-\-enable\-tagged\-hash at configure>
+is not specified, it stores the content of
+.IR ivalue .
+.I Dbzstore
+will fail unless the database files are writable.
+.I Dbzexists
+will verify whether or not the given hash exists or not. Dbz is
+optimized for this operation and it may be significantly faster than
+.IR dbzfetch() .
+.PP
+.I Dbzfresh
+is a variant of
+.I dbzinit
+for creating a new database with more control over details.
+.PP
+.IR Dbzfresh 's
+.I size
+parameter specifies the size of the first hash table within the database,
+in key-value pairs.
+Performance will be best if the number of key-value pairs stored in the
+database does not exceed about 2/3 of
+.IR size .
+(The
+.I dbzsize
+function, given the expected number of key-value pairs,
+will suggest a database size that meets these criteria.)
+Assuming that an
+.I fseek
+offset is 4 bytes,
+the
+.B .index
+file will be
+.I 4 * size
+bytes. The
+.B .hash
+file will be
+.I DBZ_INTERNAL_HASH_SIZE * size
+bytes
+(the
+.B .dir
+file is tiny and roughly constant in size)
+until
+the number of key-value pairs exceeds about 80% of
+.IR size .
+(Nothing awful will happen if the database grows beyond 100% of
+.IR size ,
+but accesses will slow down quite a bit and the
+.B .index
+and
+.B .hash
+files will grow somewhat.)
+.PP
+.I Dbz
+stores up to
+.SM DBZ_INTERNAL_HASH_SIZE
+bytes of the message-id's hash in the
+.B .hash
+file to confirm a hit. This eliminates the need to read the base file to
+handle collisions. This replaces the tagmask feature in previous dbz
+releases.
+.PP
+A
+.I size
+of ``0''
+given to
+.I dbzfresh
+is synonymous with the local default;
+the normal default is suitable for tables of 5,000,000
+key-value pairs.
+Calling
+.I dbzinit(name)
+with the empty name is equivalent to calling
+.IR dbzfresh(name,\ 0) .
+.PP
+When databases are regenerated periodically, as in news,
+it is simplest to pick the parameters for a new database based on the old one.
+This also permits some memory of past sizes of the old database, so that
+a new database size can be chosen to cover expected fluctuations.
+.I Dbzagain
+is a variant of
+.I dbzinit
+for creating a new database as a new generation of an old database.
+The database files for
+.I oldbase
+must exist.
+.I Dbzagain
+is equivalent to calling
+.I dbzfresh
+with a
+.I size
+equal to the result of applying
+.I dbzsize
+to the largest number of entries in the
+.I oldbase
+database and its previous 10 generations.
+.PP
+When many accesses are being done by the same program,
+.I dbz
+is massively faster if its first hash table is in memory.
+If the ``pag_incore'' flag is set to INCORE_MEM,
+an attempt is made to read the table in when
+the database is opened, and
+.I dbzclose
+writes it out to disk again (if it was read successfully and
+has been modified).
+.I Dbzsetoptions
+can be used to set the
+.B pag_incore
+and
+.B exists_incore
+flag to new value which should be ``INCORE_NO'', ``INCORE_MEM'', or
+\&``INCORE_MMAP'' for the
+.B .hash
+and
+.B .index
+files separately; this does not affect the status of a database that has
+already been opened. The default is ``INCORE_NO'' for the
+.B .index
+file and ``INCORE_MMAP'' for the
+.B .hash
+file. The attempt to read the table in may fail due to memory shortage;
+in this case
+.I dbz
+fails with an error.
+.IR Store s
+to an in-memory database are not (in general) written out to the file
+until
+.IR dbzclose
+or
+.IR dbzsync ,
+so if robustness in the presence of crashes
+or concurrent accesses is crucial, in-memory databases
+should probably be avoided or the
+.B writethrough
+option should be set to ``true'';
+.PP
+If the
+.B nonblock
+option is ``true'', then writes to the
+.B .hash
+and
+.B .index
+files will be done using non-blocking I/O. This can be significantly faster if
+your platform supports non-blocking I/O with files.
+.PP
+.I Dbzsync
+causes all buffers etc. to be flushed out to the files.
+It is typically used as a precaution against crashes or concurrent accesses
+when a
+.IR dbz -using
+process will be running for a long time.
+It is a somewhat expensive operation,
+especially
+for an in-memory database.
+.PP
+Concurrent reading of databases is fairly safe,
+but there is no (inter)locking,
+so concurrent updating is not.
+.PP
+An open database occupies three
+.I stdio
+streams and two file descriptors;
+Memory consumption is negligible (except for
+.I stdio
+buffers) except for in-memory databases.
+.SH SEE ALSO
+dbm(3), history(5), libinn(3)
+.SH DIAGNOSTICS
+Functions returning
+.I bool
+values return ``true'' for success, ``false'' for failure.
+Functions returning
+.I off_t
+values return a value with
+.I \-1
+for failure.
+.I Dbzinit
+attempts to have
+.I errno
+set plausibly on return, but otherwise this is not guaranteed.
+An
+.I errno
+of
+.B EDOM
+from
+.I dbzinit
+indicates that the database did not appear to be in
+.I dbz
+format.
+.PP
+If
+.SM DBZTEST
+is defined at compile-time then a
+.I main()
+function will be included. This will do performance tests and integrity test.
+.SH HISTORY
+The original
+.I dbz
+was written by
+Jon Zeeff (zeeff@b-tech.ann-arbor.mi.us).
+Later contributions by David Butler and Mark Moraes.
+Extensive reworking,
+including this documentation,
+by Henry Spencer (henry@zoo.toronto.edu) as
+part of the C News project.
+MD5 code borrowed from RSA. Extensive reworking to remove backwards
+compatibility and to add hashes into dbz files by Clayton O'Neill (coneill@oneill.net)
+.SH BUGS
+.PP
+Unlike
+.IR dbm ,
+.I dbz
+will refuse
+to
+.I dbzstore
+with a key already in the database.
+The user is responsible for avoiding this.
+.PP
+The RFC822 case mapper implements only a first approximation to the
+hideously-complex RFC822 case rules.
+.PP
+.I Dbz
+no longer tries to be call-compatible with
+.I dbm
+in any way.
--- /dev/null
+.\" Automatically generated by Pod::Man v1.37, Pod::Parser v1.32
+.\"
+.\" Standard preamble:
+.\" ========================================================================
+.de Sh \" Subsection heading
+.br
+.if t .Sp
+.ne 5
+.PP
+\fB\\$1\fR
+.PP
+..
+.de Sp \" Vertical space (when we can't use .PP)
+.if t .sp .5v
+.if n .sp
+..
+.de Vb \" Begin verbatim text
+.ft CW
+.nf
+.ne \\$1
+..
+.de Ve \" End verbatim text
+.ft R
+.fi
+..
+.\" Set up some character translations and predefined strings. \*(-- will
+.\" give an unbreakable dash, \*(PI will give pi, \*(L" will give a left
+.\" double quote, and \*(R" will give a right double quote. \*(C+ will
+.\" give a nicer C++. Capital omega is used to do unbreakable dashes and
+.\" therefore won't be available. \*(C` and \*(C' expand to `' in nroff,
+.\" nothing in troff, for use with C<>.
+.tr \(*W-
+.ds C+ C\v'-.1v'\h'-1p'\s-2+\h'-1p'+\s0\v'.1v'\h'-1p'
+.ie n \{\
+. ds -- \(*W-
+. ds PI pi
+. if (\n(.H=4u)&(1m=24u) .ds -- \(*W\h'-12u'\(*W\h'-12u'-\" diablo 10 pitch
+. if (\n(.H=4u)&(1m=20u) .ds -- \(*W\h'-12u'\(*W\h'-8u'-\" diablo 12 pitch
+. ds L" ""
+. ds R" ""
+. ds C` ""
+. ds C' ""
+'br\}
+.el\{\
+. ds -- \|\(em\|
+. ds PI \(*p
+. ds L" ``
+. ds R" ''
+'br\}
+.\"
+.\" If the F register is turned on, we'll generate index entries on stderr for
+.\" titles (.TH), headers (.SH), subsections (.Sh), items (.Ip), and index
+.\" entries marked with X<> in POD. Of course, you'll have to process the
+.\" output yourself in some meaningful fashion.
+.if \nF \{\
+. de IX
+. tm Index:\\$1\t\\n%\t"\\$2"
+..
+. nr % 0
+. rr F
+.\}
+.\"
+.\" For nroff, turn off justification. Always turn off hyphenation; it makes
+.\" way too many mistakes in technical documents.
+.hy 0
+.if n .na
+.\"
+.\" Accent mark definitions (@(#)ms.acc 1.5 88/02/08 SMI; from UCB 4.2).
+.\" Fear. Run. Save yourself. No user-serviceable parts.
+. \" fudge factors for nroff and troff
+.if n \{\
+. ds #H 0
+. ds #V .8m
+. ds #F .3m
+. ds #[ \f1
+. ds #] \fP
+.\}
+.if t \{\
+. ds #H ((1u-(\\\\n(.fu%2u))*.13m)
+. ds #V .6m
+. ds #F 0
+. ds #[ \&
+. ds #] \&
+.\}
+. \" simple accents for nroff and troff
+.if n \{\
+. ds ' \&
+. ds ` \&
+. ds ^ \&
+. ds , \&
+. ds ~ ~
+. ds /
+.\}
+.if t \{\
+. ds ' \\k:\h'-(\\n(.wu*8/10-\*(#H)'\'\h"|\\n:u"
+. ds ` \\k:\h'-(\\n(.wu*8/10-\*(#H)'\`\h'|\\n:u'
+. ds ^ \\k:\h'-(\\n(.wu*10/11-\*(#H)'^\h'|\\n:u'
+. ds , \\k:\h'-(\\n(.wu*8/10)',\h'|\\n:u'
+. ds ~ \\k:\h'-(\\n(.wu-\*(#H-.1m)'~\h'|\\n:u'
+. ds / \\k:\h'-(\\n(.wu*8/10-\*(#H)'\z\(sl\h'|\\n:u'
+.\}
+. \" troff and (daisy-wheel) nroff accents
+.ds : \\k:\h'-(\\n(.wu*8/10-\*(#H+.1m+\*(#F)'\v'-\*(#V'\z.\h'.2m+\*(#F'.\h'|\\n:u'\v'\*(#V'
+.ds 8 \h'\*(#H'\(*b\h'-\*(#H'
+.ds o \\k:\h'-(\\n(.wu+\w'\(de'u-\*(#H)/2u'\v'-.3n'\*(#[\z\(de\v'.3n'\h'|\\n:u'\*(#]
+.ds d- \h'\*(#H'\(pd\h'-\w'~'u'\v'-.25m'\f2\(hy\fP\v'.25m'\h'-\*(#H'
+.ds D- D\\k:\h'-\w'D'u'\v'-.11m'\z\(hy\v'.11m'\h'|\\n:u'
+.ds th \*(#[\v'.3m'\s+1I\s-1\v'-.3m'\h'-(\w'I'u*2/3)'\s-1o\s+1\*(#]
+.ds Th \*(#[\s+2I\s-2\h'-\w'I'u*3/5'\v'-.3m'o\v'.3m'\*(#]
+.ds ae a\h'-(\w'a'u*4/10)'e
+.ds Ae A\h'-(\w'A'u*4/10)'E
+. \" corrections for vroff
+.if v .ds ~ \\k:\h'-(\\n(.wu*9/10-\*(#H)'\s-2\u~\d\s+2\h'|\\n:u'
+.if v .ds ^ \\k:\h'-(\\n(.wu*10/11-\*(#H)'\v'-.4m'^\v'.4m'\h'|\\n:u'
+. \" for low resolution devices (crt and lpr)
+.if \n(.H>23 .if \n(.V>19 \
+\{\
+. ds : e
+. ds 8 ss
+. ds o a
+. ds d- d\h'-1'\(ga
+. ds D- D\h'-1'\(hy
+. ds th \o'bp'
+. ds Th \o'LP'
+. ds ae ae
+. ds Ae AE
+.\}
+.rm #[ #] #H #V #F C
+.\" ========================================================================
+.\"
+.IX Title "DISTRIB.PATS 5"
+.TH DISTRIB.PATS 5 "2008-04-06" "INN 2.4.5" "InterNetNews Documentation"
+.SH "NAME"
+distrib.pats \- Default values for the Distribution header
+.SH "DESCRIPTION"
+.IX Header "DESCRIPTION"
+The file \fIpathetc\fR/distrib.pats is used by \fBnnrpd\fR to determine the
+default value of the Distribution header. Blank lines and lines beginning
+with a number sign (\f(CW\*(C`#\*(C'\fR) are ignored. All other lines consist of three
+fields separated by a colon:
+.PP
+.Vb 1
+\& <weight>:<pattern>:<value>
+.Ve
+.PP
+The first field is the weight to assign to this match. If a newsgroup
+matches multiple lines, the line with the highest weight is used. This
+should be an arbitrary integer greater than zero. The order of lines in
+the file is only important if groups have equal weight (in which case, the
+first matching line will be used).
+.PP
+The second field is either the name of a newsgroup or a \fIuwildmat\fR\|(3)\-style
+pattern to specify a set of newsgroups.
+.PP
+The third field is the value that should be used for the Distribution
+header of a posted article, if this line was picked as the best match and
+no Distribution header was supplied by the user. It can be an empty
+string, specifying that no Distribution header should be added.
+.PP
+When a post is received by \fBnnrpd\fR that does not already contain a
+Distribution header, each newsgroup to which an article is posted will be
+checked against this file in turn, and the matching line with the highest
+weight will be used as the value of the Distribution header. If no lines
+match, or if the matching line has an empty string for its third field, no
+header will be added.
+.SH "HISTORY"
+.IX Header "HISTORY"
+Written by Rich \f(CW$alz\fR <rsalz@uunet.uu.net> for InterNetNews. Converted to
+\&\s-1POD\s0 by Russ Allbery <rra@stanford.edu>.
+.PP
+$Id: distrib.pats.5 7880 2008-06-16 20:37:13Z iulius $
+.SH "SEE ALSO"
+.IX Header "SEE ALSO"
+\&\fIinn.conf\fR\|(5), \fInnrpd\fR\|(8), \fIuwildmat\fR\|(3)
--- /dev/null
+.\" Automatically generated by Pod::Man v1.37, Pod::Parser v1.32
+.\"
+.\" Standard preamble:
+.\" ========================================================================
+.de Sh \" Subsection heading
+.br
+.if t .Sp
+.ne 5
+.PP
+\fB\\$1\fR
+.PP
+..
+.de Sp \" Vertical space (when we can't use .PP)
+.if t .sp .5v
+.if n .sp
+..
+.de Vb \" Begin verbatim text
+.ft CW
+.nf
+.ne \\$1
+..
+.de Ve \" End verbatim text
+.ft R
+.fi
+..
+.\" Set up some character translations and predefined strings. \*(-- will
+.\" give an unbreakable dash, \*(PI will give pi, \*(L" will give a left
+.\" double quote, and \*(R" will give a right double quote. \*(C+ will
+.\" give a nicer C++. Capital omega is used to do unbreakable dashes and
+.\" therefore won't be available. \*(C` and \*(C' expand to `' in nroff,
+.\" nothing in troff, for use with C<>.
+.tr \(*W-
+.ds C+ C\v'-.1v'\h'-1p'\s-2+\h'-1p'+\s0\v'.1v'\h'-1p'
+.ie n \{\
+. ds -- \(*W-
+. ds PI pi
+. if (\n(.H=4u)&(1m=24u) .ds -- \(*W\h'-12u'\(*W\h'-12u'-\" diablo 10 pitch
+. if (\n(.H=4u)&(1m=20u) .ds -- \(*W\h'-12u'\(*W\h'-8u'-\" diablo 12 pitch
+. ds L" ""
+. ds R" ""
+. ds C` ""
+. ds C' ""
+'br\}
+.el\{\
+. ds -- \|\(em\|
+. ds PI \(*p
+. ds L" ``
+. ds R" ''
+'br\}
+.\"
+.\" If the F register is turned on, we'll generate index entries on stderr for
+.\" titles (.TH), headers (.SH), subsections (.Sh), items (.Ip), and index
+.\" entries marked with X<> in POD. Of course, you'll have to process the
+.\" output yourself in some meaningful fashion.
+.if \nF \{\
+. de IX
+. tm Index:\\$1\t\\n%\t"\\$2"
+..
+. nr % 0
+. rr F
+.\}
+.\"
+.\" For nroff, turn off justification. Always turn off hyphenation; it makes
+.\" way too many mistakes in technical documents.
+.hy 0
+.if n .na
+.\"
+.\" Accent mark definitions (@(#)ms.acc 1.5 88/02/08 SMI; from UCB 4.2).
+.\" Fear. Run. Save yourself. No user-serviceable parts.
+. \" fudge factors for nroff and troff
+.if n \{\
+. ds #H 0
+. ds #V .8m
+. ds #F .3m
+. ds #[ \f1
+. ds #] \fP
+.\}
+.if t \{\
+. ds #H ((1u-(\\\\n(.fu%2u))*.13m)
+. ds #V .6m
+. ds #F 0
+. ds #[ \&
+. ds #] \&
+.\}
+. \" simple accents for nroff and troff
+.if n \{\
+. ds ' \&
+. ds ` \&
+. ds ^ \&
+. ds , \&
+. ds ~ ~
+. ds /
+.\}
+.if t \{\
+. ds ' \\k:\h'-(\\n(.wu*8/10-\*(#H)'\'\h"|\\n:u"
+. ds ` \\k:\h'-(\\n(.wu*8/10-\*(#H)'\`\h'|\\n:u'
+. ds ^ \\k:\h'-(\\n(.wu*10/11-\*(#H)'^\h'|\\n:u'
+. ds , \\k:\h'-(\\n(.wu*8/10)',\h'|\\n:u'
+. ds ~ \\k:\h'-(\\n(.wu-\*(#H-.1m)'~\h'|\\n:u'
+. ds / \\k:\h'-(\\n(.wu*8/10-\*(#H)'\z\(sl\h'|\\n:u'
+.\}
+. \" troff and (daisy-wheel) nroff accents
+.ds : \\k:\h'-(\\n(.wu*8/10-\*(#H+.1m+\*(#F)'\v'-\*(#V'\z.\h'.2m+\*(#F'.\h'|\\n:u'\v'\*(#V'
+.ds 8 \h'\*(#H'\(*b\h'-\*(#H'
+.ds o \\k:\h'-(\\n(.wu+\w'\(de'u-\*(#H)/2u'\v'-.3n'\*(#[\z\(de\v'.3n'\h'|\\n:u'\*(#]
+.ds d- \h'\*(#H'\(pd\h'-\w'~'u'\v'-.25m'\f2\(hy\fP\v'.25m'\h'-\*(#H'
+.ds D- D\\k:\h'-\w'D'u'\v'-.11m'\z\(hy\v'.11m'\h'|\\n:u'
+.ds th \*(#[\v'.3m'\s+1I\s-1\v'-.3m'\h'-(\w'I'u*2/3)'\s-1o\s+1\*(#]
+.ds Th \*(#[\s+2I\s-2\h'-\w'I'u*3/5'\v'-.3m'o\v'.3m'\*(#]
+.ds ae a\h'-(\w'a'u*4/10)'e
+.ds Ae A\h'-(\w'A'u*4/10)'E
+. \" corrections for vroff
+.if v .ds ~ \\k:\h'-(\\n(.wu*9/10-\*(#H)'\s-2\u~\d\s+2\h'|\\n:u'
+.if v .ds ^ \\k:\h'-(\\n(.wu*10/11-\*(#H)'\v'-.4m'^\v'.4m'\h'|\\n:u'
+. \" for low resolution devices (crt and lpr)
+.if \n(.H>23 .if \n(.V>19 \
+\{\
+. ds : e
+. ds 8 ss
+. ds o a
+. ds d- d\h'-1'\(ga
+. ds D- D\h'-1'\(hy
+. ds th \o'bp'
+. ds Th \o'LP'
+. ds ae ae
+. ds Ae AE
+.\}
+.rm #[ #] #H #V #F C
+.\" ========================================================================
+.\"
+.IX Title "DOMAIN 8"
+.TH DOMAIN 8 "2008-04-06" "INN 2.4.5" "InterNetNews Documentation"
+.SH "NAME"
+domain \- nnrpd domain resolver
+.SH "SYNOPSIS"
+.IX Header "SYNOPSIS"
+\&\fBdomain\fR \fBdomainname\fR
+.SH "DESCRIPTION"
+.IX Header "DESCRIPTION"
+This program can be used in \fIreaders.conf\fR to grant access based on the
+subdomain part of the remote hostname. In particular, it only returns
+success if the remote hostname ends in \fBdomainname\fR. (A leading dot on
+\&\fBdomainname\fR is optional; even without it, the argument must match on
+dot-separated boundaries). The \*(L"username\*(R" returned is whatever initial
+part of the remote hostname remains after \fBdomainname\fR is removed. It
+is an error if there is no initial part (that is, if the remote hostname
+is \fIexactly\fR the specified \fBdomainname\fR).
+.SH "EXAMPLE"
+.IX Header "EXAMPLE"
+The following \fIreaders.conf\fR\|(5) fragment grants access to hosts with
+internal domain names:
+.PP
+.Vb 4
+\& auth internal {
+\& res: "domain .internal"
+\& default\-domain: "example.com"
+\& }
+.Ve
+.PP
+.Vb 4
+\& access internal {
+\& users: "*@example.com"
+\& newsgroups: example.*
+\& }
+.Ve
+.PP
+Access is granted to the example.* groups for all connections from hosts
+that resolve to hostnames ending in \f(CW\*(C`.internal\*(C'\fR; a connection from
+\&\*(L"foo.internal\*(R" would match access groups as \*(L"foo@example.com\*(R".
+.SH "BUGS"
+.IX Header "BUGS"
+It seems the code does not confirm that the matching part is actually at
+the end of the remote hostname (e.g., \*(L"domain: example.com\*(R" would match
+the remote host \*(L"foo.example.com.org\*(R" by ignoring the trailing \*(L".org\*(R"
+part).
+.PP
+Does this resolver actually provide any useful functionality not
+available by using wildcards in the \fIreaders.conf\fR\|(5) \fIhosts\fR parameter?
+If so, the example above should reflect this functionality.
+.SH "HISTORY"
+.IX Header "HISTORY"
+This documentation was written by Jeffrey M. Vinocur <jeff@litech.org>.
+.PP
+$Id: domain.8 7880 2008-06-16 20:37:13Z iulius $
+.SH "SEE ALSO"
+.IX Header "SEE ALSO"
+\&\fInnrpd\fR\|(8), \fIreaders.conf\fR\|(5)
--- /dev/null
+.\" $Revision: 5909 $
+.TH EXPIRE 8
+.SH NAME
+expire \- Usenet article and history expiration program
+.SH SYNOPSIS
+.B expire
+[
+.BI \-d " dir"
+]
+[
+.BI \-f " file"
+]
+[
+.BI \-g " file"
+]
+[
+.BI \-h " file"
+]
+[
+.B \-i
+]
+[
+.B \-N
+]
+[
+.B \-n
+]
+[
+.B \-p
+]
+[
+.BI \-r " reason"
+]
+[
+.BI \-s " size"
+]
+[
+.B \-t
+]
+[
+.BI \-v " level"
+]
+[
+.BI \-w " number"
+]
+[
+.B \-x
+]
+[
+.BI \-z " file"
+]
+[
+.I expire.ctl
+]
+.SH DESCRIPTION
+.I Expire
+scans the
+.IR history (5)-format
+text file
+.I <pathdb in inn.conf>/history
+and uses the information recorded in it to purge itself of old news articles.
+Articles stored using a storage method that has self-expire
+functionality are by default not affected by
+.IR expire 's
+primary behavior (but see the ``\fB\-N\fP'' flag to disable this). In
+this case,
+.I expire.ctl
+is ignored except ``/remember/'' line for that article;
+.I expire
+does still probe to see if the article still exists and purges the
+relevant history and overview entries if appropriate.
+However, if ``groupbaseexpiry'' in
+.I inn.conf
+is true,
+.I expire
+acts on all articles as specified by
+.I expire.ctl
+regardless of whether their storage methods have self-expire
+functionality. In this case, the ``\fB\-e\fP'', \&``\fB\-k\fP'',
+``\fB\-N\fP'', ``\fB\-p\fP'', ``\fB\-q\fP'', ``\fB\-w\fP'' and
+``\fB\-z\fP'' flags are ignored.
+.PP
+Note that
+.I expire
+never purges articles which do not match any entry in
+.IR expire.ctl .
+.SH OPTIONS
+.TP
+.B \-d dir
+If the ``\fB\-d\fP'' flag is used, then the new history file and database is
+created in the specified directory,
+.IR dir .
+This is useful when the filesystem does not have sufficient space to
+hold both the old and new history files.
+When this flag is used,
+.I expire
+leaves the server paused and creates a zero-length file named after the
+new history file, with an extension of ``.done'' to indicate that
+it has successfully completed the expiration.
+The calling script should install the new history file and un-pause the server.
+The ``\fB\-r\fP'' flag should be used with this flag.
+.TP
+.B \-f file
+To specify an alternate history file, use the ``\fB\-f\fP'' flag.
+This flag is valid when used with the ``\fB\-d\fP'', and the output will
+be written to the specified file.
+The default without ``\fB\-f\fP'' flag is ``history''.
+.TP
+.B \-g file
+If the ``\fB\-g\fP'' flag is given, then a one-line summary equivalent to the
+output of ``\fB\-v\fP 1'', except preceded by the current time, will be
+appended to the specified
+.IR file .
+.TP
+.B \-h file
+To specify an alternate input text history file, use the ``\fB\-h\fP'' flag.
+.I Expire
+uses the old
+.IR dbz (3)
+database to determine the size of the new one.
+(If ``\fB\-d\fP'' flag is not used, the output filename will be the same
+as the input filename with an extension of ``.n''.)
+The default without ``\fB\-h\fP'' flag is
+.IR <pathdb\ in\ inn.conf>/history .
+.TP
+.B \-i
+To ignore the old database, use the ``\fB\-i\fP'' flag.
+.TP
+.B \-N
+The control file is normally ignored for articles in storage methods
+which have self-expire functionality.
+If the ``\fB\-N\fP'' flag is used,
+.I expire
+still uses the control file for these articles.
+.TP
+.B \-n
+If
+.I innd
+is not running, use the ``\fB\-n\fP'' flag and
+.I expire
+will not send the ``pause'' or ``go'' commands.
+(For more details on the commands, see
+.IR ctlinnd (8)).
+Note that
+.I expire
+only needs exclusive access for a very short time \(em long enough to see
+if any new articles arrived since it first hit the end of the file, and to
+rename the new files to the working files.
+.TP
+.B \-p
+.I Expire
+makes its decisions on the time the article arrived, as found in the
+.I history
+file.
+This means articles are often kept a little longer than with other
+expiration programs that base their decisions on the article's posting
+date.
+To use the article's posting date, use the ``\fB\-p\fP'' flag.
+.TP
+.B \-r reason
+.I Expire
+normally sends a ``pause'' command to the local
+.IR innd (8)
+daemon when it needs exclusive access to the history file, using
+the string ``Expiring'' as the reason.
+To give a different reason, use the ``\fB\-r\fP'' flag.
+The process ID will be appended to the reason.
+When
+.I expire
+is finished and the new history file is ready, it sends a ``go'' command.
+See also the ``\fB\-n\fP'' flag.
+.TP
+.B \-s size
+Optimize the new history database for approximately
+.I size
+pairs (lines in
+.IR history ).
+Accurately specifying the size will create a more efficient database.
+(The size should be the estimated eventual size of the file, typically
+the size of the old file.)
+.TP
+.B \-t
+If the ``\fB\-t\fP'' flag is used, then
+.I expire
+will generate a list of the tokens that should be removed on its
+standard output, and the new history file will be left in
+.IR history.n ,
+.IR history.n.dir ,
+.I history.n.index
+and
+.IR history.n.hash .
+This flag be useful for debugging when used with the ``\fB\-n\fP''
+flags. Note that if the ``\fB\-f\fP'' flag is used, then the
+name specified with that flag will be used instead of
+.IR history .
+.TP
+.B \-v level
+The ``\fB\-v\fP'' flag is used to increase the verbosity of the program,
+generating messages to standard output.
+The
+.I level
+should be a number, where higher numbers result in more output.
+Level one will print totals of the various actions done (not valid if a
+new history file is not written), level two will print a report on each
+individual file, while level five results in multiple lines of output
+for every history line processed.
+.TP
+.B \-w number
+Use the ``\fB\-w\fP'' flag to ``warp'' time so that
+.I expire
+thinks it is running at some time other then the current time.
+The value should be a signed floating point number indicating the number
+of days to use as the offset.
+.TP
+.B \-x
+If the ``\fB\-x\fP'' flag is used, then
+.I expire
+will not create any new history files. This is most useful when combined
+with the ``\fB\-n\fP'' and `\fB`\-t\fP'' flags to see how
+different expiration policies would change the amount of disk space used.
+.TP
+.B \-z file
+If the ``\fB\-z\fP'' flag is used, then articles are not removed, but their
+names are appended to the specified
+.IR file .
+See the description of
+.I delayrm
+in
+.IR news.daily (8).
+.PP
+If a filename is specified, it is taken as the control file and parsed
+according to the rules in
+.IR expire.ctl .
+A single dash (``\-'') may be used to read the file from standard input.
+If no file is specified, the file
+.I <pathetc in inn.conf>/expire.ctl
+is read.
+.SH HISTORY
+Written by Rich $alz <rsalz@uunet.uu.net> for InterNetNews.
+.de R$
+This is revision \\$3, dated \\$4.
+..
+.R$ $Id: expire.8 5909 2002-12-03 05:17:18Z vinocur $
+.SH "SEE ALSO"
+ctlinnd(8),
+dbz(3),
+expire.ctl(5),
+history(5),
+inn.conf(5),
+innd(8),
+inndcomm(3).
--- /dev/null
+.\" Automatically generated by Pod::Man v1.37, Pod::Parser v1.32
+.\"
+.\" Standard preamble:
+.\" ========================================================================
+.de Sh \" Subsection heading
+.br
+.if t .Sp
+.ne 5
+.PP
+\fB\\$1\fR
+.PP
+..
+.de Sp \" Vertical space (when we can't use .PP)
+.if t .sp .5v
+.if n .sp
+..
+.de Vb \" Begin verbatim text
+.ft CW
+.nf
+.ne \\$1
+..
+.de Ve \" End verbatim text
+.ft R
+.fi
+..
+.\" Set up some character translations and predefined strings. \*(-- will
+.\" give an unbreakable dash, \*(PI will give pi, \*(L" will give a left
+.\" double quote, and \*(R" will give a right double quote. \*(C+ will
+.\" give a nicer C++. Capital omega is used to do unbreakable dashes and
+.\" therefore won't be available. \*(C` and \*(C' expand to `' in nroff,
+.\" nothing in troff, for use with C<>.
+.tr \(*W-
+.ds C+ C\v'-.1v'\h'-1p'\s-2+\h'-1p'+\s0\v'.1v'\h'-1p'
+.ie n \{\
+. ds -- \(*W-
+. ds PI pi
+. if (\n(.H=4u)&(1m=24u) .ds -- \(*W\h'-12u'\(*W\h'-12u'-\" diablo 10 pitch
+. if (\n(.H=4u)&(1m=20u) .ds -- \(*W\h'-12u'\(*W\h'-8u'-\" diablo 12 pitch
+. ds L" ""
+. ds R" ""
+. ds C` ""
+. ds C' ""
+'br\}
+.el\{\
+. ds -- \|\(em\|
+. ds PI \(*p
+. ds L" ``
+. ds R" ''
+'br\}
+.\"
+.\" If the F register is turned on, we'll generate index entries on stderr for
+.\" titles (.TH), headers (.SH), subsections (.Sh), items (.Ip), and index
+.\" entries marked with X<> in POD. Of course, you'll have to process the
+.\" output yourself in some meaningful fashion.
+.if \nF \{\
+. de IX
+. tm Index:\\$1\t\\n%\t"\\$2"
+..
+. nr % 0
+. rr F
+.\}
+.\"
+.\" For nroff, turn off justification. Always turn off hyphenation; it makes
+.\" way too many mistakes in technical documents.
+.hy 0
+.if n .na
+.\"
+.\" Accent mark definitions (@(#)ms.acc 1.5 88/02/08 SMI; from UCB 4.2).
+.\" Fear. Run. Save yourself. No user-serviceable parts.
+. \" fudge factors for nroff and troff
+.if n \{\
+. ds #H 0
+. ds #V .8m
+. ds #F .3m
+. ds #[ \f1
+. ds #] \fP
+.\}
+.if t \{\
+. ds #H ((1u-(\\\\n(.fu%2u))*.13m)
+. ds #V .6m
+. ds #F 0
+. ds #[ \&
+. ds #] \&
+.\}
+. \" simple accents for nroff and troff
+.if n \{\
+. ds ' \&
+. ds ` \&
+. ds ^ \&
+. ds , \&
+. ds ~ ~
+. ds /
+.\}
+.if t \{\
+. ds ' \\k:\h'-(\\n(.wu*8/10-\*(#H)'\'\h"|\\n:u"
+. ds ` \\k:\h'-(\\n(.wu*8/10-\*(#H)'\`\h'|\\n:u'
+. ds ^ \\k:\h'-(\\n(.wu*10/11-\*(#H)'^\h'|\\n:u'
+. ds , \\k:\h'-(\\n(.wu*8/10)',\h'|\\n:u'
+. ds ~ \\k:\h'-(\\n(.wu-\*(#H-.1m)'~\h'|\\n:u'
+. ds / \\k:\h'-(\\n(.wu*8/10-\*(#H)'\z\(sl\h'|\\n:u'
+.\}
+. \" troff and (daisy-wheel) nroff accents
+.ds : \\k:\h'-(\\n(.wu*8/10-\*(#H+.1m+\*(#F)'\v'-\*(#V'\z.\h'.2m+\*(#F'.\h'|\\n:u'\v'\*(#V'
+.ds 8 \h'\*(#H'\(*b\h'-\*(#H'
+.ds o \\k:\h'-(\\n(.wu+\w'\(de'u-\*(#H)/2u'\v'-.3n'\*(#[\z\(de\v'.3n'\h'|\\n:u'\*(#]
+.ds d- \h'\*(#H'\(pd\h'-\w'~'u'\v'-.25m'\f2\(hy\fP\v'.25m'\h'-\*(#H'
+.ds D- D\\k:\h'-\w'D'u'\v'-.11m'\z\(hy\v'.11m'\h'|\\n:u'
+.ds th \*(#[\v'.3m'\s+1I\s-1\v'-.3m'\h'-(\w'I'u*2/3)'\s-1o\s+1\*(#]
+.ds Th \*(#[\s+2I\s-2\h'-\w'I'u*3/5'\v'-.3m'o\v'.3m'\*(#]
+.ds ae a\h'-(\w'a'u*4/10)'e
+.ds Ae A\h'-(\w'A'u*4/10)'E
+. \" corrections for vroff
+.if v .ds ~ \\k:\h'-(\\n(.wu*9/10-\*(#H)'\s-2\u~\d\s+2\h'|\\n:u'
+.if v .ds ^ \\k:\h'-(\\n(.wu*10/11-\*(#H)'\v'-.4m'^\v'.4m'\h'|\\n:u'
+. \" for low resolution devices (crt and lpr)
+.if \n(.H>23 .if \n(.V>19 \
+\{\
+. ds : e
+. ds 8 ss
+. ds o a
+. ds d- d\h'-1'\(ga
+. ds D- D\h'-1'\(hy
+. ds th \o'bp'
+. ds Th \o'LP'
+. ds ae ae
+. ds Ae AE
+.\}
+.rm #[ #] #H #V #F C
+.\" ========================================================================
+.\"
+.IX Title "EXPIRE.CTL 5"
+.TH EXPIRE.CTL 5 "2008-04-06" "INN 2.4.5" "InterNetNews Documentation"
+.SH "NAME"
+expire.ctl \- Configuration file for article expiration
+.SH "DESCRIPTION"
+.IX Header "DESCRIPTION"
+The file \fIpathetc\fR/expire.ctl is the default configuration file for
+\&\fBexpire\fR and \fBexpireover\fR, which read it at start\-up. It serves two
+purposes: it defines how long history entries for expired or rejected
+articles are remembered, and it determines how long articles stored on the
+server are retained.
+.PP
+Normally, if all of the storage methods used by the server are
+self-expiring (such as \s-1CNFS\s0), all lines except the \f(CW\*(C`/remember/\*(C'\fR setting
+(described below) are ignored. This can be changed with the \fB\-N\fR option
+to \fBexpire\fR or \fBexpireover\fR.
+.PP
+Black lines and lines beginning with a number sign (\f(CW\*(C`#\*(C'\fR) are ignored.
+All other lines should be in one of two formats. The order of the file is
+significant, and the last matching entry will be used.
+.PP
+The first format specifies how long to keep history entries for articles
+that aren't present in the news spool. These are articles that have
+either already expired, or articles which the server rejected (when
+\&\fIremembertrash\fR is set to true in \fIinn.conf\fR). There should be one and
+only one line in this format, which looks like:
+.PP
+.Vb 1
+\& /remember/:<days>
+.Ve
+.PP
+where <days> is a decimal number that specifies the minimum number of days
+a history record for a given message \s-1ID\s0 is retained, regardless of whether
+the article is present in the spool. (History entries for articles still
+present in the spool are always retained.)
+.PP
+The primary reason to retain a record of old articles is in case a peer
+offers old articles that were previously accepted but have already
+expired. Without a history record for such articles, the server would
+accept the article again and readers would see duplicate articles.
+Articles older than a certain number of days won't be accepted by the
+server at all (see \fIartcutoff\fR in \fIinn.conf\fR\|(5) and the \fB\-c\fR flag in
+\&\fIinnd\fR\|(8)), and this setting should probably match that time period (10 days
+by default) to ensure that the server never accepts duplicates.
+.PP
+Most of the lines in this file will be in the second format, which
+consists of either four or five colon-separated fields:
+.PP
+.Vb 1
+\& <pattern>:<flag>:<min>:<default>:<max>
+.Ve
+.PP
+if \fIgroupbaseexpiry\fR is true in \fIinn.conf\fR (the default), and otherwise:
+.PP
+.Vb 1
+\& <classnum>:<min>:<default>:<max>
+.Ve
+.PP
+All lines must be in the correct format given the current setting of
+\&\fIgroupbaseexpiry\fR, and therefore the two formats cannot co-exist in the
+same file.
+.PP
+Normally, a rule matches a newsgroup through the combination of the
+<pattern> and <flag> fields. <pattern> is a \fIuwildmat\fR\|(3)\-style pattern,
+specifying the newsgroups to which the line is applied. Note that the
+last matching entry will be used, so general patterns (such as defaults
+for all groups where <pattern> is \f(CW\*(C`*\*(C'\fR) should appear at the beginning of
+the file before more specific settings.
+.PP
+The <flag> field can be used to further limit newsgroups to which the line
+applies, and should be chosen from the following set:
+.PP
+.Vb 4
+\& M Only moderated groups
+\& U Only unmoderated groups
+\& A All groups
+\& X Remove the article from all groups it appears in
+.Ve
+.PP
+One of M, U, or A must be specified. X should be used in combination with
+one of the other letters, not by itself.
+.PP
+An expiration policy is applied to every article in a newsgroup it
+matches. There is no way to set an expiration policy for articles
+crossposted to groups you don't carry that's different than other articles
+in the same group. Normally, articles are not completely deleted until
+they expire out of every group to which they were posted, but if an
+article is expired following a rule where <flag> contains X, it is deleted
+out of all newsgroups to which it was posted immediately.
+.PP
+If \fIgroupbaseexpiry\fR is instead set to false, there is no <pattern> and
+<flag> field and the above does not apply. Instead, there is a single
+<classnum> field, which is either a number matching the storage class
+number specified in \fIstorage.conf\fR or \f(CW\*(C`*\*(C'\fR to specify a default for all
+storage classes. All articles stored in a storage class will be expired
+following the instructions in the line with a matching <classnum>, and
+when articles are expired, they're always removed from all groups to which
+they were posted.
+.PP
+The remaining three fields are the same in either format, and are used to
+determine how long an article should be kept. Each field should be either
+a decimal number of days (fractions like \f(CW8.5\fR are allowed, but remember
+that articles are only removed when \fBexpire\fR or \fBexpireover\fR is run,
+normally once a day by \fBnews.daily\fR) or the word \f(CW\*(C`never\*(C'\fR.
+.PP
+The middle field, <default>, will be used as the expiration period for
+most articles. The other two fields, <min> and <max>, only come into
+play if the article requests a particular expiration date with an Expires
+header. Articles with an Expires header will be expired at the date given
+in that header, subject to the constraints that they will be retained at
+least <min> days and no longer than <max> days.
+.PP
+If <min> is set to \f(CW\*(C`never\*(C'\fR, no article matching that line will ever be
+expired. If <default> is set to \f(CW\*(C`never\*(C'\fR, no article matching that line
+without an explicit Expires header will ever be expired. If <max> is
+set to \f(CW\*(C`never\*(C'\fR, Expires headers will be honored no matter how far into
+the future they are.
+.PP
+One should think of the fields as a lower bound, the default, and an upper
+bound. Since most articles do not have an Expires header, the second
+field is the most important and most commonly applied.
+.PP
+Articles that do not match any expiration rule will not be expired, but
+this is considered an error and will result in a warning. There should
+always be a default line (a line with a <pattern> of \f(CW\*(C`*\*(C'\fR and <flag> of
+\&\f(CW\*(C`A\*(C'\fR, or a line with a <classnum> of \f(CW\*(C`*\*(C'\fR), which can explicitly state
+that articles should never expire by default if that's the desired
+configuration. The default line should generally be the first line of the
+file (except for \f(CW\*(C`/remember/\*(C'\fR) so that other expiration rules can
+override it.
+.PP
+It is often useful to honor the Expires header in articles, especially
+those in moderated groups. To do this, set <min> to zero, <default> to
+whatever normal expiration you wish, and <max> to \f(CW\*(C`never\*(C'\fR or some large
+number, like 365 days for a maximum article life of a year.
+.PP
+To ignore any Expires header, set all three fields to the same value.
+.SH "EXAMPLES"
+.IX Header "EXAMPLES"
+When \fIgroupbaseexpiry\fR is true (the default):
+.PP
+.Vb 2
+\& # Keep expired article history for 10 days, matching artcutoff.
+\& /remember/:10
+.Ve
+.PP
+.Vb 2
+\& # Most articles stay for two weeks, ignoring Expires.
+\& *:A:14:14:14
+.Ve
+.PP
+.Vb 3
+\& # Accept Expires headers in moderated groups for up to a year and
+\& # retain moderated groups for a bit longer.
+\& *:M:1:30:365
+.Ve
+.PP
+.Vb 3
+\& # Keep local groups for a long time and local project groups forever.
+\& example.*:A:90:90:90
+\& example.project.*:A:never:never:never
+.Ve
+.PP
+When \fIgroupbaseexpiry\fR is false, for class-based expiration:
+.PP
+.Vb 2
+\& # Keep expired article history for 10 days, matching artcutoff.
+\& /remember/:10
+.Ve
+.PP
+.Vb 2
+\& # Set a default expiration of seven days.
+\& *:7:7:7
+.Ve
+.PP
+.Vb 2
+\& # Class 0 is retained for two weeks.
+\& 0:14:14:14
+.Ve
+.SH "HISTORY"
+.IX Header "HISTORY"
+Written by Rich \f(CW$alz\fR <rsalz@uunet.uu.net> for InterNetNews. Converted to
+\&\s-1POD\s0 by Russ Allbery <rra@stanford.edu>.
+.PP
+$Id: expire.ctl.5 7880 2008-06-16 20:37:13Z iulius $
+.SH "SEE ALSO"
+.IX Header "SEE ALSO"
+\&\fIexpire\fR\|(8), \fIexpireover\fR\|(8), \fIinn.conf\fR\|(5), \fIinnd\fR\|(8), \fInews.daily\fR\|(8),
+\&\fIstorage.conf\fR\|(5), \fIuwildmat\fR\|(3)
--- /dev/null
+.\" Automatically generated by Pod::Man v1.37, Pod::Parser v1.32
+.\"
+.\" Standard preamble:
+.\" ========================================================================
+.de Sh \" Subsection heading
+.br
+.if t .Sp
+.ne 5
+.PP
+\fB\\$1\fR
+.PP
+..
+.de Sp \" Vertical space (when we can't use .PP)
+.if t .sp .5v
+.if n .sp
+..
+.de Vb \" Begin verbatim text
+.ft CW
+.nf
+.ne \\$1
+..
+.de Ve \" End verbatim text
+.ft R
+.fi
+..
+.\" Set up some character translations and predefined strings. \*(-- will
+.\" give an unbreakable dash, \*(PI will give pi, \*(L" will give a left
+.\" double quote, and \*(R" will give a right double quote. \*(C+ will
+.\" give a nicer C++. Capital omega is used to do unbreakable dashes and
+.\" therefore won't be available. \*(C` and \*(C' expand to `' in nroff,
+.\" nothing in troff, for use with C<>.
+.tr \(*W-
+.ds C+ C\v'-.1v'\h'-1p'\s-2+\h'-1p'+\s0\v'.1v'\h'-1p'
+.ie n \{\
+. ds -- \(*W-
+. ds PI pi
+. if (\n(.H=4u)&(1m=24u) .ds -- \(*W\h'-12u'\(*W\h'-12u'-\" diablo 10 pitch
+. if (\n(.H=4u)&(1m=20u) .ds -- \(*W\h'-12u'\(*W\h'-8u'-\" diablo 12 pitch
+. ds L" ""
+. ds R" ""
+. ds C` ""
+. ds C' ""
+'br\}
+.el\{\
+. ds -- \|\(em\|
+. ds PI \(*p
+. ds L" ``
+. ds R" ''
+'br\}
+.\"
+.\" If the F register is turned on, we'll generate index entries on stderr for
+.\" titles (.TH), headers (.SH), subsections (.Sh), items (.Ip), and index
+.\" entries marked with X<> in POD. Of course, you'll have to process the
+.\" output yourself in some meaningful fashion.
+.if \nF \{\
+. de IX
+. tm Index:\\$1\t\\n%\t"\\$2"
+..
+. nr % 0
+. rr F
+.\}
+.\"
+.\" For nroff, turn off justification. Always turn off hyphenation; it makes
+.\" way too many mistakes in technical documents.
+.hy 0
+.if n .na
+.\"
+.\" Accent mark definitions (@(#)ms.acc 1.5 88/02/08 SMI; from UCB 4.2).
+.\" Fear. Run. Save yourself. No user-serviceable parts.
+. \" fudge factors for nroff and troff
+.if n \{\
+. ds #H 0
+. ds #V .8m
+. ds #F .3m
+. ds #[ \f1
+. ds #] \fP
+.\}
+.if t \{\
+. ds #H ((1u-(\\\\n(.fu%2u))*.13m)
+. ds #V .6m
+. ds #F 0
+. ds #[ \&
+. ds #] \&
+.\}
+. \" simple accents for nroff and troff
+.if n \{\
+. ds ' \&
+. ds ` \&
+. ds ^ \&
+. ds , \&
+. ds ~ ~
+. ds /
+.\}
+.if t \{\
+. ds ' \\k:\h'-(\\n(.wu*8/10-\*(#H)'\'\h"|\\n:u"
+. ds ` \\k:\h'-(\\n(.wu*8/10-\*(#H)'\`\h'|\\n:u'
+. ds ^ \\k:\h'-(\\n(.wu*10/11-\*(#H)'^\h'|\\n:u'
+. ds , \\k:\h'-(\\n(.wu*8/10)',\h'|\\n:u'
+. ds ~ \\k:\h'-(\\n(.wu-\*(#H-.1m)'~\h'|\\n:u'
+. ds / \\k:\h'-(\\n(.wu*8/10-\*(#H)'\z\(sl\h'|\\n:u'
+.\}
+. \" troff and (daisy-wheel) nroff accents
+.ds : \\k:\h'-(\\n(.wu*8/10-\*(#H+.1m+\*(#F)'\v'-\*(#V'\z.\h'.2m+\*(#F'.\h'|\\n:u'\v'\*(#V'
+.ds 8 \h'\*(#H'\(*b\h'-\*(#H'
+.ds o \\k:\h'-(\\n(.wu+\w'\(de'u-\*(#H)/2u'\v'-.3n'\*(#[\z\(de\v'.3n'\h'|\\n:u'\*(#]
+.ds d- \h'\*(#H'\(pd\h'-\w'~'u'\v'-.25m'\f2\(hy\fP\v'.25m'\h'-\*(#H'
+.ds D- D\\k:\h'-\w'D'u'\v'-.11m'\z\(hy\v'.11m'\h'|\\n:u'
+.ds th \*(#[\v'.3m'\s+1I\s-1\v'-.3m'\h'-(\w'I'u*2/3)'\s-1o\s+1\*(#]
+.ds Th \*(#[\s+2I\s-2\h'-\w'I'u*3/5'\v'-.3m'o\v'.3m'\*(#]
+.ds ae a\h'-(\w'a'u*4/10)'e
+.ds Ae A\h'-(\w'A'u*4/10)'E
+. \" corrections for vroff
+.if v .ds ~ \\k:\h'-(\\n(.wu*9/10-\*(#H)'\s-2\u~\d\s+2\h'|\\n:u'
+.if v .ds ^ \\k:\h'-(\\n(.wu*10/11-\*(#H)'\v'-.4m'^\v'.4m'\h'|\\n:u'
+. \" for low resolution devices (crt and lpr)
+.if \n(.H>23 .if \n(.V>19 \
+\{\
+. ds : e
+. ds 8 ss
+. ds o a
+. ds d- d\h'-1'\(ga
+. ds D- D\h'-1'\(hy
+. ds th \o'bp'
+. ds Th \o'LP'
+. ds ae ae
+. ds Ae AE
+.\}
+.rm #[ #] #H #V #F C
+.\" ========================================================================
+.\"
+.IX Title "EXPIREOVER 8"
+.TH EXPIREOVER 8 "2008-04-06" "INN 2.4.5" "InterNetNews Documentation"
+.SH "NAME"
+expireover \- Expire entries from the news overview database
+.SH "SYNOPSIS"
+.IX Header "SYNOPSIS"
+\&\fBexpireover\fR [\fB\-ekNpqs\fR] [\fB\-f\fR \fIfile\fR] [\fB\-w\fR \fIoffset\fR]
+[\fB\-z\fR \fIrmfile\fR] [\fB\-Z\fR \fIlowmarkfile\fR]
+.SH "DESCRIPTION"
+.IX Header "DESCRIPTION"
+\&\fBexpireover\fR expires old entries from the news overview database. It
+reads in a list of newsgroups (by default from \fIpathdb\fR/active, but a
+different file can be specified with the \fB\-f\fR option) and then removes
+from the overview database mentions of any articles that no longer exist
+in the news spool.
+.PP
+If \fIgroupbaseexpiry\fR in \fIinn.conf\fR is true, \fBexpireover\fR also removes
+old articles from the news spool according to the expiration rules in
+\&\fIexpire.ctl\fR. Otherwise it only removes overview entries for articles
+that have already been removed by some other process, and \fB\-e\fR, \fB\-k\fR,
+\&\fB\-N\fR, \fB\-p\fR, \fB\-q\fR, \fB\-w\fR, and \fB\-z\fR are all ignored.
+.PP
+When \fIgroupbaseexpiry\fR is set, the default behavior of \fBexpireover\fR is
+to remove the article from the spool once it expires out of all of the
+newsgroups to which it was crossposted. The article is, however, removed
+from the overview database of each newsgroup as soon as it expires out of
+that individual newsgroup. The effect is that an article crossposted to
+several groups will be removed from the overview database from each group
+one-by-one as its age passes the expiration threshold for that group as
+set in \fIexpire.ctl\fR, and then when it expires out of the last newsgroup,
+it will be deleted from the news spool.
+.PP
+Articles that are stored in self-expiring storage backends such as \s-1CNFS\s0
+are normally treated differently and not expired until they expire out of
+the backend regardless of \fIexpire.ctl\fR. See \fB\-N\fR, however.
+.PP
+By default, \fBexpireover\fR purges all overview information for newsgroups
+that have been removed from the server; this behavior is suppressed if
+\&\fB\-f\fR is given.
+.SH "OPTIONS"
+.IX Header "OPTIONS"
+.IP "\fB\-e\fR" 4
+.IX Item "-e"
+Remove articles from the news spool and all overview databases as soon as
+they expire out of any newsgroup to which they are posted, rather than
+retain them until they expire out of all newsgroups. \fB\-e\fR and \fB\-k\fR
+cannot be used at the same time. This flag is ignored if
+\&\fIgroupbaseexpiry\fR is false.
+.IP "\fB\-f\fR \fIfile\fR" 4
+.IX Item "-f file"
+Use \fIfile\fR as the newsgroup list instead of \fIpathdb\fR/active. \fIfile\fR
+can be \f(CW\*(C`\-\*(C'\fR to indicate standard input. Using this flag suppresses the
+normal purge of all overview information from newsgroups that have been
+removed from the server.
+.IP "\fB\-k\fR" 4
+.IX Item "-k"
+Retain all overview information for an article, as well as the article
+itself, until it expires out of all newsgroups to which it was posted.
+This can cause articles to stick around in a newsgroup for longer than the
+\&\fIexpire.ctl\fR rules indicate, when they're crossposted. \fB\-e\fR and \fB\-k\fR
+cannot be used at the same time. This flag is ignored if
+\&\fIgroupbaseexpiry\fR is false.
+.IP "\fB\-N\fR" 4
+.IX Item "-N"
+Apply \fIexpire.ctl\fR rules to expire articles even from storage methods
+that have self-expire functionality. This may remove articles from
+self-expiring storage methods before the articles \*(L"naturally\*(R" expire.
+This flag is ignored if \fIgroupbaseexpiry\fR is false.
+.IP "\fB\-p\fR" 4
+.IX Item "-p"
+By default, \fBexpireover\fR bases decisions on whether to remove an article
+on the arrival time on the server. This means that articles may be kept a
+little longer than if the decision were based on the article's posting
+date. If this option is given, expiration decisions are based on the
+article posting date instead. This flag is ignored if \fIgroupbaseexpiry\fR
+is false.
+.IP "\fB\-q\fR" 4
+.IX Item "-q"
+\&\fBexpireover\fR normally prints statistics at the end of the expiration
+process. \fB\-q\fR suppresses this report. This flag is ignored if
+\&\fIgroupbaseexpiry\fR is false.
+.IP "\fB\-s\fR" 4
+.IX Item "-s"
+\&\fBexpireover\fR normally only checks the existence of articles in the news
+spool if querying the storage method for that article to see if it still
+exists is considered \*(L"inexpensive.\*(R" To always check the existence of all
+articles regardless of how resource-intensive this may be, use the \fB\-s\fR
+flag. See \fIstorage.conf\fR\|(5) for more information about this metric.
+.IP "\fB\-w\fR \fIoffset\fR" 4
+.IX Item "-w offset"
+\&\*(L"Warps\*(R" time so that \fBexpireover\fR thinks that it's running at some time
+other than the current time. This is occasionally useful to force groups
+to be expired or not expired without changing \fIexpire.ctl\fR for the expire
+run. \fIoffset\fR should be a signed floating point number specifying the
+number of days difference from the current time to use as \*(L"now.\*(R" This
+flag is ignored if \fIgroupbaseexpiry\fR is false.
+.IP "\fB\-z\fR \fIrmfile\fR" 4
+.IX Item "-z rmfile"
+Don't remove articles immediately but instead write the path to the
+article or the token of the article to \fIrmfile\fR, which is suitable input
+for \fIfastrm\fR\|(1). This can substantially speed up deletion of expired
+articles for those storage methods where each article is a single file
+(such as tradspool and timehash). See the description of \fIdelayrm\fR in
+\&\fInews.daily\fR\|(8) for more details. This flag is ignored if
+\&\fIgroupbaseexpiry\fR is false.
+.IP "\fB\-Z\fR \fIlowmarkfile\fR" 4
+.IX Item "-Z lowmarkfile"
+Write the lowest article numbers for each newsgroup as it's expired to the
+specified file. This file is then suitable for \f(CW\*(C`ctlinnd lowmark\*(C'\fR. See
+\&\fIctlinnd\fR\|(8) for more information.
+.SH "EXAMPLES"
+.IX Header "EXAMPLES"
+Normally \fBexpireover\fR is invoked from \fInews.daily\fR\|(8), which handles such
+things as processing the \fIrmfile\fR and \fIlowmarkfile\fR if necessary.
+Sometimes it's convenient to manually expire a particular newsgroup,
+however. This can be done with a command like:
+.PP
+.Vb 2
+\& echo example.test | expireover \-f \- \-Z /usr/local/news/tmp/lowmark
+\& ctlinnd lowmark /usr/local/news/tmp/lowmark
+.Ve
+.PP
+This can be particularly useful if a lot of articles in a particular group
+have expired but the overview information is still present, causing some
+clients to see a lot of \*(L"this article may have been cancelled\*(R" messages
+when they first enter the newsgroup.
+.SH "HISTORY"
+.IX Header "HISTORY"
+Written by Rob Robertson <rob@violet.berkeley.edu> and Rich \f(CW$alz\fR
+<rsalz@uunet.uu.net> (with help from Dave Lawrence <tale@uunet.uu.net>)
+for InterNetNews.
+.PP
+$Id: expireover.8 7880 2008-06-16 20:37:13Z iulius $
+.SH "SEE ALSO"
+.IX Header "SEE ALSO"
+\&\fIactive\fR\|(5), \fIctlinnd\fR\|(8), \fIexpire\fR\|(8), \fIexpire.ctl\fR\|(5), \fIinn.conf\fR\|(5),
+\&\fInews.daily\fR\|(8).
--- /dev/null
+.TH EXPIRERM 8
+.SH NAME
+expirerm \- remove articles that have been expired.
+.SH SYNOPSIS
+.B expirerm
+.I file
+.SH DESCRIPTION
+.I Expirerm
+is a script that removes a list of files.
+The specified
+.I file
+lists the files.
+It is sorted, and then fed into a pipeline responsible for doing
+the removal, normally
+.IR fastrm (8).
+If there seemed to be a problem removing the files, then mail is sent to
+the news administrator.
+If there were no problems, then
+.I file
+is renamed to
+.I <pathlog in inn.conf>/expire.list
+where it is kept (for safety) until the next time expiration is done.
+.SH HISTORY
+Written by Landon Curt Noll <chongo@toad.com> and
+Rich $alz <rsalz@uunet.uu.net>
+.SH "SEE ALSO"
+expire(8),
+fastrm(8),
+inn.conf(5).
+
--- /dev/null
+.\" Automatically generated by Pod::Man v1.37, Pod::Parser v1.32
+.\"
+.\" Standard preamble:
+.\" ========================================================================
+.de Sh \" Subsection heading
+.br
+.if t .Sp
+.ne 5
+.PP
+\fB\\$1\fR
+.PP
+..
+.de Sp \" Vertical space (when we can't use .PP)
+.if t .sp .5v
+.if n .sp
+..
+.de Vb \" Begin verbatim text
+.ft CW
+.nf
+.ne \\$1
+..
+.de Ve \" End verbatim text
+.ft R
+.fi
+..
+.\" Set up some character translations and predefined strings. \*(-- will
+.\" give an unbreakable dash, \*(PI will give pi, \*(L" will give a left
+.\" double quote, and \*(R" will give a right double quote. \*(C+ will
+.\" give a nicer C++. Capital omega is used to do unbreakable dashes and
+.\" therefore won't be available. \*(C` and \*(C' expand to `' in nroff,
+.\" nothing in troff, for use with C<>.
+.tr \(*W-
+.ds C+ C\v'-.1v'\h'-1p'\s-2+\h'-1p'+\s0\v'.1v'\h'-1p'
+.ie n \{\
+. ds -- \(*W-
+. ds PI pi
+. if (\n(.H=4u)&(1m=24u) .ds -- \(*W\h'-12u'\(*W\h'-12u'-\" diablo 10 pitch
+. if (\n(.H=4u)&(1m=20u) .ds -- \(*W\h'-12u'\(*W\h'-8u'-\" diablo 12 pitch
+. ds L" ""
+. ds R" ""
+. ds C` ""
+. ds C' ""
+'br\}
+.el\{\
+. ds -- \|\(em\|
+. ds PI \(*p
+. ds L" ``
+. ds R" ''
+'br\}
+.\"
+.\" If the F register is turned on, we'll generate index entries on stderr for
+.\" titles (.TH), headers (.SH), subsections (.Sh), items (.Ip), and index
+.\" entries marked with X<> in POD. Of course, you'll have to process the
+.\" output yourself in some meaningful fashion.
+.if \nF \{\
+. de IX
+. tm Index:\\$1\t\\n%\t"\\$2"
+..
+. nr % 0
+. rr F
+.\}
+.\"
+.\" For nroff, turn off justification. Always turn off hyphenation; it makes
+.\" way too many mistakes in technical documents.
+.hy 0
+.if n .na
+.\"
+.\" Accent mark definitions (@(#)ms.acc 1.5 88/02/08 SMI; from UCB 4.2).
+.\" Fear. Run. Save yourself. No user-serviceable parts.
+. \" fudge factors for nroff and troff
+.if n \{\
+. ds #H 0
+. ds #V .8m
+. ds #F .3m
+. ds #[ \f1
+. ds #] \fP
+.\}
+.if t \{\
+. ds #H ((1u-(\\\\n(.fu%2u))*.13m)
+. ds #V .6m
+. ds #F 0
+. ds #[ \&
+. ds #] \&
+.\}
+. \" simple accents for nroff and troff
+.if n \{\
+. ds ' \&
+. ds ` \&
+. ds ^ \&
+. ds , \&
+. ds ~ ~
+. ds /
+.\}
+.if t \{\
+. ds ' \\k:\h'-(\\n(.wu*8/10-\*(#H)'\'\h"|\\n:u"
+. ds ` \\k:\h'-(\\n(.wu*8/10-\*(#H)'\`\h'|\\n:u'
+. ds ^ \\k:\h'-(\\n(.wu*10/11-\*(#H)'^\h'|\\n:u'
+. ds , \\k:\h'-(\\n(.wu*8/10)',\h'|\\n:u'
+. ds ~ \\k:\h'-(\\n(.wu-\*(#H-.1m)'~\h'|\\n:u'
+. ds / \\k:\h'-(\\n(.wu*8/10-\*(#H)'\z\(sl\h'|\\n:u'
+.\}
+. \" troff and (daisy-wheel) nroff accents
+.ds : \\k:\h'-(\\n(.wu*8/10-\*(#H+.1m+\*(#F)'\v'-\*(#V'\z.\h'.2m+\*(#F'.\h'|\\n:u'\v'\*(#V'
+.ds 8 \h'\*(#H'\(*b\h'-\*(#H'
+.ds o \\k:\h'-(\\n(.wu+\w'\(de'u-\*(#H)/2u'\v'-.3n'\*(#[\z\(de\v'.3n'\h'|\\n:u'\*(#]
+.ds d- \h'\*(#H'\(pd\h'-\w'~'u'\v'-.25m'\f2\(hy\fP\v'.25m'\h'-\*(#H'
+.ds D- D\\k:\h'-\w'D'u'\v'-.11m'\z\(hy\v'.11m'\h'|\\n:u'
+.ds th \*(#[\v'.3m'\s+1I\s-1\v'-.3m'\h'-(\w'I'u*2/3)'\s-1o\s+1\*(#]
+.ds Th \*(#[\s+2I\s-2\h'-\w'I'u*3/5'\v'-.3m'o\v'.3m'\*(#]
+.ds ae a\h'-(\w'a'u*4/10)'e
+.ds Ae A\h'-(\w'A'u*4/10)'E
+. \" corrections for vroff
+.if v .ds ~ \\k:\h'-(\\n(.wu*9/10-\*(#H)'\s-2\u~\d\s+2\h'|\\n:u'
+.if v .ds ^ \\k:\h'-(\\n(.wu*10/11-\*(#H)'\v'-.4m'^\v'.4m'\h'|\\n:u'
+. \" for low resolution devices (crt and lpr)
+.if \n(.H>23 .if \n(.V>19 \
+\{\
+. ds : e
+. ds 8 ss
+. ds o a
+. ds d- d\h'-1'\(ga
+. ds D- D\h'-1'\(hy
+. ds th \o'bp'
+. ds Th \o'LP'
+. ds ae ae
+. ds Ae AE
+.\}
+.rm #[ #] #H #V #F C
+.\" ========================================================================
+.\"
+.IX Title "FASTRM 1"
+.TH FASTRM 1 "2008-04-06" "INN 2.4.5" "InterNetNews Documentation"
+.SH "NAME"
+fastrm \- Quickly remove a list of files
+.SH "SYNOPSIS"
+.IX Header "SYNOPSIS"
+\&\fBfastrm\fR [\fB\-de\fR] [\fB\-u\fR|\fB\-u\fR\fIN\fR] [\fB\-s\fR|\fB\-s\fR\fIM\fR] [\fB\-c\fR|\fB\-c\fR\fII\fR]
+\&\fIbase-directory\fR
+.SH "DESCRIPTION"
+.IX Header "DESCRIPTION"
+\&\fBfastrm\fR reads a list of either file names or storage \s-1API\s0 tokens, one per
+line, from its standard input and removes them. Storage \s-1API\s0 tokens are
+removed via the \fISMcancel()\fR interface. \fBfastrm\fR does not delete files
+safely or with an eye to security, but rather cuts every corner it can to
+delete files as fast as it can. It should therefore never be run on
+publically writable directories, or in any other environment where a
+hostile party may control the directory structure in which it is working.
+.PP
+If a file name is not an absolute path name, it is considered to be
+relative to \fIbase-directory\fR as given on the command line. The
+\&\fIbase-directory\fR parameter must be a simple absolute pathname (it must
+not contain multiple consecutive slashes or references to the special
+directories \f(CW\*(C`.\*(C'\fR or \f(CW\*(C`..\*(C'\fR).
+.PP
+\&\fBfastrm\fR is designed to be faster than the typical \f(CW\*(C`| xargs rm\*(C'\fR pipeline
+when given a sorted list of file names as input. For example, \fBfastrm\fR
+will usually \fIchdir\fR\|(2) into a directory before removing files from it,
+meaning that if its input is sorted, most names passed to \fIunlink\fR\|(2) will
+be simple names. This can substantially reduce the operating system
+overhead from directory lookups.
+.PP
+\&\fBfastrm\fR assumes that its input is valid and that it is safe to call
+\&\fIunlink\fR\|(2) on every file name it is given. As a safety measure, however,
+\&\fBfastrm\fR when running as root will check with \fIstat\fR\|(2) that a file name
+doesn't specify a directory before removing it. (In some operating
+systems, root is allowed to unlink directories, even directories which
+aren't empty, which can cause file system corruption.)
+.PP
+The input to \fBfastrm\fR should always be sorted \*(-- or even better be in the
+order file names are output by \fIfind\fR\|(1) \*(-- if speed is an issue and the
+input isn't solely storage \s-1API\s0 tokens. (It deals fine with unsorted
+input, but is unlikely to be any faster in that case than a simple \f(CW\*(C`xargs
+rm\*(C'\fR command.) Sorting may even slightly speed up the removal of storage
+\&\s-1API\s0 tokens due to caching effects, since sorting will tend to keep all of
+the tokens from a particular storage method together.
+.PP
+Various additional optimizations for removing files can be turned on
+and/or tuned with options (see below). Which options will be most
+effective depends heavily on the underlying structure of the file system,
+the way in which directories are stored and searched, and similar, often
+underdocumented, operating system implementation details. The more
+sophisticated the underlying operating system and file system, the more
+likely that it will already perform the equivalent of these optimizations
+internally.
+.SH "OPTIONS"
+.IX Header "OPTIONS"
+.IP "\fB\-d\fR" 4
+.IX Item "-d"
+Don't remove any files. Instead, print a list of the files that would be
+removed to standard output. Each line contains either the current
+directory of \fBfastrm\fR at the time it would do the unlink and the relative
+path name it would pass to \fIunlink\fR\|(2) as two fields separated by whitespace
+and a \f(CW\*(C`/\*(C'\fR, the absolute path name (as a single field) that would be
+passed to \fIunlink\fR\|(2), or the string \f(CW\*(C`Token\*(C'\fR and the storage \s-1API\s0 token that
+would be removed.
+.IP "\fB\-e\fR" 4
+.IX Item "-e"
+Treat an empty input file as an error. This is most useful when \fBfastrm\fR
+is last in a pipeline after a preceding \fIsort\fR\|(1) command, ensuring that
+\&\fBfastrm\fR will fail if the sort fails.
+.IP "\fB\-c\fR\fII\fR" 4
+.IX Item "-cI"
+Controls when \fBfastrm\fR calls \fIchdir\fR\|(2). If the number of files to be
+unlinked from a given directory is at least \fII\fR, then \fBfastrm\fR will
+change to that directory before unlinking those files. Otherwise, it will
+use either the absolute path names or a path name relative to the current
+directory (whichever is likely more efficient). The \fII\fR parameter is
+optional; if just \fB\-c\fR is given, \fB\-c1\fR is assumed, which will cause
+\&\fBfastrm\fR to always chdir before calling \fIunlink\fR\|(2). The default is
+\&\fB\-c3\fR. Use \fB\-c0\fR to prevent \fBfastrm\fR from ever using \fIchdir\fR\|(2).
+.IP "\fB\-s\fR\fIM\fR" 4
+.IX Item "-sM"
+When \fB\-s\fR is given and the number of files to remove in a directory is
+greater than \fIM\fR, rather than remove files in the order given, \fBfastrm\fR
+will open the directory and read it, unlinking files in the order that
+they appear in the directory. On systems with a per-process directory
+cache or that use a linear search to find files in a directory, this
+should make directory lookups faster. The \fIM\fR parameter is optional; if
+just \fB\-s\fR is given, \fB\-s5\fR is assumed.
+.Sp
+When this option is in effect, \fBfastrm\fR won't attempt to remove files
+that it doesn't see in the directory, possibly significantly speeding it
+up if most of the files to be removed have already been deleted. However,
+using this option requires \fBfastrm\fR to do more internal work and it also
+assumes that the order of directory listings is stable in the presence of
+calls to \fIunlink\fR\|(2) between calls to \fIreaddir\fR\|(3). This may be a dangerous
+assumption with some sophisticated file systems (and in general this
+option is only useful with file systems that use unindexed linear searches
+to find files in directories or when most of the files to be removed have
+already been deleted).
+.Sp
+This optimization is off by default.
+.IP "\fB\-u\fR\fIN\fR" 4
+.IX Item "-uN"
+Specifying this option promises that there are no symbolic links in the
+directory tree from which files are being removed. This allows \fBfastrm\fR
+to make an additional optimization to its calls to \fIchdir\fR\|(2), constructing
+a relative path using \f(CW\*(C`../..\*(C'\fR and the like to pass to \fIchdir\fR\|(2) rather
+than always using absolute paths. Since this reduces the number of
+directory lookups needed with deeply nested directory structures (such as
+that typically created by traditional news spool storage), it can be a
+significant optimization, but it breaks horribly in the presence of
+symbolic links to directories.
+.Sp
+When \fB\-u\fR is given, \fBfastrm\fR will use at most \fIN\fR levels of \f(CW\*(C`..\*(C'\fR
+segments to construct paths. \fIN\fR is optional; if just \fB\-u\fR is given,
+\&\fB\-u1\fR is assumed.
+.Sp
+This optimization is off by default.
+.PP
+\&\fBfastrm\fR also accepts \fB\-a\fR and \fB\-r\fR options, which do nothing at all
+except allow you to say \f(CW\*(C`fastrm \-usa\*(C'\fR, \f(CW\*(C`fastrm \-ussr\*(C'\fR, or \f(CW\*(C`fastrm
+\&\-user\*(C'\fR. These happen to often be convenient sets of options to use.
+.SH "EXIT STATUS"
+.IX Header "EXIT STATUS"
+\&\fBfastrm\fR exits with a status of zero if there were no problems, and an
+exit status of 1 if something went wrong. Attempting to remove a file
+that does not exist is not considered a problem.
+.SH "EXAMPLES"
+.IX Header "EXAMPLES"
+\&\fBfastrm\fR is typically invoked by \s-1INN\s0 via \fIexpirerm\fR\|(8) using a command
+like:
+.PP
+.Vb 1
+\& fastrm \-e /usr/local/news/spool/articles < expire.list
+.Ve
+.PP
+To enable all optimizations and see the affect on the order of removal
+caused by \fB\-s\fR, use:
+.PP
+.Vb 1
+\& fastrm \-d \-s \-e \-u ~news/spool/articles < expire.list
+.Ve
+.PP
+If your file system has indexed directory lookups, but you have a deeply
+nested directory structure, you may want to use a set of flags like:
+.PP
+.Vb 1
+\& fastrm \-e \-u3 ~news/spool/articles < expire.list
+.Ve
+.PP
+to strongly prefer relative paths but not to use \fIreaddir\fR\|(2) to order the
+calls to \fIunlink\fR\|(2).
+.PP
+You may want to edit \fIexpirerm\fR\|(8) to change the flags passed to \fBfastrm\fR.
+.SH "WARNINGS"
+.IX Header "WARNINGS"
+\&\fBfastrm\fR cuts corners and does not worry about security, so it does not
+use \fIchdir\fR\|(2) safely and could be tricked into removing files other than
+those that were intended if run on a specially constructed file tree or a
+file tree that is being modified while it is running. It should therefore
+never be used with world-writable directories or any other directory that
+might be controlled or modified by an attacker.
+.SH "NOTES"
+.IX Header "NOTES"
+\&\fBfastrm\fR defers opening the storage subsystem or attempting to parse any
+\&\s-1INN\s0 configuration files until it encounters a token in the list of files
+to remove. It's therefore possible to use \fBfastrm\fR outside of \s-1INN\s0 as a
+general fast file removal program.
+.SH "HISTORY"
+.IX Header "HISTORY"
+\&\fBfastrm\fR was originally written by kre@munnari.oz.au. This manual page
+rewritten in \s-1POD\s0 by Russ Allbery <rra@stanford.edu> for InterNetNews.
+.PP
+$Id: fastrm.1 7880 2008-06-16 20:37:13Z iulius $
+.SH "SEE ALSO"
+.IX Header "SEE ALSO"
+\&\fIexpirerm\fR\|(8)
--- /dev/null
+.\" $Revision: 5909 $
+.TH FILECHAN 8
+.SH NAME
+filechan \- file-writing backend for InterNetNews
+.SH SYNOPSIS
+.B filechan
+[
+.BI \-d " directory"
+]
+[
+.BI \-f " num_fields"
+]
+[
+.BI \-m " mapfile"
+]
+[
+.BI \-p " pidfile"
+]
+.SH DESCRIPTION
+.I Filechan
+reads lines from standard input and copies certain fields in
+each line into files named by other fields within the line.
+.I Filechan
+is intended to be called by
+.IR innd (8)
+as a channel feed.
+(It is not a full exploder and does not accept commands; see
+.IR newsfeeds (5)
+for a description of the difference, and
+.IR buffchan (8)
+for an exploder program.)
+.PP
+.I Filechan
+input is interpreted as a sequence of lines.
+Each line contains a fixed number of initial fields, followed by a
+variable number of filename fields.
+All fields in a line are separated by whitespace.
+The default number of initial fields is one.
+.PP
+For each line of input,
+.I filechan
+writes the initial fields, separated by whitespace and followed by a
+newline, to each of the files named in the filename fields.
+When writing to a file,
+.I filechan
+opens it in append mode and tries to lock it and change the
+ownership to the user and group who owns the directory where the file is
+being written.
+.PP
+Because the time window in which a file is open is very small, complicated
+flushing and locking protocols are not needed; a
+.IR mv (1)
+followed by a
+.IR sleep (1)
+for a couple of seconds is sufficient.
+.SH OPTIONS
+.TP
+.B \-f num_fields
+The ``\fB\-f\fP'' flag may be
+used to specify a different number of initial fields.
+.TP
+.B \-d directory
+By default,
+.I filechan
+writes its output into the directory
+.IR <pathoutgoing\ in\ inn.conf> .
+The ``\fB\-d\fP'' flag may be used to specify a directory the program should
+change to before starting.
+.TP
+.B \-p pidfile
+If the ``\fB\-p\fP'' flag is used, the program will write a line containing
+its process ID (in text) to the specified file.
+.TP
+.B \-m mapfile
+A map file may be specified by using the ``\fB\-m\fP'' flag.
+Blank lines and lines starting with a number sign (``#'') are ignored.
+All other lines should have two host names separated by a colon.
+The first field is the name that may appear in the input stream;
+the second field names the file to be used when the name in the first
+field appears.
+For example, the following map file may be used to map the short
+names used in the example below to the full domain names:
+.PP
+.RS
+.nf
+# This is a comment
+uunet:news.uu.net
+foo:foo.com
+munnari:munnari.oz.au
+.fi
+.RE
+.SH EXAMPLES
+If
+.I filechan
+is invoked with ``\fB\-f 2\fP'' and given the following input:
+.PP
+.RS
+.nf
+news/software/b/132 <1643@munnari.oz.au> foo uunet
+news/software/b/133 <102060@litchi.foo.com> uunet munnari
+comp/sources/unix/2002 <999@news.foo.com> foo uunet munnari
+.fi
+.RE
+.PP
+Then the file
+.I foo
+will have these lines:
+.PP
+.RS
+.nf
+news/software/b/132 <1643@munnari.oz.au>
+comp/sources/unix/2002 <999@news.foo.com>
+.fi
+.RE
+.sp
+the file
+.I munnari
+will have these lines:
+.PP
+.RS
+.nf
+news/software/b/133 <102060@litchi.foo.com>
+comp/sources/unix/2002 <999@news.foo.com>
+.fi
+.RE
+.sp
+and the file
+.I uunet
+will have these lines:
+.PP
+.RS
+.nf
+news/software/b/132 <1643@munnari.oz.au>
+news/software/b/133 <102060@litchi.foo.com>
+comp/sources/unix/2002 <999@news.foo.com>
+.fi
+.RE
+.SH HISTORY
+Written by Robert Elz <kre@munnari.oz.au>, flags added by Rich $alz
+<rsalz@uunet.uu.net>.
+.de R$
+This is revision \\$3, dated \\$4.
+..
+.R$ $Id: filechan.8 5909 2002-12-03 05:17:18Z vinocur $
+.SH "SEE ALSO"
+buffchan(8),
+inn.conf(5),
+innd(8),
+newsfeeds(5).
--- /dev/null
+.\" $Revision: 5909 $
+.TH GETLIST 1
+.SH NAME
+getlist \- get a list from an NNTP server
+.SH SYNOPSIS
+.I getlist
+[
+.B \-A
+]
+[
+.BI \-h " host"
+]
+[
+.I list
+[
+.I pattern
+[
+.I types
+]
+]
+]
+.SH DESCRIPTION
+The
+.I getlist
+program obtains a list from an NNTP server and sends
+it to standard output.
+.PP
+The
+.B list
+may be one of
+.IR active ,
+.IR active.times ,
+.IR distributions ,
+or
+.IR newsgroups .
+These values request the
+.IR active ,
+.IR active.times ,
+.IR <pathetc\ in\ inn.conf>/distributions .
+or
+.I <pathdb in inn.conf>/newsgroups
+files, respectively.
+.SH OPTIONS
+.TP
+.B \-A
+If the ``\fB\-A\fP'' flag is used, then the program tries to authenticate
+as per
+.I passwd.nntp
+before issuing LIST command.
+.TP
+.B \-h
+If the ``\fB\-h\fP'' flag is used, then the program connects to the server
+on the specified host.
+The default is to connect to the server specified in the
+.I inn.conf
+file.
+.PP
+If the
+.I list
+parameter is
+.IR active ,
+then the
+.I pattern
+and
+.I types
+parameters may be used to limit the output.
+When
+.I pattern
+is used, only active lines with groups that match according to
+.IR uwildmat (3)
+are printed.
+When
+.I types
+is also given, only active lines that have a fourth field starting
+with a character found in
+.I types
+are printed.
+.PP
+For example, the following command will obtain the one-line descriptions
+of all newsgroups found on UUNET:
+.RS
+getlist -h news.uu.net newsgroups
+.RE
+.PP
+The following line lists all groups where local postings are permitted,
+are moderated or aliased:
+.RS
+getlist active '*' ym=
+.RE
+.PP
+Note that the listing files other than the active file is a common
+extension to the NNTP protocol and may not be available on all servers.
+.SH HISTORY
+Written by Landon Curt Noll <chongo@toad.com> for InterNetNews.
+.de R$
+This is revision \\$3, dated \\$4.
+..
+.SH "SEE ALSO"
+active(5), active.times(5), inn.conf(5), nnrpd(8), uwildmat(3).
--- /dev/null
+.\" Automatically generated by Pod::Man v1.37, Pod::Parser v1.32
+.\"
+.\" Standard preamble:
+.\" ========================================================================
+.de Sh \" Subsection heading
+.br
+.if t .Sp
+.ne 5
+.PP
+\fB\\$1\fR
+.PP
+..
+.de Sp \" Vertical space (when we can't use .PP)
+.if t .sp .5v
+.if n .sp
+..
+.de Vb \" Begin verbatim text
+.ft CW
+.nf
+.ne \\$1
+..
+.de Ve \" End verbatim text
+.ft R
+.fi
+..
+.\" Set up some character translations and predefined strings. \*(-- will
+.\" give an unbreakable dash, \*(PI will give pi, \*(L" will give a left
+.\" double quote, and \*(R" will give a right double quote. \*(C+ will
+.\" give a nicer C++. Capital omega is used to do unbreakable dashes and
+.\" therefore won't be available. \*(C` and \*(C' expand to `' in nroff,
+.\" nothing in troff, for use with C<>.
+.tr \(*W-
+.ds C+ C\v'-.1v'\h'-1p'\s-2+\h'-1p'+\s0\v'.1v'\h'-1p'
+.ie n \{\
+. ds -- \(*W-
+. ds PI pi
+. if (\n(.H=4u)&(1m=24u) .ds -- \(*W\h'-12u'\(*W\h'-12u'-\" diablo 10 pitch
+. if (\n(.H=4u)&(1m=20u) .ds -- \(*W\h'-12u'\(*W\h'-8u'-\" diablo 12 pitch
+. ds L" ""
+. ds R" ""
+. ds C` ""
+. ds C' ""
+'br\}
+.el\{\
+. ds -- \|\(em\|
+. ds PI \(*p
+. ds L" ``
+. ds R" ''
+'br\}
+.\"
+.\" If the F register is turned on, we'll generate index entries on stderr for
+.\" titles (.TH), headers (.SH), subsections (.Sh), items (.Ip), and index
+.\" entries marked with X<> in POD. Of course, you'll have to process the
+.\" output yourself in some meaningful fashion.
+.if \nF \{\
+. de IX
+. tm Index:\\$1\t\\n%\t"\\$2"
+..
+. nr % 0
+. rr F
+.\}
+.\"
+.\" For nroff, turn off justification. Always turn off hyphenation; it makes
+.\" way too many mistakes in technical documents.
+.hy 0
+.if n .na
+.\"
+.\" Accent mark definitions (@(#)ms.acc 1.5 88/02/08 SMI; from UCB 4.2).
+.\" Fear. Run. Save yourself. No user-serviceable parts.
+. \" fudge factors for nroff and troff
+.if n \{\
+. ds #H 0
+. ds #V .8m
+. ds #F .3m
+. ds #[ \f1
+. ds #] \fP
+.\}
+.if t \{\
+. ds #H ((1u-(\\\\n(.fu%2u))*.13m)
+. ds #V .6m
+. ds #F 0
+. ds #[ \&
+. ds #] \&
+.\}
+. \" simple accents for nroff and troff
+.if n \{\
+. ds ' \&
+. ds ` \&
+. ds ^ \&
+. ds , \&
+. ds ~ ~
+. ds /
+.\}
+.if t \{\
+. ds ' \\k:\h'-(\\n(.wu*8/10-\*(#H)'\'\h"|\\n:u"
+. ds ` \\k:\h'-(\\n(.wu*8/10-\*(#H)'\`\h'|\\n:u'
+. ds ^ \\k:\h'-(\\n(.wu*10/11-\*(#H)'^\h'|\\n:u'
+. ds , \\k:\h'-(\\n(.wu*8/10)',\h'|\\n:u'
+. ds ~ \\k:\h'-(\\n(.wu-\*(#H-.1m)'~\h'|\\n:u'
+. ds / \\k:\h'-(\\n(.wu*8/10-\*(#H)'\z\(sl\h'|\\n:u'
+.\}
+. \" troff and (daisy-wheel) nroff accents
+.ds : \\k:\h'-(\\n(.wu*8/10-\*(#H+.1m+\*(#F)'\v'-\*(#V'\z.\h'.2m+\*(#F'.\h'|\\n:u'\v'\*(#V'
+.ds 8 \h'\*(#H'\(*b\h'-\*(#H'
+.ds o \\k:\h'-(\\n(.wu+\w'\(de'u-\*(#H)/2u'\v'-.3n'\*(#[\z\(de\v'.3n'\h'|\\n:u'\*(#]
+.ds d- \h'\*(#H'\(pd\h'-\w'~'u'\v'-.25m'\f2\(hy\fP\v'.25m'\h'-\*(#H'
+.ds D- D\\k:\h'-\w'D'u'\v'-.11m'\z\(hy\v'.11m'\h'|\\n:u'
+.ds th \*(#[\v'.3m'\s+1I\s-1\v'-.3m'\h'-(\w'I'u*2/3)'\s-1o\s+1\*(#]
+.ds Th \*(#[\s+2I\s-2\h'-\w'I'u*3/5'\v'-.3m'o\v'.3m'\*(#]
+.ds ae a\h'-(\w'a'u*4/10)'e
+.ds Ae A\h'-(\w'A'u*4/10)'E
+. \" corrections for vroff
+.if v .ds ~ \\k:\h'-(\\n(.wu*9/10-\*(#H)'\s-2\u~\d\s+2\h'|\\n:u'
+.if v .ds ^ \\k:\h'-(\\n(.wu*10/11-\*(#H)'\v'-.4m'^\v'.4m'\h'|\\n:u'
+. \" for low resolution devices (crt and lpr)
+.if \n(.H>23 .if \n(.V>19 \
+\{\
+. ds : e
+. ds 8 ss
+. ds o a
+. ds d- d\h'-1'\(ga
+. ds D- D\h'-1'\(hy
+. ds th \o'bp'
+. ds Th \o'LP'
+. ds ae ae
+. ds Ae AE
+.\}
+.rm #[ #] #H #V #F C
+.\" ========================================================================
+.\"
+.IX Title "GREPHISTORY 1"
+.TH GREPHISTORY 1 "2008-04-06" "INN 2.4.5" "InterNetNews Documentation"
+.SH "NAME"
+grephistory \- Query the INN history database
+.SH "SYNOPSIS"
+.IX Header "SYNOPSIS"
+\&\fBgrephistory\fR [\fB\-eilnqsv\fR] [\fB\-f\fR \fIdb\fR] [\fImessage-id\fR]
+.SH "DESCRIPTION"
+.IX Header "DESCRIPTION"
+\&\fBgrephistory\fR queries the \s-1INN\s0 history database for information about the
+specified message \s-1ID\s0. If no flags are given, the program prints the
+storage \s-1API\s0 token of the corresponding article, or \f(CW\*(C`/dev/null\*(C'\fR if the
+article is listed in the history database but not stored on the server.
+If the message \s-1ID\s0 cannot be found in the database, \fBgrephistory\fR will
+print \f(CW\*(C`grephistory: not found\*(C'\fR and exit with a non-zero status.
+.PP
+Be sure to escape any special characters in the message \s-1ID\s0 from the shell.
+Single quotes are recommended for this purpose since many message IDs
+contain dollar signs.
+.SH "OPTIONS"
+.IX Header "OPTIONS"
+.IP "\fB\-e\fR" 4
+.IX Item "-e"
+Only print the storage token if the article is stored on the system. (In
+other words, suppress the \f(CW\*(C`/dev/null\*(C'\fR or \f(CW\*(C`not found\*(C'\fR output for missing
+or remembered articles.)
+.IP "\fB\-f\fR \fIdb\fR" 4
+.IX Item "-f db"
+Query the history database \fIdb\fR rather than the default history database.
+.IP "\fB\-i\fR" 4
+.IX Item "-i"
+Rather than expecting a message \s-1ID\s0 on the command line, \fBgrephistory\fR
+will read a list of message IDs on standard input, one per line. Leading
+and trailing whitespace is ignored, as are any malformed lines. It will
+print out standard output those message IDs which are not found in the
+history database. This is used when processing \f(CW\*(C`ihave\*(C'\fR control messages.
+.IP "\fB\-l\fR" 4
+.IX Item "-l"
+Display the entire line from the history database, rather than just the
+storage \s-1API\s0 token.
+.IP "\fB\-n\fR" 4
+.IX Item "-n"
+If the message \s-1ID\s0 is present in the history database but has no storage
+\&\s-1API\s0 token, print \f(CW\*(C`/dev/null\*(C'\fR and exit successfully. This can happen if
+an article has been cancelled or expired, but history information has
+still been retained. This is the default behavior.
+.IP "\fB\-q\fR" 4
+.IX Item "-q"
+Don't print any message, but still exit with the appropriate status.
+.IP "\fB\-s\fR" 4
+.IX Item "-s"
+Rather than expecting a message \s-1ID\s0 on the command line, \fBgrephistory\fR
+will read a list of message IDs on standard input, one per line. Leading
+and trailing whitespace is ignored, as are any malformed lines. It will
+print on standard output the storage \s-1API\s0 tokens for any articles that are
+still available, one per line. This flag is used when processing
+\&\f(CW\*(C`sendme\*(C'\fR control messages.
+.IP "\fB\-v\fR" 4
+.IX Item "-v"
+Print out the hash of the message \s-1ID\s0 for diagnostic purposes, as well as
+any other requested information. This flag is not useful with \fB\-s\fR.
+.SH "HISTORY"
+.IX Header "HISTORY"
+Written by Rich \f(CW$alz\fR <rsalz@uunet.uu.net> for InterNetNews. Rewritten in
+\&\s-1POD\s0 by Russ Allbery <rra@stanford.edu>.
+.Sp
+$Id: grephistory.1 7880 2008-06-16 20:37:13Z iulius $
+.SH "SEE ALSO"
+.IX Header "SEE ALSO"
+\&\fIhistory\fR\|(5), \fIinn.conf\fR\|(5)
--- /dev/null
+.\" $Revision: 3782 $
+.TH HISTORY 5
+.SH NAME
+history \- record of current and recently expired Usenet articles
+.SH DESCRIPTION
+The file
+.I <pathdb in inn.conf>/history
+keeps a record of all articles currently stored in the news system,
+as well as those that have been received but since expired.
+In a typical production environment, this file will be many megabytes.
+.PP
+The file consists of text lines.
+Each line corresponds to one article.
+The file is normally kept sorted in the order in which articles are
+received, although this is not a requirement.
+.IR Innd (8)
+appends a new line each time it files an article, and
+.IR expire (8)
+builds a new version of the file by removing old articles and purging
+old entries.
+.PP
+Each line consists of two or three fields separated by a tab, shown below
+as
+.IR \et :
+.RS
+.nf
+[Hash] \et date
+[Hash] \et date \et token
+.fi
+.RE
+.PP
+The
+.I Hash
+field is the ASCII representation of the hash of the Message-ID header.
+This is directly used for the key of the
+.IR dbz (3).
+.PP
+The
+.I date
+field consists of three sub-fields separated by a tilde.
+All sub-fields are the text representation of the number of seconds since
+the epoch \(em
+.IR i.e. ,
+a
+.IR time_t ;
+see
+.IR gettimeofday (2).
+The first sub-field is the article's arrival date.
+If copies of the article are still present then the second sub-field is
+either the value of the article's Expires header, or a hyphen if no
+expiration date was specified.
+If an article has been expired then the second sub-field will be a hyphen.
+The third sub-field is the value of the article's Date header, recording
+when the article was posted.
+.PP
+The
+.I token
+field is a token of the article.
+This field is empty if the article has been expired.
+.PP
+For example, an article whose Message-ID was
+<7q2saq$sal$1@isrv4.pa.vix.com>, posted on 26 Aug 1999 08:02:34 GMT and
+recieved at 26 Aug 1999 08:06:54 GMT, could have a
+history line (broken into three lines for display) like the
+following:
+.RS
+.nf
+[E6184A5BC2898A35A3140B149DE91D5C] \et
+ 935678987~-~935678821 \et
+ @030154574F00000000000007CE3B000004BA@
+.fi
+.RE
+.PP
+In addition to the text file, there is a
+.IR dbz (3)
+database associated with the file that uses the Message-ID field as a key
+to determine the offset in the text file where the associated line begins.
+For historical reasons, the key includes the trailing \e0 byte
+(which is not stored in the text file).
+.SH HISTORY
+Written by Rich $alz <rsalz@uunet.uu.net> for InterNetNews.
+.de R$
+This is revision \\$3, dated \\$4.
+..
+.R$ $Id: history.5 3782 2000-08-17 13:30:18Z kondou $
+.SH "SEE ALSO"
+dbz(3),
+expire(8),
+inn.conf(5),
+innd(8),
+makehistory(8).
--- /dev/null
+.\" Automatically generated by Pod::Man v1.37, Pod::Parser v1.32
+.\"
+.\" Standard preamble:
+.\" ========================================================================
+.de Sh \" Subsection heading
+.br
+.if t .Sp
+.ne 5
+.PP
+\fB\\$1\fR
+.PP
+..
+.de Sp \" Vertical space (when we can't use .PP)
+.if t .sp .5v
+.if n .sp
+..
+.de Vb \" Begin verbatim text
+.ft CW
+.nf
+.ne \\$1
+..
+.de Ve \" End verbatim text
+.ft R
+.fi
+..
+.\" Set up some character translations and predefined strings. \*(-- will
+.\" give an unbreakable dash, \*(PI will give pi, \*(L" will give a left
+.\" double quote, and \*(R" will give a right double quote. \*(C+ will
+.\" give a nicer C++. Capital omega is used to do unbreakable dashes and
+.\" therefore won't be available. \*(C` and \*(C' expand to `' in nroff,
+.\" nothing in troff, for use with C<>.
+.tr \(*W-
+.ds C+ C\v'-.1v'\h'-1p'\s-2+\h'-1p'+\s0\v'.1v'\h'-1p'
+.ie n \{\
+. ds -- \(*W-
+. ds PI pi
+. if (\n(.H=4u)&(1m=24u) .ds -- \(*W\h'-12u'\(*W\h'-12u'-\" diablo 10 pitch
+. if (\n(.H=4u)&(1m=20u) .ds -- \(*W\h'-12u'\(*W\h'-8u'-\" diablo 12 pitch
+. ds L" ""
+. ds R" ""
+. ds C` ""
+. ds C' ""
+'br\}
+.el\{\
+. ds -- \|\(em\|
+. ds PI \(*p
+. ds L" ``
+. ds R" ''
+'br\}
+.\"
+.\" If the F register is turned on, we'll generate index entries on stderr for
+.\" titles (.TH), headers (.SH), subsections (.Sh), items (.Ip), and index
+.\" entries marked with X<> in POD. Of course, you'll have to process the
+.\" output yourself in some meaningful fashion.
+.if \nF \{\
+. de IX
+. tm Index:\\$1\t\\n%\t"\\$2"
+..
+. nr % 0
+. rr F
+.\}
+.\"
+.\" For nroff, turn off justification. Always turn off hyphenation; it makes
+.\" way too many mistakes in technical documents.
+.hy 0
+.if n .na
+.\"
+.\" Accent mark definitions (@(#)ms.acc 1.5 88/02/08 SMI; from UCB 4.2).
+.\" Fear. Run. Save yourself. No user-serviceable parts.
+. \" fudge factors for nroff and troff
+.if n \{\
+. ds #H 0
+. ds #V .8m
+. ds #F .3m
+. ds #[ \f1
+. ds #] \fP
+.\}
+.if t \{\
+. ds #H ((1u-(\\\\n(.fu%2u))*.13m)
+. ds #V .6m
+. ds #F 0
+. ds #[ \&
+. ds #] \&
+.\}
+. \" simple accents for nroff and troff
+.if n \{\
+. ds ' \&
+. ds ` \&
+. ds ^ \&
+. ds , \&
+. ds ~ ~
+. ds /
+.\}
+.if t \{\
+. ds ' \\k:\h'-(\\n(.wu*8/10-\*(#H)'\'\h"|\\n:u"
+. ds ` \\k:\h'-(\\n(.wu*8/10-\*(#H)'\`\h'|\\n:u'
+. ds ^ \\k:\h'-(\\n(.wu*10/11-\*(#H)'^\h'|\\n:u'
+. ds , \\k:\h'-(\\n(.wu*8/10)',\h'|\\n:u'
+. ds ~ \\k:\h'-(\\n(.wu-\*(#H-.1m)'~\h'|\\n:u'
+. ds / \\k:\h'-(\\n(.wu*8/10-\*(#H)'\z\(sl\h'|\\n:u'
+.\}
+. \" troff and (daisy-wheel) nroff accents
+.ds : \\k:\h'-(\\n(.wu*8/10-\*(#H+.1m+\*(#F)'\v'-\*(#V'\z.\h'.2m+\*(#F'.\h'|\\n:u'\v'\*(#V'
+.ds 8 \h'\*(#H'\(*b\h'-\*(#H'
+.ds o \\k:\h'-(\\n(.wu+\w'\(de'u-\*(#H)/2u'\v'-.3n'\*(#[\z\(de\v'.3n'\h'|\\n:u'\*(#]
+.ds d- \h'\*(#H'\(pd\h'-\w'~'u'\v'-.25m'\f2\(hy\fP\v'.25m'\h'-\*(#H'
+.ds D- D\\k:\h'-\w'D'u'\v'-.11m'\z\(hy\v'.11m'\h'|\\n:u'
+.ds th \*(#[\v'.3m'\s+1I\s-1\v'-.3m'\h'-(\w'I'u*2/3)'\s-1o\s+1\*(#]
+.ds Th \*(#[\s+2I\s-2\h'-\w'I'u*3/5'\v'-.3m'o\v'.3m'\*(#]
+.ds ae a\h'-(\w'a'u*4/10)'e
+.ds Ae A\h'-(\w'A'u*4/10)'E
+. \" corrections for vroff
+.if v .ds ~ \\k:\h'-(\\n(.wu*9/10-\*(#H)'\s-2\u~\d\s+2\h'|\\n:u'
+.if v .ds ^ \\k:\h'-(\\n(.wu*10/11-\*(#H)'\v'-.4m'^\v'.4m'\h'|\\n:u'
+. \" for low resolution devices (crt and lpr)
+.if \n(.H>23 .if \n(.V>19 \
+\{\
+. ds : e
+. ds 8 ss
+. ds o a
+. ds d- d\h'-1'\(ga
+. ds D- D\h'-1'\(hy
+. ds th \o'bp'
+. ds Th \o'LP'
+. ds ae ae
+. ds Ae AE
+.\}
+.rm #[ #] #H #V #F C
+.\" ========================================================================
+.\"
+.IX Title "IDENT 8"
+.TH IDENT 8 "2008-04-06" "INN 2.4.5" "InterNetNews Documentation"
+.SH "NAME"
+ident \- nnrpd ident resolver
+.SH "SYNOPSIS"
+.IX Header "SYNOPSIS"
+\&\fBident\fR [\fB\-p\fR \fIport\fR] [\fB\-t\fR]
+.SH "DESCRIPTION"
+.IX Header "DESCRIPTION"
+This program attempts to resolve usernames for \fBnnrpd\fR by using the
+ident protocol to query the remote host. It contacts the remote host
+using either IPv4 or IPv6 depending on which protocol was used for the
+incoming \s-1NNTP\s0 connection.
+.SH "OPTIONS"
+.IX Header "OPTIONS"
+.IP "\fB\-p\fR \fIport\fR" 4
+.IX Item "-p port"
+If this option is given, attempt to contact identd on the specified
+remote port (which can be a numeric or symbolic specification).
+Non-numeric values will be looked up using \fIgetservbyname\fR\|(3). The
+default value is the result of \f(CW\*(C`getservbyname("ident")\*(C'\fR if available,
+or port 113 otherwise.
+.IP "\fB\-t\fR" 4
+.IX Item "-t"
+If this option is given, the identity returned will never have a domain
+part. That is, if the remote server returns a result containing an \f(CW\*(C`@\*(C'\fR
+character, \fBident\fR truncates the response at the \f(CW\*(C`@\*(C'\fR. This is useful
+to allow the \fIdefault-domain\fR parameter in \fIreaers.conf\fR to override
+the domain supplied by the remote host (particularly if the supplied
+domain part is an unqualified local machine name rather than a full
+domain name).
+.SH "EXAMPLE"
+.IX Header "EXAMPLE"
+The following \fIreaders.conf\fR\|(5) fragment tells nnrpd to trust ident
+information for hosts on a local network, but to replace the domain
+returned from the ident query:
+.PP
+.Vb 5
+\& auth LAN {
+\& hosts: "192.168/16"
+\& res: "ident \-t"
+\& default\-domain: "internal.example.com"
+\& }
+.Ve
+.PP
+.Vb 4
+\& access LAN {
+\& users: "*@internal.example.com"
+\& newsgroups: example.*
+\& }
+.Ve
+.PP
+Access is granted to the example.* groups for all users on the local
+network whose machines respond to ident queries.
+.SH "HISTORY"
+.IX Header "HISTORY"
+This documentation was written by Jeffrey M. Vinocur <jeff@litech.org>.
+.PP
+$Id: ident.8 7880 2008-06-16 20:37:13Z iulius $
+.SH "SEE ALSO"
+.IX Header "SEE ALSO"
+\&\fInnrpd\fR\|(8), \fIreaders.conf\fR\|(5)
--- /dev/null
+.\" $Revision: 6992 $
+.TH INCOMING.CONF 5
+.SH NAME
+incoming.conf \- names and addresses that feed us news
+.SH DESCRIPTION
+The file
+.I <pathetc in inn.conf>/incoming.conf
+consists of three types of entries: key/value, peer and group.
+Comments are from the hash character ``#'' to the end of the line.
+Blank lines are ignored. All key/value entries within each type
+must not be duplicated.
+.PP
+Key/value entries are a keyword immediately followed by a colon, at least
+one blank and a value. For example:
+.PP
+.RS
+.nf
+ max-connections: 10
+.fi
+.RE
+.PP
+A legal key does not contains blanks, colons, nor ``#''.
+There are 3 different types of values: integers, booleans, and strings.
+Integers are as to be expected. A boolean value is either ``true'' or
+``false'' (case is significant). A string value is any other sequence of
+characters. If the string needs to contain whitespace, then it must be
+quoted with double quotes.
+.PP
+Peer entries look like:
+.PP
+.RS
+.nf
+ peer <name> {
+ # body
+ }
+.fi
+.RE
+.PP
+The word ``peer'' is required. ``<name>''is a label for this peer.
+The ``<name>'' is any string valid as a key. The body of a peer entry
+contains some number of key/value entries.
+.PP
+Group entries look like:
+.PP
+.RS
+.nf
+ group <name> {
+ # body
+ }
+.fi
+.RE
+.PP
+The word ``group'' is required. The ``<name>'' is any string valid as a
+key. The body of a group entry contains any number of the three types of
+entries. So key/value pairs can be defined inside a group, and peers can
+be nested inside a group, and other groups can be nested inside a group.
+.PP
+Key/value entries that are defined outside of all peer and group entries
+are said to be at ``global scope''. Global key/value entries act as
+defaults for peers. When
+.IR innd (8)
+looks for a specific value in a peer entry
+(for example, the maximum number of connections to allow), if the value
+is not defined in the peer entry, then the enclosing groups are examined
+for the entry (starting at the closest enclosing group). If there are no
+enclosing groups, or the enclosing groups don't define the key/value,
+then the value at global scope is used.
+.PP
+A small example could be:
+.PP
+.RS
+.nf
+# Global value applied to all peers that have
+# no value of their own.
+max-connections: 5
+
+# A peer definition.
+peer uunet {
+ hostname: usenet1.uu.net
+}
+
+peer vixie {
+ hostname: gw.home.vix.com
+ max-connections: 10 # override global value.
+}
+
+# A group of two peers who can open more
+# connections than normal
+group fast-sites {
+ max-connections: 15
+
+ # Another peer. The ``max-connections'' value from the
+ # ``fast-sites'' group scope is used. The ``hostname'' value
+ # defaults to the peer's name.
+ peer data.ramona.vix.com {
+ }
+
+ peer bb.home.vix.com {
+ hostname: bb.home.vix.com
+ max-connections: 20 # he can really cook.
+ }
+}
+.fi
+.RE
+.PP
+Given the above configuration file, the defined peers would have the
+following values for the ``max-connections'' key.
+.PP
+.RS
+.nf
+ uunet 5
+ vixie 10
+ data.ramona.vix.com 15
+ bb.home.vix.com 20
+.fi
+.RE
+.PP
+Ten keys are allowed:
+.TP
+.BI hostname:
+This key requires a string value. It is a list of hostnames separated by a
+comma. A hostname is the host's FQDN, or the dotted quad ip-address of the
+peer. If this key is not present in a peer block, the hostname defaults to
+the label of the peer.
+.TP
+.BI streaming:
+This key requires a boolean value. It defines whether streaming commands
+are allowed from this peer. (default=true)
+.TP
+.BI max-connections:
+This key requires a positive integer value. It defines the maximum number
+of connections allowed. A value of zero specifies an unlimited number
+of maximum connections (``unlimited'' or ``none'' can be used as synonym).
+(default=0)
+.TP
+.BI hold-time:
+This key requires a positive integer value. It defines the hold time before
+closing, if the connection is over max-connections. A value of zero
+specifies immediate close. (default=0)
+.TP
+.BI password:
+This key requires a string value. It is used if you wish to require a peer
+to supply a password. (default=no password)
+.TP
+.BI identd:
+This key requires a string value. It is used if you wish to require a peer's
+user name retrieved through identd match the specified string. Note that
+currently
+.IR innd (8)
+does not implement any timeout in identd callbacks, so enabling this
+option may cause innd to hang if the remote peer does not respond to ident
+callbacks in a reasonable timeframe (default=no identd)
+.TP
+.BI patterns:
+This key requires a string value. It is a list of
+.IR newsfeeds (5)-style
+list of newsgroups which are to be accepted from this host. (default="*")
+.TP
+.BI email:
+This key requires a string value. Reserved for future use. (default=empty)
+.TP
+.BI comment:
+This key requires a string value. Reserved for future use. (default=empty)
+.TP
+.BI skip:
+This key requires a boolean value. Setting this entry causes this peer
+to be skipped. (default=false)
+.TP
+.BI noresendid:
+This key requires a boolean value. It defines whether
+.IR innd (8)
+should send
+``431 RESENDID'' responses if a message is offered that is being received
+from another peer. This can be useful for peers that resend messages
+right away, as innfeed does. (default=false)
+.TP
+.BI nolist:
+This key requires a boolean value. It defines whether a peer is allowed to
+issue list command. (default=false)
+.SH HISTORY
+Written by Fabien Tassin <fta@sofaraway.org> for InterNetNews.
+.de R$
+This is revision \\$3, dated \\$4.
+..
+.R$ $Id: incoming.conf.5 6992 2004-10-01 05:30:17Z rra $
+.SH "SEE ALSO"
+inn.conf(5),
+innd(8),
+newsfeeds(5),
+uwildmat(3).
--- /dev/null
+.\" Automatically generated by Pod::Man v1.37, Pod::Parser v1.32
+.\"
+.\" Standard preamble:
+.\" ========================================================================
+.de Sh \" Subsection heading
+.br
+.if t .Sp
+.ne 5
+.PP
+\fB\\$1\fR
+.PP
+..
+.de Sp \" Vertical space (when we can't use .PP)
+.if t .sp .5v
+.if n .sp
+..
+.de Vb \" Begin verbatim text
+.ft CW
+.nf
+.ne \\$1
+..
+.de Ve \" End verbatim text
+.ft R
+.fi
+..
+.\" Set up some character translations and predefined strings. \*(-- will
+.\" give an unbreakable dash, \*(PI will give pi, \*(L" will give a left
+.\" double quote, and \*(R" will give a right double quote. \*(C+ will
+.\" give a nicer C++. Capital omega is used to do unbreakable dashes and
+.\" therefore won't be available. \*(C` and \*(C' expand to `' in nroff,
+.\" nothing in troff, for use with C<>.
+.tr \(*W-
+.ds C+ C\v'-.1v'\h'-1p'\s-2+\h'-1p'+\s0\v'.1v'\h'-1p'
+.ie n \{\
+. ds -- \(*W-
+. ds PI pi
+. if (\n(.H=4u)&(1m=24u) .ds -- \(*W\h'-12u'\(*W\h'-12u'-\" diablo 10 pitch
+. if (\n(.H=4u)&(1m=20u) .ds -- \(*W\h'-12u'\(*W\h'-8u'-\" diablo 12 pitch
+. ds L" ""
+. ds R" ""
+. ds C` ""
+. ds C' ""
+'br\}
+.el\{\
+. ds -- \|\(em\|
+. ds PI \(*p
+. ds L" ``
+. ds R" ''
+'br\}
+.\"
+.\" If the F register is turned on, we'll generate index entries on stderr for
+.\" titles (.TH), headers (.SH), subsections (.Sh), items (.Ip), and index
+.\" entries marked with X<> in POD. Of course, you'll have to process the
+.\" output yourself in some meaningful fashion.
+.if \nF \{\
+. de IX
+. tm Index:\\$1\t\\n%\t"\\$2"
+..
+. nr % 0
+. rr F
+.\}
+.\"
+.\" For nroff, turn off justification. Always turn off hyphenation; it makes
+.\" way too many mistakes in technical documents.
+.hy 0
+.if n .na
+.\"
+.\" Accent mark definitions (@(#)ms.acc 1.5 88/02/08 SMI; from UCB 4.2).
+.\" Fear. Run. Save yourself. No user-serviceable parts.
+. \" fudge factors for nroff and troff
+.if n \{\
+. ds #H 0
+. ds #V .8m
+. ds #F .3m
+. ds #[ \f1
+. ds #] \fP
+.\}
+.if t \{\
+. ds #H ((1u-(\\\\n(.fu%2u))*.13m)
+. ds #V .6m
+. ds #F 0
+. ds #[ \&
+. ds #] \&
+.\}
+. \" simple accents for nroff and troff
+.if n \{\
+. ds ' \&
+. ds ` \&
+. ds ^ \&
+. ds , \&
+. ds ~ ~
+. ds /
+.\}
+.if t \{\
+. ds ' \\k:\h'-(\\n(.wu*8/10-\*(#H)'\'\h"|\\n:u"
+. ds ` \\k:\h'-(\\n(.wu*8/10-\*(#H)'\`\h'|\\n:u'
+. ds ^ \\k:\h'-(\\n(.wu*10/11-\*(#H)'^\h'|\\n:u'
+. ds , \\k:\h'-(\\n(.wu*8/10)',\h'|\\n:u'
+. ds ~ \\k:\h'-(\\n(.wu-\*(#H-.1m)'~\h'|\\n:u'
+. ds / \\k:\h'-(\\n(.wu*8/10-\*(#H)'\z\(sl\h'|\\n:u'
+.\}
+. \" troff and (daisy-wheel) nroff accents
+.ds : \\k:\h'-(\\n(.wu*8/10-\*(#H+.1m+\*(#F)'\v'-\*(#V'\z.\h'.2m+\*(#F'.\h'|\\n:u'\v'\*(#V'
+.ds 8 \h'\*(#H'\(*b\h'-\*(#H'
+.ds o \\k:\h'-(\\n(.wu+\w'\(de'u-\*(#H)/2u'\v'-.3n'\*(#[\z\(de\v'.3n'\h'|\\n:u'\*(#]
+.ds d- \h'\*(#H'\(pd\h'-\w'~'u'\v'-.25m'\f2\(hy\fP\v'.25m'\h'-\*(#H'
+.ds D- D\\k:\h'-\w'D'u'\v'-.11m'\z\(hy\v'.11m'\h'|\\n:u'
+.ds th \*(#[\v'.3m'\s+1I\s-1\v'-.3m'\h'-(\w'I'u*2/3)'\s-1o\s+1\*(#]
+.ds Th \*(#[\s+2I\s-2\h'-\w'I'u*3/5'\v'-.3m'o\v'.3m'\*(#]
+.ds ae a\h'-(\w'a'u*4/10)'e
+.ds Ae A\h'-(\w'A'u*4/10)'E
+. \" corrections for vroff
+.if v .ds ~ \\k:\h'-(\\n(.wu*9/10-\*(#H)'\s-2\u~\d\s+2\h'|\\n:u'
+.if v .ds ^ \\k:\h'-(\\n(.wu*10/11-\*(#H)'\v'-.4m'^\v'.4m'\h'|\\n:u'
+. \" for low resolution devices (crt and lpr)
+.if \n(.H>23 .if \n(.V>19 \
+\{\
+. ds : e
+. ds 8 ss
+. ds o a
+. ds d- d\h'-1'\(ga
+. ds D- D\h'-1'\(hy
+. ds th \o'bp'
+. ds Th \o'LP'
+. ds ae ae
+. ds Ae AE
+.\}
+.rm #[ #] #H #V #F C
+.\" ========================================================================
+.\"
+.IX Title "INEWS 1"
+.TH INEWS 1 "2008-04-06" "INN 2.4.5" "InterNetNews Documentation"
+.SH "NAME"
+inews \- Post a Usenet article to the local news server
+.SH "SYNOPSIS"
+.IX Header "SYNOPSIS"
+\&\fBinews\fR [\fB\-ADhNORSVW\fR] [\fB\-acdeFfnortwx\fR \fIvalue\fR] [\fB\-p\fR \fIport\fR] [\fIfile\fR]
+.SH "DESCRIPTION"
+.IX Header "DESCRIPTION"
+\&\fBinews\fR reads a Usenet news article, perhaps with headers, from \fIfile\fR
+or standard input if no file is given. It adds some headers and performs
+some consistency checks. If the article does not meet those checks, the
+article is rejected. If it passes the checks, \fBinews\fR sends the article
+to the local news server as specified in \fIinn.conf\fR.
+.PP
+By default, if a file named \fI.signature\fR exists in the home directory of
+the posting user, it is appended to the post, preceeded by a line that
+contains only \f(CW\*(C`\-\- \*(C'\fR. Signatures are not allowed to be more than four
+lines long.
+.PP
+Cancel messages can only be posted with \fBinews\fR if the sender of the
+cancel message matches the sender of the original message being
+cancelled. The same check is also applied to Supersedes. Sender in this
+case means the contents of the Sender header if present, otherwise the
+From header.
+.PP
+Control messages other than cancel messages are only allowed if \fBinews\fR
+is being run by the news user or by a user in the news group and if the
+control message is recognized. If the article contains a Distribution
+header with a distribution that matches one of the bad distribution
+patterns in \fIinn/options.h\fR (anything containing a period by default),
+the message will be rejected. The message will also be rejected if
+\&\fIcheckincludedtext\fR is true in \fIinn.conf\fR, it contains more quoted text
+than original text, and it is over 40 lines long.
+.PP
+If not provided, the Path header of an article is constructed as follows:
+The basic Path header will be \*(L"not\-for\-mail\*(R". If \fIpathhost\fR is specified
+in \fIinn.conf\fR, it will be added to the beginning Path. Otherwise, if
+\&\fIserver\fR is specified, the full domain of the local host will be added to
+the beginning of the Path. Then, if \fB\-x\fR was given, its value will be
+added to the beginning of the Path.
+.PP
+If posting fails, a copy of the failed post will be saved in a file named
+\&\fIdead.article\fR in the home directory of the user running \fBinews\fR.
+\&\fBinews\fR exits with a non-zero status if posting failed or with a zero
+status if posting was successful.
+.SH "OPTIONS"
+.IX Header "OPTIONS"
+Most of the options to \fBinews\fR take a single value and set the
+corresponding header in the message that is posted. If the value is more
+than one word or contains any shell metacharacters, it must be quoted to
+protect it from the shell. Here are all the options that set header
+fields and the corresponding header:
+.PP
+.Vb 12
+\& \-a Approved
+\& \-c Control
+\& \-d Distribution
+\& \-e Expires
+\& \-F References
+\& \-f From
+\& \-n Newsgroups
+\& \-o Organization
+\& \-r Reply\-To
+\& \-t Subject
+\& \-w Followup\-To
+\& \-x Path prefix
+.Ve
+.PP
+The \fB\-x\fR argument will be added to the beginning of the normal Path
+header; it will not replace it.
+.IP "\fB\-A\fR, \fB\-V\fR, \fB\-W\fR" 4
+.IX Item "-A, -V, -W"
+Accepted for compatibility with C News. These options have no affect.
+.IP "\fB\-D\fR, \fB\-N\fR" 4
+.IX Item "-D, -N"
+Perform the consistency checks and add headers where appropriate, but then
+print the article to standard output rather than sending it to the server.
+\&\fB\-N\fR is accepted as as synonym for compatibility with C News.
+.IP "\fB\-h\fR" 4
+.IX Item "-h"
+Normally, this flag should always be given. It indicates that the article
+consists of headers, a blank line, and then the message body. If it is
+omitted, the input is taken to be just the body of the message, and any
+desired headers have to be specified with command-line options as
+described above.
+.IP "\fB\-O\fR" 4
+.IX Item "-O"
+By default, an Organization header will be added if none is present in the
+article. To prevent adding the default (from \fIorganization\fR in
+\&\fIinn.conf\fR), use this flag.
+.IP "\fB\-p\fR \fIport\fR" 4
+.IX Item "-p port"
+Connect to the specified port on the server rather than to the default
+(port 119).
+.IP "\fB\-R\fR" 4
+.IX Item "-R"
+Reject all control messages.
+.IP "\fB\-S\fR" 4
+.IX Item "-S"
+Do not attempt to append \fI~/.signature\fR to the message, even if it
+exists.
+.SH "NOTES"
+.IX Header "NOTES"
+If the \s-1NNTP\s0 server requests authentication, \fBinews\fR will try to read
+\&\fIpasswd.nntp\fR to get the username and password to use and will therefore
+need read access to that file. This is typically done by making that file
+group-readable and adding all users who should be able to use \fBinews\fR to
+post to that server to the appropriate group.
+.PP
+\&\fBinews\fR used to do even more than it does now, and all of the remaining
+checks that are not dependent on the user running \fBinews\fR should probably
+be removed in favor of letting the news server handle them.
+.PP
+Since \s-1INN\s0's \fBinews\fR uses \fIinn.conf\fR and some other corners of an \s-1INN\s0
+installation, it's not very appropriate as a general stand-alone \fBinews\fR
+program for general use on a system that's not running a news server.
+Other, more suitable versions of \fBinews\fR are available as part of various
+Unix news clients or by themselves.
+.SH "HISTORY"
+.IX Header "HISTORY"
+Written by Rich \f(CW$alz\fR <rsalz@uunet.uu.net> for InterNetNews. Rewritten in
+\&\s-1POD\s0 by Russ Allbery <rra@stanford.edu>.
+.SH "SEE ALSO"
+.IX Header "SEE ALSO"
+\&\fIinn.conf\fR\|(5), \fIrnews\fR\|(1)
--- /dev/null
+.\" Automatically generated by Pod::Man v1.37, Pod::Parser v1.32
+.\"
+.\" Standard preamble:
+.\" ========================================================================
+.de Sh \" Subsection heading
+.br
+.if t .Sp
+.ne 5
+.PP
+\fB\\$1\fR
+.PP
+..
+.de Sp \" Vertical space (when we can't use .PP)
+.if t .sp .5v
+.if n .sp
+..
+.de Vb \" Begin verbatim text
+.ft CW
+.nf
+.ne \\$1
+..
+.de Ve \" End verbatim text
+.ft R
+.fi
+..
+.\" Set up some character translations and predefined strings. \*(-- will
+.\" give an unbreakable dash, \*(PI will give pi, \*(L" will give a left
+.\" double quote, and \*(R" will give a right double quote. \*(C+ will
+.\" give a nicer C++. Capital omega is used to do unbreakable dashes and
+.\" therefore won't be available. \*(C` and \*(C' expand to `' in nroff,
+.\" nothing in troff, for use with C<>.
+.tr \(*W-
+.ds C+ C\v'-.1v'\h'-1p'\s-2+\h'-1p'+\s0\v'.1v'\h'-1p'
+.ie n \{\
+. ds -- \(*W-
+. ds PI pi
+. if (\n(.H=4u)&(1m=24u) .ds -- \(*W\h'-12u'\(*W\h'-12u'-\" diablo 10 pitch
+. if (\n(.H=4u)&(1m=20u) .ds -- \(*W\h'-12u'\(*W\h'-8u'-\" diablo 12 pitch
+. ds L" ""
+. ds R" ""
+. ds C` ""
+. ds C' ""
+'br\}
+.el\{\
+. ds -- \|\(em\|
+. ds PI \(*p
+. ds L" ``
+. ds R" ''
+'br\}
+.\"
+.\" If the F register is turned on, we'll generate index entries on stderr for
+.\" titles (.TH), headers (.SH), subsections (.Sh), items (.Ip), and index
+.\" entries marked with X<> in POD. Of course, you'll have to process the
+.\" output yourself in some meaningful fashion.
+.if \nF \{\
+. de IX
+. tm Index:\\$1\t\\n%\t"\\$2"
+..
+. nr % 0
+. rr F
+.\}
+.\"
+.\" For nroff, turn off justification. Always turn off hyphenation; it makes
+.\" way too many mistakes in technical documents.
+.hy 0
+.if n .na
+.\"
+.\" Accent mark definitions (@(#)ms.acc 1.5 88/02/08 SMI; from UCB 4.2).
+.\" Fear. Run. Save yourself. No user-serviceable parts.
+. \" fudge factors for nroff and troff
+.if n \{\
+. ds #H 0
+. ds #V .8m
+. ds #F .3m
+. ds #[ \f1
+. ds #] \fP
+.\}
+.if t \{\
+. ds #H ((1u-(\\\\n(.fu%2u))*.13m)
+. ds #V .6m
+. ds #F 0
+. ds #[ \&
+. ds #] \&
+.\}
+. \" simple accents for nroff and troff
+.if n \{\
+. ds ' \&
+. ds ` \&
+. ds ^ \&
+. ds , \&
+. ds ~ ~
+. ds /
+.\}
+.if t \{\
+. ds ' \\k:\h'-(\\n(.wu*8/10-\*(#H)'\'\h"|\\n:u"
+. ds ` \\k:\h'-(\\n(.wu*8/10-\*(#H)'\`\h'|\\n:u'
+. ds ^ \\k:\h'-(\\n(.wu*10/11-\*(#H)'^\h'|\\n:u'
+. ds , \\k:\h'-(\\n(.wu*8/10)',\h'|\\n:u'
+. ds ~ \\k:\h'-(\\n(.wu-\*(#H-.1m)'~\h'|\\n:u'
+. ds / \\k:\h'-(\\n(.wu*8/10-\*(#H)'\z\(sl\h'|\\n:u'
+.\}
+. \" troff and (daisy-wheel) nroff accents
+.ds : \\k:\h'-(\\n(.wu*8/10-\*(#H+.1m+\*(#F)'\v'-\*(#V'\z.\h'.2m+\*(#F'.\h'|\\n:u'\v'\*(#V'
+.ds 8 \h'\*(#H'\(*b\h'-\*(#H'
+.ds o \\k:\h'-(\\n(.wu+\w'\(de'u-\*(#H)/2u'\v'-.3n'\*(#[\z\(de\v'.3n'\h'|\\n:u'\*(#]
+.ds d- \h'\*(#H'\(pd\h'-\w'~'u'\v'-.25m'\f2\(hy\fP\v'.25m'\h'-\*(#H'
+.ds D- D\\k:\h'-\w'D'u'\v'-.11m'\z\(hy\v'.11m'\h'|\\n:u'
+.ds th \*(#[\v'.3m'\s+1I\s-1\v'-.3m'\h'-(\w'I'u*2/3)'\s-1o\s+1\*(#]
+.ds Th \*(#[\s+2I\s-2\h'-\w'I'u*3/5'\v'-.3m'o\v'.3m'\*(#]
+.ds ae a\h'-(\w'a'u*4/10)'e
+.ds Ae A\h'-(\w'A'u*4/10)'E
+. \" corrections for vroff
+.if v .ds ~ \\k:\h'-(\\n(.wu*9/10-\*(#H)'\s-2\u~\d\s+2\h'|\\n:u'
+.if v .ds ^ \\k:\h'-(\\n(.wu*10/11-\*(#H)'\v'-.4m'^\v'.4m'\h'|\\n:u'
+. \" for low resolution devices (crt and lpr)
+.if \n(.H>23 .if \n(.V>19 \
+\{\
+. ds : e
+. ds 8 ss
+. ds o a
+. ds d- d\h'-1'\(ga
+. ds D- D\h'-1'\(hy
+. ds th \o'bp'
+. ds Th \o'LP'
+. ds ae ae
+. ds Ae AE
+.\}
+.rm #[ #] #H #V #F C
+.\" ========================================================================
+.\"
+.IX Title "INN.CONF 5"
+.TH INN.CONF 5 "2008-04-06" "INN 2.4.5" "InterNetNews Documentation"
+.SH "NAME"
+inn.conf \- Configuration data for InterNetNews programs
+.SH "DESCRIPTION"
+.IX Header "DESCRIPTION"
+\&\fIinn.conf\fR in \fIpathetc\fR is the primary general configuration file for
+all InterNetNews programs. Settings which control the general operation
+of various programs, as well as the paths to all portions of the news
+installation, are found here. The \s-1INNCONF\s0 environment variable, if set,
+specifies an alternate path to \fIinn.conf\fR.
+.PP
+This file is intended to be fairly static. Any changes made to it will
+generally not affect any running programs until they restart. Unlike
+nearly every other configuration file, \fIinn.conf\fR cannot be reloaded
+dynamically using \fIctlinnd\fR\|(8); \fIinnd\fR\|(8) must be stopped and restarted for
+relevant changes to \fIinn.conf\fR to take effect (\f(CW\*(C`ctlinnd xexec innd\*(C'\fR is
+the fastest way to do this.)
+.PP
+Blank lines and lines starting with a number sign (\f(CW\*(C`#\*(C'\fR) are ignored. All
+other lines specify parameters, and should be of the following form:
+.PP
+.Vb 1
+\& <name>: <value>
+.Ve
+.PP
+(Any amount of whitespace can be put after the colon and is optional.) If
+the value contains embedded whitespace or any of the characers \f(CW\*(C`[]<\*(C'\fR\*(L"\e:>,
+it must be enclosed in double quotes (\*(R""). A backslash (\f(CW\*(C`\e\*(C'\fR) can be used
+to escape quotes and backslashes inside double quotes. <name> is
+case\-sensitive; \f(CW\*(C`server\*(C'\fR is not the same as \f(CW\*(C`Server\*(C'\fR or \f(CW\*(C`SERVER\*(C'\fR.
+(\fIinn.conf\fR parameters are generally all in lowercase.)
+.PP
+If <name> occurs more than once in the file, the first value is used.
+Some parameters specified in the file may be overridden by environment
+variables. Most parameters have default values if not specified in
+\&\fIinn.conf\fR; those defaults are noted in the description of each
+parameter.
+.PP
+Many parameters take a boolean value. For all such parameters, the value
+may be specified as \f(CW\*(C`true\*(C'\fR, \f(CW\*(C`yes\*(C'\fR, or \f(CW\*(C`on\*(C'\fR to turn it on and may be any
+of \f(CW\*(C`false\*(C'\fR, \f(CW\*(C`no\*(C'\fR, or \f(CW\*(C`off\*(C'\fR to turn it off. The case of these values is
+significant.
+.PP
+This documentation is extremely long and organized as a reference manual
+rather than as a tutorial. If this is your first exposure to \s-1INN\s0 and
+these parameters, it would be better to start by reading other man pages
+and referring to this one only when an \fIinn.conf\fR parameter is explicitly
+mentioned. Those parameters which need to be changed when setting up a
+new server are discussed in \fI\s-1INSTALL\s0\fR.
+.SH "PARAMETERS"
+.IX Header "PARAMETERS"
+.Sh "General Settings"
+.IX Subsection "General Settings"
+These parameters are used by a wide variety of different components of
+\&\s-1INN\s0.
+.IP "\fIdomain\fR" 4
+.IX Item "domain"
+This should be the domain name of the local host. It should not have a
+leading period, and it should not be a full host address. It is used only
+if the \fIGetFQDN()\fR routine in \fIlibinn\fR\|(3) cannot get the fully-qualified
+domain name by using either the \fIgethostname\fR\|(3) or \fIgethostbyname\fR\|(3) calls.
+The check is very simple; if either routine returns a name with a period
+in it, then it is assumed to have the full domain name. As this parameter
+is rarely used, do not use it to affect the righthand side of
+autogenerated Message\-IDs; see instead \fIvirtualhost\fR and \fIdomain\fR in
+readers.conf. The default value is unset.
+.IP "\fIinnflags\fR" 4
+.IX Item "innflags"
+The flags to pass to innd on startup. See \fIinnd\fR\|(8) for details on the
+possible flags. The default value is unset.
+.IP "\fImailcmd\fR" 4
+.IX Item "mailcmd"
+The path to the program to be used for mailing reports and control
+messages. The default is \fIpathbin\fR/innmail. This should not normally
+need to be changed.
+.IP "\fImta\fR" 4
+.IX Item "mta"
+The command to use when mailing postings to moderators and for the use of
+\&\fIinnmail\fR\|(1). The message, with headers and an added To: header, will be
+piped into this program. The string \f(CW%s\fR, if present, will be replaced
+by the e\-mail address of the moderator. It's strongly recommended for
+this command to include \f(CW%s\fR on the command line rather than use the
+addresses in the To: and Cc: headers of the message, since the latter
+approach allows the news server to be abused as a mechanism to send mail
+to arbitrary addresses and will result in unexpected behavior. There is
+no default value for this parameter; it must be set in \fIinn.conf\fR or a
+fatal error message will be logged via syslog.
+.Sp
+For most systems, \f(CW\*(C`/usr/lib/sendmail \-oi \-oem %s\*(C'\fR (adjusted for the
+correct path to sendmail) is a good choice.
+.IP "\fIpathhost\fR" 4
+.IX Item "pathhost"
+What to put into the Path: header to represent the local site. This is
+added to the Path: header of all articles that pass through the system,
+including locally posted articles, and is also used when processing some
+control messages and when naming the server in status reports. There is
+no default value; this parameter must be set in \fIinn.conf\fR or \s-1INN\s0 will
+not start. A good value to use is the fully-qualified hostname of the
+system.
+.IP "\fIserver\fR" 4
+.IX Item "server"
+The name of the default \s-1NNTP\s0 server. If \fInnrpdposthost\fR is not set and
+\&\s-1UNIX\s0 domain sockets are not supported, \fInnrpd\fR\|(8) tries to hand off
+locally-posted articles through an \s-1INET\s0 domain socket to this server.
+\&\fIactsync\fR\|(8), \fInntpget\fR\|(8), and \fIgetlist\fR\|(8) also use this value as the default
+server to connect to. In the latter cases, the value of the \s-1NNTPSERVER\s0
+environment variable, if it exists, overrides this. The default value is
+unset.
+.Sh "Feed Configuration"
+.IX Subsection "Feed Configuration"
+These parameters govern incoming and outgoing feeds: what size of
+articles are accepted, what filtering and verification is performed on
+them, whether articles in groups not carried by the server are still
+stored and propagated, and other similar settings.
+.IP "\fIartcutoff\fR" 4
+.IX Item "artcutoff"
+Articles older than this number of days are dropped. This setting should
+probably match the setting on the \f(CW\*(C`/remember/\*(C'\fR line in \fIexpire.ctl\fR.
+The default value is \f(CW10\fR.
+.IP "\fIbindaddress\fR" 4
+.IX Item "bindaddress"
+Which \s-1IP\s0 address \fIinnd\fR\|(8) should bind itself to. This must be in
+dotted-quad format (nnn.nnn.nnn.nnn). If set to \f(CW\*(C`all\*(C'\fR or not set, innd
+defaults to listening on all interfaces. The value of the
+\&\s-1INND_BIND_ADDRESS\s0 environment variable, if set, overrides this setting.
+The default value is unset.
+.IP "\fIbindaddress6\fR" 4
+.IX Item "bindaddress6"
+Like \fIbindaddress\fR but for IPv6 sockets. If only one of the \fIbindaddress\fR
+and \fIbindaddress6\fR parameters is used, then only the socket for the
+corresponding address family is created. If both parameters are used
+then two sockets are created. If neither of them is used, the list of
+sockets to listen on will be determined by the system library
+\&\fI\fIgetaddrinfo\fI\|(3)\fR function. The value of the \s-1INND_BIND_ADDRESS6\s0, if set,
+overrides this setting. The default value is unset.
+.Sp
+Note that you will generally need to put double quotes ("") around this
+value if you set it, since IPv6 addresses contain colons.
+.IP "\fIhiscachesize\fR" 4
+.IX Item "hiscachesize"
+If set to a value other than \f(CW0\fR, a hash of recently received message IDs
+is kept in memory to speed history lookups. The value is the amount of
+memory to devote to the cache in kilobytes. The cache is only used for
+incoming feeds and a small cache can hold quite a few message IDs, so
+large values aren't necessarily useful unless you have incoming feeds that
+are badly delayed. A good value for a system with more than one incoming
+feed is \f(CW256\fR; systems with only one incoming feed should probably leave
+this at \f(CW0\fR. The default value is \f(CW0\fR.
+.IP "\fIignorenewsgroups\fR" 4
+.IX Item "ignorenewsgroups"
+Whether newsgroup creation control messages (newgroup and rmgroup) should
+be fed as if they were posted to the newsgroup they are creating or
+deleting rather than to the newsgroups listed in the Newsgroups: header.
+If this parameter is set, the newsgroup affected by the control message
+will be extracted from the Control: header and the article will be fed as
+if its Newsgroups: header contained solely that newsgroup. This is useful
+for routing control messages to peers when they are posted to irrelevant
+newsgroups that shouldn't be matched against the peer's desired newsgroups
+in \fInewsfeeds\fR. This is a boolean value and the default is false.
+.IP "\fIimmediatecancel\fR" 4
+.IX Item "immediatecancel"
+When using the timecaf storage method, article cancels are normally just
+cached to be cancelled, not cancelled immediately. If this is set to
+true, they will instead by cancelled as soon as the cancel is processed.
+This is a boolean value and the default is false.
+.Sp
+This setting is ignored unless the timecaf storage method is used.
+.IP "\fIlinecountfuzz\fR" 4
+.IX Item "linecountfuzz"
+If set to something other than \f(CW0\fR, the line count of the article is
+checked against the Lines: header of the article (if present) and the
+artice is rejected if the values differ by more than this amount. A
+reasonable setting is \f(CW5\fR, which is the standard maximum signature length
+plus one (some injection software calculates the Lines: header before
+adding the signature). The default value is \f(CW0\fR, which tells \s-1INN\s0 not to
+check the Lines: header of incoming articles.
+.IP "\fImaxartsize\fR" 4
+.IX Item "maxartsize"
+The maximum size of article (headers and body) that will be accepted by
+the server, in bytes. A value of \f(CW0\fR allows any size of article, but
+note that \fBinnd\fR will crash if system memory is exceeded. The default
+value is \f(CW1000000\fR (approximately 1 \s-1MB\s0). See also \fIlocalmaxartsize\fR.
+.IP "\fImaxconnections\fR" 4
+.IX Item "maxconnections"
+The maximum number of incoming \s-1NNTP\s0 connections \fIinnd\fR\|(8) will accept. The
+default value is \f(CW50\fR.
+.IP "\fIpathalias\fR" 4
+.IX Item "pathalias"
+If set, this value is prepended to the Path: header of accepted posts
+(before \fIpathhost\fR) if it doesn't already appear in the Path: header.
+The main purpose of this parameter is to configure all news servers within
+a particular organization to add a common identity string to the
+Path: header. The default value is unset.
+.IP "\fIpathcluster\fR" 4
+.IX Item "pathcluster"
+If set, this value is appended to the Path: header of accepted posts
+(after \fIpathhost\fR) if it isn't already present as the last element
+of the Path: header. The main purpose of this parameter is to make
+several news servers appear as one server. The default value is unset.
+.Sp
+Note that the Path: header reads right to left, so appended means inserted
+at the leftmost side of the Path: header.
+.IP "\fIpgpverify\fR" 4
+.IX Item "pgpverify"
+Whether to enable \s-1PGP\s0 verification of control messages other than cancel.
+This is a boolean value and the default is based on whether configure found
+pgp, pgpv, or gpgv.
+.IP "\fIport\fR" 4
+.IX Item "port"
+What \s-1TCP\s0 port \fIinnd\fR\|(8) should listen on. The default value is \f(CW119\fR, the
+standard \s-1NNTP\s0 port.
+.IP "\fIrefusecybercancels\fR" 4
+.IX Item "refusecybercancels"
+Whether to refuse all articles whose message IDs start with
+\&\f(CW\*(C`<cancel.\*(C'\fR. This message \s-1ID\s0 convention is widely followed by spam
+cancellers, so the vast majority of such articles will be cancels of spam.
+This check, if enabled, is done before the history check and the message
+\&\s-1ID\s0 is not written to the history file. This is a boolean value and the
+default is false.
+.Sp
+This is a somewhat messy, inefficient, and inexact way of refusing spam
+cancels. A much better way is to ask all of your upstream peers to not
+send to you any articles with \f(CW\*(C`cyberspam\*(C'\fR in the Path: header (usually
+accomplished by having them mark \f(CW\*(C`cyberspam\*(C'\fR as an alias for your machine
+in their feed configuration). The filtering enabled by this parameter is
+hard\-coded; general filtering of message IDs can be done via the embedded
+filtering support.
+.IP "\fIremembertrash\fR" 4
+.IX Item "remembertrash"
+By default, \fIinnd\fR\|(8) records rejected articles in history so that, if
+offered the same article again, it can be refused before it is sent. If
+you wish to disable this behavior, set this to false. This can cause a
+substantial increase in the amount of bandwidth consumed by incoming news
+if you have several peers and reject a lot of articles, so be careful with
+it. Even if this is set to true, \s-1INN\s0 won't log some rejected articles to
+history if there's reason to believe the article might be accepted if
+offered by a different peer, so there is usually no reason to set this to
+false (although doing so can decrease the size of the history file). This
+is a boolean value and the default is true.
+.IP "\fIsourceaddress\fR" 4
+.IX Item "sourceaddress"
+Which local \s-1IP\s0 address to bind to for outgoing \s-1NNTP\s0 sockets (used by
+\&\fIinnxmit\fR\|(8) among other programs, but \fInot\fR \fIinnfeed\fR\|(8) \*(-- see
+\&\fIbindaddress\fR in \fIinnfeed.conf\fR\|(5) for that). This must be in dotted-quad
+format (nnn.nnn.nnn.nnn). If set to \f(CW\*(C`all\*(C'\fR or not set, the operating
+system will choose the source \s-1IP\s0 address for outgoing connections. The
+default value is unset.
+.IP "\fIsourceaddress6\fR" 4
+.IX Item "sourceaddress6"
+Like \fIsourceaddress\fR but for IPv6 sockets.
+.IP "\fIverifycancels\fR" 4
+.IX Item "verifycancels"
+Set this to true to enable a simplistic check on all cancel messages,
+attempting to verify (by simple header comparison) that the cancel message
+is from the same person as the original post. This can't be done if the
+cancel arrives before the article does, and is extremely easy to spoof.
+While this check may once have served a purpose, it's now essentially
+security via obscurity, commonly avoided by abusers, and probably not
+useful. This is a boolean value, and the default is false.
+.IP "\fIwanttrash\fR" 4
+.IX Item "wanttrash"
+Set this to true if you want to file articles posted to unknown newsgroups
+(newsgroups not in the \fIactive\fR file) into the \f(CW\*(C`junk\*(C'\fR newsgroup rather
+than rejecting them. This is sometimes useful for a transit news server
+that needs to propagate articles in all newsgroups regardless if they're
+carried locally. This is a boolean value and the default is false.
+.IP "\fIwipcheck\fR" 4
+.IX Item "wipcheck"
+If \s-1INN\s0 is offered an article by a peer on one channel, it will return
+deferral responses (code 436) to all other offers of that article for this
+many seconds. (After this long, if the peer that offered the article
+still hasn't sent it, it will be accepted from other channels.) The
+default value is \f(CW5\fR and probably doesn't need to be changed.
+.IP "\fIwipexpire\fR" 4
+.IX Item "wipexpire"
+How long, in seconds, to keep track of message IDs offered on a channel
+before expiring articles that still haven't been sent. The default value
+is \f(CW10\fR and probably doesn't need to be changed.
+.IP "\fIdontrejectfiltered\fR" 4
+.IX Item "dontrejectfiltered"
+Normally \fIinnd\fR\|(8) rejects incoming articles when directed to do so by any
+enabled article filters (Perl, Python, and \s-1TCL\s0). However, this parameter
+causes such articles \fInot\fR to be rejected; instead filtering can be
+applied on outbound articles. If this parameter is set, all articles will
+be accepted on the local machine, but articles rejected by the filter will
+\&\fInot\fR be fed to any peers specified in \fInewsfeeds\fR with the \f(CW\*(C`Af\*(C'\fR flag.
+.Sh "Article Storage"
+.IX Subsection "Article Storage"
+These parameters affect how articles are stored on disk.
+.IP "\fIcnfscheckfudgesize\fR" 4
+.IX Item "cnfscheckfudgesize"
+If set to a value other than \f(CW0\fR, the claimed size of articles in \s-1CNFS\s0
+cycbuffs is checked against \fImaxartsize\fR plus this value, and if larger,
+the \s-1CNFS\s0 cycbuff is considered corrupt. This can be useful as a sanity
+check after a system crash, but be careful using this parameter if you
+have changed \fImaxartsize\fR recently. The default value is \f(CW0\fR.
+.IP "\fIenableoverview\fR" 4
+.IX Item "enableoverview"
+Whether to write out overview data for articles. If set to false, \s-1INN\s0
+will run much faster, but reading news from the system will be impossible
+(the server will be for news transit only). If this option is set to
+true, \fIovmethod\fR must also be set. This is a boolean value and the
+default is true.
+.IP "\fIgroupbaseexpiry\fR" 4
+.IX Item "groupbaseexpiry"
+Whether to enable newsgroup-based expiry. If set to false, article expiry
+is done based on storage class of storing method. If set to true (and
+overview information is available), expiry is done by newsgroup name.
+This affects the format of \fIexpire.ctl\fR. This is a boolean value and the
+default is true.
+.IP "\fImergetogroups\fR" 4
+.IX Item "mergetogroups"
+Whether to file all postings to \f(CW\*(C`to.*\*(C'\fR groups in the pseudonewsgroup
+\&\f(CW\*(C`to\*(C'\fR. If this is set to true, the newsgroup \f(CW\*(C`to\*(C'\fR must exist in the
+\&\fIactive\fR file or \s-1INN\s0 will not start. (See the discussion of \f(CW\*(C`to.\*(C'\fR
+groups in \fIinnd\fR\|(8) under \s-1CONTROL\s0 \s-1MESSAGES\s0.) This is a boolean value and
+the default is false.
+.IP "\fIovercachesize\fR" 4
+.IX Item "overcachesize"
+How many cache slots to reserve for open overview files. If \s-1INN\s0 is
+writing overview files (see \fIenableoverview\fR), \fIovmethod\fR is set to
+\&\f(CW\*(C`tradindexed\*(C'\fR, and this is set to a value other than \f(CW0\fR, \s-1INN\s0 will keep
+around and open that many recently written-to overview files in case more
+articles come in for those newsgroups. Every overview cache slot consumes
+two file descriptors, so be careful not to set this value too high. You
+may be able to use the \f(CW\*(C`limit\*(C'\fR command to see how many open file
+descriptors your operating system allows. \fIinnd\fR\|(8) also uses an open file
+descriptor for each incoming feed and outgoing channel or batch file, and
+if it runs out of open file descriptors it may throttle and stop accepting
+new news. The default value is \f(CW15\fR (which is probably way too low if
+you have a large number of file descriptors available).
+.Sp
+This setting is ignored unless \fIovmethod\fR is set to \f(CW\*(C`tradindexed\*(C'\fR.
+.IP "\fIovgrouppat\fR" 4
+.IX Item "ovgrouppat"
+If set, restricts the overview data stored by \s-1INN\s0 to only the newsgroups
+matching this comma-separated list of wildmat expressions. Newsgroups not
+matching this setting may not be readable, and if \fIgroupbaseexpiry\fR is
+set to true and the storage method for these newsgroups does not have
+self-expire functionality, storing overview data will fail.
+The default is unset.
+.IP "\fIovmethod\fR" 4
+.IX Item "ovmethod"
+Which overview storage method to use. Currently supported values are
+\&\f(CW\*(C`tradindexed\*(C'\fR, \f(CW\*(C`buffindexed\*(C'\fR, and \f(CW\*(C`ovdb\*(C'\fR. There is no default value;
+this parameter must be set if \fIenableoverview\fR is true (the default).
+.RS 4
+.ie n .IP """buffindexed""" 4
+.el .IP "\f(CWbuffindexed\fR" 4
+.IX Item "buffindexed"
+Stores overview data and index information into buffers, which are
+preconfigured files defined in \fIbuffinedexed.conf\fR. \f(CW\*(C`buffindexed\*(C'\fR never
+consumes additional disk space beyond that allocated to these buffers.
+.ie n .IP """tradindexed""" 4
+.el .IP "\f(CWtradindexed\fR" 4
+.IX Item "tradindexed"
+Uses two files per newsgroup, one containing the overview data and one
+containing the index. Fast for readers, but slow to write to.
+.ie n .IP """ovdb""" 4
+.el .IP "\f(CWovdb\fR" 4
+.IX Item "ovdb"
+Stores data into a Berkeley \s-1DB\s0 database. See the \fIovdb\fR\|(5) man page.
+.RE
+.RS 4
+.RE
+.IP "\fIhismethod\fR" 4
+.IX Item "hismethod"
+Which history storage method to use. The only currently supported
+value is \f(CW\*(C`hisv6\*(C'\fR. There is no default value; this parameter must
+be set.
+.RS 4
+.ie n .IP """hisv6""" 4
+.el .IP "\f(CWhisv6\fR" 4
+.IX Item "hisv6"
+Stores history data in the \s-1INN\s0 history v6 format: \fIhistory\fR\|(5) text
+file and a number of \fIdbz\fR\|(3) database files; this may be in true history
+v6 format, or tagged hash format, depending on the build
+options. Separation of these two is a project which has not yet been
+undertaken.
+.RE
+.RS 4
+.RE
+.IP "\fIstoreonxref\fR" 4
+.IX Item "storeonxref"
+If set to true, articles will be stored based on the newsgroup names in
+the Xref: header rather than in the Newsgroups: header. This affects what
+the patterns in \fIstorage.conf\fR apply to. The primary interesting effect
+of setting this to true is to enable filing of all control messages
+according to what storage class the control pseudogroups are filed in
+rather than according to the newsgroups the control messages are posted
+to. This is a boolean value and the default is true.
+.IP "\fIuseoverchan\fR" 4
+.IX Item "useoverchan"
+Whether to \fIinnd\fR\|(8) should create overview data internally through
+\&\fIlibstorage\fR\|(3). If set to false, innd creates overview data by itself. If
+set to true, innd does not create; instead overview data must be created
+by \fIoverchan\fR\|(8) from an appropriate entry in \fInewsfeeds\fR. Setting to true
+may be useful, if innd cannot keep up with incoming feed and the
+bottleneck is creation of overview data within innd. This is a boolean
+value and the default is false.
+.IP "\fIwireformat\fR" 4
+.IX Item "wireformat"
+Only used with the tradspool storage method, this says whether to write
+articles in wire format. Wire format means storing articles with \f(CW\*(C`\er\en\*(C'\fR at
+the end of each line and with periods at the beginning of lines doubled,
+the article format required by the \s-1NNTP\s0 protocol. Articles stored in this
+format are suitable for sending directly to a network connection without
+requiring conversion, and therefore setting this to true can make the
+server more efficient. The primary reason not to set this is if you have
+old existing software that looks around in the spool and doesn't
+understand how to read wire format. Storage methods other than tradspool
+always store articles in wire format. This is a boolean value and the
+default is false.
+.IP "\fIxrefslave\fR" 4
+.IX Item "xrefslave"
+Whether to act as the slave of another server. If set, \s-1INN\s0 attempts to
+duplicate exactly the article numbering of the server feeding it by
+looking at the Xref: header of incoming articles and assigning the same
+article numbers to articles as was noted in the Xref: header from the
+upstream server. The result is that clients should be able to point at
+either server interchangeably (using some load balancing scheme, for
+example) and see the same internal article numbering. Servers with this
+parameter set should generally only have one upstream feed, and should
+always have \fInnrpdposthost\fR set to hand locally posted articles off to
+the master server. The upstream should be careful to always feed articles
+in order (\fIinnfeed\fR\|(8) can have problems with this in the event of a
+backlog). This is a boolean value and the default is false.
+.IP "\fInfswriter\fR" 4
+.IX Item "nfswriter"
+For servers writing articles, determine whether the article spool is
+on \s-1NFS\s0 storage. If set, \s-1INN\s0 attempts to flush articles to the spool
+in a more timely manner, rather than relying on the operating system
+to flush things such as the \s-1CNFS\s0 article bitmaps. You should only set
+this parameter if you are attempting to use a shared \s-1NFS\s0 spool on a
+machine acting as a single writer within a cluster. This is a boolean
+value and the default is false.
+.IP "\fInfsreader\fR" 4
+.IX Item "nfsreader"
+For servers reading articles, determine whether the article spool is
+on \s-1NFS\s0 storage. If set, \s-1INN\s0 will attempt to force articles and
+overviews to be read directly from the \s-1NFS\s0 spool rather than from
+cached copies. You should only set this parameter if you are
+attempting to use a shared \s-1NFS\s0 spool on a machine acting a reader a
+cluster. This is a boolean value and the default is false.
+.IP "\fInfsreaderdelay\fR" 4
+.IX Item "nfsreaderdelay"
+For servers reading articles, determine whether the article spool is
+on \s-1NFS\s0 storage. If \fInfsreader\fR is set, \s-1INN\s0 will use the value of
+\&\fInfsreaderdelay\fR to delay the apparent arrival time of articles to
+clients by this amount; this value should be tuned based on the \s-1NFS\s0
+cache timeouts locally. This default is 60 (1 minute).
+.IP "\fImsgidcachesize\fR" 4
+.IX Item "msgidcachesize"
+How many cache slots to reserve for Message \s-1ID\s0 to storage token
+translations. When serving overview data to clients (\s-1NEWNEWS\s0, \s-1XOVER\s0
+etc.), \fInnrpd\fR\|(8) can cache the storage token associated with a Message
+\&\s-1ID\s0 and save the cost of looking it up in the history file; for some
+configurations setting this parameter can save more than 90% of the
+wall clock time for a session. The default value is 10000.
+.IP "\fItradindexedmmap\fR" 4
+.IX Item "tradindexedmmap"
+Whether to attempt to \fImmap()\fR tradindexed overviews articles. Setting
+this to true will give better performance on most systems, but some
+systems have problems with \fImmap()\fR. If this is set to false, overviews
+will be read into memory before being sent to readers. This is a
+boolean value and the default is true.
+.Sh "Reading"
+.IX Subsection "Reading"
+These parameters affect the behavior of \s-1INN\s0 for readers. Most of them are
+used by \fInnrpd\fR\|(8). There are some special sets of settings that are broken
+out separately after the initial alphabetized list.
+.IP "\fIallownewnews\fR" 4
+.IX Item "allownewnews"
+Whether to allow use of the \s-1NEWNEWS\s0 command by clients. This command used
+to put a heavy load on the server in older versions of \s-1INN\s0, but is now
+reasonably efficient, at least if only one newsgroup is specified by the
+client. This is a boolean value and the default is true. If you use the
+\&\fIaccess\fR parameter in \fIreaders.conf\fR, be sure to read about the way it
+overrides \fIallownewnews\fR.
+.IP "\fIarticlemmap\fR" 4
+.IX Item "articlemmap"
+Whether to attempt to \fImmap()\fR articles. Setting this to true will give
+better performance on most systems, but some systems have problems with
+\&\fImmap()\fR. If this is set to false, articles will be read into memory before
+being sent to readers. This is a boolean value and the default is false.
+.IP "\fIclienttimeout\fR" 4
+.IX Item "clienttimeout"
+How long (in seconds) a client connection can be idle before it exits.
+When setting this parameter, be aware that some newsreaders use the same
+connection for reading and posting and don't deal well with the connection
+timing out while a post is being composed. If the system isn't having a
+problem with too many long-lived connections, it may be a good idea to
+increase this value to \f(CW3600\fR (an hour). The default value is \f(CW600\fR
+(ten minutes).
+.IP "\fIinitialtimeout\fR" 4
+.IX Item "initialtimeout"
+How long (in seconds) \fBnnrpd\fR will wait for the first command from a
+reader connection before dropping the connection. This is a defensive
+timeout intended to protect the news server from badly behaved reader
+clients that open and abandon a multitude of connections without every
+closing them. The default value is \f(CW10\fR (ten seconds), which may need to
+be increased if many clients connect via slow network links.
+.IP "\fInnrpdcheckart\fR" 4
+.IX Item "nnrpdcheckart"
+Whether \fBnnrpd\fR should check the existence of an article before listing
+it as present in response to an \s-1NNTP\s0 command. The primary use of this
+setting is to prevent nnrpd from returning information about articles
+which are no longer present on the server but which still have overview
+data available. Checking the existence of articles before returning
+overview information slows down the overview commands, but reduces the
+number of \*(L"article is missing\*(R" errors seen by the client. This is a
+boolean value and the default is true.
+.IP "\fInnrpperlauth\fR" 4
+.IX Item "nnrpperlauth"
+This parameter is now obsolete; see \*(L"Changes to Perl Authentication
+Support for nnrpd\*(R" in \fIdoc/hook\-perl\fR.
+.IP "\fInnrppythonauth\fR" 4
+.IX Item "nnrppythonauth"
+This parameter is now obsolete; see \*(L"Changes to Python Authentication and
+Access Control Support for nnrpd\*(R" in \fIdoc/hook\-python\fR.
+.IP "\fInoreader\fR" 4
+.IX Item "noreader"
+Normally, \fIinnd\fR\|(8) will fork a copy of \fInnrpd\fR\|(8) for all incoming
+connections from hosts not listed in \fIincoming.conf\fR. If this parameter
+is set to true, those connections will instead be rejected with a 502
+error code. This should be set to true for a transit-only server that
+doesn't support readers, or if nnrpd is running in daemon mode or being
+started out of inetd. This is a boolean value and the default is false.
+.IP "\fIreaderswhenstopped\fR" 4
+.IX Item "readerswhenstopped"
+Whether to allow readers to connect even if the server is paused or
+throttled. This is only applicable if \fInnrpd\fR\|(8) is spawned from \fIinnd\fR\|(8)
+rather than run out of inetd or in daemon mode. This is a boolean value
+and the default is false.
+.IP "\fIreadertrack\fR" 4
+.IX Item "readertrack"
+Whether to enable the tracking system for client behavior. Tracked
+information is recorded to \fIpathlog\fR/tracklogs/log\-ID, where \s-1ID\s0 is
+determined by nnrpd's \s-1PID\s0 and launch time.) Currently the information
+recorded includes initial connection and posting; only information about
+clients listed in \fInnrpd.track\fR is recorded. This is a boolean value and
+the default is false.
+.IP "\fInnrpdflags\fR" 4
+.IX Item "nnrpdflags"
+When \fInnrpd\fR\|(8) is spawned from \fIinnd\fR\|(8), these flags are passed as
+arguments to the nnrpd process. This setting does not affect instances
+of nnrpd that are started in daemon mode, or instances that are started
+via another listener process such as \fIinetd\fR\|(8) or \fIxinetd\fR\|(8). Shell
+quoting and metacharacters are not supported. This is a string value
+and the default is unset.
+.IP "\fInnrpdloadlimit\fR" 4
+.IX Item "nnrpdloadlimit"
+If set to a value other than \f(CW0\fR, connections to nnrpd will be refused
+if the system load average is higher than this value. The default value
+is \f(CW16\fR.
+.PP
+\&\s-1INN\s0 has optional support for generating keyword information automatically
+from article body text and putting that information in overview for the
+use of clients that know to look for it. The following parameters control
+that feature.
+.PP
+This may be too slow if you're taking a substantial feed, and probably
+will not be useful for the average news reader; enabling this is not
+recommended unless you have some specific intention to take advantage of
+it.
+.IP "\fIkeywords\fR" 4
+.IX Item "keywords"
+Whether the keyword generation support should be enabled. This is a
+boolean value and the default is false.
+.Sp
+\&\s-1FIXME:\s0 Currently, support for keyword generation is configured into \s-1INN\s0
+semi-randomly (based on whether configure found the regex library); it
+should be an option to configure and that option should be mentioned here.
+.IP "\fIkeyartlimit\fR" 4
+.IX Item "keyartlimit"
+Articles larger than this value in bytes will not have keywords generated
+for them (since it would take too long to do so). The default value is
+\&\f(CW100000\fR (approximately 100 \s-1KB\s0).
+.IP "\fIkeylimit\fR" 4
+.IX Item "keylimit"
+Maximum number of bytes allocated for keyword data. If there are more
+keywords than will fit into this many bytes when separated by commas, the
+rest are discarded. The default value is \f(CW512\fR.
+.IP "\fIkeymaxwords\fR" 4
+.IX Item "keymaxwords"
+Maximum number of keywords that will be generated for an article. (The
+keyword generation code will attempt to discard \*(L"noise\*(R" words, so the
+number of keywords actually writen into the overview will usually be
+smaller than this even if the maximum number of keywords is found.) The
+default value is \f(CW250\fR.
+.Sh "Posting"
+.IX Subsection "Posting"
+These parameters are only used by \fInnrpd\fR\|(8), \fIinews\fR\|(1), and other programs
+that accept or generate postings. There are some special sets of settings
+that are broken out separately after the initial alphabetized list.
+.IP "\fIaddnntppostingdate\fR" 4
+.IX Item "addnntppostingdate"
+Whether to add an NNTP\-Posting\-Date: header to all local posts. This is a
+boolean value and the default is true. Note that \s-1INN\s0 either does not add
+this header or adds the name or \s-1IP\s0 address of the client. There is no
+intrinsic support for obfuscating the name of the client. That has to be
+done with a user-written Perl filter, if desired.
+.IP "\fIaddnntppostinghost\fR" 4
+.IX Item "addnntppostinghost"
+Whether to add an NNTP\-Posting\-Host: header to all local posts giving the
+\&\s-1FQDN\s0 or \s-1IP\s0 address of the system from which the post was received. This
+is a boolean value and the default is true.
+.IP "\fIcheckincludedtext\fR" 4
+.IX Item "checkincludedtext"
+Whether to check local postings for the ratio of new to quoted text and
+reject them if that ratio is under 50%. Included text is recognized by
+looking for lines beginning with \f(CW\*(C`>\*(C'\fR, \f(CW\*(C`|\*(C'\fR, or \f(CW\*(C`:\*(C'\fR. This is a
+boolean value and the default is false.
+.IP "\fIcomplaints\fR" 4
+.IX Item "complaints"
+The value of the X\-Complaints\-To: header added to all local posts. The
+default is the newsmaster's e\-mail address. (If the newsmaster, selected
+at configure time and defaulting to \f(CW\*(C`usenet\*(C'\fR, doesn't contain \f(CW\*(C`@\*(C'\fR, the
+address will consist of the newsmaster, a \f(CW\*(C`@\*(C'\fR, and the value of
+\&\fIfromhost\fR.)
+.IP "\fIfromhost\fR" 4
+.IX Item "fromhost"
+Contains a domain used to construct e\-mail addresses. The address of the
+local news administrator will be given as <user>@\fIfromhost\fR, where <user>
+is the newsmaster user set at compile time (\f(CW\*(C`usenet\*(C'\fR by default). This
+setting will also be used by \fImailpost\fR\|(8) to fully qualify addresses and by
+\&\fIinews\fR\|(1) to generate the Sender: header (and From: header if missing).
+The value of the \s-1FROMHOST\s0 environment variable, if set, overrides this
+setting. The default is the fully-qualified domain name of the local
+host.
+.IP "\fIlocalmaxartsize\fR" 4
+.IX Item "localmaxartsize"
+The maximum article size (in bytes) for locally posted articles. Articles
+larger than this will be rejected. A value of \f(CW0\fR allows any size of
+article, but note that \fBnnrpd\fR and \fBinnd\fR will crash if system memory is
+exceeded. See also \fImaxartsize\fR, which applies to all articles including
+those posted locally. The default value is \f(CW1000000\fR (approximately 1
+\&\s-1MB\s0).
+.IP "\fImoderatormailer\fR" 4
+.IX Item "moderatormailer"
+The address to which to send submissions for moderated groups. It is only
+used if the \fImoderators\fR file doesn't exist, or if the moderated group to
+which an article is posted is not matched by any entry in that file, and
+takes the same form as an entry in the \fImoderators\fR file. In most cases,
+\&\f(CW\*(C`%s@moderators.isc.org\*(C'\fR is a good value for this parameter (\f(CW%s\fR is
+expanded into a form of the newsgroup name). See \fImoderators\fR\|(5) for more
+details about the syntax. The default is unset. If this parameter isn't
+set and an article is posted to a moderated group that does not have a
+matching entry in the \fImoderators\fR file, the posting will be rejected
+with an error.
+.IP "\fInnrpdauthsender\fR" 4
+.IX Item "nnrpdauthsender"
+Whether to generate a Sender: header based on reader authentication. If
+this parameter is set, a Sender: header will be added to local posts
+containing the identity assigned by \fIreaders.conf\fR. If the assigned
+identity does not include an \f(CW\*(C`@\*(C'\fR, the reader's hostname is used. If this
+parameter is set but no identity is be assigned, the Sender: header will
+be removed from all posts even if the poster includes one. This is a
+boolean value and the default is false.
+.IP "\fInnrpdposthost\fR" 4
+.IX Item "nnrpdposthost"
+If set, \fInnrpd\fR\|(8) and \fIrnews\fR\|(1) will pass all locally posted articles to the
+specified host rather than trying to inject them locally. See also
+\&\fInnrpdpostport\fR. This should always be set if \fIxrefslave\fR is true. The
+default value is unset.
+.IP "\fInnrpdpostport\fR" 4
+.IX Item "nnrpdpostport"
+The port on the remote server to connect to to post when \fInnrpdposthost\fR
+is used. The default value is \f(CW119\fR.
+.IP "\fIorganization\fR" 4
+.IX Item "organization"
+What to put in the Organization: header if it is left blank by the poster.
+The value of the \s-1ORGANIZATION\s0 environment variable, if set, overrides this
+setting. The default is unset, which tells \s-1INN\s0 not to insert an
+Organization: header.
+.IP "\fIspoolfirst\fR" 4
+.IX Item "spoolfirst"
+If true, \fInnrpd\fR\|(8) will spool new articles rather than attempting to send
+them to \fIinnd\fR\|(8). If false, nnrpd will spool articles only if it receives
+an error trying to send them to innd. Setting this to true can be useful
+if nnrpd must respond as fast as possible to the client; however, when
+set, articles will not appear to readers until they are given to innd.
+nnrpd won't do this; \f(CW\*(C`rnews \-U\*(C'\fR must be run periodically to take the
+spooled articles and post them. This is a boolean value and the default
+is false.
+.IP "\fIstrippostcc\fR" 4
+.IX Item "strippostcc"
+Whether to strip To:, Cc:, and Bcc: headers out of all local posts via
+\&\fInnrpd\fR\|(8). The primary purpose of this setting is to prevent abuse of the
+news server by posting to a moderated group and including To: or Cc:
+headers in the post so that the news server will send the article to
+arbitrary addresses. \s-1INN\s0 now protects against this abuse in other ways
+provided \fImta\fR is set to a command that includes \f(CW%s\fR and honors it, so
+this is generally no longer needed. This is a boolean value and the
+default is false.
+.PP
+\&\fInnrpd\fR\|(8) has support for controlling high-volume posters via an
+exponential backoff algorithm, as configured by the following parameters.
+.PP
+Exponential posting backoff works as follows: News clients are indexed by
+\&\s-1IP\s0 address (or username, see \fIbackoffauth\fR below). Each time a post is
+received from an \s-1IP\s0 address, the time of posting is stored (along with the
+previous sleep time, see below). After a configurable number of posts in
+a configurable period of time, \fInnrpd\fR\|(8) will activate posting backoff and
+begin to sleep for increasing periods of time before actually posting
+anything. Posts will still be accepted, but at an increasingly reduced
+rate.
+.PP
+After backoff has been activated, the length of time to sleep is computed
+based on the difference in time between the last posting and the current
+posting. If this difference is less than \fIbackoffpostfast\fR, the new
+sleep time will be 1 + (previous sleep time * \fIbackoffk\fR). If this
+difference is less than \fIbackoffpostslow\fR but greater than
+\&\fIbackoffpostfast\fR, then the new sleep time will equal the previous sleep
+time. If this difference is greater than \fIbackoffpostslow\fR, the new
+sleep time is zero and posting backoff is deactivated for this poster.
+.PP
+Exponential posting backoff will not be enabled unless \fIbackoffdb\fR is set
+and \fIbackoffpostfast\fR and \fIbackoffpostslow\fR are set to something other
+than their default values.
+.PP
+Here are the parameters that control exponential posting backoff:
+.IP "\fIbackoffauth\fR" 4
+.IX Item "backoffauth"
+Whether to index posting backoffs by user rather than by source \s-1IP\s0
+address. You must be using authentication in \fInnrpd\fR\|(8) for a value of true
+to have any meaning. This is a boolean value and the default is false.
+.IP "\fIbackoffdb\fR" 4
+.IX Item "backoffdb"
+The path to a directory, writeable by the news user, that will contain the
+backoff database. There is no default for this parameter; you must
+provide a path to a creatable or writeable directory to enable exponential
+backoff.
+.IP "\fIbackoffk\fR" 4
+.IX Item "backoffk"
+The amount to multiply the previous sleep time by if the user is still
+posting too quickly. A value of \f(CW2\fR will double the sleep time for each
+excessive post. The default value is \f(CW1\fR.
+.IP "\fIbackoffpostfast\fR" 4
+.IX Item "backoffpostfast"
+Postings from the same identity that arrive in less than this amount of
+time (in seconds) will trigger increasing sleep time in the backoff
+algorithm. The default value is \f(CW0\fR.
+.IP "\fIbackoffpostslow\fR" 4
+.IX Item "backoffpostslow"
+Postings from the same identity that arrive in greater than this amount of
+time (in seconds) will reset the backoff algorithm. Another way to look
+at this constant is to realize that posters will be allowed to generate at
+most 86400/\fIbackoffpostslow\fR posts per day. The default value is \f(CW1\fR.
+.IP "\fIbackofftrigger\fR" 4
+.IX Item "backofftrigger"
+This many postings are allowed before the backoff algorithm is triggered.
+The default value is \f(CW10000\fR.
+.Sh "Monitoring"
+.IX Subsection "Monitoring"
+These parameters control the behavior of \fIinnwatch\fR\|(8), the program that
+monitors \s-1INN\s0 and informs the news administrator if anything goes wrong
+with it.
+.IP "\fIdoinnwatch\fR" 4
+.IX Item "doinnwatch"
+Whether to start \fIinnwatch\fR\|(8) from rc.news. This is a boolean value, and
+the default is true.
+.IP "\fIinnwatchbatchspace\fR" 4
+.IX Item "innwatchbatchspace"
+Free space in \fIpathoutgoing\fR, in \fIinndf\fR\|(8) output units (normally
+kilobytes), at which \fIinnd\fR\|(8) will be throttled by \fIinnwatch\fR\|(8), assuming a
+default \fIinnwatch.ctl\fR. The default value is \f(CW800\fR.
+.IP "\fIinnwatchlibspace\fR" 4
+.IX Item "innwatchlibspace"
+Free space in \fIpathdb\fR, in \fIinndf\fR\|(8) output units (normally kilobytes), at
+which \fIinnd\fR\|(8) will be throttled by \fIinnwatch\fR\|(8), assuming a default
+\&\fIinnwatch.ctl\fR. The default value is \f(CW25000\fR.
+.IP "\fIinnwatchloload\fR" 4
+.IX Item "innwatchloload"
+Load average times 100 at which \fIinnd\fR\|(8) will be restarted by \fIinnwatch\fR\|(8)
+(undoing a previous pause or throttle), assuming a default
+\&\fIinnwatch.ctl\fR. The default value is \f(CW1000\fR (that is, a load average of
+10.00).
+.IP "\fIinnwatchhiload\fR" 4
+.IX Item "innwatchhiload"
+Load average times 100 at which \fIinnd\fR\|(8) will be throttled by \fIinnwatch\fR\|(8),
+assuming a default \fIinnwatch.ctl\fR. The default value is \f(CW2000\fR (that
+is, a load average of 20.00).
+.IP "\fIinnwatchpauseload\fR" 4
+.IX Item "innwatchpauseload"
+Load average times 100 at which \fIinnd\fR\|(8) will be paused by \fIinnwatch\fR\|(8),
+assuming a default \fIinnwatch.ctl\fR. The default value is \f(CW1500\fR (that
+is, a load average of 15.00).
+.IP "\fIinnwatchsleeptime\fR" 4
+.IX Item "innwatchsleeptime"
+How long (in seconds) \fIinnwatch\fR\|(8) will sleep between each check of \s-1INN\s0.
+The default value is \f(CW600\fR.
+.IP "\fIinnwatchspoolnodes\fR" 4
+.IX Item "innwatchspoolnodes"
+Free inodes in \fIpatharticles\fR at which \fIinnd\fR\|(8) will be throttled by
+\&\fIinnwatch\fR\|(8), assuming a default \fIinnwatch.ctl\fR. The default value is
+\&\f(CW200\fR.
+.IP "\fIinnwatchspoolspace\fR" 4
+.IX Item "innwatchspoolspace"
+Free space in \fIpatharticles\fR and \fIpathoverview\fR, in \fIinndf\fR\|(8) output
+units (normally kilobytes), at which \fIinnd\fR\|(8) will be throttled by
+\&\fIinnwatch\fR\|(8), assuming a default \fIinnwatch.ctl\fR. The default value is
+\&\f(CW8000\fR.
+.Sh "Logging"
+.IX Subsection "Logging"
+These parameters control what information \s-1INN\s0 logs.
+.IP "\fIdocnfsstat\fR" 4
+.IX Item "docnfsstat"
+Whether to start \fIcnfsstat\fR\|(8) when \fIinnd\fR\|(8) is started. cnfsstat will log
+the status of all \s-1CNFS\s0 cycbuffs to syslog on a periodic basis (frequency
+is the default for \f(CW\*(C`cnfsstat \-l\*(C'\fR, currently 600 seconds). This is a
+boolean value and the default is false.
+.IP "\fIlogartsize\fR" 4
+.IX Item "logartsize"
+Whether the size of accepted articles (in bytes) should be written to the
+article log file. This is useful for flow rate statistics and is
+recommended. This is a boolean value and the default is true.
+.IP "\fIlogcancelcomm\fR" 4
+.IX Item "logcancelcomm"
+Set this to true to log \f(CW\*(C`ctlinnd cancel\*(C'\fR commands to syslog. This is a
+boolean value and the default is false.
+.IP "\fIlogcycles\fR" 4
+.IX Item "logcycles"
+How many old logs \fIscanlogs\fR\|(8) keeps. \fIscanlogs\fR\|(8) is generally run by
+\&\fInews.daily\fR\|(8) and will archive compressed copies of this many days worth
+of old logs. The default value is \f(CW3\fR.
+.IP "\fIlogipaddr\fR" 4
+.IX Item "logipaddr"
+Whether the verified name of the remote feeding host should be logged to
+the article log for incoming articles rather than the last entry in the
+Path: header. The only reason to ever set this to false is due to some
+interactions with \fInewsfeeds\fR flags; see \fInewsfeeds\fR\|(5) for more
+information. This is a boolean value and the default is true.
+.IP "\fIlogsitename\fR" 4
+.IX Item "logsitename"
+Whether the names of the sites to which accepted articles will be sent
+should be put into the article log file. This is useful for debugging and
+statistics and can be used by \fInewsrequeue\fR\|(8). This is a boolean value and
+the default is true.
+.IP "\fInnrpdoverstats\fR" 4
+.IX Item "nnrpdoverstats"
+Whether nnrpd overview statistics should be logged via syslog. This can
+be useful for measuring overview performance. This is a boolean value and
+the default is false.
+.IP "\fInntpactsync\fR" 4
+.IX Item "nntpactsync"
+How many articles to process on an incoming channel before logging the
+activity. The default value is \f(CW200\fR.
+.Sp
+\&\s-1FIXME:\s0 This is a rather unintuitive name for this parameter.
+.IP "\fInntplinklog\fR" 4
+.IX Item "nntplinklog"
+Whether to put the storage \s-1API\s0 token for accepted articles (used by
+nntplink) in the article log. This is a boolean value and the default is
+false.
+.IP "\fIstathist\fR" 4
+.IX Item "stathist"
+Where to write history statistics for analysis with
+\&\fIcontrib/stathist.pl\fR; this can be modified with \fIctlinnd\fR\|(8) while innd is
+running. Logging does not occur unless a path is given, and there is no
+default value.
+.IP "\fIstatus\fR" 4
+.IX Item "status"
+How frequently (in seconds) \fIinnd\fR\|(8) should write out a status report. The
+report is written to \fIpathhttp\fR/inn_status.html. If this is set to \f(CW0\fR or
+\&\f(CW\*(C`false\*(C'\fR, status reporting is disabled. The default value is \f(CW0\fR.
+.IP "\fItimer\fR" 4
+.IX Item "timer"
+How frequently (in seconds) \fIinnd\fR\|(8) should report performance timings to
+syslog. If this is set to \f(CW0\fR, performance timing is disabled. Enabling
+this is highly recommended, and \fIinnreport\fR\|(8) can produce a nice summary of
+the timings. If set to \f(CW0\fR, performance timings in \fInnrpd\fR\|(8) are also
+disabled, although nnrpd always reports statistics on exit and therefore
+any non-zero value is equivalent for it. The default value is \f(CW0\fR.
+.Sh "System Tuning"
+.IX Subsection "System Tuning"
+The following parameters can be modified to tune the low-level operation
+of \s-1INN\s0. In general, you shouldn't need to modify any of them except
+possibly \fIrlimitnofile\fR unless the server is having difficulty.
+.IP "\fIbadiocount\fR" 4
+.IX Item "badiocount"
+How many read or write failures until a channel is put to sleep or
+closed. The default value is \f(CW5\fR.
+.IP "\fIblockbackoff\fR" 4
+.IX Item "blockbackoff"
+Each time an attempted write returns \s-1EAGAIN\s0 or \s-1EWOULDBLOCK\s0, \fIinnd\fR\|(8) will
+wait for an increasing number of seconds before trying it again. This is
+the multiplier for the sleep time. If you're having trouble with channel
+feeds not keeping up, it may be good to change this value to \f(CW2\fR or \f(CW3\fR,
+since then when the channel fills \s-1INN\s0 will try again in a couple of
+seconds rather than waiting two minutes. The default value is \f(CW120\fR.
+.IP "\fIchaninacttime\fR" 4
+.IX Item "chaninacttime"
+The time (in seconds) to wait between noticing inactive channels. The
+default value is \f(CW600\fR.
+.IP "\fIchanretrytime\fR" 4
+.IX Item "chanretrytime"
+How many seconds to wait before a channel restarts. The default value is
+\&\f(CW300\fR.
+.IP "\fIdatamovethreshold\fR" 4
+.IX Item "datamovethreshold"
+The threshold for deciding whether to move already-read data to the top of
+buffer or extend the buffer. The buffer described here is used for reading
+\&\s-1NNTP\s0 data. Increasing this value may improve performance, but it should
+not be increased on Systems with insufficient memory. Permitted values
+are between \f(CW0\fR and \f(CW1048576\fR (out of range values are treated as
+\&\f(CW1048576\fR) and the default value is \f(CW8192\fR.
+.IP "\fIicdsynccount\fR" 4
+.IX Item "icdsynccount"
+How many article writes between updating the active and history files.
+The default value is \f(CW10\fR.
+.IP "\fIkeepmmappedthreshold\fR" 4
+.IX Item "keepmmappedthreshold"
+When using buffindexed, retrieving overview data (that is, responding to
+\&\s-1XOVER\s0 or running expireover) causes mmapping of all overview data blocks
+which include requested overview data for newsgroup. But for high volume
+newsgroups like control.cancel, this may cause too much mmapping at once
+leading to system resource problems. To avoid this, if the amount to be
+mmapped exceeds \fIkeepmmappedthreshold\fR (in \s-1KB\s0), buffindexed mmap's just
+one overview block (8 \s-1KB\s0). This parameter is specific to buffindexed
+overview storage method. The default value is \f(CW1024\fR (1 \s-1MB\s0).
+.IP "\fImaxcmdreadsize\fR" 4
+.IX Item "maxcmdreadsize"
+If set to anything other than \f(CW0\fR, maximum buffer size (in bytes) for
+reading \s-1NNTP\s0 command will have this value. It should not be large on
+systems which are slow to process and store articles, as that would lead
+to \fIinnd\fR\|(8) spending a long time on each channel and keeping other channels
+waiting. The default value is \s-1BUFSIZ\s0 defined in stdio.h (\f(CW1024\fR in most
+environments, see \fIsetbuf\fR\|(3)).
+.IP "\fImaxforks\fR" 4
+.IX Item "maxforks"
+How many times to attempt a \fIfork\fR\|(2) before giving up. The default value
+is \f(CW10\fR.
+.IP "\fInicekids\fR" 4
+.IX Item "nicekids"
+If set to anything other than \f(CW0\fR, all child processes of \fIinnd\fR\|(8) will
+have this \fInice\fR\|(2) value. This is usually used to give all child processes
+of \fIinnd\fR\|(8) a lower priority (higher nice value) so that \fIinnd\fR\|(8) can get
+the lion's share of the \s-1CPU\s0 when it needs it. The default value is \f(CW4\fR.
+.IP "\fInicenewnews\fR" 4
+.IX Item "nicenewnews"
+If set to anything greater than \f(CW0\fR, all \fInnrpd\fR\|(8) processes that receive
+and process a \s-1NEWNEWS\s0 command will \fInice\fR\|(2) themselves to this value
+(giving other nnrpd processes a higher priority). The default value is
+\&\f(CW0\fR. Note that this value will be ignored if set to a lower value than
+\&\fInicennrpd\fR (or \fInicekids\fR if \fInnrpd\fR\|(8) is spawned from \fIinnd\fR\|(8)).
+.IP "\fInicennrpd\fR" 4
+.IX Item "nicennrpd"
+If set to anything greater than \f(CW0\fR, all \fInnrpd\fR\|(8) processes will \fInice\fR\|(1)
+themselves to this value. This gives other news processes a higher
+priority and can help \fIoverchan\fR\|(8) keep up with incoming news (if that's
+the object, be sure \fIoverchan\fR\|(8) isn't also set to a lower priority via
+\&\fInicekids\fR). The default value is \f(CW0\fR, which will cause \fInnrpd\fR\|(8)
+processes spawned from \fIinnd\fR\|(8) to use the value of \fInicekids\fR, while
+\&\fInnrpd\fR\|(8) run as a daemon will use the system default priority. Note that
+for \fInnrpd\fR\|(8) processes spawned from \fIinnd\fR\|(8), this value will be ignored if
+set to a value lower than \fInicekids\fR.
+.IP "\fIpauseretrytime\fR" 4
+.IX Item "pauseretrytime"
+Wait for this many seconds before noticing inactive channels.
+Wait for this many seconds before innd processes articles when it's paused
+or the number of channel write failures exceeds \fIbadiocount\fR. The
+default value is \f(CW300\fR.
+.IP "\fIpeertimeout\fR" 4
+.IX Item "peertimeout"
+How long (in seconds) an \fIinnd\fR\|(8) incoming channel may be inactive before
+innd closes it. The default value is \f(CW3600\fR (an hour).
+.IP "\fIrlimitnofile\fR" 4
+.IX Item "rlimitnofile"
+The maximum number of file descriptors that \fIinnd\fR\|(8) or \fIinnfeed\fR\|(8) can have
+open at once. If \fIinnd\fR\|(8) or \fIinnfeed\fR\|(8) attempts to open more file
+descriptors than this value, it is possible the program may throttle or
+otherwise suffer reduced functionality. The number of open file
+descriptors is roughly the maximum number of incoming feeds and outgoing
+batches for \fIinnd\fR\|(8) and the number of outgoing streams for \fIinnfeed\fR\|(8). If
+this parameter is set to a negative value, the default limit of the
+operating system will be used; this will normally be adequate on systems
+other than Solaris. Nearly all operating systems have some hard maximum
+limit beyond which this value cannot be raised, usually either 128, 256,
+or 1024. The default value of this parameter is \f(CW\*(C`\-1\*(C'\fR. Setting it to
+\&\f(CW256\fR on Solaris systems is highly recommended.
+.Sh "Paths and File Names"
+.IX Subsection "Paths and File Names"
+.IP "\fIpatharchive\fR" 4
+.IX Item "patharchive"
+Where to store archived news. The default value is \fIpathspool\fR/archive.
+.IP "\fIpatharticles\fR" 4
+.IX Item "patharticles"
+The path to where the news articles are stored (for storage methods other
+than \s-1CNFS\s0). The default value is \fIpathspool\fR/articles.
+.IP "\fIpathbin\fR" 4
+.IX Item "pathbin"
+The path to the news binaries. The default value is \fIpathnews\fR/bin.
+.IP "\fIpathcontrol\fR" 4
+.IX Item "pathcontrol"
+The path to the files that handle control messages. The code for handling
+each separate type of control message is located here. Be very careful
+what you put in this directory with a name ending in \f(CW\*(C`.pl\*(C'\fR, as it can
+potentially be a severe security risk. The default value is
+\&\fIpathbin\fR/control.
+.IP "\fIpathdb\fR" 4
+.IX Item "pathdb"
+The path to the database files used and updated by the server (currently,
+\&\fIactive\fR, \fIactive.times\fR, \fIhistory\fR and its indices, and
+\&\fInewsgroups\fR). The default value is \fIpathnews\fR/db.
+.IP "\fIpathetc\fR" 4
+.IX Item "pathetc"
+The path to the news configuration files. The default value is
+\&\fIpathnews\fR/etc.
+.IP "\fIpathfilter\fR" 4
+.IX Item "pathfilter"
+The path to the Perl, Tcl, and Python filters. The default value is
+\&\fIpathbin\fR/filter.
+.IP "\fIpathhttp\fR" 4
+.IX Item "pathhttp"
+Where any \s-1HTML\s0 files (such as periodic status reports) are placed. If the
+news reports should be available in real-time on the web, the files in
+this directory should be served by a web server. The default value is
+the value of \fIpathlog\fR.
+.IP "\fIpathincoming\fR" 4
+.IX Item "pathincoming"
+Location where incoming batched news is stored. The default value is
+\&\fIpathspool\fR/incoming.
+.IP "\fIpathlog\fR" 4
+.IX Item "pathlog"
+Where the news log files are written. The default value is
+\&\fIpathnews\fR/log.
+.IP "\fIpathnews\fR" 4
+.IX Item "pathnews"
+The home directory of the news user and usually the root of the news
+hierarchy. There is no default; this parameter must be set in \fIinn.conf\fR
+or \s-1INN\s0 will refuse to start.
+.IP "\fIpathoutgoing\fR" 4
+.IX Item "pathoutgoing"
+Default location for outgoing feed files. The default value is
+\&\fIpathspool\fR/outgoing.
+.IP "\fIpathoverview\fR" 4
+.IX Item "pathoverview"
+The path to news overview files. The default value is
+\&\fIpathspool\fR/overview.
+.IP "\fIpathrun\fR" 4
+.IX Item "pathrun"
+The path to files required while the server is running and run-time state
+information. This includes lock files and the sockets for communicating
+with \fIinnd\fR\|(8). This directory and the control sockets in it should be
+protected from unprivileged users other than the news user. The default
+value is \fIpathnews\fR/run.
+.IP "\fIpathspool\fR" 4
+.IX Item "pathspool"
+The root of the news spool hierarchy. This used mostly to set the
+defaults for other parameters, and to determine the path to the backlog
+directory for \fIinnfeed\fR\|(8). The default value is \fIpathnews\fR/spool.
+.IP "\fIpathtmp\fR" 4
+.IX Item "pathtmp"
+Where \s-1INN\s0 puts temporary files. For security reasons, this is not the
+same as the system temporary files directory (\s-1INN\s0 creates a lot of
+temporary files with predictable names and does not go to particularly
+great lengths to protect against symlink attacks and the like; this
+is safe provided that normal users can't write into its temporary
+directory). The default value is set at configure time and defaults to
+\&\fIpathnews\fR/tmp.
+.SH "EXAMPLE"
+.IX Header "EXAMPLE"
+Here is a very minimalist example that only sets those parameters that are
+required.
+.PP
+.Vb 5
+\& mta: /usr/lib/sendmail \-oi \-oem %s
+\& ovmethod: tradindexed
+\& pathhost: news.example.com
+\& pathnews: /usr/local/news
+\& hismethod: hisv6
+.Ve
+.PP
+For a more comprehensive example, see the sample \fIinn.conf\fR distributed
+with \s-1INN\s0 and installed as a starting point; it contains all of the default
+values for reference.
+.SH "HISTORY"
+.IX Header "HISTORY"
+Written by Rich \f(CW$alz\fR <rsalz@uunet.uu.net> for InterNetNews and since
+modified, updated, and reorganized by innumerable other people.
+.PP
+$Id: inn.conf.5 7880 2008-06-16 20:37:13Z iulius $
+.SH "SEE ALSO"
+.IX Header "SEE ALSO"
+\&\fIinews\fR\|(1), \fIinnd\fR\|(8), \fIinnwatch\fR\|(8), \fInnrpd\fR\|(8), \fIrnews\fR\|(1).
+.PP
+Nearly every program in \s-1INN\s0 uses this file to one degree or another. The
+above are just the major and most frequently mentioned ones.
--- /dev/null
+.TH INNCHECK 8
+.SH NAME
+inncheck \- check inn configuration and database files.
+.SH SYNOPSIS
+.B inncheck
+[
+.B \-a
+]
+[
+.B \-v
+]
+[
+.B \-pedantic
+]
+[
+.B \-f
+]
+[
+.B \-perm
+]
+[
+.B \-noperm
+]
+[
+.B "file=value | file"
+]
+.SH DESCRIPTION
+.I Inncheck
+examines various configuration files and databases and verifies things
+about them. Things verified depend on the file being checked, but generally
+are things like permissions, ownership, syntax errors in config files, etc.
+.PP
+.I Inncheck
+does not make changes to any files \(em it just reports what it
+thinks may be wrong, and it is up to the operator to fix the problem.
+.PP
+The set of files checked may be restricted by using \fBfile\fP or
+\fBfile=value\fP arguments. For example, putting \fBincoming.conf\fP causes
+only the
+.I incoming.conf
+file to be checked. Using \fBincoming.conf=/tmp/incoming.conf\fP on the
+command line will cause
+.I inncheck
+to only verify the incoming.conf file, and it will perform the
+checks on the file
+/tmp/incoming.conf file instead of the default one.
+.PP
+Valid values for
+.I file
+are:
+.PP
+.RS
+.nf
+ active
+ control.ctl
+ expire.ctl
+ incoming.conf
+ inn.conf
+ moderators
+ newsfeeds
+ overview.fmt
+ nntpsend.ctl
+ passwd.nntp
+ readers.conf
+.fi
+.RE
+.SH OPTIONS
+.TP
+.B \-a
+If any ``\fBfile\fP'' value or ``\fBfile=value\fP'' pairs (see below) are
+given, then normally only the files they refer to are checked. Use
+the ``\fB\-a\fP'' flag to specify that
+.I all
+files should be checked regardless. In this case the form \fBfile=value\fP
+will be the more useful.
+.TP
+.B \-v
+Use the ``\fB\-v\fP'' option to get more verbose output.
+.TP
+.B \-pedantic
+Use the ``\fB\-pedantic\fP'' option to get reports on things that are not
+necessarily wrong, but may indicate a bad configuration \(em such as
+\fIinn.conf\fP missing a key.
+.TP
+.B \-f
+Use the ``\fB\-f\fP'' flag to have inncheck print the appropriate
+chown/chgrp/chmod command necessary to fix a problem that it reports. Any
+other output lines will be prefixed with a ``#'' character to make the
+output be valid input for a shell. Note that the ``\fB\-perm\fP'' flag
+must be used as well when using this flag.
+.TP
+.B \-perm
+Inncheck checks all files for permission problems.
+If the ``\fB\-perm\fP'' flag is used, then
+.I only
+the files specified by the \fBfile\fP or \fBfile=value\fP command line
+arguments will be checked for problems other than permission problems.
+.TP
+.B \-noperm
+To avoid doing any checking of file permissions or ownership, use
+the ``\fB-noperm\fP'' option.
+.SH EXAMPLES
+.PP
+To have
+.I inncheck
+check all files for syntax and permission problems simply:
+.PP
+.RS
+.nf
+inncheck
+.fi
+.RE
+.PP
+To have
+.I inncheck
+check all files for permission problems and to verify the syntax of the
+active and incoming.conf files do:
+.PP
+.RS
+.nf
+inncheck -perm active incoming.conf
+.fi
+.RE
+.PP
+To fix the permissions problems noted in the output of the above
+command, modify it as follow:
+.PP
+.RS
+.nf
+inncheck -f -perm | sh
+.fi
+.RE
+.PP
+To have
+.I inncheck
+check the test newsfeeds file in /var/tmp/newsfeeds.testing, do:
+.PP
+.RS
+.nf
+inncheck newsfeeds=/var/tmp/newsfeeds.testing
+.fi
+.RE
+.PP
+To have
+.I inncheck
+check all the files as it normally does, but to specify a different
+location for the newsfeeds file, so:
+.PP
+.RS
+.nf
+inncheck -a newsfeeds=/var/tmp/newsfeeds.testing
+.fi
+.RE
+.SH BUGS
+If the ``\fB-f\fP'' and ``\fB-perm\fP'' options are used together, along with
+``\fB\-a\fP'' or some ``\fBfile\fP'' or ``\fBfile=value\fP'' arguments that
+refer to a file with a syntax problem, then the output will no longer be
+valid input for a shell.
+.SH HISTORY
+Written by Brendan Kehoe <brendan@cygnus.com> and
+Rich Salz <rsalz@uunet.uu.net>
+.de R$
+This is revision \\$3, dated \\$4.
+..
+.R$ $Id: inncheck.8 5909 2002-12-03 05:17:18Z vinocur $
+.SH "SEE ALSO"
+active(5),
+expire.ctl(5),
+history(5),
+incoming.conf(5),
+inn.conf(5),
+newsfeeds(5)
--- /dev/null
+.\" Automatically generated by Pod::Man v1.37, Pod::Parser v1.32
+.\"
+.\" Standard preamble:
+.\" ========================================================================
+.de Sh \" Subsection heading
+.br
+.if t .Sp
+.ne 5
+.PP
+\fB\\$1\fR
+.PP
+..
+.de Sp \" Vertical space (when we can't use .PP)
+.if t .sp .5v
+.if n .sp
+..
+.de Vb \" Begin verbatim text
+.ft CW
+.nf
+.ne \\$1
+..
+.de Ve \" End verbatim text
+.ft R
+.fi
+..
+.\" Set up some character translations and predefined strings. \*(-- will
+.\" give an unbreakable dash, \*(PI will give pi, \*(L" will give a left
+.\" double quote, and \*(R" will give a right double quote. \*(C+ will
+.\" give a nicer C++. Capital omega is used to do unbreakable dashes and
+.\" therefore won't be available. \*(C` and \*(C' expand to `' in nroff,
+.\" nothing in troff, for use with C<>.
+.tr \(*W-
+.ds C+ C\v'-.1v'\h'-1p'\s-2+\h'-1p'+\s0\v'.1v'\h'-1p'
+.ie n \{\
+. ds -- \(*W-
+. ds PI pi
+. if (\n(.H=4u)&(1m=24u) .ds -- \(*W\h'-12u'\(*W\h'-12u'-\" diablo 10 pitch
+. if (\n(.H=4u)&(1m=20u) .ds -- \(*W\h'-12u'\(*W\h'-8u'-\" diablo 12 pitch
+. ds L" ""
+. ds R" ""
+. ds C` ""
+. ds C' ""
+'br\}
+.el\{\
+. ds -- \|\(em\|
+. ds PI \(*p
+. ds L" ``
+. ds R" ''
+'br\}
+.\"
+.\" If the F register is turned on, we'll generate index entries on stderr for
+.\" titles (.TH), headers (.SH), subsections (.Sh), items (.Ip), and index
+.\" entries marked with X<> in POD. Of course, you'll have to process the
+.\" output yourself in some meaningful fashion.
+.if \nF \{\
+. de IX
+. tm Index:\\$1\t\\n%\t"\\$2"
+..
+. nr % 0
+. rr F
+.\}
+.\"
+.\" For nroff, turn off justification. Always turn off hyphenation; it makes
+.\" way too many mistakes in technical documents.
+.hy 0
+.if n .na
+.\"
+.\" Accent mark definitions (@(#)ms.acc 1.5 88/02/08 SMI; from UCB 4.2).
+.\" Fear. Run. Save yourself. No user-serviceable parts.
+. \" fudge factors for nroff and troff
+.if n \{\
+. ds #H 0
+. ds #V .8m
+. ds #F .3m
+. ds #[ \f1
+. ds #] \fP
+.\}
+.if t \{\
+. ds #H ((1u-(\\\\n(.fu%2u))*.13m)
+. ds #V .6m
+. ds #F 0
+. ds #[ \&
+. ds #] \&
+.\}
+. \" simple accents for nroff and troff
+.if n \{\
+. ds ' \&
+. ds ` \&
+. ds ^ \&
+. ds , \&
+. ds ~ ~
+. ds /
+.\}
+.if t \{\
+. ds ' \\k:\h'-(\\n(.wu*8/10-\*(#H)'\'\h"|\\n:u"
+. ds ` \\k:\h'-(\\n(.wu*8/10-\*(#H)'\`\h'|\\n:u'
+. ds ^ \\k:\h'-(\\n(.wu*10/11-\*(#H)'^\h'|\\n:u'
+. ds , \\k:\h'-(\\n(.wu*8/10)',\h'|\\n:u'
+. ds ~ \\k:\h'-(\\n(.wu-\*(#H-.1m)'~\h'|\\n:u'
+. ds / \\k:\h'-(\\n(.wu*8/10-\*(#H)'\z\(sl\h'|\\n:u'
+.\}
+. \" troff and (daisy-wheel) nroff accents
+.ds : \\k:\h'-(\\n(.wu*8/10-\*(#H+.1m+\*(#F)'\v'-\*(#V'\z.\h'.2m+\*(#F'.\h'|\\n:u'\v'\*(#V'
+.ds 8 \h'\*(#H'\(*b\h'-\*(#H'
+.ds o \\k:\h'-(\\n(.wu+\w'\(de'u-\*(#H)/2u'\v'-.3n'\*(#[\z\(de\v'.3n'\h'|\\n:u'\*(#]
+.ds d- \h'\*(#H'\(pd\h'-\w'~'u'\v'-.25m'\f2\(hy\fP\v'.25m'\h'-\*(#H'
+.ds D- D\\k:\h'-\w'D'u'\v'-.11m'\z\(hy\v'.11m'\h'|\\n:u'
+.ds th \*(#[\v'.3m'\s+1I\s-1\v'-.3m'\h'-(\w'I'u*2/3)'\s-1o\s+1\*(#]
+.ds Th \*(#[\s+2I\s-2\h'-\w'I'u*3/5'\v'-.3m'o\v'.3m'\*(#]
+.ds ae a\h'-(\w'a'u*4/10)'e
+.ds Ae A\h'-(\w'A'u*4/10)'E
+. \" corrections for vroff
+.if v .ds ~ \\k:\h'-(\\n(.wu*9/10-\*(#H)'\s-2\u~\d\s+2\h'|\\n:u'
+.if v .ds ^ \\k:\h'-(\\n(.wu*10/11-\*(#H)'\v'-.4m'^\v'.4m'\h'|\\n:u'
+. \" for low resolution devices (crt and lpr)
+.if \n(.H>23 .if \n(.V>19 \
+\{\
+. ds : e
+. ds 8 ss
+. ds o a
+. ds d- d\h'-1'\(ga
+. ds D- D\h'-1'\(hy
+. ds th \o'bp'
+. ds Th \o'LP'
+. ds ae ae
+. ds Ae AE
+.\}
+.rm #[ #] #H #V #F C
+.\" ========================================================================
+.\"
+.IX Title "INNCONFVAL 1"
+.TH INNCONFVAL 1 "2008-04-06" "INN 2.4.5" "InterNetNews Documentation"
+.SH "NAME"
+innconfval \- Get configuration parameters from inn.conf
+.SH "SYNOPSIS"
+.IX Header "SYNOPSIS"
+\&\fBinnconfval\fR [\fB\-pstv\fR] [\fB\-i\fR \fIfile\fR] [\fIparameter\fR ...]
+.PP
+\&\fBinnconfval\fR \fB\-C\fR [\fB\-i\fR \fIfile\fR]
+.SH "DESCRIPTION"
+.IX Header "DESCRIPTION"
+\&\fBinnconfval\fR normally prints the values of the parameters specified on
+the command line. By default, it just prints the parameter values, but if
+\&\fB\-p\fR, \fB\-s\fR, or \fB\-t\fR are given, it instead prints the parameter and
+value in the form of a variable assignment in Perl, Bourne shell, or Tcl
+respectively. If no parameters are specifically requested, \fBinnconfval\fR
+prints out all parameter values (this isn't particularly useful unless one
+of \fB\-p\fR, \fB\-s\fR, or \fB\-t\fR were specified).
+.PP
+All parameters are taken from \fIinn.conf\fR except for \fIversion\fR, which is
+always the version string of \s-1INN\s0.
+.PP
+If given the \fB\-C\fR option, \fBinnconfval\fR instead checks \fIinn.conf\fR,
+reporting any problems found to standard error. \fBinnconfval\fR will exit
+with status 0 if no problems are found and with status 1 otherwise.
+.SH "OPTIONS"
+.IX Header "OPTIONS"
+.IP "\fB\-C\fR" 4
+.IX Item "-C"
+Check \fIinn.conf\fR rather than printing out the values of parameters.
+.IP "\fB\-i\fR \fIfile\fR" 4
+.IX Item "-i file"
+Use \fIfile\fR as the source configuration file rather than \fIinn.conf\fR.
+\&\fIfile\fR must be a valid \fIinn.conf\fR file and will be parsed the same as
+\&\fIinn.conf\fR would be.
+.IP "\fB\-p\fR" 4
+.IX Item "-p"
+Print out parameters as Perl assignment statements. The variable name
+will be the same as the \fIinn.conf\fR parameter, and string values will be
+enclosed in single quotes with appropriate escaping. Boolean values will
+be mapped to \f(CW\*(C`true\*(C'\fR or \f(CW\*(C`false\*(C'\fR, and string parameters that are set to
+\&\s-1NULL\s0 will be mapped to empty strings.
+.IP "\fB\-s\fR" 4
+.IX Item "-s"
+Print out parameters as Bourne shell assignment statements. The variable
+name will be the \fIinn.conf\fR parameter name in all capitals, and all
+variables will be exported. String values will be enclosed in single
+quotes with appropriate escaping, and boolean values will be mapped to
+\&\f(CW\*(C`true\*(C'\fR or \f(CW\*(C`false\*(C'\fR. String parameters that are set to \s-1NULL\s0 will be
+mapped to empty strings.
+.IP "\fB\-t\fR" 4
+.IX Item "-t"
+Print out parameters as Tcl assignment statements. The variable name will
+be the same as the \fIinn.conf\fR parameter name but with \f(CW\*(C`inn_\*(C'\fR prepended,
+and string variables will be escaped appropriately. Boolean values will
+be mapped to \f(CW\*(C`true\*(C'\fR or \f(CW\*(C`false\*(C'\fR and string parameters that are set to
+\&\s-1NULL\s0 will be mapped to empty strings.
+.IP "\fB\-v\fR" 4
+.IX Item "-v"
+Print \s-1INN\s0's version. This is equivalent to \f(CW\*(C`innconfval version\*(C'\fR.
+.SH "HISTORY"
+.IX Header "HISTORY"
+Written by Rich \f(CW$alz\fR <rsalz@uunet.uu.net> for InterNetNews.
+.PP
+$Id: innconfval.1 7880 2008-06-16 20:37:13Z iulius $
+.SH "SEE ALSO"
+.IX Header "SEE ALSO"
+\&\fIinn.conf\fR\|(5)
--- /dev/null
+.\" Automatically generated by Pod::Man v1.37, Pod::Parser v1.32
+.\"
+.\" Standard preamble:
+.\" ========================================================================
+.de Sh \" Subsection heading
+.br
+.if t .Sp
+.ne 5
+.PP
+\fB\\$1\fR
+.PP
+..
+.de Sp \" Vertical space (when we can't use .PP)
+.if t .sp .5v
+.if n .sp
+..
+.de Vb \" Begin verbatim text
+.ft CW
+.nf
+.ne \\$1
+..
+.de Ve \" End verbatim text
+.ft R
+.fi
+..
+.\" Set up some character translations and predefined strings. \*(-- will
+.\" give an unbreakable dash, \*(PI will give pi, \*(L" will give a left
+.\" double quote, and \*(R" will give a right double quote. \*(C+ will
+.\" give a nicer C++. Capital omega is used to do unbreakable dashes and
+.\" therefore won't be available. \*(C` and \*(C' expand to `' in nroff,
+.\" nothing in troff, for use with C<>.
+.tr \(*W-
+.ds C+ C\v'-.1v'\h'-1p'\s-2+\h'-1p'+\s0\v'.1v'\h'-1p'
+.ie n \{\
+. ds -- \(*W-
+. ds PI pi
+. if (\n(.H=4u)&(1m=24u) .ds -- \(*W\h'-12u'\(*W\h'-12u'-\" diablo 10 pitch
+. if (\n(.H=4u)&(1m=20u) .ds -- \(*W\h'-12u'\(*W\h'-8u'-\" diablo 12 pitch
+. ds L" ""
+. ds R" ""
+. ds C` ""
+. ds C' ""
+'br\}
+.el\{\
+. ds -- \|\(em\|
+. ds PI \(*p
+. ds L" ``
+. ds R" ''
+'br\}
+.\"
+.\" If the F register is turned on, we'll generate index entries on stderr for
+.\" titles (.TH), headers (.SH), subsections (.Sh), items (.Ip), and index
+.\" entries marked with X<> in POD. Of course, you'll have to process the
+.\" output yourself in some meaningful fashion.
+.if \nF \{\
+. de IX
+. tm Index:\\$1\t\\n%\t"\\$2"
+..
+. nr % 0
+. rr F
+.\}
+.\"
+.\" For nroff, turn off justification. Always turn off hyphenation; it makes
+.\" way too many mistakes in technical documents.
+.hy 0
+.if n .na
+.\"
+.\" Accent mark definitions (@(#)ms.acc 1.5 88/02/08 SMI; from UCB 4.2).
+.\" Fear. Run. Save yourself. No user-serviceable parts.
+. \" fudge factors for nroff and troff
+.if n \{\
+. ds #H 0
+. ds #V .8m
+. ds #F .3m
+. ds #[ \f1
+. ds #] \fP
+.\}
+.if t \{\
+. ds #H ((1u-(\\\\n(.fu%2u))*.13m)
+. ds #V .6m
+. ds #F 0
+. ds #[ \&
+. ds #] \&
+.\}
+. \" simple accents for nroff and troff
+.if n \{\
+. ds ' \&
+. ds ` \&
+. ds ^ \&
+. ds , \&
+. ds ~ ~
+. ds /
+.\}
+.if t \{\
+. ds ' \\k:\h'-(\\n(.wu*8/10-\*(#H)'\'\h"|\\n:u"
+. ds ` \\k:\h'-(\\n(.wu*8/10-\*(#H)'\`\h'|\\n:u'
+. ds ^ \\k:\h'-(\\n(.wu*10/11-\*(#H)'^\h'|\\n:u'
+. ds , \\k:\h'-(\\n(.wu*8/10)',\h'|\\n:u'
+. ds ~ \\k:\h'-(\\n(.wu-\*(#H-.1m)'~\h'|\\n:u'
+. ds / \\k:\h'-(\\n(.wu*8/10-\*(#H)'\z\(sl\h'|\\n:u'
+.\}
+. \" troff and (daisy-wheel) nroff accents
+.ds : \\k:\h'-(\\n(.wu*8/10-\*(#H+.1m+\*(#F)'\v'-\*(#V'\z.\h'.2m+\*(#F'.\h'|\\n:u'\v'\*(#V'
+.ds 8 \h'\*(#H'\(*b\h'-\*(#H'
+.ds o \\k:\h'-(\\n(.wu+\w'\(de'u-\*(#H)/2u'\v'-.3n'\*(#[\z\(de\v'.3n'\h'|\\n:u'\*(#]
+.ds d- \h'\*(#H'\(pd\h'-\w'~'u'\v'-.25m'\f2\(hy\fP\v'.25m'\h'-\*(#H'
+.ds D- D\\k:\h'-\w'D'u'\v'-.11m'\z\(hy\v'.11m'\h'|\\n:u'
+.ds th \*(#[\v'.3m'\s+1I\s-1\v'-.3m'\h'-(\w'I'u*2/3)'\s-1o\s+1\*(#]
+.ds Th \*(#[\s+2I\s-2\h'-\w'I'u*3/5'\v'-.3m'o\v'.3m'\*(#]
+.ds ae a\h'-(\w'a'u*4/10)'e
+.ds Ae A\h'-(\w'A'u*4/10)'E
+. \" corrections for vroff
+.if v .ds ~ \\k:\h'-(\\n(.wu*9/10-\*(#H)'\s-2\u~\d\s+2\h'|\\n:u'
+.if v .ds ^ \\k:\h'-(\\n(.wu*10/11-\*(#H)'\v'-.4m'^\v'.4m'\h'|\\n:u'
+. \" for low resolution devices (crt and lpr)
+.if \n(.H>23 .if \n(.V>19 \
+\{\
+. ds : e
+. ds 8 ss
+. ds o a
+. ds d- d\h'-1'\(ga
+. ds D- D\h'-1'\(hy
+. ds th \o'bp'
+. ds Th \o'LP'
+. ds ae ae
+. ds Ae AE
+.\}
+.rm #[ #] #H #V #F C
+.\" ========================================================================
+.\"
+.IX Title "INND 8"
+.TH INND 8 "2008-04-06" "INN 2.4.5" "InterNetNews Documentation"
+.SH "NAME"
+innd \- InterNetNews daemon
+.SH "SYNOPSIS"
+.IX Header "SYNOPSIS"
+\&\fBinnd\fR [\fB\-aCdfNrsu\fR] [\fB\-c\fR \fIdays\fR] [\fB\-H\fR \fIcount\fR] [\fB\-i\fR \fIcount\fR]
+[\fB\-I\fR \fIaddress\fR] [\fB\-l\fR \fIsize\fR] [\fB\-m\fR \fImode\fR] [\fB\-n\fR \fIflag\fR]
+[\fB\-o\fR \fIcount\fR] [\fB\-p\fR \fIfd\fR] [\fB\-P\fR \fIport\fR] [\fB\-t\fR \fItimeout\fR]
+[\fB\-T\fR \fIcount\fR] [\fB\-X\fR \fIseconds\fR]
+.SH "DESCRIPTION"
+.IX Header "DESCRIPTION"
+\&\fBinnd\fR, the InterNetNews daemon, handles all incoming \s-1NNTP\s0 feeds,
+coordinates the storage, retransmission, and overview generation for all
+accepted articles, and manages the \fIactive\fR\|(5) and \fIhistory\fR\|(5) databases. It
+handles incoming connections on the \s-1NNTP\s0 port, and also creates and
+listens to a local Unix-domain stream socket in order to receive articles
+from local processes such as \fInnrpd\fR\|(8) and \fIrnews\fR\|(1).
+.PP
+As the master daemon, \fBinnd\fR should generally be started at boot and be
+always running. It listens to a Unix-domain datagram socket for commands
+to control its activites, commands that can be sent using \fIctlinnd\fR\|(8). The
+current status of \fBinnd\fR can be obtained by running \f(CW\*(C`ctlinnd mode\*(C'\fR, or
+for more detailed output, \fIinnstat\fR\|(8).
+.PP
+\&\fBinnd\fR can be in one of three operating modes: running, paused, or
+throttled. Running is the normal mode; when the server is throttled, it
+closes connections and rejects new ones. Paused is like a temporary
+throttle, suspending \fBinnd\fR's activities but not causing the server to
+shut down existing connections. The mode is normally changed via
+\&\fIctlinnd\fR\|(8), either by various automated processes (such as nightly article
+expiration) or manually by the news administrator, but \fBinnd\fR will also
+throttle itself if it encounters \s-1ENOSPC\s0 errors in writing data or an
+excessive number of I/O errors (among other problems).
+.PP
+\&\fBinnd\fR normally takes care of spawning \fInnrpd\fR\|(8) to handle connections
+from news reading clients, but it can be run on a separate port from
+\&\fInnrpd\fR\|(8) so that feed connections and news reading connections are handled
+separately (this can often be faster). Normally, \fBinnd\fR listens on port
+119, the assigned port for \s-1NNTP\s0; if it is desireable to run \fBinnd\fR and
+\&\fInnrpd\fR\|(8) on separate ports, it's recommended that \fInnrpd\fR\|(8) be given port
+119 (since many news reading clients connect only to that port) and that
+port 433 be used for \fBinnd\fR.
+.PP
+The primary configuration files that control \fBinnd\fR's activities are
+\&\fIincoming.conf\fR, which specifies what remote sites \fBinnd\fR will accept
+connections from, \fInewsfeeds\fR, which specifies what is to be done with
+incoming articles besides storing them, and \fIinn.conf\fR, which sets a wide
+variety of configuration parameters. Some parameters in \fIinn.conf\fR\|(5) can
+also be set with command-line flags; for these, the command-line flags
+take precedence if used.
+.PP
+\&\fBinnd\fR should normally not run directly. It must run as the news user or
+all sorts of file ownership problems may result, and normally the port it
+listens on (119 or 433) is privileged and must be opened by root.
+Instead, \fBinnd\fR should normally be started via \fIinndstart\fR\|(8), a small
+setuid-root program that opens the appropriate port, cleans up the
+environment, changes to the news user, and then runs \fBinnd\fR, passing
+along any command-line arguments.
+.PP
+To use IPv6, \fBinnd\fR must be started by \fBinndstart\fR.
+.SH "OPTIONS"
+.IX Header "OPTIONS"
+For the options below that override \fIinn.conf\fR settings, see \fIinn.conf\fR\|(5)
+for the default values if neither the \fIinn.conf\fR setting nor the
+command-line option is given.
+.IP "\fB\-a\fR" 4
+.IX Item "-a"
+By default, if a host connects to \fBinnd\fR but is not listed in
+\&\fIincoming.conf\fR, the connection is handed off to \fBnnrpd\fR (or rejected if
+\&\fInoreader\fR is set in \fIinn.conf\fR). If \fB\-a\fR is given, \fIincoming.conf\fR
+is ignored and any host can connect and transfer articles. This flag
+should never be used with an accessible server connected to Usenet; it
+would open the server up for all sorts of abuse.
+.IP "\fB\-c\fR \fIdays\fR" 4
+.IX Item "-c days"
+\&\fBinnd\fR normally rejects any article that is older (in days) than the
+value of \fIartcutoff\fR in \fIinn.conf\fR. This option, if given, overrides
+the value of that setting. If \fIdays\fR is 0, this check is suppressed and
+\&\fBinnd\fR will accept articles regardless of how old they are.
+.IP "\fB\-C\fR" 4
+.IX Item "-C"
+This flag tells \fBinnd\fR to accept and propagate but not actually process
+cancel or supersede messages. This is intended for sites concerned about
+abuse of cancels, or that wish to use another cancel mechanism with
+stronger authentication.
+.IP "\fB\-d\fR, \fB\-f\fR" 4
+.IX Item "-d, -f"
+\&\fBinnd\fR normally puts itself into the background, points its standard
+output and error to log files, and disassociates itself from the
+terminal. Using \fB\-d\fR prevents all of this, resulting in log messages
+being written to standard output; this is generally useful only for
+debugging. Using \fB\-f\fR prevents the backgrounding and disassociation but
+still redirects output; it may be useful if you want to monitor \fBinnd\fR
+with a program that would be confused by forks.
+.IP "\fB\-H\fR \fIcount\fR, \fB\-T\fR \fIcount\fR, \fB\-X\fR \fIseconds\fR" 4
+.IX Item "-H count, -T count, -X seconds"
+These flags control the number of connections per minute that are allowed.
+This code is meant to protect your server from newsreader clients that
+make too many connections per minute (and therefore these flags are
+probably only useful when \fBinnd\fR is spawning \fBnnrpd\fR). You probably
+should not use these options unless you're having problems. The table
+used for this check is fixed at 128 entries and is used as a ring; the
+size was chosen to make calculating the index easy and to be fairly sure
+that it won't run out of space. In practice, it is unlikely that even
+half the table will be used at any given moment.
+.Sp
+The \fB\-H\fR flag limits the number of times a host is allowed to connect to
+the server per the time interval given by \fB\-X\fR. The default is \f(CW2\fR.
+.Sp
+The \fB\-T\fR flag limits the total number of incoming connections per the
+time interval given by \fB\-X\fR. The maximum value is \f(CW128\fR, and the
+default is \f(CW60\fR.
+.IP "\fB\-i\fR \fIcount\fR" 4
+.IX Item "-i count"
+\&\fBinnd\fR normally allows a maximum number of concurrent \s-1NNTP\s0 connections
+given by the value of \fImaxconnections\fR in \fIinn.conf\fR. This option, if
+given, overrides the value of that setting. If \fIcount\fR is \f(CW0\fR, this
+check is suppressed.
+.IP "\fB\-I\fR \fIaddress\fR" 4
+.IX Item "-I address"
+Normally if \fBinnd\fR itself binds to a port, it lets the operating system
+pick the source \s-1IP\s0 address (unless \fIbindaddress\fR is set in \fIinn.conf\fR).
+If this option is given, it specifies the \s-1IP\s0 address that \s-1INN\s0 should bind
+as. This is only relevant for servers with multiple local \s-1IP\s0 addresses.
+The \s-1IP\s0 address must be in dotted quad (\f(CW\*(C`nnn.nnn.nnn.nnn\*(C'\fR) format.
+.Sp
+This option is rarely useful since \fBinnd\fR should not be binding to a
+port itself. Instead, use \fIinndstart\fR\|(8) and its analgous \fB\-I\fR option.
+.IP "\fB\-l\fR \fIsize\fR" 4
+.IX Item "-l size"
+\&\fBinnd\fR normally rejects any article larger than the value of
+\&\fImaxartsize\fR in \fIinn.conf\fR. This option, if given, overrides the value
+of that setting and specifies a maximum article size of \fIsize\fR. If
+\&\fIsize\fR is \f(CW0\fR, this check is suppressed.
+.IP "\fB\-m\fR \fImode\fR" 4
+.IX Item "-m mode"
+Normally \fBinnd\fR starts in the \f(CW\*(C`running\*(C'\fR mode. If this option is given,
+it specifies what mode \fBinnd\fR should start in. \fImode\fR should begin with
+one of \f(CW\*(C`g\*(C'\fR, \f(CW\*(C`p\*(C'\fR, or \f(CW\*(C`t\*(C'\fR, and the starting mode will be set to
+\&\f(CW\*(C`running\*(C'\fR, \f(CW\*(C`paused\*(C'\fR, or \f(CW\*(C`throttled\*(C'\fR, respectively, based on that
+initial letter. (\f(CW\*(C`g\*(C'\fR is short for \f(CW\*(C`go\*(C'\fR.)
+.IP "\fB\-N\fR" 4
+.IX Item "-N"
+If this option is given, any filters (Perl, Tcl, or Python) are disabled
+before \fBinnd\fR starts (normally, filters default to being enabled). The
+filters can be enabled after \fBinnd\fR has started with \fIctlinnd\fR\|(8).
+.IP "\fB\-n\fR \fIflag\fR" 4
+.IX Item "-n flag"
+Whether \fBinnd\fR allows (and hands off to \fBnnrpd\fR) reader connections
+while paused or throttled is normally determined by the value of
+\&\fIreaderswhenstopped\fR in \fIinn.conf\fR). This option, if given, overrides
+that value. If \fIflag\fR is \f(CW\*(C`n\*(C'\fR, \fBinnd\fR will not allow readers if it is
+paused or throttled. If \fIflag\fR is \f(CW\*(C`y\*(C'\fR, readers will be allowed
+regardless of \fBinnd\fR's operating mode.
+.IP "\fB\-o\fR \fIcount\fR" 4
+.IX Item "-o count"
+This flag limits the number of file descriptors that are available for
+outgoing file feeds. The default is the number of available file
+descriptors minus some reserved for internal use (which could potentially
+starve \fBinnd\fR of descriptors to use for accepting new connections). If
+\&\fBinnd\fR has more file feeds than \fIcount\fR, some of them will be buffered
+and only written out periodically.
+.Sp
+Normally you never need to use this option, since the number of outgoing
+feeds is fixed, being the number of file feeds configured in \fInewsfeeds\fR,
+and is generally small (particularly given that \fIinnfeed\fR\|(8) is now used for
+most outgoing feeds at large sites).
+.IP "\fB\-p\fR \fIfd\fR" 4
+.IX Item "-p fd"
+If this flag is given, \fBinnd\fR expects the file descriptor given by \fIfd\fR
+to already be open and bound to the appropriate local port and to be
+suitable for listening to for incoming connections. This is how
+\&\fBinndstart\fR tells \fBinnd\fR which open file descriptor is the network
+connection. If this flag is not given, \fBinnd\fR will attempt to open its
+network socket itself. \fBinndstart\fR always passes this flag to \fBinnd\fR.
+.IP "\fB\-P\fR \fIport\fR" 4
+.IX Item "-P port"
+The port \fBinnd\fR should listen on is normally given by the value of
+\&\fIport\fR in \fIinn.conf\fR. This option, if given, overrides that value and
+specifies the port that \fBinnd\fR should bind to. This option is rarely
+useful since \fBinnd\fR normally does not bind itself; instead the analgous
+\&\fB\-P\fR option to \fIinndstart\fR\|(8) should be used. Since \fBinnd\fR should never
+be run as root, \fIport\fR has to be a non-privileged port (one larger than
+1024).
+.IP "\fB\-r\fR" 4
+.IX Item "-r"
+Instructs \fBinnd\fR to renumber the \fIactive\fR file after starting, just as
+if a \f(CW\*(C`ctlinnd renumber\*(C'\fR command were sent.
+.IP "\fB\-s\fR" 4
+.IX Item "-s"
+Just check the syntax of the \fInewsfeeds\fR file and exit. \fBinnd\fR will
+exit with a non-zero status if any errors are found; the actual errors
+will be reported via \fIsyslog\fR\|(3).
+.IP "\fB\-t\fR \fIseconds\fR" 4
+.IX Item "-t seconds"
+Normally, \fBinnd\fR will flush any changes to history and the active file
+after 300 seconds of inactivity. This option changes that timeout to
+\&\fIseconds\fR.
+.IP "\fB\-u\fR" 4
+.IX Item "-u"
+The news log (the trace information for every article accepted by \fBinnd\fR)
+is normally buffered. This option changes the log to be unbuffered.
+.SH "CONTROL MESSAGES"
+.IX Header "CONTROL MESSAGES"
+Arriving articles that have a Control: header are called \*(L"control
+messages\*(R". Except for cancel messages, these messages are handled by
+\&\fIcontrolchan\fR\|(8) via a feed set up in \fInewsfeeds\fR.
+.PP
+(Cancel messages update the history database, so they must be handled
+internally; the cost of syncing, locking, then unlocking would be too high
+given the number of cancel messages that are received. Note that if an
+article is cancelled before it is received by the news server, it will
+be rejected when it arrives since the history database has been updated;
+it is useful for rejecting spam before it arrives.)
+.PP
+The distribution of control messages is different than that of standard
+articles. Control messages are normally filed into the pseudo-newsgroup
+named \f(CW\*(C`control\*(C'\fR regardless of which newsgroup they were actually posted
+to. If, however, a \f(CW\*(C`control.\*(C'\fR\fIcommand\fR newsgroup exists that matches
+the control command, the control message will be filed into that group
+instead. For example, a newgroup control message will be filed in
+\&\f(CW\*(C`control.newgroup\*(C'\fR if that group exists; otherwise, it will be filed in
+\&\f(CW\*(C`control\*(C'\fR.
+.PP
+If you want to specifically feed all control messages to a given site
+regardless of whether the control messages would affect the newsgroups
+you're feeding that site, you can put the appropriate control newsgroup in
+the subscription list. For example, to feed all cancel messages to a
+given remote site (normally a bad idea), add \f(CW\*(C`control.cancel\*(C'\fR to its
+subscription list. Normally it's best to exclude the control newsgroups
+from feeds to keep from sending your peers more control messages than they
+care about. That's why the \fInewsfeeds\fR pattern \f(CW\*(C`!control,!control.*\*(C'\fR
+is as often as not specified (adding this pattern do not prevent control
+messages which affect the newsgroups fed to a site from being sent to it).
+.PP
+checkgroups, newgroup and rmgroup control messages receive additional special
+treatment. If one of these control messages is approved and posted to the
+newsgroup being created or removed (or to the admin group to which the
+checkgroups is posted), the message will be sent to all sites
+whose subscription patterns would cause them to receive articles posted to
+that group. For example, if a newgroup control message for a nonexistent
+newsgroup \f(CW\*(C`news.admin.meow\*(C'\fR is received, it will be sent to any site
+whose subscription pattern would cause it to receive \f(CW\*(C`news.admin.meow\*(C'\fR if
+that newsgroup existed (such as a pattern of \f(CW\*(C`news.admin.*\*(C'\fR). For this
+reason, it is correct to post newgroup messages to the newsgroup that the
+control message would create. It is \fInot\fR generally correct to crosspost
+newgroup messages to some \*(L"well\-propagated\*(R" newsgroup; not only will this
+not actually improve their propagation to sites that want such control
+messages, but it will also cause sites that do not want those control
+messages to receive them. Therefore, assuming that a newgroup control
+message is sent to the group \f(CW\*(C`news.admin.meow\*(C'\fR (specified in the
+Newsgroups: header) in order to create the group \f(CW\*(C`news.admin.meow\*(C'\fR,
+the sites with the following subscription patterns will receive it:
+.PP
+.Vb 4
+\& *,@news.*
+\& news.*
+\& news.*,!control,!control.*
+\& control,control.*
+.Ve
+.PP
+but the sites with the following subscription patterns will not receive it:
+.PP
+.Vb 2
+\& *,@news.*,!control,!control.*
+\& comp.*,@news.*
+.Ve
+.PP
+If a control message is posted to a group whose name ends with the four
+characters \f(CW\*(C`.ctl\*(C'\fR, this suffix is stripped off and the control message is
+propagated as if it were posted to the base group. For example, a cancel
+message posted to \f(CW\*(C`news.admin.ctl\*(C'\fR will be sent to all sites that
+subscribe to \f(CW\*(C`control.cancel\*(C'\fR (or \f(CW\*(C`control\*(C'\fR if that newsgroup doesn't
+exist) or \f(CW\*(C`news.admin\*(C'\fR. This behavior is present for historical
+compatibility reasons and should be considered obsolete; support for the
+\&\f(CW\*(C`.ctl\*(C'\fR suffix may be removed in a future version of \s-1INN\s0.
+.PP
+Finally, articles posted to newsgroups beginning with \f(CW\*(C`to.\*(C'\fR are treated
+specially. Provided that either that newsgroup exists in the \fIactive\fR file
+or \fImergetogroups\fR is set in \fIinn.conf\fR, the remainder of the newsgroup
+is taken to be a site name, as configured in \fInewsfeeds\fR, and the article
+is sent to that site. If \fImergetogroups\fR is set, the article will be
+filed in the group named \f(CW\*(C`to\*(C'\fR (which must exist in the \fIactive\fR file). For
+example, with \fImergetogroups\fR set, an article posted to \f(CW\*(C`to.uunet\*(C'\fR will
+be filed in \f(CW\*(C`to\*(C'\fR and sent to the site \f(CW\*(C`uunet\*(C'\fR.
+.SH "PROTOCOL DIFFERENCES"
+.IX Header "PROTOCOL DIFFERENCES"
+\&\fBinnd\fR implements the \s-1NNTP\s0 commands defined in \s-1RFC\s0 977, with the
+following differences:
+.IP "1." 4
+The \s-1LIST\s0 command may be followed by an optional \s-1ACTIVE\s0, \s-1ACTIVE\s0.TIMES, or
+\&\s-1NEWSGROUPS\s0. There is only basic support for \s-1LIST\s0 in \fBinnd\fR since feeding
+peers normally don't need it; see \fInnrpd\fR\|(8) for full support.
+.IP "2." 4
+The \s-1AUTHINFO\s0 \s-1USER\s0 and \s-1AUTHINFO\s0 \s-1PASS\s0 commands are implemented, although the
+authentication is currently limited to matching a password for a given
+peer specified in \fIincoming.conf\fR. These are based on the reference Unix
+implementation.
+.IP "3." 4
+A new command, \s-1MODE\s0 \s-1READER\s0, is implemented. This command will cause the
+server to pass the connection to \fBnnrpd\fR.
+.IP "4." 4
+The streaming extension (\s-1MODE\s0 \s-1STREAM\s0, \s-1CHECK\s0, and \s-1TAKETHIS\s0) is fully
+supported.
+.IP "5." 4
+A batch transfer command, \s-1XBATCH\s0 \fIbyte-count\fR, is provided. This command
+will read \fIbyte-count\fR bytes and store them for later processing by
+\&\fIrnews\fR\|(1) (which must be run separately, probably from cron). See
+\&\fIinnxbatch\fR\|(8) and \fIbackends/sendxbatches\fR for more details on this
+extension.
+.IP "6." 4
+\&\fBinnd\fR implements a limited subset of the protocol useful for
+transferring news. The only other commands implemented are \s-1HEAD\s0, \s-1HELP\s0,
+\&\s-1IHAVE\s0, \s-1STAT\s0, and \s-1QUIT\s0. The remaining commands are mostly only useful for
+readers and are implemented by \fInnrpd\fR\|(8).
+.SH "HEADER MODIFICATIONS"
+.IX Header "HEADER MODIFICATIONS"
+\&\fBinnd\fR modifies as few article headers as possible, although it could be
+better in this area.
+.PP
+Empty headers and headers that consist of nothing but whitespace are
+dropped.
+.PP
+The local site's name (as set with the \fIpathhost\fR parameter in
+\&\fIinn.conf\fR) and an exclamation point are prepended to the Path: header,
+provided the first site name in the Path: header is different from the
+local one. In addition, \fIpathalias\fR and \fIpathcluster\fR may be similarly
+respectively prepended and appended to the Path: header; see \fIinn.conf\fR\|(5)
+for the details.
+.PP
+The Xref: header is removed and a new one created.
+.PP
+A Lines: header will be added if the article was missing one.
+.PP
+\&\fBinnd\fR does not rewrite incorrect headers. For example, it will not
+replace an incorrect Lines header, though it may reject such an article
+depending on the value of \fIlinecountfuzz\fR in \fIinn.conf\fR.
+.SH "CANCEL FEEDS"
+.IX Header "CANCEL FEEDS"
+In order to efficiently apply a large number of local cancels (such as
+from processing NoCeMs or from some other external source), \s-1INN\s0 supports a
+special feed mode available only to connections to the local Unix domain
+socket (not to connections to any network sockets).
+.PP
+To enter this mode, connect to the Unix domain socket (\fIpathrun\fR/nntpin)
+and send the command \s-1MODE\s0 \s-1CANCEL\s0. The response will have code \f(CW284\fR.
+Every subsequent line sent on that connection should consist of a single
+message \s-1ID\s0. An attempt will be made to cancel that message \s-1ID\s0, and the
+server will reply \f(CW289\fR for success or \f(CW484\fR for failure. (Failure can
+occur, for example, if the server is paused or throttled, or the
+Message-ID is corrupt. Failure does \fInot\fR occur if the article to be
+cancelled does not exist.)
+.SH "LOGGING"
+.IX Header "LOGGING"
+\&\fBinnd\fR reports all incoming articles in its log file (\fIpathlog\fR/news).
+This is a text file with a variable number of space-separated fields in
+one of the following formats:
+.PP
+.Vb 5
+\& mon dd hh:mm:ss.mmm + feed <message\-id> site ...
+\& mon dd hh:mm:ss.mmm j feed <message\-id> site ...
+\& mon dd hh:mm:ss.mmm c feed <message\-id> Cancelling <message\-id>
+\& mon dd hh:mm:ss.mmm \- feed <message\-id> reason
+\& mon dd hh:mm:ss.mmm ? feed <message\-id> reason
+.Ve
+.PP
+There may also be hostname and/or size fields after the message \s-1ID\s0
+depending on the settings of \fInntplinklog\fR and \fIlogartsize\fR in
+\&\fIinn.conf\fR.
+.PP
+The first three fields are the date and time to millisecond resolution.
+The fifth field is the site that sent the article (based on the Path
+header) and the sixth field is the article's message \s-1ID\s0; they will be a
+question mark if the information is not available.
+.PP
+The fourth field indicates whether the article was accepted or not. If it
+is a plus sign, then the article was accepted. If it is the letter \f(CW\*(C`j\*(C'\fR
+then the article was accepted, but all of the newsgroups to which the
+article was posted were set to mode \f(CW\*(C`j\*(C'\fR in the active file (or not listed
+in the active file and \fIwanttrash\fR was set in \fIinn.conf\fR) so the article
+was filed into the \f(CW\*(C`junk\*(C'\fR newsgroup. In both of these cases, the article
+has been accepted and the \f(CW\*(C`site ...\*(C'\fR field contains the space-separated
+list of sites to which the article is being sent.
+.PP
+If the fourth field is the letter \f(CW\*(C`c\*(C'\fR, then a cancel message was accepted
+before the original article arrived, and a history entry for the cancelled
+message was created so that \fBinnd\fR will reject that message if it arrives
+later.
+.PP
+If the fourth field is a minus sign, then the article was rejected. The
+reasons for rejection generated by \fBinnd\fR include:
+.PP
+.Vb 20
+\& "%s" header too long
+\& "%s" wants to cancel <%s> by "%s"
+\& Article exceeds local limit of %s bytes
+\& Article posted in the future \-\- "%s"
+\& Bad "%s" header
+\& Can't write history
+\& Duplicate
+\& Duplicate "%s" header
+\& EOF in headers
+\& Linecount %s != %s +\- %s
+\& Missing %s header
+\& No body
+\& No colon\-space in "%s" header
+\& No space
+\& Space before colon in "%s" header
+\& Too old \-\- "%s"
+\& Unapproved for "%s"
+\& Unwanted newsgroup "%s"
+\& Unwanted distribution "%s"
+\& Whitespace in "Newsgroups" header \-\- "%s"
+.Ve
+.PP
+where \f(CW%s\fR, above, is replaced by more specific information. (The Perl,
+Python, andr Tcl filters, if used, may reject articles with other
+reasons.)
+.PP
+If the fourth field is the letter \f(CW\*(C`?\*(C'\fR, the article contains strange
+strings, such as \s-1CR\s0 without \s-1LF\s0 or \s-1LF\s0 without \s-1CR\s0. (These characters should
+never occur in isolation, only together as \s-1CRLF\s0 to indicate the end of a
+line.) This log message is just informational, to give an idea of how
+widespread such articles are; \fBinnd\fR does not reject such articles.
+.PP
+Note that when \fIwanttrash\fR is set to true in \fIinn.conf\fR and an article
+is received that isn't posted to any valid newsgroups, it will be accepted
+and logged with two lines, a \f(CW\*(C`j\*(C'\fR line and a minus sign line.
+.PP
+\&\fBinnd\fR also makes extensive reports through \fIsyslog\fR\|(3). The first word of
+the log message will be the name of the site if the entry is site-specific
+(such as a \*(L"connected\*(R" message). The first word will be \f(CW\*(C`SERVER\*(C'\fR if the
+message relates to the server itself, such as when a read error occurs.
+.PP
+If the second word is the four letters \f(CW\*(C`cant\*(C'\fR, then an error is being
+reported. (The absence of an apostrophe is intentional; it makes it
+easier to grep from the command line and easier to find error messages in
+FAQs using a search engine.) In this case, the next two words generally
+name the system call or library routine that failed and the object upon
+which the action was being performed. The rest of the line may contain
+other information.
+.PP
+In other cases, the second word attempts to summarize what change has been
+made, while the rest of the line gives more specific information. The
+word \f(CW\*(C`internal\*(C'\fR generally indicates an internal logic error.
+.SH "SIGNALS"
+.IX Header "SIGNALS"
+\&\fBinnd\fR will catch \s-1SIGTERM\s0 and \s-1SIGHUP\s0 and shut down. If \fB\-d\fR is used,
+\&\s-1SIGINT\s0 will also be caught and will result in an orderly shutdown.
+.PP
+\&\fBinnd\fR will catch the \s-1SIGUSR1\s0 signal and recreate the control channel
+used by \fIctlinnd\fR\|(8).
+.SH "BUGS"
+.IX Header "BUGS"
+\&\fBinnd\fR normally attempts to strip \s-1IP\s0 options from incoming connections,
+since it uses IP-based authentication and source routing can confuse that.
+However, this doesn't work on all systems, and it doesn't work at all in
+the presence of IPv6 support (and is disabled in that case). Hence, if
+using \fBinnd\fR with IPv6 support, make sure that your kernel or router
+disables source routing.
+.SH "HISTORY"
+.IX Header "HISTORY"
+Written by Rich \f(CW$alz\fR <rsalz@uunet.uu.net> for InterNetNews.
+.PP
+$Id: innd.8 7880 2008-06-16 20:37:13Z iulius $
+.SH "SEE ALSO"
+.IX Header "SEE ALSO"
+\&\fIactive\fR\|(5), \fIctlinnd\fR\|(8), \fIdbz\fR\|(3), \fIhistory\fR\|(5), \fIincoming.conf\fR\|(5), \fIinn.conf\fR\|(5),
+\&\fInewsfeeds\fR\|(5), \fInnrpd\fR\|(8), \fIrnews\fR\|(1), \fIsyslog\fR\|(3).
--- /dev/null
+.\" $Revision: 1586 $
+.TH INNDCOMM 3
+.SH NAME
+inndcomm \- INND communication part of InterNetNews library
+.SH SYNOPSIS
+.nf
+.ta \w' unsigned long 'u
+.B
+#include "inndcomm.h"
+
+.B "int"
+.B "ICCopen()"
+
+.B "int"
+.B "ICCclose()"
+
+.B "void"
+.B "ICCsettimeout(i)"
+.B " int i;"
+
+.B "int"
+.B "ICCcommand(cmd, argv, replyp)"
+.B " char cmd;"
+.B " char *argv[];"
+.B " char **replyp;"
+
+.B "int"
+.B "ICCcancel(mesgid)"
+.B " char *mesgid;"
+
+.B "int"
+.B "ICCreserve(why)"
+.B " char *why;"
+
+.B "int"
+.B "ICCpause(why)"
+.B " char *why;"
+
+.B "int"
+.B "ICCgo(why)"
+.B " char *why;"
+
+.B "extern char *ICCfailure;"
+.fi
+.SH DESCRIPTION
+The routines described in this manual page are part of the InterNetNews
+library,
+.IR libinn (3).
+They are used to send commands to a running
+.IR innd (8)
+daemon on the local host.
+The letters ``ICC'' stand for
+.IR I nnd
+.IR C ontrol
+.IR C ommand.
+.PP
+.I ICCopen
+creates a
+Unix-domain datagram socket and binds it to the server's control socket, if
+.I <HAVE_UNIX_DOMAIN_SOCKETS in include/config.h>
+is defined. Otherwise it creates
+a named pipe for communicating with the server.
+It returns \-1 on failure or zero on success.
+This routine must be called before any other routine.
+.PP
+.I ICCclose
+closes any descriptors that have been created by
+.IR ICCopen .
+It returns \-1 on failure or zero on success.
+.PP
+.I ICCsettimeout
+can be called before any of the following routines to determine how long
+the library should wait before giving up on getting the server's reply.
+This is done by setting and catching a SIGALRM
+.IR signal (2).
+If the timeout is less then zero then no reply will be waited for.
+The SC_SHUTDOWN, SC_XABORT, and SC_XEXEC commands do not get a reply either.
+The default, which can be obtained by setting the timeout to zero, is to
+wait until the server replies.
+.PP
+.I ICCcommand
+sends the command
+.I cmd
+with parameters
+.I argv
+to the server.
+It returns \-1 on error.
+If the server replies, and
+.I replyp
+is not NULL, it will be filled in with an allocated buffer that contains
+the full text of the server's reply.
+This buffer is a string in the form of ``<digits><space><text>''
+where ``digits'' is the text value of the recommended exit code;
+zero indicates success.
+Replies longer then 4000 bytes will be truncated.
+The possible values of
+.I cmd
+are defined in the ``inndcomm.h'' header file.
+The parameters for each command are described in
+.IR ctlinnd (8).
+This routine returns \-1 on communication failure, or the exit status
+sent by the server which will never be negative.
+.PP
+.I ICCcancel
+sends a ``cancel'' message to the server.
+.I Mesgid
+is the Message-ID of the article that should be canceled.
+The return value is the same as for
+.IR ICCcommand .
+.PP
+.IR ICCpause ,
+.IR ICCreserve ,
+and
+.I ICCgo
+send a ``pause,'' ``reserve,'' or ``go'' command to the server, respectively.
+If
+.I ICCreserve
+is used, then the
+.I why
+value used in the
+.I ICCpause
+invocation must match; the value used in the
+.I ICCgo
+invocation must always match that the one used in the
+.I ICCpause
+invocation.
+The return value for all three routines is the same as for
+.IR ICCcommand .
+.PP
+If any routine described above fails, the
+.I ICCfailure
+variable will identify the system call that failed.
+.SH HISTORY
+Written by Rich $alz <rsalz@uunet.uu.net> for InterNetNews.
+.de R$
+This is revision \\$3, dated \\$4.
+..
+.R$ $Id: inndcomm.3 1586 1998-12-09 15:53:55Z kondou $
+.SH "SEE ALSO"
+ctlinnd(8),
+innd(8),
+libinn(3).
--- /dev/null
+.\" Automatically generated by Pod::Man v1.37, Pod::Parser v1.32
+.\"
+.\" Standard preamble:
+.\" ========================================================================
+.de Sh \" Subsection heading
+.br
+.if t .Sp
+.ne 5
+.PP
+\fB\\$1\fR
+.PP
+..
+.de Sp \" Vertical space (when we can't use .PP)
+.if t .sp .5v
+.if n .sp
+..
+.de Vb \" Begin verbatim text
+.ft CW
+.nf
+.ne \\$1
+..
+.de Ve \" End verbatim text
+.ft R
+.fi
+..
+.\" Set up some character translations and predefined strings. \*(-- will
+.\" give an unbreakable dash, \*(PI will give pi, \*(L" will give a left
+.\" double quote, and \*(R" will give a right double quote. \*(C+ will
+.\" give a nicer C++. Capital omega is used to do unbreakable dashes and
+.\" therefore won't be available. \*(C` and \*(C' expand to `' in nroff,
+.\" nothing in troff, for use with C<>.
+.tr \(*W-
+.ds C+ C\v'-.1v'\h'-1p'\s-2+\h'-1p'+\s0\v'.1v'\h'-1p'
+.ie n \{\
+. ds -- \(*W-
+. ds PI pi
+. if (\n(.H=4u)&(1m=24u) .ds -- \(*W\h'-12u'\(*W\h'-12u'-\" diablo 10 pitch
+. if (\n(.H=4u)&(1m=20u) .ds -- \(*W\h'-12u'\(*W\h'-8u'-\" diablo 12 pitch
+. ds L" ""
+. ds R" ""
+. ds C` ""
+. ds C' ""
+'br\}
+.el\{\
+. ds -- \|\(em\|
+. ds PI \(*p
+. ds L" ``
+. ds R" ''
+'br\}
+.\"
+.\" If the F register is turned on, we'll generate index entries on stderr for
+.\" titles (.TH), headers (.SH), subsections (.Sh), items (.Ip), and index
+.\" entries marked with X<> in POD. Of course, you'll have to process the
+.\" output yourself in some meaningful fashion.
+.if \nF \{\
+. de IX
+. tm Index:\\$1\t\\n%\t"\\$2"
+..
+. nr % 0
+. rr F
+.\}
+.\"
+.\" For nroff, turn off justification. Always turn off hyphenation; it makes
+.\" way too many mistakes in technical documents.
+.hy 0
+.if n .na
+.\"
+.\" Accent mark definitions (@(#)ms.acc 1.5 88/02/08 SMI; from UCB 4.2).
+.\" Fear. Run. Save yourself. No user-serviceable parts.
+. \" fudge factors for nroff and troff
+.if n \{\
+. ds #H 0
+. ds #V .8m
+. ds #F .3m
+. ds #[ \f1
+. ds #] \fP
+.\}
+.if t \{\
+. ds #H ((1u-(\\\\n(.fu%2u))*.13m)
+. ds #V .6m
+. ds #F 0
+. ds #[ \&
+. ds #] \&
+.\}
+. \" simple accents for nroff and troff
+.if n \{\
+. ds ' \&
+. ds ` \&
+. ds ^ \&
+. ds , \&
+. ds ~ ~
+. ds /
+.\}
+.if t \{\
+. ds ' \\k:\h'-(\\n(.wu*8/10-\*(#H)'\'\h"|\\n:u"
+. ds ` \\k:\h'-(\\n(.wu*8/10-\*(#H)'\`\h'|\\n:u'
+. ds ^ \\k:\h'-(\\n(.wu*10/11-\*(#H)'^\h'|\\n:u'
+. ds , \\k:\h'-(\\n(.wu*8/10)',\h'|\\n:u'
+. ds ~ \\k:\h'-(\\n(.wu-\*(#H-.1m)'~\h'|\\n:u'
+. ds / \\k:\h'-(\\n(.wu*8/10-\*(#H)'\z\(sl\h'|\\n:u'
+.\}
+. \" troff and (daisy-wheel) nroff accents
+.ds : \\k:\h'-(\\n(.wu*8/10-\*(#H+.1m+\*(#F)'\v'-\*(#V'\z.\h'.2m+\*(#F'.\h'|\\n:u'\v'\*(#V'
+.ds 8 \h'\*(#H'\(*b\h'-\*(#H'
+.ds o \\k:\h'-(\\n(.wu+\w'\(de'u-\*(#H)/2u'\v'-.3n'\*(#[\z\(de\v'.3n'\h'|\\n:u'\*(#]
+.ds d- \h'\*(#H'\(pd\h'-\w'~'u'\v'-.25m'\f2\(hy\fP\v'.25m'\h'-\*(#H'
+.ds D- D\\k:\h'-\w'D'u'\v'-.11m'\z\(hy\v'.11m'\h'|\\n:u'
+.ds th \*(#[\v'.3m'\s+1I\s-1\v'-.3m'\h'-(\w'I'u*2/3)'\s-1o\s+1\*(#]
+.ds Th \*(#[\s+2I\s-2\h'-\w'I'u*3/5'\v'-.3m'o\v'.3m'\*(#]
+.ds ae a\h'-(\w'a'u*4/10)'e
+.ds Ae A\h'-(\w'A'u*4/10)'E
+. \" corrections for vroff
+.if v .ds ~ \\k:\h'-(\\n(.wu*9/10-\*(#H)'\s-2\u~\d\s+2\h'|\\n:u'
+.if v .ds ^ \\k:\h'-(\\n(.wu*10/11-\*(#H)'\v'-.4m'^\v'.4m'\h'|\\n:u'
+. \" for low resolution devices (crt and lpr)
+.if \n(.H>23 .if \n(.V>19 \
+\{\
+. ds : e
+. ds 8 ss
+. ds o a
+. ds d- d\h'-1'\(ga
+. ds D- D\h'-1'\(hy
+. ds th \o'bp'
+. ds Th \o'LP'
+. ds ae ae
+. ds Ae AE
+.\}
+.rm #[ #] #H #V #F C
+.\" ========================================================================
+.\"
+.IX Title "INNDF 8"
+.TH INNDF 8 "2008-04-06" "INN 2.4.5" "InterNetNews Documentation"
+.SH "NAME"
+inndf \- Report free disk, inodes, and overview information
+.SH "SYNOPSIS"
+.IX Header "SYNOPSIS"
+\&\fBinndf\fR [\fB\-Fhi\fR] [\fB\-f\fR \fIfilename\fR] \fIdirectory\fR [\fIdirectory\fR ...]
+.PP
+\&\fBinndf\fR \fB\-n\fR
+.PP
+\&\fBinndf\fR \fB\-o\fR
+.SH "DESCRIPTION"
+.IX Header "DESCRIPTION"
+\&\fBinndf\fR was originally a replacement for \f(CW\*(C`df | awk\*(C'\fR in \fIinnwatch.ctl\fR\|(5)
+and \fIinnstat\fR\|(8), and now also reports various other usage information about
+\&\s-1INN\s0's storage that \fIdf\fR\|(1) doesn't understand. \fBinndf\fR doesn't sync, forks
+less, and is generally less complicated than \fIdf\fR\|(1).
+.PP
+Its default behavior is to report free kilobytes (not disk blocks), or
+free inodes if \fB\-i\fR is used, in the file systems holding the directories
+given on the command line. (A kilobyte in this case is 1024 bytes.) If
+only one directory is given, the output will be a simple number; if more
+than one directory is given, the output will be formatted for human
+readability.
+.PP
+If \fIenableoverview\fR is set to true in \fIinn.conf\fR, \fBinndf\fR can also be
+used to get information about the overview database. With the \fB\-n\fR
+option, it reports a count of the total number of overview records stored.
+With \fB\-o\fR, it reports the percentage of space used in the overview
+database (for those overview methods where this is meaningful data).
+.SH "OPTIONS"
+.IX Header "OPTIONS"
+.IP "\fB\-f\fR \fIfilename\fR" 4
+.IX Item "-f filename"
+\&\fIfilename\fR should contain a list of directories to use in addition to
+those given by the arguments, one per line. Blank lines and anything
+after \f(CW\*(C`#\*(C'\fR on any line are ignored.
+.IP "\fB\-F\fR" 4
+.IX Item "-F"
+Like \fB\-f\fR execpt that the filename is \fIpathetc\fR/filesystems and it is
+not an error if this file doesn't exist. (This option is used primarily
+by such things as \fIinnstat\fR\|(8), so that the news administrator can add
+additional file systems to check to \fIpathetc\fR/filesystems without having
+to modify the script.)
+.IP "\fB\-h\fR" 4
+.IX Item "-h"
+Print a usage message and exit.
+.IP "\fB\-i\fR" 4
+.IX Item "-i"
+Report the number of free inodes rather than the amount of free disk
+space.
+.IP "\fB\-n\fR" 4
+.IX Item "-n"
+Report the total number of records in the overview database. Note that
+crossposted articles will have one overview record for each newsgroup
+they're posted to.
+.IP "\fB\-o\fR" 4
+.IX Item "-o"
+Report the percentage usage of the overview database space. This is only
+meaningful for overview methods that pre-allocate a certain amount of
+space rather than grow to accomodate more records. Currently, this flag
+is only useful for the buffindexed overview method.
+.SH "EXAMPLES"
+.IX Header "EXAMPLES"
+Print the free kilobytes in /news/spool as a simple number:
+.PP
+.Vb 1
+\& inndf /news/spool
+.Ve
+.PP
+Report the free inodes in /usr/local/news and /news/spool in a format
+designed for human readability:
+.PP
+.Vb 1
+\& inndf \-i /usr/local/news /news/spool
+.Ve
+.PP
+The same, but also add in all file systems in \fIpathetc\fR/filesystems:
+.PP
+.Vb 1
+\& inndf \-i \-F /usr/local/news /news/spool
+.Ve
+.PP
+Print out the number of overview records and the percentage space used by
+a buffindexed overview database:
+.PP
+.Vb 1
+\& inndf \-no
+.Ve
+.SH "HISTORY"
+.IX Header "HISTORY"
+\&\fBinndf\fR was written by Ian Dickinson <idickins@fore.com>. This manual
+page was written by Swa Frantzen <Swa.Frantzen@belgium.eu.net>. Thanks
+also to the following folks for ports, patches, and comments:
+.PP
+.Vb 7
+\& Mahesh Ramachandran <rr@eel.ufl.edu>
+\& Chuck Swiger <chuck@its.com>
+\& Sang\-yong Suh <sysuh@kigam.re.kr>
+\& Brad Dickey <bdickey@haverford.edu>
+\& Taso N. Devetzis <devetzis@snet.net>
+\& Wei\-Yeh Lee <weiyeh@columbia.edu>
+\& Jeff Garzik <jeff.garzik@spinne.com>
+.Ve
+.PP
+and to all the other folks I met and worked with during my 10 years as a
+newsadmin.
+.PP
+Katsuhiro Kondou added the \fB\-n\fR and \fB\-o\fR options. Russ Allbery added
+reporting of percentage free disk space. Support for \fB\-f\fR and \fB\-F\fR was
+added by Fabien Tassin <fta@sofaraway.org>.
+.SH "SEE ALSO"
+.IX Header "SEE ALSO"
+\&\fIdf\fR\|(1), \fIinnwatch.ctl\fR\|(5), \fIinnstat\fR\|(8).
--- /dev/null
+.\" Automatically generated by Pod::Man v1.37, Pod::Parser v1.32
+.\"
+.\" Standard preamble:
+.\" ========================================================================
+.de Sh \" Subsection heading
+.br
+.if t .Sp
+.ne 5
+.PP
+\fB\\$1\fR
+.PP
+..
+.de Sp \" Vertical space (when we can't use .PP)
+.if t .sp .5v
+.if n .sp
+..
+.de Vb \" Begin verbatim text
+.ft CW
+.nf
+.ne \\$1
+..
+.de Ve \" End verbatim text
+.ft R
+.fi
+..
+.\" Set up some character translations and predefined strings. \*(-- will
+.\" give an unbreakable dash, \*(PI will give pi, \*(L" will give a left
+.\" double quote, and \*(R" will give a right double quote. \*(C+ will
+.\" give a nicer C++. Capital omega is used to do unbreakable dashes and
+.\" therefore won't be available. \*(C` and \*(C' expand to `' in nroff,
+.\" nothing in troff, for use with C<>.
+.tr \(*W-
+.ds C+ C\v'-.1v'\h'-1p'\s-2+\h'-1p'+\s0\v'.1v'\h'-1p'
+.ie n \{\
+. ds -- \(*W-
+. ds PI pi
+. if (\n(.H=4u)&(1m=24u) .ds -- \(*W\h'-12u'\(*W\h'-12u'-\" diablo 10 pitch
+. if (\n(.H=4u)&(1m=20u) .ds -- \(*W\h'-12u'\(*W\h'-8u'-\" diablo 12 pitch
+. ds L" ""
+. ds R" ""
+. ds C` ""
+. ds C' ""
+'br\}
+.el\{\
+. ds -- \|\(em\|
+. ds PI \(*p
+. ds L" ``
+. ds R" ''
+'br\}
+.\"
+.\" If the F register is turned on, we'll generate index entries on stderr for
+.\" titles (.TH), headers (.SH), subsections (.Sh), items (.Ip), and index
+.\" entries marked with X<> in POD. Of course, you'll have to process the
+.\" output yourself in some meaningful fashion.
+.if \nF \{\
+. de IX
+. tm Index:\\$1\t\\n%\t"\\$2"
+..
+. nr % 0
+. rr F
+.\}
+.\"
+.\" For nroff, turn off justification. Always turn off hyphenation; it makes
+.\" way too many mistakes in technical documents.
+.hy 0
+.if n .na
+.\"
+.\" Accent mark definitions (@(#)ms.acc 1.5 88/02/08 SMI; from UCB 4.2).
+.\" Fear. Run. Save yourself. No user-serviceable parts.
+. \" fudge factors for nroff and troff
+.if n \{\
+. ds #H 0
+. ds #V .8m
+. ds #F .3m
+. ds #[ \f1
+. ds #] \fP
+.\}
+.if t \{\
+. ds #H ((1u-(\\\\n(.fu%2u))*.13m)
+. ds #V .6m
+. ds #F 0
+. ds #[ \&
+. ds #] \&
+.\}
+. \" simple accents for nroff and troff
+.if n \{\
+. ds ' \&
+. ds ` \&
+. ds ^ \&
+. ds , \&
+. ds ~ ~
+. ds /
+.\}
+.if t \{\
+. ds ' \\k:\h'-(\\n(.wu*8/10-\*(#H)'\'\h"|\\n:u"
+. ds ` \\k:\h'-(\\n(.wu*8/10-\*(#H)'\`\h'|\\n:u'
+. ds ^ \\k:\h'-(\\n(.wu*10/11-\*(#H)'^\h'|\\n:u'
+. ds , \\k:\h'-(\\n(.wu*8/10)',\h'|\\n:u'
+. ds ~ \\k:\h'-(\\n(.wu-\*(#H-.1m)'~\h'|\\n:u'
+. ds / \\k:\h'-(\\n(.wu*8/10-\*(#H)'\z\(sl\h'|\\n:u'
+.\}
+. \" troff and (daisy-wheel) nroff accents
+.ds : \\k:\h'-(\\n(.wu*8/10-\*(#H+.1m+\*(#F)'\v'-\*(#V'\z.\h'.2m+\*(#F'.\h'|\\n:u'\v'\*(#V'
+.ds 8 \h'\*(#H'\(*b\h'-\*(#H'
+.ds o \\k:\h'-(\\n(.wu+\w'\(de'u-\*(#H)/2u'\v'-.3n'\*(#[\z\(de\v'.3n'\h'|\\n:u'\*(#]
+.ds d- \h'\*(#H'\(pd\h'-\w'~'u'\v'-.25m'\f2\(hy\fP\v'.25m'\h'-\*(#H'
+.ds D- D\\k:\h'-\w'D'u'\v'-.11m'\z\(hy\v'.11m'\h'|\\n:u'
+.ds th \*(#[\v'.3m'\s+1I\s-1\v'-.3m'\h'-(\w'I'u*2/3)'\s-1o\s+1\*(#]
+.ds Th \*(#[\s+2I\s-2\h'-\w'I'u*3/5'\v'-.3m'o\v'.3m'\*(#]
+.ds ae a\h'-(\w'a'u*4/10)'e
+.ds Ae A\h'-(\w'A'u*4/10)'E
+. \" corrections for vroff
+.if v .ds ~ \\k:\h'-(\\n(.wu*9/10-\*(#H)'\s-2\u~\d\s+2\h'|\\n:u'
+.if v .ds ^ \\k:\h'-(\\n(.wu*10/11-\*(#H)'\v'-.4m'^\v'.4m'\h'|\\n:u'
+. \" for low resolution devices (crt and lpr)
+.if \n(.H>23 .if \n(.V>19 \
+\{\
+. ds : e
+. ds 8 ss
+. ds o a
+. ds d- d\h'-1'\(ga
+. ds D- D\h'-1'\(hy
+. ds th \o'bp'
+. ds Th \o'LP'
+. ds ae ae
+. ds Ae AE
+.\}
+.rm #[ #] #H #V #F C
+.\" ========================================================================
+.\"
+.IX Title "INNDSTART 8"
+.TH INNDSTART 8 "2008-04-06" "INN 2.4.5" "InterNetNews Documentation"
+.SH "NAME"
+inndstart \- Start innd
+.SH "SYNOPSIS"
+.IX Header "SYNOPSIS"
+\&\fBinndstart\fR [\fB\-P\fR \fIport\fR] [\fB\-I\fR \fIaddress\fR] [\fIinnd-options\fR]
+.SH "DESCRIPTION"
+.IX Header "DESCRIPTION"
+The purpose of \fBinndstart\fR is to raise system file descriptor limits,
+open the privileged news transfer port, and then start \fIinnd\fR\|(8), passing it
+the open file descriptor for the news port. \fBinndstart\fR is used since
+only privileged programs can perform those two operations and since
+\&\fBinnd\fR should not run with elevated privileges. It is installed setuid
+root and drops privileges to the news user (as set at configure time)
+before running \fBinnd\fR.
+.PP
+Normally there is no need to run \fBinndstart\fR directly. Instead, run
+\&\fIrc.news\fR\|(8) as the news user, and it will handle running \fBinndstart\fR
+appropriately for you.
+.PP
+Since \fBinndstart\fR is setuid root, it is extremely restrictive about who
+can run it and what it is willing to do. See \*(L"\s-1SECURITY\s0\*(R" for the full
+details.
+.PP
+\&\fBinndstart\fR can only be run by the news user; if run by any other user,
+it will abort. It will also only bind to ports 119, 433, or a port number
+given at configure time with \fB\-\-with\-innd\-port\fR among those ports below
+1024, although it can bind to any port above 1024. This is to prevent
+various security exploits possible by binding to arbitrary privileged
+ports.
+.PP
+Before running \fBinnd\fR, \fBinndstart\fR cleans out the environment and sets
+only those environment variables listed in \*(L"\s-1ENVIRONMENT\s0\*(R".
+.SH "OPTIONS"
+.IX Header "OPTIONS"
+.IP "\fB\-P\fR \fIport\fR" 4
+.IX Item "-P port"
+Bind to \fIport\fR instead of whatever is specified by \fIport\fR in
+\&\fIinn.conf\fR. Note that this is subject to the constraints mentioned
+above.
+.IP "\fB\-I\fR \fIaddress\fR" 4
+.IX Item "-I address"
+Bind as \fIaddress\fR instead of whatever is specified by \fIbindaddress\fR in
+\&\fIinn.conf\fR. The default behavior is to bind to \s-1INADDR_ANY\s0, and that's
+what's desired almost all the time. This option, and the \fIinn.conf\fR
+parameter, may be useful if the machine has multiple interface cards and
+\&\fBinnd\fR should only be listening on a particular one.
+.PP
+All other options given on the command line are passed verbatim to
+\&\fBinnd\fR. In addition, \fBinndstart\fR will give the \fB\-p\fR option to \fBinnd\fR,
+specifying the file descriptor of the open network socket.
+.SH "SECURITY"
+.IX Header "SECURITY"
+\&\fBinndstart\fR is setuid root, and therefore an expected point of attack.
+It has therefore been carefully written with security in mind. In a
+normal \s-1INN\s0 installation, it is installed setuid root and executable only
+by users in the news group.
+.PP
+Ideally, everything about \fBinndstart\fR's operations would be hard-coded so
+that it could not be modified. Fighting against this desire, however, is
+the ideal that as much of \s-1INN\s0's operation as possible should be
+configurable at run-time using \fIinn.conf\fR, and the news system should be
+able to an alternate inn.conf by setting \s-1INNCONF\s0 to the path to that file
+before starting any programs. The configuration data therefore can't be
+trusted.
+.PP
+The security model used is:
+.IP "\(bu" 2
+\&\fBinndstart\fR can only be executed by the news user and news group, as
+determined at configure time and compiled into \fBinndstart\fR as constants.
+Similarly, \fBinndstart\fR will always \fIsetuid()\fR and \fIsetgid()\fR to those users
+before running \fBinnd\fR. This is to prevent a user other than news but in
+the news group from using \fBinndstart\fR to leverage that access into access
+to the news account.
+.IP "\(bu" 2
+As mentioned above, \fBinndstart\fR will only bind to a very limited subset
+of ports below 1024. There are various attacks that can be performed
+using random low-numbered ports, including exploits of the \fIrsh\fR\|(1) family
+of commands on some systems.
+.IP "\(bu" 2
+\&\fBinndstart\fR does as little as possible as root, dropping privileges
+before performing any operations that do not require elevated privileges.
+.PP
+This program therefore gives the news user the ability to revoke system
+file descriptor limits and bind to the news port, and nothing else.
+.SH "DIAGNOSTICS"
+.IX Header "DIAGNOSTICS"
+\&\fBinndstart\fR may log the following messages to syslog and print them to
+stderr.
+.ie n .IP "can't bind: %s" 4
+.el .IP "can't bind: \f(CW%s\fR" 4
+.IX Item "can't bind: %s"
+(Fatal) Unable to bind to the designated port. This usually means that
+something else is already running on the news port. Check with
+\&\fInetstat\fR\|(8) and make sure that \fIinetd\fR\|(8) doesn't think it's running a
+service on the same port you're trying to run \fBinnd\fR on.
+.ie n .IP "can't bind to restricted port %d" 4
+.el .IP "can't bind to restricted port \f(CW%d\fR" 4
+.IX Item "can't bind to restricted port %d"
+(Fatal) \fBinndstart\fR was told to bind to a low numbered port (under 1024)
+other than 119, 433, or a port number given at configure time. This is
+not allowed for security reasons. If you're running \fBinnd\fR on a port
+other than 119 or 433, you need to give the \-\-with\-innd\-port flag to
+\&\f(CW\*(C`configure\*(C'\fR when you compile \s-1INN\s0.
+.ie n .IP "can't exec %s:\fR \f(CW%s" 4
+.el .IP "can't exec \f(CW%s:\fR \f(CW%s\fR" 4
+.IX Item "can't exec %s: %s"
+(Fatal) \fBinndstart\fR was unable to execute \fBinnd\fR. Make sure that
+\&\fIpathbin\fR is set correctly in inn.conf and that \fBinnd\fR is located in
+that directory and is executable by the news user.
+.IP "can't getgrnam(%s)" 4
+.IX Item "can't getgrnam(%s)"
+(Fatal) Unable to determine the \s-1GID\s0 for the compiled-in news group.
+Perhaps the news group is not listed in \fI/etc/group\fR.
+.IP "can't getpwnam(%s)" 4
+.IX Item "can't getpwnam(%s)"
+(Fatal) Unable to determine the \s-1UID\s0 for the compiled-in news user.
+Perhaps the news user is not listed in \fI/etc/passwd\fR.
+.ie n .IP "can't open socket: %s" 4
+.el .IP "can't open socket: \f(CW%s\fR" 4
+.IX Item "can't open socket: %s"
+(Fatal) Something went wrong in creating the network socket. Chances are
+your system is out of resources of some kind.
+.ie n .IP "can't set file descriptor limit to %d:\fR \f(CW%s" 4
+.el .IP "can't set file descriptor limit to \f(CW%d:\fR \f(CW%s\fR" 4
+.IX Item "can't set file descriptor limit to %d: %s"
+(Warning) Unable to set the system file descriptor limit to the specified
+value; the limit was left unchanged. Perhaps that value is too high for
+your system. Try changing \fIrlimitnofile\fR in \fIinn.conf\fR to a smaller
+value.
+.ie n .IP "can't set \s-1SO_REUSEADDR:\s0 %s" 4
+.el .IP "can't set \s-1SO_REUSEADDR:\s0 \f(CW%s\fR" 4
+.IX Item "can't set SO_REUSEADDR: %s"
+(Warning) \fBinndstart\fR attempts to set \s-1SO_REUSEADDR\s0 using \fIsetsockopt\fR\|(2) so
+that if \fBinnd\fR exits, it can be restarted again immediately without
+waiting for the port to time out. For some reason, this failed, and that
+option was not set on the port.
+.ie n .IP "can't seteuid to %d:\fR \f(CW%s" 4
+.el .IP "can't seteuid to \f(CW%d:\fR \f(CW%s\fR" 4
+.IX Item "can't seteuid to %d: %s"
+(Fatal) Unable to change the effective \s-1UID\s0. If \fBinndstart\fR has the
+correct permissions (setuid root) and seteuid to root (\s-1UID\s0 0) is failing,
+this may mean that your system has \fIseteuid\fR\|(2) but doesn't have support for
+\&\s-1POSIX\s0 saved UIDs. If this is the case, please report this to the \s-1INN\s0
+maintainers.
+.ie n .IP "can't setgid to %d:\fR \f(CW%s" 4
+.el .IP "can't setgid to \f(CW%d:\fR \f(CW%s\fR" 4
+.IX Item "can't setgid to %d: %s"
+(Fatal) Dropping privileges to the news group failed for some reason.
+.ie n .IP "can't setgroups (is inndstart setuid root?): %s" 4
+.el .IP "can't setgroups (is inndstart setuid root?): \f(CW%s\fR" 4
+.IX Item "can't setgroups (is inndstart setuid root?): %s"
+(Warning) Dropping all supplemental groups except the news group failed
+for some reason, and the process group membership was left unchanged.
+This almost always indicates that \fBinndstart\fR isn't setuid root as it has
+to be to do what it does. Make sure that \fBinndstart\fR is setuid root,
+owned by group news, and mode 4710.
+.ie n .IP "can't setuid to %d:\fR \f(CW%s" 4
+.el .IP "can't setuid to \f(CW%d:\fR \f(CW%s\fR" 4
+.IX Item "can't setuid to %d: %s"
+(Fatal) Dropping privileges to the news user failed for some reason.
+.ie n .IP "invalid address %s" 4
+.el .IP "invalid address \f(CW%s\fR" 4
+.IX Item "invalid address %s"
+(Fatal) \fB\-I\fR was specified on the command line, but the argument wasn't a
+valid address. Addresses must be given as numeric \s-1IP\s0 addresses.
+.IP "invalid bindaddress in inn.conf (%s)" 4
+.IX Item "invalid bindaddress in inn.conf (%s)"
+(Fatal) The \fIbindaddress\fR specified in \fIinn.conf\fR could not be converted
+to an \s-1IP\s0 address. See \fIinn.conf\fR\|(5) for more information about valid
+values.
+.ie n .IP "invalid port %s (must be a number)" 4
+.el .IP "invalid port \f(CW%s\fR (must be a number)" 4
+.IX Item "invalid port %s (must be a number)"
+(Fatal) \fB\-P\fR was specified on the command line, but the argument wasn't a
+valid port. Ports must be port numbers; service names are not allowed.
+.IP "missing address after \-I" 4
+.IX Item "missing address after -I"
+(Fatal) \fB\-I\fR was given on the command line, but no address was given
+after the option.
+.IP "missing port after \-P" 4
+.IX Item "missing port after -P"
+(Fatal) \fB\-P\fR was given on the command line, but no port was given after
+the option.
+.ie n .IP "must be run by user %s\fR (%d), not \f(CW%d" 4
+.el .IP "must be run by user \f(CW%s\fR (%d), not \f(CW%d\fR" 4
+.IX Item "must be run by user %s (%d), not %d"
+(Fatal) Someone other than the news user attempted to run \fBinndstart\fR.
+\&\fBinndstart\fR may only be run by the news user for security reasons.
+.SH "EXAMPLES"
+.IX Header "EXAMPLES"
+Normally, \fBinndstart\fR is never run directly. However, a simple way to
+just restart \fBinnd\fR (if it is not running) without running any other
+auxilliary programs or performing any of the other checks done by
+\&\fIrc.news\fR\|(8) is to just run:
+.PP
+.Vb 1
+\& inndstart
+.Ve
+.PP
+as the news user.
+.PP
+To start \fBinnd\fR on port 433, passing it the \f(CW\*(C`\-c21\*(C'\fR option, use:
+.PP
+.Vb 1
+\& inndstart \-P433 \-c21
+.Ve
+.SH "ENVIRONMENT"
+.IX Header "ENVIRONMENT"
+One environment variable affects the operation of \fBinndstart\fR itself:
+.IP "\s-1INNCONF\s0" 8
+.IX Item "INNCONF"
+The full path to the \fIinn.conf\fR\|(5) file to read, rather than the default.
+This can be used to run multiple copies of \s-1INN\s0 on the same machine with
+different settings.
+.PP
+When executing \fBinnd\fR, \fBinndstart\fR cleans out the entire environmnent
+and sets only the following variables:
+.IP "\s-1BIND_INADDR\s0" 8
+.IX Item "BIND_INADDR"
+Passed verbatim from \fBinndstart\fR's environment. This is used by various
+programs to override the \fIbindaddress\fR parameter in \fIinn.conf\fR and
+therefore must be in \fBinnd\fR's environment for programs like \fIinnfeed\fR\|(8).
+.IP "\s-1HOME\s0" 8
+.IX Item "HOME"
+Set to \fIpathnews\fR from \fIinn.conf\fR.
+.IP "\s-1LOGNAME\s0" 8
+.IX Item "LOGNAME"
+Set to the news master, as determined at configure time.
+.IP "\s-1PATH\s0" 8
+.IX Item "PATH"
+Set to \fIpathbin\fR from \fIinn.conf\fR, \fIpathetc\fR from \fIinn.conf\fR, and then
+\&\fI/bin\fR, \fI/usr/bin\fR, and \fI/usr/ucb\fR in that order.
+.IP "\s-1SHELL\s0" 8
+.IX Item "SHELL"
+Set to the path to the system Bourne shell as determined by configure
+(probably \fI/bin/sh\fR).
+.IP "\s-1TMPDIR\s0" 8
+.IX Item "TMPDIR"
+Set to \fIpathtmp\fR from inn.conf.
+.IP "\s-1TZ\s0" 8
+.IX Item "TZ"
+Passed verbatim from \fBinndstart\fR's environment.
+.IP "\s-1USER\s0" 8
+.IX Item "USER"
+Set to the news master, as determined at configure time.
+.SH "FILES"
+.IX Header "FILES"
+.IP "inn.conf" 4
+.IX Item "inn.conf"
+Read for \fIpathnews\fR, \fIpathbin\fR, \fIpathtmp\fR, \fIrlimitnofile\fR,
+\&\fIbindaddress\fR, and \fIport\fR.
+.IP "\fIpathbin\fR/innd" 4
+.IX Item "pathbin/innd"
+The binary that is executed as \fBinnd\fR and passed the open network socket.
+.SH "HISTORY"
+.IX Header "HISTORY"
+Written by Russ Allbery <rra@stanford.edu> for InterNetNews.
+.PP
+$Id: inndstart.8 7880 2008-06-16 20:37:13Z iulius $
+.SH "SEE ALSO"
+.IX Header "SEE ALSO"
+\&\fIinn.conf\fR\|(5), \fIinnd\fR\|(8)
--- /dev/null
+.\" -*- nroff -*-
+.\"
+.\" Author: James A. Brister <brister@vix.com> -- berkeley-unix --
+.\" Start Date: Sat, 20 Jan 1996 15:50:56 +1100
+.\" Project: INN -- innfeed
+.\" File: innfeed.1
+.\" RCSId: $Id: innfeed.1 7798 2008-04-26 08:47:01Z iulius $
+.\" Description: Man page for innfeed(1)
+.\"
+.TH INNFEED 1
+.\"
+.\"
+.\"
+.\"
+.\"
+.SH NAME
+innfeed \- multi-host, multi-connection, streaming NNTP feeder.
+.SH SYNOPSIS
+.B innfeed
+[
+.B \-a spool-dir
+]
+[
+.BI \-b " directory"
+]
+[
+.B \-C
+]
+[
+.BI \-c " filename"
+]
+[
+.BI \-d " num"
+]
+[
+.BI \-e " bytes"
+]
+[
+.B \-h
+]
+[
+.BI \-l " filename"
+]
+[
+.B \-m
+]
+[
+.B \-M
+]
+[
+.B \-o bytes
+]
+[
+.B \-p file
+]
+[
+.B \-S file
+]
+[
+.B \-x
+]
+[
+.B \-y
+]
+[
+.B \-z
+]
+[
+.B \-v
+]
+[ file ]
+.\"
+.\"
+.\"
+.\"
+.\"
+.SH DESCRIPTION
+.PP
+.I Innfeed
+implements the NNTP protocol for transferring news between computers. It
+handles the standard IHAVE protocol as well as the CHECK/TAKETHIS
+streaming extension. Innfeed can feed any number of remote hosts at once
+and will open multiple connections to each host if configured to do so. The
+only limitations are the process limits for open file descriptors and memory.
+.PP
+As an alternative to using NNTP, INN may also be fed to an IMAP
+server. This is done by using an executable called imapfeed, which is
+identical to innfeed except for the delivery process. The new version
+has two types of connections: an LMTP connection to deliver regular
+messages and an IMAP connection to handle control messages. The
+startinnfeed process can then be told to start imapfeed instead of innfeed.
+(See the INSTALL file for how to do this.)
+.\"
+.\"
+.\"
+.\"
+.\"
+.SH MODES
+.PP
+.I Innfeed
+has three modes of operation: channel, funnel-file and batch.
+.PP
+Channel mode is used when no filename is given on the command line,
+the ``input-file'' keyword is \fInot\fP given in the config file, \fIand\fP
+the ``\fI\-x\fP'' option is \fInot\fP given.
+In channel mode innfeed runs with stdin connected via a pipe to
+innd. Whenever innd closes this pipe (and it has several reasons during
+normal processing to do so), innfeed will exit. It first will try to
+finish sending all articles it was in the middle of transmitting, before
+issuing a QUIT command. This means innfeed may take a while to exit
+depending on how slow your peers are. It never (well, almost never) just
+drops the connection.
+.PP
+The recommended way to restart innfeed when run in channel mode is
+therefore to tell innd to close the pipe and spawn a new innfeed process.
+This can be done with ``ctlinnd flush <feed>'' where <feed> is the name of
+the innfeed channel feed in ``\fInewsfeeds\fP''.
+.PP
+Funnel-file mode is used when a filename is given as an argument
+or the ``input-file'' keyword is given in the config file.
+In funnel file mode it reads the specified file for the same formatted
+information as innd would give in channel mode. It is expected that innd is
+continually writing to this file, so when innfeed reaches the end of the file
+it will check periodically for new information. To prevent the funnel file
+from growing without bounds, you will need to periodically move the file to
+the side (or simply remove it) and have innd flush the file. Then, after
+the file is flushed by innd, you can send innfeed a SIGALRM, and it too
+will close the file and open the new file created by innd. Something like:
+.PP
+.RS
+.nf
+innfeed -p /var/run/news/innfeed.pid my-funnel-file &
+while true; do
+ sleep 43200
+ rm -f my-funnel-file
+ ctlinnd flush funnel-file-site
+ kill -ALRM `cat /var/run/news/innfeed.pid`
+done
+.fi
+.RE
+.PP
+Batch mode is used when the ``\fI\-x\fP'' flag is used.
+In batch mode innfeed will ignore stdin, and will simply process any
+backlog created by a previously running innfeed. This mode is not normally
+needed as innfeed will take care of backlog processing.
+.\"
+.\"
+.\"
+.\"
+.\"
+.SH CONFIGURATION
+Innfeed expects a couple of things to be able to run correctly: a directory
+where it can store backlog files and a configuration file to describe which
+peers it should handle.
+.PP
+The configuration file is described in
+.IR innfeed.conf (5).
+The ``\fI\-c\fP''
+option can be used to specify a different file.
+.PP
+For each peer (say, ``\fIfoo\fP''), innfeed manages up to 4 files in the
+backlog directory: a ``\fIfoo.lock\fP'' file, which prevents other
+instances of innfeed from interfering with this one; a ``\fIfoo.input\fP''
+file which has old article information innfeed is reading for
+re-processing; a ``\fIfoo.output\fP'' file where innfeed is writing
+information on articles that couldn't be processed (normally due to a slow
+or blocked peer); and a ``\fIfoo\fP'' file.
+.PP
+This last file (``\fIfoo\fP'') is never created by innfeed, but if innfeed
+notices it, it will rename it to ``\fIfoo.input\fP'' at the next
+opportunity and will start reading from it. This lets you create a batch
+file and put it in a place where innfeed will find it. You should never
+alter the .input or .output files of a running innfeed.
+.PP
+The format of these last three files is one of the following:
+.PP
+.RS
+.nf
+/path/to/article <message-id>
+@token@ <message-id>
+.fi
+.RE
+.PP
+This is the same as the first two fields of the lines innd feeds to
+innfeed, and the same as the first two fields of the lines of the batch
+file innd will write if innfeed is unavailable for some reason. When
+innfeed processes its own batch files it ignores everything after the first
+two whitespace separated fields, so moving the innd-created batch file to
+the appropriate spot will work, even though the lines have extra fields.
+.PP
+The first field can also be a storage API token. The two types of lines can
+be intermingled; innfeed will use the storage manager if appropriate and
+otherwise treat the first field as a filename to read directly.
+.PP
+Innfeed writes its current status to the file ``\fIinnfeed.status\fP'' (or
+the file given by the ``\fI-S\fP'' option). This file contains details on
+the process as a whole, and on each peer this instance of innfeed is
+managing.
+.PP
+If innfeed is told to send an article to a host it is not managing, then
+the article information will be put into a file matching the pattern
+``\fIinnfeed-dropped.*\fP'', with part of the file name matching the pid of
+the innfeed process that is writing to it. Innfeed will not process this
+file except to write to it. If nothing is written to the file then it will
+be removed if innfeed exits normally.
+.\"
+.\"
+.\"
+.\"
+.\"
+.SH SIGNALS
+.PP
+Upon receipt of a SIGALRM innfeed will close the funnel-file specified on
+the command line, and will reopen it (see funnel file description above).
+.PP
+Innfeed with catch SIGINT and will write a large debugging snapshot of the
+state of the running system.
+.PP
+Innfeed will catch SIGHUP and will reload the config file.
+See
+.IR innfeed.conf (5)
+for more details.
+.PP
+Innfeed will catch SIGCHLD and will close and reopen all backlog files.
+.PP
+Innfeed will catch SIGTERM and will do an orderly shutdown.
+.PP
+Upon receipt of a SIGUSR1 innfeed will increment the debugging level by
+one; receipt of a SIGUSR2 will decrement it by one. The debugging level
+starts at zero (unless the ``-d'' option it used), in which case no debugging
+information is emitted. A larger value for the level means more debugging
+information. Numbers up to 5 are currently useful.
+.\"
+.\"
+.\"
+.\"
+.\"
+.SH SYSLOG ENTRIES
+.PP
+There are 3 different categories of syslog entries for statistics: Host,
+Connection and Global.
+.PP
+The Host statistics are generated for a given peer at regular intervals
+after the first connection is made (or, if the remote is unreachable, after
+spooling starts). The Host statistics give totals over all Connections that
+have been active during the given time frame. For example (broken here to
+fit the page, with ``vixie'' being the peer):
+.PP
+.nf
+ May 23 12:49:08 data innfeed[16015]: vixie checkpoint
+ seconds 1381 offered 2744 accepted 1286
+ refused 1021 rejected 437 missing 0 spooled 990
+ on_close 0 unspooled 240 deferred 10 requeued 25
+ queue 42.1/100:14,35,13,4,24,10
+.fi
+.PP
+These meanings of these fields are:
+.nr XX \w'unspooled '
+.TP \n(XXu
+seconds
+The time since innfeed connected to the host or since the statistics
+were reset by a ``final'' log entry.
+.TP
+offered
+The number of IHAVE commands sent to the host if it is not in streaming mode.
+The sum of the number of TAKETHIS commands sent when no-CHECK mode
+is in effect plus the number CHECK commands sent in streaming mode (when
+no-CHECK mode is not in effect).
+.TP
+accepted
+The number of articles which were sent to the remote host and accepted
+by it.
+.TP
+refused
+The number of articles offered to the host that it it indicated it
+didn't want because it had already seen the Message-ID. The remote
+host indicates this by sending a 435 response to an IHAVE command or
+a 438 response to a CHECK command.
+.TP
+rejected
+The number of articles transferred to the host that it did not accept
+because it determined either that it already had the article or it did
+not want it because of the article's Newsgroups: or Distribution: headers,
+etc. The remote host indicates that it is rejecting the article by
+sending a 437 or 439 response after innfeed sent the entire article.
+.TP
+missing
+The number of articles which innfeed was told to offer to the host but
+which were not present in the article spool. These articles were probably
+cancelled or expired before innfeed was able to offer them to the host.
+.TP
+spooled
+The number of article entries that were written to the .output backlog file
+because the articles could not either be sent to the host or be refused
+by it. Articles are generally spooled either because new articles are
+arriving more quickly than they can be offered to the host, or because
+innfeed closed all the connections to the host and pushed all the
+articles currently in progress to the .output backlog file.
+.TP
+on_close
+The number of articles that were spooled when innfeed closed all the
+connections to the host.
+.TP
+unspooled
+The number of article entries that were read from the .input backlog
+file.
+.TP
+deferred
+The number of articles that the host told innfeed to retry later by
+sending a 431 or 436 response. Innfeed immediately puts these articles
+back on the tail of the queue.
+.TP
+requeued
+The number of articles that were in progress on connections when innfeed
+dropped those connections and put the articles back on the queue. These
+connections may have been broken by a network problem or became unresponsive
+causing innfeed to time them out.
+.TP
+queue
+The first number is the average (mean) queue size during the previous logging
+interval. The second number is the maximum allowable queue size.
+The third number is the percentage of the time that the queue
+was empty. The fourth through seventh numbers are the percentages of the
+time that the queue was >0% to 25% full, 25% to 50% full, 50% to 75%
+full, and 75% to <100% full. The last number is the percentage of the
+time that the queue was totally full.
+.PP
+If the ``\fI\-z\fP'' option is used (see below), then when the peer stats are
+generated, each Connection will log its stats too. For example, for
+connection number zero (from a set of five):
+.PP
+.nf
+ May 23 12:49:08 data innfeed[16015]: vixie:0 checkpoint
+ seconds 1381 offered 596 accepted 274
+ refused 225 rejected 97
+.fi
+.PP
+If you only open a maximum of one Connection to a remote, then there will
+be a close correlation between Connection numbers and Host numbers, but in
+general you can't tie the two sets of number together in any easy or very
+meaningful way. When a Connection closes it will always log its stats.
+.PP
+If all Connections for a Host get closed together, then the Host logs its
+stats as ``final'' and resets its counters. If the feed is so busy that
+there's always at least one Connection open and running, then after some
+amount of time (set via the config file), the Host stats are logged as
+final and reset. This is to make generating higher level stats from log
+files, by other programs, easier.
+.PP
+There is one log entry that is emitted for a Host just after its last
+Connection closes and innfeed is preparing to exit. This entry contains
+counts over the entire life of the process. The ``seconds'' field is from the
+first time a Connection was successfully built, or the first time spooling
+started. If a Host has been completely idle, it will have no such log entry.
+.PP
+.nf
+ May 23 12:49:08 data innfeed[16015]: decwrl global
+ seconds 1381 offered 34 accepted 22
+ refused 3 rejected 7 missing 0
+.fi
+.PP
+The final log entry is emitted immediately before exiting. It contains a
+summary of the statistics over the entire life of the process.
+.PP
+.nf
+ Feb 13 14:43:41 data innfeed-0.9.4[22344]: ME global
+ seconds 15742 offered 273441 accepted 45750
+ refused 222008 rejected 3334 missing 217
+.fi
+.PP
+.\"
+.\"
+.\"
+.\"
+.\"
+.SH OPTIONS
+.TP
+.B \-a
+The ``\fI\-a\fP'' flag is used to specify the top of the article spool
+tree. Innfeed does a chdir(2) to this directory, so it should probably be
+an absolute path. The default is <patharticles\ in\ inn.conf>.
+.TP
+.B \-b
+The ``\fI\-b\fP'' flag may be used to specify a different directory for backlog
+file storage and retrieval. If the path is relative then it is relative
+to <pathspool\ in\ inn.conf>. The default is ``\fIinnfeed\fP''.
+.TP
+.B \-c
+The ``\fI\-c\fP'' flag may be used to specify a different config file from the
+default value. If the path is relative then it is relative to
+<pathetc\ in\ inn.conf>. The default is ``\fIinnfeed.conf\fP''.
+.TP
+.B \-C
+The ``\fI\-C\fP'' flag is used to have innfeed simply check the config
+file, report on any errors and then exit.
+.TP
+.B \-d
+The ``\fI\-d\fP'' flag may be used to specify the initial logging level. All
+debugging messages go to stderr (which may not be what you want, see the
+``\fI\-l\fP'' flag below).
+.TP
+.B \-e
+The ``\fI\-e\fP'' flag may be used to specify the size limit (in bytes) for the
+\fI\%.output\fP backlog files innfeed creates. If the output file gets bigger
+than 10% more than the given number, innfeed will replace the output file
+with the tail of the original version. The default value is 0, which means
+there is no limit.
+.TP
+.B \-h
+Use the ``\fI\-h\fP'' flag to print the usage message.
+.TP
+.B \-l
+The ``\fI\-l\fP'' flag may be used to specify a different log file from
+stderr. As innd starts innfeed with stderr attached to /dev/null, using this
+option can be useful in catching any abnormal error messages, or any
+debugging messages (all ``normal'' errors messages go to syslog).
+.TP
+.B \-M
+If innfeed has been built with mmap support, then the ``\fI\-M\fP'' flag
+turns OFF the use of mmap(); otherwise it has no effect.
+.TP
+.B \-m
+The ``\fI\-m\fP'' flag is used to turn on logging of all missing
+articles. Normally if an article is missing, innfeed keeps a count, but
+logs no further information. When this flag is used, details about
+message-id and expected pathname are logged.
+.TP
+.B \-o
+The ``\fI\-o\fP'' flag sets a value of the maximum number of bytes of article
+data innfeed is supposed to keep in memory. This doesn't work properly yet.
+.TP
+.B \-p
+The ``\fI\-p\fP'' flag is used to specify the filename to write the pid of the
+process into. A relative path is relative to <pathrun\ in\ inn.conf>. The
+default is ``\fIinnfeed.pid\fP''.
+.TP
+.B \-S
+The ``\fI\-S\fP'' flag specifies the name of the file to write the periodic
+staus to. If the path is relative it is considered relative to
+<pathlog\ in\ inn.conf>. The default is ``\fIinnfeed.status\fP''.
+.TP
+.B \-v
+When the ``\fI\-v\fP'' flag is given, version information is printed to stderr
+and then innfeed exits.
+.TP
+.B \-x
+The ``\fI\-x\fP'' flag is used to tell innfeed not to expect any article
+information from innd but just to process any backlog files that exist and
+then exit.
+.TP
+.B \-y
+The ``\fI\-y\fP'' flag is used to allow dynamic peer binding. If this flag is
+used and article information is received from innd that specifies an
+unknown peer, then the peer name is taken to be the IP name too, and an
+association with it is created. Using this it is possible to only
+have the global defaults in the
+.I innfeed.conf
+file, provided the peername as used by innd is the same as the ip name.
+Note that
+.I innfeed
+with ``\fI\-y\fP'' and no peer in
+.I innfeed.conf
+would cause a problem that
+.I innfeed
+drops the first article.
+.TP
+.B \-z
+The ``\fI\-z\fP'' flag is used to cause each connection, in a parallel feed
+configuration, to report statistics when the controller for the connections
+prints its statistics.
+.TP
+.\"
+.\"
+.\"
+.\"
+.\"
+.SH BUGS
+.PP
+When using the ``-x'' option, the config file entry's
+``initial-connections'' field will be the total number of connections
+created and used, no matter how many big the batch file, and no
+matter how big the ``max-connectiond'' field specifies. Thus a value
+of 0 for ``initial-connections'' means nothing will happen in ``-x''
+mode.
+.PP
+Innfeed does not automatically grab the file out of out.going--this needs
+to be prepared for it by external means.
+.PP
+Probably too many other bugs to count.
+.\"
+.\"
+.\"
+.\"
+.\"
+.SH FILES
+innfeed.conf config file.
+.br
+innfeed directory for backlog files.
+.\"
+.\"
+.\"
+.\"
+.\"
+.SH HISTORY
+Written by James Brister <brister@vix.com> for InterNetNews.
+.de R$
+This is revision \\$3, dated \\$4.
+..
+.R$ $Id: innfeed.1 7798 2008-04-26 08:47:01Z iulius $
+.SH SEE ALSO
+.IR innfeed.conf(5)
--- /dev/null
+.\" -*- nroff -*-
+.\"
+.\" Author: James A. Brister <brister@vix.com> -- berkeley-unix --
+.\" Start Date: Sun, 21 Jan 1996 00:47:37 +1100
+.\" Project: INN -- innfeed
+.\" File: innfeed.conf.5
+.\" RCSId: $Id: innfeed.conf.5 7778 2008-04-17 21:27:22Z iulius $
+.\" Description: Man page for innfeed.conf(5)
+.\"
+.TH innfeed.conf 5
+.SH NAME
+innfeed.conf \- configuration file for innfeed
+.SH DESCRIPTION
+.PP
+This man page describes the configuration file for version 1.0 of
+innfeed. This format has changed dramatically since version 0.9.3.
+.PP
+The file
+.B innfeed.conf
+is used to control the innfeed(1) program. It is a fairly free-format file
+that consists of three types of entries: \fIkey/value\fP, \fIpeer\fP and
+\fIgroup\fP.
+Comments are from the hash character ``#'' to the end of the line.
+.PP
+\fIKey/value\fP entries are a keyword and a value separated by a colon
+(which can itself be surrounded by whitespace). For example:
+.PP
+.RS
+.nf
+max-connections: 10
+.fi
+.RE
+.PP
+A legal
+key starts with a letter and contains only letters, digits, and ``_'',
+``-''.
+.LP
+There are 5 different type of values: integers, floating-point numbers,
+characters, booleans, and strings. Integer and floating point numbers are
+as to be expected except that exponents in floating point numbers are not
+supported. A boolean value is either ``true'' or ``false'' (case is not
+significant). A character value is a single-quoted character as defined by
+the C-language. A string value is any other sequence of characters. If the
+string needs to contain whitespace, then it must be quoted with double
+quotes, and uses the same format for embedding non-printing characters as
+normal C-language string.
+.PP
+Peer entries look like:
+.PP
+.RS
+.nf
+peer <name> {
+ # body ...
+}
+.fi
+.RE
+.PP
+The word ``peer'' is required. The ``<name>'' is the same as the site name
+in INN's newsfeeds file. The body of a peer entry contains some number
+(possibly zero) of key/value entries.
+.PP
+Group entries look like:
+.PP
+.RS
+.nf
+group <name> {
+ # body
+}
+.fi
+.RE
+.PP
+The word ``group'' is required. The ``<name>'' is any string valid as a
+key. The body of a group entry contains any number of the three types of
+entries. So key/value pairs can be defined inside a group, and peers can be
+nested inside a group, and other groups can be nested inside a group.
+.PP
+Key/value entries that are defined outside of all peer and group entries
+are said to be at ``global scope''. There are global key/value entries that
+apply to the process as a whole (for example the location of the backlog
+file directory), and there are global key/value entries that act as
+defaults for peers. When innfeed looks for a specific value in a peer entry
+(for example, the maximum number of connections to set up), if the value is
+not defined in the peer entry, then the enclosing groups are examined for
+the entry (starting at the closest enclosing group). If there are no
+enclosing groups, or the enclosing groups don't define the key/value, then
+the value at global scope is used.
+.PP
+A small example could be:
+.PP
+.RS
+.nf
+# Global value applied to all peers that have
+# no value of their own.
+max-connections: 5
+
+# A peer definition. ``uunet'' is the name used by innd in
+# the newsfeeds file.
+peer uunet {
+ ip-name: usenet1.uu.net
+}
+
+peer vixie {
+ ip-name: gw.home.vix.com
+ max-connections: 10 # override global value.
+}
+
+# A group of two peers who can handle more connections
+# than normal
+group fast-sites {
+ max-connections: 15
+
+ # Another peer. The ``max-connections'' value from the
+ # ``fast-sites'' group scope is used. The ``ip-name'' value
+ # defaults to the peer's name.
+ peer data.ramona.vix.com {
+ }
+
+ peer bb.home.vix.com {
+ max-connections: 20 # he can really cook.
+ }
+}
+.fi
+.RE
+.PP
+Given the above configuration file, the defined peers would have the
+following values for the ``max-connections'' key.
+.PP
+.RS
+.nf
+uunet 5
+vixie 10
+data.ramona.vix.com 15
+bb.home.vix.com 20
+.fi
+.RE
+.PP
+Innfeed ignores key/value pairs it is not interested in. Some config file
+values can be set via a command line option, in which case that setting
+overrides the settings in the file.
+.PP
+Config files can be included in other config files via the syntax:
+.sp 1
+.nf
+.RS
+$INCLUDE filename
+.RE
+.fi
+.sp 1
+There is a maximum nesting depth of 10.
+.PP
+For a fuller example config file, see the supplied \fIinnfeed.conf\fP.
+.SH "GLOBAL VALUES"
+.PP
+The following listing show all the keys that apply to the process as
+whole. These are not required (compiled-in defaults are used where needed).
+.TP
+.B news-spool
+This key requires a pathname value. It specifies where the top of the
+article spool is. This corresponds to the ``\fI\-a\fP'' command-line
+option.
+.TP
+.B input-file
+This key requires a pathname value. It specifies the pathname (relative to
+the \fBbacklog-directory\fP) that should be read in funnel-file mode. This
+corresponds to giving a filename as an argument on the command-line (i.e.
+its presence also implies that funnel-file mode should be used).
+.TP
+.B pid-file
+This key requires a pathname value. It specifies the pathname (relative to
+the \fBbacklog-directory\fP) where the pid of the innfeed process should be
+stored. This corresponds to the ``\fI\-p\fP'' command-line option.
+.TP
+.B debug-level
+This key defines the debug level for the process. A non-zero number
+generates a lot of messages to stderr, or to the config-defined ``log-file''.
+This corresponds to the ``\fI\-d\fP'' command-line option.
+.TP
+.B use-mmap
+This key requires a boolean value. It specifies whether mmaping should be
+used if innfeed has been built with mmap support. If article data on disk
+is not in NNTP-ready format (CR/LF at the end of each line), then after
+mmaping the article is read into memory and fixed up, so mmaping has no
+positive effect (and possibly some negative effect depending on your
+system), and so in such a case this value should be \fIfalse\fP. This
+corresponds to the ``\fI\-M\fP'' command-line option.
+.TP
+.B log-file
+This key requires a pathname value. It specifies where any logging messages
+that couldn't be sent via syslog(3) should go (such as those generated when
+a positive value for ``\fBdebug-value\fP'', is used). This corresponds to
+the ``\fI\-l\fP'' command-line option. A relative pathname is relative to
+the ``\fBbacklog-directory\fP'' value.
+.\" .TP
+.\" .B initial-sleep
+.\" This key requires a positive integer value. It specifies how many seconds
+.\" innfeed should sleep at startup before attempting to take out its locks. On
+.\" fast machines and with innfeed handling many connections, it can take too
+.\" long for innfeed to recognise that its input has been closed, and that it
+.\" should release any locks it holds.
+.\"..................................................
+.TP
+.B backlog-directory
+This key requires a pathname value. It specifies where the current innfeed
+process should store backlog files. This corresponds to the ``\fI\-b\fP''
+command-line option.
+.TP
+.B backlog-highwater
+This key requires a positive integer value. It specifies how many articles
+should be kept on the backlog file queue before starting to write new
+entries to disk.
+.TP
+.B backlog-ckpt-period
+This key requires a positive integer value. It specifies how many seconds
+between checkpoints of the input backlog file. Too small a number will mean
+frequent disk accesses, too large a number will mean after a crash innfeed
+will re-offer more already-processed articles than necessary.
+.TP
+.B backlog-newfile-period
+This key requires a positive integer value. It specifies how many seconds
+before each checks for externally generated backlog files that are to be
+picked up and processed.
+.TP
+.B backlog-rotate-period
+This key requires a positive integer value. It specifies how many seconds
+elapse before
+.B innfeed
+checks for a manually created backlog file and moves the output backlog
+file to the input backlog file.
+.\"..................................................
+.TP
+.B dns-retry
+This key requires a positive integer value. It defines the number of seconds
+between attempts to re-lookup host information that previous failed to be
+resolved.
+.TP
+.B dns-expire
+This key requires a positive integer value. It defines the number of seconds
+between refreshes of name to address DNS translation. This is so long-running
+processes don't get stuck with stale data, should peer ip addresses change.
+.TP
+.B close-period
+This key requires a positive integer value. It is the maximum number of
+seconds a connection should be kept open. Some NNTP servers don't deal well
+with connections being held open for long periods.
+.TP
+.B gen-html
+This key requires a boolean value. It specifies whether the
+\fBstatus-file\fP should be HTML-ified.
+.TP
+.B status-file
+This key requires a pathname value. It specifies the pathname (relative to
+the \fBbacklog-directory\fP) where the periodic status of the innfeed
+process should be stored. This corresponds to the ``\fI\-S\fP''
+command-line option.
+.TP
+.B connection-stats
+This key requires a boolean value. If the value is true, then whenever the
+transmission statistics for a peer are logged, then each active connection
+logs its own statistics. This corresponds to the ``\fI\-z\fP''
+command-line option.
+.TP
+.B host-queue-highwater
+This key requires a positive integer value. It defines how many articles
+will be held internally for a peer before new arrivals cause article
+information to be spooled to the backlog file.
+.TP
+.B stats-period
+This key requires a positive integer value. It defines how many seconds
+innfeed waits between generating statistics on transfer rates.
+.TP
+.B stats-reset
+This key requires a positive integer value. It defines how many seconds
+innfeed waits before resetting all internal transfer counters back to zero
+(after logging one final time). This is so a innfeed-process running more
+than a day will generate ``final'' stats that will be picked up by logfile
+processing scripts.
+.\"..................................................
+.TP
+.B initial-reconnect-time
+This key requires a positive integer value. It defines how many seconds to
+first wait before retrying to reconnect after a connection failure. If the
+next attempt fails too, then the reconnect time is approximately doubled
+until the connection succeeds, or \fBmax-reconnection-time\fP is reached.
+.TP
+.B max-reconnect-time
+This key requires an integer value. It defines the maximum number of
+seconds to wait between attempt to reconnect to a peer. The initial value
+for reconnection attempts is defined by \fBinitial-reconnect-time\fP, and
+it is doubled after each failure, up to this value.
+.TP
+.B stdio-fdmax
+This key requires a non-negative integer value. If the value is greater
+than zero, then whenever a network socket file descriptor is created and
+it has a value \fIless than\fP this, the file descriptor will be dup'ed to
+bring the value up greater than this. This is to leave lower numbered file
+descriptors free for stdio. Certain systems, Sun's in particular, require
+this. SunOS 4.1.x usually requires a value of 128 and Solaris requires a
+value of 256. The default if this is not specified, is 0.
+.\"..................................................
+.SH "GLOBAL PEER DEFAULTS"
+.PP
+All the key/value pairs mentioned in this section must be specified at
+global scope. They may also be specified inside a group or peer
+definition. Note that when peers are added dynamically (i.e. when
+innfeed receives an article for an unspecified peer), it will add
+the peer site using the parameters specified at global scope.
+.TP
+.B article-timeout
+This key requires a non-negative integer value. If no articles need to be
+sent to the peer for this many seconds, then the peer is considered idle
+and all its active connections are torn down.
+.TP
+.B response-timeout
+This key requires a non-negative integer value. It defines the maximum
+amount of time to wait for a response from the peer after issuing a
+command.
+.TP
+.B initial-connections
+This key requires a non-negative integer value. It defines the number of
+connections to be opened immediately when setting up a peer binding. A
+value of 0 means no connections will be created until an article needs to
+be sent.
+.TP
+.B max-connections
+This key requires positive integer value. It defines the maximum number of
+connections to run in parallel to the peer. A value of zero specifies an
+unlimited number of maximum connections. In general use of an unlimited
+number of maximum connections is not recommended. Do not ever set
+\fBmax-connections\fP to zero with \fBdynamic-method\fP 0 set, as this will
+saturate peer hosts with connections. [ Note that in previous versions
+of innfeed, a value of 1 had a special meaning. This is no longer the case,
+1 means a maximum of 1 connection ].
+.TP
+.B dynamic-method
+This key requires an integer value between 0 and 3. It controls how connections
+(up to max-connections) are opened, up to the maximum specified by
+\fBmax-connections\fP. In general (and specifically, with \fBdynamic-method\fP
+0), a new connection is opened when the current number of connections is
+below \fBmax-connections\fP, and an article is to be sent while no current
+connections are idle. Without further restraint (i.e. using
+\fBdynamic-method\fP 0), in practice this means that \fBmax-connections\fP
+connections are established while articles are being sent. Use of other
+\fBdynamic-method\fP settings imposes a further limit on the amount of
+connections opened below that specified by \fBmax-connections\fP. This
+limit is calculated in different ways, depending of the value of
+\fBdynamic-method\fP.
+Users should note that adding additional connections is not always
+productive - just because opening twice as many connections results
+in a small percentage increase of articles accepted by the remote peer,
+this may be at considerable resource cost both locally and at the remote
+site, whereas the remote site might well have received the extra articles
+sent from another peer a fraction of a second later. Opening large
+numbers of connections is considered antisocial.
+The meanings of the various settings are:
+.RS
+.TP
+.B 0 no method
+Increase of connections up to \fBmax-connections\fP is unrestrained.
+.TP
+.B 1 maximize articles per second
+Connections are increased (up to \fBmax-connections\fP) and decreased so as
+to maximize the number of articles per second sent, while using the fewest
+connections to do this.
+.TP
+.B 2 set target queue length
+Connections are increased (up to \fBmax-connections\fP) and decreased so as
+to keep the queue of articles to be sent within the bounds set by
+\fBdynamic-backlog-low\fP and \fBdynamic-backlog-high\fP,
+while using the minimum resources possible.
+As the queue will tend to fill if the site is not keeping up, this method
+ensures that the maximum number of articles are offered to the peer
+while using the minimum number of connections to achieve this.
+.TP
+.B 3 combination
+This method uses a combination of methods 1 and 2 above. For sites
+accepting a large percentage of articles, method 2 will be used to
+ensure these sites are offered as complete a feed as possible. For sites
+accepting a small percentage of articles, method 1 is used, to minimize
+remote resource usage. For intermediate sites, an appropriate combination
+is used.
+.RE
+.TP
+.B dynamic-backlog-low
+This key requires an integer value between 0 and 100. It represents (as a
+percentage) the low water mark for the host queue. If the host queue falls
+below this level while using \fBdynamic-method\fP 2 or 3, and if 2 or more
+connections are open, innfeed will attempt to drop connections to the host.
+An IIR filter is applied to the value to prevent connection flap (see
+\fBdynamic-filter\fP). A value of 25.0 is recommended. This value
+must be smaller than \fBdynamic-backlog-high\fP.
+.TP
+.B dynamic-backlog-high
+This key requries an integer value between 0 and 100. It represents (as a
+percentage) the high water mark for the host queue. If the host queue rises
+above this level while using \fBdynamic-method\fP 2 or 3, and if less than
+\fBmax-connections\fP are open to the host, innfeed will attempt
+to open further connections to the host. An IIR filter is applied to the value
+to prevent connection flap (see \fBdynamic-filter\fP). A value of 50.0
+is recommended. This value must be larger than \fBdynamic-backlog-low\fP.
+.TP
+.B dynamic-backlog-filter
+This key requires a floating-point value between 0 and 1. It represents the
+filter coefficient used by the IIR filter used to implement
+\fBdynamic-method\fP 2 and 3.
+The recommended value of this filter is 0.7, giving a time
+constant of 1/(1-0.7) articles. Higher values will result in slower
+response to queue fullness changes, lower values in faster response.
+.TP
+.B max-queue-size
+This key requires a positive integer value. It defines the maximum number
+of articles to process at one time when using streaming to transmit to a
+peer. Larger numbers mean more memory consumed as articles usually get
+pulled into memory (see the description of \fBuse-mmap\fP).
+.TP
+.B streaming
+This key requires a boolean value. It defines whether streaming commands
+are used to transmit articles to the peers.
+.TP
+.B no-check-high
+This key requires a floating-point number which must be in the range [0.0,
+100.0]. When running transmitting with the streaming commands, innfeed
+attempts an optimization called ``no-CHECK'' mode. This involves \fInot\fP
+asking the peer if it wants the article, but just sending it. This
+optimization occurs when the percentage of the articles the peer has
+accepted gets larger than this number. If this value is set to 100.0, then
+this effectively turns off no-CHECK mode, as the percentage can never get
+above 100.0. If this value is too small, then the number of articles the
+peer rejects will get bigger (and your bandwidth will be wasted). A value
+of 95.0 usually works pretty well. NOTE: In innfeed 0.9.3 and earlier this
+value was in the range [0.0, 9.0].
+.TP
+.B no-check-low:
+This key requires a floating-point number which must be in the range [0.0,
+100.0), and it must be smaller that the value for \fBno-check-high\fP. When
+running in no-CHECK mode, as described above, if the percentage of articles
+the remote accepts drops below this number, then the no-CHECK optimization
+is turned off until the percentage gets above the \fBno-check-high\fP value
+again. If there is small difference between this and the
+\fBno-check-high\fP value (less than about 5.0), then innfeed may
+frequently go in and out of no-CHECK mode. If the difference is too big,
+then it will make it harder to get out of no-CHECK mode when necessary
+(wasting bandwidth). Keeping this to between 5.0 and 10.0 less than
+\fBno-check-high\fP usually works pretty well.
+.TP
+.B no-check-filter
+This is a floating point value representing the time constant, in articles,
+over which the CHECK / no-CHECK calculations are done. The recommended
+value is 50.0 which will implement an IIR filter of time constant 50. This
+roughly equates to making a decision about the mode over the previous
+50 articles. A higher number will result in a slower response to changing
+percentages of articles accepted; a lower number will result in a faster
+response.
+.TP
+.B bindaddress
+This key requires a string value. It specifies which outgoing IPv4 address
+innfeed should bind the local end of its connection to.
+Must be an IPv4 address in dotted-quad format (nnn.nnn.nnn.nnn), "any",
+or "none". If not set or set to "any", innfeed defaults
+to letting the kernel choose this address.
+If set to "none", innfeed will not use IPv4 for outgoing connections
+to peers in this scope (i.e. it forces IPv6).
+The default value is unset.
+.TP
+.B bindaddress6
+This key requires a string value. It behaves like \fBbindaddress\fP except
+for outgoing IPv6 connections. Must be in numeric IPv6 format, "any",
+or "none". If set to "none", innfeed will not use IPv6 for outgoing
+connections to peers in this scope. A value containing colons must be
+enclosed in double quotes.
+.TP
+.B port-number
+This key requires a positive integer value. It defines the tcp/ip port
+number to use when connecting to the remote.
+.TP
+.B force-ipv4
+This key requires a boolean value. By default it is set to false.
+Setting it to true is the same as setting "bindaddress6: none"
+and removing "bindaddress: none" if it was set.
+.TP
+.B drop-deferred
+This key requires a boolean value. By default it is set to false. When
+set to true, and a peer replies with code 431 or 436 (try again later) just
+drop the article and don't try to re-send it. This is useful for some
+peers that keep on deferring articles for a long time to prevent innfeed
+from trying to offer the same article over and over again.
+.TP
+.B min-queue-connection
+This key requires a boolean value. By default it is set to false. When
+set to true, innfeed will attempt to use a connection with the least queue
+size (or the first empty connection). If this key is set to true, it is
+recommended that \fBdynamic-method\fP be set to 0. This allows for article
+propagation with the least delay.
+.TP
+.B no-backlog
+This key requires a boolean value. It specifies whether spooling should
+be enabled (false, the default) or disabled (true). Note that when no-backlog
+is set, articles reported as "spooled" are actually silently discarded.
+.TP
+.B backlog-limit
+This key requires a non-negative integer value. If the number is 0 then
+backlog files are allowed to grown without bound when the peer is unable to
+keep up with the article flow. If this number is greater than 0 then it
+specifies the size (in bytes) the backlog file should get truncated to when
+the backlog file reaches a certain limit. The limit depends on whether
+\fBbacklog-factor\fP or \fBbacklog-limit-highwater\fP is used.
+.TP
+.B backlog-factor
+This key requires a floating point value, which must be larger than 1.0. It
+is used in conjunction with the peer key \fBbacklog-limit\fP. If
+\fBbacklog-limit\fP has a value greater than zero, then when the backlog
+file gets larger than the value \fBbacklog-limit * backlog-factor\fP, then
+the backlog file will be truncated to the size \fBbacklog-limit\fP. For
+example if \fBbacklog-limit\fP has a value of 1000000, and
+\fBbacklog-factor\fP has a value of 2.0, then when the backlogfile gets to
+be larger than 2000000 bytes in size, it will be truncated to 1000000 bytes.
+The front
+portion of the file is removed, and the trimming happens on line boundaries,
+so the final size may be a bit less than this number. If
+\fBbacklog-limit-highwater\fP is defined too, then \fBbacklog-factor\fP
+takes precedence.
+.TP
+.B backlog-limit-highwater
+This key requires a positive integer value that must be larger than the
+value for \fBbacklog-limit\fP. If the size of the backlog file gets larger
+than this value (in bytes), then the backlog file will be shrunk down to
+the size of \fBbacklog-limit\fP. If both \fBbacklog-factor\fP and
+\fBbacklog-limit-highwater\fP are defined, then the value of
+\fBbacklog-factor\fP is used.
+.TP
+.B backlog-feed-first
+This key requires a boolean value. By default it is set to false. When set
+to true, the backlog is fed before new files. This is intended to enforce
+in-order delivery, so setting this to true when initial-connections or
+max-connections is more than 1 is inconsistent.
+.TP
+.B username
+This key requires a string value. If the value is defined, then innfeed
+tries to authenticate by ``AUTHINFO USER'' and this value used for user name.
+\fBpassword\fP must also be defined, if this key is defined.
+.TP
+.B password
+This key requires a string value. The value is the password
+used for ``AUTHINFO PASS''.
+\fBusername\fP must also be defined, if this key is defined.
+.TP
+.B deliver
+This key is used with imapfeed to authenticate to a remote host. It is optional.
+There are several parameters that must be included with deliver:
+.RS
+.TP
+.B deliver-authname
+The authname is who you want to authenticate as.
+.TP
+.B deliver-password
+This is the appropriate password for authname.
+.TP
+.B deliver-username
+The username is who you want to "act" as, that is, who is actually
+going to be using the server.
+.TP
+.B deliver-realm
+In this case, the "realm" is the realm in which the specified authname
+is valid. Currently this is only needed by the DIGEST-MD5 SASL
+mechanism.
+.TP
+.B deliver-rcpt-to
+A printf-style format string for creating the envelope recipient address.
+The pattern MUST include a single string specifier which will be
+replaced with the newgroup (e.g "bb+%s"). The default is "+%s".
+.TP
+.B deliver-to-header
+An optional printf-style format string for creating a To: header to be
+prepended to the article. The pattern MUST include a single string
+specifier which will be replaced with the newgroup (e.g
+"post+%s@domain"). If not specified, the To: header will not be
+prepended.
+.RE
+.\"..................................................
+.SH "PEER VALUES"
+As previously explained, the peer definitions can contain redefinitions of
+any of the key/value pairs described in the \fBGLOBAL PEER DEFAULTS\fP
+section above. There is one key/value pair that is specific to a peer
+definition.
+.TP
+.B ip-name
+This key requires a word value. The word is the host's FQDN, or the dotted
+quad ip-address. If this value is not specified then the name of the peer
+is taken to also be its ip-name. See the entry for
+data.ramona.vix.com in the example below.
+.\"..................................................
+.SH RELOADING
+.PP
+If innfeed gets a SIGHUP signal, then it will reread the config file. All
+values at global scope except for ``\fBbacklog-directory\fP'' can be
+changed (although note that ``\fBbindaddress\fP'' and
+``\fBbindaddress6\fP'' changes will only affect new connections). Any new
+peers are added and any missing peers have their connections closed.
+.\"..................................................
+.SH EXAMPLE
+.PP
+Below is the sample innfeed.conf file.
+.RS
+.nf
+#
+# innfeed.conf file. See the comment block at the
+# end for a fuller description.
+#
+
+##
+## Global values. Not specific to any peer. These
+## are optional, but if used will override the
+## compiled in values. Command-line options used
+## will override these values.
+##
+
+pid-file: innfeed.pid
+debug-level: 0
+use-mmap: false
+log-file: innfeed.log
+stdio-fdmax: 0
+
+backlog-directory: innfeed
+backlog-rotate-period: 60
+backlog-ckpt-period: 30
+backlog-newfile-period: 600
+
+dns-retry: 900
+dns-expire: 86400
+close-period: 3600
+gen-html: false
+status-file: innfeed.status
+connection-stats: false
+host-queue-highwater: 200
+stats-period: 600
+stats-reset: 43200
+
+max-reconnect-time: 3600
+initial-reconnect-time: 30
+
+
+##
+## Defaults for all peers. These must all exist at
+## global scope. Any of them can be redefined
+## inside a peer or group definition.
+##
+
+article-timeout: 600
+response-timeout: 300
+initial-connections: 1
+max-connections: 5
+max-queue-size: 25
+streaming: true
+no-check-high: 95.0
+no-check-low: 90.0
+no-check-filter: 50.0
+port-number: 119
+backlog-limit: 0
+backlog-factor: 1.10
+backlog-limit-highwater:0
+dynamic-method: 3
+dynamic-backlog-filter: 0.7
+dynamic-backlog-low: 25.0
+dynamic-backlog-high: 50.0
+no-backlog: false
+
+##
+## Peers.
+##
+peer decwrl {
+ ip-name: news1.pa.dec.com
+}
+
+peer uunet {
+ ip-name: news.uunet.uu.net
+ max-connections: 10
+}
+
+peer data.ramona.vix.com {
+ # ip-name defaults to data.ramona.vix.com
+ streaming: false
+}
+
+peer bb.home.vix.com {
+ ip-name: 192.5.5.33
+}
+
+
+
+# Blank lines are ignored. Everything after a '#'
+# is ignored too.
+#
+# Format is:
+# key : value
+#
+# See innfeed.conf(5) for a description of
+# necessary & useful keys. Unknown keys and their
+# values are ignored.
+#
+# Values may be a integer, floating-point, c-style
+# single-quoted characters, boolean, and strings.
+#
+# If a string value contains whitespace, or
+# embedded quotes, or the comment character
+# (``#''), then the whole string must be quoted
+# with double quotes. Inside the quotes, you may
+# use the standard c-escape sequence
+# (\\t,\\n,\\r,\\f,\\v,\\",\\').
+#
+# Examples:
+# eg-string: "New\\tConfig\\tfile\\n"
+# eg-long-string: "A long string that goes
+# over multiple lines. The
+# newline is kept in the
+# string except when quoted
+# with a backslash \\
+# as here."
+# eg-simple-string: A-no-quote-string
+# eg-integer: 10
+# eg-boolean: true
+# eg-char: 'a'
+# eg-ctrl-g: '\\007'
+
+.fi
+.RE
+.SH HISTORY
+Written by James Brister <brister@vix.com> for InterNetNews.
+.de R$
+This is revision \\$3, dated \\$4.
+..
+.R$ $Id: innfeed.conf.5 7778 2008-04-17 21:27:22Z iulius $
+.SH SEE ALSO
+innfeed(1), newsfeeds(5)
--- /dev/null
+.\" Automatically generated by Pod::Man v1.37, Pod::Parser v1.32
+.\"
+.\" Standard preamble:
+.\" ========================================================================
+.de Sh \" Subsection heading
+.br
+.if t .Sp
+.ne 5
+.PP
+\fB\\$1\fR
+.PP
+..
+.de Sp \" Vertical space (when we can't use .PP)
+.if t .sp .5v
+.if n .sp
+..
+.de Vb \" Begin verbatim text
+.ft CW
+.nf
+.ne \\$1
+..
+.de Ve \" End verbatim text
+.ft R
+.fi
+..
+.\" Set up some character translations and predefined strings. \*(-- will
+.\" give an unbreakable dash, \*(PI will give pi, \*(L" will give a left
+.\" double quote, and \*(R" will give a right double quote. \*(C+ will
+.\" give a nicer C++. Capital omega is used to do unbreakable dashes and
+.\" therefore won't be available. \*(C` and \*(C' expand to `' in nroff,
+.\" nothing in troff, for use with C<>.
+.tr \(*W-
+.ds C+ C\v'-.1v'\h'-1p'\s-2+\h'-1p'+\s0\v'.1v'\h'-1p'
+.ie n \{\
+. ds -- \(*W-
+. ds PI pi
+. if (\n(.H=4u)&(1m=24u) .ds -- \(*W\h'-12u'\(*W\h'-12u'-\" diablo 10 pitch
+. if (\n(.H=4u)&(1m=20u) .ds -- \(*W\h'-12u'\(*W\h'-8u'-\" diablo 12 pitch
+. ds L" ""
+. ds R" ""
+. ds C` ""
+. ds C' ""
+'br\}
+.el\{\
+. ds -- \|\(em\|
+. ds PI \(*p
+. ds L" ``
+. ds R" ''
+'br\}
+.\"
+.\" If the F register is turned on, we'll generate index entries on stderr for
+.\" titles (.TH), headers (.SH), subsections (.Sh), items (.Ip), and index
+.\" entries marked with X<> in POD. Of course, you'll have to process the
+.\" output yourself in some meaningful fashion.
+.if \nF \{\
+. de IX
+. tm Index:\\$1\t\\n%\t"\\$2"
+..
+. nr % 0
+. rr F
+.\}
+.\"
+.\" For nroff, turn off justification. Always turn off hyphenation; it makes
+.\" way too many mistakes in technical documents.
+.hy 0
+.if n .na
+.\"
+.\" Accent mark definitions (@(#)ms.acc 1.5 88/02/08 SMI; from UCB 4.2).
+.\" Fear. Run. Save yourself. No user-serviceable parts.
+. \" fudge factors for nroff and troff
+.if n \{\
+. ds #H 0
+. ds #V .8m
+. ds #F .3m
+. ds #[ \f1
+. ds #] \fP
+.\}
+.if t \{\
+. ds #H ((1u-(\\\\n(.fu%2u))*.13m)
+. ds #V .6m
+. ds #F 0
+. ds #[ \&
+. ds #] \&
+.\}
+. \" simple accents for nroff and troff
+.if n \{\
+. ds ' \&
+. ds ` \&
+. ds ^ \&
+. ds , \&
+. ds ~ ~
+. ds /
+.\}
+.if t \{\
+. ds ' \\k:\h'-(\\n(.wu*8/10-\*(#H)'\'\h"|\\n:u"
+. ds ` \\k:\h'-(\\n(.wu*8/10-\*(#H)'\`\h'|\\n:u'
+. ds ^ \\k:\h'-(\\n(.wu*10/11-\*(#H)'^\h'|\\n:u'
+. ds , \\k:\h'-(\\n(.wu*8/10)',\h'|\\n:u'
+. ds ~ \\k:\h'-(\\n(.wu-\*(#H-.1m)'~\h'|\\n:u'
+. ds / \\k:\h'-(\\n(.wu*8/10-\*(#H)'\z\(sl\h'|\\n:u'
+.\}
+. \" troff and (daisy-wheel) nroff accents
+.ds : \\k:\h'-(\\n(.wu*8/10-\*(#H+.1m+\*(#F)'\v'-\*(#V'\z.\h'.2m+\*(#F'.\h'|\\n:u'\v'\*(#V'
+.ds 8 \h'\*(#H'\(*b\h'-\*(#H'
+.ds o \\k:\h'-(\\n(.wu+\w'\(de'u-\*(#H)/2u'\v'-.3n'\*(#[\z\(de\v'.3n'\h'|\\n:u'\*(#]
+.ds d- \h'\*(#H'\(pd\h'-\w'~'u'\v'-.25m'\f2\(hy\fP\v'.25m'\h'-\*(#H'
+.ds D- D\\k:\h'-\w'D'u'\v'-.11m'\z\(hy\v'.11m'\h'|\\n:u'
+.ds th \*(#[\v'.3m'\s+1I\s-1\v'-.3m'\h'-(\w'I'u*2/3)'\s-1o\s+1\*(#]
+.ds Th \*(#[\s+2I\s-2\h'-\w'I'u*3/5'\v'-.3m'o\v'.3m'\*(#]
+.ds ae a\h'-(\w'a'u*4/10)'e
+.ds Ae A\h'-(\w'A'u*4/10)'E
+. \" corrections for vroff
+.if v .ds ~ \\k:\h'-(\\n(.wu*9/10-\*(#H)'\s-2\u~\d\s+2\h'|\\n:u'
+.if v .ds ^ \\k:\h'-(\\n(.wu*10/11-\*(#H)'\v'-.4m'^\v'.4m'\h'|\\n:u'
+. \" for low resolution devices (crt and lpr)
+.if \n(.H>23 .if \n(.V>19 \
+\{\
+. ds : e
+. ds 8 ss
+. ds o a
+. ds d- d\h'-1'\(ga
+. ds D- D\h'-1'\(hy
+. ds th \o'bp'
+. ds Th \o'LP'
+. ds ae ae
+. ds Ae AE
+.\}
+.rm #[ #] #H #V #F C
+.\" ========================================================================
+.\"
+.IX Title "INNMAIL 1"
+.TH INNMAIL 1 "2008-04-06" "INN 2.4.5" "InterNetNews Documentation"
+.SH "NAME"
+innmail \- Simple mail\-sending program
+.SH "SYNOPSIS"
+.IX Header "SYNOPSIS"
+\&\fBinnmail\fR [\fB\-h\fR] [\fB\-s\fR \fIsubject\fR] \fIaddress\fR [\fIaddress\fR ...]
+.SH "DESCRIPTION"
+.IX Header "DESCRIPTION"
+\&\fBinnmail\fR is a Perl script intended to provide the non-interactive
+mail-sending functionality of \fImail\fR\|(1) while avoiding nasty security
+problems. It takes the body of a mail message on standard input and sends
+it to the specified addresses by invoking the value of \fImta\fR in
+\&\fIinn.conf\fR.
+.PP
+At least one address (formatted for the \s-1MTA\s0 specified in \fIinn.conf\fR, if it
+matters) is required. \fBinnmail\fR will sanitize the addresses so that they
+contain only alphanumerics and the symbols \f(CW\*(C`@\*(C'\fR, \f(CW\*(C`.\*(C'\fR, \f(CW\*(C`\-\*(C'\fR, \f(CW\*(C`+\*(C'\fR, \f(CW\*(C`_\*(C'\fR,
+and \f(CW\*(C`%\*(C'\fR.
+.PP
+\&\fBinnmail\fR was written to be suitable for the \fImailcmd\fR setting in
+\&\fIinn.conf\fR.
+.SH "OPTIONS"
+.IX Header "OPTIONS"
+.IP "\fB\-h\fR" 4
+.IX Item "-h"
+Gives usage information.
+.IP "\fB\-s\fR \fIsubject\fR" 4
+.IX Item "-s subject"
+Sets the Subject: header of the message. A warning is issued if this
+option is omitted.
+.SH "EXAMPLES"
+.IX Header "EXAMPLES"
+This sends a one-line message to the local user \f(CW\*(C`joe\*(C'\fR:
+.PP
+.Vb 1
+\& echo "A one\-line message." | innmail \-s "Simple message" joe
+.Ve
+.PP
+\&\fBinnmail\fR by default is used by \s-1INN\s0 for sending nightly reports and
+control message reports.
+.SH "BUGS"
+.IX Header "BUGS"
+\&\fBinnmail\fR fails on addresses that begin with \f(CW\*(C`\-\*(C'\fR, although one might
+hope that the news server will not need to contact any such addresses.
+.PP
+There are many \*(L"correct\*(R" addresses that will be silently modified by the
+sanitization process. A news administrator should be careful to use
+particularly sane addresses if they may be passed to \fBinnmail\fR.
+.SH "HISTORY"
+.IX Header "HISTORY"
+\&\fBinnmail\fR was written by James Brister <brister@vix.com> for
+InterNetNews. This manual page was originally written by Jeffrey
+M. Vinocur.
+.SH "SEE ALSO"
+.IX Header "SEE ALSO"
+\&\fIinn.conf\fR\|(5), \fImail\fR\|(1).
--- /dev/null
+.TH innreport 8
+.SH NAME
+innreport \- summarize INN log files.
+.SH SYNOPSIS
+innreport -f innreport.conf [ -[no]options ] [ logfiles ]
+.SH DESCRIPTION
+.I Innreport
+is a
+.IR perl (1)
+script that summarizes INN log files. It is normally invoked by
+.IR scanlogs (8).
+Supported programs are
+.IR innd (8),
+.IR innfeed (1),
+.IR innxmit (8),
+.I nntplink,
+.IR nnrpd (8),
+.IR batcher (8),
+.IR rnews (1),
+.IR crosspost (8)
+and a few others.
+.SH OPTIONS
+There are lots of 'em. Run innreport with ``\-h'' or ``\-help'' to get full
+details.
+.SH HISTORY
+Written by Fabien Tassin <fta@sofaraway.org> for InterNetNews.
+.de R$
+This is revision \\$3, dated \\$4.
+..
+.R$ $Id: innreport.8 4320 2001-01-12 16:57:49Z kondou $
+
+.SH "SEE ALSO"
+innd(8),
+innfeed(8),
+innxmit(8),
+news.daily(8),
+newslog(5),
+nnrpd(8),
+scanlogs(8),
+writelog(8).
--- /dev/null
+.TH INNSTAT 8
+.SH NAME
+innstat \- print snapshot of INN system
+.SH SYNOPSIS
+.B innstat
+.SH DESCRIPTION
+The
+.I innstat
+script prints a snapshot of the INN system.
+It displays the operating mode of the server,
+as well as disk usage and the status of all log and lock files.
+.SH HISTORY
+Written by Landon Curt Noll <chongo@toad.com> and Rich $alz
+<rsalz@uunet.uu.net> for InterNetNews.
+.de R$
+This is revision \\$3, dated \\$4.
+..
+.R$ $Id: innstat.8 584 1998-04-09 15:16:17Z mibsoft $
+.SH "SEE ALSO"
+innd(8),
+news.daily(8),
+newslog(5),
+nnrpd(8)
--- /dev/null
+.\" Automatically generated by Pod::Man v1.37, Pod::Parser v1.32
+.\"
+.\" Standard preamble:
+.\" ========================================================================
+.de Sh \" Subsection heading
+.br
+.if t .Sp
+.ne 5
+.PP
+\fB\\$1\fR
+.PP
+..
+.de Sp \" Vertical space (when we can't use .PP)
+.if t .sp .5v
+.if n .sp
+..
+.de Vb \" Begin verbatim text
+.ft CW
+.nf
+.ne \\$1
+..
+.de Ve \" End verbatim text
+.ft R
+.fi
+..
+.\" Set up some character translations and predefined strings. \*(-- will
+.\" give an unbreakable dash, \*(PI will give pi, \*(L" will give a left
+.\" double quote, and \*(R" will give a right double quote. \*(C+ will
+.\" give a nicer C++. Capital omega is used to do unbreakable dashes and
+.\" therefore won't be available. \*(C` and \*(C' expand to `' in nroff,
+.\" nothing in troff, for use with C<>.
+.tr \(*W-
+.ds C+ C\v'-.1v'\h'-1p'\s-2+\h'-1p'+\s0\v'.1v'\h'-1p'
+.ie n \{\
+. ds -- \(*W-
+. ds PI pi
+. if (\n(.H=4u)&(1m=24u) .ds -- \(*W\h'-12u'\(*W\h'-12u'-\" diablo 10 pitch
+. if (\n(.H=4u)&(1m=20u) .ds -- \(*W\h'-12u'\(*W\h'-8u'-\" diablo 12 pitch
+. ds L" ""
+. ds R" ""
+. ds C` ""
+. ds C' ""
+'br\}
+.el\{\
+. ds -- \|\(em\|
+. ds PI \(*p
+. ds L" ``
+. ds R" ''
+'br\}
+.\"
+.\" If the F register is turned on, we'll generate index entries on stderr for
+.\" titles (.TH), headers (.SH), subsections (.Sh), items (.Ip), and index
+.\" entries marked with X<> in POD. Of course, you'll have to process the
+.\" output yourself in some meaningful fashion.
+.if \nF \{\
+. de IX
+. tm Index:\\$1\t\\n%\t"\\$2"
+..
+. nr % 0
+. rr F
+.\}
+.\"
+.\" For nroff, turn off justification. Always turn off hyphenation; it makes
+.\" way too many mistakes in technical documents.
+.hy 0
+.if n .na
+.\"
+.\" Accent mark definitions (@(#)ms.acc 1.5 88/02/08 SMI; from UCB 4.2).
+.\" Fear. Run. Save yourself. No user-serviceable parts.
+. \" fudge factors for nroff and troff
+.if n \{\
+. ds #H 0
+. ds #V .8m
+. ds #F .3m
+. ds #[ \f1
+. ds #] \fP
+.\}
+.if t \{\
+. ds #H ((1u-(\\\\n(.fu%2u))*.13m)
+. ds #V .6m
+. ds #F 0
+. ds #[ \&
+. ds #] \&
+.\}
+. \" simple accents for nroff and troff
+.if n \{\
+. ds ' \&
+. ds ` \&
+. ds ^ \&
+. ds , \&
+. ds ~ ~
+. ds /
+.\}
+.if t \{\
+. ds ' \\k:\h'-(\\n(.wu*8/10-\*(#H)'\'\h"|\\n:u"
+. ds ` \\k:\h'-(\\n(.wu*8/10-\*(#H)'\`\h'|\\n:u'
+. ds ^ \\k:\h'-(\\n(.wu*10/11-\*(#H)'^\h'|\\n:u'
+. ds , \\k:\h'-(\\n(.wu*8/10)',\h'|\\n:u'
+. ds ~ \\k:\h'-(\\n(.wu-\*(#H-.1m)'~\h'|\\n:u'
+. ds / \\k:\h'-(\\n(.wu*8/10-\*(#H)'\z\(sl\h'|\\n:u'
+.\}
+. \" troff and (daisy-wheel) nroff accents
+.ds : \\k:\h'-(\\n(.wu*8/10-\*(#H+.1m+\*(#F)'\v'-\*(#V'\z.\h'.2m+\*(#F'.\h'|\\n:u'\v'\*(#V'
+.ds 8 \h'\*(#H'\(*b\h'-\*(#H'
+.ds o \\k:\h'-(\\n(.wu+\w'\(de'u-\*(#H)/2u'\v'-.3n'\*(#[\z\(de\v'.3n'\h'|\\n:u'\*(#]
+.ds d- \h'\*(#H'\(pd\h'-\w'~'u'\v'-.25m'\f2\(hy\fP\v'.25m'\h'-\*(#H'
+.ds D- D\\k:\h'-\w'D'u'\v'-.11m'\z\(hy\v'.11m'\h'|\\n:u'
+.ds th \*(#[\v'.3m'\s+1I\s-1\v'-.3m'\h'-(\w'I'u*2/3)'\s-1o\s+1\*(#]
+.ds Th \*(#[\s+2I\s-2\h'-\w'I'u*3/5'\v'-.3m'o\v'.3m'\*(#]
+.ds ae a\h'-(\w'a'u*4/10)'e
+.ds Ae A\h'-(\w'A'u*4/10)'E
+. \" corrections for vroff
+.if v .ds ~ \\k:\h'-(\\n(.wu*9/10-\*(#H)'\s-2\u~\d\s+2\h'|\\n:u'
+.if v .ds ^ \\k:\h'-(\\n(.wu*10/11-\*(#H)'\v'-.4m'^\v'.4m'\h'|\\n:u'
+. \" for low resolution devices (crt and lpr)
+.if \n(.H>23 .if \n(.V>19 \
+\{\
+. ds : e
+. ds 8 ss
+. ds o a
+. ds d- d\h'-1'\(ga
+. ds D- D\h'-1'\(hy
+. ds th \o'bp'
+. ds Th \o'LP'
+. ds ae ae
+. ds Ae AE
+.\}
+.rm #[ #] #H #V #F C
+.\" ========================================================================
+.\"
+.IX Title "INNUPGRADE 8"
+.TH INNUPGRADE 8 "2008-04-06" "INN 2.4.5" "InterNetNews Documentation"
+.SH "NAME"
+innupgrade \- Upgrade INN configuration files
+.SH "SYNOPSIS"
+.IX Header "SYNOPSIS"
+\&\fBinnupgrade\fR \fIdirectory\fR
+.PP
+\&\fBinnupgrade\fR [\fB\-t\fR \fItype\fR] \fB\-f\fR \fIfile\fR
+.SH "DESCRIPTION"
+.IX Header "DESCRIPTION"
+\&\fBinnupgrade\fR is intended to be run during a major upgrade of \s-1INN\s0 to fix
+the configuration files with any required changes. If given a directory,
+it will scan that directory for any files that it has updates defined for,
+try to perform those updates, and replace the files with updated versions
+if applying the updates resulted in any changes. The old versions of the
+files will be saved with a \f(CW\*(C`.OLD\*(C'\fR extension.
+.PP
+If the \fB\-f\fR flag is used, only that file will be updated. If the file
+name doesn't match the standard file name of an \s-1INN\s0 configuration file,
+the optional \fB\-t\fR flag may be given to specify the type. See
+\&\*(L"\s-1EXAMPLES\s0\*(R" for an example of this.
+.PP
+Currently, \fBinnupgrade\fR knows how to apply the following updates:
+.IP "\(bu" 2
+\&\fIinn.conf\fR: Quote values with whitespace and comment out keys with no
+values, required for the change in configuration parsers introduced in \s-1INN\s0
+2.4. The new format is not backward compatible with the previous parser,
+since the previous parser will include the double-quotes in the value of
+the parameter.
+.PP
+Normally, \fBinnupgrade\fR should be run on the \fIpathetc\fR directory after
+any upgrade of \s-1INN\s0 other than a patch release (any upgrade that changes
+the first or second version numbers). This may occur automatically during
+the upgrade process.
+.SH "OPTIONS"
+.IX Header "OPTIONS"
+.IP "\fB\-f\fR \fIfile\fR" 4
+.IX Item "-f file"
+Only act on \fIfile\fR rather than working on an entire directory.
+.IP "\fB\-t\fR \fItype\fR" 4
+.IX Item "-t type"
+For a file specified with \fB\-f\fR, parse it and upgrade it as if it were
+named \fItype\fR. Used for upgrading files with the same syntax as normal
+\&\s-1INN\s0 configuration files but with different names. Only makes sense in
+combination with \fB\-f\fR.
+.SH "EXAMPLES"
+.IX Header "EXAMPLES"
+Upgrade any configuration files found in \fI/usr/local/news/etc\fR:
+.PP
+.Vb 1
+\& innupgrade /usr/local/news/etc
+.Ve
+.PP
+Upgrade only \fI/news/etc/inn.conf\fR:
+.PP
+.Vb 1
+\& innupgrade \-f /news/etc/inn.conf
+.Ve
+.PP
+Upgrade a file named \fIinn\-special.conf\fR that should have the same syntax
+as \fIinn.conf\fR:
+.PP
+.Vb 1
+\& innupgrade \-t inn.conf \-f inn\-special.conf
+.Ve
+.PP
+Any upgrade rules that apply to \fIinn.conf\fR will be applied to the
+alternate file.
+.SH "HISTORY"
+.IX Header "HISTORY"
+Written by Russ Allbery <rra@stanford.edu> for InterNetNews.
+.PP
+$Id: innupgrade.8 7880 2008-06-16 20:37:13Z iulius $
--- /dev/null
+.TH INNWATCH 8
+.SH NAME
+innwatch \- monitor innd.
+.SH SYNOPSIS
+.B innwatch
+[
+.BI -l " logfile"
+]
+[
+.BI -t " seconds"
+]
+.SH DESCRIPTION
+.I Innwatch
+is normally started by
+.IR rc.news .
+It periodically \(em every
+.I <innwatchsleeptime in inn.conf>
+seconds \(em examines the load average, and the number of free blocks
+and inodes on the spool partition, as described by its
+control file,
+.IR innwatch.ctl .
+.PP
+If the load gets too high, or the disk gets too full, it throttles the server.
+When the condition restores, it unblocks the server.
+In addition, on each pass through the loop it will check the
+logfile
+.I <pathlog in inn.conf>/news.crit
+to see if it has been modified, and send mail to the news administrator
+if so.
+.PP
+Upon receipt of an interrupt signal (SIGINT),
+.IR innwatch
+will report its status in the file
+.IR <pathrun\ in\ inn.conf>/innwatch.status .
+.SH OPTIONS
+.TP
+.B \-l logfile
+To specify a log file to watch, other than the default of
+.IR news.crit ,
+use the ``\fB\-l\fP'' flag.
+.TP
+.B \-t seconds
+To change the period between checks from the default from
+.I inn.conf ,
+use the ``\fB\-t\fP''
+flag.
+.SH HISTORY
+Written by Mike Cooper <mcooper@usc.edu>, with modifications by
+<kre@munnari.oz.au>, Steve Groom <stevo@elroy.Jpl.Nasa.Gov> and
+Christophe Wolfhugel <wolf@pasteur.fr>.
+.de R$
+This is revision \\$3, dated \\$4.
+..
+.R$ $Id: innwatch.8 5909 2002-12-03 05:17:18Z vinocur $
+.SH "SEE ALSO"
+ctlinnd(8),
+inn.conf(5),
+innwatch.ctl(5),
+shlock(1).
--- /dev/null
+.\" $Revision: 5909 $
+.TH INNWATCH.CTL 5
+.SH NAME
+innwatch.ctl \- control Usenet supervision by innwatch
+.SH DESCRIPTION
+The file
+.I <pathetc in inn.conf>/innwatch.ctl
+is used to determine what actions are taken during the periodic
+supervisions by
+.IR innwatch .
+.PP
+The file consists of a series of lines; blank lines and lines beginning
+with a number sign (``#'') are ignored.
+All other lines consist of seven fields, each preceded by a delimiting
+character, for example:
+.sp 1
+.nf
+.RS
+:label:state:condition:test:limit:command:reason
+.RE
+or
+.RS
+@label@state@condition@test@limit@command@reason
+.RE
+.fi
+.PP
+The delimiter can be any one of several non-alphanumeric characters that does
+not appear elsewhere in the line; there is no way to quote it to
+include it in any of the fields.
+Any of ``!'', ``,'', ``:'', ``@'', ``;'', or ``?'' is a good choice.
+Each line can have a different delimiter; the first character on each line
+is the delimiter for that line.
+White space surrounding delimiters, except before the first, is ignored,
+and does not form part of the fields; white space within fields is
+permitted.
+All delimiters must be present.
+.PP
+The first field is a label for this control line.
+It is used as an internal state indicator and in
+.I ctlinnd
+messages to control the server.
+If this field is empty, the line number is used.
+.PP
+The second field specifies when this control line should be used.
+It consists of a list of labels
+and special indicators,
+separated by whitespace.
+If the current state matches against any of the labels in this field,
+this line will be used as described below.
+The values that may be used are:
+.IP "\-"
+This line matches if the current state is the same as the label on
+this line, or if the current state is ``run'', the initial state.
+This is also the default state if this field is empty.
+.IP "+"
+This line matches if the current state is ``run''.
+.IP "*"
+This line always matches.
+.IP "label"
+This line matches if the current state is the specified ``label''.
+.IP "\-label"
+This line matches if the current state is not the specified ``label''.
+.PP
+The third field specifies a shell command that is invoked if this line matches.
+Do not use any shell filename expansion characters such as ``*'', ``?'',
+or ``['' (even quoted, they're not likely to work as intended).
+If the command succeeds, as indicated by its exit status, it is expected
+to have printed a single integer to standard output.
+This gives the value of this control line, to be used below.
+If the command fails, the line is ignored.
+The command is executed with its current directory set to the news spool
+articles directory,
+.IR <patharticles\ in\ inn.conf> .
+.PP
+The fourth field specifies the operator to use to test the value returned above.
+It should be one of the two letter numeric test operators defined in
+.IR test (1)
+such as ``eq'', ``lt'' and the like.
+The leading dash (``\-'') should not be included.
+.PP
+The fifth field specifies a constant with which to compare the value using
+the operator just defined.
+This is done by invoking the command:
+.sp 1
+.RS
+test value -operator constant
+.RE
+.sp 1
+The line is said to ``succeed'' if it returns true.
+.PP
+The sixth field specifies what should be done if the line succeeds,
+and in some cases if it fails.
+Any of the following words may be used:
+.IP throttle
+Causes
+.I innwatch
+to throttle the server if this line succeeds.
+It also sets the state to the value of the line's label.
+If the line fails, and the state was previously equal to
+the label on this line (that is, this line had previously succeeded),
+then a
+.I go
+command will be sent to the server, and
+.I innwatch
+will return to the ``run'' state.
+The ``throttle'' is only performed if the current state is ``run'' or a
+state other than the label of this line, regardless of whether the command
+succeeds.
+.IP pause
+Is identical to ``throttle'' except that the server is paused.
+.IP shutdown
+Sends a ``shutdown'' command to the server.
+It is for emergency use only.
+.IP flush
+Sends a ``flush'' command to the server.
+.IP go
+Causes
+.I innwatch
+to send a ``go'' command to the server and to set the state to ``run''.
+.IP exit
+Causes
+.I innwatch
+to exit.
+.PP
+.IP skip
+The remainder of the control file is skipped for the current pass.
+.PP
+The last field specifies the reason that is used in those
+.I ctlinnd
+commands that require one.
+More strictly, it is part of the reason \(em
+.I innwatch
+appends some information to it.
+In order to enable other sites to recognize the state of the local
+.I innd
+server, this field should usually be set to one of several standard
+values.
+Use ``No\ space'' if the server is rejecting articles because of a lack
+of filesystem resources.
+Use ``loadav'' if the server is rejecting articles because of a lack
+of CPU resources.
+.PP
+Once
+.I innwatch
+has taken some action as a consequence of its control line, it skips the
+rest of the control file for this pass.
+If the action was to restart the server (that is, issue a ``go'' command),
+then the next pass will commence almost immediately, so that
+.I innwatch
+can discover any other condition that may mean that the server should
+be suspended again.
+.SH EXAMPLES
+.RS
+.nf
+@@@inndf .@lt@10000@throttle@No space
+@@@inndf -i .@lt@1000@throttle@No space (inodes)
+.fi
+.RE
+.PP
+The first line causes the server to be throttled if the free space drops
+below 10000 units
+(using whatever units
+.IR inndf (8)
+uses), and restarted again when free space increases above the threshold.
+.PP
+The second line does the same for inodes.
+.PP
+The next three lines act as a group and should
+appear in the following order.
+It is easier to explain them, however, if they are described from the last up.
+.PP
+.RS
+.nf
+!load!load hiload!loadavg!lt!5!go!
+:hiload:+ load:loadavg:gt:8:throttle:loadav
+/load/+/loadavg/ge/6/pause/loadav
+.fi
+.RE
+.PP
+The final line causes the server to be paused if
+.I innwatch
+is in the ``run'' state and the load average rises to, or above, six.
+The state is set to ``load'' when this happens.
+The previous line causes the server to be throttled when
+.I innwatch
+is in the ``run'' or ``load'' state, and the load average rises above eight.
+The state is set to ``hiload'' when this happens.
+Note that
+.I innwatch
+can switch the server from ``paused'' to ``throttled'' if the load average
+rises from below six to between six and seven, and then to above eight.
+The first line causes the server to be sent a ``go'' command if
+.I innwatch
+is in the ``load'' or ``hiload'' state, and the load average drops below five.
+.PP
+Note that all three lines assume a mythical command
+.I loadavg
+that is assumed to print the current load average as an integer.
+In more practical circumstances, a pipe of
+.I uptime
+into
+.I awk
+is more likely to be useful.
+.SH BUGS
+This file must be tailored for each individual site, the sample supplied
+is truly no more than a sample.
+The file should be ordered so that the more common problems are tested first.
+.PP
+The ``run'' state is not actually identified by the label with that three
+letter name, and using it will not work as expected.
+.PP
+Using an ``unusual'' character for the delimiter such as ``('', ``*'',
+``&'', ``\(ga'', ``\(aa'', and the like, is likely to lead to obscure and
+hard to locate bugs.
+.SH HISTORY
+Written by <kre@munnari.oz.au> for InterNetNews.
+.de R$
+This is revision \\$3, dated \\$4.
+..
+.R$ $Id: innwatch.ctl.5 5909 2002-12-03 05:17:18Z vinocur $
+.SH "SEE ALSO"
+inn.conf(5),
+innd(8),
+inndf(8),
+ctlinnd(8),
+news.daily(8).
--- /dev/null
+.TH INNXBATCH 8
+.SH NAME
+innxbatch \- send xbatched Usenet articles to a remote NNTP server
+.SH SYNOPSIS
+.I innxbatch
+[
+.B \-D
+]
+[
+.BI \-t " timeout"
+]
+[
+.BI \-T " timeout"
+]
+[
+.B \-v
+]
+.I host
+.I file ...
+.SH DESCRIPTION
+.I Innxbatch
+connects to the NNTP server at the specified
+.I host
+and sends it the specified xbatch files, using the XBATCH extension to
+the NNTP protocol. It is normally invoked by a script run out of
+.IR cron (8)
+that uses
+.IR shlock (1)
+to lock the host name, followed by a
+.IR ctlinnd (8)
+command to flush the batchfile.
+.PP
+Each file is removed after it has been successfully transferred.
+.PP
+If a communication error such as a
+.IR write (2)
+failure, or an unexpected reply from the remote server occurs,
+.I innxbatch
+will stop sending and leave all remaining files untouched for later retry.
+
+
+.SH OPTIONS
+.TP
+.B \-t seconds
+.I Innxbatch
+normally blocks until the connection is made.
+To specify a timeout on how long to try to make the connection, use
+the ``\-t'' flag.
+.TP
+.B \-T seconds
+To specify the total amount of time that should be allowed for article
+transfers, use the ``\-T'' flag.
+.br
+The default is to wait until an I/O error occurs, or all the articles have
+been transferred. If the ``\-T'' flag is used, the time is checked
+just before each article is started; it will not abort a transfer that
+is in progress.
+.TP
+.B \-v
+Upon exit,
+.I innxbatch
+reports transfer and CPU usage statistics via
+.IR syslog (3).
+If the ``\-v'' flag is used, they will also be printed on the standard
+output.
+.TP
+.B \-D
+Use the ``\-D'' flag to print debugging information on standard error.
+This will show the protocol transactions between
+.I innxbatch
+and the NNTP server on the remote host.
+.SH EXAMPLES
+A sample
+.I newsfeeds(5)
+entry to produce appropriate xbatch files (thanks to Karsten Leipold
+<poldi@dfn.de>):
+.sp 1
+.nf
+ nase\e
+ :*\e
+ :Tc,Wnb\e
+.ds R$ <pathbin in inn.conf>
+ :\*(R$/batcher \e
+.ds R$ <$ac_cv_path_COMPRESS in config.cache>
+.ds P$ <pathoutgoing in inn.conf>
+ -p "(\*(R$ >\e
+ \*(P$/nase.\e$\e$)" \e
+ nase.do.main
+.fi
+.sp 1
+A sample script to invoke
+.I innxbatch(8)
+is:
+.sp 1
+.nf
+ #!/bin/sh
+ ## SH script to send xbatches for a site, wrapped around innxbatch
+ ## Invocation:
+ ## sendxbatches.sh <sitename> <hostname> <xbatch file name> ...
+
+ if [ $# -le 3 ]
+ then
+ echo "usage: $0 <sitename> <hostname> <xbatch file name>"
+ exit 1
+ fi
+
+ . <pathbin in inn.conf>/innshellvars
+
+ site="$1"; host="$2"; shift; shift
+
+ ctlinnd flush "$site" \e
+ && sleep 5 \e
+ && exec $NEWSBIN/innxbatch -v -D "$host" $*
+.fi
+.SH HISTORY
+Written by Stefan Petri <petri@ibr.cs.tu-bs.de>, modelled after
+.IR innxmit (8)
+and the XBATCH patch for the nntp reference implementation.
+.SH "SEE ALSO"
+ctlinnd(8),
+inn.conf(5),
+innd(8),
+innxmit(8),
+newsfeeds(5),
+nntpsend(8),
+shlock(1).
--- /dev/null
+.\" $Revision: 5909 $
+.TH INNXMIT 8
+.SH NAME
+innxmit \- send Usenet articles to a remote NNTP server
+.SH SYNOPSIS
+.I innxmit
+[
+.B \-a
+]
+[
+.B \-c
+]
+[
+.B \-d
+]
+[
+.B \-H
+]
+[
+.B \-l
+]
+[
+.BI \-P " portnum"
+]
+[
+.B \-p
+]
+[
+.B \-r
+]
+[
+.B \-s
+]
+[
+.BI \-T " timeout"
+]
+[
+.BI \-t " timeout"
+]
+.I host
+.I file
+.SH DESCRIPTION
+.I Innxmit
+connects to the NNTP server at the specified
+.I host
+(validating itself via
+.IR passwd.nntp
+if possible)
+and sends it the articles specified in the batchfile named
+.IR file .
+It is normally invoked by a script run out of
+.IR cron (8)
+that uses
+.IR shlock (1)
+to lock the host name, followed by a
+.IR ctlinnd (8)
+command to flush the batchfile.
+.PP
+If the
+.I file
+is not an absolute pathname, it is taken relative to the
+.I <pathoutgoing in inn.conf>
+directory.
+It is normally written by specifying the ``Wnm'' flags in the
+.I newsfeeds
+file.
+Each line in the batchfile should be in one of the following formats:
+.PP
+.RS
+.nf
+token Message-ID
+token
+.fi
+.RE
+.PP
+The
+.I token
+field names the article to be sent.
+If the
+.I Message-ID
+field is not specified, it will be obtained by scanning the article.
+The
+.I token
+and
+.I Message-Id
+fields are separated by a space.
+.PP
+If a communication error such as a
+.IR write (2)
+failure occurs,
+.I innxmit
+will stop sending and rewrite the batchfile to contain the current
+article and any other unsent articles.
+.SH OPTIONS
+.TP
+.B \-a
+If all articles were sent successfully,
+.I innxmit
+will remove the batchfile; otherwise it will rewrite it to contain the
+list of unsent articles.
+If no articles were sent or rejected, the file is left untouched.
+This can cause the batchfile to grow excessively large if many articles
+have been expired and there are communication problems.
+To always rewrite the batchfile, use the ``\fB\-a\fP'' flag.
+.TP
+.B \-c
+In streaming mode, a check of each message ID is still made to avoid sending
+articles already on the server.
+The ``\fB\-c\fP'' flag will, if streaming mode is supported,
+result in sending articles without checking.
+This results in slightly greater throughput and may be appropriate when
+it is known that the site could not already have the articles such as in
+the case of a "leaf" site.
+.TP
+.B \-d
+Use the ``\fB\-d\fP'' flag to print debugging information on standard error.
+This will show the protocol transactions between
+.I innxmit
+and the NNTP server on the remote host.
+.TP
+.B \-H
+If the ``\fB\-H\fP'' flag is given, then only headers are sent to
+.I host
+for all articles except control messages.
+And Bytes: header is also included even if it does not exist in the original
+article. ``\fB\-H\fP'' flag is useful for diablo reader.
+.TP
+.B \-l
+The ``\fB\-l\fP'' flag is used to turn on logging of reasons the remote gives
+for rejecting an article.
+.TP
+.B \-P portnum
+To specify a port number other than the default, use the ``\fB\-P\fP'' flag.
+.TP
+.B \-p
+If the ``\fB\-p\fP'' flag is given, then no connection is made and the batchfile
+is purged of entries that refer to files that no longer exist.
+This implies the ``\fB\-a\fP'' flag.
+.TP
+.B \-r
+If the remote server sends an unexpected reply code,
+.I innxmit
+will requeue the article and proceed.
+Use the ``\fB\-r\fP'' flag if the article should not be requeued.
+.TP
+.B \-s
+.I Innxmit
+will attempt to negotiate a streaming mode extension of the NNTP
+protocol with the server at connect time.
+If successful it will use a slightly different protocol that enhances
+throughput.
+If the server does not recognize the streaming mode negotiation
+.I innxmit
+will revert to normal NNTP transfer mode.
+Use the ``\fB\-s\fP'' flag to disable the attempt to negotiate the streaming
+mode extension.
+.TP
+.B \-T seconds
+To specify the total amount of time that should be allowed for article
+transfers, use the ``\fB\-T\fP'' flag.
+The default is to wait until an I/O error occurs, or all the articles have
+been transferred.
+If the ``\fB\-T\fP'' flag is used, the time is checked just before each
+article is started; it will not abort a transfer that is in progress.
+.TP
+.B \-t seconds
+.I Innxmit
+normally blocks until the connection is made.
+To specify a timeout on how long to try to make the connection, use
+the ``\fB\-t\fP''
+flag.
+.TP
+.B \-v
+Upon exit,
+.I innxmit
+reports transfer and CPU usage statistics via
+.IR syslog (3).
+If the ``\fB\-v\fP'' flag is used, they will also be printed on the standard
+output.
+.SH HISTORY
+Written by Rich $alz <rsalz@uunet.uu.net> for InterNetNews.
+.de R$
+This is revision \\$3, dated \\$4.
+..
+.R$ $Id: innxmit.8 5909 2002-12-03 05:17:18Z vinocur $
+.SH "SEE ALSO"
+ctlinnd(8),
+inn.conf(5),
+innd(8),
+newsfeeds(5),
+shlock(1).
--- /dev/null
+.\" Automatically generated by Pod::Man v1.37, Pod::Parser v1.32
+.\"
+.\" Standard preamble:
+.\" ========================================================================
+.de Sh \" Subsection heading
+.br
+.if t .Sp
+.ne 5
+.PP
+\fB\\$1\fR
+.PP
+..
+.de Sp \" Vertical space (when we can't use .PP)
+.if t .sp .5v
+.if n .sp
+..
+.de Vb \" Begin verbatim text
+.ft CW
+.nf
+.ne \\$1
+..
+.de Ve \" End verbatim text
+.ft R
+.fi
+..
+.\" Set up some character translations and predefined strings. \*(-- will
+.\" give an unbreakable dash, \*(PI will give pi, \*(L" will give a left
+.\" double quote, and \*(R" will give a right double quote. \*(C+ will
+.\" give a nicer C++. Capital omega is used to do unbreakable dashes and
+.\" therefore won't be available. \*(C` and \*(C' expand to `' in nroff,
+.\" nothing in troff, for use with C<>.
+.tr \(*W-
+.ds C+ C\v'-.1v'\h'-1p'\s-2+\h'-1p'+\s0\v'.1v'\h'-1p'
+.ie n \{\
+. ds -- \(*W-
+. ds PI pi
+. if (\n(.H=4u)&(1m=24u) .ds -- \(*W\h'-12u'\(*W\h'-12u'-\" diablo 10 pitch
+. if (\n(.H=4u)&(1m=20u) .ds -- \(*W\h'-12u'\(*W\h'-8u'-\" diablo 12 pitch
+. ds L" ""
+. ds R" ""
+. ds C` ""
+. ds C' ""
+'br\}
+.el\{\
+. ds -- \|\(em\|
+. ds PI \(*p
+. ds L" ``
+. ds R" ''
+'br\}
+.\"
+.\" If the F register is turned on, we'll generate index entries on stderr for
+.\" titles (.TH), headers (.SH), subsections (.Sh), items (.Ip), and index
+.\" entries marked with X<> in POD. Of course, you'll have to process the
+.\" output yourself in some meaningful fashion.
+.if \nF \{\
+. de IX
+. tm Index:\\$1\t\\n%\t"\\$2"
+..
+. nr % 0
+. rr F
+.\}
+.\"
+.\" For nroff, turn off justification. Always turn off hyphenation; it makes
+.\" way too many mistakes in technical documents.
+.hy 0
+.if n .na
+.\"
+.\" Accent mark definitions (@(#)ms.acc 1.5 88/02/08 SMI; from UCB 4.2).
+.\" Fear. Run. Save yourself. No user-serviceable parts.
+. \" fudge factors for nroff and troff
+.if n \{\
+. ds #H 0
+. ds #V .8m
+. ds #F .3m
+. ds #[ \f1
+. ds #] \fP
+.\}
+.if t \{\
+. ds #H ((1u-(\\\\n(.fu%2u))*.13m)
+. ds #V .6m
+. ds #F 0
+. ds #[ \&
+. ds #] \&
+.\}
+. \" simple accents for nroff and troff
+.if n \{\
+. ds ' \&
+. ds ` \&
+. ds ^ \&
+. ds , \&
+. ds ~ ~
+. ds /
+.\}
+.if t \{\
+. ds ' \\k:\h'-(\\n(.wu*8/10-\*(#H)'\'\h"|\\n:u"
+. ds ` \\k:\h'-(\\n(.wu*8/10-\*(#H)'\`\h'|\\n:u'
+. ds ^ \\k:\h'-(\\n(.wu*10/11-\*(#H)'^\h'|\\n:u'
+. ds , \\k:\h'-(\\n(.wu*8/10)',\h'|\\n:u'
+. ds ~ \\k:\h'-(\\n(.wu-\*(#H-.1m)'~\h'|\\n:u'
+. ds / \\k:\h'-(\\n(.wu*8/10-\*(#H)'\z\(sl\h'|\\n:u'
+.\}
+. \" troff and (daisy-wheel) nroff accents
+.ds : \\k:\h'-(\\n(.wu*8/10-\*(#H+.1m+\*(#F)'\v'-\*(#V'\z.\h'.2m+\*(#F'.\h'|\\n:u'\v'\*(#V'
+.ds 8 \h'\*(#H'\(*b\h'-\*(#H'
+.ds o \\k:\h'-(\\n(.wu+\w'\(de'u-\*(#H)/2u'\v'-.3n'\*(#[\z\(de\v'.3n'\h'|\\n:u'\*(#]
+.ds d- \h'\*(#H'\(pd\h'-\w'~'u'\v'-.25m'\f2\(hy\fP\v'.25m'\h'-\*(#H'
+.ds D- D\\k:\h'-\w'D'u'\v'-.11m'\z\(hy\v'.11m'\h'|\\n:u'
+.ds th \*(#[\v'.3m'\s+1I\s-1\v'-.3m'\h'-(\w'I'u*2/3)'\s-1o\s+1\*(#]
+.ds Th \*(#[\s+2I\s-2\h'-\w'I'u*3/5'\v'-.3m'o\v'.3m'\*(#]
+.ds ae a\h'-(\w'a'u*4/10)'e
+.ds Ae A\h'-(\w'A'u*4/10)'E
+. \" corrections for vroff
+.if v .ds ~ \\k:\h'-(\\n(.wu*9/10-\*(#H)'\s-2\u~\d\s+2\h'|\\n:u'
+.if v .ds ^ \\k:\h'-(\\n(.wu*10/11-\*(#H)'\v'-.4m'^\v'.4m'\h'|\\n:u'
+. \" for low resolution devices (crt and lpr)
+.if \n(.H>23 .if \n(.V>19 \
+\{\
+. ds : e
+. ds 8 ss
+. ds o a
+. ds d- d\h'-1'\(ga
+. ds D- D\h'-1'\(hy
+. ds th \o'bp'
+. ds Th \o'LP'
+. ds ae ae
+. ds Ae AE
+.\}
+.rm #[ #] #H #V #F C
+.\" ========================================================================
+.\"
+.IX Title "libauth 3"
+.TH libauth 3 "2008-04-06" "INN 2.4.5" "InterNetNews Documentation"
+.SH "NAME"
+libauth \- routines for writing nnrpd resolvers and authenticators
+.SH "SYNOPSIS"
+.IX Header "SYNOPSIS"
+.Vb 1
+\& #include "libauth.h"
+.Ve
+.PP
+.Vb 5
+\& struct res_info {
+\& struct sockaddr *client;
+\& struct sockaddr *local;
+\& char *clienthostname;
+\& };
+.Ve
+.PP
+.Vb 4
+\& struct auth_info {
+\& char *username;
+\& char *password;
+\& };
+.Ve
+.PP
+.Vb 2
+\& struct auth_info *get_auth_info(FILE *);
+\& struct res_info *get_res_info (FILE *);
+.Ve
+.PP
+.Vb 2
+\& void free_auth_info(struct auth_info*);
+\& void free_res_info (struct res_info*);
+.Ve
+.SH "DESCRIPTION"
+.IX Header "DESCRIPTION"
+These functions provide a convenient C frontend to the nnrpd external
+authentication interface documented in \fIdoc/external\-auth\fR. Use of
+this library is \fBnot\fR required; in particular, external resolvers and
+authenticators written in languages other than C will need to implement
+the necessary functionality themselves.
+.PP
+The \fIget_auth_info()\fR and \fIget_res_info()\fR functions allocate sufficient
+memory for a \fBstruct auth_info\fR or \fBstruct res_info\fR and any necessary
+fields, and return a pointer to the struct with the fields filled in
+from information supplied by nnrpd (the \fBFILE*\fR parameter generally
+should be \f(CW\*(C`stdin\*(C'\fR). Both functions return \s-1NULL\s0 on error. The caller
+is responsible for deallocating the memory by using the functions below.
+.PP
+The string fields of both structs are straightforward. The \fBclient\fR
+and \fBlocal\fR fields of \fBstruct res_info\fR actually point to instances of
+\&\fBstruct sockaddr_in\fR (or \fBstruct sockaddr_in6\fR if IPv6 support is
+compiled in).
+.PP
+The \fIfree_auth_info()\fR and \fIfree_res_info()\fR functions free the struct
+passed in as argument and all necessary fields.
+.SH "BUGS"
+.IX Header "BUGS"
+In many cases, nnrpd provides more information than is normally useful
+(for example, even when calling an authenticator, the resolver
+information is often provided.) On the other hand, in certain cases it
+provides less information than might be expected (for example, if nnrpd
+is reading from stdin rather than a socket). The implementation is
+capable of handling at least the first of these issues, but that
+functionality is not exposed in the interface.
+.PP
+At present, \fIlibauth.h\fR and its implementation are located in
+\&\fIauthprogs/\fR; perhaps they should be moved to \fIinclude/\fR and \fIlib/\fR,
+respectively?
+.SH "HISTORY"
+.IX Header "HISTORY"
+Written by Jeffrey M. Vinocur <jeff@litech.org> for InterNetNews.
+.PP
+$Id: libauth.3 7880 2008-06-16 20:37:13Z iulius $
+.SH "SEE ALSO"
+.IX Header "SEE ALSO"
+\&\fInnrpd\fR\|(8), \fIreaders.conf\fR\|(5), \fIdoc/external\-auth\fR
--- /dev/null
+.\" $Revision: 6124 $
+.TH LIBINN 3
+.SH NAME
+libinn \- InterNetNews library routines
+.SH SYNOPSIS
+.nf
+.ta \w' unsigned long 'u
+.B
+#include "libinn.h"
+
+.B "typedef struct _TIMEINFO {"
+.B " time_t time;"
+.B " long usec;"
+.B " long tzone;
+.B "} TIMEINFO;"
+
+.B "char *"
+.B "GenerateMessageID(domain)"
+.B " char *domain;"
+
+.B "void"
+.B "HeaderCleanFrom(from)"
+.B " char *from;"
+
+.B "char *"
+.B "HeaderFind(Article, Header, size)"
+.B " char *Article;"
+.B " char *Header;"
+.B " int size;"
+
+.B "FILE *"
+.B "CAopen(FromServer, ToServer)"
+.B " FILE *FromServer;"
+.B " FILE *ToServer;"
+
+.B "FILE *"
+.B "CAlistopen(FromServer, ToServer, request)"
+.B " FILE *FromServer;"
+.B " FILE *ToServer;"
+.B " char *request;"
+
+.B "void"
+.B "CAclose()"
+
+.B "struct _DDHANDLE *"
+.B "DDstart(FromServer, ToServer)"
+.B " FILE *FromServer;"
+.B " FILE *ToServer;"
+
+.B "void"
+.B "DDcheck(h, group)"
+.B " DDHANDLE *h;"
+.B " char *group;"
+
+.B "char *"
+.B "DDend(h)"
+.B " DDHANDLE *h;"
+
+.B "void"
+.B "close_on_exec(fd, flag)"
+.B " int fd;"
+.B " bool flag;"
+
+.B "int"
+.B "nonblocking(fd, flag)"
+.B " int fd;"
+.B " bool flag;"
+
+.B "bool"
+.B "inn_lock_file(fd, type, flag)"
+.B " int fd;"
+.B " LOCKTYPE type;"
+.B " bool block;"
+
+.B "char *"
+.B "GetFQDN(domain)"
+.B " char *domain;"
+
+.B "char *"
+.B "GetModeratorAddress(FromServer, ToServer, group, moderatormailer)"
+.B " FILE *FromServer;"
+.B " FILE *ToServer;"
+.B " char *group;"
+.B " char *moderatormailer;"
+
+.B "int"
+.B "GetResourceUsage(usertime, systime)"
+.B " double *usertime;"
+.B " double *systime;"
+
+.B "int"
+.B "GetTimeInfo(now)"
+.B " TIMEINFO *now;"
+
+.B "int"
+.B "NNTPlocalopen(FromServerp, ToServerp, errbuff)"
+.B " FILE **FromServerp;"
+.B " FILE **ToServerp;"
+.B " char *errbuff;"
+
+.B "int"
+.B "NNTPremoteopen(port, FromServerp, ToServerp, errbuff)"
+.B " int port;"
+.B " FILE **FromServerp;"
+.B " FILE **ToServerp;"
+.B " char *errbuff;"
+
+.B "int"
+.B "NNTPconnect(host, port, FromServerp, ToServerp, errbuff)"
+.B " char *host;"
+.B " int port;"
+.B " FILE **FromServerp;"
+.B " FILE **ToServerp;"
+.B " char *errbuff;"
+
+.B "int"
+.B "NNTPsendarticle(text, ToServer, Terminate)"
+.B " char *text;"
+.B " FILE *ToServer;"
+.B " int Terminate;"
+
+.B "int"
+.B "NNTPsendpassword(server, FromServer, ToServer)"
+.B " char *server;"
+.B " FILE *FromServer;"
+.B " FILE *ToServer;"
+
+.B "void"
+.B "Radix32(value, p)
+.B " unsigned long value;"
+.B " char *p;"
+
+.B "char *"
+.B "ReadInFile(name, Sbp)"
+.B " char *name;"
+.B " struct stat *Sbp;"
+
+.B "char *"
+.B "ReadInDescriptor(fd, Sbp)"
+.B " int fd;"
+.B " struct stat *Sbp;"
+
+.B "HASH"
+.B "HashMessageID(MessageID)"
+.B " const char *MessageID;"
+.fi
+.SH DESCRIPTION
+.I Libinn
+is a library of utility routines for manipulating Usenet articles and
+related data.
+It is not necessary to use the header file
+.IR libinn.h ;
+if it is not available, it is only necessary to properly declare the
+.I TIMEINFO
+datatype, as given above.
+.PP
+.I GenerateMessageID
+uses the current time, process-ID, and fully-qualified domain name, which is
+passed as an argument and used if local host can not be resolved or it is
+different from ``domain'' set in
+.IR inn.conf ,
+to create a Message-ID header that is highly likely to be unique.
+The returned value points to static space that is reused on subsequent calls.
+.PP
+.I HeaderCleanFrom
+removes the extraneous information from the value of a ``From'' or ``Reply-To''
+header and leaves just the official mailing address.
+In particular, the following transformations are made to the
+.I from
+parameter:
+.RS
+.nf
+.ta \w'stuff <address> 'u
+address --> address
+address (stuff) --> address
+stuff <address> --> address
+.fi
+.RE
+The transformations are simple, based on RFC\ 1036 which limits the format
+of the header.
+.PP
+.I HeaderFind
+searches through
+.I Article
+looking for the specified
+.IR Header .
+.I Size
+should be the length of the header name.
+It returns a pointer to the value of the header, skipping leading whitespace,
+or NULL if the header cannot be found.
+.I Article
+should be a standard C string containing the text of the article; the end
+of the headers is indicated by a blank line \(em two consecutive \en
+characters.
+.PP
+.I CAopen
+and
+.I CAclose
+provide news clients with access to the active file; the ``CA'' stands for
+.IR C lient
+.IR A ctive.
+.I CAopen
+opens the
+.I active
+file for reading.
+It returns a pointer to an open FILE, or NULL on error.
+If a local or NFS-mounted copy exists,
+.I CAopen
+will use that file.
+The
+.I FromServer
+and
+.I ToServer
+parameters should be FILE's connected to the NNTP server for input and
+output, respectively.
+See
+.I NNTPremoteopen
+or
+.IR NNTPlocalopen ,
+below.
+If either parameter is NULL, then
+.I CAopen
+will just return NULL if the file is not locally available.
+If they are not NULL,
+.I CAopen
+will use them to query the NNTP server using
+the ``list'' command to make a local temporary copy.
+.PP
+The
+.I CAlistopen
+sends a ``list'' command to the server and returns a temporary file
+containing the results.
+The
+.I request
+parameter, if not NULL, will be sent as an argument to the command.
+Unlike
+.IR CAopen ,
+this routine will never use a locally-available copy of the active file.
+.PP
+.I CAclose
+closes the active file and removes any temporary file that might have
+been created by
+.I CAopen
+or
+.IR CAlistopen .
+.PP
+.I CloseOnExec
+can make a descriptor ``close-on-exec'' so that it is not shared
+with any child processes.
+If the flag is non-zero, the file is so marked; if zero, the ``close-on-exec''
+mode is cleared.
+.PP
+.IR DDstart ,
+.IR DDcheck ,
+and
+.I DDend
+are used to set the Distribution header; the ``DD'' stands for
+.IR D efault
+.IR D istribution.
+The
+.I distrib.pats
+file is consulted to determine the proper value for the Distribution
+header after all newsgroups have been checked.
+.I DDstart
+begins the parsing.
+It returns a pointer to an opaque handle that should be used on subsequent
+calls.
+The
+.I FromServer
+and
+.I ToServer
+parameters should be FILE's connected to the NNTP server for input and
+output, respectively.
+If either parameter is NULL, then an empty default will ultimately be returned
+if the file is not locally available.
+.PP
+.I DDcheck
+should be called
+with the handle,
+.IR h ,
+returned by
+.I DDstart
+and a newgroups,
+.IR group ,
+to check.
+It can be called as often as necessary.
+.PP
+.I DDend
+releases any state maintained in the handle and returns an allocated copy
+of the text that should be used for the Distribution header.
+.PP
+.I SetNonBlocking
+enables (if
+.I flag
+is non-zero) or disables (if
+.I flag
+is zero) non-blocking I/O on the indicated descriptor.
+It returns \-1 on failure or zero on success.
+.PP
+.I inn_lock_file
+tries to lock the file descriptor
+.IR fd .
+If
+.I block
+is true it will block until the lock can be made, otherwise
+it will return false if the file cannot be locked.
+.I type
+is one of: INN_LOCK_READ, INN_LOCK_WRITE, or INN_LOCK_UNLOCK.
+It returns false on failure or true on success.
+.PP
+.I GetFQDN
+returns the fully-qualified domain name of the local host.
+.I Domain
+is used if local host can not be resolved.
+The returned value points to static space that is reused on subsequent calls,
+or NULL on error.
+.PP
+.I GetModeratorAddress
+returns the mailing address of the moderator for specified
+.I group
+or NULL on error.
+.I Moderatormailer
+is used as its address, if there is no matched moderator.
+See
+.IR moderators (5)
+for details on how the address is determined.
+.I GetModeratorAddress
+does no checking to see if the specified group is actually moderated.
+The returned value points to static space that is reused on subsequent
+calls.
+The
+.I FromServer
+and
+.I ToServer
+parameters should be FILE's connected to the NNTP server for input and
+output, respectively. If either of these parameters is NULL, then an
+attempt to get the list from a local copy is made.
+.PP
+.I GetResourceUsage
+fills in the
+.I usertime
+and
+.I systime
+parameters with the total user and system time used by the current
+process and any children it may have spawned.
+If
+.I <HAVE_GETRUSAGE in include/config.h>
+is defined, it gets the values by doing a
+.IR getrusage (2)
+system call; otherwise it calls
+.IR times (2).
+It returns \-1 on failure, or zero on success.
+.PP
+.I GetTimeInfo
+fills in the
+.I now
+parameter with information about the current time and tzone.
+The ``time'' and ``usec'' fields will be filled in by a call to
+.IR gettimeofday (2)
+if
+.I <$ac_cv_func_gettimeofday in config.cache>
+is ``yes''. Otherwise, the ``time'' field will be filled in by a call to
+.IR time (2),
+and the ``usec'' field will be set to zero.
+The ``tzone'' field will be filled in with the current offset from GMT.
+If
+.I <HAVE_TM_GMTOFF in include/config.h>
+is defined, this is done by calling
+.IR localtime (3)
+and taking the value of the ``tm_gmtoff'' field, negating it, and dividing
+it by 60.
+Otherwise, this is done by calling
+.IR localtime (3)
+and comparing the value with that returned by a call to
+.IR gmtime (3).
+
+For efficiency, the ``tzone'' field is only recalculated if more than an
+hour pass passed since the last time
+.I GetTimeInfo
+has been called.
+This routine returns \-1 on failure, or zero on success.
+.PP
+.I NNTPlocalopen
+opens a connection to the private port of an InterNetNews server running on
+the local host, if
+.I <HAVE_UNIX_DOMAIN_SOCKETS in include/config.h>
+is defined.
+It returns \-1 on failure, or zero on success.
+.I FromServerp
+and
+.I ToServerp
+will be filled in with FILE's which can be used to communicate
+with the server.
+.I Errbuff
+can either be NULL or a pointer to a buffer at least 512 bytes long.
+If not NULL, and the server refuses the connection, then it will be
+filled in with the text of the server's reply.
+This routine is not for general use.
+If
+.I <HAVE_UNIX_DOMAIN_SOCKETS in include/config.h>
+is not defined, this
+is a stub routine, for compatibility with systems that have Unix-domain
+stream sockets.
+It always returns \-1.
+.PP
+.I NNTPremoteopen
+does the same except that it uses ``innconf->server''
+as the local server, and opens a connection to the
+.IR port .
+Any client program can use this routine.
+It returns \-1 on failure, or zero on success.
+.PP
+.I NNTPconnect
+is the same as
+.I NNTPremoteopen
+except that the desired host is given as the
+.I host
+parameter.
+.PP
+.I NNTPsendarticle
+writes
+.I text
+on
+.I ToServer
+using NNTP conventions for line termination.
+The text should consist of one or more lines ending with a newline.
+If
+.I Terminate
+is non-zero, then the routine will also write the NNTP data-termination
+marker on the stream.
+It returns \-1 on failure, or zero on success.
+.PP
+.I NNTPsendpassword
+sends authentication information to an NNTP server by finding the appropriate
+entry in the
+.I passwd.nntp
+file.
+.I Server
+contains the name of the host; ``innconf->server'' will be used if
+.I server
+is NULL.
+.I FromServer
+and
+.I ToServer
+should be FILE's that are connected to the server.
+No action is taken if the specified host is not listed in the password file.
+.PP
+.I Radix32
+converts the number in
+.I value
+into a radix-32 string into the buffer pointed to by
+.IR p .
+The number is split into five-bit pieces and each pieces is converted
+into a character using the alphabet
+.I "0..9a..v"
+to represent the numbers 0..32.
+Only the lowest 32 bits of
+.I value
+are used, so
+.I p
+need only point to a buffer of eight bytes (seven characters and the
+trailing \e0).
+.PP
+.I ReadInFile
+reads the file named
+.I name
+into allocated memory, appending a terminating \e0 byte.
+It returns a pointer to the space, or NULL on error.
+If
+.I Sbp
+is not NULL, it is taken as the address of a place to store the results
+of a
+.IR stat (2)
+call.
+.PP
+.I ReadInDescriptor
+performs the same function as
+.I ReadInFile
+except that
+.I fd
+refers to an already-open file.
+.PP
+.I HashMessageID
+returns hashed message-id using MD5.
+.SH EXAMPLES
+.RS
+.nf
+char *p;
+char *Article;
+char buff[256], errbuff[256];
+FILE *F;
+FILE *ToServer;
+FILE *FromServer;
+int port = 119;
+
+if ((p = HeaderFind(Article, "From", 4)) == NULL)
+ Fatal("Can't find From line");
+(void)strcpy(buff, p);
+HeaderCleanFrom(buff);
+
+if ((F = CAopen(FromServer, ToServer)) == NULL)
+ Fatal("Can't open active file");
+
+/* Don't pass the file on to our children. */
+CloseOnExec(fileno(F), 1);
+
+/* Make a local copy. */
+p = ReadInDescriptor(fileno(F), (struct stat *)NULL);
+
+/* Close the file. */
+CAclose();
+
+if (NNTPremoteopen(port, &FromServer, &ToServer, errbuff) < 0)
+ Fatal("Can't connect to server");
+
+if ((p = GetModeratorAddress("comp.sources.unix")) == NULL)
+ Fatal("Can't find moderator's address");
+.fi
+.RE
+.SH HISTORY
+Written by Rich $alz <rsalz@uunet.uu.net> for InterNetNews.
+.de R$
+This is revision \\$3, dated \\$4.
+..
+.R$ $Id: libinn.3 6124 2003-01-14 06:03:29Z rra $
+.SH "SEE ALSO"
+active(5),
+dbz(3z),
+parsedate(3),
+inn.conf(5),
+inndcomm(3),
+moderators(5),
+passwd.nntp(5).
--- /dev/null
+.\" Automatically generated by Pod::Man v1.37, Pod::Parser v1.32
+.\"
+.\" Standard preamble:
+.\" ========================================================================
+.de Sh \" Subsection heading
+.br
+.if t .Sp
+.ne 5
+.PP
+\fB\\$1\fR
+.PP
+..
+.de Sp \" Vertical space (when we can't use .PP)
+.if t .sp .5v
+.if n .sp
+..
+.de Vb \" Begin verbatim text
+.ft CW
+.nf
+.ne \\$1
+..
+.de Ve \" End verbatim text
+.ft R
+.fi
+..
+.\" Set up some character translations and predefined strings. \*(-- will
+.\" give an unbreakable dash, \*(PI will give pi, \*(L" will give a left
+.\" double quote, and \*(R" will give a right double quote. \*(C+ will
+.\" give a nicer C++. Capital omega is used to do unbreakable dashes and
+.\" therefore won't be available. \*(C` and \*(C' expand to `' in nroff,
+.\" nothing in troff, for use with C<>.
+.tr \(*W-
+.ds C+ C\v'-.1v'\h'-1p'\s-2+\h'-1p'+\s0\v'.1v'\h'-1p'
+.ie n \{\
+. ds -- \(*W-
+. ds PI pi
+. if (\n(.H=4u)&(1m=24u) .ds -- \(*W\h'-12u'\(*W\h'-12u'-\" diablo 10 pitch
+. if (\n(.H=4u)&(1m=20u) .ds -- \(*W\h'-12u'\(*W\h'-8u'-\" diablo 12 pitch
+. ds L" ""
+. ds R" ""
+. ds C` ""
+. ds C' ""
+'br\}
+.el\{\
+. ds -- \|\(em\|
+. ds PI \(*p
+. ds L" ``
+. ds R" ''
+'br\}
+.\"
+.\" If the F register is turned on, we'll generate index entries on stderr for
+.\" titles (.TH), headers (.SH), subsections (.Sh), items (.Ip), and index
+.\" entries marked with X<> in POD. Of course, you'll have to process the
+.\" output yourself in some meaningful fashion.
+.if \nF \{\
+. de IX
+. tm Index:\\$1\t\\n%\t"\\$2"
+..
+. nr % 0
+. rr F
+.\}
+.\"
+.\" For nroff, turn off justification. Always turn off hyphenation; it makes
+.\" way too many mistakes in technical documents.
+.hy 0
+.if n .na
+.\"
+.\" Accent mark definitions (@(#)ms.acc 1.5 88/02/08 SMI; from UCB 4.2).
+.\" Fear. Run. Save yourself. No user-serviceable parts.
+. \" fudge factors for nroff and troff
+.if n \{\
+. ds #H 0
+. ds #V .8m
+. ds #F .3m
+. ds #[ \f1
+. ds #] \fP
+.\}
+.if t \{\
+. ds #H ((1u-(\\\\n(.fu%2u))*.13m)
+. ds #V .6m
+. ds #F 0
+. ds #[ \&
+. ds #] \&
+.\}
+. \" simple accents for nroff and troff
+.if n \{\
+. ds ' \&
+. ds ` \&
+. ds ^ \&
+. ds , \&
+. ds ~ ~
+. ds /
+.\}
+.if t \{\
+. ds ' \\k:\h'-(\\n(.wu*8/10-\*(#H)'\'\h"|\\n:u"
+. ds ` \\k:\h'-(\\n(.wu*8/10-\*(#H)'\`\h'|\\n:u'
+. ds ^ \\k:\h'-(\\n(.wu*10/11-\*(#H)'^\h'|\\n:u'
+. ds , \\k:\h'-(\\n(.wu*8/10)',\h'|\\n:u'
+. ds ~ \\k:\h'-(\\n(.wu-\*(#H-.1m)'~\h'|\\n:u'
+. ds / \\k:\h'-(\\n(.wu*8/10-\*(#H)'\z\(sl\h'|\\n:u'
+.\}
+. \" troff and (daisy-wheel) nroff accents
+.ds : \\k:\h'-(\\n(.wu*8/10-\*(#H+.1m+\*(#F)'\v'-\*(#V'\z.\h'.2m+\*(#F'.\h'|\\n:u'\v'\*(#V'
+.ds 8 \h'\*(#H'\(*b\h'-\*(#H'
+.ds o \\k:\h'-(\\n(.wu+\w'\(de'u-\*(#H)/2u'\v'-.3n'\*(#[\z\(de\v'.3n'\h'|\\n:u'\*(#]
+.ds d- \h'\*(#H'\(pd\h'-\w'~'u'\v'-.25m'\f2\(hy\fP\v'.25m'\h'-\*(#H'
+.ds D- D\\k:\h'-\w'D'u'\v'-.11m'\z\(hy\v'.11m'\h'|\\n:u'
+.ds th \*(#[\v'.3m'\s+1I\s-1\v'-.3m'\h'-(\w'I'u*2/3)'\s-1o\s+1\*(#]
+.ds Th \*(#[\s+2I\s-2\h'-\w'I'u*3/5'\v'-.3m'o\v'.3m'\*(#]
+.ds ae a\h'-(\w'a'u*4/10)'e
+.ds Ae A\h'-(\w'A'u*4/10)'E
+. \" corrections for vroff
+.if v .ds ~ \\k:\h'-(\\n(.wu*9/10-\*(#H)'\s-2\u~\d\s+2\h'|\\n:u'
+.if v .ds ^ \\k:\h'-(\\n(.wu*10/11-\*(#H)'\v'-.4m'^\v'.4m'\h'|\\n:u'
+. \" for low resolution devices (crt and lpr)
+.if \n(.H>23 .if \n(.V>19 \
+\{\
+. ds : e
+. ds 8 ss
+. ds o a
+. ds d- d\h'-1'\(ga
+. ds D- D\h'-1'\(hy
+. ds th \o'bp'
+. ds Th \o'LP'
+. ds ae ae
+. ds Ae AE
+.\}
+.rm #[ #] #H #V #F C
+.\" ========================================================================
+.\"
+.IX Title "libinnhist 3"
+.TH libinnhist 3 "2008-04-06" "INN 2.4.5" "InterNetNews Documentation"
+.SH "NAME"
+his \- routines for managing INN history
+.SH "SYNOPSIS"
+.IX Header "SYNOPSIS"
+\&\fB#include <inn/history.h>\fR
+.PP
+\&\fBstruct history;\fR
+.PP
+\&\fBstruct histstats {\fR
+\&\fB int hitpos;\fR
+\&\fB int hitneg;\fR
+\&\fB int misses;\fR
+\&\fB int dne;\fR
+\&\fB};\fR
+.PP
+\&\fB#define \s-1HIS_RDONLY\s0 ...\fR
+\&\fB#define \s-1HIS_RDWR\s0 ...\fR
+\&\fB#define \s-1HIS_CREAT\s0 ...\fR
+\&\fB#define \s-1HIS_ONDISK\s0 ...\fR
+\&\fB#define \s-1HIS_INCORE\s0 ...\fR
+\&\fB#define \s-1HIS_MMAP\s0 ...\fR
+.PP
+\&\fBenum {\fR
+\&\fB \s-1HISCTLG_PATH\s0,\fR
+\&\fB \s-1HISCTLS_PATH\s0,\fR
+\&\fB \s-1HISCTLS_SYNCCOUNT\s0,\fR
+\&\fB \s-1HISCTLS_NPAIRS\s0,\fR
+\&\fB \s-1HISCTLS_IGNOREOLD\s0,\fR
+\&\fB \s-1HISCTLS_STATINTERVAL\s0\fR
+\&\fB};\fR
+.PP
+\&\fBstruct history *HISopen(const char *\fR\fIpath\fR\fB, const char *\fR\fImethod\fR\fB, int \fR\fIflags\fR\fB);\fR
+.PP
+\&\fBbool HISclose(struct history *\fR\fIhistory\fR\fB);\fR
+.PP
+\&\fBbool HISsync(struct history *\fR\fIhistory\fR\fB);\fR
+.PP
+\&\fBvoid HISsetcache(struct history *\fR\fIhistory\fR\fB, size_t \fR\fIsize\fR\fB);\fR
+.PP
+\&\fBbool HISlookup(struct history *\fR\fIhistory\fR\fB, const char *\fR\fIkey\fR\fB, time_t *\fR\fIarrived\fR\fB, time_t *\fR\fIposted\fR\fB, time_t *\fR\fIexpires\fR\fB, \s-1TOKEN\s0 *\fR\fItoken\fR\fB);\fR
+.PP
+\&\fBbool HIScheck(struct history *\fR\fIhistory\fR\fB, const char *\fR\fIkey\fR\fB);\fR
+.PP
+\&\fBbool HISwrite(struct history *\fR\fIhistory\fR\fB, const char *\fR\fIkey\fR\fB, time_t \fR\fIarrived\fR\fB, time_t \fR\fIposted\fR\fB, time_t \fR\fIexpires\fR\fB, const \s-1TOKEN\s0 *\fR\fItoken\fR\fB);\fR
+.PP
+\&\fBbool HISremember(struct history *\fR\fIhistory\fR\fB, const char *\fR\fIkey\fR\fB, time_t \fR\fIarrived\fR\fB);\fR
+.PP
+\&\fBbool HISreplace(struct history *\fR\fIhistory\fR\fB, const char *\fR\fIkey\fR\fB, time_t \fR\fIarrived\fR\fB, time_t \fR\fIposted\fR\fB, time_t \fR\fIexpires\fR\fB, const \s-1TOKEN\s0 *\fR\fItoken\fR\fB);\fR
+.PP
+\&\fBbool HISexpire(struct history *\fR\fIhistory\fR\fB, const char *\fR\fIpath\fR\fB, const char *\fR\fIreason\fR\fB, bool \fR\fIwriting\fR\fB, void *\fR\fIcookie\fR\fB, time_t \fR\fIthreshold\fR\fB, bool (*\fR\fIexists\fR\fB)(void *cookie, time_t arrived, time_t posted, time_t expires, const \s-1TOKEN\s0 *token));\fR
+.PP
+\&\fBbool HISwalk(struct history *\fR\fIhistory\fR\fB, const char *\fR\fIreason\fR\fB, void *\fR\fIcookie\fR\fB, bool (*\fR\fIcallback\fR\fB)(void *cookie, time_t arrived, time_t posted, time_t expires, const \s-1TOKEN\s0 *token));\fR
+.PP
+\&\fBstruct histstats HISstats(struct history *\fR\fIhistory\fR\fB);\fR
+.PP
+\&\fBconst char *HISerror(struct history *\fR\fIhistory\fR\fB);\fR
+.PP
+\&\fBbool HISctl(struct history *\fR\fIhistory\fR\fB, int \fR\fIrequest\fR\fB, void *\fR\fIval\fR\fB);\fR
+.SH "DESCRIPTION"
+.IX Header "DESCRIPTION"
+These functions provide provide access to the \s-1INN\s0 history
+database. They maintain key/value pairs in an opaque database whilst
+providing for expiry of outdated information.
+.PP
+The history structure is an opaque handle returned from HISopen.
+.PP
+The \fBHISopen\fR function opens the history file designated by \fIpath\fR
+using the mode \fIflags\fR using the specified \fImethod\fR. \fIflags\fR may be
+\&\fB\s-1HIS_RDONLY\s0\fR to indicate that read-only access to the history
+database is desired, or \fB\s-1HIS_RDWR\s0\fR for read/write access. History
+methods are defined at build time; the history method currently
+available is \*(L"hisv6\*(R". On success a newly initialised history handle is
+returned, or \fB\s-1NULL\s0\fR on failure.
+.PP
+\&\fB\s-1HIS_ONDISK\s0\fR, \fB\s-1HIS_INCORE\s0\fR and \fB\s-1HIS_MMAP\s0\fR may be logically ORed
+into \fIflags\fR to provide a hint to the underlying history manager as
+to how it should handle its data files; \fB\s-1HIS_ONDISK\s0\fR indicates that
+the caller would like as much of the data to be kept on disk (and out
+of memory), \fB\s-1HIS_INCORE\s0\fR indicates that the data files should be kept
+in main memory where possible and \fB\s-1HIS_MMAP\s0\fR that the files should be
+\&\fImmap()\fRed into the processes address space. \fB\s-1HIS_INCORE\s0\fR is typically
+used where a mass rebuild of the history database is being performed;
+the underlying history manager may assume that the caller will call
+\&\fBHISsync\fR() to sync the data files to disk.
+.PP
+The \fB\s-1HIS_CREAT\s0\fR flag indicates that the history database should be
+initialised as new; if any options which affect creation of the
+database need to be set an anonymous history handle should be created
+by calling \fBHISopen\fR with \fIpath\fR set to \fB\s-1NULL\s0\fR, any options set
+using \fBHISctl\fR, then the database opened by calling \fBHISctl\fR with
+\&\fB\s-1HISCTLS_PATH\s0\fR.
+.PP
+The \fBHISclose\fR function closes the handle \fIhistory\fR and deallocates
+any resources associated with it. It returns \fBfalse\fR on failure or
+\&\fBtrue\fR on success.
+.PP
+The \fBHISsync\fR function synchronises any outstanding transactions
+associated with \fIhistory\fR to disk.
+.PP
+\&\fBHISsetcache\fR associates a cache used for speeding up HIScheck with
+\&\fIhistory\fR. The cache will occupy approximately \fIsize\fR bytes.
+.PP
+\&\fBHISlookup\fR retrieves a token from \fIhistory\fR based on the passed
+\&\fIkey\fR (normally the Message\-ID). If no entry with an associated token
+can be found, \fBHISlookup\fR will return \fBfalse\fR. If a token is found
+\&\fIarrived\fR, \fIexpires\fR, and \fIposted\fR are filled in with the message
+arrival, expiry, and posting times respectively (or zero, if the time
+component is not available), in addition to \fItoken\fR being set to the
+retrieved token and a function return value of \fBtrue\fR. Any of
+arrived, expires, posted, or token may be \fB\s-1NULL\s0\fR in which case that
+component is not returned to the caller, without affecting the return
+value.
+.PP
+\&\fBHIScheck\fR checks the database \fIhistory\fR for \fIkey\fR (normally the
+Message\-ID); if \fIkey\fR has previously been set via \fBHISwrite\fR,
+\&\fBHIScheck\fR returns \fBtrue\fR, else \fBfalse\fR.
+.PP
+\&\fBHISwrite\fR writes a new entry to the database \fIhistory\fR associated
+with \fIkey\fR. \fIarrived\fR, \fIposted\fR, and \fIexpired\fR specify the arrival,
+posting, and expiry time respectively; \fIposted\fR and \fIexpired\fR may be
+specifed as <= 0 in which case that component shall be treated as
+absent in the database. \fItoken\fR is associated with the specified
+\&\fIkey\fR. \fBHISwrite\fR returns \fBtrue\fR on success, or \fBfalse\fR on
+failure. The behaviour when \fIkey\fR is not unique with respect to the
+existing entries in \fIhistory\fR is unspecified.
+.PP
+\&\fBHISremember\fR writes a new entry to the database \fIhistory\fR
+associated with \fIkey\fR, merely remembering that this \fIkey\fR has been
+seen, together with its arrival time \fIarrived\fR. \fBHISremember\fR
+returns \fBtrue\fR on success, or \fBfalse\fR on failure. The behaviour when
+\&\fIkey\fR is not unique with respect to the existing entries in
+\&\fIhistory\fR is unspecified.
+.PP
+\&\fBHISreplace\fR replaces an existing entry in the database \fIhistory\fR,
+associated with \fIkey\fR. \fIarrived\fR, \fIposted\fR, \fIexpired\fR specify the
+arrival, posting and expiry time respectively; \fIposted\fR and
+\&\fIexpired\fR may be specifed as <= 0 in which case that component shall
+be treated as absent in the database. \fItoken\fR is associated with the
+specified \fIkey\fR; if \fB\s-1NULL\s0\fR then the history database merely
+remembers that this \fIkey\fR has been seen, together with its arrival
+time. \fBHISreplace\fR returns \fBtrue\fR on success, or \fBfalse\fR on
+failure.
+.PP
+\&\fBHISexpire\fR expires the history database associated with \fIhistory\fR,
+creating a new, replacement, database in the same location if \fIpath\fR
+is \fB\s-1NULL\s0\fR, or in \fIpath\fR if not \fB\s-1NULL\s0\fR; if \fIpath\fR is not \fB\s-1NULL\s0\fR
+then the replacement of the old history database with the new one is
+assumed to be performed out of band by the caller. The \fIwriting\fR flag
+is normally passed as \fBtrue\fR, if you wish to inhibit writing of the
+new database (and so merely see the callbacks), \fIwriting\fR may be set
+\&\fBfalse\fR.
+.PP
+If the underlying history mechanism needs to pause the server, the
+\&\fIreason\fR string is used as the argument to the `ctlinnd pause'
+command, and as such the server should be reserved by the caller prior
+to calling \fBHISexpire\fR; if the caller wishes to inhibit pausing of
+the server, passing \fB\s-1NULL\s0\fR will achieve this. If \fIreason\fR is not
+\&\fB\s-1NULL\s0\fR, then on successful return from \fBHISexpire\fR the server will
+be left paused and the caller should unpause it.
+.PP
+The history database is scanned and entries with an associated storage
+token are passed to the discrimination function \fIexists\fR.
+.PP
+If \fIexists\fR() returns \fBfalse\fR it indicates that stored entity
+associated with token is no longer available (or no longer required),
+and therefore the associated history entry may be expired once it
+meets the \fIthreshold\fR constraint. If \fIexists\fR() returns \fBtrue\fR the
+entry is kept as-is in the newly expired history database.
+.PP
+The \fIexists\fR function is passed the arrival, posting and expiry
+times, in addition to the token associated with the entry. Note that
+posting and/or expiry may be zero, but that the token will never be
+\&\fB\s-1NULL\s0\fR (such entries are handled solely via the threshold
+mechanism). The storage token passed to the discrimination function
+may updated if required (for example, as might be needed by a
+hierachical storage management implementation).
+.PP
+Entries in the database with an arrival time less than \fIthreshold\fR
+with no token associated with them are deleted from the database.
+.PP
+The parameter \fIcookie\fR is passed to the discrimination function, and
+may be used for any purpose required by the caller.
+.PP
+If the discrimination function attempts to access the underlying
+database (for read or write) during the callback, the behaviour is
+unspecified.
+.PP
+\&\fBHISwalk\fR provides an iteration function for the specified \fIhistory\fR
+database. For every entry in the history database, \fIcallback\fR is
+invoked, passing the \fIcookie\fR, arrival, posting, and expiry times, in
+addition to the token associated with the entry. If the \fIcallback\fR()
+returns \fBfalse\fR the iteration is aborted and \fBHISwalk\fR returns
+\&\fBfalse\fR to the caller.
+.PP
+To process the entire database in the presence of a running server,
+\&\fIreason\fR may be passed; if this argument is not \fB\s-1NULL\s0\fR, it is used
+as an an argument to the `ctlinnd (reserve|pause|go)' commands. If
+\&\fIreason\fR is \fB\s-1NULL\s0\fR and the server is running, the behaviour of
+\&\fBHISwalk\fR is undefined.
+.PP
+If the callback function attempts to access the underlying database
+during the callback, the behaviour is unspecified.
+.PP
+\&\fBHISstats\fR returns statistics on the history cache mechanism; given a
+handle \fIhistory\fR, the return value is a \fIstruct histstats\fR
+detailing:
+.ie n .IP """hitpos""" 4
+.el .IP "\f(CWhitpos\fR" 4
+.IX Item "hitpos"
+The number of times an item was found directly in the cache and known
+to exist in the underlying history manager.
+.ie n .IP """hitneg""" 4
+.el .IP "\f(CWhitneg\fR" 4
+.IX Item "hitneg"
+The number of times an item was found directly in the cache and known
+not to exist in the underlying history manager.
+.ie n .IP """misses""" 4
+.el .IP "\f(CWmisses\fR" 4
+.IX Item "misses"
+The number of times an item was not found directly in the cache, but
+on retrieval from the underlying history manager was found to exist.
+.ie n .IP """dne""" 4
+.el .IP "\f(CWdne\fR" 4
+.IX Item "dne"
+The number of times an item was not found directly in the cache, but
+on retrieval from the underlying history manager was found not to exist.
+.PP
+Note that the history cache is only checked by \fBHIScheck\fR and only
+affected by \fBHIScheck\fR, \fBHISwrite\fR, \fBHISremember\fR and
+\&\fBHISreplace\fR. Following a call to \fBHISstats\fR the history statistics
+associated with \fIhistory\fR are cleared.
+.PP
+\&\fBHISerror\fR returns a string describing the most recent error
+associated with \fIhistory\fR; the format and content of these strings is
+history manager dependent. Note that on setting an error, the history
+\&\s-1API\s0 will call the \fBwarn\fR function from \fIlibinn\fR\|(3).
+.PP
+\&\fBHISctl\fR provides a control interface to the underlying history
+manager. The \fIrequest\fR argument determines the type of the request
+and the meaning of the \fIval\fR argument. The values for \fIrequest\fR are:
+.ie n .IP """HISCTLG_PATH"" (const char **)" 4
+.el .IP "\f(CWHISCTLG_PATH\fR (const char **)" 4
+.IX Item "HISCTLG_PATH (const char **)"
+Get the base file path which the history handle represents. \fIval\fR
+should be a pointer to a location of type \fBconst char *\fR. The
+result must not later be passed to \fIfree\fR\|(3).
+.ie n .IP """HISCTLS_PATH"" (const char *)" 4
+.el .IP "\f(CWHISCTLS_PATH\fR (const char *)" 4
+.IX Item "HISCTLS_PATH (const char *)"
+Set the base file path which this history handle should use; typically
+this is used after an anonymous handle has been created using
+\&\fBHISopen(\s-1NULL\s0, ...)\fR. \fIval\fR should be a value of type \fBconst char
+*\fR and will be copied before being stored internally.
+.ie n .IP """HISCTLS_SYNCCOUNT"" (size_t *)" 4
+.el .IP "\f(CWHISCTLS_SYNCCOUNT\fR (size_t *)" 4
+.IX Item "HISCTLS_SYNCCOUNT (size_t *)"
+Set an upper bound on how many history operations may be pending in
+core before being synced to permanent storage; \fB0\fR indicates
+unlimited. \fIval\fR should be a pointer to a value of type \fBsize_t\fR and
+will not be modified by the call.
+.ie n .IP """HISCTLS_NPAIRS"" (size_t *)" 4
+.el .IP "\f(CWHISCTLS_NPAIRS\fR (size_t *)" 4
+.IX Item "HISCTLS_NPAIRS (size_t *)"
+Set a hint to the to the underlying history manager as to how many
+entries there are expected to be in the history database; \fB0\fR
+indicates that an automatic or default sizing should be made. \fIval\fR
+should be a pointer to a value of type \fBsize_t\fR and will not be
+modified by the call.
+.ie n .IP """HISCTLS_IGNOREOLD"" (bool *)" 4
+.el .IP "\f(CWHISCTLS_IGNOREOLD\fR (bool *)" 4
+.IX Item "HISCTLS_IGNOREOLD (bool *)"
+Instruct the underlying history manager to ignore existing database
+when creating new ones; typically this option may be set to \fBtrue\fR if
+the administrator believes that the existing history database is
+corrupt and that ignoring it may help. \fIval\fR should be a pointer to a
+value of type \fBbool\fR and will not be modified by the call.
+.ie n .IP """HISCTLS_STATINTERVAL"" (time_t *)" 4
+.el .IP "\f(CWHISCTLS_STATINTERVAL\fR (time_t *)" 4
+.IX Item "HISCTLS_STATINTERVAL (time_t *)"
+For the history v6 and tagged hash managers, set the interval, in
+seconds, between \fIstat\fR\|(2)s of the history files checking for replaced
+files (as happens during expire); this option is typically used by
+\&\fInnrpd\fR\|(8) like applications. \fIval\fR should be a pointer to a value of
+type \fBtime_t\fR and will not be modified by the call.
+.SH "HISTORY"
+.IX Header "HISTORY"
+Written by Alex Kiernan <alexk@demon.net> for InterNetNews 2.4.0.
+.Sp
+$Id: libinnhist.3 7880 2008-06-16 20:37:13Z iulius $
--- /dev/null
+.\" $Revision: 6124 $
+.TH LIBSTORAGE 3
+.SH NAME
+libstorage \- InterNetNews Storage API library routines
+.SH SYNOPSIS
+.nf
+.ta \w' unsigned long 'u
+
+#include "storage.h"
+
+.B "bool IsToken(const char *text);"
+
+.B "char *TokenToText(const TOKEN token);"
+
+.B "TOKEN TextToToken(const char *text);"
+
+.B "bool SMsetup(SMSETUP type, void *value);"
+
+.B "bool SMinit(void);"
+
+.B "TOKEN SMstore(const ARTHANDLE article);"
+
+.B "ARTHANDLE *SMretrieve(const TOKEN token, const RETRTYPE amount);"
+
+.B "ARTHANDLE *SMnext(const ARTHANDLE *article, const RETRTYPE amount);"
+
+.B "void SMfreearticle(ARTHANDLE *article);"
+
+.B "bool SMcancel(TOKEN token);"
+
+.B "bool SMprobe(PROBETYPE type, TOKEN *token, void *value);"
+
+.B "void SMprintfiles(FILE *file, TOKEN token, char **xref, int ngroups)"
+
+.B "bool SMflushcacheddata(FLUSHTYPE type);"
+
+.B "void SMshutdown(void);"
+
+.B "int SMerrno;"
+.B "char *SMerrorstr;"
+
+#include "ov.h"
+
+.B "bool OVopen(int mode);"
+
+.B "bool OVctl(OVCTLTYPE type, void *val);"
+
+.B "bool OVgroupstats(char *group, int *lo, int *hi, int *count, int *flag);"
+
+.B "bool OVgroupadd(char *group, ARTNUM lo, ARTNUM hi, char *flag);"
+
+.B "bool OVgroupdel(char *group);"
+
+.B "OVADDRESULT OVadd(TOKEN token, char *data, int len, time_t arrived);"
+
+.B "bool OVcancel(TOKEN token);"
+
+.B "void *OVopensearch(char *group, int low, int high);"
+
+.B "bool OVsearch(void *handle, ARTNUM *artnum, char **data, int *len, TOKEN *token, time_t *arrived);"
+
+.B "void OVclosesearch(void *handle);"
+
+.B "bool OVgetartinfo(char *group, ARTNUM artnum, char **data, int *len, TOKEN *token);"
+
+.B "bool OVexpiregroup(char *group, int *lo);"
+
+.B "typedef struct _OVGE {"
+.B " bool delayrm;"
+.B " bool usepost;"
+.B " bool quiet;"
+.B " bool keep;"
+.B " bool earliest;"
+.B " bool ignoreselfexpire;"
+.B " char *filename;"
+.B " time_t now;"
+.B " float timewarp;"
+.B "} OVGE;"
+
+.B "void OVclose(void);"
+
+.fi
+.SH DESCRIPTION
+.I Libstorage
+is a library of common utility (the storage manager) routines for accessing
+Usenet articles and related data independent of particular storage method;
+this is known as the storage API.
+The storage manager's function is to isolate the applications from the
+individual methods and make the policy decisions as to which storage method
+should be called to store an article.
+.PP
+One of the core concepts in the storage API is that articles are not
+manipulated using the message-id or article number, but rather a token that
+uniquely identifies the article to the method that stored it. This may seem
+to be redundant since the message-id already is a unique identifier for the
+article, however, since the storage method generates the token, it can
+encode all the information it needs to locate the article in the token.
+.PP
+\&``OV'' is a common utility routines for accessing newsgroups and overview
+data independent of particular overview method.
+The ``OV'' function is to isolate the applications from the
+individual methods.
+.PP
+All articles passed through the storage API are assumed to be in wire
+format. Wire format means ``\\CR\\LF'' at the end of lines, ``.'' at the
+beginning of lines, and ``.\\CR\\LF'' at the end of article on NNTP stream
+are not stripped. This has a performance win when transferring articles.
+For the ``tradspool'' method, wire format can be disabled. This is just
+for compatibility which is needed by some old tools written for
+traditional spool.
+.PP
+.I IsToken
+checks to see if the text is formatted as a text token string.
+It returns true if formatted correctly or returns false if not.
+.PP
+.I TokenToText
+converts token into text string for output.
+.PP
+.I TextToToken
+converts text into token.
+.PP
+.I SMsetup
+configures some parameters for use by storage manager.
+.I Type
+is one of following.
+.sp 1
+.in +0.5i
+.nf
+SM_RDWR allow read/write open for storage
+ files (default is false)
+SM_PREOPEN open all storage files at startup
+ time and keep them (default is false)
+.fi
+.in -0.5i
+.sp 1
+The
+.I value
+is the pointer which tells each type's value.
+It returns true if setup is successful, or false if not.
+.PP
+.I SMinit
+calls the setup function for all of the configured methods based on
+.IR SMsetup .
+This function should be called prior to all other storage API functions which
+begin with ``SM'' except
+.IR SMsetup .
+It returns true if initialization is successful or returns false if not.
+.I SMinit
+returns true, unless all storage methods fail initialization.
+.PP
+.I SMstore
+stores an article specified with
+.IR article .
+If
+.I arrived
+is specified,
+.I SMstore
+uses its value as article's arrival time; otherwise
+.I SMstore
+uses the current time for it.
+.I SMstore
+returns token type as
+.IR type ,
+or returns
+.I TOKEN_EMPTY
+if article is not stored because some error occurs or simply does not
+match any
+.IR uwildmat (3)
+in
+.IR storage.conf .
+.I SMstore
+fails if
+.I SM_RDWR
+has not been set to true with
+.IR SMsetup .
+.PP
+.I SMretrieve
+retrieves an article specified with
+.IR token .
+.I Amount
+is the one of following which specifies retrieving type.
+.sp 1
+.in +0.5i
+.nf
+RETR_ALL retrieve whole article
+RETR_HEAD retrieve headers of article
+RETR_BODY retrieve body of article
+RETR_STAT just check to see if article exists
+.fi
+.in -0.5i
+.sp 1
+.PP
+The data area indicated by
+.I ARTHANDLE
+should not be modified.
+.PP
+.I SMnext
+is similar in function to
+.I SMretrieve
+except that it is intended for traversing the method's article store
+sequentially.
+To start a query,
+.I SMnext
+should be called with a NULL pointer
+.IR ARTHANDLE .
+Then
+.I SMnext
+returns
+.I ARTHANDLE
+which should be used for the next query.
+If a NULL pointer
+.I ARTHANDLE
+is returned, no articles are left to be queried.
+If
+.I data
+of
+.I ARTHANDLE
+is NULL pointer or
+.I len
+of
+.I ARTHANDLE
+is 0, it indicates the article may be corrupted and should be cancelled by
+.IR SMcancel .
+The data area indicated by
+.I ARTHANDLE
+should not be modified.
+.PP
+.I SMfreearticle
+frees all allocated memory used by
+.I SMretrieve
+and
+.IR SMnext .
+If
+.I SMnext
+will be called with previously returned
+.IR ARTHANDLE ,
+.I SMfreearticle
+should not be called as
+.I SMnext
+frees allocated memory internally.
+.PP
+.I SMcancel
+removes the article specified with
+.IR token .
+It returns true if cancellation is successful or returns false if not.
+.I SMcancel
+fails if
+.I SM_RDWR
+has not been set to true with
+.IR SMsetup .
+.PP
+.I SMprobe
+checks the token on
+.IR PROBETYPE .
+.I Type
+is one of following.
+.sp 1
+.in +0.5i
+.nf
+SELFEXPIRE checks to see if the method of the token
+ has self expire functionality
+SMARTNGNUM gets newsgroup name and article number
+ of the token.
+.fi
+.in -0.5i
+.sp 1
+.PP
+.I SMprintfiles
+shows file name or token usable by
+.IR fastrm (8).
+.PP
+.I SMflushcacheddata
+flushes cached data on each storage method.
+.I Type
+is one of following.
+.sp 1
+.in +0.5i
+.nf
+SM_HEAD flushes cached header
+SM_CANCELEDART flushes articles which should be canceled
+SM_ALL flushes all cached data
+.fi
+.in -0.5i
+.sp 1
+.PP
+.I SMshutdown
+calls the shutdown for each configured storage method and
+then frees any resources it has allocated for itself.
+.PP
+.I SMerrno
+and
+.I SMerrorstr
+indicate the reason of the last error concerning storage manager.
+.PP
+.I OVopen
+calls the setup function for configured method which is specified as
+\&``ovmethod'' in
+.IR inn.conf .
+.I Mode
+is constructed from following.
+.sp 1
+.in +0.5i
+.nf
+OV_READ allow read open for overview
+ method
+OV_WRITE allow write open for overview
+ method
+.fi
+.in -0.5i
+.sp 1
+This function should be called prior to all other OV functions which
+begin with ``OV''.
+.PP
+.I OVctl
+probes or sets some parameters for overview method.
+.I Type
+is one of following.
+.sp 1
+.in +0.5i
+.nf
+OVGROUPBASEDEXPIRE setup how group-based expiry is
+ done
+OVCUTOFFLOW do not add overview data, if the
+ data is under lowest article
+OVSORT probe which key is suitable for
+ overview method
+OVSPACE probe overview space usage
+OVSTATALL stat all articles when
+ OVexpiregroup is called
+.fi
+.in -0.5i
+.sp 1
+.PP
+.I OVgroupstats
+retrieves specified newsgroup information from overview method.
+.PP
+.I OVgroupadd
+informs overview method that specified newsgroup is being added.
+.PP
+.I OVgroupdel
+informs overview method that specified newsgroup is being removed.
+.PP
+.I OVadd
+stores an overview data.
+.PP
+.I OVcancel
+requests the overview method delete overview data specified with token.
+.PP
+.I OVopensearch
+requests the overview method prepare overview data retrieval.
+The request range is determined by
+.I low
+and
+.IR high .
+.PP
+.I OVsearch
+retrieves information; article number, overview data, or arrival time.
+.I OVsearch
+should be called with NULL handle when it is the first time;
+subsequent
+.I OVsearch
+calls should use the handle returned by the previous call to
+.IR OVsearch .
+.I OVsearch
+returns true, unless it reaches high which is specified by
+.IR OVopensearch .
+Retrieved overview data are sorted by article number, and
+.I len
+is ``0'' if there is no overview data for article. Note that the
+retrieved data is not neccessarily null-terminated; you should only rely
+on
+.I len
+octets of overview data being present.
+.PP
+.I OVclosesearch
+frees all resources which have been allocated by
+.IR OVopensearch .
+.PP
+.I OVgetartinfo
+retrieves overview data and token specified with
+.IR artnum .
+.PP
+.I OVexpiregroup
+expires overview data for the newsgroup.
+.I OVexpiregroup
+checks the existense of the article and purges overview data if the
+article no longer exists.
+If ``groupbaseexpiry'' in
+.I inn.conf
+is true,
+.I OVexpiregroup
+also expires articles.
+.PP
+.I OVclose
+frees all resources which are used by the overview method.
+.SH HISTORY
+Written by Katsuhiro Kondou <kondou@nec.co.jp> for InterNetNews.
+.de R$
+This is revision \\$3, dated \\$4.
+..
+.R$ $Id: libstorage.3 6124 2003-01-14 06:03:29Z rra $
+.SH "SEE ALSO"
+expire(8),
+inn.conf(5),
+storage.conf(5).
--- /dev/null
+.\" Automatically generated by Pod::Man v1.37, Pod::Parser v1.32
+.\"
+.\" Standard preamble:
+.\" ========================================================================
+.de Sh \" Subsection heading
+.br
+.if t .Sp
+.ne 5
+.PP
+\fB\\$1\fR
+.PP
+..
+.de Sp \" Vertical space (when we can't use .PP)
+.if t .sp .5v
+.if n .sp
+..
+.de Vb \" Begin verbatim text
+.ft CW
+.nf
+.ne \\$1
+..
+.de Ve \" End verbatim text
+.ft R
+.fi
+..
+.\" Set up some character translations and predefined strings. \*(-- will
+.\" give an unbreakable dash, \*(PI will give pi, \*(L" will give a left
+.\" double quote, and \*(R" will give a right double quote. \*(C+ will
+.\" give a nicer C++. Capital omega is used to do unbreakable dashes and
+.\" therefore won't be available. \*(C` and \*(C' expand to `' in nroff,
+.\" nothing in troff, for use with C<>.
+.tr \(*W-
+.ds C+ C\v'-.1v'\h'-1p'\s-2+\h'-1p'+\s0\v'.1v'\h'-1p'
+.ie n \{\
+. ds -- \(*W-
+. ds PI pi
+. if (\n(.H=4u)&(1m=24u) .ds -- \(*W\h'-12u'\(*W\h'-12u'-\" diablo 10 pitch
+. if (\n(.H=4u)&(1m=20u) .ds -- \(*W\h'-12u'\(*W\h'-8u'-\" diablo 12 pitch
+. ds L" ""
+. ds R" ""
+. ds C` ""
+. ds C' ""
+'br\}
+.el\{\
+. ds -- \|\(em\|
+. ds PI \(*p
+. ds L" ``
+. ds R" ''
+'br\}
+.\"
+.\" If the F register is turned on, we'll generate index entries on stderr for
+.\" titles (.TH), headers (.SH), subsections (.Sh), items (.Ip), and index
+.\" entries marked with X<> in POD. Of course, you'll have to process the
+.\" output yourself in some meaningful fashion.
+.if \nF \{\
+. de IX
+. tm Index:\\$1\t\\n%\t"\\$2"
+..
+. nr % 0
+. rr F
+.\}
+.\"
+.\" For nroff, turn off justification. Always turn off hyphenation; it makes
+.\" way too many mistakes in technical documents.
+.hy 0
+.if n .na
+.\"
+.\" Accent mark definitions (@(#)ms.acc 1.5 88/02/08 SMI; from UCB 4.2).
+.\" Fear. Run. Save yourself. No user-serviceable parts.
+. \" fudge factors for nroff and troff
+.if n \{\
+. ds #H 0
+. ds #V .8m
+. ds #F .3m
+. ds #[ \f1
+. ds #] \fP
+.\}
+.if t \{\
+. ds #H ((1u-(\\\\n(.fu%2u))*.13m)
+. ds #V .6m
+. ds #F 0
+. ds #[ \&
+. ds #] \&
+.\}
+. \" simple accents for nroff and troff
+.if n \{\
+. ds ' \&
+. ds ` \&
+. ds ^ \&
+. ds , \&
+. ds ~ ~
+. ds /
+.\}
+.if t \{\
+. ds ' \\k:\h'-(\\n(.wu*8/10-\*(#H)'\'\h"|\\n:u"
+. ds ` \\k:\h'-(\\n(.wu*8/10-\*(#H)'\`\h'|\\n:u'
+. ds ^ \\k:\h'-(\\n(.wu*10/11-\*(#H)'^\h'|\\n:u'
+. ds , \\k:\h'-(\\n(.wu*8/10)',\h'|\\n:u'
+. ds ~ \\k:\h'-(\\n(.wu-\*(#H-.1m)'~\h'|\\n:u'
+. ds / \\k:\h'-(\\n(.wu*8/10-\*(#H)'\z\(sl\h'|\\n:u'
+.\}
+. \" troff and (daisy-wheel) nroff accents
+.ds : \\k:\h'-(\\n(.wu*8/10-\*(#H+.1m+\*(#F)'\v'-\*(#V'\z.\h'.2m+\*(#F'.\h'|\\n:u'\v'\*(#V'
+.ds 8 \h'\*(#H'\(*b\h'-\*(#H'
+.ds o \\k:\h'-(\\n(.wu+\w'\(de'u-\*(#H)/2u'\v'-.3n'\*(#[\z\(de\v'.3n'\h'|\\n:u'\*(#]
+.ds d- \h'\*(#H'\(pd\h'-\w'~'u'\v'-.25m'\f2\(hy\fP\v'.25m'\h'-\*(#H'
+.ds D- D\\k:\h'-\w'D'u'\v'-.11m'\z\(hy\v'.11m'\h'|\\n:u'
+.ds th \*(#[\v'.3m'\s+1I\s-1\v'-.3m'\h'-(\w'I'u*2/3)'\s-1o\s+1\*(#]
+.ds Th \*(#[\s+2I\s-2\h'-\w'I'u*3/5'\v'-.3m'o\v'.3m'\*(#]
+.ds ae a\h'-(\w'a'u*4/10)'e
+.ds Ae A\h'-(\w'A'u*4/10)'E
+. \" corrections for vroff
+.if v .ds ~ \\k:\h'-(\\n(.wu*9/10-\*(#H)'\s-2\u~\d\s+2\h'|\\n:u'
+.if v .ds ^ \\k:\h'-(\\n(.wu*10/11-\*(#H)'\v'-.4m'^\v'.4m'\h'|\\n:u'
+. \" for low resolution devices (crt and lpr)
+.if \n(.H>23 .if \n(.V>19 \
+\{\
+. ds : e
+. ds 8 ss
+. ds o a
+. ds d- d\h'-1'\(ga
+. ds D- D\h'-1'\(hy
+. ds th \o'bp'
+. ds Th \o'LP'
+. ds ae ae
+. ds Ae AE
+.\}
+.rm #[ #] #H #V #F C
+.\" ========================================================================
+.\"
+.IX Title "list 3"
+.TH list 3 "2008-04-06" "INN 2.4.5" "InterNetNews Documentation"
+.SH "NAME"
+list \- list routines
+.SH "SYNOPSIS"
+.IX Header "SYNOPSIS"
+\&\fB#include <inn/list.h>\fR
+.PP
+struct node {
+ struct node *succ;
+ struct node *pred;
+};
+.PP
+struct list {
+ struct node *head;
+ struct node *tail;
+ struct node *tailpred;
+};
+.PP
+\&\fBvoid list_new(struct list *\fR\fIlist\fR\fB);\fR
+.PP
+\&\fBstruct node *list_addhead(struct list *\fR\fIlist\fR\fB, struct node *\fR\fInode\fR\fB);\fR
+.PP
+\&\fBstruct node *list_addtail(struct list *\fR\fIlist\fR\fB, struct node *\fR\fInode\fR\fB);\fR
+.PP
+\&\fBstruct node *list_head(struct list *\fR\fIlist\fR\fB);\fR
+.PP
+\&\fBstruct node *list_tail(struct list *\fR\fIlist\fR\fB);\fR
+.PP
+\&\fBstruct node *list_succ(struct node *\fR\fInode\fR\fB);\fR
+.PP
+\&\fBstruct node *list_pred(struct node *\fR\fInode\fR\fB);\fR
+.PP
+\&\fBstruct node *list_remhead(struct list *\fR\fIlist\fR\fB);\fR
+.PP
+\&\fBstruct node *list_remtail(struct list *\fR\fIlist\fR\fB);\fR
+.PP
+\&\fBstruct node *list_remove(struct node *\fR\fInode\fR\fB);\fR
+.PP
+\&\fBstruct node *list_insert(struct list *\fR\fIlist\fR\fB, struct node *\fR\fInode\fR\fB, struct node *\fR\fIpred\fR\fB);\fR
+.PP
+\&\fBbool list_isempty(struct list *\fR\fIlist\fR\fB);\fR
+.SH "DESCRIPTION"
+.IX Header "DESCRIPTION"
+\&\fBlist_new\fR initialises the list header \fIlist\fR so as to create an
+empty list.
+.PP
+\&\fBlist_addhead\fR adds \fInode\fR to the head of \fIlist\fR, returning the node
+just added.
+.PP
+\&\fBlist_addtail\fR adds \fInode\fR to the tail of \fIlist\fR, returning the node
+just added.
+.PP
+\&\fBlist_head\fR returns a pointer to the the node at the head of \fIlist\fR
+or \fB\s-1NULL\s0\fR if the list is empty.
+.PP
+\&\fBlist_tail\fR returns a pointer to the the node at the tail of \fIlist\fR
+or \fB\s-1NULL\s0\fR if the list is empty.
+.PP
+\&\fBlist_succ\fR returns the next (successor) node on the list after
+\&\fInode\fR or \fB\s-1NULL\s0\fR if \fInode\fR was the final node.
+.PP
+\&\fBlist_pred\fR returns the previous (predecessor) node on the list before
+\&\fInode\fR or \fB\s-1NULL\s0\fR if \fInode\fR was the first node.
+.PP
+\&\fBlist_remhead\fR removes the first node from \fIlist\fR and returns it to
+the caller. If the list is empty \fB\s-1NULL\s0\fR is returned.
+.PP
+\&\fBlist_remtail\fR removes the last node from \fIlist\fR and returns it to
+the caller. If the list is empty \fB\s-1NULL\s0\fR is returned.
+.PP
+\&\fBlist_remove\fR removes \fInode\fR from the list it is on and returns it
+to the caller.
+.PP
+\&\fBlist_insert\fR inserts \fInode\fR onto \fIlist\fR after the node \fIpred\fR. If
+\&\fIpred\fR is \fB\s-1NULL\s0\fR then \fInode\fR is added to the head of \fIlist\fR.
+.SH "HISTORY"
+.IX Header "HISTORY"
+Written by Alex Kiernan <alex.kiernan@thus.net> for InterNetNews 2.4.0.
+.PP
+$Id: list.3 7880 2008-06-16 20:37:13Z iulius $
--- /dev/null
+.\" Automatically generated by Pod::Man v1.37, Pod::Parser v1.32
+.\"
+.\" Standard preamble:
+.\" ========================================================================
+.de Sh \" Subsection heading
+.br
+.if t .Sp
+.ne 5
+.PP
+\fB\\$1\fR
+.PP
+..
+.de Sp \" Vertical space (when we can't use .PP)
+.if t .sp .5v
+.if n .sp
+..
+.de Vb \" Begin verbatim text
+.ft CW
+.nf
+.ne \\$1
+..
+.de Ve \" End verbatim text
+.ft R
+.fi
+..
+.\" Set up some character translations and predefined strings. \*(-- will
+.\" give an unbreakable dash, \*(PI will give pi, \*(L" will give a left
+.\" double quote, and \*(R" will give a right double quote. \*(C+ will
+.\" give a nicer C++. Capital omega is used to do unbreakable dashes and
+.\" therefore won't be available. \*(C` and \*(C' expand to `' in nroff,
+.\" nothing in troff, for use with C<>.
+.tr \(*W-
+.ds C+ C\v'-.1v'\h'-1p'\s-2+\h'-1p'+\s0\v'.1v'\h'-1p'
+.ie n \{\
+. ds -- \(*W-
+. ds PI pi
+. if (\n(.H=4u)&(1m=24u) .ds -- \(*W\h'-12u'\(*W\h'-12u'-\" diablo 10 pitch
+. if (\n(.H=4u)&(1m=20u) .ds -- \(*W\h'-12u'\(*W\h'-8u'-\" diablo 12 pitch
+. ds L" ""
+. ds R" ""
+. ds C` ""
+. ds C' ""
+'br\}
+.el\{\
+. ds -- \|\(em\|
+. ds PI \(*p
+. ds L" ``
+. ds R" ''
+'br\}
+.\"
+.\" If the F register is turned on, we'll generate index entries on stderr for
+.\" titles (.TH), headers (.SH), subsections (.Sh), items (.Ip), and index
+.\" entries marked with X<> in POD. Of course, you'll have to process the
+.\" output yourself in some meaningful fashion.
+.if \nF \{\
+. de IX
+. tm Index:\\$1\t\\n%\t"\\$2"
+..
+. nr % 0
+. rr F
+.\}
+.\"
+.\" For nroff, turn off justification. Always turn off hyphenation; it makes
+.\" way too many mistakes in technical documents.
+.hy 0
+.if n .na
+.\"
+.\" Accent mark definitions (@(#)ms.acc 1.5 88/02/08 SMI; from UCB 4.2).
+.\" Fear. Run. Save yourself. No user-serviceable parts.
+. \" fudge factors for nroff and troff
+.if n \{\
+. ds #H 0
+. ds #V .8m
+. ds #F .3m
+. ds #[ \f1
+. ds #] \fP
+.\}
+.if t \{\
+. ds #H ((1u-(\\\\n(.fu%2u))*.13m)
+. ds #V .6m
+. ds #F 0
+. ds #[ \&
+. ds #] \&
+.\}
+. \" simple accents for nroff and troff
+.if n \{\
+. ds ' \&
+. ds ` \&
+. ds ^ \&
+. ds , \&
+. ds ~ ~
+. ds /
+.\}
+.if t \{\
+. ds ' \\k:\h'-(\\n(.wu*8/10-\*(#H)'\'\h"|\\n:u"
+. ds ` \\k:\h'-(\\n(.wu*8/10-\*(#H)'\`\h'|\\n:u'
+. ds ^ \\k:\h'-(\\n(.wu*10/11-\*(#H)'^\h'|\\n:u'
+. ds , \\k:\h'-(\\n(.wu*8/10)',\h'|\\n:u'
+. ds ~ \\k:\h'-(\\n(.wu-\*(#H-.1m)'~\h'|\\n:u'
+. ds / \\k:\h'-(\\n(.wu*8/10-\*(#H)'\z\(sl\h'|\\n:u'
+.\}
+. \" troff and (daisy-wheel) nroff accents
+.ds : \\k:\h'-(\\n(.wu*8/10-\*(#H+.1m+\*(#F)'\v'-\*(#V'\z.\h'.2m+\*(#F'.\h'|\\n:u'\v'\*(#V'
+.ds 8 \h'\*(#H'\(*b\h'-\*(#H'
+.ds o \\k:\h'-(\\n(.wu+\w'\(de'u-\*(#H)/2u'\v'-.3n'\*(#[\z\(de\v'.3n'\h'|\\n:u'\*(#]
+.ds d- \h'\*(#H'\(pd\h'-\w'~'u'\v'-.25m'\f2\(hy\fP\v'.25m'\h'-\*(#H'
+.ds D- D\\k:\h'-\w'D'u'\v'-.11m'\z\(hy\v'.11m'\h'|\\n:u'
+.ds th \*(#[\v'.3m'\s+1I\s-1\v'-.3m'\h'-(\w'I'u*2/3)'\s-1o\s+1\*(#]
+.ds Th \*(#[\s+2I\s-2\h'-\w'I'u*3/5'\v'-.3m'o\v'.3m'\*(#]
+.ds ae a\h'-(\w'a'u*4/10)'e
+.ds Ae A\h'-(\w'A'u*4/10)'E
+. \" corrections for vroff
+.if v .ds ~ \\k:\h'-(\\n(.wu*9/10-\*(#H)'\s-2\u~\d\s+2\h'|\\n:u'
+.if v .ds ^ \\k:\h'-(\\n(.wu*10/11-\*(#H)'\v'-.4m'^\v'.4m'\h'|\\n:u'
+. \" for low resolution devices (crt and lpr)
+.if \n(.H>23 .if \n(.V>19 \
+\{\
+. ds : e
+. ds 8 ss
+. ds o a
+. ds d- d\h'-1'\(ga
+. ds D- D\h'-1'\(hy
+. ds th \o'bp'
+. ds Th \o'LP'
+. ds ae ae
+. ds Ae AE
+.\}
+.rm #[ #] #H #V #F C
+.\" ========================================================================
+.\"
+.IX Title "MAILPOST 8"
+.TH MAILPOST 8 "2008-04-26" "INN 2.4.5" "InterNetNews Documentation"
+.SH "NAME"
+mailpost \- Feed an e\-mail message into a newsgroup
+.SH "SYNOPSIS"
+.IX Header "SYNOPSIS"
+\&\fBmailpost\fR [\fB\-hn\fR] [\fB\-a\fR \fIaddr\fR] [\fB\-b\fR \fIdatabase\fR] [\fB\-c\fR \fIwait-time\fR]
+[\fB\-d\fR \fIdistribution\fR] [\fB\-f\fR \fIaddr\fR] [\fB\-m\fR \fImailing-list\fR]
+[\fB\-o\fR \fIoutput-command\fR] [\fB\-p\fR \fIport\fR] [\fB\-r\fR \fIaddr\fR]
+[\fB\-x\fR \fIheader\fR[\fB:\fR\fIheader\fR...]] \fInewsgroups\fR
+.SH "DESCRIPTION"
+.IX Header "DESCRIPTION"
+The \fBmailpost\fR program reads a properly formatted e\-mail message from stdin
+and feeds it to \fBinews\fR for posting to a news server. \fInewsgroups\fR is a
+whitespace-separated list of group names to which to post the article
+(at least one newsgroup must be specified).
+.PP
+Before feeding the article to \fBinews\fR, it checks that the article has not
+been seen before, and it changes some headers (cleans up some address
+headers, removes X\-Trace: and X\-Complaints\-To:, and puts \f(CW\*(C`X\-\*(C'\fR in front
+of unknown headers).
+.PP
+If the article has been seen before (\fBmailpost\fR records the Message-ID of
+each article it handles), then the article will be silently dropped. Other
+errors will cause the article to be mailed to the newsmaster (selected
+at configure time and defaulting to \f(CW\*(C`usenet\*(C'\fR).
+.PP
+Normally, \fBmailpost\fR is run by \fIsendmail\fR\|(8) via an alias entry:
+.PP
+.Vb 2
+\& local\-mail\-wreck\-bikes: "|<pathbin in inn.conf>/mailpost
+\& \-b /var/tmp \-d local local.mail.rec.bicycles.racing"
+.Ve
+.PP
+Instead of \fI/var/tmp\fR, the mail spool directory can be specified,
+or any other directory where the \fBmailpost\fR process has write access.
+.SH "OPTIONS"
+.IX Header "OPTIONS"
+.IP "\fB\-a\fR \fIaddr\fR" 4
+.IX Item "-a addr"
+If the \fB\-a\fR flag is used, the value given is added to the article
+as an Approved: header.
+.IP "\fB\-b\fR \fIdatabase\fR" 4
+.IX Item "-b database"
+If the \fB\-b\fR flag is used, then it defines the location of the database
+used to store the Message-IDs of articles sent on. This is to prevent articles
+looping around if a news-to-mail gateway sends them back here. This option may
+be required if the \fBmailpost\fR process does not have write access to the news
+temporary directory. The default value is \fIpathtmp\fR as set in \fIinn.conf\fR.
+.IP "\fB\-c\fR \fIwait-time\fR" 4
+.IX Item "-c wait-time"
+The \fB\-c\fR flag indicates a length of time to sleep before posting. If
+duplicate messages are received in this interval (by any instance of
+\&\fBmailpost\fR using the same database), the article is only posted once, but
+with Newsgroups: header modified to crosspost the article to all indicated
+groups. The units for \fIwait-time\fR are seconds; a reasonable value may be
+anywhere from tens to hundreds of seconds, or even higher, depending on how
+long mail can be delayed on its way to your system.
+.IP "\fB\-d\fR \fIdistribution\fR" 4
+.IX Item "-d distribution"
+If the \fB\-d\fR flag is used, the value given is added to the article as a
+Distribution: header.
+.IP "\fB\-f\fR \fIaddr\fR" 4
+.IX Item "-f addr"
+The \fB\-f\fR flag is a synonym for the \fB\-r\fR flag.
+.IP "\fB\-h\fR" 4
+.IX Item "-h"
+Print usage information and exit.
+.IP "\fB\-m\fR \fImailing-list\fR" 4
+.IX Item "-m mailing-list"
+If the \fB\-m\fR flag is used, the value given is added to the article in a
+Mailing\-List: header, if such a header doesn't already exist.
+.IP "\fB\-n\fR" 4
+.IX Item "-n"
+If the \fB\-n\fR flag is used, neither an article is posted nor a mail is sent
+in case an error occurs. Everything is written to the standard output.
+.IP "\fB\-o\fR \fIoutput-command\fR" 4
+.IX Item "-o output-command"
+Specifies the program to which the resulting article processed by \fBmailpost\fR
+should be sent. For debugging purpose, \f(CW\*(C`\-o cat\*(C'\fR can be used. The default
+value is \f(CW\*(C`inews \-S \-h\*(C'\fR.
+.IP "\fB\-p\fR \fIport\fR" 4
+.IX Item "-p port"
+Specifies the port on which \fBnnrpd\fR is listening, used for article posting.
+If given, \fB\-p\fR is passed along to \fBinews\fR.
+.IP "\fB\-r\fR \fIaddr\fR" 4
+.IX Item "-r addr"
+A heuristic is used to determine a reasonable value for the Path: header.
+The \fB\-r\fR flag indicates what to use if no other value can be determined.
+.IP "\fB\-x\fR \fIheader\fR[\fB:\fR\fIheader\fR...]" 4
+.IX Item "-x header[:header...]"
+A colon-separated list of additional headers which should be treated as
+known headers; these headers will be passed through to \fBinews\fR without
+having \f(CW\*(C`X\-\*(C'\fR prepended.
+.Sp
+Known headers are:
+.Sp
+.Vb 12
+\& Approved
+\& Content\-*
+\& Date
+\& Distribution
+\& From
+\& Mailing\-List
+\& Message\-ID
+\& MIME\-*
+\& References
+\& Return\-Path
+\& Sender
+\& Subject
+.Ve
+.SH "FILES"
+.IX Header "FILES"
+.IP "\fIpathbin\fR/mailpost" 4
+.IX Item "pathbin/mailpost"
+The Perl script itself used to feed an e\-mail message to a newsgroup.
+.IP "\fIpathtmp\fR/mailpost\-msgid.dir and \fIpathtmp\fR/mailpost\-msgid.pag" 4
+.IX Item "pathtmp/mailpost-msgid.dir and pathtmp/mailpost-msgid.pag"
+The default database files which record previously seen Message\-IDs.
+.SH "HISTORY"
+.IX Header "HISTORY"
+Written by Paul Vixie long ago and then hacked up by James Brister for \s-1INN\s0
+integration.
+.PP
+$Id: mailpost.8 7880 2008-06-16 20:37:13Z iulius $
+.SH "SEE ALSO"
+.IX Header "SEE ALSO"
+\&\fIactive\fR\|(5), \fIinews\fR\|(1), \fIinn.conf\fR\|(5), \fInnrpd\fR\|(8), \fIuwildmat\fR\|(3).
--- /dev/null
+.\" $Revision: 5909 $
+.TH MAKEACTIVE 8
+.SH NAME
+makeactive \- tool to recover Usenet active file.
+.SH SYNOPSIS
+.IR makeactive (8)
+is obsolete.
+.SH "SEE ALSO"
+active(5)
--- /dev/null
+.\" $Revision: 5909 $
+.TH MAKEDBZ 8
+.SH NAME
+makedbz \- rebuild dbz files
+.SH SYNOPSIS
+.B makedbz
+[
+.BI \-f " filename"
+]
+[
+.B \-i
+]
+[
+.B \-o
+]
+[
+.BI \-s " size"
+]
+.SH DESCRIPTION
+.PP
+.I Makedbz
+rebuilds
+.IR dbz (3)
+database.
+The default name of the text file is
+.IR <pathdb\ in\ inn.conf>/history ;
+to specify a different name, use the ``\fB\-f\fP'' flag.
+.SH OPTIONS
+.TP
+.B \-f file
+If the ``\fB\-f\fP'' flag is used, then the database files are named
+.IR file.dir ,
+.IR file.index ,
+and
+.IR file.hash .
+If the ``\fB\-f\fP'' flag is not used, then a temporary link to the name
+.I history.n
+is made and the database files are written as
+.I history.n.index
+,
+.I history.n.hash
+and
+.IR history.n.dir .
+.TP
+.B \-i
+To ignore the old database use the ``\fB\-i\fP'' flag.
+Using the ``\fB\-o\fP'' or ``\fB\-s\fP'' flags implies the ``\fB\-i\fP'' flag.
+.TP
+.B \-o
+If the ``\fB\-o\fP'' flag is used, then the link is not made and any existing
+history files are overwritten.
+If the old database exists,
+.I makedbz
+will use it to determine the size of the new database.
+.TP
+.B \-s size
+The program will also ignore any old database if the ``\fB\-s\fP'' flag is used
+to specify the approximate number of entries in the new database.
+Accurately specifying the size is an optimization that will create a more
+efficient database. Size is measured in key-value pairs (i.e., lines).
+(The size should be the estimated eventual size of the file, typically
+the size of the old file.)
+For more information, see the discussion of
+.I dbzfresh
+and
+.I dbzsize
+in
+.IR dbz (3).
+.SH HISTORY
+Written by Katsuhiro Kondou <kondou@nec.co.jp> for InterNetNews.
+.de R$
+This is revision \\$3, dated \\$4.
+..
+.R$ $Id: makedbz.8 5909 2002-12-03 05:17:18Z vinocur $
+.SH "SEE ALSO"
+dbz(3),
+history(5),
+inn.conf(5).
--- /dev/null
+.\" Automatically generated by Pod::Man v1.37, Pod::Parser v1.32
+.\"
+.\" Standard preamble:
+.\" ========================================================================
+.de Sh \" Subsection heading
+.br
+.if t .Sp
+.ne 5
+.PP
+\fB\\$1\fR
+.PP
+..
+.de Sp \" Vertical space (when we can't use .PP)
+.if t .sp .5v
+.if n .sp
+..
+.de Vb \" Begin verbatim text
+.ft CW
+.nf
+.ne \\$1
+..
+.de Ve \" End verbatim text
+.ft R
+.fi
+..
+.\" Set up some character translations and predefined strings. \*(-- will
+.\" give an unbreakable dash, \*(PI will give pi, \*(L" will give a left
+.\" double quote, and \*(R" will give a right double quote. \*(C+ will
+.\" give a nicer C++. Capital omega is used to do unbreakable dashes and
+.\" therefore won't be available. \*(C` and \*(C' expand to `' in nroff,
+.\" nothing in troff, for use with C<>.
+.tr \(*W-
+.ds C+ C\v'-.1v'\h'-1p'\s-2+\h'-1p'+\s0\v'.1v'\h'-1p'
+.ie n \{\
+. ds -- \(*W-
+. ds PI pi
+. if (\n(.H=4u)&(1m=24u) .ds -- \(*W\h'-12u'\(*W\h'-12u'-\" diablo 10 pitch
+. if (\n(.H=4u)&(1m=20u) .ds -- \(*W\h'-12u'\(*W\h'-8u'-\" diablo 12 pitch
+. ds L" ""
+. ds R" ""
+. ds C` ""
+. ds C' ""
+'br\}
+.el\{\
+. ds -- \|\(em\|
+. ds PI \(*p
+. ds L" ``
+. ds R" ''
+'br\}
+.\"
+.\" If the F register is turned on, we'll generate index entries on stderr for
+.\" titles (.TH), headers (.SH), subsections (.Sh), items (.Ip), and index
+.\" entries marked with X<> in POD. Of course, you'll have to process the
+.\" output yourself in some meaningful fashion.
+.if \nF \{\
+. de IX
+. tm Index:\\$1\t\\n%\t"\\$2"
+..
+. nr % 0
+. rr F
+.\}
+.\"
+.\" For nroff, turn off justification. Always turn off hyphenation; it makes
+.\" way too many mistakes in technical documents.
+.hy 0
+.if n .na
+.\"
+.\" Accent mark definitions (@(#)ms.acc 1.5 88/02/08 SMI; from UCB 4.2).
+.\" Fear. Run. Save yourself. No user-serviceable parts.
+. \" fudge factors for nroff and troff
+.if n \{\
+. ds #H 0
+. ds #V .8m
+. ds #F .3m
+. ds #[ \f1
+. ds #] \fP
+.\}
+.if t \{\
+. ds #H ((1u-(\\\\n(.fu%2u))*.13m)
+. ds #V .6m
+. ds #F 0
+. ds #[ \&
+. ds #] \&
+.\}
+. \" simple accents for nroff and troff
+.if n \{\
+. ds ' \&
+. ds ` \&
+. ds ^ \&
+. ds , \&
+. ds ~ ~
+. ds /
+.\}
+.if t \{\
+. ds ' \\k:\h'-(\\n(.wu*8/10-\*(#H)'\'\h"|\\n:u"
+. ds ` \\k:\h'-(\\n(.wu*8/10-\*(#H)'\`\h'|\\n:u'
+. ds ^ \\k:\h'-(\\n(.wu*10/11-\*(#H)'^\h'|\\n:u'
+. ds , \\k:\h'-(\\n(.wu*8/10)',\h'|\\n:u'
+. ds ~ \\k:\h'-(\\n(.wu-\*(#H-.1m)'~\h'|\\n:u'
+. ds / \\k:\h'-(\\n(.wu*8/10-\*(#H)'\z\(sl\h'|\\n:u'
+.\}
+. \" troff and (daisy-wheel) nroff accents
+.ds : \\k:\h'-(\\n(.wu*8/10-\*(#H+.1m+\*(#F)'\v'-\*(#V'\z.\h'.2m+\*(#F'.\h'|\\n:u'\v'\*(#V'
+.ds 8 \h'\*(#H'\(*b\h'-\*(#H'
+.ds o \\k:\h'-(\\n(.wu+\w'\(de'u-\*(#H)/2u'\v'-.3n'\*(#[\z\(de\v'.3n'\h'|\\n:u'\*(#]
+.ds d- \h'\*(#H'\(pd\h'-\w'~'u'\v'-.25m'\f2\(hy\fP\v'.25m'\h'-\*(#H'
+.ds D- D\\k:\h'-\w'D'u'\v'-.11m'\z\(hy\v'.11m'\h'|\\n:u'
+.ds th \*(#[\v'.3m'\s+1I\s-1\v'-.3m'\h'-(\w'I'u*2/3)'\s-1o\s+1\*(#]
+.ds Th \*(#[\s+2I\s-2\h'-\w'I'u*3/5'\v'-.3m'o\v'.3m'\*(#]
+.ds ae a\h'-(\w'a'u*4/10)'e
+.ds Ae A\h'-(\w'A'u*4/10)'E
+. \" corrections for vroff
+.if v .ds ~ \\k:\h'-(\\n(.wu*9/10-\*(#H)'\s-2\u~\d\s+2\h'|\\n:u'
+.if v .ds ^ \\k:\h'-(\\n(.wu*10/11-\*(#H)'\v'-.4m'^\v'.4m'\h'|\\n:u'
+. \" for low resolution devices (crt and lpr)
+.if \n(.H>23 .if \n(.V>19 \
+\{\
+. ds : e
+. ds 8 ss
+. ds o a
+. ds d- d\h'-1'\(ga
+. ds D- D\h'-1'\(hy
+. ds th \o'bp'
+. ds Th \o'LP'
+. ds ae ae
+. ds Ae AE
+.\}
+.rm #[ #] #H #V #F C
+.\" ========================================================================
+.\"
+.IX Title "MAKEHISTORY 8"
+.TH MAKEHISTORY 8 "2008-04-06" "INN 2.4.5" "InterNetNews Documentation"
+.SH "NAME"
+makehistory \- Initialize or rebuild INN history database
+.SH "SYNOPSIS"
+.IX Header "SYNOPSIS"
+\&\fBmakehistory\fR [\fB\-abeFIOx\fR] [\fB\-f\fR \fIfilename\fR] [\fB\-l\fR \fIcount\fR]
+[\fB\-T\fR \fItmpdir\fR] [\fB\-s\fR \fIsize\fR]
+.SH "DESCRIPTION"
+.IX Header "DESCRIPTION"
+\&\fBmakehistory\fR rebuilds the \fIhistory\fR\|(5) text file, which contains a list of
+Message-IDs of articles already seen by the server. It can also be used
+to rebuild the overview database. Note that the \fIdbz\fR\|(3) indexes for the
+history file are rebuilt by \fImakedbz\fR\|(8), not by \fBmakehistory\fR as in
+earlier versions of \s-1INN\s0.
+.PP
+The default location of the history text file is \fIpathdb\fR/history; to
+specify an alternate location, use the \fB\-f\fR flag.
+.PP
+By default, \fBmakehistory\fR will scan the entire spool, using the storage
+manager, and write a history line for every article. To also generate
+overview information, use the \fB\-O\fR flag.
+.PP
+\&\s-1WARNING:\s0 If you're trying to rebuild the overview database, be sure to
+stop \fIinnd\fR\|(8) and delete or zero out the existing database before you start
+for the best results. An overview rebuild should not be done while the
+server is running. Unless the existing overview is deleted, you may end
+up with problems like out-of-order overview entries, excessively large
+overview buffers, and the like.
+.PP
+If \fIovmethod\fR in \fIinn.conf\fR is \f(CW\*(C`ovdb\*(C'\fR, you must have the ovdb processes
+running while rebuilding overview. ovdb needs them available while
+writing overview entries. You can start them by hand separate from the
+rest of the server by running \fBovdb_init\fR; see \fIovdb_init\fR\|(8) for more
+details.
+.SH "OPTIONS"
+.IX Header "OPTIONS"
+.IP "\fB\-a\fR" 4
+.IX Item "-a"
+Append to the history file rather than generating a new one. If you
+append to the main history file, make sure \fIinnd\fR\|(8) is throttled or not
+running, or you can corrupt the history.
+.IP "\fB\-b\fR" 4
+.IX Item "-b"
+Delete any messages found in the spool that do not have valid Message-ID
+headers in them.
+.IP "\fB\-e\fR" 4
+.IX Item "-e"
+Compute Bytes headers which is used for overview data. This option is valid
+only if \fB\-O\fR flag is specified and \fIoverview.fmt\fR includes \f(CW\*(C`Bytes:\*(C'\fR.
+.IP "\fB\-f\fR \fIfilename\fR" 4
+.IX Item "-f filename"
+Rather than writing directly to \fIpathdb\fR/history, instead write to
+\&\fIfilename\fR.
+.IP "\fB\-F\fR" 4
+.IX Item "-F"
+Fork a separate process to flush overview data to disk rather than doing
+it directly. The advantage of this is that it allows \fBmakehistory\fR to
+continue to collect more data from the spool while the first batch of data
+is being written to the overview database. The disadvantage is that up to
+twice as much temporary disk space will be used for the generated overview
+data. This option only makes sense in combination with \fB\-O\fR. With
+\&\f(CW\*(C`buffindexed\*(C'\fR, the \f(CW\*(C`overchan\*(C'\fR program is invoked to write overview.
+.IP "\fB\-I\fR" 4
+.IX Item "-I"
+Don't store overview data for articles numbered lower than the lowest
+article number in \fIactive\fR. This is useful if there are for whatever
+reason old articles on disk that shouldn't be available to readers or put
+into the overview database.
+.IP "\fB\-l\fR \fIcount\fR" 4
+.IX Item "-l count"
+This option specifies how many articles to process before writing the
+accumulated overview information out to the overview database. The
+default is \f(CW10000\fR. Since overview write performance is faster with
+sorted data, each \*(L"batch\*(R" gets sorted. Increasing the batch size
+with this option may further improve write performance, at the cost
+of longer sort times. Also, temporary space will be needed to store
+the overview batches. At a rough estimate, about 300 * \fIcount\fR bytes
+of temporary space will be required (not counting temp files created
+by \fIsort\fR\|(1)). See the description of the \fB\-T\fR option for how to
+specify the temporary storage location. This option has no effect
+with \f(CW\*(C`buffindexed\*(C'\fR, because \f(CW\*(C`buffindexed\*(C'\fR does not need sorted
+overview and no batching is done.
+.IP "\fB\-s\fR \fIsize\fR" 4
+.IX Item "-s size"
+Size the history database for approximately \fIsize\fR pairs. Accurately
+specifying the size is an optimization that will create a more
+efficient database. (The size should be the estimated eventual size
+of the \fIhistory\fR file, typically the size of the old file, in lines.)
+.IP "\fB\-O\fR" 4
+.IX Item "-O"
+Create the overview database as well as the history file. Overview
+information is only required if the server supports readers; it is not
+needed for a transit-only server (see \fIenableoverview\fR in \fIinn.conf\fR\|(5)).
+If you are using the \f(CW\*(C`buffindexed\*(C'\fR overview storage method, erase all of
+your overview buffers before running \fBmakehistory\fR with \fB\-O\fR.
+.IP "\fB\-T\fR \fItmpdir\fR" 4
+.IX Item "-T tmpdir"
+If \fB\-O\fR is given, \fBmakehistory\fR needs a location to write temporary
+overview data. By default, it uses \fIpathtmp\fR, set in \fIinn.conf\fR, but if
+this option is given, the provided \fItmpdir\fR is used instead. This is
+also used for temporary files created by \fIsort\fR\|(1) (which is invoked in the
+process of writing overview information since sorted overview information
+writes faster). By default, sort usually uses your system temporary
+directory; see the \fIsort\fR\|(1) man page on your system to be sure.
+.IP "\fB\-x\fR" 4
+.IX Item "-x"
+If this option is given, \fBmakehistory\fR won't write out history file
+entries. This is useful mostly for building overview without generating
+a new history file.
+.SH "EXAMPLES"
+.IX Header "EXAMPLES"
+Here's a typical example of rebuilding the entire history and overview
+database, removing broken articles in the news spool. This uses the
+default temporary file locations and should be done while innd isn't
+running (or is throttled).
+.PP
+.Vb 1
+\& makehistory \-b \-f history.n \-O \-l 30000 \-I
+.Ve
+.PP
+This will rebuild the overview (if using \f(CW\*(C`buffindexed\*(C'\fR, erase the
+existing overview buffers before running this command) and leave a new
+history file as \f(CW\*(C`history.n\*(C'\fR in \fIpathdb\fR. To preserve all of the history
+entries from the old history file that correspond to rejected articles or
+expired articles, follow the above command with:
+.PP
+.Vb 2
+\& cd /usr/local/news/db
+\& awk 'NF == 2 { print }' < history >> history.n
+.Ve
+.PP
+(replacing the path with your \fIpathdb\fR, if it isn't the default). Then
+look over the new history file for problems and run:
+.PP
+.Vb 1
+\& makedbz \-s `wc \-l < history` \-f history.n
+.Ve
+.PP
+Then rename all of the files matching \f(CW\*(C`history.n.*\*(C'\fR to \f(CW\*(C`history.*\*(C'\fR,
+replacing the current history database and indexes. After that, it's safe
+to unthrottle innd.
+.PP
+For a simpler example:
+.PP
+.Vb 1
+\& makehistory \-b \-f history.n \-I \-O
+.Ve
+.PP
+will scan the spool, removing broken articles and generating history and
+overview entries for articles missing from history.
+.PP
+To just rebuild overview:
+.PP
+.Vb 1
+\& makehistory \-O \-x \-F
+.Ve
+.SH "FILES"
+.IX Header "FILES"
+.IP "inn.conf" 4
+.IX Item "inn.conf"
+Read for \fIpathdb\fR, \fIpathtmp\fR, and other settings.
+.IP "\fIpathdb\fR/history" 4
+.IX Item "pathdb/history"
+This is the default output file for \fBmakehistory\fR.
+.IP "\fIpathtmp\fR" 4
+.IX Item "pathtmp"
+Where temporary files are written unless \fB\-T\fR is given.
+.SH "HISTORY"
+.IX Header "HISTORY"
+Originally written by Rich \f(CW$alz\fR <rsalz@uunet.uu.net> for InterNetNews and
+updated by various other people since.
+.PP
+$Id: makehistory.8 7880 2008-06-16 20:37:13Z iulius $
+.SH "SEE ALSO"
+.IX Header "SEE ALSO"
+\&\fIdbz\fR\|(3), \fIactive\fR\|(5), \fIhistory\fR\|(5), \fIinn.conf\fR\|(5), \fIctlinnd\fR\|(8), \fIinnd\fR\|(8),
+\&\fImakedbz\fR\|(8), \fIovdb_init\fR\|(8), \fIoverview.fmt\fR\|(5).
--- /dev/null
+.\" $Revision: 5909 $
+.TH MOD-ACTIVE 8
+.SH NAME
+mod-active \- batch processing of ctlinnd newgroup/rmgroup/changegroup
+.SH SYNOPSIS
+.B mod-active
+[
+.I ctlinnd_command_file
+]
+.SH DESCRIPTION
+.B mod-active
+is a
+.B perl
+script that updates the
+.I active
+file based on its input lines of ctlinnd newgroup, rmgroup and
+changegroup commands. It pauses the server briefly while the existing
+active file is read and rewritten, which not only keeps
+.B innd
+from updating the active file but also locks against other instances
+of
+.BR mod-active .
+.PP
+The input to
+.B mod-active
+can come either from one or more files named on the command line, or
+from the standard input. Typically its input is the output from the
+.B docheckgroups
+or
+.B actsync
+commands. Every line which contains the string "ctlinnd newgroup",
+"ctlinnd rmgroup", or "ctlinnd changegroup", optionally preceded by
+whitespace and/or the path to
+.BR ctlinnd ,
+is noted for the update. Redundant commands, such as a newgroup
+directive for a group that already exists, are silently ignored. All
+other lines in the input are also silently ignored.
+.PP
+After the new
+.I active
+file has been generated, the existing one is renamed to
+.I active.old
+and the new one is moved into place. The script then displays the
+differences between the two files.
+.PP
+Any groups that were added to the
+.I active
+file are also added to the
+.I active.times
+file with the string "checkgroups-update".
+.SH BUGS
+Though
+.B innd
+is paused while
+.B mod-active
+works, it is not inconceivable that there could be a conflict if
+something else tries to update the active file during the relatively
+short time that mod-active is working. The two most realistic ways I
+can think of for this to happen are either by an administrator
+concurrently doing a manual ctlinnd command, or by
+.B innd
+receiving a control message, then
+.B mod-active
+pausing the server, then the control message handler script that
+.B innd
+forked running its own
+.B ctlinnd
+command while
+.B mod-active
+is working.
+I've been using
+.B mod-active
+regularly for several years, though, and never had either problem.
+.SH HISTORY
+Written by David C Lawrence <tale@isc.org>.
+.de R$
+This is revision \\$3, dated \\$4.
+..
+.SH "SEE ALSO"
+.IR active (5),
+.IR active.times (5),
+.IR actsync (8),
+.IR ctlinnd (8),
+.IR innd (8).
--- /dev/null
+.\" $Revision: 5909 $
+.TH MODERATORS 5
+.SH NAME
+moderators \- submission addresses for moderated newsgroups
+.SH DESCRIPTION
+When an unapproved article is posted locally to a moderated newsgroup,
+it is not passed off to
+.IR innd (8)
+for normal handling; instead it's instead sent via e-mail to the submission
+address for that newsgroup. The file
+.I <pathetc in inn.conf>/moderators
+lists the submission addresses for moderated newsgroups.
+This file is used by
+.IR nnrpd "(8), " inews (1),
+and any other program that uses the GetModeratorAddress() routine (see
+.IR libinn (3)).
+.PP
+The moderators file is a list of associations between
+.IR uwildmat(3)
+patterns matching newsgroups and the submission address for those
+newsgroups.
+Blank lines and lines starting with a number sign (``#'') are ignored.
+All other lines should consist of two fields separated by a colon.
+.PP
+The first field specifies the group or groups to match using
+.IR uwildmat(3).
+The first matching line is used, so more specific patterns should occur
+earlier than general patterns.
+.PP
+The second field, the submission address, may have at most one
+.I %s
+anywhere in it; if present, this will be replaced by the name of the
+newsgroup with periods replaced by dashes. If there is a literal ``%'' in
+the submission address, it must be written as ``%%'' (even if not followed
+by an ``s'').
+.PP
+Here is a sample file:
+.RS
+.nf
+
+example.important:announce-request@example.com
+example.*:%s@smtp.example.com
+gnu.*:%s@prep.ai.mit.edu
+*:%s@moderators.isc.org
+
+.fi
+.RE
+Using the above file, postings to the moderated newsgroup in the left
+column will be sent to the address shown in the right column:
+.RS
+.nf
+
+.ta \w'example.x.announce 'u
+example.important announce-request@example.com
+example.x.announce example-x-announce@smtp.example.com
+gnu.emacs.sources gnu-emacs-sources@prep.ai.mit.edu
+comp.sources.unix comp-sources-unix@moderators.isc.org
+
+.fi
+.RE
+Periods are converted to dashes for historical reasons, from back in the
+days when periods in the local part of addresses were not always handled
+correctly. It's probably no longer necessary, but so much now depends on
+it that it can't be easily changed.
+.PP
+It's intended that the sample moderators file included in the INN
+distribution always be sufficient for all world-wide newsgroups. The
+hosts behind moderators.isc.org have graciously volunteered to handle
+forwarding tasks for all world-wide newsgroups so that individual sites
+don't have to keep track of the submission addresses for moderated groups.
+The forwarding database used by moderators.isc.org is coordinated by
+moderators-request@isc.org; if you know of a world-wide newsgroup
+hierarchy that is not correctly handled by moderators.isc.org, please send
+the details to that address.
+.PP
+Given that, the only thing you should have to add to the sample file under
+normal circumstances are the forwarding addresses for local or
+limited-distribution moderated groups.
+.SH HISTORY
+Written by Rich $alz <rsalz@uunet.uu.net> for InterNetNews.
+.de R$
+This is revision \\$3, dated \\$4.
+..
+.R$ $Id: moderators.5 5909 2002-12-03 05:17:18Z vinocur $
+.SH "SEE ALSO"
+inews(1), inn.conf(5), libinn(3), uwildmat(3).
--- /dev/null
+.\" Automatically generated by Pod::Man v1.37, Pod::Parser v1.32
+.\"
+.\" Standard preamble:
+.\" ========================================================================
+.de Sh \" Subsection heading
+.br
+.if t .Sp
+.ne 5
+.PP
+\fB\\$1\fR
+.PP
+..
+.de Sp \" Vertical space (when we can't use .PP)
+.if t .sp .5v
+.if n .sp
+..
+.de Vb \" Begin verbatim text
+.ft CW
+.nf
+.ne \\$1
+..
+.de Ve \" End verbatim text
+.ft R
+.fi
+..
+.\" Set up some character translations and predefined strings. \*(-- will
+.\" give an unbreakable dash, \*(PI will give pi, \*(L" will give a left
+.\" double quote, and \*(R" will give a right double quote. \*(C+ will
+.\" give a nicer C++. Capital omega is used to do unbreakable dashes and
+.\" therefore won't be available. \*(C` and \*(C' expand to `' in nroff,
+.\" nothing in troff, for use with C<>.
+.tr \(*W-
+.ds C+ C\v'-.1v'\h'-1p'\s-2+\h'-1p'+\s0\v'.1v'\h'-1p'
+.ie n \{\
+. ds -- \(*W-
+. ds PI pi
+. if (\n(.H=4u)&(1m=24u) .ds -- \(*W\h'-12u'\(*W\h'-12u'-\" diablo 10 pitch
+. if (\n(.H=4u)&(1m=20u) .ds -- \(*W\h'-12u'\(*W\h'-8u'-\" diablo 12 pitch
+. ds L" ""
+. ds R" ""
+. ds C` ""
+. ds C' ""
+'br\}
+.el\{\
+. ds -- \|\(em\|
+. ds PI \(*p
+. ds L" ``
+. ds R" ''
+'br\}
+.\"
+.\" If the F register is turned on, we'll generate index entries on stderr for
+.\" titles (.TH), headers (.SH), subsections (.Sh), items (.Ip), and index
+.\" entries marked with X<> in POD. Of course, you'll have to process the
+.\" output yourself in some meaningful fashion.
+.if \nF \{\
+. de IX
+. tm Index:\\$1\t\\n%\t"\\$2"
+..
+. nr % 0
+. rr F
+.\}
+.\"
+.\" For nroff, turn off justification. Always turn off hyphenation; it makes
+.\" way too many mistakes in technical documents.
+.hy 0
+.if n .na
+.\"
+.\" Accent mark definitions (@(#)ms.acc 1.5 88/02/08 SMI; from UCB 4.2).
+.\" Fear. Run. Save yourself. No user-serviceable parts.
+. \" fudge factors for nroff and troff
+.if n \{\
+. ds #H 0
+. ds #V .8m
+. ds #F .3m
+. ds #[ \f1
+. ds #] \fP
+.\}
+.if t \{\
+. ds #H ((1u-(\\\\n(.fu%2u))*.13m)
+. ds #V .6m
+. ds #F 0
+. ds #[ \&
+. ds #] \&
+.\}
+. \" simple accents for nroff and troff
+.if n \{\
+. ds ' \&
+. ds ` \&
+. ds ^ \&
+. ds , \&
+. ds ~ ~
+. ds /
+.\}
+.if t \{\
+. ds ' \\k:\h'-(\\n(.wu*8/10-\*(#H)'\'\h"|\\n:u"
+. ds ` \\k:\h'-(\\n(.wu*8/10-\*(#H)'\`\h'|\\n:u'
+. ds ^ \\k:\h'-(\\n(.wu*10/11-\*(#H)'^\h'|\\n:u'
+. ds , \\k:\h'-(\\n(.wu*8/10)',\h'|\\n:u'
+. ds ~ \\k:\h'-(\\n(.wu-\*(#H-.1m)'~\h'|\\n:u'
+. ds / \\k:\h'-(\\n(.wu*8/10-\*(#H)'\z\(sl\h'|\\n:u'
+.\}
+. \" troff and (daisy-wheel) nroff accents
+.ds : \\k:\h'-(\\n(.wu*8/10-\*(#H+.1m+\*(#F)'\v'-\*(#V'\z.\h'.2m+\*(#F'.\h'|\\n:u'\v'\*(#V'
+.ds 8 \h'\*(#H'\(*b\h'-\*(#H'
+.ds o \\k:\h'-(\\n(.wu+\w'\(de'u-\*(#H)/2u'\v'-.3n'\*(#[\z\(de\v'.3n'\h'|\\n:u'\*(#]
+.ds d- \h'\*(#H'\(pd\h'-\w'~'u'\v'-.25m'\f2\(hy\fP\v'.25m'\h'-\*(#H'
+.ds D- D\\k:\h'-\w'D'u'\v'-.11m'\z\(hy\v'.11m'\h'|\\n:u'
+.ds th \*(#[\v'.3m'\s+1I\s-1\v'-.3m'\h'-(\w'I'u*2/3)'\s-1o\s+1\*(#]
+.ds Th \*(#[\s+2I\s-2\h'-\w'I'u*3/5'\v'-.3m'o\v'.3m'\*(#]
+.ds ae a\h'-(\w'a'u*4/10)'e
+.ds Ae A\h'-(\w'A'u*4/10)'E
+. \" corrections for vroff
+.if v .ds ~ \\k:\h'-(\\n(.wu*9/10-\*(#H)'\s-2\u~\d\s+2\h'|\\n:u'
+.if v .ds ^ \\k:\h'-(\\n(.wu*10/11-\*(#H)'\v'-.4m'^\v'.4m'\h'|\\n:u'
+. \" for low resolution devices (crt and lpr)
+.if \n(.H>23 .if \n(.V>19 \
+\{\
+. ds : e
+. ds 8 ss
+. ds o a
+. ds d- d\h'-1'\(ga
+. ds D- D\h'-1'\(hy
+. ds th \o'bp'
+. ds Th \o'LP'
+. ds ae ae
+. ds Ae AE
+.\}
+.rm #[ #] #H #V #F C
+.\" ========================================================================
+.\"
+.IX Title "MOTD.NEWS 5"
+.TH MOTD.NEWS 5 "2008-04-06" "INN 2.4.5" "InterNetNews Documentation"
+.SH "NAME"
+motd.news \- Message of the day information for readers
+.SH "DESCRIPTION"
+.IX Header "DESCRIPTION"
+This file, found in \fIpathetc\fR/motd.news, contains local information for
+news readers in a free-form format. The entire file is returned verbatim
+to any client that issues the \s-1LIST\s0 \s-1MOTD\s0 command. This might be used for
+new information, notification of upcoming downtime, or similar purposes.
+.PP
+Be aware that use of the \s-1LIST\s0 \s-1MOTD\s0 command is not widespread and most news
+clients will never ask for this file.
+.PP
+If this file is missing, it is not an error. The server will just send
+the client an empty response.
+.SH "HISTORY"
+.IX Header "HISTORY"
+Rewritten in \s-1POD\s0 by Russ Allbery <rra@stanford.edu> for InterNetNews.
+.PP
+$Id: motd.news.5 7880 2008-06-16 20:37:13Z iulius $
+.SH "SEE ALSO"
+.IX Header "SEE ALSO"
+\&\fIinn.conf\fR\|(5)
--- /dev/null
+.TH NEWS.DAILY 8
+.SH NAME
+news.daily \- do regular Usenet system administration
+.SH SYNOPSIS
+.B news.daily
+[
+.B keyword...
+]
+
+.SH DESCRIPTION
+.I News.daily
+performs a number of important Usenet administrative functions.
+This includes producing a status report, removing old news articles,
+processing log files, rotating the archived log files, renumbering the
+active file,
+removing any old socket files found in the
+.I <pathrun in inn.conf>
+directory, and collecting the output.
+.I "This program should be run under the news administrator's id, not as root."
+.PP
+By default,
+.I news.daily
+performs all of its functions and mails the output to the news administrator,
+.IR <USER\ specified\ with\ \-\-with\-news\-master\ at\ configure> .
+By specifying ``keywords'' on the command line, it is possible to
+modify the functions performed, as well as change the arguments given to
+.IR expire (8)
+and
+.IR expireover (8).
+.PP
+.I News.daily
+should be run once a day, typically out of
+.IR cron (8).
+It may be run more often, but such invocations should at least use the
+\&``norotate'' keyword (or perhaps the \&``notdaily'' keyword) to
+prevent the log files from being processed and rotated too fast.
+.PP
+The
+.IR shlock (1)
+program is used to prevent simultaneous executions.
+.SH "KEYWORDS"
+.PP
+The following keywords may be used:
+.TP
+.I delayrm
+This uses the ``\fB\-z\fP'' flag when invoking
+.I expire
+and
+.IR expireover .
+The names of articles to be removed are written to a temporary file, and
+then renamed after expiration by calling
+.IR expirerm (8).
+.TP
+.IR expctl= path
+Specify the file to use as the
+.IR expire.ctl (5)
+file for
+.IR expire .
+.TP
+.IR expdir= path
+By default,
+.I expire
+builds the new
+.IR history (5)
+file and database in the same directory as the current files.
+Using this keyword specifies a different local to build the new files
+(by passing the ``\fB\-d\fP'' flag to
+.IR expire ),
+which will then be moved to the right location when finished.
+.TP
+.I nostat
+This keyword disables the status report generated by
+.I innstat
+(see
+.IR innstat (8)).
+Without this keyword, the status report is the first function performed,
+just prior to obtaining the
+.I news.daily
+lock.
+.TP
+.I notdaily
+By default
+.I news.daily
+expects to be run only once a day, and it does
+various things (like rotating logs) that normally should only be done on
+daily basis. Use this keyword any extra times
+.I news.daily
+is run in the
+day and the normal logfile processing (and rotation) will not be done.
+.TP
+.I noexpire
+By default,
+.I expire
+is invoked to remove old news articles.
+Using this keyword disables this function.
+.TP
+.I noexpireover
+By default,
+.I expireover
+is invoked to remove old overview database, if
+.I enableoverview
+is set in
+.IR inn.conf .
+Using this keyword disables this function.
+.TP
+.I noexplog
+.I Expire
+normally appends information to
+.I <pathlog in inn.conf>/expire.log
+(see
+.IR newslog (5)).
+Using this keyword causes the
+.I expire
+output to be handled as part of
+.IR news.daily 's
+output.
+It has no effect if the ``noexpire'' keyword is used.
+.TP
+.IR flags= "'args\ for\ expire'"
+By default,
+.I expire
+is invoked with argument ``\-v1''.
+Using this keyword changes the arguments to those specified.
+Be careful to use quotes if multiple arguments are needed.
+This keyword has no effect if the ``noexpire'' keyword is used.
+.TP
+.I nologs
+After expiration,
+.IR scanlogs (8)
+is invoked to process the log files.
+Using this keyword disables all log processing functions.
+.TP
+.I norotate
+By default, log processing includes rotating and cleaning out log files.
+Using this keyword disables the rotating and cleaning aspect of the log
+processing: the logs files are only scanned for information and no contents
+are altered.
+.IP
+This keyword has no effect if the ``nologs'' keyword is used.
+The ``norotate'' keyword is passed on to
+.I scanlogs
+if it is invoked.
+.TP
+.I norenumber
+This keyword disables the
+.IR ctlinnd (8)
+renumber operation.
+Normally, the low-water marks for all newsgroups (see
+.IR active (5))
+are reset.
+.TP
+.I norm
+By default, any socket
+.I ctlinnd
+socket that has not been modified for two days will be removed.
+Using this keyword disables this function.
+.TP
+.I nomail
+.I News.daily
+normally sends a mail message containing the results to the administrator.
+Using this keyword causes this message to be sent to stdout and stderr instead.
+Normally, all utilities invoked by the script have their stdout and stderr
+redirected into a file.
+If the file is empty, no message is sent.
+.TP
+.I expireover
+The
+.I expireover
+program is called after expiration to purge the overview databases.
+If no overview data is created, the ``expireover''
+keyword is not needed. This is the case that the server runs only for
+feeder(no reader).
+.TP
+.IR expireoverflags= "'args\ for\ expireover'"
+If the ``expireover'' keyword is used, this keyword may be used to specify
+the flags to be passed to
+.IR expireover .
+If the ``delayrm'' keyword is used, then the default value is ``\-z''
+and the list of deleted files; otherwise, the default value is ``\-s''.
+.TP
+.I /full/path
+The program specified by the given path is executed just before any
+expiration is done.
+A typical use is to specify an alternate expiration program and use the
+\&``noexpire'' keyword.
+Multiple programs may be specified; they will be invoked in order.
+.TP
+.IR postexec= "'post executed program'"
+The program specified by the given path is executed just after all
+expiration is done.
+Multiple programs may be specified; they will be invoked in order.
+.TP
+.I lowmark
+If the ``lowmark'' keyword is used,
+.IR ctlinnd (8)
+lowmark is used for renumbering
+.IR active .
+Normal
+.IR ctlinnd (8)
+renumber operation will take long time. With ``lowmark'' keyword this will
+take less time.
+If the ``lowmark'' keyword is used,
+\&``norenumber'' keyword is not needed, since
+.I news.daily
+specifies it implicitly.
+.TP
+.IR tmpdir= path
+Sets the environment variable TMPDIR to the specified path.
+Various parts of the expire process, such as sort, will then use this
+path as the directory for temporary files.
+.SH HISTORY
+.I News.daily
+and this manual page written by Landon Curt Noll <chongo@toad.com> and
+Rich $alz <rsalz@uunet.uu.net>.
+.de R$
+This is revision \\$3, dated \\$4.
+..
+.R$ $Id: news.daily.8 6398 2003-07-12 19:15:50Z rra $
+.SH "SEE ALSO"
+active(5),
+ctlinnd(8),
+expire(8),
+fastrm(8),
+inn.conf(5),
+newslog(5),
+innwatch.ctl(5),
+shlock(1).
--- /dev/null
+.\" -*- nroff -*-
+.\" $Revision: 5909 $
+.TH NEWS2MAIL 8
+.SH NAME
+news2mail \- a channel script to gateway news into email.
+.SH SYNOPSIS
+.I news2mail
+.SH DESCRIPTION
+.I news2mail
+runs as a channel process underneath innd. It is set up as channel feed in
+newsfeeds, with different mailing lists as funnel entries pointing to it (see
+below).
+.PP
+.I news2mail
+uses a config file
+.PP
+.RS
+.I <pathetc\ in\ inn.conf>/news2mail.cf
+.RE
+.PP
+to map mailing list names to email addresses.
+.PP
+.I news2mail
+causes sendmail to queue the messages for later delivery (to avoid DOS attacks
+by mass postings). You must run 'sendmail -q' periodically to get the queue
+processed.
+.SH CONFIG FILE
+The config file format is simple: comments (start with ``#'') and blank lines
+are ignored. All other lines have two fields on them. The first is the list
+name and is what innd uses (i.e. the site field of the entry in the newsfeeds
+file). The second field is the actual email address to send the article to. In
+the email message, the ``To'' header will have the mailing list name (i.e. the
+first field).
+.PP
+.RS
+.nf
+# list-name address
+big-red-ants@ucsd.edu big-red-ants-digest@ucsd.edu
+news-software@ucsd.edu news-software-digest@ucsd.edu
+.fi
+.RE
+.PP
+a set of newsfeeds entries for these lists would be:
+.PP
+.RS
+.nf
+.ds R$ <pathbin in inn.conf>
+n2m!:!*:Tc,Ac,Wn*:\*(R$/news2mail
+
+big-red-ants@ucsd.edu:rec.pets.redants.*:Tm:n2m!
+
+news-software@ucsd.edu:news.software.nntp:Tm:n2m!
+.fi
+.RE
+.PP
+.I news2mail
+strips most article headers from the article before mailing. It leaves: From, Subject
+Date, Organization and Message-ID in there. It add a To header with the mailing
+list name in it.
+.SH HISTORY
+news2mail was written by Brian Kantor. This man page was written by James
+Brister.
+.de R$
+This is revision \\$3, dated \\$4.
+..
+.R$ $Id: news2mail.8 5909 2002-12-03 05:17:18Z vinocur $
+.SH "SEE ALSO"
+ctlinnd(8),
+inn.conf(5),
+innd(8),
+newsfeeds(5),
+shlock(1).
+
--- /dev/null
+.\" Automatically generated by Pod::Man v1.37, Pod::Parser v1.32
+.\"
+.\" Standard preamble:
+.\" ========================================================================
+.de Sh \" Subsection heading
+.br
+.if t .Sp
+.ne 5
+.PP
+\fB\\$1\fR
+.PP
+..
+.de Sp \" Vertical space (when we can't use .PP)
+.if t .sp .5v
+.if n .sp
+..
+.de Vb \" Begin verbatim text
+.ft CW
+.nf
+.ne \\$1
+..
+.de Ve \" End verbatim text
+.ft R
+.fi
+..
+.\" Set up some character translations and predefined strings. \*(-- will
+.\" give an unbreakable dash, \*(PI will give pi, \*(L" will give a left
+.\" double quote, and \*(R" will give a right double quote. \*(C+ will
+.\" give a nicer C++. Capital omega is used to do unbreakable dashes and
+.\" therefore won't be available. \*(C` and \*(C' expand to `' in nroff,
+.\" nothing in troff, for use with C<>.
+.tr \(*W-
+.ds C+ C\v'-.1v'\h'-1p'\s-2+\h'-1p'+\s0\v'.1v'\h'-1p'
+.ie n \{\
+. ds -- \(*W-
+. ds PI pi
+. if (\n(.H=4u)&(1m=24u) .ds -- \(*W\h'-12u'\(*W\h'-12u'-\" diablo 10 pitch
+. if (\n(.H=4u)&(1m=20u) .ds -- \(*W\h'-12u'\(*W\h'-8u'-\" diablo 12 pitch
+. ds L" ""
+. ds R" ""
+. ds C` ""
+. ds C' ""
+'br\}
+.el\{\
+. ds -- \|\(em\|
+. ds PI \(*p
+. ds L" ``
+. ds R" ''
+'br\}
+.\"
+.\" If the F register is turned on, we'll generate index entries on stderr for
+.\" titles (.TH), headers (.SH), subsections (.Sh), items (.Ip), and index
+.\" entries marked with X<> in POD. Of course, you'll have to process the
+.\" output yourself in some meaningful fashion.
+.if \nF \{\
+. de IX
+. tm Index:\\$1\t\\n%\t"\\$2"
+..
+. nr % 0
+. rr F
+.\}
+.\"
+.\" For nroff, turn off justification. Always turn off hyphenation; it makes
+.\" way too many mistakes in technical documents.
+.hy 0
+.if n .na
+.\"
+.\" Accent mark definitions (@(#)ms.acc 1.5 88/02/08 SMI; from UCB 4.2).
+.\" Fear. Run. Save yourself. No user-serviceable parts.
+. \" fudge factors for nroff and troff
+.if n \{\
+. ds #H 0
+. ds #V .8m
+. ds #F .3m
+. ds #[ \f1
+. ds #] \fP
+.\}
+.if t \{\
+. ds #H ((1u-(\\\\n(.fu%2u))*.13m)
+. ds #V .6m
+. ds #F 0
+. ds #[ \&
+. ds #] \&
+.\}
+. \" simple accents for nroff and troff
+.if n \{\
+. ds ' \&
+. ds ` \&
+. ds ^ \&
+. ds , \&
+. ds ~ ~
+. ds /
+.\}
+.if t \{\
+. ds ' \\k:\h'-(\\n(.wu*8/10-\*(#H)'\'\h"|\\n:u"
+. ds ` \\k:\h'-(\\n(.wu*8/10-\*(#H)'\`\h'|\\n:u'
+. ds ^ \\k:\h'-(\\n(.wu*10/11-\*(#H)'^\h'|\\n:u'
+. ds , \\k:\h'-(\\n(.wu*8/10)',\h'|\\n:u'
+. ds ~ \\k:\h'-(\\n(.wu-\*(#H-.1m)'~\h'|\\n:u'
+. ds / \\k:\h'-(\\n(.wu*8/10-\*(#H)'\z\(sl\h'|\\n:u'
+.\}
+. \" troff and (daisy-wheel) nroff accents
+.ds : \\k:\h'-(\\n(.wu*8/10-\*(#H+.1m+\*(#F)'\v'-\*(#V'\z.\h'.2m+\*(#F'.\h'|\\n:u'\v'\*(#V'
+.ds 8 \h'\*(#H'\(*b\h'-\*(#H'
+.ds o \\k:\h'-(\\n(.wu+\w'\(de'u-\*(#H)/2u'\v'-.3n'\*(#[\z\(de\v'.3n'\h'|\\n:u'\*(#]
+.ds d- \h'\*(#H'\(pd\h'-\w'~'u'\v'-.25m'\f2\(hy\fP\v'.25m'\h'-\*(#H'
+.ds D- D\\k:\h'-\w'D'u'\v'-.11m'\z\(hy\v'.11m'\h'|\\n:u'
+.ds th \*(#[\v'.3m'\s+1I\s-1\v'-.3m'\h'-(\w'I'u*2/3)'\s-1o\s+1\*(#]
+.ds Th \*(#[\s+2I\s-2\h'-\w'I'u*3/5'\v'-.3m'o\v'.3m'\*(#]
+.ds ae a\h'-(\w'a'u*4/10)'e
+.ds Ae A\h'-(\w'A'u*4/10)'E
+. \" corrections for vroff
+.if v .ds ~ \\k:\h'-(\\n(.wu*9/10-\*(#H)'\s-2\u~\d\s+2\h'|\\n:u'
+.if v .ds ^ \\k:\h'-(\\n(.wu*10/11-\*(#H)'\v'-.4m'^\v'.4m'\h'|\\n:u'
+. \" for low resolution devices (crt and lpr)
+.if \n(.H>23 .if \n(.V>19 \
+\{\
+. ds : e
+. ds 8 ss
+. ds o a
+. ds d- d\h'-1'\(ga
+. ds D- D\h'-1'\(hy
+. ds th \o'bp'
+. ds Th \o'LP'
+. ds ae ae
+. ds Ae AE
+.\}
+.rm #[ #] #H #V #F C
+.\" ========================================================================
+.\"
+.IX Title "NEWSFEEDS 5"
+.TH NEWSFEEDS 5 "2008-04-06" "INN 2.4.5" "InterNetNews Documentation"
+.SH "NAME"
+newsfeeds \- Determine where Usenet articles are sent
+.SH "DESCRIPTION"
+.IX Header "DESCRIPTION"
+The file \fIpathetc\fR/newsfeeds specifies how incoming articles should be
+distributed to other programs and files on the server. It is parsed by
+the InterNetNews server \fIinnd\fR\|(8) when it starts up, or when directed to by
+\&\fIctlinnd\fR\|(8). \fBinnd\fR doesn't send articles to remote sites itself, so
+\&\fInewsfeeds\fR doesn't directly determine which remote news servers articles
+are sent to. Instead, it specifies what batch files should be created or
+which programs should be run (and what information should be sent to
+them), and then this information is used by programs like \fIinnxmit\fR\|(8) and
+\&\fIinnfeed\fR\|(8) to feed articles to remote sites.
+.PP
+The \fInewsfeeds\fR file isn't used solely to set up feeding accepted
+articles to remote sites but also to pass them (or bits of information
+about them) to any local programs or files that want that data. For
+example, \fIcontrolchan\fR\|(8), a daemon that processes incoming control
+messages, runs out of \fInewsfeeds\fR, as could a news to mail gateway.
+.PP
+The file is interpreted as a set of lines, parsed according to the
+following rules: If a line ends with a backslash, the backslash, the
+newline, and any whitespace at the start of the next line is deleted.
+This is repeated until the entire \*(L"logical\*(R" line is collected. If the
+logical line is blank or starts with a number sign (\f(CW\*(C`#\*(C'\fR), it is ignored.
+.PP
+All other lines are interpreted as feed entries. An entry should consist
+of four colon-separated fields; two of the fields may have optional
+sub\-fields, marked off by a slash. Fields or sub-fields that take
+multiple parameters should be separated by a comma. Extra whitespace can
+cause problems and should be avoided. Except for the site names, case is
+significant. The format of an entry is:
+.PP
+.Vb 4
+\& sitename[/exclude,exclude,...]\e
+\& :pattern,pattern...[/distribution,distribution...]\e
+\& :flag,flag...\e
+\& :parameter
+.Ve
+.PP
+Each field is described below.
+.PP
+The \fIsitename\fR is the name of the site to which a news article can be
+sent. It is used for writing log entries and for determining if an
+article should be forwarded to a site. (A \*(L"site\*(R" is the generic term for
+some destination of newsfeed data; it often corresponds to a remote news
+peer, but doesn't have to. For example, a local archiving program run
+from \fInewsfeeds\fR is also a \*(L"site.\*(R") If \fIsitename\fR already appears in
+the article's Path: header, then the article will not be sent to the site.
+The name is usually whatever the remote site uses to identify itself in
+the Path: header, but can be almost any word.
+.PP
+Be careful, though, to avoid having the \fIsitename\fR accidentally match a
+Path: header entry unintentionally. For this reason, special local
+entries (such as archivers or gateways) should probably end with an
+exclamation point to make sure that they do not have the same name as any
+real site. For example, \f(CW\*(C`gateway\*(C'\fR is an obvious name for the local entry
+that forwards articles out to a mailing list. If a site with the name
+\&\f(CW\*(C`gateway\*(C'\fR posts an article, when the local site receives the article it
+will see the name in the Path and not send the article to its own
+\&\f(CW\*(C`gateway\*(C'\fR entry. Since \f(CW\*(C`gateway!\*(C'\fR can't appear as an individual Path:
+entry since \f(CW\*(C`!\*(C'\fR is a delimiter in the Path: header, that would be a
+better thing to use for \fIsitename\fR.
+.PP
+(Another way to avoid this problem is with the \f(CW\*(C`Ap\*(C'\fR flag; see the
+description below.)
+.PP
+If an entry has an exclusion sub\-field, the article will not be sent to
+that site if any of \fIexclude\fR appear in the Path: header. (It's
+sometimes convenient to have the \fIsitename\fR be an abbreviated form of the
+name of the remote site, since all the \fIsitename\fRs to which an article
+is sent are written to the log and using shorter \fIsitename\fRs can
+therefore improve performance for large servers. In this case, the Path:
+header entries of those sites should be given as \fIexclude\fR entries and
+the \f(CW\*(C`Ap\*(C'\fR flag used so that the abbreviated \fIsitename\fR doesn't
+accidentally match some other Path: header entry.)
+.PP
+The same \fIsitename\fR can be used more than once and the appropriate action
+will be taken for each entry that should receive the article, but this is
+recommended only for program feeds to avoid confusion. Case is not
+significant in site names.
+.PP
+The comma-separated \fIpattern\fR specifies which groups to send to the site;
+it is interpreted to build a \*(L"subscription list\*(R" for the site. The
+default subscription is to get all groups carried by the server. It is a
+\&\fIuwildmat\fR\|(3) pattern supporting poison (\f(CW\*(C`@\*(C'\fR) wildcards; see the \fIuwildmat\fR\|(3)
+man page for full details on the pattern matching language. \fIpattern\fR
+will be matched against every newsgroup carried by the server and all
+newsgroups that match will be added to the subscription list for the site.
+.PP
+Normally, a given article (or information about it) is sent to a site if
+any of the newsgroups to which the article was posted are in that site's
+subscription list. If a newsgroup matches a \f(CW\*(C`@\*(C'\fR pattern in \fIpattern\fR,
+then not only is it not added to the subscription list, but any articles
+crossposted to that newsgroup also will not be sent to that site even if
+other newsgroups to which it was crossposted are in that site's
+subscription list. This is called a poison pattern (because matching
+groups are \*(L"poisoned\*(R").
+.PP
+For example, to receive all comp.* groups, but only comp.sources.unix
+within the sources newsgroups, the following \fIpattern\fR can be used:
+.PP
+.Vb 1
+\& comp.*,!comp.sources.*,comp.sources.unix
+.Ve
+.PP
+Note that the trailing \f(CW\*(C`.*\*(C'\fR is required; the pattern has to match the
+whole newsgroup name. \f(CW\*(C`comp.sources.*\*(C'\fR could be written \f(CW\*(C`comp.sources*\*(C'\fR
+and would exclude the newsgroup comp.sources (if it exists) as well as the
+groups in the comp.sources.* hierarchy, but note that this would also
+exclude a newsgroup named comp.sources\-only (whereas the above pattern
+would add that group to the site subscription list since it matches
+\&\f(CW\*(C`comp.*\*(C'\fR and none of the other patterns.
+.PP
+For another example, to feed alt.* and misc.* to a given site but not any
+articles posted to alt.binaries.warez (even if they're also crossposted to
+other alt.* or misc.* groups), the following pattern can be used:
+.PP
+.Vb 1
+\& alt.*,@alt.binaries.warez,misc.*
+.Ve
+.PP
+Note, however, that if you reversed the \f(CW\*(C`alt.*\*(C'\fR and <@alt.binaries.warez>
+entries, this pattern would be equivalent to \f(CW\*(C`alt.*,misc.*\*(C'\fR, since the
+last matching pattern determines whether a given newsgroup matches and the
+poison logic only applies if the poison entry is the last matching entry.
+.PP
+Control messages follow slightly different propagation rules than normal
+articles; see \fIinnd\fR\|(8) for the details. Note that most subscriptions
+should have \f(CW\*(C`!junk,!control,!control.*\*(C'\fR in their pattern list due to those
+propagation rules (and since junk is a special internal newsgroup; see
+\&\fIwanttrash\fR in \fIinn.conf\fR\|(5) for more details on what it's used for) and
+that the best way to keep control messages local to a site is with a
+distribution.
+.PP
+A subscription can be further modified by specifying distributions that
+the site should or should not receive. The default is to send all
+articles to all sites that subscribe to any of the groups where it has
+been posted, but if an article has a Distribution: header and any
+\&\fIdistribution\fRs are specified, then they are checked according to the
+following rules:
+.IP "1." 4
+If the Distribution: header matches any of the values in the sub\-field,
+the article is sent.
+.IP "2." 4
+If a \fIdistribution\fR starts with an exclamation point, and it matches the
+Distribution: header, the article is not sent.
+.IP "3." 4
+If the Distribution: header does not match any \fIdistribution\fR in the
+site's entry and no negations were used, the article is not sent.
+.IP "4." 4
+If the Distribution: header does not match any \fIdistribution\fR in the
+site's entry and any \fIdistribution\fR started with an exclamation point,
+the article is sent.
+.PP
+If an article has more than one distribution specified, then each one is
+handled according according to the above rules. If any of the specified
+distributions indicate that the article should be sent, it is; if none do,
+it is not sent. In other words, the rules are used as a logical or.
+.PP
+It is almost definitely a mistake to have a single feed that specifies
+distributions that start with an exclamation point along with some that
+don't.
+.PP
+Distributions are text words, not patterns; entries like \f(CW\*(C`*\*(C'\fR or \f(CW\*(C`all\*(C'\fR
+have no special meaning.
+.PP
+The \fIflag\fR field is described in \*(L"\s-1FLAG\s0 \s-1VALUES\s0\*(R". The interpretation of
+the \fIparameter\fR field depends on the type of feed and is explained in
+more detail in \*(L"\s-1FEED\s0 \s-1TYPES\s0\*(R". It can be omitted for some types of
+feeds.
+.PP
+The site named \f(CW\*(C`ME\*(C'\fR is special. There must be exactly one such entry,
+and it should be the first entry in the file. If the \f(CW\*(C`ME\*(C'\fR entry has an
+exclusion sub\-field, incoming articles are rejected completely if any of
+the names specified in that exclusion sub-field appear in their Path:
+headers. If the \f(CW\*(C`ME\*(C'\fR entry has a subscription list, that list is
+prepended to the subscription list of all other entries. For example,
+\&\f(CW\*(C`*,!control,!control.*,!junk,!foo.*\*(C'\fR could be used to set the default subscription
+list for all other feeds so that local postings are not propagated unless
+\&\f(CW\*(C`foo.*\*(C'\fR explicitly appears in the site's subscription list. This feature
+tends to be somewhat confusing since the default subscription is prepended
+and can be overridden by other patterns.
+.PP
+If the \f(CW\*(C`ME\*(C'\fR entry has a distribution sub\-field, only articles that match
+that distribution list are accepted and all other articles are rejected.
+A common use for this is to put something like \f(CW\*(C`/!local\*(C'\fR in the \f(CW\*(C`ME\*(C'\fR
+entry to reject local postings from other misconfigured sites.
+.PP
+Finally, it is also possible to set variables in \fInewsfeeds\fR and use them
+later in the file. A line starting with \f(CW\*(C`$\*(C'\fR sets a variable. For
+example:
+.PP
+.Vb 1
+\& $LOCALGROUPS=local.*,example.*
+.Ve
+.PP
+This sets the variable \f(CW\*(C`LOCALGROUPS\*(C'\fR to \f(CW\*(C`local.*,example.*\*(C'\fR. This
+variable can later be used elsewhere in the file, such as in a site entry
+like:
+.PP
+.Vb 1
+\& news.example.com:$LOCALGROUPS:Tf,Wnm:
+.Ve
+.PP
+which is then completely equivalent to:
+.PP
+.Vb 1
+\& news.example.com:local.*,example.*:Tf,Wnm:
+.Ve
+.PP
+Variables aren't solely simple substitution. If either \f(CW\*(C`!\*(C'\fR or \f(CW\*(C`@\*(C'\fR
+immediately preceeds the variable and the value of the variable contains
+commas, that character will be duplicated before each comma. This
+somewhat odd-sounding behavior is designed to make it easier to use
+variables to construct feed patterns. The utility becomes more obvious
+when you observe that the line:
+.PP
+.Vb 1
+\& news.example.net:*,@$LOCALGROUPS:Tf,Wnm:
+.Ve
+.PP
+is therefore equivalent to:
+.PP
+.Vb 1
+\& news.example.net:*,@local.*,@example.*:Tf,Wnm:
+.Ve
+.PP
+which (as explained below) excludes all of the groups in \f(CW$LOCALGROUPS\fR from
+the feed to that site.
+.SH "FLAG VALUES"
+.IX Header "FLAG VALUES"
+The \fIflags\fR parameter specifies miscellaneous parameters, including the
+type of feed, what information should be sent to it, and various
+limitations on what articles should be sent to a site. They may be
+specified in any order and should be separated by commas. Flags that take
+values should have the value immediately after the flag letter with no
+whitespace. The valid flags are:
+.IP "\fB<\fR \fIsize\fR" 4
+.IX Item "< size"
+An article will only be sent to this site if it is less than \fIsize\fR bytes
+long. The default is no limit.
+.IP "\fB>\fR \fIsize\fR" 4
+.IX Item "> size"
+An article will only be sent to this site if it is greater than \fIsize\fR
+bytes long. The default is no limit.
+.IP "\fBA\fR \fIchecks\fR" 4
+.IX Item "A checks"
+An article will only be sent to this site if it meets the requirements
+specified in \fIchecks\fR, which should be chosen from the following set.
+\&\fIchecks\fR can be multiple letters if appropriate.
+.RS 4
+.IP "c" 3
+.IX Item "c"
+Exclude all kinds of control messages.
+.IP "C" 3
+.IX Item "C"
+Only send control messages, not regular articles.
+.IP "d" 3
+.IX Item "d"
+Only send articles with a Distribution header. Combined with a particular
+distribution value in the \fIdistribution\fR part of the site entry, this can
+be used to limit articles sent to a site to just those with a particuliar
+distribution.
+.IP "e" 3
+.IX Item "e"
+Only send articles where every newsgroup listed in the Newsgroups: header
+exists in the active file.
+.IP "f" 3
+.IX Item "f"
+Don't send articles rejected by filters. This is only useful when
+\&\fIdontrejectfiltered\fR is set in \fIinn.conf\fR. With that variable set, this
+lets one accept all articles but not propagate filtered ones to some
+sites.
+.IP "o" 3
+Only send articles for which overview data was stored.
+.IP "O" 3
+.IX Item "O"
+Send articles to this site that don't have an X\-Trace: header, even if the
+\&\f(CW\*(C`O\*(C'\fR flag is also given.
+.IP "p" 3
+.IX Item "p"
+Only check the exclusions against the Path: header of articles; don't
+check the site name. This is useful if your site names aren't the same as
+the Path: entries added by those remote sites, or for program feeds where
+the site name is arbitrary and unrelated to the Path: header.
+.RE
+.RS 4
+.Sp
+If both \f(CW\*(C`c\*(C'\fR and \f(CW\*(C`C\*(C'\fR are given, the last specified one takes precedence.
+.RE
+.IP "\fBB\fR \fIhigh\fR/\fIlow\fR" 4
+.IX Item "B high/low"
+If a site is being fed by a file, channel, or exploder (see below), the
+server will normally start trying to write the information as soon as
+possible. Providing a buffer may give better system performance and help
+smooth out overall load if a large batch of news comes in. The value of
+the this flag should be two numbers separated by a slash. \fIhigh\fR
+specifies the point at which the server can start draining the feed's I/O
+buffer, and \fIlow\fR specifies when to stop writing and begin buffering
+again; the units are bytes. The default is to do no buffering, sending
+output as soon as it is possible to do so.
+.IP "\fBC\fR \fIcount\fR" 4
+.IX Item "C count"
+If this flag is specified, an article will only be sent to this site if
+the number of groups it is posted to, plus the square of the number of
+groups followups would appear in, is no more than \fIcount\fR. \f(CW30\fR is a
+good value for this flag, allowing crossposts to up to 29 groups when
+followups are set to a single group or poster and only allowing crossposts
+to 5 groups when followups aren't set.
+.IP "\fBF\fR \fIname\fR" 4
+.IX Item "F name"
+Specifies the name of the file that should be used if it's necessary to
+begin spooling for the site (see below). If \fIname\fR is not an absolute
+path, it is taken to be relative to \fIpathoutgoing\fR in \fIinn.conf\fR. If
+\&\fIname\fR is a directory, the file \fItogo\fR in that directory will be used as
+the file name.
+.IP "\fBG\fR \fIcount\fR" 4
+.IX Item "G count"
+If this flag is specified, an article will only be sent to this site if it
+is posted to no more than \fIcount\fR newsgroups. This has the problem of
+filtering out many FAQs, as well as newsgroup creation postings and
+similar administrative announcements. Either the \fBC\fR flag or the \fBU\fR
+flag is a better solution.
+.IP "\fBH\fR \fIcount\fR" 4
+.IX Item "H count"
+If this flag is specified, an article will only be sent to this site if it
+has \fIcount\fR or fewer sites in its Path: line. This flag should only be
+used as a rough guide because of the loose interpretation of the Path:
+header; some sites put the poster's name in the header, and some sites
+that might logically be considered to be one hop become two because they
+put the posting workstation's name in the header. The default value for
+\&\fIcount\fR if not specified is one. (Also see the \fBO\fR flag, which is
+sometimes more appropriate for some uses of this flag.)
+.IP "\fBI\fR \fIsize\fR" 4
+.IX Item "I size"
+The flag specifies the size of the internal buffer for a file feed. If
+there are more file feeds than allowed by the system, they will be
+buffered internally in least-recently-used order. If the internal buffer
+grows bigger then \fIsize\fR bytes, however, the data will be written out to
+the appropriate file. The default value is 16 \s-1KB\s0.
+.IP "\fBN\fR \fIstatus\fR" 4
+.IX Item "N status"
+Restricts the articles sent to this site to those in newsgroups with the
+moderation status given by \fIstatus\fR. If \fIstatus\fR is \f(CW\*(C`m\*(C'\fR, only articles
+in moderated groups are sent; if \fIstatus\fR is \f(CW\*(C`u\*(C'\fR, only articles in
+unmoderated groups are sent.
+.IP "\fBO\fR \fIoriginator\fR" 4
+.IX Item "O originator"
+If this flag is specified, an article will only be sent to this site if it
+contains an X\-Trace: header and the first field of this header matches
+\&\fIoriginator\fR. \fIoriginator\fR is a \fIuwildmat\fR\|(3) expression without commas or
+a list of such expressions, separated by \f(CW\*(C`/\*(C'\fR. The article is never sent
+if the first character of the pattern begins with \f(CW\*(C`@\*(C'\fR and the rest of the
+pattern matches. One use of this flag is to restrict the feed to locally
+generated posts by using an \fIoriginator\fR pattern that matches the
+X\-Trace: header added by the local server.
+.IP "\fBP\fR \fIpriority\fR" 4
+.IX Item "P priority"
+The nice priority that this channel or program feed should receive. This
+should be a positive number between 0 and 20 and is the priority that the
+new process will run with. This flag can be used to raise the priority to
+normal if you're using the \fInicekids\fR parameter in \fIinn.conf\fR.
+.IP "\fBQ\fR \fIhashfeed\fR" 4
+.IX Item "Q hashfeed"
+Specifies the \fIhashfeed\fR match expression for this site. It must be in
+the form \f(CW\*(C`value/mod\*(C'\fR or \f(CW\*(C`start\-end/mod\*(C'\fR. The Message-ID of the article
+is hashed using \s-1MD5\s0, which results in a 128\-bit hash. The lowest
+32\ bits are then taken by default as the hashfeed value (which is an
+integer). If the hashfeed value modulus \f(CW\*(C`mod\*(C'\fR plus one equals \f(CW\*(C`value\*(C'\fR or
+is between \f(CW\*(C`start\*(C'\fR and \f(CW\*(C`end\*(C'\fR, the article will be fed to this site. All
+these numbers must be integers.
+.Sp
+It is a deterministic way to control the flow of articles and to split a feed.
+For instance:
+.Sp
+.Vb 2
+\& Q1/2 Feeds about 50% of all articles to this site.
+\& Q2/2 Feeds the other 50% of all articles.
+.Ve
+.Sp
+Another example with three sites:
+.Sp
+.Vb 3
+\& Q1\-3/10 Feeds about 30% of all articles.
+\& Q4\-5/10 Feeds about 20% of all articles.
+\& Q6\-10/10 Feeds about 50% of all articles.
+.Ve
+.Sp
+If this flag is specified multiple times, the contents will be
+logically \f(CW\*(C`OR\*(C'\fRed together (just one match is needed).
+.Sp
+You can use an extended syntax of the form \f(CW\*(C`value/mod_offset\*(C'\fR or
+\&\f(CW\*(C`start\-end/mod_offset\*(C'\fR. As \s-1MD5\s0 generates a 128\-bit return value,
+it is possible to specify from which byte-offset the 32\-bit integer
+used by hashfeed starts. The default value for \f(CW\*(C`offset\*(C'\fR is \f(CW\*(C`_0\*(C'\fR
+and thirteen overlapping values from \f(CW\*(C`_0\*(C'\fR to \f(CW\*(C`_12\*(C'\fR can be used.
+Only up to four totally independent values exist: \f(CW\*(C`_0\*(C'\fR, \f(CW\*(C`_4\*(C'\fR,
+\&\f(CW\*(C`_8\*(C'\fR and \f(CW\*(C`_12\*(C'\fR.
+.Sp
+Therefore, it allows to a generate a second level of deterministic
+distribution. Indeed, if a news server is fed \f(CW\*(C`Q1/2\*(C'\fR, it can go on
+splitting thanks to \f(CW\*(C`Q1\-3/9_4\*(C'\fR for instance.
+.Sp
+The algorithm is compatible with the one used by Diablo\ 5.1 and up.
+If you want to use the legacy quickhashing method used by Diablo
+before 5.1, you can put an \f(CW\*(C`@\*(C'\fR sign just after the \fBQ\fR flag (for
+instance \f(CW\*(C`Q@1\-3/10\*(C'\fR, but the distribution of the messages is not
+perfect with this legacy method whose use is discouraged and for
+which offsets cannot be used).
+.IP "\fBS\fR \fIsize\fR" 4
+.IX Item "S size"
+If the amount of data queued for the site gets to be larger than \fIsize\fR
+bytes, the server will switch to spooling, appending to a file specified
+by the \fBF\fR flag, or \fIpathoutgoing\fR/\fIsitename\fR if \fBF\fR is not specified.
+Spooling usually happens only for channel or exploder feeds, when the
+spawned program isn't keeping up with its input.
+.IP "\fBT\fR \fItype\fR" 4
+.IX Item "T type"
+This flag specifies the type of feed for this site. \fItype\fR should be a
+letter chosen from the following set:
+.Sp
+.Vb 6
+\& c Channel
+\& f File
+\& l Log entry only
+\& m Funnel (multiple entries feed into one)
+\& p Program
+\& x Exploder
+.Ve
+.Sp
+Each feed is described below in \*(L"\s-1FEED\s0 \s-1TYPES\s0\*(R". The default is \fBTf\fR,
+for a file feed.
+.IP "\fBU\fR \fIcount\fR" 4
+.IX Item "U count"
+If this flag is specified, an article will only be sent to this site if
+followups to this article would be posted to no more than \fIcount\fR
+newsgroups. (Also see \fBC\fR for a more complex way of handling this.)
+.IP "\fBW\fR \fIitems\fR" 4
+.IX Item "W items"
+For a file, channel, or exploder feed, this flag controls what information
+will be sent to this site. For a program feed, only the asterisk (\f(CW\*(C`*\*(C'\fR)
+has any effect. \fIitems\fR should be chosen from the following set:
+.RS 4
+.IP "b" 3
+.IX Item "b"
+Size of the article (in wire format, meaning with \s-1CRLF\s0 at the end of each
+line, periods doubled at the beginning of lines, and ending in a line with
+a single period) in bytes.
+.IP "e" 3
+.IX Item "e"
+The time the article will expire as seconds since epoch if it has an
+Expires: header, \f(CW0\fR otherwise.
+.IP "f" 3
+.IX Item "f"
+The storage \s-1API\s0 token of the article (the same as \f(CW\*(C`n\*(C'\fR). The article can
+be retrieved given the storage \s-1API\s0 token by using \fIsm\fR\|(8).
+.IP "g" 3
+.IX Item "g"
+The newsgroup the article is in; if cross\-posted, then the first of the
+groups to which the article was posted that this site gets. (The
+difference from \f(CW\*(C`G\*(C'\fR is that this sends the newsgroup to which the article
+was posted even if it is a control message.)
+.IP "h" 3
+.IX Item "h"
+The history hash key of the article (derived from the message \s-1ID\s0).
+.IP "m" 3
+.IX Item "m"
+The message \s-1ID\s0 of the article.
+.IP "n" 3
+.IX Item "n"
+The storage \s-1API\s0 token of the article. The article can be retrieved given
+the storage \s-1API\s0 token by using \fIsm\fR\|(8).
+.IP "p" 3
+.IX Item "p"
+The time the article was posted a seconds since epoch.
+.IP "s" 3
+.IX Item "s"
+The site that fed the article to the server. This is taken from either
+the Path: header or the \s-1IP\s0 address of the sending site depending on the
+value of \fIlogipaddr\fR in \fIinn.conf\fR. If \fIlogipaddr\fR is true and the \s-1IP\s0
+address is \f(CW0.0.0.0\fR (meaning that the article was fed from localhost by
+a program like \fIrnews\fR\|(8)), the Path: header value will be sent instead.
+.IP "t" 3
+.IX Item "t"
+The time the article was received as seconds since epoch.
+.IP "\&*" 3
+The names of the appropriate funnel entries, or all sites that get the
+article (see below for more details).
+.IP "D" 3
+.IX Item "D"
+The value of the Distribution: header of the article, or \f(CW\*(C`?\*(C'\fR if there is
+no such header in the article.
+.IP "G" 3
+.IX Item "G"
+Where the article is stored. If the newsgroup is crossposted, this is
+generally the first of the groups to which it was posted that this site
+receives; however, control messages are filed in control or control.*
+(which is the difference between this item and \f(CW\*(C`g\*(C'\fR).
+.IP "H" 3
+.IX Item "H"
+All of the headers, followed by a blank line. The Xref header will
+already be present, and a Bytes header containing the article's size in
+bytes as in the \f(CW\*(C`b\*(C'\fR item will be added to the headers. If used, this
+should be the only item in the list.
+.IP "N" 3
+.IX Item "N"
+The value of the Newsgroups: header.
+.IP "P" 3
+.IX Item "P"
+The value of the Path: header.
+.IP "O" 3
+.IX Item "O"
+Overview data for the article.
+.IP "R" 3
+.IX Item "R"
+Information needed for replication (the Xref header without the site
+name).
+.RE
+.RS 4
+.Sp
+More than one letter can be given. If multiple items are specified, they
+will be written in the order specified separated by spaces. (\f(CW\*(C`H\*(C'\fR should
+be the only item if given, but if it's not a newline will be sent before
+the beginning of the headers.) The default is \fBWn\fR.
+.Sp
+The \f(CW\*(C`H\*(C'\fR and \f(CW\*(C`O\*(C'\fR items are intended for use by programs that create news
+overview databases or require similar information. \fBWnteO\fR is the flag
+to generate input needed by the \fIoverchan\fR\|(8) program.
+.Sp
+The asterisk (\f(CW\*(C`*\*(C'\fR) has special meaning. Normally it expands to a
+space-separated list of all sites that received the current article. If,
+however, this site is a target of a funnel feed (in other words, if it is
+named by other sites which have the \fBTm\fR flag), then the asterisk expands
+to the names of the funnel feeds that received the article. Similarly, if
+the site is a program feed, an asterisk in the \fIparameter\fR field will be
+expanded into the list of funnel feeds that received the article. A
+program feed cannot get the site list unless it is the target of other
+\&\fBTm\fR feeds.
+.RE
+.SH "FEED TYPES"
+.IX Header "FEED TYPES"
+\&\fBinnd\fR provides four basic types of feeds: log, file, program, and
+channel. An exploder is a special type of channel. In addition, several
+entries can feed into the same feed; these are funnel feeds, which refer
+to an entry that is one of the other types. Funnel feeds are partially
+described above with the description of the \fBW*\fR flag. A funnel feed
+gets every article that would be sent to any of the feeds that funnel into
+it and normally include the \fBW*\fR flag in their flags so that the program
+processing that feed knows which sites received which articles. The most
+common funnel feed is \fIinnfeed\fR\|(8).
+.PP
+Note that the term \*(L"feed\*(R" is technically a misnomer, since the server
+doesn't transfer articles itself and only writes data to a file, program,
+or log telling another program to transfer the articles.
+.PP
+The simplest feed is a log feed (\fBTl\fR). Other than a mention in the news
+log file, \fIpathlog\fR/news, no data is written out. This is equivalent to
+a \fBTf\fR entry writing to \fI/dev/null\fR, except that no file is ever opened.
+Flushing a log feed does nothing.
+.PP
+A file feed (\fBTf\fR) is the next simplest type of feed. When the site
+should receive an article, the specified data is written out to the file
+named by the \fIparameter\fR field. If \fIparameter\fR is not an absolute path,
+it is taken to be relative to \fIpathoutgoing\fR in \fIinn.conf\fR. If
+\&\fIparameter\fR is not given, it defaults to \fIpathoutgoing\fR/\fIsitename\fR.
+The file name should be unique (two file feeds should not ever point to
+the same file).
+.PP
+File feeds are designed for use by external programs that periodically
+process the written data. To cooperate with \fBinnd\fR properly, such
+external programs should first rename the batch file and then send a flush
+command for that site to \fBinnd\fR using \fIctlinnd\fR\|(8). \fBinnd\fR will then
+write out any buffered data, close the file, and reopen it (under the
+original name), and the program can process the data in the renamed file
+at its leisure. File feeds are most frequently used in combination with
+\&\fInntpsend\fR\|(8).
+.PP
+A program feed (\fBTp\fR) spawns a given program for every article that the
+site receives. The \fIparamter\fR field must be the command line to execute,
+and should contain one instance of \f(CW%s\fR, which will be replaced by the
+storage \s-1API\s0 token of the article (the actual article can be retrieved by
+the program using \fIsm\fR\|(8)). The program will not receive anything on
+standard input (unlike earlier versions of \s-1INN\s0, where the article is sent
+to the program on stdin), and standard output and error from the program
+will be set to the error log (\fIpathlog\fR/errlog). \fBinnd\fR will try to
+avoid spawning a shell if the command has no shell meta\-characters; this
+feature can be defeated if necessary for some reason by appending a
+semi-colon to the end of the command. The full path name of the program
+to be run must be specified unless the command will be run by the shell
+(and it is strongly recommended that the full path name always be
+specified regardless).
+.PP
+If a program feed is the target of a funnel, and if \fBW*\fR appears in the
+flags of the site, a single asterisk may be present in the \fIparameter\fR
+and will be replaced by a space-separated list of names of the sites
+feeding into the funnel which received the relevant article. If the site
+is not the target of a funnel, or if the \fBW*\fR flag is not used, the
+asterisk has no special meaning.
+.PP
+Flushing a program feed does nothing.
+.PP
+For a channel (\fBTc\fR) or exploder (\fBTx\fR) feed, the \fIparameter\fR field
+again names the process to start. As with program feeds, the full path to
+the program must be specified. However, rather than spawning the program
+for every article, it is spawned once and then whenever the site receives
+an article, the data specified by the site flags is written to the
+standard input of the spawned program. Standard output and error are set
+as with program feeds. If the process exits, it will be restarted
+automatically. If the process cannot be started, the server will spool
+input to a file named \fIpathoutgoing\fR/\fIsitename\fR and will try to start
+the process again later.
+.PP
+When a channel or exploder feed is flushed, the server closes its end of
+the pipe to the program's standard input. Any pending data that has not
+been written will be spooled; see the description of the \fBS\fR flag above.
+The server will then spawn a new instance of the program. No signal is
+sent to the program; it is up to the program handling a channel or
+exploder feed to notice end of file on its standard input and exit
+appropriately.
+.PP
+Exploders are a special type of channel feed. In addition to the channel
+feed behavior described above, exploders can also be sent command lines.
+These lines start with an exclamation point and their interpretation is up
+to the exploder. The following commands are generated automatically by
+the server:
+.PP
+.Vb 4
+\& !newgroup group
+\& !rmgroup group
+\& !flush
+\& !flush site
+.Ve
+.PP
+These commands are sent whenever the \fIctlinnd\fR\|(8) command of the same name
+is received by the server. In addition, the \fIctlinnd\fR\|(8) \f(CW\*(C`send\*(C'\fR command
+can be used to send an arbitrary command line to an exploder. The primary
+exploder is \fIbuffchan\fR\|(8).
+.PP
+Finally, \fBTm\fR feeds are the input to a funnel. The \fIparameter\fR field of
+the site should name the site handling articles for all of the funnel
+inputs.
+.SH "EXAMPLES"
+.IX Header "EXAMPLES"
+All of the following examples assume that \s-1INN\s0 was installed with a prefix
+of \fI/usr/local/news\fR; if you installed it somewhere else, modify the
+paths as appropriate.
+.PP
+The syntax of the \fInewsfeeds\fR file is so complex because you can specify
+a staggering variety of feeds. \s-1INN\s0 is capable of interacting with a wide
+variety of programs that do various things with news articles. Far and
+away the most common two entries in \fInewsfeeds\fR, however, are file feeds
+for \fInntpsend\fR\|(8) and funnel feeds for \fIinnfeed\fR\|(8).
+.PP
+The former look like this:
+.PP
+.Vb 1
+\& feed.example.com:*,!control,!control.*,!junk:Tf,Wnm:
+.Ve
+.PP
+which generates a file named \fIpathoutgoing\fR/feed.example.com containing
+one line per article consisting of the storage \s-1API\s0 token, a space, and the
+message \s-1ID\s0.
+.PP
+The latter look like this:
+.PP
+.Vb 1
+\& feed.example.com:*,!control,!control.*,!junk:Tm:innfeed!
+.Ve
+.PP
+Very similar, except that this is the input to a funnel feed named
+\&\f(CW\*(C`innfeed!\*(C'\fR. One could also write this as:
+.PP
+.Vb 1
+\& example/feed.example.com:*,!control,!control.*,!junk:Ap,Tm:innfeed!
+.Ve
+.PP
+(note the \fBAp\fR so that articles that contain just \f(CW\*(C`example\*(C'\fR in the Path:
+header will still be sent), which is completely equivalent except that
+this will be logged in \fIpathlog\fR/news as going to the site \f(CW\*(C`example\*(C'\fR
+rather than \f(CW\*(C`feed.example.com\*(C'\fR.
+.PP
+The typical feed entry for \fIinnfeed\fR\|(8) is a good example of a channel feed
+that's the target of various funnel feeds:
+.PP
+.Vb 1
+\& innfeed!:!*:Tc,Wnm*:/usr/local/news/bin/startinnfeed \-y
+.Ve
+.PP
+Note that the \fIpattern\fR for this feed is just \f(CW\*(C`!*\*(C'\fR so that it won't
+receive any articles directly. The feed should only receive those
+articles that would go to one of the funnel feeds that are feeding into
+it. \fIinnfeed\fR\|(8) (spawned by \fBstartinnfeed\fR) will receive one line per
+article on its standard input containing the storage \s-1API\s0 token, the
+message \s-1ID\s0, and a space-separated list of sites that should receive that
+article.
+.PP
+Here's a more esoteric example of a channel feed:
+.PP
+.Vb 2
+\& watcher!:*:Tc,Wbnm\e
+\& :exec awk '$1 > 1000000 { print "BIG", $2, $3 }' > /dev/console
+.Ve
+.PP
+This receives the byte size of each article along with the storage \s-1API\s0
+token and message \s-1ID\s0, and prints to the console a line for every article
+that's over a million bytes. This is actually rather a strange way to
+write this since \s-1INN\s0 can do the size check itself; the following is
+equivalent:
+.PP
+.Vb 2
+\& watcher!:*:Tc,>1000000,Wbnm\e
+\& :exec awk '{ print "BIG", $2, $3}' > /dev/console
+.Ve
+.PP
+Here's a cute, really simple news to mail gateway that also serves as an
+example of a fairly fancy program feed:
+.PP
+.Vb 2
+\& mailer!:!*:W*,Tp\e
+\& :sm %s | innmail \-s "News article" *
+.Ve
+.PP
+Remember that \f(CW%s\fR is replaced by the storage \s-1API\s0 token, so this
+retrieves the article and pipes it into \fBinnmail\fR (which is safer than
+programs like \fIMail\fR\|(1) because it doesn't parse the body for tilde
+commands) with a given subject line. Note the use of \f(CW\*(C`*\*(C'\fR in the command
+line and \fBW*\fR in the flags; this entry is designed to be used as the
+target of funnel feeds such as:
+.PP
+.Vb 2
+\& peter@example.com:news.software.nntp:Tm:mailer!
+\& sue@example.com:news.admin.misc:Tm:mailer!
+.Ve
+.PP
+Suppose that the server receives an article crossposted between
+news.admin.misc and news.software.nntp. The server will notice that the
+article should be sent to the site \f(CW\*(C`peter@example.com\*(C'\fR and the site
+\&\f(CW\*(C`bob@example.com\*(C'\fR, both of which funnel into \f(CW\*(C`mailer!\*(C'\fR, so it will look
+at the \f(CW\*(C`mailer!\*(C'\fR site and end up executing the command line:
+.PP
+.Vb 1
+\& sm @...@ | innmail \-s "News article" peter@example.com sue@example.com
+.Ve
+.PP
+which will mail the article to both Peter and Sue.
+.PP
+Finally, another very useful example of a channel feed: the standard
+entry for \fIcontrolchan\fR\|(8).
+.PP
+.Vb 3
+\& controlchan!\e
+\& :!*,control,control.*,!control.cancel/!collabra\-internal\e
+\& :Tc,Wnsm:/usr/local/news/bin/controlchan
+.Ve
+.PP
+This program only wants information about articles posted to a control
+newsgroup other than control.cancel, which due to the sorting of control
+messages described in \fIinnd\fR\|(8) will send it all control messages except for
+cancel messages. In this case, we also exclude any article with a
+distribution of \f(CW\*(C`collabra\-internal\*(C'\fR. \fBcontrolchan\fR gets the storage
+\&\s-1API\s0 token, the name of the sending site (for processing old-style ihave
+and sendme control messages, be sure to read about \fIlogipaddr\fR in
+\&\fIcontrolchan\fR\|(8)), and the message \s-1ID\s0 for each article.
+.PP
+For many other examples, including examples of the special \f(CW\*(C`ME\*(C'\fR site
+entry, see the example \fInewsfeeds\fR file distributed with \s-1INN\s0. Also see the
+install documentation that comes with \s-1INN\s0 for information about setting up
+the standard newsfeeds entries used by most sites.
+.SH "HISTORY"
+.IX Header "HISTORY"
+Written by Rich \f(CW$alz\fR <rsalz@uunet.uu.net> for InterNetNews. Reformatted
+and rewritten in \s-1POD\s0 by Russ Allbery <rra@stanford.edu>.
+.PP
+$Id: newsfeeds.5 7880 2008-06-16 20:37:13Z iulius $
+.SH "SEE ALSO"
+.IX Header "SEE ALSO"
+\&\fIactive\fR\|(5), \fIbuffchan\fR\|(8), \fIcontrolchan\fR\|(8), \fIctlinnd\fR\|(8), \fIinn.conf\fR\|(5), \fIinnd\fR\|(8),
+\&\fIinnfeed\fR\|(8), \fIinnxmit\fR\|(8), \fInntpsend\fR\|(8), \fIuwildmat\fR\|(3).
--- /dev/null
+.TH NEWSLOG 5
+.SH NAME
+newslog \- description of Usenet log files
+.SH DESCRIPTION
+Most log files created by Usenet programs reside in the
+.I <pathlog in inn.conf>
+directory and have a ``.log'' extension.
+Several versions are usually kept with an additional extension such as ``.1'',
+``.2'', etc. \(em the higher the number, the older the log.
+The older versions may be compressed and thus may have a ``.1.gz'',
+``.2.gz'', etc. extension.
+.PP
+The
+.I scanlogs
+script and related utilities (see
+.IR scanlogs (8))
+are responsible for rotating and compressing these files.
+.PP
+Some log files always have data, others only have data if there is a
+problem, and others are only created if a particular program is used
+or configuration parameter is set.
+The
+.I innstat
+script (see
+.IR innstat (8))
+monitors the size of all log files.
+.PP
+The following files will only accumulate data under the direction of
+.IR control.ctl (5):
+.sp 1
+.RS
+control.log
+miscctl.log
+newgroup.log
+rmgroup.log
+unwanted.log
+.RE
+.sp 1
+In order to create these files, the ``message'' and ``action'' fields of
+.I control.ctl
+should be chosen from the following table:
+.sp 1
+.RS
+.nf
+.ta \w'newgroup 'u +\w'doit=newgroup 'u
+Message Action Meaning
+all log=miscctl Log all messages by default
+default log=miscctl Log unknown messages
+newgroup doit=newgroup Create group and log message
+newgroup log=newgroup Log message
+rmgroup doit=rmgroup Remove group and log message
+rmgroup log=rmgroup Log message
+``other'' doit=miscctl log and process the message
+``other'' log=miscctl Log message
+.fi
+.RE
+.sp 1
+Here, ``other'' refers to any other control message such as:
+.sp 1
+.RS
+.nf
+checkgroups
+ihave
+sendme
+sendsys
+senduuname
+version
+.fi
+.RE
+.PP
+The following is a list of log files:
+.TP
+.I control.log
+This file maintains a count of the number of newgroup and rmgroup control
+messages seen for each newsgroup.
+The count is of the number of control messages with the indicated
+arguments, regardless if they were actually processed.
+All control arguments, including invalid ones, are counted.
+This file is updated by
+.IR tally.control ,
+which is invoked by
+.I scanlogs
+if either the newgroup or rmgroup logs exist.
+This file is not rotated.
+.TP
+.I errlog
+This file contains the standard output and standard error of any program
+spawned by
+.IR innd (8),
+such as channel feeds configured in
+.IR newsfeeds .
+This file should normally be empty.
+.I Scanlogs
+will print the entire contents of this log file if it is non-empty.
+.TP
+.I expire.log
+By default, when
+.I news.daily
+is going to expire old news articles, it writes the date to this file,
+followed by any output from
+.IR expire (8)
+and the ending date.
+All lines but the first are indented four spaces.
+.TP
+.I miscctl.log
+When
+.I control.ctl
+is configured as described above, all control messages except newgroup
+and rmgroup are appended to this file by
+.IR writelog .
+There will be a summary line describing the message and the action
+taken, followed by the article indented by four spaces, and a blank line.
+.TP
+.I newgroup.log
+When
+.I control.ctl
+is configured as described above, all newgroup messages are appended
+to this file using the same format as for
+.IR miscctl.log .
+.TP
+.I news
+This file logs articles received by
+.IR innd .
+.I Scanlogs
+summarizes the rejected articles reported in this file.
+.TP
+.I news.crit
+All critical error messages issued by
+.I innd
+are appended to this file via
+.IR syslog (3).
+This log file should be empty.
+.I Scanlogs
+will print the entire contents of this log file if it is non-empty.
+You should have the following line in your system
+.I syslog.conf
+file, using a tab character for the delimiter:
+.sp 1
+.RS
+.RS
+news.crit <pathlog in inn.conf>/news.crit
+.RE
+.RE
+.sp 1
+(A typical entry is shown; it should agree with
+.I <pathlog in inn.conf>.)
+.TP
+.I news.err
+All major error messages issued by
+.I innd
+are appended to this file via
+.IR syslog (3).
+This log file should be empty.
+.I Scanlogs
+will print the entire contents of this log file if it is non-empty.
+You should have the following line in your system
+.I syslog.conf
+file, using a tab character for the delimiter:
+.sp 1
+.RS
+.RS
+news.err <pathlog in inn.conf>/news.err
+.RE
+.RE
+.sp 1
+(A typical entry is shown; it should agree with
+.I <pathlog in inn.conf>.)
+.TP
+.I news.notice
+All standard error messages and status messages issued by
+.I innd
+are appended to this file via
+.IR syslog (3).
+.I Scanlogs
+uses the
+.IR perl (1)
+script
+.IR innreport (8)
+to summarize this file.
+You should have the following line in your system
+.I syslog.conf
+file, using a tab character for the delimiter:
+.sp 1
+.RS
+.RS
+news.notice <pathlog in inn.conf>/news.notice
+.RE
+.RE
+(A typical entry is shown; it should agree with
+.I <pathlog in inn.conf>.)
+.TP
+.I nntpsend.log
+The
+.IR nntpsend (8)
+programs appends all status messages to this file.
+.TP
+.I rmgroup.log
+When
+.I control.ctl
+is configured as described above, all rmgroup messages are appended to this
+file using the same format as for
+.IR miscctl.log .
+.TP
+.I unwanted.log
+This log maintains a count of the number of articles that were rejected
+because they were posted to newsgroups that do not exist at the local site.
+This file is updated by
+.I tally.unwanted
+and maintained in reverse numeric order (the most popular rejected group
+first).
+This file is not rotated.
+.SH HISTORY
+Written by Landon Curt Noll <chongo@toad.com> and Rich $alz
+<rsalz@uunet.uu.net> for InterNetNews.
+.de R$
+This is revision \\$3, dated \\$4.
+..
+.R$ $Id: newslog.5 6398 2003-07-12 19:15:50Z rra $
+.SH "SEE ALSO"
+control.ctl(5),
+ctlinnd(8),
+expire(8),
+inn.conf(5),
+innd(8),
+news.daily(8),
+nntpsend(8),
+syslog.conf(5).
--- /dev/null
+.\" Automatically generated by Pod::Man v1.37, Pod::Parser v1.32
+.\"
+.\" Standard preamble:
+.\" ========================================================================
+.de Sh \" Subsection heading
+.br
+.if t .Sp
+.ne 5
+.PP
+\fB\\$1\fR
+.PP
+..
+.de Sp \" Vertical space (when we can't use .PP)
+.if t .sp .5v
+.if n .sp
+..
+.de Vb \" Begin verbatim text
+.ft CW
+.nf
+.ne \\$1
+..
+.de Ve \" End verbatim text
+.ft R
+.fi
+..
+.\" Set up some character translations and predefined strings. \*(-- will
+.\" give an unbreakable dash, \*(PI will give pi, \*(L" will give a left
+.\" double quote, and \*(R" will give a right double quote. \*(C+ will
+.\" give a nicer C++. Capital omega is used to do unbreakable dashes and
+.\" therefore won't be available. \*(C` and \*(C' expand to `' in nroff,
+.\" nothing in troff, for use with C<>.
+.tr \(*W-
+.ds C+ C\v'-.1v'\h'-1p'\s-2+\h'-1p'+\s0\v'.1v'\h'-1p'
+.ie n \{\
+. ds -- \(*W-
+. ds PI pi
+. if (\n(.H=4u)&(1m=24u) .ds -- \(*W\h'-12u'\(*W\h'-12u'-\" diablo 10 pitch
+. if (\n(.H=4u)&(1m=20u) .ds -- \(*W\h'-12u'\(*W\h'-8u'-\" diablo 12 pitch
+. ds L" ""
+. ds R" ""
+. ds C` ""
+. ds C' ""
+'br\}
+.el\{\
+. ds -- \|\(em\|
+. ds PI \(*p
+. ds L" ``
+. ds R" ''
+'br\}
+.\"
+.\" If the F register is turned on, we'll generate index entries on stderr for
+.\" titles (.TH), headers (.SH), subsections (.Sh), items (.Ip), and index
+.\" entries marked with X<> in POD. Of course, you'll have to process the
+.\" output yourself in some meaningful fashion.
+.if \nF \{\
+. de IX
+. tm Index:\\$1\t\\n%\t"\\$2"
+..
+. nr % 0
+. rr F
+.\}
+.\"
+.\" For nroff, turn off justification. Always turn off hyphenation; it makes
+.\" way too many mistakes in technical documents.
+.hy 0
+.if n .na
+.\"
+.\" Accent mark definitions (@(#)ms.acc 1.5 88/02/08 SMI; from UCB 4.2).
+.\" Fear. Run. Save yourself. No user-serviceable parts.
+. \" fudge factors for nroff and troff
+.if n \{\
+. ds #H 0
+. ds #V .8m
+. ds #F .3m
+. ds #[ \f1
+. ds #] \fP
+.\}
+.if t \{\
+. ds #H ((1u-(\\\\n(.fu%2u))*.13m)
+. ds #V .6m
+. ds #F 0
+. ds #[ \&
+. ds #] \&
+.\}
+. \" simple accents for nroff and troff
+.if n \{\
+. ds ' \&
+. ds ` \&
+. ds ^ \&
+. ds , \&
+. ds ~ ~
+. ds /
+.\}
+.if t \{\
+. ds ' \\k:\h'-(\\n(.wu*8/10-\*(#H)'\'\h"|\\n:u"
+. ds ` \\k:\h'-(\\n(.wu*8/10-\*(#H)'\`\h'|\\n:u'
+. ds ^ \\k:\h'-(\\n(.wu*10/11-\*(#H)'^\h'|\\n:u'
+. ds , \\k:\h'-(\\n(.wu*8/10)',\h'|\\n:u'
+. ds ~ \\k:\h'-(\\n(.wu-\*(#H-.1m)'~\h'|\\n:u'
+. ds / \\k:\h'-(\\n(.wu*8/10-\*(#H)'\z\(sl\h'|\\n:u'
+.\}
+. \" troff and (daisy-wheel) nroff accents
+.ds : \\k:\h'-(\\n(.wu*8/10-\*(#H+.1m+\*(#F)'\v'-\*(#V'\z.\h'.2m+\*(#F'.\h'|\\n:u'\v'\*(#V'
+.ds 8 \h'\*(#H'\(*b\h'-\*(#H'
+.ds o \\k:\h'-(\\n(.wu+\w'\(de'u-\*(#H)/2u'\v'-.3n'\*(#[\z\(de\v'.3n'\h'|\\n:u'\*(#]
+.ds d- \h'\*(#H'\(pd\h'-\w'~'u'\v'-.25m'\f2\(hy\fP\v'.25m'\h'-\*(#H'
+.ds D- D\\k:\h'-\w'D'u'\v'-.11m'\z\(hy\v'.11m'\h'|\\n:u'
+.ds th \*(#[\v'.3m'\s+1I\s-1\v'-.3m'\h'-(\w'I'u*2/3)'\s-1o\s+1\*(#]
+.ds Th \*(#[\s+2I\s-2\h'-\w'I'u*3/5'\v'-.3m'o\v'.3m'\*(#]
+.ds ae a\h'-(\w'a'u*4/10)'e
+.ds Ae A\h'-(\w'A'u*4/10)'E
+. \" corrections for vroff
+.if v .ds ~ \\k:\h'-(\\n(.wu*9/10-\*(#H)'\s-2\u~\d\s+2\h'|\\n:u'
+.if v .ds ^ \\k:\h'-(\\n(.wu*10/11-\*(#H)'\v'-.4m'^\v'.4m'\h'|\\n:u'
+. \" for low resolution devices (crt and lpr)
+.if \n(.H>23 .if \n(.V>19 \
+\{\
+. ds : e
+. ds 8 ss
+. ds o a
+. ds d- d\h'-1'\(ga
+. ds D- D\h'-1'\(hy
+. ds th \o'bp'
+. ds Th \o'LP'
+. ds ae ae
+. ds Ae AE
+.\}
+.rm #[ #] #H #V #F C
+.\" ========================================================================
+.\"
+.IX Title "NINPATHS 8"
+.TH NINPATHS 8 "2008-04-06" "INN 2.4.5" "InterNetNews Documentation"
+.SH "NAME"
+ninpaths \- Report Usenet Path statistics (new inpaths)
+.SH "SYNOPSIS"
+.IX Header "SYNOPSIS"
+\&\fBninpaths\fR \fB\-p\fR \fB\-d\fR \fIdumpfile\fR
+.PP
+\&\fBninpaths\fR \fB\-r\fR \fIsite\fR \fB\-u\fR \fIdumpfile\fR [\fB\-u\fR \fIdumpfile\fR ...] \fB\-v\fR
+\&\fIlevel\fR
+.SH "DESCRIPTION"
+.IX Header "DESCRIPTION"
+This is an efficient and space-saving inpaths reporting program. It works
+as follows: you feed it the Path lines via an \s-1INN\s0 channel feed or some
+other similar method, and from time to time the program writes all its
+internal counters accumulated so far to a dump file. Another instance of
+the program picks up all the dump files, adds them up and formats them
+into the report. The purpose of the final report is to summarize the
+frequency of occurrence of sites in the Path headers of articles.
+.PP
+Some central sites accumulate the Path data from many news servers running
+this program or one like it, and then report statistics on the most
+frequently seen news servers in Usenet article Path lines. The
+\&\fBsendinpaths\fR shell script can be run once a month to mail the
+accumulated statistics to such a site and remove the old dump files.
+.PP
+You can get a working setup by doing the following:
+.IP "1." 4
+Create a directory at \fIpathlog\fR/path (replacing \fIpathlog\fR here and in
+all steps that follow with the full path to your \s-1INN\s0 log directory).
+.IP "2." 4
+Set up a channel feed using an entry like:
+.Sp
+.Vb 1
+\& inpaths!:*:Tc,WP:ninpaths \-p \-d <pathlog>/path/inpaths.%d
+.Ve
+.Sp
+if your version of \s-1INN\s0 supports \s-1WP\s0 (2.0 and later all do). Replace
+<pathlog> with the full path to your \s-1INN\s0 log directory.
+.IP "3." 4
+Enter into your news user crontab something like:
+.Sp
+.Vb 1
+\& 6 6 * * * ctlinnd flush inpaths!
+.Ve
+.Sp
+(the actual time doesn't matter). This will force \fBninpaths\fR to generate
+a dump file once a day.
+.IP "4." 4
+Once per month, run the \fBsendinpaths\fR script, which collects the dumps,
+makes a report, and then deletes the old dumps. (You can generate a
+report without mailing it and without deleting it with \f(CW\*(C`sendinpaths \-n\*(C'\fR.)
+.SH "OPTIONS"
+.IX Header "OPTIONS"
+.IP "\fB\-d\fR \fIdumpfile\fR" 4
+.IX Item "-d dumpfile"
+Save dumps in \fIdumpfile\fR. Any \f(CW%d\fR in \fIdumpfile\fR will be replaced with
+the current system time when the dump is made. This option should be used
+with \fB\-p\fR.
+.IP "\fB\-p\fR" 4
+.IX Item "-p"
+Read Path lines from standard input.
+.IP "\fB\-r\fR \fIsite\fR" 4
+.IX Item "-r site"
+Generate a report for \fIsite\fR. Generally \fIsite\fR should be the value of
+\&\fIpathhost\fR from \fIinn.conf\fR.
+.IP "\fB\-u\fR \fIdumpfile\fR" 4
+.IX Item "-u dumpfile"
+Read data from \fIdumpfile\fR. This option can be repeated to read data from
+multiple dump files.
+.IP "\fB\-v\fR \fIlevel\fR" 4
+.IX Item "-v level"
+Set the verbosity level of the report. Valid values for \fIlevel\fR are 0,
+1, and 2, with 2 being the default.
+.SH "NOTES"
+.IX Header "NOTES"
+If your \s-1INN\s0 doesn't have the \s-1WP\s0 feed flag (1.5 does not, 1.6 does, 1.7 I
+don't know, 2.0 and later all do), use the following newsfeeds entry:
+.PP
+.Vb 1
+\& inpaths!:*:Tc,WH:ginpaths
+.Ve
+.PP
+where \fBginpaths\fR is the following script:
+.PP
+.Vb 2
+\& #!/bin/sh
+\& exec egrep '^Path: ' | ninpaths \-p \-d <pathlog>/path/inpaths.%d
+.Ve
+.PP
+replacing <pathlog> as above.
+.SH "SEE ALSO"
+.IX Header "SEE ALSO"
+\&\fInewsfeeds\fR\|(5), \fIsendinpaths\fR\|(8)
+.PP
+This is a slightly modified version of Olaf Titz's original ninpaths
+program, which is posted to alt.sources and kept on his \s-1WWW\s0 archive under
+<http://sites.inka.de/~bigred/sw/>.
+.SH "HISTORY"
+.IX Header "HISTORY"
+\&\fBninpaths\fR was written by Olaf Titz <olaf@bigred.inka.de>.
+.PP
+The idea and some implementation details for ninpaths come from the
+original inpaths program, but most of the code has been rewritten for
+clarity. This program is in the public domain.
--- /dev/null
+.\" Automatically generated by Pod::Man v1.37, Pod::Parser v1.32
+.\"
+.\" Standard preamble:
+.\" ========================================================================
+.de Sh \" Subsection heading
+.br
+.if t .Sp
+.ne 5
+.PP
+\fB\\$1\fR
+.PP
+..
+.de Sp \" Vertical space (when we can't use .PP)
+.if t .sp .5v
+.if n .sp
+..
+.de Vb \" Begin verbatim text
+.ft CW
+.nf
+.ne \\$1
+..
+.de Ve \" End verbatim text
+.ft R
+.fi
+..
+.\" Set up some character translations and predefined strings. \*(-- will
+.\" give an unbreakable dash, \*(PI will give pi, \*(L" will give a left
+.\" double quote, and \*(R" will give a right double quote. \*(C+ will
+.\" give a nicer C++. Capital omega is used to do unbreakable dashes and
+.\" therefore won't be available. \*(C` and \*(C' expand to `' in nroff,
+.\" nothing in troff, for use with C<>.
+.tr \(*W-
+.ds C+ C\v'-.1v'\h'-1p'\s-2+\h'-1p'+\s0\v'.1v'\h'-1p'
+.ie n \{\
+. ds -- \(*W-
+. ds PI pi
+. if (\n(.H=4u)&(1m=24u) .ds -- \(*W\h'-12u'\(*W\h'-12u'-\" diablo 10 pitch
+. if (\n(.H=4u)&(1m=20u) .ds -- \(*W\h'-12u'\(*W\h'-8u'-\" diablo 12 pitch
+. ds L" ""
+. ds R" ""
+. ds C` ""
+. ds C' ""
+'br\}
+.el\{\
+. ds -- \|\(em\|
+. ds PI \(*p
+. ds L" ``
+. ds R" ''
+'br\}
+.\"
+.\" If the F register is turned on, we'll generate index entries on stderr for
+.\" titles (.TH), headers (.SH), subsections (.Sh), items (.Ip), and index
+.\" entries marked with X<> in POD. Of course, you'll have to process the
+.\" output yourself in some meaningful fashion.
+.if \nF \{\
+. de IX
+. tm Index:\\$1\t\\n%\t"\\$2"
+..
+. nr % 0
+. rr F
+.\}
+.\"
+.\" For nroff, turn off justification. Always turn off hyphenation; it makes
+.\" way too many mistakes in technical documents.
+.hy 0
+.if n .na
+.\"
+.\" Accent mark definitions (@(#)ms.acc 1.5 88/02/08 SMI; from UCB 4.2).
+.\" Fear. Run. Save yourself. No user-serviceable parts.
+. \" fudge factors for nroff and troff
+.if n \{\
+. ds #H 0
+. ds #V .8m
+. ds #F .3m
+. ds #[ \f1
+. ds #] \fP
+.\}
+.if t \{\
+. ds #H ((1u-(\\\\n(.fu%2u))*.13m)
+. ds #V .6m
+. ds #F 0
+. ds #[ \&
+. ds #] \&
+.\}
+. \" simple accents for nroff and troff
+.if n \{\
+. ds ' \&
+. ds ` \&
+. ds ^ \&
+. ds , \&
+. ds ~ ~
+. ds /
+.\}
+.if t \{\
+. ds ' \\k:\h'-(\\n(.wu*8/10-\*(#H)'\'\h"|\\n:u"
+. ds ` \\k:\h'-(\\n(.wu*8/10-\*(#H)'\`\h'|\\n:u'
+. ds ^ \\k:\h'-(\\n(.wu*10/11-\*(#H)'^\h'|\\n:u'
+. ds , \\k:\h'-(\\n(.wu*8/10)',\h'|\\n:u'
+. ds ~ \\k:\h'-(\\n(.wu-\*(#H-.1m)'~\h'|\\n:u'
+. ds / \\k:\h'-(\\n(.wu*8/10-\*(#H)'\z\(sl\h'|\\n:u'
+.\}
+. \" troff and (daisy-wheel) nroff accents
+.ds : \\k:\h'-(\\n(.wu*8/10-\*(#H+.1m+\*(#F)'\v'-\*(#V'\z.\h'.2m+\*(#F'.\h'|\\n:u'\v'\*(#V'
+.ds 8 \h'\*(#H'\(*b\h'-\*(#H'
+.ds o \\k:\h'-(\\n(.wu+\w'\(de'u-\*(#H)/2u'\v'-.3n'\*(#[\z\(de\v'.3n'\h'|\\n:u'\*(#]
+.ds d- \h'\*(#H'\(pd\h'-\w'~'u'\v'-.25m'\f2\(hy\fP\v'.25m'\h'-\*(#H'
+.ds D- D\\k:\h'-\w'D'u'\v'-.11m'\z\(hy\v'.11m'\h'|\\n:u'
+.ds th \*(#[\v'.3m'\s+1I\s-1\v'-.3m'\h'-(\w'I'u*2/3)'\s-1o\s+1\*(#]
+.ds Th \*(#[\s+2I\s-2\h'-\w'I'u*3/5'\v'-.3m'o\v'.3m'\*(#]
+.ds ae a\h'-(\w'a'u*4/10)'e
+.ds Ae A\h'-(\w'A'u*4/10)'E
+. \" corrections for vroff
+.if v .ds ~ \\k:\h'-(\\n(.wu*9/10-\*(#H)'\s-2\u~\d\s+2\h'|\\n:u'
+.if v .ds ^ \\k:\h'-(\\n(.wu*10/11-\*(#H)'\v'-.4m'^\v'.4m'\h'|\\n:u'
+. \" for low resolution devices (crt and lpr)
+.if \n(.H>23 .if \n(.V>19 \
+\{\
+. ds : e
+. ds 8 ss
+. ds o a
+. ds d- d\h'-1'\(ga
+. ds D- D\h'-1'\(hy
+. ds th \o'bp'
+. ds Th \o'LP'
+. ds ae ae
+. ds Ae AE
+.\}
+.rm #[ #] #H #V #F C
+.\" ========================================================================
+.\"
+.IX Title "NNRPD 8"
+.TH NNRPD 8 "2008-04-06" "INN 2.4.5" "InterNetNews Documentation"
+.SH "NAME"
+nnrpd \- NNTP server for reader clients
+.SH "SYNOPSIS"
+.IX Header "SYNOPSIS"
+\&\fBnnrpd\fR [\fB\-DfnoSt\fR] [\fB\-b\fR \fIaddress\fR] [\fB\-c\fR \fIconfigfile\fR]
+[\fB\-g\fR \fIshadowgroup\fR>] [\fB\-i\fR \fIinitial\fR] [\fB\-I\fR \fIinstance\fR] [\fB\-p\fR \fIport\fR]
+[\fB\-P\fR \fIprefork\fR] [\fB\-r\fR \fIreason\fR] [\fB\-s\fR \fIpadding\fR]
+.SH "DESCRIPTION"
+.IX Header "DESCRIPTION"
+\&\fBnnrpd\fR is an \s-1NNTP\s0 server for newsreaders. It accepts commands on its
+standard input and responds on its standard output. It is normally
+invoked by \fIinnd\fR\|(8) with those descriptors attached to a remote client
+connection. \fBnnrpd\fR also supports running as a standalone daemon.
+.PP
+Unlike \fIinnd\fR\|(8) \fBnnrpd\fR supports all \s-1NNTP\s0 commands for user-oriented
+reading and posting. \fBnnrpd\fR uses the \fIreaders.conf\fR file to control
+who is authorized to access the Usenet database.
+.PP
+On exit, \fBnnrpd\fR will report usage statistics through \fIsyslog\fR\|(3).
+.PP
+\&\fBnnrpd\fR only reads config files (both \fIreaders.conf\fR and \fIinn.conf\fR)
+when it is spawned. You can therefore never change the behavior of a
+client that's already connected. If \fBnnrpd\fR is run from \fBinnd\fR (the
+default) or from \fIinetd\fR\|(8), \fIxinetd\fR\|(8), or some equivalent, a new \fBnnrpd\fR
+process is spawned for every connection and therefore any changes to
+configuration files will be immediately effective for all new
+connections. If you are instead running \fBnnrpd\fR with the \fB\-D\fR option,
+any configuration changes won't take effect until \fBnnrpd\fR is restarted.
+.PP
+The \fIinn.conf\fR setting \fInnrpdflags\fR can be used to pass any of the
+options below to instances of \fBnnrpd\fR that are spawned directly from
+\&\fBinnd\fR. Many options only make sense when \fB\-D\fR is used, so these
+options should not be used with \fInnrpdflags\fR. See also the discussion
+of \fInnrpdflags\fR in \fIinn.conf\fR\|(5).
+.PP
+When \fInnrpdloadlimit\fR in \fIinn.conf\fR is not 0, it will also reject
+connections if the load average is greater than that value (typically 16).
+\&\fBnnrpd\fR can also prevent high-volume posters from abusing your
+resources. See the discussion of exponential backoff in \fIinn.conf\fR\|(5).
+.SH "OPTIONS"
+.IX Header "OPTIONS"
+.IP "\fB\-b\fR \fIaddress\fR" 4
+.IX Item "-b address"
+The \fB\-b\fR parameter instructs \fBnnrpd\fR to bind to the specified \s-1IP\s0
+address when started as a standalone daemon using the \fB\-D\fR flag. This
+has to be a valid IPv4 or IPv6 address belonging to an interface of
+the local host. It can also be ::0 (although the default is 0.0.0.0
+if unspecified).
+.IP "\fB\-c\fR \fIconfigfile\fR" 4
+.IX Item "-c configfile"
+By default, \fBnnrpd\fR reads the \fIreaders.conf\fR to determine how to
+authenticate connections. The \fB\-c\fR flag specifies an alternate file
+for this purpose. If the file name isn't fully qualified, it is taken
+to be relative to \fIpathetc\fR in \fIinn.conf\fR (this is useful to have
+several instances of \fBnnrpd\fR running on different ports or \s-1IP\s0
+addresses with different settings.)
+.IP "\fB\-D\fR" 4
+.IX Item "-D"
+If specified, this parameter causes \fBnnrpd\fR to operate as a
+daemon. That is, it detaches itself and runs in the background,
+forking a process for every connection. By default \fBnnrpd\fR listens on
+the \s-1NNTP\s0 port (119), so either \fIinnd\fR\|(8) has to be started on another
+port or \fBnnrpd\fR \fB\-p\fR parameter. Note that with this parameter,
+\&\fBnnrpd\fR continues running until killed. This means that it reads
+\&\fIinn.conf\fR once on startup and never again until restarted. \fBnnrpd\fR
+should therefore be restarted if inn.conf is changed.
+.Sp
+When started in daemon mode, \fBnnrpd\fR will write its \s-1PID\s0 into a file in
+the \fIpathrun\fR directory. The file will be named \fInnrpd\-%d.pid\fR, where
+\&\f(CW%d\fR is replaced with the port that \fBnnrpd\fR is configured to listen on
+(119 unless the \fB\-p\fR option is given).
+.IP "\fB\-f\fR" 4
+.IX Item "-f"
+If specified, \fBnnrpd\fR does not detach itself and runs in the
+foreground when started as a standalone daemon using the \fB\-D\fR flag.
+.IP "\fB\-g\fR \fIshadowgroup\fR" 4
+.IX Item "-g shadowgroup"
+On systems that have a shadow password file, \fBnnrpd\fR tries to add the
+group \fIshadow\fR as a supplementary group if it is running in
+standalone mode. On many systems, members of that group have read
+permission for the shadow password file. The \fB\-g\fR parameter instructs
+\&\fBnnrpd\fR to try to add the named group as a supplementary group on
+shadow systems instead of \fIshadow\fR. This only works if
+\&\f(CW\*(C`HAVE_GETSPNAM\*(C'\fR in \fIinclude/config.h\fR is defined and \fBnnrpd\fR is
+running in standalone mode since this call only works when \fBnnrpd\fR is
+started as root.
+.IP "\fB\-i\fR \fIinitial\fR" 4
+.IX Item "-i initial"
+Specify an initial command to \fBnnrpd\fR. When used, \fIinitial\fR is taken
+as if it were the first command received by \fBnnrpd\fR.
+.IP "\fB\-I\fR \fIinstance\fR" 4
+.IX Item "-I instance"
+If specified \fIinstance\fR is used as an additional static portion
+within MessageIDs generated by \fBnnrpd\fR; typically this option would
+be used where a cluster of machines exist with the same virtual
+hostname and must be disambiguated during posts.
+.IP "\fB\-n\fR" 4
+.IX Item "-n"
+The \fB\-n\fR flag turns off resolution of \s-1IP\s0 addresses to names. If you
+only use IP-based restrictions in \fIreaders.conf\fR and can handle \s-1IP\s0
+addresses in your logs, using this flag may result in some additional
+speed.
+.IP "\fB\-o\fR" 4
+.IX Item "-o"
+The \fB\-o\fR flag causes all articles to be spooled instead of sending
+them to \fIinnd\fR\|(8). \fBrnews\fR with the \fB\-U\fR flag should be invoked from
+cron on a regular basis to take care of these articles. This flag is
+useful if \fIinnd\fR\|(8) in accepting articles and \fBnnrpd\fR is started
+standalone or using \fIinetd\fR\|(8).
+.IP "\fB\-p\fR \fIport\fR" 4
+.IX Item "-p port"
+The \fB\-p\fR parameter instructs \fBnnrpd\fR to listen on \fIport\fR when
+started as a standalone daemon using the \fB\-D\fR flag.
+.IP "\fB\-P\fR \fIprefork\fR" 4
+.IX Item "-P prefork"
+The \fB\-P\fR parameter instructs \fBnnrpd\fR to prefork \fIprefork\fR children
+awaiting connections when started as a standalone daemon using the
+\&\fB\-D\fR flag.
+.IP "\fB\-r\fR \fIreason\fR" 4
+.IX Item "-r reason"
+If the \fB\-r\fR flag is used, then \fBnnrpd\fR will reject the incoming
+connection giving \fIreason\fR as the text. This flag is used by \fIinnd\fR\|(8)
+when it is paused or throttled.
+.IP "\fB\-s\fR \fIpadding\fR" 4
+.IX Item "-s padding"
+As each command is received, \fBnnrpd\fR tries to change its \f(CW\*(C`argv\*(C'\fR
+array so that \fIps\fR\|(1) will print out the command being executed. To get
+a full display, the \fB\-s\fR flag may be used with a long string as its
+argument, which will be overwritten when the program changes its
+title.
+.IP "\fB\-S\fR" 4
+.IX Item "-S"
+If specified, \fBnnrpd\fR will start a negotiation for \s-1SSL\s0 session as
+soon as connected. To use this flag, \f(CW\*(C`\-\-with\-openssl\*(C'\fR must have been
+specified at \f(CW\*(C`configure\*(C'\fR time.
+.IP "\fB\-t\fR" 4
+.IX Item "-t"
+If the \fB\-t\fR flag is used then all client commands and initial
+responses will be traced by reporting them in syslog. This flag is set
+by \fIinnd\fR\|(8) under the control of the \fIctlinnd\fR\|(8) \f(CW\*(C`trace\*(C'\fR command, and
+is toggled upon receipt of a \f(CW\*(C`SIGHUP\*(C'\fR; see \fIsignal\fR\|(2).
+.SH "SSL SUPPORT"
+.IX Header "SSL SUPPORT"
+If \s-1INN\s0 is built with \f(CW\*(C`\-\-with\-openssl\*(C'\fR, \fBnnrpd\fR will support news reading
+over \s-1TLS\s0 (also known as \s-1SSL\s0). For clients that use the \s-1STARTTLS\s0 command,
+no special configuration is needed beyond creating a \s-1TLS/SSL\s0 certificate
+for the server. You should do this in exactly the same way that you would
+generate a certificate for a web server.
+.PP
+If you're happy with a self-signed certificate (which will generate
+warnings with some news reader clients), you can create and install one in
+the default path by running \f(CW\*(C`make cert\*(C'\fR after \f(CW\*(C`make install\*(C'\fR when
+installing \s-1INN\s0, or by running the following commands:
+.PP
+.Vb 6
+\& openssl req \-new \-x509 \-nodes \-out /usr/local/news/lib/cert.pem \e
+\& \-days 366 \-keyout /usr/local/news/lib/key.pem
+\& chown news:news /usr/local/news/lib/cert.pem
+\& chmod 640 /usr/local/news/lib/cert.pem
+\& chown news:news /usr/local/news/lib/key.pem
+\& chmod 600 /usr/local/news/lib/key.pem
+.Ve
+.PP
+Replace the paths with something appropriate to your \s-1INN\s0 installation.
+This will create a self-signed certificate that will expire in a year.
+The \fBopenssl\fR program will ask you a variety of questions about your
+organization. Enter the fully qualified domain name of the server as the
+name the certificate is for.
+.PP
+Most news clients currently do not use the \s-1STARTTLS\s0 command, however, and
+instead expect to connect to a separate port (563) and start an \s-1SSL\s0
+negotiation immediately. \fBinnd\fR does not, however, know how to listen
+for connections to that port and then spawn \fBnnrpd\fR the way that it does
+for regular reader connections. You will therefore need to arrange for
+\&\fBnnrpd\fR to listen on that port through some other means. This can be
+done with the \fB\-D\fR flag (and \f(CW\*(C`\-P 563\*(C'\fR), but the easiest way is probably
+to add a line like:
+.PP
+.Vb 1
+\& nntps stream tcp nowait news /usr/lib/news/bin/nnrpd nnrpd \-S
+.Ve
+.PP
+to \fI/etc/inetd.conf\fR or the equivalent on your system and let \fBinetd\fR
+run \fBnnrpd\fR. (Change the path to \fBnnrpd\fR to match your installation if
+needed.) You may need to replace \f(CW\*(C`nntps\*(C'\fR with \f(CW563\fR if \f(CW\*(C`nntps\*(C'\fR isn't
+defined in \fI/etc/services\fR on your system.
+.SH "PROTOCOL DIFFERENCES"
+.IX Header "PROTOCOL DIFFERENCES"
+\&\fBnnrpd\fR implements the \s-1NNTP\s0 commands defined in \s-1RFC\s0 977, with the
+following differences:
+.IP "1." 4
+The \f(CW\*(C`slave\*(C'\fR command is not implemented. This command has never been
+fully defined.
+.IP "2." 4
+The \f(CW\*(C`list\*(C'\fR command may be followed by the optional word \f(CW\*(C`active.times\*(C'\fR,
+\&\f(CW\*(C`distributions\*(C'\fR, \f(CW\*(C`distrib.pats\*(C'\fR, \f(CW\*(C`moderators\*(C'\fR, \f(CW\*(C`newsgroups\*(C'\fR,
+\&\f(CW\*(C`subscriptions\*(C'\fR, or \f(CW\*(C`Ioverview.fmt\*(C'\fR to get a list of when newsgroups
+where created, a list of valid distributions, a file specifying default
+distribution patterns, moderators list, a one-per-line description of the
+current set of newsgroups, a list of the automatic group subscriptions, or
+a listing of the \fIoverview.fmt\fR file.
+.Sp
+The command \f(CW\*(C`list active\*(C'\fR is equivalent to the \f(CW\*(C`list\*(C'\fR command. This
+is a common extension.
+.IP "3." 4
+The \f(CW\*(C`xhdr\*(C'\fR, \f(CW\*(C`authinfo user\*(C'\fR and \f(CW\*(C`authinfo pass\*(C'\fR commands are
+implemented. These are based on the reference Unix implementation. See
+\&\s-1RFC\s0 2980.
+.IP "4." 4
+A new command, \f(CW\*(C`xpat header range|MessageID pat [morepat...]\*(C'\fR, is
+provided. The first argument is the case-insensitive name of the header
+to be searched. The second argument is either an article range or a
+single Message\-ID, as specified in \s-1RFC\s0 977. The third argument is a
+\&\f(CW\*(C`uwildmat\*(C'\fR(3)\-style pattern; if there are additional arguments they are
+joined together separated by a single space to form the complete pattern.
+This command is similar to the \f(CW\*(C`xhdr\*(C'\fR command. It returns a \f(CW221\fR
+response code, followed by the text response of all article numbers that
+match the pattern.
+.IP "5." 4
+The \f(CW\*(C`listgroup group\*(C'\fR command is provided. This is a comment extension.
+It is equivalent to the \f(CW\*(C`group\*(C'\fR command, except that the reply is a
+multi-line response containing the list of all article numbers in the
+group.
+.IP "6." 4
+The \f(CW\*(C`xgtitle [group]\*(C'\fR command is provided. This extension is used by
+ANU\-News. It returns a \f(CW282\fR reply code, followed by a one-line
+description of all newsgroups thatmatch the pattern. The default is the
+current group.
+.IP "7." 4
+The \f(CW\*(C`xover [range]\*(C'\fR command is provided. It returns a \f(CW224\fR reply code,
+followed by the overview data for the specified range; the default is to
+return the data for the current article.
+.IP "8." 4
+The \f(CW\*(C`xpath MessageID\*(C'\fR command is provided; see \fIinnd\fR\|(8).
+.IP "9." 4
+The \f(CW\*(C`date\*(C'\fR command is provided; this is based on the draft \s-1NNTP\s0 protocol
+revision (draft\-ietf\-nntpext\-imp\-04.txt). It returns a one-line response
+code of \f(CW111\fR followed by the \s-1GMT\s0 date and time on the server in the form
+\&\f(CW\*(C`YYYYMMDDhhmmss\*(C'\fR.
+.SH "HISTORY"
+.IX Header "HISTORY"
+Written by Rich \f(CW$alz\fR <rsalz@uunet.uu.net> for InterNetNews. Overview
+support added by Rob Robertston <rob@violet.berkeley.edu> and Rich in
+January, 1993. Exponential backoff (for posting) added by Dave Hayes in
+Febuary 1998.
+.PP
+$Id: nnrpd.8 7880 2008-06-16 20:37:13Z iulius $
+.SH "SEE ALSO"
+.IX Header "SEE ALSO"
+\&\fIctlinnd\fR\|(8), \fIinnd\fR\|(8), \fIinn.conf\fR\|(5), \fIsignal\fR\|(2), \fIuwildmat\fR\|(3).
--- /dev/null
+.\" $Revision: 5909 $
+.TH NNRPD.TRACK 5
+.SH NAME
+nnrpd.track \- file to specify hosts to be tracked by nnrpd.
+.SH DESCRIPTION
+This file, which is located in
+.I <pathetc in inn.conf>,
+specifies which hosts are to have their activities recorded during an
+.I nnrpd
+session.
+The
+.I nnrpd
+server reads it when first spawned by
+.IR innd ,
+provided
+.I readertrack
+in
+.I inn.conf
+is true; otherwise this file is not used.
+.PP
+Entries consist of one host specification per line, each line having two
+fields, separated by a colon:
+.RS
+.nf
+
+host:identity
+.fi
+.RE
+.PP
+The first field is either the FQDN of a host, or a domain name (in
+the form *.domain.com).
+.PP
+The second field is simply a segment of text which may be used to
+more easily identify the client, typically an e-mail address or other
+identifying mark.
+.PP
+For example:
+.RS
+.nf
+
+nasty.foo.com:nasty@foo.com
+*.bar.com:VeryNastyClient
+.fi
+.RE
+.PP
+Written by Steve Carrie <stephenc@uk.uu.net> for InterNetNews.
+.de R$
+This is revision \\$3, dated \\$4.
+..
+.R$ $Id: nnrpd.track.5 5909 2002-12-03 05:17:18Z vinocur $
+.SH "SEE ALSO"
+inn.conf(5),
+innd(8),
+newsfeeds(5),
+nnrpd(8),
--- /dev/null
+.\" $Revision: 5909 $
+.TH NNTPGET 1
+.SH NAME
+nntpget \- get Usenet articles from a remote NNTP server
+.SH SYNOPSIS
+.I nntpget
+[
+.BI \-d " dist"
+]
+[
+.BI \-f " file"
+]
+[
+.BI \-n " newsgroups"
+]
+[
+.BI \-t " timestring"
+]
+[
+.B \-o
+]
+[
+.BI \-u " file"
+]
+[
+.B \-v
+]
+.I host
+.SH DESCRIPTION
+.I Nntpget
+connects to the NNTP server at the specified
+.I host
+and retrieves articles from it. The Message-ID's of the desired articles
+are read from standard input. The articles are sent to standard output.
+.SH OPTIONS
+.TP
+.B \-o
+The ``\-o'' option may be used only if the command is executed on the
+host where the
+.IR innd (8)
+server is running.
+If this option is used,
+.I nntpget
+connects to the specified remote
+.I host
+to retrieve articles.
+Any article not present in the local
+.I history
+database is then fetched from the remote site and offered to the local server.
+.TP
+.B \-v
+If the ``\fB\-v\fP'' option is used with the ``\fB\-o\fP'' option then the
+Message-ID
+of each article will be sent to standard output as it is processed.
+.TP
+.B \-f
+The list of article Message-ID's is normally read from standard input.
+If the ``\fB\-f\fP'' option is used, then a ``newnews'' command is used
+to retrieve
+all articles newer then the modification date of the specified
+.IR file .
+.TP
+.B \-u
+The ``\fB\-u\fP'' option is like ``\fB\-f\fP'' except that if the transfer
+succeedes, the file will be updated with a statistics line, modifying its
+timestamp so that it can be used in later invocations.
+.TP
+.B \-t
+If the ``\-t'' option is used, then the specified
+.I timestring
+is used as the time and date parameter to the ``newnews'' command.
+.TP
+.B \-n
+If either the ``\fB\-u\fP'' or ``\fB\-f\fP'' options are used, then
+the ``\fB\-n\fP'' option
+may be used to specify a newsgroup list. The default is ``*''.
+.TP
+.B \-d
+The ``\fB\-d\fP'' option may be
+used to specify a distribution list when using the ``\fB\-u\fP''
+or ``\fB\-f\fP'' options.
+The default is no distribution list.
+.SH HISTORY
+Written by Rich $alz <rsalz@uunet.uu.net> for InterNetNews.
+.de R$
+This is revision \\$3, dated \\$4.
+..
+.R$ $Id: nntpget.1 5909 2002-12-03 05:17:18Z vinocur $
+.SH "SEE ALSO"
+innd(8).
--- /dev/null
+.TH NNTPSEND 8
+.SH NAME
+nntpsend \- send Usenet articles to remote site
+.SH SYNOPSIS
+.B nntpsend
+[
+.B \-a
+]
+[
+.B \-c
+]
+[
+.B \-D
+]
+[
+.B \-d
+]
+[
+.B \-l
+]
+[
+.B \-N
+]
+[
+.B \-n
+]
+[
+.BI \-P " portnum"
+]
+[
+.B \-p
+]
+[
+.B \-r
+]
+[
+.B \-S
+]
+[
+.BI \-s " size"
+]
+[
+.BI \-T " timelimit"
+]
+[
+.BI \-t " timeout"
+]
+[
+.BI \-w " delay"
+]
+[
+.I sitename
+.I fqdn
+] ...
+.SH DESCRIPTION
+.I Nntpsend
+is a front-end that invokes
+.IR innxmit (1)
+to send Usenet articles to a remote NNTP site.
+.PP
+The sites to be fed may be specified by giving
+.I sitename
+.I fqdn
+pairs on the command line.
+If no such pairs are given,
+.I nntpsend
+defaults to the information given in the
+.I nntpsend.ctl
+config file.
+.PP
+The
+.I sitename
+should be the name of the site as specified in the
+.IR newsfeeds (5)
+file.
+The
+.I fqdn
+should be the hostname or IP address of the remote site.
+.PP
+An
+.I innxmit
+is launched for sites with queued news.
+All
+.I innxmit
+processes are spawned in the background and the script waits for
+them all to finish before returning.
+Output is sent to the file
+.IR <pathlog\ in\ inn.conf>/nntpsend.log .
+In order to keep from overwhelming the local system,
+.I nntpsend
+waits five seconds before spawning each child.
+.PP
+.I Nntpsend
+expects that the batchfile for a site is named
+.IR <pathoutgoing\ in\ inn.conf>/sitename .
+To prevent batchfile corruption,
+.IR shlock (1)
+is used to ``lock'' these files.
+.PP
+When
+.I sitename
+.I fqdn
+pairs are given on the command line,
+any flags given on the command completely describe how
+.I innxmit
+and
+.I shrinkfile
+operate.
+When no such pairs are given on the command line, then
+the information found in
+.I nntpsend.ctl
+becomes the default flags for that site.
+Any flags given on the command line override the default flags
+for the site.
+.SH OPTIONS
+.TP
+.B "\-d \-D"
+The ``\-d'' flag causes
+.I nntpsend
+to send output to stdout rather than the log file
+.IR <pathlog\ in\ inn.conf>/nntpsend.log .
+The ``\-D'' flag does the same
+and it passes ``\-d'' to all
+.I innxmit
+invocations, which in turn causes
+.I innxmit
+to go into debug mode.
+.TP
+.B -n
+If the ``\-n'' flag is used, then
+.I nntpsend
+does not use
+.IR shlock (1)
+and does not lock batch files.
+.TP
+.B \-s size
+If the ``\-s'' flag is used, then
+.IR shrinkfile (1)
+will be invoked to perform a head truncation on the batchfile and the flag
+will be passed to it.
+.TP
+.B \-w delay
+If the ``\-w'' flag is used, then
+.I nntpsend
+waits for
+.I delay
+seconds after flushing the site before launching
+.IR innxmit .
+.TP
+.B "\-a \-c \-l \-N \-P \-p \-r \-S \-T \-t"
+The ``\fB\-a\fP'', ``\fB\-c\fP'', ``\fB\-l\fP'', ``\fB\-P\fP'', ``\fB\-p\fP'',
+``\fB\-r\fP'', \``\fB\-S\fP'', ``\fB\-T\fP'' and ``\fB\-t\fP''
+flags are passed on to the child
+.I innxmit
+program. The ``\-N'' flag is passed as ``\fB\-s\fP'' flag to the child
+.I innxmit
+program.
+See
+.IR innxmit (8)
+for more details.
+Note that if the ``\-p'' flag is used then no connection is made and
+no articles are fed to the remote site.
+It is useful to have
+.IR cron (8)
+invoke
+.I nntpsend
+with this flag in case a site cannot be reached for an extended period of time.
+.SH EXAMPLES
+With the following
+.IR nntpsend.ctl (5)
+control file:
+.PP
+.RS
+.nf
+nsavax:erehwon.nsavax.gov::-S -t60
+group70:group70.org::
+walldrug:walldrug.com:4m-1m:-T1800 -t300
+kremvax:kremvax.cis:2m:
+.fi
+.RE
+.PP
+The command:
+.PP
+.RS
+nntpsend
+.PP
+.RE
+will result in the following:
+.PP
+.RS
+.nf
+Sitename Truncation Innxmit flags
+nsavax (none) \-a \-S \-t60
+group70 (none) \-a \-t180
+walldrug 1m if >4m \-a \-T1800 \-t300
+kremvax 2m \-a \-t180
+.fi
+.RE
+.PP
+The command:
+.PP
+.RS
+nntpsend \-d \-T1200
+.RE
+.PP
+will result in the following:
+.PP
+.RS
+.nf
+Sitename Truncation Innxmit flags
+nsavax (none) \-a \-d \-S \-T1200 \-t60
+group70 (none) \-a \-d \-T1200 \-t180
+walldrug 1m if >4m \-a \-d \-T1200 \-t300
+kremvax 2m \-a \-d \-T1200 \-t180
+.fi
+.RE
+.PP
+The command:
+.PP
+.RS
+nntpsend \-s 5m \-T1200 nsavax erehwon.nsavax.gov group70 group70.org
+.PP
+.RE
+will result in the following:
+.PP
+.RS
+.nf
+Sitename Truncation Innxmit flags
+nsavax 5m \-a \-T1200 \-t180
+group70 5m \-a \-T1200 \-t180
+.fi
+.RE
+.PP
+Remember that ``\-a'' is always given, and ``\-t'' defaults to 180.
+.SH HISTORY
+Written by Landon Curt Noll <chongo@toad.com>
+and Rich $alz <rsalz@uunet.uu.net> for InterNetNews.
+.de R$
+This is revision \\$3, dated \\$4.
+..
+.R$ $Id: nntpsend.8 5909 2002-12-03 05:17:18Z vinocur $
+.SH "SEE ALSO"
+inn.conf(5),
+innxmit(1),
+newsfeeds(5),
+nntpsend.ctl(5),
+shrinkfile(1).
--- /dev/null
+.TH NNTPSEND.CTL 5
+.SH NAME
+nntpsend.ctl \- list of sites to feed via nntpsend
+.SH DESCRIPTION
+The file
+.I <pathetc in inn.conf>/nntpsend.ctl
+specifies the default list of sites to be fed by
+.IR nntpsend (8).
+.PP
+Comments begin with a number sign (``#'') and continue through the end
+of the line.
+Blank lines and comments are ignored.
+All other lines should consist of four fields separated by a colon.
+.PP
+The first field is the name of the site as specified in the
+.I newsfeeds
+file.
+.PP
+The second field should be the hostname or IP address of the remote site.
+.PP
+The third field, if non-empty, specifies the default head truncation size of
+site's batchfile.
+If this field is empty, no truncation is performed.
+This field may be of the form ``\fRmaxsize-truncsize\fP'', in which case it
+is passed to
+.I shrinkfile
+as ``\fR\-m maxsize \-s truncsize\fP''; otherwise it is of the form
+``\fRtruncsize\fP'', in which case it is passed as ``\fR\-s truncsize\fP''.
+.PP
+The fourth field specifies some default flags passed to
+.IR innxmit (8).
+The flag ``\-a'' is always given to
+.I innxmit
+and need not appear here.
+If no ``\-t timeout'' flag is given in this field or on the
+.I nntpsend
+command line, ``\-t\ 180'' will be given to
+.IR innxmit .
+.SH HISTORY
+Written by Landon Curt Noll <chongo@toad.com> for InterNetNews.
+.de R$
+This is revision \\$3, dated \\$4.
+..
+.R$ $Id: nntpsend.ctl.5 5909 2002-12-03 05:17:18Z vinocur $
+.SH "SEE ALSO"
+innxmit(8), newsfeeds(5), nntpsend(8), trunc(1).
--- /dev/null
+.\" Automatically generated by Pod::Man v1.37, Pod::Parser v1.32
+.\"
+.\" Standard preamble:
+.\" ========================================================================
+.de Sh \" Subsection heading
+.br
+.if t .Sp
+.ne 5
+.PP
+\fB\\$1\fR
+.PP
+..
+.de Sp \" Vertical space (when we can't use .PP)
+.if t .sp .5v
+.if n .sp
+..
+.de Vb \" Begin verbatim text
+.ft CW
+.nf
+.ne \\$1
+..
+.de Ve \" End verbatim text
+.ft R
+.fi
+..
+.\" Set up some character translations and predefined strings. \*(-- will
+.\" give an unbreakable dash, \*(PI will give pi, \*(L" will give a left
+.\" double quote, and \*(R" will give a right double quote. \*(C+ will
+.\" give a nicer C++. Capital omega is used to do unbreakable dashes and
+.\" therefore won't be available. \*(C` and \*(C' expand to `' in nroff,
+.\" nothing in troff, for use with C<>.
+.tr \(*W-
+.ds C+ C\v'-.1v'\h'-1p'\s-2+\h'-1p'+\s0\v'.1v'\h'-1p'
+.ie n \{\
+. ds -- \(*W-
+. ds PI pi
+. if (\n(.H=4u)&(1m=24u) .ds -- \(*W\h'-12u'\(*W\h'-12u'-\" diablo 10 pitch
+. if (\n(.H=4u)&(1m=20u) .ds -- \(*W\h'-12u'\(*W\h'-8u'-\" diablo 12 pitch
+. ds L" ""
+. ds R" ""
+. ds C` ""
+. ds C' ""
+'br\}
+.el\{\
+. ds -- \|\(em\|
+. ds PI \(*p
+. ds L" ``
+. ds R" ''
+'br\}
+.\"
+.\" If the F register is turned on, we'll generate index entries on stderr for
+.\" titles (.TH), headers (.SH), subsections (.Sh), items (.Ip), and index
+.\" entries marked with X<> in POD. Of course, you'll have to process the
+.\" output yourself in some meaningful fashion.
+.if \nF \{\
+. de IX
+. tm Index:\\$1\t\\n%\t"\\$2"
+..
+. nr % 0
+. rr F
+.\}
+.\"
+.\" For nroff, turn off justification. Always turn off hyphenation; it makes
+.\" way too many mistakes in technical documents.
+.hy 0
+.if n .na
+.\"
+.\" Accent mark definitions (@(#)ms.acc 1.5 88/02/08 SMI; from UCB 4.2).
+.\" Fear. Run. Save yourself. No user-serviceable parts.
+. \" fudge factors for nroff and troff
+.if n \{\
+. ds #H 0
+. ds #V .8m
+. ds #F .3m
+. ds #[ \f1
+. ds #] \fP
+.\}
+.if t \{\
+. ds #H ((1u-(\\\\n(.fu%2u))*.13m)
+. ds #V .6m
+. ds #F 0
+. ds #[ \&
+. ds #] \&
+.\}
+. \" simple accents for nroff and troff
+.if n \{\
+. ds ' \&
+. ds ` \&
+. ds ^ \&
+. ds , \&
+. ds ~ ~
+. ds /
+.\}
+.if t \{\
+. ds ' \\k:\h'-(\\n(.wu*8/10-\*(#H)'\'\h"|\\n:u"
+. ds ` \\k:\h'-(\\n(.wu*8/10-\*(#H)'\`\h'|\\n:u'
+. ds ^ \\k:\h'-(\\n(.wu*10/11-\*(#H)'^\h'|\\n:u'
+. ds , \\k:\h'-(\\n(.wu*8/10)',\h'|\\n:u'
+. ds ~ \\k:\h'-(\\n(.wu-\*(#H-.1m)'~\h'|\\n:u'
+. ds / \\k:\h'-(\\n(.wu*8/10-\*(#H)'\z\(sl\h'|\\n:u'
+.\}
+. \" troff and (daisy-wheel) nroff accents
+.ds : \\k:\h'-(\\n(.wu*8/10-\*(#H+.1m+\*(#F)'\v'-\*(#V'\z.\h'.2m+\*(#F'.\h'|\\n:u'\v'\*(#V'
+.ds 8 \h'\*(#H'\(*b\h'-\*(#H'
+.ds o \\k:\h'-(\\n(.wu+\w'\(de'u-\*(#H)/2u'\v'-.3n'\*(#[\z\(de\v'.3n'\h'|\\n:u'\*(#]
+.ds d- \h'\*(#H'\(pd\h'-\w'~'u'\v'-.25m'\f2\(hy\fP\v'.25m'\h'-\*(#H'
+.ds D- D\\k:\h'-\w'D'u'\v'-.11m'\z\(hy\v'.11m'\h'|\\n:u'
+.ds th \*(#[\v'.3m'\s+1I\s-1\v'-.3m'\h'-(\w'I'u*2/3)'\s-1o\s+1\*(#]
+.ds Th \*(#[\s+2I\s-2\h'-\w'I'u*3/5'\v'-.3m'o\v'.3m'\*(#]
+.ds ae a\h'-(\w'a'u*4/10)'e
+.ds Ae A\h'-(\w'A'u*4/10)'E
+. \" corrections for vroff
+.if v .ds ~ \\k:\h'-(\\n(.wu*9/10-\*(#H)'\s-2\u~\d\s+2\h'|\\n:u'
+.if v .ds ^ \\k:\h'-(\\n(.wu*10/11-\*(#H)'\v'-.4m'^\v'.4m'\h'|\\n:u'
+. \" for low resolution devices (crt and lpr)
+.if \n(.H>23 .if \n(.V>19 \
+\{\
+. ds : e
+. ds 8 ss
+. ds o a
+. ds d- d\h'-1'\(ga
+. ds D- D\h'-1'\(hy
+. ds th \o'bp'
+. ds Th \o'LP'
+. ds ae ae
+. ds Ae AE
+.\}
+.rm #[ #] #H #V #F C
+.\" ========================================================================
+.\"
+.IX Title "OVDB 5"
+.TH OVDB 5 "2008-04-06" "INN 2.4.5" "InterNetNews Documentation"
+.SH "NAME"
+ovdb \- Overview storage method for INN
+.SH "DESCRIPTION"
+.IX Header "DESCRIPTION"
+Ovdb is a storage method that uses the BerkeleyDB library to store
+overview data. It requires version 2.6.x or later of the BerkeleyDB
+library, but has mostly been tested with version 3 and 4.
+.PP
+Ovdb makes use of the full transaction/logging/locking functionality of
+the BerkeleyDB environment. BerkeleyDB may be downloaded from
+<http://www.sleepycat.com> and is needed to build the ovdb backend.
+.SH "UPGRADING"
+.IX Header "UPGRADING"
+This is version 2 of ovdb. If you have a database created with a previous
+version of ovdb (such as the one shipped with \s-1INN\s0 2.3.0) your database
+will need to be upgraded using \fIovdb_init\fR\|(8). See the man page
+\&\fIovdb_init\fR\|(8) for upgrade instructions.
+.SH "INSTALLATION"
+.IX Header "INSTALLATION"
+To build ovdb support into \s-1INN\s0, specify the option \f(CW\*(C`\-\-with\-berkeleydb\*(C'\fR
+when running the configure script. By default, configure will search for
+a BerkeleyDB tree in several likely locations, and choose the highest
+version (based on the name of the directory, e.g., \fIBerkeleyDB.3.0\fR) that
+it finds. There will be a message in the configure output indicating the
+chosen pathname.
+.PP
+You can override this pathname by adding a path to the option, e.g.,
+\&\f(CW\*(C`\-\-with\-berkeleydb=/usr/BerkeleyDB.3.1\*(C'\fR. This directory is expected to
+have subdirectories \fIinclude\fR and \fIlib\fR, containing \fIdb.h\fR, and the
+library itself, respectively.
+.PP
+The ovdb database will take up more disk space for a given spool than the
+other overview methods. Plan on needing at least 1.1 \s-1KB\s0 for every article
+in your spool (not counting crossposts). So, if you have 5 million
+articles, you'll need at least 5.5 \s-1GB\s0 of disk space for ovdb. With
+BerkeleyDB 2.x, the db files are 'grow only'; the library will not shrink
+them, even if data is removed. So, reserving extra space above the
+estimate is a good idea. Plus, you'll need additional space for
+transaction logs: at least 100 \s-1MB\s0. By default the transaction logs go in
+the same directory as the database. To improve performance, they can be
+placed on a different disk \*(-- see the \s-1DB_CONFIG\s0 section.
+.SH "CONFIGURATION"
+.IX Header "CONFIGURATION"
+To enable ovdb, set the \fIovmethod\fR parameter in \fIinn.conf\fR to \f(CW\*(C`ovdb\*(C'\fR.
+The ovdb database is stored in the directory specified by the
+\&\fIpathoverview\fR paramter in \fIinn.conf\fR. This is the \*(L"\s-1DB_HOME\s0\*(R" directory.
+To start out, this directory should be empty (other than an optional
+\&\fI\s-1DB_CONFIG\s0\fR file; see \s-1DB_CONFIG\s0 for details) and \fBinnd\fR (or
+\&\fBmakehistory\fR) will create the files as necessary in that directory.
+Make sure the directory is owned by the news user.
+.PP
+Other parameters for configuring ovdb are in the \fIovdb.conf\fR\|(5)
+configuration file. See also the sample \fIovdb.conf\fR.
+.IP "cachesize" 4
+.IX Item "cachesize"
+Size of the memory pool cache, in kilobytes. The cache will have a
+backing store file in the \s-1DB\s0 directory which will be at least as big. In
+general, the bigger the cache, the better. Use \f(CW\*(C`ovdb_stat \-m\*(C'\fR to see
+cache hit percentages. To make a change of this parameter take effect,
+shut down and restart \s-1INN\s0 (be sure to kill all of the nnrpds when shutting
+down). Default is 8000, which is adequate for small to medium sized
+servers. Large servers will probably need at least 20000.
+.IP "numdbfiles" 4
+.IX Item "numdbfiles"
+Overview data is split between this many files. Currently, \fBinnd\fR will
+keep all of the files open, so don't set this too high or \fBinnd\fR may run
+out of file descriptors. \fBnnrpd\fR only opens one at a time, regardless.
+May be set to one, or just a few, but only do that if your \s-1OS\s0 supports
+large (>2G) files. Changing this parameter has no effect on an
+already-established database. Default is 32.
+.IP "txn_nosync" 4
+.IX Item "txn_nosync"
+If txn_nosync is set to false, BerkeleyDB flushes the log after every
+transaction. This minimizes the number of transactions that may be lost
+in the event of a crash, but results in significantly degraded
+performance. Default is true.
+.IP "useshm" 4
+.IX Item "useshm"
+If useshm is set to true, BerkeleyDB will use shared memory instead of
+mmap for its environment regions (cache, lock, etc). With some platforms,
+this may improve performance. Default is false. This parameter is
+ignored if you have BerkeleyDB 2.x
+.IP "shmkey" 4
+.IX Item "shmkey"
+Sets the shared memory key used by BerkeleyDB when 'useshm' is true.
+BerkeleyDB will create several (usually 5) shared memory segments, using
+sequentially numbered keys starting with 'shmkey'. Choose a key that does
+not conflict with any existing shared memory segments on your system.
+Default is 6400. This parameter is only used with BerkeleyDB 3.1 or
+newer.
+.IP "pagesize" 4
+.IX Item "pagesize"
+Sets the page size for the \s-1DB\s0 files (in bytes). Must be a power of 2.
+Best choices are 4096 or 8192. The default is 8192. Changing this
+parameter has no effect on an already-established database.
+.IP "minkey" 4
+.IX Item "minkey"
+Sets the minimum number of keys per page. See the BerkeleyDB
+documentation for more info. Default is based on page size:
+.Sp
+.Vb 1
+\& default_minkey = MAX(2, pagesize / 2048 \- 1)
+.Ve
+.Sp
+The lowest allowed minkey is 2. Setting minkey higher than the default is
+not recommended, as it will cause the databases to have a lot of overflow
+pages. Changing this parameter has no effect on an already-established
+database.
+.IP "maxlocks" 4
+.IX Item "maxlocks"
+Sets the BerkeleyDB \*(L"lk_max\*(R" parameter, which is the maxmium number of
+locks that can exist in the database at the same time. Default is 4000.
+.IP "nocompact" 4
+.IX Item "nocompact"
+The nocompact parameter affects expireover's behavior. The expireover
+function in ovdb can do its job in one of two ways: by simply deleting
+expired records from the database, or by re-writing the overview records
+into a different location leaving out the expired records. The first
+method is faster, but it leaves 'holes' that result in space that can not
+immediately be reused. The second method 'compacts' the records by
+rewriting them.
+.Sp
+If this parameter is set to 0, expireover will compact all newsgroups; if
+set to 1, expireover will not compact any newsgroups; and if set to a
+value greater than one, expireover will only compact groups that have less
+than that number of articles.
+.Sp
+Experience has shown that compacting has minimal effect (other than
+making expireover take longer) so the default is now 1. This parameter
+will probably be removed in the future.
+.IP "readserver" 4
+.IX Item "readserver"
+Normally, each nnrpd process directly accesses the BerkeleyDB environment.
+The process of attaching to the database (and detaching when finished) is
+fairly expensive, and can result in high loads in situations when there
+are lots of reader connections of relatively short duration.
+.Sp
+When the readserver parameter is \*(L"true\*(R", the nnrpds will access overview
+via a helper server (\fBovdb_server\fR \*(-- which is started by \fBovdb_init\fR).
+This can also result in cleaner shutdowns for the database, improving
+stability and avoiding deadlocks and corrupted databases. If you are
+experiencing any instability in ovdb, try setting this parameter to true.
+Default is false.
+.IP "numrsprocs" 4
+.IX Item "numrsprocs"
+This parameter is only used when \fIreadserver\fR is true. It sets the
+number of ovdb_server processes. As each ovdb_server can process only one
+transaction at a time, running more servers can improve reader response
+times. Default is 5.
+.IP "maxrsconn" 4
+.IX Item "maxrsconn"
+This parameter is only used when \fIreadserver\fR is true. It sets a maximum
+number of readers that a given ovdb_server process will serve at one time.
+This means the maximum number of readers for all of the ovdb_server
+processes is (numrsprocs * maxrsconn). Default is 0, which means an
+umlimited number of connections is allowed.
+.SH "DB_CONFIG"
+.IX Header "DB_CONFIG"
+A file called \fI\s-1DB_CONFIG\s0\fR may be placed in the database directory to
+customize where the various database files and transaction logs are
+written. By default, all of the files are written in the \*(L"\s-1DB_HOME\s0\*(R"
+directory. One way to improve performance is to put the transaction logs
+on a different disk. To do this, put:
+.PP
+.Vb 1
+\& DB_LOG_DIR /path/to/logs
+.Ve
+.PP
+in the \fI\s-1DB_CONFIG\s0\fR file. If the pathname you give starts with a /, it is
+treated as an absolute path; otherwise, it is relative to the \*(L"\s-1DB_HOME\s0\*(R"
+directory. Make sure that any directories you specify exist and have
+proper ownership/mode before starting \s-1INN\s0, because they won't be created
+automatically. Also, don't change the \s-1DB_CONFIG\s0 file while anything that
+uses ovdb is running.
+.PP
+Another thing that you can do with this file is to split the overview
+database across multiple disks. In the \fI\s-1DB_CONFIG\s0\fR file, you can list
+directories that BerkeleyDB will search when it goes to open a database.
+.PP
+For example, let's say that you have \fIpathoverview\fR set to
+\&\fI/mnt/overview\fR and you have four additional file systems created on
+\&\fI/mnt/ov?\fR. You would create a file \*(L"/mnt/overview/DB_CONFIG\*(R" containing
+the following lines:
+.PP
+.Vb 5
+\& set_data_dir /mnt/overview
+\& set_data_dir /mnt/ov1
+\& set_data_dir /mnt/ov2
+\& set_data_dir /mnt/ov3
+\& set_data_dir /mnt/ov4
+.Ve
+.PP
+(For BerkeleyDB 2.x, replace \f(CW\*(C`set_data_dir\*(C'\fR with \f(CW\*(C`DB_DATA_DIR\*(C'\fR.)
+.PP
+Distribute your ovNNNNN files into the four filesystems. (say, 8 each).
+When called upon to open a database file, the db library will look for it
+in each of the specified directories (in order). If said file is not
+found, one will be created in the first of those directories.
+.PP
+Whenever you change \s-1DB_CONFIG\s0 or move database files around, make sure all
+news processes that use the database are shut down first (including
+nnrpds).
+.PP
+The \s-1DB_CONFIG\s0 functionality is part of BerkeleyDB itself, rather than
+something provided by ovdb. See the BerkeleyDB documentation for complete
+details for the version of BerkeleyDB that you're running.
+.SH "RUNNING"
+.IX Header "RUNNING"
+When starting the news system, \fBrc.news\fR will invoke \fBovdb_init\fR.
+\&\fBovdb_init\fR must be run before using the database. It performs the
+following tasks:
+.IP "\(bu" 4
+Creates the database environment, if necessary.
+.IP "\(bu" 4
+If the database is idle, it performs a normal recovery. The recovery will
+remove stale locks, recreate the memory pool cache, and repair any damage
+caused by a system crash or improper shutdown.
+.IP "\(bu" 4
+Starts the \s-1DB\s0 housekeeping processes (\fBovdb_monitor\fR) if they're not
+already running.
+.PP
+And when stopping \s-1INN\s0, \fBrc.news\fR kills the ovdb_monitor processes after
+the other \s-1INN\s0 processes have been shut down.
+.SH "DIAGNOSTICS"
+.IX Header "DIAGNOSTICS"
+Problems relating to ovdb are logged to news.err with \*(L"\s-1OVDB\s0\*(R" in the error
+message.
+.PP
+\&\s-1INN\s0 programs that use overview will fail to start up if the ovdb_monitor
+processes aren't running. Be sure to run \fBovdb_init\fR before running
+anything that accesses overview.
+.PP
+Also, \s-1INN\s0 programs that use overview will fail to start up if the user
+running them is not the \*(L"news\*(R" user.
+.PP
+If a program accessing the database crashes, or otherwise exits uncleanly,
+it might leave a stale lock in the database. This lock could cause other
+processes to deadlock on that stale lock. To fix this, shut down all news
+processes (using \f(CW\*(C`kill \-9\*(C'\fR if necessary) and then restart. \fBovdb_init\fR
+should perform a recovery operation which will remove the locks and repair
+damage caused by killing the deadlocked processes.
+.SH "FILES"
+.IX Header "FILES"
+.IP "inn.conf" 4
+.IX Item "inn.conf"
+The \fIovmethod\fR and \fIpathoverview\fR parameters are relevant to ovdb.
+.IP "ovdb.conf" 4
+.IX Item "ovdb.conf"
+Optional configuration file for tuning. See \s-1CONFIGURATION\s0 above.
+.IP "\fIpathoverview\fR" 4
+.IX Item "pathoverview"
+Directory where the database goes. BerkeleyDB calls it the '\s-1DB_HOME\s0'
+directory.
+.IP "\fIpathoverview\fR/DB_CONFIG" 4
+.IX Item "pathoverview/DB_CONFIG"
+Optional file to configure the layout of the database files.
+.IP "\fIpathrun\fR/ovdb.sem" 4
+.IX Item "pathrun/ovdb.sem"
+A file that gets locked by every process that is accessing the database.
+This is used by \fBovdb_init\fR to determine whether the database is active
+or quiescent.
+.IP "\fIpathrun\fR/ovdb_monitor.pid" 4
+.IX Item "pathrun/ovdb_monitor.pid"
+Contains the process \s-1ID\s0 of \fBovdb_monitor\fR.
+.SH "TO DO"
+.IX Header "TO DO"
+Implement a way to limit how many databases can be open at once (to reduce
+file descriptor usage); maybe using something similar to the cache code in
+ov3.c
+.SH "HISTORY"
+.IX Header "HISTORY"
+Written by Heath Kehoe <hakehoe@avalon.net> for InterNetNews
+.SH "SEE ALSO"
+.IX Header "SEE ALSO"
+\&\fIinn.conf\fR\|(5), \fIinnd\fR\|(8), \fInnrpd\fR\|(8), \fIovdb_init\fR\|(8), \fIovdb_monitor\fR\|(8),
+\&\fIovdb_stat\fR\|(8)
+.PP
+BerkeleyDB documentation: in the \fIdocs\fR directory of the BerkeleyDB
+source distribution, or on the Sleepycat web page:
+<http://www.sleepycat.com/>.
--- /dev/null
+.\" Automatically generated by Pod::Man v1.37, Pod::Parser v1.32
+.\"
+.\" Standard preamble:
+.\" ========================================================================
+.de Sh \" Subsection heading
+.br
+.if t .Sp
+.ne 5
+.PP
+\fB\\$1\fR
+.PP
+..
+.de Sp \" Vertical space (when we can't use .PP)
+.if t .sp .5v
+.if n .sp
+..
+.de Vb \" Begin verbatim text
+.ft CW
+.nf
+.ne \\$1
+..
+.de Ve \" End verbatim text
+.ft R
+.fi
+..
+.\" Set up some character translations and predefined strings. \*(-- will
+.\" give an unbreakable dash, \*(PI will give pi, \*(L" will give a left
+.\" double quote, and \*(R" will give a right double quote. \*(C+ will
+.\" give a nicer C++. Capital omega is used to do unbreakable dashes and
+.\" therefore won't be available. \*(C` and \*(C' expand to `' in nroff,
+.\" nothing in troff, for use with C<>.
+.tr \(*W-
+.ds C+ C\v'-.1v'\h'-1p'\s-2+\h'-1p'+\s0\v'.1v'\h'-1p'
+.ie n \{\
+. ds -- \(*W-
+. ds PI pi
+. if (\n(.H=4u)&(1m=24u) .ds -- \(*W\h'-12u'\(*W\h'-12u'-\" diablo 10 pitch
+. if (\n(.H=4u)&(1m=20u) .ds -- \(*W\h'-12u'\(*W\h'-8u'-\" diablo 12 pitch
+. ds L" ""
+. ds R" ""
+. ds C` ""
+. ds C' ""
+'br\}
+.el\{\
+. ds -- \|\(em\|
+. ds PI \(*p
+. ds L" ``
+. ds R" ''
+'br\}
+.\"
+.\" If the F register is turned on, we'll generate index entries on stderr for
+.\" titles (.TH), headers (.SH), subsections (.Sh), items (.Ip), and index
+.\" entries marked with X<> in POD. Of course, you'll have to process the
+.\" output yourself in some meaningful fashion.
+.if \nF \{\
+. de IX
+. tm Index:\\$1\t\\n%\t"\\$2"
+..
+. nr % 0
+. rr F
+.\}
+.\"
+.\" For nroff, turn off justification. Always turn off hyphenation; it makes
+.\" way too many mistakes in technical documents.
+.hy 0
+.if n .na
+.\"
+.\" Accent mark definitions (@(#)ms.acc 1.5 88/02/08 SMI; from UCB 4.2).
+.\" Fear. Run. Save yourself. No user-serviceable parts.
+. \" fudge factors for nroff and troff
+.if n \{\
+. ds #H 0
+. ds #V .8m
+. ds #F .3m
+. ds #[ \f1
+. ds #] \fP
+.\}
+.if t \{\
+. ds #H ((1u-(\\\\n(.fu%2u))*.13m)
+. ds #V .6m
+. ds #F 0
+. ds #[ \&
+. ds #] \&
+.\}
+. \" simple accents for nroff and troff
+.if n \{\
+. ds ' \&
+. ds ` \&
+. ds ^ \&
+. ds , \&
+. ds ~ ~
+. ds /
+.\}
+.if t \{\
+. ds ' \\k:\h'-(\\n(.wu*8/10-\*(#H)'\'\h"|\\n:u"
+. ds ` \\k:\h'-(\\n(.wu*8/10-\*(#H)'\`\h'|\\n:u'
+. ds ^ \\k:\h'-(\\n(.wu*10/11-\*(#H)'^\h'|\\n:u'
+. ds , \\k:\h'-(\\n(.wu*8/10)',\h'|\\n:u'
+. ds ~ \\k:\h'-(\\n(.wu-\*(#H-.1m)'~\h'|\\n:u'
+. ds / \\k:\h'-(\\n(.wu*8/10-\*(#H)'\z\(sl\h'|\\n:u'
+.\}
+. \" troff and (daisy-wheel) nroff accents
+.ds : \\k:\h'-(\\n(.wu*8/10-\*(#H+.1m+\*(#F)'\v'-\*(#V'\z.\h'.2m+\*(#F'.\h'|\\n:u'\v'\*(#V'
+.ds 8 \h'\*(#H'\(*b\h'-\*(#H'
+.ds o \\k:\h'-(\\n(.wu+\w'\(de'u-\*(#H)/2u'\v'-.3n'\*(#[\z\(de\v'.3n'\h'|\\n:u'\*(#]
+.ds d- \h'\*(#H'\(pd\h'-\w'~'u'\v'-.25m'\f2\(hy\fP\v'.25m'\h'-\*(#H'
+.ds D- D\\k:\h'-\w'D'u'\v'-.11m'\z\(hy\v'.11m'\h'|\\n:u'
+.ds th \*(#[\v'.3m'\s+1I\s-1\v'-.3m'\h'-(\w'I'u*2/3)'\s-1o\s+1\*(#]
+.ds Th \*(#[\s+2I\s-2\h'-\w'I'u*3/5'\v'-.3m'o\v'.3m'\*(#]
+.ds ae a\h'-(\w'a'u*4/10)'e
+.ds Ae A\h'-(\w'A'u*4/10)'E
+. \" corrections for vroff
+.if v .ds ~ \\k:\h'-(\\n(.wu*9/10-\*(#H)'\s-2\u~\d\s+2\h'|\\n:u'
+.if v .ds ^ \\k:\h'-(\\n(.wu*10/11-\*(#H)'\v'-.4m'^\v'.4m'\h'|\\n:u'
+. \" for low resolution devices (crt and lpr)
+.if \n(.H>23 .if \n(.V>19 \
+\{\
+. ds : e
+. ds 8 ss
+. ds o a
+. ds d- d\h'-1'\(ga
+. ds D- D\h'-1'\(hy
+. ds th \o'bp'
+. ds Th \o'LP'
+. ds ae ae
+. ds Ae AE
+.\}
+.rm #[ #] #H #V #F C
+.\" ========================================================================
+.\"
+.IX Title "OVDB_INIT 8"
+.TH OVDB_INIT 8 "2008-04-06" "INN 2.4.5" "InterNetNews Documentation"
+.SH "NAME"
+ovdb_init \- Prepare ovdb database for use
+.SH "SYNOPSYS"
+.IX Header "SYNOPSYS"
+ovdb_init [\f(CW\*(C`\-u\*(C'\fR|\f(CW\*(C`\-r\*(C'\fR]
+.SH "DESCRIPTION"
+.IX Header "DESCRIPTION"
+This command must be run before any other process can access the
+overview database. It performs the following steps:
+.IP "1" 4
+.IX Item "1"
+Creates the database environment, if necessary
+.IP "2" 4
+.IX Item "2"
+If the database is idle (and if the \f(CW\*(C`\-u\*(C'\fR option is not specified),
+it performs a normal recovery. The recovery will remove stale locks,
+recreate the memory pool cache, and repair any damage caused by a system
+crash or improper shutdown.
+.IP "3" 4
+.IX Item "3"
+If the \f(CW\*(C`\-u\*(C'\fR option is specified, it performs any necessary upgrades
+to the database. See the \s-1UPGRADING\s0 section below.
+.IP "4" 4
+.IX Item "4"
+Starts the \s-1DB\s0 housekeeping processes (ovdb_monitor) if they're not
+already running. (Unless the \f(CW\*(C`\-r\*(C'\fR option is specified).
+.IP "5" 4
+.IX Item "5"
+Starts the ovdb readserver (ovdb_server) processes if \f(CW\*(C`readserver\*(C'\fR
+in \fIovdb.conf\fR is \f(CW\*(C`true\*(C'\fR, and if they're not
+already running. (Unless the \f(CW\*(C`\-r\*(C'\fR option is specified).
+.PP
+Returns exit status of 0 if all steps were completed successfully.
+In the event of an error, messages are written to syslog and/or stderr.
+.PP
+If a recovery was attempted but it failed, the database may be
+damaged beyond repair, requiring a rebuild with \fImakehistory\fR\|(8).
+.PP
+This command is normally invoked automatically by \fIrc.news\fR\|(8).
+.PP
+It is \s-1OK\s0 to run this command multiple times.
+.SH "OPTIONS"
+.IX Header "OPTIONS"
+.ie n .IP """\-r""" 4
+.el .IP "\f(CW\-r\fR" 4
+.IX Item "-r"
+Perform recovery only. \f(CW\*(C`ovdb_monitor\*(C'\fR is not started.
+.ie n .IP """\-u""" 4
+.el .IP "\f(CW\-u\fR" 4
+.IX Item "-u"
+Perform any needed upgrades. Recovery is not attempted.
+\&\f(CW\*(C`ovdb_monitor\*(C'\fR is started if the upgrade succeeded.
+.SH "UPGRADING"
+.IX Header "UPGRADING"
+There are two situations in which the database will need to be
+upgraded:
+.IP "\(bu" 4
+You upgrade the BerkeleyDB library to a newer version, for example
+from 2.7.7 to 3.1.17. In this case, the BerkeleyDB db\->\fIupgrade()\fR
+method is used.
+.IP "\(bu" 4
+You upgrade ovdb to a newer major version; i.e., ovdb\-1.0 to ovdb\-2.0.
+.PP
+In both of these cases, the database is upgraded in\-place; and the
+upgrade can not be undone. Do not interrupt the upgrade process once
+it has started, because there is a risk of irrepairable corruption.
+The upgrade may take several minutes to complete.
+If an upgrade does get interrupted, try running the upgrade again.
+.PP
+Here's an example procedure to upgrade a database created with BerkeleyDB
+2.7.7 to use BerkeleyDB 3.1.17:
+.IP "1" 4
+.IX Item "1"
+Build and install the BerkeleyDB 3.1.17
+.IP "2" 4
+.IX Item "2"
+Run configure in the \s-1INN\s0 source tree and make sure it picks up the
+right BerkeleyDB directory (e.g., /usr/local/BerkeleyDB.3.1)
+.IP "3" 4
+.IX Item "3"
+Do a \f(CW\*(C`make\*(C'\fR
+.IP "4" 4
+.IX Item "4"
+Shut down \s-1INN\s0 (e.g., with \f(CW\*(C`rc.news stop\*(C'\fR). Be sure to kill all nnrpds as
+well.
+.IP "5" 4
+.IX Item "5"
+Do a \f(CW\*(C`make update\*(C'\fR to install the new binaries.
+.IP "6" 4
+.IX Item "6"
+Run \f(CW\*(C`ovdb_init \-u\*(C'\fR as the news user.
+.IP "7" 4
+.IX Item "7"
+Start \s-1INN\s0 with \f(CW\*(C`rc.news\*(C'\fR
+.PP
+It is \s-1OK\s0 to specify \f(CW\*(C`\-u\*(C'\fR even if no upgrades are needed.
+.SH "HISTORY"
+.IX Header "HISTORY"
+Written by Heath Kehoe <hakehoe@avalon.net> for InterNetNews.
+.SH "SEE ALSO"
+.IX Header "SEE ALSO"
+\&\fIovdb\fR\|(5), \fImakehistory\fR\|(8)
--- /dev/null
+.\" Automatically generated by Pod::Man v1.37, Pod::Parser v1.32
+.\"
+.\" Standard preamble:
+.\" ========================================================================
+.de Sh \" Subsection heading
+.br
+.if t .Sp
+.ne 5
+.PP
+\fB\\$1\fR
+.PP
+..
+.de Sp \" Vertical space (when we can't use .PP)
+.if t .sp .5v
+.if n .sp
+..
+.de Vb \" Begin verbatim text
+.ft CW
+.nf
+.ne \\$1
+..
+.de Ve \" End verbatim text
+.ft R
+.fi
+..
+.\" Set up some character translations and predefined strings. \*(-- will
+.\" give an unbreakable dash, \*(PI will give pi, \*(L" will give a left
+.\" double quote, and \*(R" will give a right double quote. \*(C+ will
+.\" give a nicer C++. Capital omega is used to do unbreakable dashes and
+.\" therefore won't be available. \*(C` and \*(C' expand to `' in nroff,
+.\" nothing in troff, for use with C<>.
+.tr \(*W-
+.ds C+ C\v'-.1v'\h'-1p'\s-2+\h'-1p'+\s0\v'.1v'\h'-1p'
+.ie n \{\
+. ds -- \(*W-
+. ds PI pi
+. if (\n(.H=4u)&(1m=24u) .ds -- \(*W\h'-12u'\(*W\h'-12u'-\" diablo 10 pitch
+. if (\n(.H=4u)&(1m=20u) .ds -- \(*W\h'-12u'\(*W\h'-8u'-\" diablo 12 pitch
+. ds L" ""
+. ds R" ""
+. ds C` ""
+. ds C' ""
+'br\}
+.el\{\
+. ds -- \|\(em\|
+. ds PI \(*p
+. ds L" ``
+. ds R" ''
+'br\}
+.\"
+.\" If the F register is turned on, we'll generate index entries on stderr for
+.\" titles (.TH), headers (.SH), subsections (.Sh), items (.Ip), and index
+.\" entries marked with X<> in POD. Of course, you'll have to process the
+.\" output yourself in some meaningful fashion.
+.if \nF \{\
+. de IX
+. tm Index:\\$1\t\\n%\t"\\$2"
+..
+. nr % 0
+. rr F
+.\}
+.\"
+.\" For nroff, turn off justification. Always turn off hyphenation; it makes
+.\" way too many mistakes in technical documents.
+.hy 0
+.if n .na
+.\"
+.\" Accent mark definitions (@(#)ms.acc 1.5 88/02/08 SMI; from UCB 4.2).
+.\" Fear. Run. Save yourself. No user-serviceable parts.
+. \" fudge factors for nroff and troff
+.if n \{\
+. ds #H 0
+. ds #V .8m
+. ds #F .3m
+. ds #[ \f1
+. ds #] \fP
+.\}
+.if t \{\
+. ds #H ((1u-(\\\\n(.fu%2u))*.13m)
+. ds #V .6m
+. ds #F 0
+. ds #[ \&
+. ds #] \&
+.\}
+. \" simple accents for nroff and troff
+.if n \{\
+. ds ' \&
+. ds ` \&
+. ds ^ \&
+. ds , \&
+. ds ~ ~
+. ds /
+.\}
+.if t \{\
+. ds ' \\k:\h'-(\\n(.wu*8/10-\*(#H)'\'\h"|\\n:u"
+. ds ` \\k:\h'-(\\n(.wu*8/10-\*(#H)'\`\h'|\\n:u'
+. ds ^ \\k:\h'-(\\n(.wu*10/11-\*(#H)'^\h'|\\n:u'
+. ds , \\k:\h'-(\\n(.wu*8/10)',\h'|\\n:u'
+. ds ~ \\k:\h'-(\\n(.wu-\*(#H-.1m)'~\h'|\\n:u'
+. ds / \\k:\h'-(\\n(.wu*8/10-\*(#H)'\z\(sl\h'|\\n:u'
+.\}
+. \" troff and (daisy-wheel) nroff accents
+.ds : \\k:\h'-(\\n(.wu*8/10-\*(#H+.1m+\*(#F)'\v'-\*(#V'\z.\h'.2m+\*(#F'.\h'|\\n:u'\v'\*(#V'
+.ds 8 \h'\*(#H'\(*b\h'-\*(#H'
+.ds o \\k:\h'-(\\n(.wu+\w'\(de'u-\*(#H)/2u'\v'-.3n'\*(#[\z\(de\v'.3n'\h'|\\n:u'\*(#]
+.ds d- \h'\*(#H'\(pd\h'-\w'~'u'\v'-.25m'\f2\(hy\fP\v'.25m'\h'-\*(#H'
+.ds D- D\\k:\h'-\w'D'u'\v'-.11m'\z\(hy\v'.11m'\h'|\\n:u'
+.ds th \*(#[\v'.3m'\s+1I\s-1\v'-.3m'\h'-(\w'I'u*2/3)'\s-1o\s+1\*(#]
+.ds Th \*(#[\s+2I\s-2\h'-\w'I'u*3/5'\v'-.3m'o\v'.3m'\*(#]
+.ds ae a\h'-(\w'a'u*4/10)'e
+.ds Ae A\h'-(\w'A'u*4/10)'E
+. \" corrections for vroff
+.if v .ds ~ \\k:\h'-(\\n(.wu*9/10-\*(#H)'\s-2\u~\d\s+2\h'|\\n:u'
+.if v .ds ^ \\k:\h'-(\\n(.wu*10/11-\*(#H)'\v'-.4m'^\v'.4m'\h'|\\n:u'
+. \" for low resolution devices (crt and lpr)
+.if \n(.H>23 .if \n(.V>19 \
+\{\
+. ds : e
+. ds 8 ss
+. ds o a
+. ds d- d\h'-1'\(ga
+. ds D- D\h'-1'\(hy
+. ds th \o'bp'
+. ds Th \o'LP'
+. ds ae ae
+. ds Ae AE
+.\}
+.rm #[ #] #H #V #F C
+.\" ========================================================================
+.\"
+.IX Title "OVDB_MONITOR 8"
+.TH OVDB_MONITOR 8 "2008-04-06" "INN 2.4.5" "InterNetNews Documentation"
+.SH "NAME"
+ovdb_monitor \- Database maintenance
+.SH "SYNOPSYS"
+.IX Header "SYNOPSYS"
+Use \f(CW\*(C`ovdb_init\*(C'\fR to start ovdb_monitor
+.SH "DESCRIPTION"
+.IX Header "DESCRIPTION"
+When started (by \f(CW\*(C`ovdb_init\*(C'\fR), \f(CW\*(C`ovdb_monitor\*(C'\fR forks three processes
+that perform routine database maintenance tasks. These are:
+transaction checkpointing, deadlock detection, and transaction log
+removal. The process \s-1ID\s0 of the parent is written to
+\&\fI\fIpathrun\fI/ovdb_monitor.pid\fR. This \s-1PID\s0 is used by other \s-1INN\s0
+commands to verify that ovdb_monitor is running.
+.PP
+To shut down ovdb_monitor, send a \s-1TERM\s0 signal to the process \s-1ID\s0
+in \fI\fIpathrun\fI/ovdb_monitor.pid\fR . The parent process will shut
+down the three children and wait for their exit before exiting itself.
+.SH "HISTORY"
+.IX Header "HISTORY"
+Written by Heath Kehoe <hakehoe@avalon.net> for InterNetNews.
+.SH "SEE ALSO"
+.IX Header "SEE ALSO"
+\&\fIovdb\fR\|(5), \fIovdb_init\fR\|(8)
--- /dev/null
+.\" Automatically generated by Pod::Man v1.37, Pod::Parser v1.32
+.\"
+.\" Standard preamble:
+.\" ========================================================================
+.de Sh \" Subsection heading
+.br
+.if t .Sp
+.ne 5
+.PP
+\fB\\$1\fR
+.PP
+..
+.de Sp \" Vertical space (when we can't use .PP)
+.if t .sp .5v
+.if n .sp
+..
+.de Vb \" Begin verbatim text
+.ft CW
+.nf
+.ne \\$1
+..
+.de Ve \" End verbatim text
+.ft R
+.fi
+..
+.\" Set up some character translations and predefined strings. \*(-- will
+.\" give an unbreakable dash, \*(PI will give pi, \*(L" will give a left
+.\" double quote, and \*(R" will give a right double quote. \*(C+ will
+.\" give a nicer C++. Capital omega is used to do unbreakable dashes and
+.\" therefore won't be available. \*(C` and \*(C' expand to `' in nroff,
+.\" nothing in troff, for use with C<>.
+.tr \(*W-
+.ds C+ C\v'-.1v'\h'-1p'\s-2+\h'-1p'+\s0\v'.1v'\h'-1p'
+.ie n \{\
+. ds -- \(*W-
+. ds PI pi
+. if (\n(.H=4u)&(1m=24u) .ds -- \(*W\h'-12u'\(*W\h'-12u'-\" diablo 10 pitch
+. if (\n(.H=4u)&(1m=20u) .ds -- \(*W\h'-12u'\(*W\h'-8u'-\" diablo 12 pitch
+. ds L" ""
+. ds R" ""
+. ds C` ""
+. ds C' ""
+'br\}
+.el\{\
+. ds -- \|\(em\|
+. ds PI \(*p
+. ds L" ``
+. ds R" ''
+'br\}
+.\"
+.\" If the F register is turned on, we'll generate index entries on stderr for
+.\" titles (.TH), headers (.SH), subsections (.Sh), items (.Ip), and index
+.\" entries marked with X<> in POD. Of course, you'll have to process the
+.\" output yourself in some meaningful fashion.
+.if \nF \{\
+. de IX
+. tm Index:\\$1\t\\n%\t"\\$2"
+..
+. nr % 0
+. rr F
+.\}
+.\"
+.\" For nroff, turn off justification. Always turn off hyphenation; it makes
+.\" way too many mistakes in technical documents.
+.hy 0
+.if n .na
+.\"
+.\" Accent mark definitions (@(#)ms.acc 1.5 88/02/08 SMI; from UCB 4.2).
+.\" Fear. Run. Save yourself. No user-serviceable parts.
+. \" fudge factors for nroff and troff
+.if n \{\
+. ds #H 0
+. ds #V .8m
+. ds #F .3m
+. ds #[ \f1
+. ds #] \fP
+.\}
+.if t \{\
+. ds #H ((1u-(\\\\n(.fu%2u))*.13m)
+. ds #V .6m
+. ds #F 0
+. ds #[ \&
+. ds #] \&
+.\}
+. \" simple accents for nroff and troff
+.if n \{\
+. ds ' \&
+. ds ` \&
+. ds ^ \&
+. ds , \&
+. ds ~ ~
+. ds /
+.\}
+.if t \{\
+. ds ' \\k:\h'-(\\n(.wu*8/10-\*(#H)'\'\h"|\\n:u"
+. ds ` \\k:\h'-(\\n(.wu*8/10-\*(#H)'\`\h'|\\n:u'
+. ds ^ \\k:\h'-(\\n(.wu*10/11-\*(#H)'^\h'|\\n:u'
+. ds , \\k:\h'-(\\n(.wu*8/10)',\h'|\\n:u'
+. ds ~ \\k:\h'-(\\n(.wu-\*(#H-.1m)'~\h'|\\n:u'
+. ds / \\k:\h'-(\\n(.wu*8/10-\*(#H)'\z\(sl\h'|\\n:u'
+.\}
+. \" troff and (daisy-wheel) nroff accents
+.ds : \\k:\h'-(\\n(.wu*8/10-\*(#H+.1m+\*(#F)'\v'-\*(#V'\z.\h'.2m+\*(#F'.\h'|\\n:u'\v'\*(#V'
+.ds 8 \h'\*(#H'\(*b\h'-\*(#H'
+.ds o \\k:\h'-(\\n(.wu+\w'\(de'u-\*(#H)/2u'\v'-.3n'\*(#[\z\(de\v'.3n'\h'|\\n:u'\*(#]
+.ds d- \h'\*(#H'\(pd\h'-\w'~'u'\v'-.25m'\f2\(hy\fP\v'.25m'\h'-\*(#H'
+.ds D- D\\k:\h'-\w'D'u'\v'-.11m'\z\(hy\v'.11m'\h'|\\n:u'
+.ds th \*(#[\v'.3m'\s+1I\s-1\v'-.3m'\h'-(\w'I'u*2/3)'\s-1o\s+1\*(#]
+.ds Th \*(#[\s+2I\s-2\h'-\w'I'u*3/5'\v'-.3m'o\v'.3m'\*(#]
+.ds ae a\h'-(\w'a'u*4/10)'e
+.ds Ae A\h'-(\w'A'u*4/10)'E
+. \" corrections for vroff
+.if v .ds ~ \\k:\h'-(\\n(.wu*9/10-\*(#H)'\s-2\u~\d\s+2\h'|\\n:u'
+.if v .ds ^ \\k:\h'-(\\n(.wu*10/11-\*(#H)'\v'-.4m'^\v'.4m'\h'|\\n:u'
+. \" for low resolution devices (crt and lpr)
+.if \n(.H>23 .if \n(.V>19 \
+\{\
+. ds : e
+. ds 8 ss
+. ds o a
+. ds d- d\h'-1'\(ga
+. ds D- D\h'-1'\(hy
+. ds th \o'bp'
+. ds Th \o'LP'
+. ds ae ae
+. ds Ae AE
+.\}
+.rm #[ #] #H #V #F C
+.\" ========================================================================
+.\"
+.IX Title "OVDB_SERVER 8"
+.TH OVDB_SERVER 8 "2008-04-06" "INN 2.4.5" "InterNetNews Documentation"
+.SH "NAME"
+ovdb_server \- overview 'helper' server
+.SH "SYNOPSYS"
+.IX Header "SYNOPSYS"
+Use \f(CW\*(C`ovdb_init\*(C'\fR to start ovdb_server
+.SH "DESCRIPTION"
+.IX Header "DESCRIPTION"
+If the \f(CW\*(C`readserver\*(C'\fR parameter in \fIovdb.conf\fR is true,
+\&\f(CW\*(C`ovdb_init\*(C'\fR will start \f(CW\*(C`ovdb_server\*(C'\fR.
+.PP
+\&\f(CW\*(C`ovdb_server\*(C'\fR opens the overview database, and accesses it
+on behalf of the nnrpd reader processes.
+.PP
+To shut down ovdb_server, send a \s-1TERM\s0 signal to the process \s-1ID\s0
+in \fI\fIpathrun\fI/ovdb_server.pid\fR . The parent process will shut
+down its children and wait for their exit before exiting itself.
+.SH "HISTORY"
+.IX Header "HISTORY"
+Written by Heath Kehoe <hakehoe@avalon.net> for InterNetNews.
+.SH "SEE ALSO"
+.IX Header "SEE ALSO"
+\&\fIovdb\fR\|(5), \fIovdb_init\fR\|(8)
--- /dev/null
+.\" Automatically generated by Pod::Man v1.37, Pod::Parser v1.32
+.\"
+.\" Standard preamble:
+.\" ========================================================================
+.de Sh \" Subsection heading
+.br
+.if t .Sp
+.ne 5
+.PP
+\fB\\$1\fR
+.PP
+..
+.de Sp \" Vertical space (when we can't use .PP)
+.if t .sp .5v
+.if n .sp
+..
+.de Vb \" Begin verbatim text
+.ft CW
+.nf
+.ne \\$1
+..
+.de Ve \" End verbatim text
+.ft R
+.fi
+..
+.\" Set up some character translations and predefined strings. \*(-- will
+.\" give an unbreakable dash, \*(PI will give pi, \*(L" will give a left
+.\" double quote, and \*(R" will give a right double quote. \*(C+ will
+.\" give a nicer C++. Capital omega is used to do unbreakable dashes and
+.\" therefore won't be available. \*(C` and \*(C' expand to `' in nroff,
+.\" nothing in troff, for use with C<>.
+.tr \(*W-
+.ds C+ C\v'-.1v'\h'-1p'\s-2+\h'-1p'+\s0\v'.1v'\h'-1p'
+.ie n \{\
+. ds -- \(*W-
+. ds PI pi
+. if (\n(.H=4u)&(1m=24u) .ds -- \(*W\h'-12u'\(*W\h'-12u'-\" diablo 10 pitch
+. if (\n(.H=4u)&(1m=20u) .ds -- \(*W\h'-12u'\(*W\h'-8u'-\" diablo 12 pitch
+. ds L" ""
+. ds R" ""
+. ds C` ""
+. ds C' ""
+'br\}
+.el\{\
+. ds -- \|\(em\|
+. ds PI \(*p
+. ds L" ``
+. ds R" ''
+'br\}
+.\"
+.\" If the F register is turned on, we'll generate index entries on stderr for
+.\" titles (.TH), headers (.SH), subsections (.Sh), items (.Ip), and index
+.\" entries marked with X<> in POD. Of course, you'll have to process the
+.\" output yourself in some meaningful fashion.
+.if \nF \{\
+. de IX
+. tm Index:\\$1\t\\n%\t"\\$2"
+..
+. nr % 0
+. rr F
+.\}
+.\"
+.\" For nroff, turn off justification. Always turn off hyphenation; it makes
+.\" way too many mistakes in technical documents.
+.hy 0
+.if n .na
+.\"
+.\" Accent mark definitions (@(#)ms.acc 1.5 88/02/08 SMI; from UCB 4.2).
+.\" Fear. Run. Save yourself. No user-serviceable parts.
+. \" fudge factors for nroff and troff
+.if n \{\
+. ds #H 0
+. ds #V .8m
+. ds #F .3m
+. ds #[ \f1
+. ds #] \fP
+.\}
+.if t \{\
+. ds #H ((1u-(\\\\n(.fu%2u))*.13m)
+. ds #V .6m
+. ds #F 0
+. ds #[ \&
+. ds #] \&
+.\}
+. \" simple accents for nroff and troff
+.if n \{\
+. ds ' \&
+. ds ` \&
+. ds ^ \&
+. ds , \&
+. ds ~ ~
+. ds /
+.\}
+.if t \{\
+. ds ' \\k:\h'-(\\n(.wu*8/10-\*(#H)'\'\h"|\\n:u"
+. ds ` \\k:\h'-(\\n(.wu*8/10-\*(#H)'\`\h'|\\n:u'
+. ds ^ \\k:\h'-(\\n(.wu*10/11-\*(#H)'^\h'|\\n:u'
+. ds , \\k:\h'-(\\n(.wu*8/10)',\h'|\\n:u'
+. ds ~ \\k:\h'-(\\n(.wu-\*(#H-.1m)'~\h'|\\n:u'
+. ds / \\k:\h'-(\\n(.wu*8/10-\*(#H)'\z\(sl\h'|\\n:u'
+.\}
+. \" troff and (daisy-wheel) nroff accents
+.ds : \\k:\h'-(\\n(.wu*8/10-\*(#H+.1m+\*(#F)'\v'-\*(#V'\z.\h'.2m+\*(#F'.\h'|\\n:u'\v'\*(#V'
+.ds 8 \h'\*(#H'\(*b\h'-\*(#H'
+.ds o \\k:\h'-(\\n(.wu+\w'\(de'u-\*(#H)/2u'\v'-.3n'\*(#[\z\(de\v'.3n'\h'|\\n:u'\*(#]
+.ds d- \h'\*(#H'\(pd\h'-\w'~'u'\v'-.25m'\f2\(hy\fP\v'.25m'\h'-\*(#H'
+.ds D- D\\k:\h'-\w'D'u'\v'-.11m'\z\(hy\v'.11m'\h'|\\n:u'
+.ds th \*(#[\v'.3m'\s+1I\s-1\v'-.3m'\h'-(\w'I'u*2/3)'\s-1o\s+1\*(#]
+.ds Th \*(#[\s+2I\s-2\h'-\w'I'u*3/5'\v'-.3m'o\v'.3m'\*(#]
+.ds ae a\h'-(\w'a'u*4/10)'e
+.ds Ae A\h'-(\w'A'u*4/10)'E
+. \" corrections for vroff
+.if v .ds ~ \\k:\h'-(\\n(.wu*9/10-\*(#H)'\s-2\u~\d\s+2\h'|\\n:u'
+.if v .ds ^ \\k:\h'-(\\n(.wu*10/11-\*(#H)'\v'-.4m'^\v'.4m'\h'|\\n:u'
+. \" for low resolution devices (crt and lpr)
+.if \n(.H>23 .if \n(.V>19 \
+\{\
+. ds : e
+. ds 8 ss
+. ds o a
+. ds d- d\h'-1'\(ga
+. ds D- D\h'-1'\(hy
+. ds th \o'bp'
+. ds Th \o'LP'
+. ds ae ae
+. ds Ae AE
+.\}
+.rm #[ #] #H #V #F C
+.\" ========================================================================
+.\"
+.IX Title "OVDB_STAT 8"
+.TH OVDB_STAT 8 "2008-04-06" "INN 2.4.5" "InterNetNews Documentation"
+.SH "NAME"
+ovdb_stat \- Display information from the ovdb database
+.SH "SYNOPSYS"
+.IX Header "SYNOPSYS"
+\&\fBovdb_stat\fR \fB\-Hgci\fR [\fB\-r\fR \fIartnumrange\fR] newsgroup [newsgroup ...]
+.PP
+\&\fBovdb_stat\fR \fB\-Hklmtv\fR [\fB\-d\fR \fIdatabase\fR]
+.SH "DESCRIPTION"
+.IX Header "DESCRIPTION"
+\&\fBovdb_stat\fR displays information from the ovdb database: BerkeleyDB
+statistics, newsgroup data, and overview records; and optionally
+outputs in \s-1HTML\s0 format.
+.SH "OPTIONS"
+.IX Header "OPTIONS"
+.IP "\fB\-g\fR" 4
+.IX Item "-g"
+Newsgroup himark, lowmark, article count, and flag for the given newsgroups
+(as stored in the ovdb \*(L"groupinfo\*(R" database) are displayed.
+.IP "\fB\-c\fR" 4
+.IX Item "-c"
+Similar to \fB\-g\fR, except the himark, lowmark, and count are calculated
+by actually scanning the overview records and counting them.
+This can be a lengthy operation on groups with lots of articles.
+.IP "\fB\-i\fR" 4
+.IX Item "-i"
+Internal data regarding the given newsgroups are displayed.
+.IP "\fB\-r\fR \fIartnumrange\fR" 4
+.IX Item "-r artnumrange"
+Overview records are retrieved. The \fIartnumrange\fR parameter may be
+a single article number, or a range of articles in the format \f(CW\*(C`low\-hi\*(C'\fR.
+.IP "\fB\-H\fR" 4
+.IX Item "-H"
+Output is presented in \s-1HTML\s0 format.
+.IP "\fB\-k\fR" 4
+.IX Item "-k"
+Displays lock region statistics, as returned by the BerkeleyDB \fIlock_stat()\fR
+call.
+.IP "\fB\-l\fR" 4
+.IX Item "-l"
+Displays log region statistics, as returned by the BerkeleyDB \fIlog_stat()\fR
+call.
+.IP "\fB\-m\fR" 4
+.IX Item "-m"
+Displays global memory pool statistics, as returned by the
+BerkeleyDB \fImemp_stat()\fR call.
+.IP "\fB\-M\fR" 4
+.IX Item "-M"
+Same as \fB\-m\fR, and also displays memory pool statistics for each
+database file.
+.IP "\fB\-t\fR" 4
+.IX Item "-t"
+Displays log region statistics, as returned by the BerkeleyDB \fItxn_stat()\fR
+call.
+.IP "\fB\-v\fR" 4
+.IX Item "-v"
+Displays ovdb version, and BerkeleyDB version.
+.IP "\fB\-d\fR \fIdatabase\fR" 4
+.IX Item "-d database"
+Displays information about the given database, as returned by the
+BerkeleyDB db\->\fIstat()\fR call. This operation may take a long time
+on busy systems (several minutes or more).
+.SH "WARNINGS"
+.IX Header "WARNINGS"
+ovdb_stat may be safely killed with the \s-1INT\s0, \s-1TERM\s0, or \s-1HUP\s0 signals.
+It catches those signals and exits cleanly.
+Do not kill ovdb_stat with other signals, unless absolutely necessary,
+because it may leave stale locks in the \s-1DB\s0 environment.
+.SH "HISTORY"
+.IX Header "HISTORY"
+Written by Heath Kehoe <hakehoe@avalon.net> for InterNetNews.
+.SH "SEE ALSO"
+.IX Header "SEE ALSO"
+\&\fIovdb\fR\|(5)
--- /dev/null
+.\" $Revision: 5909 $
+.TH OVERCHAN 8
+.SH NAME
+overchan \- update the news overview database
+.SH SYNOPSIS
+.B overchan
+[
+.BR file \ ...
+]
+.SH DESCRIPTION
+.I Overchan
+reads article data from
+.I files
+or standard input if none are specified.
+(A single dash in the file list means to read standard input.)
+It uses this information to update the news overview database.
+.I Overchan
+was originally designed to be used by InterNetNews or the C News ``mkov'' packages
+to update the database as the articles come in.
+For current INN, the database is stored in a selected overview method.
+This can be done within
+.IR innd (8)
+itself, but
+.IR overchan (8)
+can be used instead, if
+.I <useoverchan in inn.conf>
+is ``true'' and appropriate setup is done in
+.IR newsfeeds ,
+for example:
+.PP
+.RS
+overview!:*:Tc,WnteO:<pathbin in inn.conf>/overchan
+.RE
+.PP
+.I Overchan
+input data consists of a line of text, separated into four parts by a space.
+The first part is a token for the article.
+The second part is time when the article was received.
+The third part is time when the article will be expired (which represents
+the Expires header).
+The fourth part is the data to be stored.
+.PP
+The data in the overview files should be expired by running
+.IR expireover (8).
+This is normally done by adding the ``expireover'' flag to the
+.IR news.daily (8)
+invocation.
+.PP
+.SH HISTORY
+Written by Rob Robertson <rob@violet.berkeley.edu>
+and Rich $alz <rsalz@uunet.uu.net> for InterNetNews.
+.de R$
+This is revision \\$3, dated \\$4.
+..
+.R$ $Id: overchan.8 5909 2002-12-03 05:17:18Z vinocur $
+.SH "SEE ALSO"
+expireover(8),
+inn.conf(5),
+news.daily(8),
+newsfeeds(5).
--- /dev/null
+.\" $Revision: 5909 $
+.TH OVERVIEW.FMT 5
+.SH NAME
+overview.fmt \- format of news overview database
+.SH DESCRIPTION
+The file
+.I <pathetc in inn.conf>/overview.fmt
+specifies the organization of the news overview database.
+Blank lines and lines beginning with a number sign (``#'') are ignored.
+The order of lines in this file is important; it determines the order
+in which the fields will appear in the database.
+.PP
+Most lines will consist of an article header name, optionally
+followed by a colon.
+A trailing set of lines can have the word ``full'' appear after the
+colon; this indicates that the header should appear as well as its value.
+.PP
+If this file is changed, new overview records will be constructed with
+the modified format. If it is desired that existing records be updated to
+the new format, it is necessary to rebuild the overview database using
+.IR makehistory (8)
+after removing all existing overview files.
+.PP
+\&``Xref:full'' must be included, or
+.IR innd (8)
+and other programs which utilize overview method cannot start.
+.PP
+The default file, show below, is compatible with Geoff Collyer's ``nov''
+package:
+.PP
+.RS
+.nf
+Subject:
+From:
+Date:
+Message-ID:
+References:
+Bytes:
+Lines:
+Xref:full
+.fi
+.RE
+.PP
+Usually the only modifications which should be made to the default file
+are additions of new fields to the end of the file; all such fields
+should have ``full'' after the colon. The first eight fields should
+remain in the same order as above.
+.SH HISTORY
+Written by Rich $alz <rsalz@uunet.uu.net> for InterNetNews.
+Intended to be compatible with the
+.I nov
+package written by Geoff Collyer <geoff@world.std.com>.
+.de R$
+This is revision \\$3, dated \\$4.
+..
+.R$ $Id: overview.fmt.5 5909 2002-12-03 05:17:18Z vinocur $
+.SH "SEE ALSO"
+inn.conf(5)
--- /dev/null
+.\" $Revision: 6312 $
+.TH PARSEDATE 3
+.SH NAME
+parsedate \- convert time and date string to number
+.SH SYNOPSIS
+.nf
+.ta \w' unsigned long 'u
+.B "#include <sys/types.h>"
+
+.B "typedef struct _TIMEINFO {"
+.B " time_t time;"
+.B " long usec;"
+.B " long tzone;
+.B "} TIMEINFO;"
+
+.B "time_t"
+.B "parsedate(text, now)"
+.B " char *text;"
+.B " TIMEINFO *now;"
+.fi
+.SH DESCRIPTION
+.I Parsedate
+converts many common time specifications into the number of seconds
+since the epoch \(em
+.IR i.e. ,
+a
+.IR time_t ;
+see
+.IR time (2).
+.PP
+.I Parsedate
+returns the time, or \-1 on error.
+.I Text
+is a character string containing the time and date.
+.I Now
+is a pointer to the time that should be used for calculating relative dates.
+If
+.I now
+is NULL, then
+.I GetTimeInfo in
+.IR libinn (3)
+is used to obtain the current time and timezone.
+.PP
+The character string consists of zero or more specifications of the following
+form:
+.TP
+.I time
+A time of day, which is of the form
+.IR hh [ :mm [ :ss ]]
+.RI [ meridian ]
+.RI [ zone ]
+or
+.IR hhmm
+.RI [ meridian ]
+.RI [ zone ].
+If no meridian is specified,
+.I hh
+is interpreted on a 24-hour clock.
+.TP
+.I date
+A specific month and day with optional year.
+The acceptable formats are
+.IR mm/dd [ /yy ],
+.IR yyyy/mm/dd ,
+.IR "monthname dd" "[, " yy ],
+.IR "dd monthname" " [" yy "],
+and
+.IR "day, dd monthname yy" .
+The default year is the current year.
+If the year is less then 100, then 1900 is added to it; if it is
+less then 21, then 2000 is added to it.
+.TP
+.I "relative time"
+A specification relative to the current time.
+The format is
+.IR "number unit" ;
+acceptable units are
+.IR year ,
+.IR month ,
+.IR week ,
+.IR day ,
+.IR hour ,
+.I minute
+(or
+.IR min ),
+and
+.I second
+(or
+.IR sec ).
+The unit can be specified as a singular or plural, as in
+.IR "3 weeks" .
+.PP
+The actual date is calculated according to the following steps.
+First, any absolute date and/or time is processed and converted.
+Using that time as the base, day-of-week specifications are added.
+Next, relative specifications are used.
+If a date or day is specified, and no absolute or relative time is given,
+midnight is used.
+Finally, a correction is applied so that the correct hour of the day is
+produced after allowing for daylight savings time differences.
+.PP
+.I Parsedate
+ignores case when parsing all words; unknown words are taken to be unknown
+timezones, which are treated as GMT.
+The names of the months and days of the week can be abbreviated to their
+first three letters, with optional trailing period.
+Periods are ignored in any timezone or meridian values.
+.SH BUGS
+.I Parsedate
+does not accept all desirable and unambiguous constructions.
+Semantically incorrect dates such as ``February 31'' are accepted.
+.PP
+Daylight savings time is always taken as a one-hour change which is wrong
+for some places.
+The daylight savings time correction can get confused if parsing a
+time within an hour of when the reckoning changes, or if given a
+partial date.
+.SH HISTORY
+Originally written by Steven M. Bellovin <smb@research.att.com> while
+at the University of North Carolina at Chapel Hill and distributed
+under the name
+.IR getdate .
+.PP
+A major overhaul was done by Rich $alz <rsalz@bbn.com> and Jim Berets
+<jberets@bbn.com> in August, 1990.
+.PP
+It was further revised (primarily to remove obsolete constructs and timezone
+names) a year later by Rich (now <rsalz@osf.org>) for InterNetNews,
+and the name was changed.
+.de R$
+This is revision \\$3, dated \\$4.
+..
+.R$ $Id: parsedate.3 6312 2003-05-04 21:40:11Z rra $
+.SH "SEE ALSO"
+date(1),
+ctime(3),
+libinn(3),
+time(2).
--- /dev/null
+.\" Automatically generated by Pod::Man v1.37, Pod::Parser v1.32
+.\"
+.\" Standard preamble:
+.\" ========================================================================
+.de Sh \" Subsection heading
+.br
+.if t .Sp
+.ne 5
+.PP
+\fB\\$1\fR
+.PP
+..
+.de Sp \" Vertical space (when we can't use .PP)
+.if t .sp .5v
+.if n .sp
+..
+.de Vb \" Begin verbatim text
+.ft CW
+.nf
+.ne \\$1
+..
+.de Ve \" End verbatim text
+.ft R
+.fi
+..
+.\" Set up some character translations and predefined strings. \*(-- will
+.\" give an unbreakable dash, \*(PI will give pi, \*(L" will give a left
+.\" double quote, and \*(R" will give a right double quote. \*(C+ will
+.\" give a nicer C++. Capital omega is used to do unbreakable dashes and
+.\" therefore won't be available. \*(C` and \*(C' expand to `' in nroff,
+.\" nothing in troff, for use with C<>.
+.tr \(*W-
+.ds C+ C\v'-.1v'\h'-1p'\s-2+\h'-1p'+\s0\v'.1v'\h'-1p'
+.ie n \{\
+. ds -- \(*W-
+. ds PI pi
+. if (\n(.H=4u)&(1m=24u) .ds -- \(*W\h'-12u'\(*W\h'-12u'-\" diablo 10 pitch
+. if (\n(.H=4u)&(1m=20u) .ds -- \(*W\h'-12u'\(*W\h'-8u'-\" diablo 12 pitch
+. ds L" ""
+. ds R" ""
+. ds C` ""
+. ds C' ""
+'br\}
+.el\{\
+. ds -- \|\(em\|
+. ds PI \(*p
+. ds L" ``
+. ds R" ''
+'br\}
+.\"
+.\" If the F register is turned on, we'll generate index entries on stderr for
+.\" titles (.TH), headers (.SH), subsections (.Sh), items (.Ip), and index
+.\" entries marked with X<> in POD. Of course, you'll have to process the
+.\" output yourself in some meaningful fashion.
+.if \nF \{\
+. de IX
+. tm Index:\\$1\t\\n%\t"\\$2"
+..
+. nr % 0
+. rr F
+.\}
+.\"
+.\" For nroff, turn off justification. Always turn off hyphenation; it makes
+.\" way too many mistakes in technical documents.
+.hy 0
+.if n .na
+.\"
+.\" Accent mark definitions (@(#)ms.acc 1.5 88/02/08 SMI; from UCB 4.2).
+.\" Fear. Run. Save yourself. No user-serviceable parts.
+. \" fudge factors for nroff and troff
+.if n \{\
+. ds #H 0
+. ds #V .8m
+. ds #F .3m
+. ds #[ \f1
+. ds #] \fP
+.\}
+.if t \{\
+. ds #H ((1u-(\\\\n(.fu%2u))*.13m)
+. ds #V .6m
+. ds #F 0
+. ds #[ \&
+. ds #] \&
+.\}
+. \" simple accents for nroff and troff
+.if n \{\
+. ds ' \&
+. ds ` \&
+. ds ^ \&
+. ds , \&
+. ds ~ ~
+. ds /
+.\}
+.if t \{\
+. ds ' \\k:\h'-(\\n(.wu*8/10-\*(#H)'\'\h"|\\n:u"
+. ds ` \\k:\h'-(\\n(.wu*8/10-\*(#H)'\`\h'|\\n:u'
+. ds ^ \\k:\h'-(\\n(.wu*10/11-\*(#H)'^\h'|\\n:u'
+. ds , \\k:\h'-(\\n(.wu*8/10)',\h'|\\n:u'
+. ds ~ \\k:\h'-(\\n(.wu-\*(#H-.1m)'~\h'|\\n:u'
+. ds / \\k:\h'-(\\n(.wu*8/10-\*(#H)'\z\(sl\h'|\\n:u'
+.\}
+. \" troff and (daisy-wheel) nroff accents
+.ds : \\k:\h'-(\\n(.wu*8/10-\*(#H+.1m+\*(#F)'\v'-\*(#V'\z.\h'.2m+\*(#F'.\h'|\\n:u'\v'\*(#V'
+.ds 8 \h'\*(#H'\(*b\h'-\*(#H'
+.ds o \\k:\h'-(\\n(.wu+\w'\(de'u-\*(#H)/2u'\v'-.3n'\*(#[\z\(de\v'.3n'\h'|\\n:u'\*(#]
+.ds d- \h'\*(#H'\(pd\h'-\w'~'u'\v'-.25m'\f2\(hy\fP\v'.25m'\h'-\*(#H'
+.ds D- D\\k:\h'-\w'D'u'\v'-.11m'\z\(hy\v'.11m'\h'|\\n:u'
+.ds th \*(#[\v'.3m'\s+1I\s-1\v'-.3m'\h'-(\w'I'u*2/3)'\s-1o\s+1\*(#]
+.ds Th \*(#[\s+2I\s-2\h'-\w'I'u*3/5'\v'-.3m'o\v'.3m'\*(#]
+.ds ae a\h'-(\w'a'u*4/10)'e
+.ds Ae A\h'-(\w'A'u*4/10)'E
+. \" corrections for vroff
+.if v .ds ~ \\k:\h'-(\\n(.wu*9/10-\*(#H)'\s-2\u~\d\s+2\h'|\\n:u'
+.if v .ds ^ \\k:\h'-(\\n(.wu*10/11-\*(#H)'\v'-.4m'^\v'.4m'\h'|\\n:u'
+. \" for low resolution devices (crt and lpr)
+.if \n(.H>23 .if \n(.V>19 \
+\{\
+. ds : e
+. ds 8 ss
+. ds o a
+. ds d- d\h'-1'\(ga
+. ds D- D\h'-1'\(hy
+. ds th \o'bp'
+. ds Th \o'LP'
+. ds ae ae
+. ds Ae AE
+.\}
+.rm #[ #] #H #V #F C
+.\" ========================================================================
+.\"
+.IX Title "PASSWD.NNTP 5"
+.TH PASSWD.NNTP 5 "2008-04-06" "INN 2.4.5" "InterNetNews Documentation"
+.SH "NAME"
+passwd.nntp \- passwords for connecting to remote NNTP servers
+.SH "DESCRIPTION"
+.IX Header "DESCRIPTION"
+The file \fIpasswd.nntp\fR in \fIpathetc\fR contains host / name / password
+triplets for use when authenticating client programs to \s-1NNTP\s0 servers.
+This file is normally interpreted by \fINNTPsendpassword()\fR in \fIlibinn\fR\|(3).
+Blank lines and lines beginning with a number sign (\f(CW\*(C`#\*(C'\fR) are ignored.
+All other lines should consist of three or four fields separated by
+colons:
+.PP
+.Vb 2
+\& host:name:password
+\& host:name:password:style
+.Ve
+.PP
+The first field is the name of a host, and is matched in a
+case-insensitive manner. (No detailed matching, such as comparing \s-1IP\s0
+addresses, is done.)
+.PP
+The second field is a user name, and the third is a password. If either
+the username or password is empty, then that portion of the
+authentication will not occur. (For example, when connecting to a
+remote \s-1INN\s0 for peering, only the password is needed.)
+.PP
+The optional fourth field specifies the type of authentication to use.
+At present, the only recognized \*(L"authentication style\*(R" is \f(CW\*(C`authinfo\*(C'\fR;
+this is also the default. It means that \s-1NNTP\s0 \*(L"authinfo\*(R" commands are
+used to authenticate to the remote host. (The \f(CW\*(C`authinfo\*(C'\fR command is a
+common extension to \s-1RFC\s0 977.)
+.PP
+For example:
+.PP
+.Vb 3
+\& ## UUNET needs a password, MIT doesn't.
+\& mit.edu:bbn::authinfo
+\& uunet.uu.net:bbn:yoyoma:authinfo
+.Ve
+.PP
+This file should not be world\-readable.
+.SH "HISTORY"
+.IX Header "HISTORY"
+Written by Rich \f(CW$alz\fR <rsalz@uunet.uu.net> for InterNetNews. This is
+revision \f(CW$Revision:\fR 5089 $, dated \f(CW$Date:\fR 2002\-02\-03 20:03:41 +0100 (dim, 03 fév 2002) $.
+.PP
+$Id: passwd.nntp.5 7880 2008-06-16 20:37:13Z iulius $
+.SH "SEE ALSO"
+.IX Header "SEE ALSO"
+\&\fIinn.conf\fR\|(5), \fIinnd\fR\|(8), \fIlibinn\fR\|(3).
--- /dev/null
+.\" Automatically generated by Pod::Man v1.37, Pod::Parser v1.32
+.\"
+.\" Standard preamble:
+.\" ========================================================================
+.de Sh \" Subsection heading
+.br
+.if t .Sp
+.ne 5
+.PP
+\fB\\$1\fR
+.PP
+..
+.de Sp \" Vertical space (when we can't use .PP)
+.if t .sp .5v
+.if n .sp
+..
+.de Vb \" Begin verbatim text
+.ft CW
+.nf
+.ne \\$1
+..
+.de Ve \" End verbatim text
+.ft R
+.fi
+..
+.\" Set up some character translations and predefined strings. \*(-- will
+.\" give an unbreakable dash, \*(PI will give pi, \*(L" will give a left
+.\" double quote, and \*(R" will give a right double quote. \*(C+ will
+.\" give a nicer C++. Capital omega is used to do unbreakable dashes and
+.\" therefore won't be available. \*(C` and \*(C' expand to `' in nroff,
+.\" nothing in troff, for use with C<>.
+.tr \(*W-
+.ds C+ C\v'-.1v'\h'-1p'\s-2+\h'-1p'+\s0\v'.1v'\h'-1p'
+.ie n \{\
+. ds -- \(*W-
+. ds PI pi
+. if (\n(.H=4u)&(1m=24u) .ds -- \(*W\h'-12u'\(*W\h'-12u'-\" diablo 10 pitch
+. if (\n(.H=4u)&(1m=20u) .ds -- \(*W\h'-12u'\(*W\h'-8u'-\" diablo 12 pitch
+. ds L" ""
+. ds R" ""
+. ds C` ""
+. ds C' ""
+'br\}
+.el\{\
+. ds -- \|\(em\|
+. ds PI \(*p
+. ds L" ``
+. ds R" ''
+'br\}
+.\"
+.\" If the F register is turned on, we'll generate index entries on stderr for
+.\" titles (.TH), headers (.SH), subsections (.Sh), items (.Ip), and index
+.\" entries marked with X<> in POD. Of course, you'll have to process the
+.\" output yourself in some meaningful fashion.
+.if \nF \{\
+. de IX
+. tm Index:\\$1\t\\n%\t"\\$2"
+..
+. nr % 0
+. rr F
+.\}
+.\"
+.\" For nroff, turn off justification. Always turn off hyphenation; it makes
+.\" way too many mistakes in technical documents.
+.hy 0
+.if n .na
+.\"
+.\" Accent mark definitions (@(#)ms.acc 1.5 88/02/08 SMI; from UCB 4.2).
+.\" Fear. Run. Save yourself. No user-serviceable parts.
+. \" fudge factors for nroff and troff
+.if n \{\
+. ds #H 0
+. ds #V .8m
+. ds #F .3m
+. ds #[ \f1
+. ds #] \fP
+.\}
+.if t \{\
+. ds #H ((1u-(\\\\n(.fu%2u))*.13m)
+. ds #V .6m
+. ds #F 0
+. ds #[ \&
+. ds #] \&
+.\}
+. \" simple accents for nroff and troff
+.if n \{\
+. ds ' \&
+. ds ` \&
+. ds ^ \&
+. ds , \&
+. ds ~ ~
+. ds /
+.\}
+.if t \{\
+. ds ' \\k:\h'-(\\n(.wu*8/10-\*(#H)'\'\h"|\\n:u"
+. ds ` \\k:\h'-(\\n(.wu*8/10-\*(#H)'\`\h'|\\n:u'
+. ds ^ \\k:\h'-(\\n(.wu*10/11-\*(#H)'^\h'|\\n:u'
+. ds , \\k:\h'-(\\n(.wu*8/10)',\h'|\\n:u'
+. ds ~ \\k:\h'-(\\n(.wu-\*(#H-.1m)'~\h'|\\n:u'
+. ds / \\k:\h'-(\\n(.wu*8/10-\*(#H)'\z\(sl\h'|\\n:u'
+.\}
+. \" troff and (daisy-wheel) nroff accents
+.ds : \\k:\h'-(\\n(.wu*8/10-\*(#H+.1m+\*(#F)'\v'-\*(#V'\z.\h'.2m+\*(#F'.\h'|\\n:u'\v'\*(#V'
+.ds 8 \h'\*(#H'\(*b\h'-\*(#H'
+.ds o \\k:\h'-(\\n(.wu+\w'\(de'u-\*(#H)/2u'\v'-.3n'\*(#[\z\(de\v'.3n'\h'|\\n:u'\*(#]
+.ds d- \h'\*(#H'\(pd\h'-\w'~'u'\v'-.25m'\f2\(hy\fP\v'.25m'\h'-\*(#H'
+.ds D- D\\k:\h'-\w'D'u'\v'-.11m'\z\(hy\v'.11m'\h'|\\n:u'
+.ds th \*(#[\v'.3m'\s+1I\s-1\v'-.3m'\h'-(\w'I'u*2/3)'\s-1o\s+1\*(#]
+.ds Th \*(#[\s+2I\s-2\h'-\w'I'u*3/5'\v'-.3m'o\v'.3m'\*(#]
+.ds ae a\h'-(\w'a'u*4/10)'e
+.ds Ae A\h'-(\w'A'u*4/10)'E
+. \" corrections for vroff
+.if v .ds ~ \\k:\h'-(\\n(.wu*9/10-\*(#H)'\s-2\u~\d\s+2\h'|\\n:u'
+.if v .ds ^ \\k:\h'-(\\n(.wu*10/11-\*(#H)'\v'-.4m'^\v'.4m'\h'|\\n:u'
+. \" for low resolution devices (crt and lpr)
+.if \n(.H>23 .if \n(.V>19 \
+\{\
+. ds : e
+. ds 8 ss
+. ds o a
+. ds d- d\h'-1'\(ga
+. ds D- D\h'-1'\(hy
+. ds th \o'bp'
+. ds Th \o'LP'
+. ds ae ae
+. ds Ae AE
+.\}
+.rm #[ #] #H #V #F C
+.\" ========================================================================
+.\"
+.IX Title "PERL-NOCEM 8"
+.TH PERL-NOCEM 8 "2008-04-06" "INN 2.4.4" "InterNetNews Documentation"
+.SH "NAME"
+perl\-nocem \- A NoCeM\-on\-spool implementation for INN\ 2.x
+.SH "SYNOPSIS"
+.IX Header "SYNOPSIS"
+perl-nocem
+.SH "DESCRIPTION"
+.IX Header "DESCRIPTION"
+NoCeM, which is pronounced \fINo See 'Em\fR, is a protocol enabling
+authenticated third-parties to issue notices which can be used
+to cancel unwanted articles (like spam and articles in moderated
+newsgroups which were not approved by their moderators). It can
+also be used by readers as a \fIthird-party killfile\fR. It is
+intended to eventually replace the protocol for third-party cancel
+messages.
+.PP
+\&\fBperl-nocem\fR processes third\-party, PGP-signed article cancellation
+notices. It is possible not to honour all NoCeM notices but only those
+which are sent by people whom you trust (that is to say if you trust
+the \s-1PGP\s0 key they use to sign their NoCeM notices). Indeed, it is up
+to you to decide whether you wish to honour their notices, depending
+on the criteria they use.
+.PP
+Processing NoCeM notices is easy to set up:
+.IP "1." 4
+Import the keys of the NoCeM issuers you trust in order to check
+the authenticity of their notices. You can do:
+.Sp
+.Vb 1
+\& gpg \-\-no\-default\-keyring \-\-primary\-keyring <pathetc>/pgp/ncmring.gpg \-\-import <key\-file>
+.Ve
+.Sp
+where <pathetc> is the value of the \fIpathetc\fR parameter set in \fIinn.conf\fR
+and <key\-file> the file containing the key(s) to import. The keyring
+must be located in \fIpathetc\fR/pgp/ncmring.gpg (create the directory
+before using \fBgpg\fR). For old PGP-generated keys, you may have to use
+\&\fB\-\-allow\-non\-selfsigned\-uid\fR if they are not properly self\-signed,
+but anyone creating a key really should self-sign the key. Current
+\&\s-1PGP\s0 implementations do this automatically.
+.Sp
+The keys of NoCeM issuers can be found in the web site of \fIThe NoCeM Registry\fR:
+<http://www.xs4all.nl/~rosalind/nocemreg/nocemreg.html>. You can even
+download there a unique file which contains all the keys.
+.IP "2." 4
+Create a \fInocem.ctl\fR config file in \fIpathetc\fR indicating the NoCeM issuers
+and notices you want to follow. This permission file contains lines like:
+.Sp
+.Vb 3
+\& annihilator\-1:*
+\& clewis@ferret.ocunix:mmf
+\& stephane@asynchrone:mmf,openproxy,spam
+.Ve
+.Sp
+This will remove all articles for which the issuer (first part of the line,
+before the colon \f(CW\*(C`:\*(C'\fR) has issued NoCeM notices corresponding to the
+criteria specified after the colon.
+.Sp
+You will also find information about that on the web site of
+\&\fIThe NoCeM Registry\fR.
+.IP "3." 4
+Add to the \fInewsfeeds\fR file an entry like this one in order to feed
+\&\fBperl-nocem\fR the NoCeM notices posted to alt.nocem.misc and
+news.lists.filters:
+.Sp
+.Vb 3
+\& nocem!\e
+\& :!*,alt.nocem.misc,news.lists.filters\e
+\& :Tc,Wf,Ap:<pathbin>/perl\-nocem
+.Ve
+.Sp
+with the correct path to \fBperl-nocem\fR, located in <pathbin>. Then, reload
+the \fInewsfeeds\fR file (\f(CW\*(C`ctlinnd reload newsfeeds 'NoCeM channel feed'\*(C'\fR).
+.Sp
+Note that you should at least carry news.lists.filters on your news
+server (or other newsgroups where NoCeM notices are sent) if you wish
+to process them.
+.IP "4." 4
+Everything should now work. However, do not hesitate to manually test
+\&\fBperl-nocem\fR with a NoCeM notice, using:
+.Sp
+.Vb 1
+\& grephistory '<Message\-ID>' | perl\-nocem
+.Ve
+.Sp
+Indeed, \fBperl-nocem\fR expects tokens on its standard input, and
+\&\fBgrephistory\fR can easily give it the token of a known article,
+thanks to its Message\-ID.
+.PP
+When you have verified that everything works, you can eventually turn
+off regular spam cancels, if you want, not processing any longer
+cancels containing \f(CW\*(C`cyberspam\*(C'\fR in the Path: header (see the
+\&\fIrefusecybercancels\fR parameter in \fIinn.conf\fR).
+.SH "FILES"
+.IX Header "FILES"
+.IP "\fIpathbin\fR/perl\-nocem" 4
+.IX Item "pathbin/perl-nocem"
+The Perl script itself used to process NoCeM notices.
+.IP "\fIpathetc\fR/nocem.ctl" 4
+.IX Item "pathetc/nocem.ctl"
+The configuration file which specifies the NoCeM notices to be processed.
+.IP "\fIpathetc\fR/pgp/ncmring.gpg" 4
+.IX Item "pathetc/pgp/ncmring.gpg"
+The keyring which contains the public keys of trusted NoCeM issuers.
+.SH "BUGS"
+.IX Header "BUGS"
+The Subject: header is not checked for the @@NCM string and there is no
+check for the presence of the References: header.
+.PP
+The Newsgroups: pseudo header is not checked, but this can be done in
+\&\fIlocal_want_cancel_id()\fR.
+.PP
+The Hierarchies: header is ignored.
+.SH "HISTORY"
+.IX Header "HISTORY"
+Copyright 2000 by Miquel van Smoorenburg <miquels@cistron.nl>.
+.PP
+Copyright 2001 by Marco d'Itri <md@linux.it>.
+.PP
+$Id: perl-nocem.8 7733 2008-04-06 09:16:20Z iulius $
+.SH "SEE ALSO"
+.IX Header "SEE ALSO"
+\&\fIgpgv\fR\|(1), \fIgrephistory\fR\|(1), \fIinn.conf\fR\|(5), \fInewsfeeds\fR\|(5), \fIpgp\fR\|(1).
--- /dev/null
+.\" Automatically generated by Pod::Man v1.37, Pod::Parser v1.32
+.\"
+.\" Standard preamble:
+.\" ========================================================================
+.de Sh \" Subsection heading
+.br
+.if t .Sp
+.ne 5
+.PP
+\fB\\$1\fR
+.PP
+..
+.de Sp \" Vertical space (when we can't use .PP)
+.if t .sp .5v
+.if n .sp
+..
+.de Vb \" Begin verbatim text
+.ft CW
+.nf
+.ne \\$1
+..
+.de Ve \" End verbatim text
+.ft R
+.fi
+..
+.\" Set up some character translations and predefined strings. \*(-- will
+.\" give an unbreakable dash, \*(PI will give pi, \*(L" will give a left
+.\" double quote, and \*(R" will give a right double quote. \*(C+ will
+.\" give a nicer C++. Capital omega is used to do unbreakable dashes and
+.\" therefore won't be available. \*(C` and \*(C' expand to `' in nroff,
+.\" nothing in troff, for use with C<>.
+.tr \(*W-
+.ds C+ C\v'-.1v'\h'-1p'\s-2+\h'-1p'+\s0\v'.1v'\h'-1p'
+.ie n \{\
+. ds -- \(*W-
+. ds PI pi
+. if (\n(.H=4u)&(1m=24u) .ds -- \(*W\h'-12u'\(*W\h'-12u'-\" diablo 10 pitch
+. if (\n(.H=4u)&(1m=20u) .ds -- \(*W\h'-12u'\(*W\h'-8u'-\" diablo 12 pitch
+. ds L" ""
+. ds R" ""
+. ds C` ""
+. ds C' ""
+'br\}
+.el\{\
+. ds -- \|\(em\|
+. ds PI \(*p
+. ds L" ``
+. ds R" ''
+'br\}
+.\"
+.\" If the F register is turned on, we'll generate index entries on stderr for
+.\" titles (.TH), headers (.SH), subsections (.Sh), items (.Ip), and index
+.\" entries marked with X<> in POD. Of course, you'll have to process the
+.\" output yourself in some meaningful fashion.
+.if \nF \{\
+. de IX
+. tm Index:\\$1\t\\n%\t"\\$2"
+..
+. nr % 0
+. rr F
+.\}
+.\"
+.\" For nroff, turn off justification. Always turn off hyphenation; it makes
+.\" way too many mistakes in technical documents.
+.hy 0
+.if n .na
+.\"
+.\" Accent mark definitions (@(#)ms.acc 1.5 88/02/08 SMI; from UCB 4.2).
+.\" Fear. Run. Save yourself. No user-serviceable parts.
+. \" fudge factors for nroff and troff
+.if n \{\
+. ds #H 0
+. ds #V .8m
+. ds #F .3m
+. ds #[ \f1
+. ds #] \fP
+.\}
+.if t \{\
+. ds #H ((1u-(\\\\n(.fu%2u))*.13m)
+. ds #V .6m
+. ds #F 0
+. ds #[ \&
+. ds #] \&
+.\}
+. \" simple accents for nroff and troff
+.if n \{\
+. ds ' \&
+. ds ` \&
+. ds ^ \&
+. ds , \&
+. ds ~ ~
+. ds /
+.\}
+.if t \{\
+. ds ' \\k:\h'-(\\n(.wu*8/10-\*(#H)'\'\h"|\\n:u"
+. ds ` \\k:\h'-(\\n(.wu*8/10-\*(#H)'\`\h'|\\n:u'
+. ds ^ \\k:\h'-(\\n(.wu*10/11-\*(#H)'^\h'|\\n:u'
+. ds , \\k:\h'-(\\n(.wu*8/10)',\h'|\\n:u'
+. ds ~ \\k:\h'-(\\n(.wu-\*(#H-.1m)'~\h'|\\n:u'
+. ds / \\k:\h'-(\\n(.wu*8/10-\*(#H)'\z\(sl\h'|\\n:u'
+.\}
+. \" troff and (daisy-wheel) nroff accents
+.ds : \\k:\h'-(\\n(.wu*8/10-\*(#H+.1m+\*(#F)'\v'-\*(#V'\z.\h'.2m+\*(#F'.\h'|\\n:u'\v'\*(#V'
+.ds 8 \h'\*(#H'\(*b\h'-\*(#H'
+.ds o \\k:\h'-(\\n(.wu+\w'\(de'u-\*(#H)/2u'\v'-.3n'\*(#[\z\(de\v'.3n'\h'|\\n:u'\*(#]
+.ds d- \h'\*(#H'\(pd\h'-\w'~'u'\v'-.25m'\f2\(hy\fP\v'.25m'\h'-\*(#H'
+.ds D- D\\k:\h'-\w'D'u'\v'-.11m'\z\(hy\v'.11m'\h'|\\n:u'
+.ds th \*(#[\v'.3m'\s+1I\s-1\v'-.3m'\h'-(\w'I'u*2/3)'\s-1o\s+1\*(#]
+.ds Th \*(#[\s+2I\s-2\h'-\w'I'u*3/5'\v'-.3m'o\v'.3m'\*(#]
+.ds ae a\h'-(\w'a'u*4/10)'e
+.ds Ae A\h'-(\w'A'u*4/10)'E
+. \" corrections for vroff
+.if v .ds ~ \\k:\h'-(\\n(.wu*9/10-\*(#H)'\s-2\u~\d\s+2\h'|\\n:u'
+.if v .ds ^ \\k:\h'-(\\n(.wu*10/11-\*(#H)'\v'-.4m'^\v'.4m'\h'|\\n:u'
+. \" for low resolution devices (crt and lpr)
+.if \n(.H>23 .if \n(.V>19 \
+\{\
+. ds : e
+. ds 8 ss
+. ds o a
+. ds d- d\h'-1'\(ga
+. ds D- D\h'-1'\(hy
+. ds th \o'bp'
+. ds Th \o'LP'
+. ds ae ae
+. ds Ae AE
+.\}
+.rm #[ #] #H #V #F C
+.\" ========================================================================
+.\"
+.IX Title "PGPVERIFY 1"
+.TH PGPVERIFY 1 "2008-04-06" "INN 2.4.4" "InterNetNews Documentation"
+.SH "NAME"
+pgpverify \- Cryptographically verify Usenet control messages
+.SH "SYNOPSIS"
+.IX Header "SYNOPSIS"
+\&\fBpgpverify\fR [\fB\-test\fR] < \fImessage\fR
+.SH "DESCRIPTION"
+.IX Header "DESCRIPTION"
+The \fBpgpverify\fR program reads (on standard input) a Usenet control
+message that has been cryptographically signed using the \fBsigncontrol\fR
+program (or some other program that produces a compatible format).
+\&\fBpgpverify\fR then uses a \s-1PGP\s0 implementation to determine who signed the
+control message. If the control message has a valid signature,
+\&\fBpgpverify\fR prints (to stdout) the user \s-1ID\s0 of the key that signed the
+message. Otherwise, it exits with a non-zero exit status.
+.PP
+If \fBpgpverify\fR is installed as part of \s-1INN\s0, it uses \s-1INN\s0's configuration
+to determine what signature verification program to use, how to log
+errors, what temporary directory to use, and what keyring to use.
+Otherwise, all of those parameters can be set by editing the beginning of
+this script.
+.PP
+By default, when running as part of \s-1INN\s0, \fBpgpverify\fR expects the \s-1PGP\s0 key
+ring to be found in \fIpathetc\fR/pgp (as either \fIpubring.pgp\fR or
+\&\fIpubring.gpg\fR depending on whether \s-1PGP\s0 or GnuPG is used to verify
+signatures). If that directory doesn't exist, it will fall back on using
+the default key ring, which is in a \fI.pgp\fR or \fI.gnupg\fR subdirectory of
+the running user's home directory.
+.PP
+\&\s-1INN\s0, when using GnuPG, configures \fBpgpverify\fR to use \fBgpgv\fR, which by
+default expects keys to be in a keyring named \fItrustedkeys.gpg\fR, since it
+doesn't implement trust checking directly. \fBpgpverify\fR uses that file if
+present but falls back to \fIpubring.gpg\fR if it's not found. This bypasses
+the trust model for checking keys, but is compatible with the way that
+\&\fBpgpverify\fR used to behave. Of course, if a keyring is found in
+\&\fIpathetc\fR/pgp or configured at the top of the script, that overrides all of
+this behavior.
+.SH "OPTIONS"
+.IX Header "OPTIONS"
+The \fB\-test\fR flag causes \fBpgpverify\fR to print out the input that it is
+passing to \s-1PGP\s0 (which is a reconstructed version of the input that
+supposedly created the control message) as well as the output from \s-1PGP\s0's
+analysis of the message.
+.SH "EXIT STATUS"
+.IX Header "EXIT STATUS"
+\&\fBpgpverify\fR may exit with the following statuses:
+.IP "0\&" 4
+.IX Item "0"
+The control message had a good \s-1PGP\s0 signature.
+.IP "1" 4
+.IX Item "1"
+The control message had no \s-1PGP\s0 signature.
+.IP "2" 4
+.IX Item "2"
+The control message had an unknown \s-1PGP\s0 signature.
+.IP "3" 4
+.IX Item "3"
+The control message had a bad \s-1PGP\s0 signature.
+.IP "255" 4
+.IX Item "255"
+A problem occurred not directly related to \s-1PGP\s0 analysis of signature.
+.SH "ENVIRONMENT"
+.IX Header "ENVIRONMENT"
+\&\fBpgpverify\fR does not modify or otherwise alter the environment before
+invoking the \fBpgp\fR or \fBgpgv\fR program. It is the responsibility of the
+person who installs \fBpgpverify\fR to ensure that when \fBpgp\fR or \fBgpgv\fR
+runs, it has the ability to locate and read a \s-1PGP\s0 key file that contains
+the \s-1PGP\s0 public keys for the appropriate Usenet hierarchy administrators.
+\&\fBpgpverify\fR can be pointed to an appropriate key ring by editing
+variables at the beginning of this script.
+.SH "NOTES"
+.IX Header "NOTES"
+Historically, Usenet news server administrators have configured their news
+servers to automatically honor Usenet control messages based on the
+originator of the control messages and the hierarchies for which the
+control messages applied. For example, in the past, David Lawrence always
+issued control messages for the \*(L"Big\ 8\*(R" hierarchies (comp, humanities,
+misc, news, rec, sci, soc, talk). Usenet news administrators would
+configure their news server software to automatically honor newgroup and
+rmgroup control messages that originated from David Lawrence and applied
+to any of the Big\ 8 hierarchies.
+.PP
+Unfortunately, Usenet news articles (including control messages) are
+notoriously easy to forge. Soon, malicious users realized they could
+create or remove (at least temporarily) any Big\ 8 newsgroup they wanted by
+simply forging an appropriate control message in David Lawrence's name.
+As Usenet became more widely used, forgeries became more common.
+.PP
+The \fBpgpverify\fR program was designed to allow Usenet news administrators
+to configure their servers to cryptographically verify control messages
+before automatically acting on them. Under the \fBpgpverify\fR system, a Usenet
+hierarchy maintainer creates a \s-1PGP\s0 public/private key pair and
+disseminates the public key. Whenever the hierarchy maintainer issues a
+control message, he uses the \fBsigncontrol\fR program to sign the control
+message with the \s-1PGP\s0 private key. Usenet news administrators configure
+their news servers to run the \fBpgpverify\fR program on the appropriate
+control messages, and take action based on the \s-1PGP\s0 key User \s-1ID\s0 that signed
+the control message, not the name and address that appear in the control
+message's From: or Sender: headers.
+.PP
+Thus, appropriate use of the \fBsigncontrol\fR and \fBpgpverify\fR programs
+essentially eliminates the possibility of malicious users forging Usenet
+control messages that sites will act upon, as such users would have to
+obtain the \s-1PGP\s0 private key in order to forge a control message that would
+pass the cryptographic verification step. If the hierarchy administrators
+properly protect their \s-1PGP\s0 private keys, the only way a malicious user
+could forge a validly-signed control message would be by breaking the
+public key encryption algorithm, which (at least at this time) is believed
+to be prohibitively difficult for \s-1PGP\s0 keys of a sufficient bit length.
+.SH "HISTORY"
+.IX Header "HISTORY"
+\&\fBpgpverify\fR was written by David C Lawrence <tale@isc.org>. Manual page
+provided by James Ralston. It is currently maintained by Russ Allbery
+<rra@stanford.edu>.
+.SH "COPYRIGHT AND LICENSE"
+.IX Header "COPYRIGHT AND LICENSE"
+David Lawrence wrote: \*(L"Our lawyer told me to include the following. The
+upshot of it is that you can use the software for free as much as you
+like.\*(R"
+.PP
+Copyright (c) 1996 \s-1UUNET\s0 Technologies, Inc.
+All rights reserved.
+.PP
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are
+met:
+.IP "1." 4
+Redistributions of source code must retain the above copyright notice,
+this list of conditions and the following disclaimer.
+.IP "2." 4
+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.
+.IP "3." 4
+All advertising materials mentioning features or use of this software must
+display the following acknowledgement:
+.Sp
+.Vb 1
+\& This product includes software developed by UUNET Technologies, Inc.
+.Ve
+.IP "4." 4
+The name of \s-1UUNET\s0 Technologies (\*(L"\s-1UUNET\s0\*(R") may not be used to endorse or
+promote products derived from this software without specific prior written
+permission.
+.PP
+\&\s-1THIS\s0 \s-1SOFTWARE\s0 \s-1IS\s0 \s-1PROVIDED\s0 \s-1BY\s0 \s-1UUNET\s0 \*(L"\s-1AS\s0 \s-1IS\s0\*(R" \s-1AND\s0 \s-1ANY\s0 \s-1EXPRESS\s0 \s-1OR\s0 \s-1IMPLIED\s0
+\&\s-1WARRANTIES\s0, \s-1INCLUDING\s0, \s-1BUT\s0 \s-1NOT\s0 \s-1LIMITED\s0 \s-1TO\s0, \s-1THE\s0 \s-1IMPLIED\s0 \s-1WARRANTIES\s0 \s-1OF\s0
+\&\s-1MERCHANTABILITY\s0 \s-1AND\s0 \s-1FITNESS\s0 \s-1FOR\s0 A \s-1PARTICULAR\s0 \s-1PURPOSE\s0 \s-1ARE\s0 \s-1DISCLAIMED\s0. \s-1IN\s0
+\&\s-1NO\s0 \s-1EVENT\s0 \s-1SHALL\s0 \s-1UUNET\s0 \s-1BE\s0 \s-1LIABLE\s0 \s-1FOR\s0 \s-1ANY\s0 \s-1DIRECT\s0, \s-1INDIRECT\s0, \s-1INCIDENTAL\s0,
+\&\s-1SPECIAL\s0, \s-1EXEMPLARY\s0, \s-1OR\s0 \s-1CONSEQUENTIAL\s0 \s-1DAMAGES\s0 (\s-1INCLUDING\s0, \s-1BUT\s0 \s-1NOT\s0 \s-1LIMITED\s0
+\&\s-1TO\s0, \s-1PROCUREMENT\s0 \s-1OF\s0 \s-1SUBSTITUTE\s0 \s-1GOODS\s0 \s-1OR\s0 \s-1SERVICES\s0; \s-1LOSS\s0 \s-1OF\s0 \s-1USE\s0, \s-1DATA\s0, \s-1OR\s0
+\&\s-1PROFITS\s0; \s-1OR\s0 \s-1BUSINESS\s0 \s-1INTERRUPTION\s0) \s-1HOWEVER\s0 \s-1CAUSED\s0 \s-1AND\s0 \s-1ON\s0 \s-1ANY\s0 \s-1THEORY\s0 \s-1OF\s0
+\&\s-1LIABILITY\s0, \s-1WHETHER\s0 \s-1IN\s0 \s-1CONTRACT\s0, \s-1STRICT\s0 \s-1LIABILITY\s0, \s-1OR\s0 \s-1TORT\s0 (\s-1INCLUDING\s0
+\&\s-1NEGLIGENCE\s0 \s-1OR\s0 \s-1OTHERWISE\s0) \s-1ARISING\s0 \s-1IN\s0 \s-1ANY\s0 \s-1WAY\s0 \s-1OUT\s0 \s-1OF\s0 \s-1THE\s0 \s-1USE\s0 \s-1OF\s0 \s-1THIS\s0
+\&\s-1SOFTWARE\s0, \s-1EVEN\s0 \s-1IF\s0 \s-1ADVISED\s0 \s-1OF\s0 \s-1THE\s0 \s-1POSSIBILITY\s0 \s-1OF\s0 \s-1SUCH\s0 \s-1DAMAGE\s0.
+.SH "SEE ALSO"
+.IX Header "SEE ALSO"
+\&\fIgpgv\fR\|(1), \fIpgp\fR\|(1).
+.PP
+<ftp://ftp.isc.org/pub/pgpcontrol/> is where the most recent versions of
+\&\fBsigncontrol\fR and \fBpgpverify\fR live, along with \s-1PGP\s0 public keys used for
+hierarchy administration.
--- /dev/null
+.\" $Revision: 5909 $
+.TH PRUNEHISTORY 8
+.SH NAME
+prunehistory \- remove tokens from Usenet history file
+.SH SYNOPSIS
+.B prunehistory
+[
+.BI \-f " filename"
+]
+[
+.B \-p
+]
+.SH DESCRIPTION
+.I Prunehistory
+modifies a
+.IR history (5)-format
+text file to ``remove'' a set of tokens from it.
+The tokens are removed by overwriting them with spaces, so that the
+size and position of any following entries does not change. This has an
+effect similar to expiring the article, in that it is still mentioned in
+the history database but cannot be retrieved.
+.PP
+.I Prunehistory
+reads the standard input.
+The input is taken as a set of lines.
+Blank lines and lines starting with a number sign (``#'') are ignored.
+All other lines are should consist of a Message-ID followed by zero or
+more other fields (which are ignored).
+.PP
+The Message-ID is used as the
+.IR dbz (3)
+key to get an offset into the text file.
+.PP
+Since
+.IR innd (8)
+only appends
+to the text file,
+.I prunehistory
+does not need to have any interaction with it.
+.SH OPTIONS
+.TP
+.B \-p
+.I Prunehistory
+will normally complain about lines that do not follow the correct format.
+If the ``\-p'' flag is used, then the program will silently print any
+invalid lines on its standard output.
+(Blank lines and comment lines are also passed through.)
+This can be useful when
+.I prunehistory
+is used as a filter for other programs such as
+.IR reap .
+.TP
+.BI \-f " filename"
+The default name of the history file is
+.IR <pathdb\ in\ inn.conf>/history ;
+to specify a different name, use the ``\-f'' flag.
+.SH HISTORY
+Written by Rich $alz <rsalz@uunet.uu.net> for InterNetNews.
+.de R$
+This is revision \\$3, dated \\$4.
+..
+.R$ $Id: prunehistory.8 5909 2002-12-03 05:17:18Z vinocur $
+.SH "SEE ALSO"
+dbz(3),
+history(5),
+inn.conf(5),
+innd(8).
--- /dev/null
+.\" Automatically generated by Pod::Man v1.37, Pod::Parser v1.32
+.\"
+.\" Standard preamble:
+.\" ========================================================================
+.de Sh \" Subsection heading
+.br
+.if t .Sp
+.ne 5
+.PP
+\fB\\$1\fR
+.PP
+..
+.de Sp \" Vertical space (when we can't use .PP)
+.if t .sp .5v
+.if n .sp
+..
+.de Vb \" Begin verbatim text
+.ft CW
+.nf
+.ne \\$1
+..
+.de Ve \" End verbatim text
+.ft R
+.fi
+..
+.\" Set up some character translations and predefined strings. \*(-- will
+.\" give an unbreakable dash, \*(PI will give pi, \*(L" will give a left
+.\" double quote, and \*(R" will give a right double quote. \*(C+ will
+.\" give a nicer C++. Capital omega is used to do unbreakable dashes and
+.\" therefore won't be available. \*(C` and \*(C' expand to `' in nroff,
+.\" nothing in troff, for use with C<>.
+.tr \(*W-
+.ds C+ C\v'-.1v'\h'-1p'\s-2+\h'-1p'+\s0\v'.1v'\h'-1p'
+.ie n \{\
+. ds -- \(*W-
+. ds PI pi
+. if (\n(.H=4u)&(1m=24u) .ds -- \(*W\h'-12u'\(*W\h'-12u'-\" diablo 10 pitch
+. if (\n(.H=4u)&(1m=20u) .ds -- \(*W\h'-12u'\(*W\h'-8u'-\" diablo 12 pitch
+. ds L" ""
+. ds R" ""
+. ds C` ""
+. ds C' ""
+'br\}
+.el\{\
+. ds -- \|\(em\|
+. ds PI \(*p
+. ds L" ``
+. ds R" ''
+'br\}
+.\"
+.\" If the F register is turned on, we'll generate index entries on stderr for
+.\" titles (.TH), headers (.SH), subsections (.Sh), items (.Ip), and index
+.\" entries marked with X<> in POD. Of course, you'll have to process the
+.\" output yourself in some meaningful fashion.
+.if \nF \{\
+. de IX
+. tm Index:\\$1\t\\n%\t"\\$2"
+..
+. nr % 0
+. rr F
+.\}
+.\"
+.\" For nroff, turn off justification. Always turn off hyphenation; it makes
+.\" way too many mistakes in technical documents.
+.hy 0
+.if n .na
+.\"
+.\" Accent mark definitions (@(#)ms.acc 1.5 88/02/08 SMI; from UCB 4.2).
+.\" Fear. Run. Save yourself. No user-serviceable parts.
+. \" fudge factors for nroff and troff
+.if n \{\
+. ds #H 0
+. ds #V .8m
+. ds #F .3m
+. ds #[ \f1
+. ds #] \fP
+.\}
+.if t \{\
+. ds #H ((1u-(\\\\n(.fu%2u))*.13m)
+. ds #V .6m
+. ds #F 0
+. ds #[ \&
+. ds #] \&
+.\}
+. \" simple accents for nroff and troff
+.if n \{\
+. ds ' \&
+. ds ` \&
+. ds ^ \&
+. ds , \&
+. ds ~ ~
+. ds /
+.\}
+.if t \{\
+. ds ' \\k:\h'-(\\n(.wu*8/10-\*(#H)'\'\h"|\\n:u"
+. ds ` \\k:\h'-(\\n(.wu*8/10-\*(#H)'\`\h'|\\n:u'
+. ds ^ \\k:\h'-(\\n(.wu*10/11-\*(#H)'^\h'|\\n:u'
+. ds , \\k:\h'-(\\n(.wu*8/10)',\h'|\\n:u'
+. ds ~ \\k:\h'-(\\n(.wu-\*(#H-.1m)'~\h'|\\n:u'
+. ds / \\k:\h'-(\\n(.wu*8/10-\*(#H)'\z\(sl\h'|\\n:u'
+.\}
+. \" troff and (daisy-wheel) nroff accents
+.ds : \\k:\h'-(\\n(.wu*8/10-\*(#H+.1m+\*(#F)'\v'-\*(#V'\z.\h'.2m+\*(#F'.\h'|\\n:u'\v'\*(#V'
+.ds 8 \h'\*(#H'\(*b\h'-\*(#H'
+.ds o \\k:\h'-(\\n(.wu+\w'\(de'u-\*(#H)/2u'\v'-.3n'\*(#[\z\(de\v'.3n'\h'|\\n:u'\*(#]
+.ds d- \h'\*(#H'\(pd\h'-\w'~'u'\v'-.25m'\f2\(hy\fP\v'.25m'\h'-\*(#H'
+.ds D- D\\k:\h'-\w'D'u'\v'-.11m'\z\(hy\v'.11m'\h'|\\n:u'
+.ds th \*(#[\v'.3m'\s+1I\s-1\v'-.3m'\h'-(\w'I'u*2/3)'\s-1o\s+1\*(#]
+.ds Th \*(#[\s+2I\s-2\h'-\w'I'u*3/5'\v'-.3m'o\v'.3m'\*(#]
+.ds ae a\h'-(\w'a'u*4/10)'e
+.ds Ae A\h'-(\w'A'u*4/10)'E
+. \" corrections for vroff
+.if v .ds ~ \\k:\h'-(\\n(.wu*9/10-\*(#H)'\s-2\u~\d\s+2\h'|\\n:u'
+.if v .ds ^ \\k:\h'-(\\n(.wu*10/11-\*(#H)'\v'-.4m'^\v'.4m'\h'|\\n:u'
+. \" for low resolution devices (crt and lpr)
+.if \n(.H>23 .if \n(.V>19 \
+\{\
+. ds : e
+. ds 8 ss
+. ds o a
+. ds d- d\h'-1'\(ga
+. ds D- D\h'-1'\(hy
+. ds th \o'bp'
+. ds Th \o'LP'
+. ds ae ae
+. ds Ae AE
+.\}
+.rm #[ #] #H #V #F C
+.\" ========================================================================
+.\"
+.IX Title "PULLNEWS 1"
+.TH PULLNEWS 1 "2008-06-05" "INN 2.4.5" "InterNetNews Documentation"
+.SH "NAME"
+pullnews \- Pull news from multiple news servers and feed it to another
+.SH "SYNOPSIS"
+.IX Header "SYNOPSIS"
+\&\fBpullnews\fR [\fB\-hnqRx\fR] [\fB\-b\fR \fIfraction\fR] [\fB\-c\fR \fIconfig\fR] [\fB\-C\fR \fIwidth\fR]
+[\fB\-d\fR \fIlevel\fR] [\fB\-f\fR \fIfraction\fR] [\fB\-F\fR \fIfakehop\fR] [\fB\-g\fR \fIgroups\fR]
+[\fB\-G\fR \fInewsgroups\fR] [\fB\-H\fR \fIheaders\fR] [\fB\-k\fR \fIcheckpt\fR] [\fB\-l\fR \fIlogfile\fR]
+[\fB\-m\fR \fIheader_pats\fR] [\fB\-M\fR \fInum\fR] [\fB\-N\fR \fItimeout\fR] [\fB\-p\fR \fIport\fR]
+[\fB\-P\fR \fIhop_limit\fR] [\fB\-Q\fR \fIlevel\fR] [\fB\-r\fR \fIfile\fR] [\fB\-s\fR \fIto-server\fR[:\fIport\fR]]
+[\fB\-S\fR \fImax-run\fR] [\fB\-t\fR \fIretries\fR] [\fB\-T\fR \fIconnect-pause\fR] [\fB\-w\fR \fInum\fR]
+[\fB\-z\fR \fIarticle-pause\fR] [\fB\-Z\fR \fIgroup-pause\fR] [\fIfrom-server\fR ...]
+.SH "REQUIREMENTS"
+.IX Header "REQUIREMENTS"
+The \f(CW\*(C`Net::NNTP\*(C'\fR module must be installed. This module is available as part
+of the libnet distribution and comes with recent versions of Perl. For
+older versions of Perl, you can download it from <http://www.cpan.org/>.
+.SH "DESCRIPTION"
+.IX Header "DESCRIPTION"
+\&\fBpullnews\fR reads a config file in the running user's home directory
+(normally called \fI~/.pullnews\fR) and connects to the upstream servers
+given there as a reader client. By default, it connects to all servers
+listed in the configuration file, but you can limit \fBpullnews\fR to
+specific servers by listing them on the command line: a whitespace-separated
+list of server names can be specified, like \fIfrom-server\fR for one of them.
+For each server it connects to, it pulls over articles and feeds them to the
+destination server via the \s-1IHAVE\s0 or \s-1POST\s0 commands. This means that the system
+\&\fBpullnews\fR is run on must have feeding access to the destination news server.
+.PP
+\&\fBpullnews\fR is designed for very small sites that do not want to bother
+setting up traditional peering and is not meant for handling large feeds.
+.SH "OPTIONS"
+.IX Header "OPTIONS"
+.IP "\fB\-b\fR \fIfraction\fR" 4
+.IX Item "-b fraction"
+Backtrack on server numbering reset. Specify the proportion (\f(CW0.0\fR to \f(CW1.0\fR)
+of a group's articles to pull when the server's article number is less than
+our high for that group. When \fIfraction\fR is \f(CW1.0\fR, pull all the articles on
+a renumbered server. The default is to do nothing.
+.IP "\fB\-c\fR \fIconfig\fR" 4
+.IX Item "-c config"
+Normally, the config file is stored in \fI~/.pullnews\fR for the user running
+\&\fBpullnews\fR. If \fB\-c\fR is given, \fIconfig\fR will be used as the config file
+instead. This is useful if you're running \fBpullnews\fR as a system user on
+an automated basis out of cron rather than as an individual user.
+.Sp
+See \*(L"\s-1CONFIG\s0 \s-1FILE\s0\*(R" below for the format of this file.
+.IP "\fB\-C\fR \fIwidth\fR" 4
+.IX Item "-C width"
+Use \fIwidth\fR characters per line for the progress table. The default value
+is \f(CW50\fR.
+.IP "\fB\-d\fR \fIlevel\fR" 4
+.IX Item "-d level"
+Set the debugging level to the integer \fIlevel\fR; more debugging output
+will be logged as this increases. The default value is \f(CW0\fR.
+.IP "\fB\-f\fR \fIfraction\fR" 4
+.IX Item "-f fraction"
+This changes the proportion of articles to get from each group to
+\&\fIfraction\fR and should be in the range \f(CW0.0\fR to \f(CW1.0\fR (\f(CW1.0\fR being
+the default).
+.IP "\fB\-F\fR \fIfakehop\fR" 4
+.IX Item "-F fakehop"
+Prepend \fIfakehop\fR as a host to the Path: header of articles fed.
+.IP "\fB\-g\fR \fIgroups\fR" 4
+.IX Item "-g groups"
+Specify a collection of groups to get. \fIgroups\fR is a list of
+newsgroups separated by commas (only commas, no spaces). Each group must
+be defined in the config file, and only the remote hosts that carry those
+groups will be contacted. Note that this is a simple list of groups, not
+a wildmat expression, and wildcards are not supported.
+.IP "\fB\-G\fR \fInewsgroups\fR" 4
+.IX Item "-G newsgroups"
+Add the comma-separated list of groups \fInewsgroups\fR to each server in the
+configuration file (see also \fB\-g\fR and \fB\-w\fR).
+.IP "\fB\-h\fR" 4
+.IX Item "-h"
+Print a usage message and exit.
+.IP "\fB\-H\fR \fIheaders\fR" 4
+.IX Item "-H headers"
+Remove these named headers (colon\-separated list) from fed articles.
+.IP "\fB\-k\fR \fIcheckpt\fR" 4
+.IX Item "-k checkpt"
+Checkpoint (save) the config file every \fIcheckpt\fR articles
+(default is \f(CW0\fR, that is to say at the end of the session).
+.IP "\fB\-l\fR \fIlogfile\fR" 4
+.IX Item "-l logfile"
+Log progress/stats to \fIlogfile\fR (default is \f(CW\*(C`stdout\*(C'\fR).
+.IP "\fB\-m\fR \fIheader_pats\fR" 4
+.IX Item "-m header_pats"
+Feed an article based on header matching. The argument is a number of
+whitespace-separated tuples (each tuple being a colon-separated header and
+regular expression). For instance:
+.Sp
+.Vb 1
+\& Hdr1:regexp1 !Hdr2:regexp2
+.Ve
+.Sp
+specifies that the article will be passed only if the \f(CW\*(C`Hdr1:\*(C'\fR header
+matches \f(CW\*(C`regexp1\*(C'\fR and the \f(CW\*(C`Hdr2:\*(C'\fR header does not match \f(CW\*(C`regexp2\*(C'\fR.
+.IP "\fB\-M\fR \fInum\fR" 4
+.IX Item "-M num"
+Specify the maximum number of articles (per group) to process.
+The default is to process all new articles. See also \fB\-f\fR.
+.IP "\fB\-n\fR" 4
+.IX Item "-n"
+Do nothing but read articles \-\-\ does not feed articles downstream,
+writes no \fBrnews\fR file, does not update the config file.
+.IP "\fB\-N\fR \fItimeout\fR" 4
+.IX Item "-N timeout"
+Specify the timeout length, as \fItimeout\fR seconds,
+when establishing an \s-1NNTP\s0 connection.
+.IP "\fB\-p\fR \fIport\fR" 4
+.IX Item "-p port"
+Connect to the destination news server on a port other than the default of
+\&\f(CW119\fR. This option does not change the port used to connect to the source
+news servers.
+.IP "\fB\-P\fR \fIhop_limit\fR" 4
+.IX Item "-P hop_limit"
+Restrict feeding an article based on the number of hops it has already made.
+Count the hops in the Path: header (\fIhop_count\fR), feeding the article only
+when \fIhop_limit\fR is \f(CW\*(C`+num\*(C'\fR and \fIhop_count\fR is more than \fInum\fR;
+or \fIhop_limit\fR is \f(CW\*(C`\-num\*(C'\fR and \fIhop_count\fR is less than \fInum\fR.
+.IP "\fB\-q\fR" 4
+.IX Item "-q"
+Print out less status information while running.
+.IP "\fB\-Q\fR \fIlevel\fR" 4
+.IX Item "-Q level"
+Set the quietness level (\f(CW\*(C`\-Q 2\*(C'\fR is equivalent to \f(CW\*(C`\-q\*(C'\fR). The higher this
+value, the less gets logged. The default is \f(CW0\fR.
+.IP "\fB\-r\fR \fIfile\fR" 4
+.IX Item "-r file"
+Rather than feeding the downloaded articles to a destination server, instead
+create a batch file that can later be fed to a server using \fBrnews\fR. See
+\&\fIrnews\fR\|(1) for more information about the batch file format.
+.IP "\fB\-R\fR" 4
+.IX Item "-R"
+Be a reader (use \s-1MODE\s0 \s-1READER\s0 and \s-1POST\s0 commands) to the downstream
+server. The default is to use the \s-1IHAVE\s0 command.
+.IP "\fB\-s\fR \fIto-server\fR[:\fIport\fR]" 4
+.IX Item "-s to-server[:port]"
+Normally, \fBpullnews\fR will feed the articles it retrieves to the news
+server running on localhost. To connect to a different host, specify a
+server with the \fB\-s\fR flag. You can also specify the port with this same
+flag or use \fB\-p\fR.
+.IP "\fB\-S\fR \fImax-run\fR" 4
+.IX Item "-S max-run"
+Specify the maximum time \fImax-run\fR in seconds for \fBpullnews\fR to run.
+.IP "\fB\-t\fR \fIretries\fR" 4
+.IX Item "-t retries"
+The maximum number (\fIretries\fR) of attempts to connect to a server
+(see also \fB\-T\fR). The default is \f(CW0\fR.
+.IP "\fB\-T\fR \fIconnect-pause\fR" 4
+.IX Item "-T connect-pause"
+Pause \fIconnect-pause\fR seconds between connection retries (see also \fB\-t\fR).
+The default is \f(CW1\fR.
+.IP "\fB\-w\fR \fInum\fR" 4
+.IX Item "-w num"
+Set each group's high watermark (last received article number) to \fInum\fR.
+If \fInum\fR is negative, calculate \fICurrent\fR+\fInum\fR instead (i.e. get the last
+\&\fInum\fR articles). Therefore, a \fInum\fR of \f(CW0\fR will re-get all articles on the
+server; whereas a \fInum\fR of \f(CW\*(C`\-0\*(C'\fR will get no old articles, setting the
+watermark to \fICurrent\fR (the most recent article on the server).
+.IP "\fB\-x\fR" 4
+.IX Item "-x"
+If the \fB\-x\fR flag is used, an Xref: header is added to any article
+that lacks one. It can be useful for instance if articles are fed
+to a news server which has \fIxrefslave\fR set in \fIinn.conf\fR.
+.IP "\fB\-z\fR \fIarticle-pause\fR" 4
+.IX Item "-z article-pause"
+Sleep \fIarticle-pause\fR seconds between articles. The default is \f(CW0\fR.
+.IP "\fB\-Z\fR \fIgroup-pause\fR" 4
+.IX Item "-Z group-pause"
+Sleep \fIgroup-pause\fR seconds between groups. The default is \f(CW0\fR.
+.SH "CONFIG FILE"
+.IX Header "CONFIG FILE"
+The config file for \fBpullnews\fR is divided into blocks, one block for each
+remote server to connect to. A block begins with the host line, which
+must have no leading whitespace and contains just the hostname of the
+remote server, optionally followed by authentication details (username
+and password for that server).
+.PP
+Following the host line should be one or more newsgroup lines which start
+with whitespace followed by the name of a newsgroup to retrieve. Only one
+newsgroup should be listed on each line.
+.PP
+\&\fBpullnews\fR will update the config file to include the time the group was
+last checked and the highest numbered article successfully retrieved and
+transferred to the destination server. It uses this data to avoid doing
+duplicate work the next time it runs.
+.PP
+The full syntax is:
+.PP
+.Vb 3
+\& <host> [<username> <password>]
+\& <group> [<time> <high>]
+\& <group> [<time> <high>]
+.Ve
+.PP
+where the <host> line must not have leading whitespace and the <group>
+lines must.
+.PP
+A typical configuration file would be:
+.PP
+.Vb 7
+\& # Format group date high
+\& data.pa.vix.com
+\& rec.bicycles.racing 908086612 783
+\& rec.humor.funny 908086613 18
+\& comp.programming.threads
+\& nnrp.vix.com pull sekret
+\& comp.std.lisp
+.Ve
+.PP
+Note that an earlier run of \fBpullnews\fR has filled in details about the
+last article downloads from the two rec.* groups. The two comp.* groups
+were just added by the user and have not yet been checked.
+.PP
+The nnrp.vix.com server requires authentication, and \fBpullnews\fR will use
+the username \f(CW\*(C`pull\*(C'\fR and the password \f(CW\*(C`sekret\*(C'\fR.
+.SH "FILES"
+.IX Header "FILES"
+.IP "\fIpathbin\fR/pullnews" 4
+.IX Item "pathbin/pullnews"
+The Perl script itself used to pull news from upstream servers and feed
+it to another news server.
+.IP "\fI$HOME\fR/.pullnews" 4
+.IX Item "$HOME/.pullnews"
+The default config file. It is in the running user's home directory
+(normally called \fI~/.pullnews\fR).
+.SH "HISTORY"
+.IX Header "HISTORY"
+\&\fBpullnews\fR was written by James Brister for \s-1INN\s0. The documentation was
+rewritten in \s-1POD\s0 by Russ Allbery <rra@stanford.edu>.
+.PP
+Geraint A. Edwards greatly improved \fBpullnews\fR, adding no more than 16\ new
+recognized flags, fixing some bugs and integrating the \fBbackupfeed\fR
+contrib script by Kai Henningsen, adding again 6\ other flags.
+.PP
+$Id: pullnews.pod 7853 2008\-05\-27 19:07:45Z iulius $
+.SH "SEE ALSO"
+.IX Header "SEE ALSO"
+\&\fIincoming.conf\fR\|(5), \fIrnews\fR\|(1).
--- /dev/null
+#! /bin/sh
+## $Revision: 2790 $
+##
+## Prepare a manpage for installation, and install it. Usage:
+## putman <style> "<installitflags>" <source> <dest-dir>
+case $# in
+4)
+ ;;
+*)
+ echo "Can't install manpage: wrong number of arguments." 1>&2
+esac
+
+STYLE="$1"
+FLAGS="$2"
+SRC="$3"
+DEST="$4"
+
+case "X${STYLE}" in
+XNONE)
+ exit 0
+ ;;
+XSOURCE)
+ exec /bin/sh ../../support/install-sh ${FLAGS} ${SRC} ${DEST}
+ ;;
+XNROFF-PACK)
+ T=${TMPDIR-/tmp}/man$$
+ nroff -man ${SRC} >$T
+ /bin/sh ../../support/install-sh ${FLAGS} $T ${DEST} && pack ${DEST}
+ rm -f $T
+ exit
+ ;;
+XNROFF-PACK-SCO)
+ T=${TMPDIR-/tmp}/man$$
+ nroff -man ${SRC} >$T
+ DEST2=`echo ${DEST} | sed -e 's/\..$/.INN/'`
+ /bin/sh ../../support/install-sh ${FLAGS} $T ${DEST2} && pack ${DEST2}
+ rm -f $T
+ exit
+ ;;
+XBSD4.4)
+ T=${TMPDIR-/tmp}/man$$
+ nroff -man ${SRC} >$T
+ DEST2=`echo ${DEST} | sed -e 's/\..$/.0/'`
+ /bin/sh ../../support/install-sh ${FLAGS} $T ${DEST2}
+ rm -f $T
+ exit
+ ;;
+esac
+
+echo "Can't install manpage: unknown method ${STYLE}." 1>&2
+exit 1
--- /dev/null
+.\" Automatically generated by Pod::Man v1.37, Pod::Parser v1.32
+.\"
+.\" Standard preamble:
+.\" ========================================================================
+.de Sh \" Subsection heading
+.br
+.if t .Sp
+.ne 5
+.PP
+\fB\\$1\fR
+.PP
+..
+.de Sp \" Vertical space (when we can't use .PP)
+.if t .sp .5v
+.if n .sp
+..
+.de Vb \" Begin verbatim text
+.ft CW
+.nf
+.ne \\$1
+..
+.de Ve \" End verbatim text
+.ft R
+.fi
+..
+.\" Set up some character translations and predefined strings. \*(-- will
+.\" give an unbreakable dash, \*(PI will give pi, \*(L" will give a left
+.\" double quote, and \*(R" will give a right double quote. \*(C+ will
+.\" give a nicer C++. Capital omega is used to do unbreakable dashes and
+.\" therefore won't be available. \*(C` and \*(C' expand to `' in nroff,
+.\" nothing in troff, for use with C<>.
+.tr \(*W-
+.ds C+ C\v'-.1v'\h'-1p'\s-2+\h'-1p'+\s0\v'.1v'\h'-1p'
+.ie n \{\
+. ds -- \(*W-
+. ds PI pi
+. if (\n(.H=4u)&(1m=24u) .ds -- \(*W\h'-12u'\(*W\h'-12u'-\" diablo 10 pitch
+. if (\n(.H=4u)&(1m=20u) .ds -- \(*W\h'-12u'\(*W\h'-8u'-\" diablo 12 pitch
+. ds L" ""
+. ds R" ""
+. ds C` ""
+. ds C' ""
+'br\}
+.el\{\
+. ds -- \|\(em\|
+. ds PI \(*p
+. ds L" ``
+. ds R" ''
+'br\}
+.\"
+.\" If the F register is turned on, we'll generate index entries on stderr for
+.\" titles (.TH), headers (.SH), subsections (.Sh), items (.Ip), and index
+.\" entries marked with X<> in POD. Of course, you'll have to process the
+.\" output yourself in some meaningful fashion.
+.if \nF \{\
+. de IX
+. tm Index:\\$1\t\\n%\t"\\$2"
+..
+. nr % 0
+. rr F
+.\}
+.\"
+.\" For nroff, turn off justification. Always turn off hyphenation; it makes
+.\" way too many mistakes in technical documents.
+.hy 0
+.if n .na
+.\"
+.\" Accent mark definitions (@(#)ms.acc 1.5 88/02/08 SMI; from UCB 4.2).
+.\" Fear. Run. Save yourself. No user-serviceable parts.
+. \" fudge factors for nroff and troff
+.if n \{\
+. ds #H 0
+. ds #V .8m
+. ds #F .3m
+. ds #[ \f1
+. ds #] \fP
+.\}
+.if t \{\
+. ds #H ((1u-(\\\\n(.fu%2u))*.13m)
+. ds #V .6m
+. ds #F 0
+. ds #[ \&
+. ds #] \&
+.\}
+. \" simple accents for nroff and troff
+.if n \{\
+. ds ' \&
+. ds ` \&
+. ds ^ \&
+. ds , \&
+. ds ~ ~
+. ds /
+.\}
+.if t \{\
+. ds ' \\k:\h'-(\\n(.wu*8/10-\*(#H)'\'\h"|\\n:u"
+. ds ` \\k:\h'-(\\n(.wu*8/10-\*(#H)'\`\h'|\\n:u'
+. ds ^ \\k:\h'-(\\n(.wu*10/11-\*(#H)'^\h'|\\n:u'
+. ds , \\k:\h'-(\\n(.wu*8/10)',\h'|\\n:u'
+. ds ~ \\k:\h'-(\\n(.wu-\*(#H-.1m)'~\h'|\\n:u'
+. ds / \\k:\h'-(\\n(.wu*8/10-\*(#H)'\z\(sl\h'|\\n:u'
+.\}
+. \" troff and (daisy-wheel) nroff accents
+.ds : \\k:\h'-(\\n(.wu*8/10-\*(#H+.1m+\*(#F)'\v'-\*(#V'\z.\h'.2m+\*(#F'.\h'|\\n:u'\v'\*(#V'
+.ds 8 \h'\*(#H'\(*b\h'-\*(#H'
+.ds o \\k:\h'-(\\n(.wu+\w'\(de'u-\*(#H)/2u'\v'-.3n'\*(#[\z\(de\v'.3n'\h'|\\n:u'\*(#]
+.ds d- \h'\*(#H'\(pd\h'-\w'~'u'\v'-.25m'\f2\(hy\fP\v'.25m'\h'-\*(#H'
+.ds D- D\\k:\h'-\w'D'u'\v'-.11m'\z\(hy\v'.11m'\h'|\\n:u'
+.ds th \*(#[\v'.3m'\s+1I\s-1\v'-.3m'\h'-(\w'I'u*2/3)'\s-1o\s+1\*(#]
+.ds Th \*(#[\s+2I\s-2\h'-\w'I'u*3/5'\v'-.3m'o\v'.3m'\*(#]
+.ds ae a\h'-(\w'a'u*4/10)'e
+.ds Ae A\h'-(\w'A'u*4/10)'E
+. \" corrections for vroff
+.if v .ds ~ \\k:\h'-(\\n(.wu*9/10-\*(#H)'\s-2\u~\d\s+2\h'|\\n:u'
+.if v .ds ^ \\k:\h'-(\\n(.wu*10/11-\*(#H)'\v'-.4m'^\v'.4m'\h'|\\n:u'
+. \" for low resolution devices (crt and lpr)
+.if \n(.H>23 .if \n(.V>19 \
+\{\
+. ds : e
+. ds 8 ss
+. ds o a
+. ds d- d\h'-1'\(ga
+. ds D- D\h'-1'\(hy
+. ds th \o'bp'
+. ds Th \o'LP'
+. ds ae ae
+. ds Ae AE
+.\}
+.rm #[ #] #H #V #F C
+.\" ========================================================================
+.\"
+.IX Title "qio 3"
+.TH qio 3 "2008-04-06" "INN 2.4.5" "InterNetNews Documentation"
+.SH "NAME"
+qio \- Quick I/O routines for reading files
+.SH "SYNOPSIS"
+.IX Header "SYNOPSIS"
+\&\fB#include <inn/qio.h>\fR
+.PP
+\&\fB\s-1QIOSTATE\s0 *QIOopen(const char *\fR\fIname\fR\fB);\fR
+.PP
+\&\fB\s-1QIOSTATE\s0 *QIOfdopen(int\fR \fIfd\fR\fB);\fR
+.PP
+\&\fBvoid QIOclose(\s-1QIOSTATE\s0 *\fR\fIqp\fR\fB);\fR
+.PP
+\&\fBchar *QIOread(\s-1QIOSTATE\s0 *\fR\fIqp\fR\fB);\fR
+.PP
+\&\fBint QIOfileno(\s-1QIOSTATE\s0 *\fR\fIqp\fR\fB);\fR
+.PP
+\&\fBsize_t QIOlength(\s-1QIOSTATE\s0 *\fR\fIqp\fR\fB);\fR
+.PP
+\&\fBint QIOrewind(\s-1QIOSTATE\s0 *\fR\fIqp\fR\fB);\fR
+.PP
+\&\fBoff_t QIOtell(\s-1QIOSTATE\s0 *\fR\fIqp\fR\fB);\fR
+.PP
+\&\fBbool QIOerror(\s-1QIOSTATE\s0 *\fR\fIqp\fR\fB);\fR
+.PP
+\&\fBbool QIOtoolong(\s-1QIOSTATE\s0 *\fR\fIqp\fR\fB);\fR
+.SH "DESCRIPTION"
+.IX Header "DESCRIPTION"
+The routines described in this manual page are part of \fIlibinn\fR\|(3). They
+are used to provide quick read access to files; the \s-1QIO\s0 routines use
+buffering adapted to the block size of the device, similar to stdio, but
+with a more convenient syntax for reading newline-terminated lines. \s-1QIO\s0
+is short for \*(L"Quick I/O\*(R" (a bit of a misnomer, as \s-1QIO\s0 provides read-only
+access to files only).
+.PP
+The \s-1QIOSTATE\s0 structure returned by \fBQIOopen\fR and \fBQIOfdopen\fR is the
+analog to stdio's \s-1FILE\s0 structure and should be treated as a black box by
+all users of these routines. Only the above \s-1API\s0 should be used.
+.PP
+\&\fBQIOopen\fR opens the given file for reading. For regular files, if your
+system provides that information and the size is reasonable, \s-1QIO\s0 will use
+the block size of the underlying file system as its buffer size;
+otherwise, it will default to a buffer of 8 \s-1KB\s0. Returns a pointer to use
+for subsequent calls, or \s-1NULL\s0 on error. \fBQIOfdopen\fR performs the same
+operation except on an already-open file descriptor (\fIfd\fR must designate
+a file open for reading).
+.PP
+\&\fBQIOclose\fR closes the open file and releases any resources used by the
+\&\s-1QIOSTATE\s0 structure. The \s-1QIOSTATE\s0 pointer should not be used again after
+it has been passed to this function.
+.PP
+\&\fBQIOread\fR reads the next newline-terminated line in the file and returns
+a pointer to it, with the trailing newline replaced by nul. The returned
+pointer is a pointer into a buffer in the \s-1QIOSTATE\s0 object and therefore
+will remain valid until \fBQIOclose\fR is called on that object. If \s-1EOF\s0 is
+reached, an error occurs, or if the line is longer than the buffer size,
+\&\s-1NULL\s0 is returned instead. To distinguish between the error cases, use
+\&\fBQIOerror\fR and \fBQIOtoolong\fR.
+.PP
+\&\fBQIOfileno\fR returns the descriptor of the open file.
+.PP
+\&\fBQIOlength\fR returns the length in bytes of the last line returned by
+\&\fBQIOread\fR. Its return value is only defined after a successful call to
+\&\fBQIOread\fR.
+.PP
+\&\fBQIOrewind\fR sets the read pointer back to the beginning of the file and
+reads the first block of the file in anticipation of future reads. It
+returns 0 if successful and \-1 on error.
+.PP
+\&\fBQIOtell\fR returns the current value of the read pointer (the \fIlseek\fR\|(2)
+offset at which the next line will start).
+.PP
+\&\fBQIOerror\fR returns true if there was an error in the last call to
+\&\fBQIOread\fR, false otherwise. \fBQIOtoolong\fR returns true if there was an
+error and the error was that the line was too long. If \fBQIOread\fR returns
+\&\s-1NULL\s0, these functions should be called to determine what happened. If
+\&\fBQIOread\fR returned \s-1NULL\s0 and \fBQIOerror\fR is false, \s-1EOF\s0 was reached. Note
+that if \fBQIOtoolong\fR returns true, the next call to \fBQIOread\fR will try
+to read the remainder of the line and will likely return a partial line;
+users of this library should in general treat long lines as fatal errors.
+.SH "EXAMPLES"
+.IX Header "EXAMPLES"
+This block of code opens \fI/etc/motd\fR and reads it a line at a time,
+printing out each line preceeded by its offset in the file.
+.PP
+.Vb 3
+\& QIOSTATE *qp;
+\& off_t offset;
+\& char *p;
+.Ve
+.PP
+.Vb 12
+\& qp = QIOopen("/etc/motd");
+\& if (qp == NULL) {
+\& perror("Open error");
+\& exit(1);
+\& }
+\& for (p = QIOread(qp); p != NULL; p = QIOread(qp))
+\& printf("%ld: %s\en", (unsigned long) QIOtell(qp), p);
+\& if (QIOerror(qp)) {
+\& perror("Read error");
+\& exit(1);
+\& }
+\& QIOclose(qp);
+.Ve
+.SH "HISTORY"
+.IX Header "HISTORY"
+Written by Rich \f(CW$alz\fR <rsalz@uunet.uu.net> for InterNetNews. Updated by
+Russ Allbery <rra@stanford.edu>.
+.PP
+$Id: qio.3 7880 2008-06-16 20:37:13Z iulius $
--- /dev/null
+.\" Automatically generated by Pod::Man v1.37, Pod::Parser v1.32
+.\"
+.\" Standard preamble:
+.\" ========================================================================
+.de Sh \" Subsection heading
+.br
+.if t .Sp
+.ne 5
+.PP
+\fB\\$1\fR
+.PP
+..
+.de Sp \" Vertical space (when we can't use .PP)
+.if t .sp .5v
+.if n .sp
+..
+.de Vb \" Begin verbatim text
+.ft CW
+.nf
+.ne \\$1
+..
+.de Ve \" End verbatim text
+.ft R
+.fi
+..
+.\" Set up some character translations and predefined strings. \*(-- will
+.\" give an unbreakable dash, \*(PI will give pi, \*(L" will give a left
+.\" double quote, and \*(R" will give a right double quote. \*(C+ will
+.\" give a nicer C++. Capital omega is used to do unbreakable dashes and
+.\" therefore won't be available. \*(C` and \*(C' expand to `' in nroff,
+.\" nothing in troff, for use with C<>.
+.tr \(*W-
+.ds C+ C\v'-.1v'\h'-1p'\s-2+\h'-1p'+\s0\v'.1v'\h'-1p'
+.ie n \{\
+. ds -- \(*W-
+. ds PI pi
+. if (\n(.H=4u)&(1m=24u) .ds -- \(*W\h'-12u'\(*W\h'-12u'-\" diablo 10 pitch
+. if (\n(.H=4u)&(1m=20u) .ds -- \(*W\h'-12u'\(*W\h'-8u'-\" diablo 12 pitch
+. ds L" ""
+. ds R" ""
+. ds C` ""
+. ds C' ""
+'br\}
+.el\{\
+. ds -- \|\(em\|
+. ds PI \(*p
+. ds L" ``
+. ds R" ''
+'br\}
+.\"
+.\" If the F register is turned on, we'll generate index entries on stderr for
+.\" titles (.TH), headers (.SH), subsections (.Sh), items (.Ip), and index
+.\" entries marked with X<> in POD. Of course, you'll have to process the
+.\" output yourself in some meaningful fashion.
+.if \nF \{\
+. de IX
+. tm Index:\\$1\t\\n%\t"\\$2"
+..
+. nr % 0
+. rr F
+.\}
+.\"
+.\" For nroff, turn off justification. Always turn off hyphenation; it makes
+.\" way too many mistakes in technical documents.
+.hy 0
+.if n .na
+.\"
+.\" Accent mark definitions (@(#)ms.acc 1.5 88/02/08 SMI; from UCB 4.2).
+.\" Fear. Run. Save yourself. No user-serviceable parts.
+. \" fudge factors for nroff and troff
+.if n \{\
+. ds #H 0
+. ds #V .8m
+. ds #F .3m
+. ds #[ \f1
+. ds #] \fP
+.\}
+.if t \{\
+. ds #H ((1u-(\\\\n(.fu%2u))*.13m)
+. ds #V .6m
+. ds #F 0
+. ds #[ \&
+. ds #] \&
+.\}
+. \" simple accents for nroff and troff
+.if n \{\
+. ds ' \&
+. ds ` \&
+. ds ^ \&
+. ds , \&
+. ds ~ ~
+. ds /
+.\}
+.if t \{\
+. ds ' \\k:\h'-(\\n(.wu*8/10-\*(#H)'\'\h"|\\n:u"
+. ds ` \\k:\h'-(\\n(.wu*8/10-\*(#H)'\`\h'|\\n:u'
+. ds ^ \\k:\h'-(\\n(.wu*10/11-\*(#H)'^\h'|\\n:u'
+. ds , \\k:\h'-(\\n(.wu*8/10)',\h'|\\n:u'
+. ds ~ \\k:\h'-(\\n(.wu-\*(#H-.1m)'~\h'|\\n:u'
+. ds / \\k:\h'-(\\n(.wu*8/10-\*(#H)'\z\(sl\h'|\\n:u'
+.\}
+. \" troff and (daisy-wheel) nroff accents
+.ds : \\k:\h'-(\\n(.wu*8/10-\*(#H+.1m+\*(#F)'\v'-\*(#V'\z.\h'.2m+\*(#F'.\h'|\\n:u'\v'\*(#V'
+.ds 8 \h'\*(#H'\(*b\h'-\*(#H'
+.ds o \\k:\h'-(\\n(.wu+\w'\(de'u-\*(#H)/2u'\v'-.3n'\*(#[\z\(de\v'.3n'\h'|\\n:u'\*(#]
+.ds d- \h'\*(#H'\(pd\h'-\w'~'u'\v'-.25m'\f2\(hy\fP\v'.25m'\h'-\*(#H'
+.ds D- D\\k:\h'-\w'D'u'\v'-.11m'\z\(hy\v'.11m'\h'|\\n:u'
+.ds th \*(#[\v'.3m'\s+1I\s-1\v'-.3m'\h'-(\w'I'u*2/3)'\s-1o\s+1\*(#]
+.ds Th \*(#[\s+2I\s-2\h'-\w'I'u*3/5'\v'-.3m'o\v'.3m'\*(#]
+.ds ae a\h'-(\w'a'u*4/10)'e
+.ds Ae A\h'-(\w'A'u*4/10)'E
+. \" corrections for vroff
+.if v .ds ~ \\k:\h'-(\\n(.wu*9/10-\*(#H)'\s-2\u~\d\s+2\h'|\\n:u'
+.if v .ds ^ \\k:\h'-(\\n(.wu*10/11-\*(#H)'\v'-.4m'^\v'.4m'\h'|\\n:u'
+. \" for low resolution devices (crt and lpr)
+.if \n(.H>23 .if \n(.V>19 \
+\{\
+. ds : e
+. ds 8 ss
+. ds o a
+. ds d- d\h'-1'\(ga
+. ds D- D\h'-1'\(hy
+. ds th \o'bp'
+. ds Th \o'LP'
+. ds ae ae
+. ds Ae AE
+.\}
+.rm #[ #] #H #V #F C
+.\" ========================================================================
+.\"
+.IX Title "RADIUS 8"
+.TH RADIUS 8 "2008-04-06" "INN 2.4.5" "InterNetNews Documentation"
+.SH "NAME"
+radius \- nnrpd RADIUS password authenticator
+.SH "SYNOPSIS"
+.IX Header "SYNOPSIS"
+\&\fBradius\fR [\fB\-h\fR] [\fB\-f\fR \fIconfig\fR]
+.SH "DESCRIPTION"
+.IX Header "DESCRIPTION"
+\&\fBradius\fR is an nnrpd authenticator, accepting a username and password
+from nnrpd (given to nnrpd by a reader connection) and attempting to
+authenticate that username and password against a \s-1RADIUS\s0 server. See
+\&\fIreaders.conf\fR\|(5) for more information on how to configure an nnrpd
+authenticator. It is useful for a site that already does user
+authentication via \s-1RADIUS\s0 and wants to authenticate news reading
+connections as well.
+.PP
+By default, \fBradius\fR reads \fIpathetc\fR/radius.conf for configuration
+information, but a different configuration file can be specified with
+\&\fB\-f\fR. See \fIradius.conf\fR\|(5) for a description of the configuration file.
+.SH "OPTIONS"
+.IX Header "OPTIONS"
+.IP "\fB\-f\fR \fIconfig\fR" 4
+.IX Item "-f config"
+Read \fIconfig\fR instead of \fIpathetc\fR/radius.conf for configuration
+information.
+.IP "\fB\-h\fR" 4
+.IX Item "-h"
+Print out a usage message and exit.
+.SH "EXAMPLE"
+.IX Header "EXAMPLE"
+The following \fIreaders.conf\fR\|(5) fragment tells nnrpd to authenticate all
+connections using this authenticator:
+.PP
+.Vb 5
+\& auth radius {
+\& auth: radius
+\& default: <FAIL>
+\& default\-domain: example.com
+\& }
+.Ve
+.PP
+\&\f(CW\*(C`@example.com\*(C'\fR will be appended to the user-supplied identity, and if
+\&\s-1RADIUS\s0 authentication failes, the user will be assigned an identity of
+\&\f(CW\*(C`<FAIL>@example.com\*(C'\fR.
+.SH "BUGS"
+.IX Header "BUGS"
+It has been reported that this authenticator doesn't work with Ascend
+\&\s-1RADIUS\s0 servers, but does work with Cistron \s-1RADIUS\s0 servers. It's also
+believed to work with Livingston's \s-1RADIUS\s0 server. Contributions to make
+it work better with different types of \s-1RADIUS\s0 servers would be gratefully
+accepted.
+.PP
+This code has not been audited against the \s-1RADIUS\s0 protocol and may not
+implement it correctly.
+.SH "HISTORY"
+.IX Header "HISTORY"
+The \s-1RADIUS\s0 authenticator was originally written by Aidan Cully. This
+documentation was written by Russ Allbery <rra@stanford.edu>.
+.PP
+$Id: radius.8 7880 2008-06-16 20:37:13Z iulius $
+.SH "SEE ALSO"
+.IX Header "SEE ALSO"
+\&\fInnrpd\fR\|(8), \fIradius.conf\fR\|(5), \fIreaders.conf\fR\|(5)
+.PP
+\&\s-1RFC\s0 2865, Remote Authentication Dial In User Service.
--- /dev/null
+.\" Automatically generated by Pod::Man v1.37, Pod::Parser v1.32
+.\"
+.\" Standard preamble:
+.\" ========================================================================
+.de Sh \" Subsection heading
+.br
+.if t .Sp
+.ne 5
+.PP
+\fB\\$1\fR
+.PP
+..
+.de Sp \" Vertical space (when we can't use .PP)
+.if t .sp .5v
+.if n .sp
+..
+.de Vb \" Begin verbatim text
+.ft CW
+.nf
+.ne \\$1
+..
+.de Ve \" End verbatim text
+.ft R
+.fi
+..
+.\" Set up some character translations and predefined strings. \*(-- will
+.\" give an unbreakable dash, \*(PI will give pi, \*(L" will give a left
+.\" double quote, and \*(R" will give a right double quote. \*(C+ will
+.\" give a nicer C++. Capital omega is used to do unbreakable dashes and
+.\" therefore won't be available. \*(C` and \*(C' expand to `' in nroff,
+.\" nothing in troff, for use with C<>.
+.tr \(*W-
+.ds C+ C\v'-.1v'\h'-1p'\s-2+\h'-1p'+\s0\v'.1v'\h'-1p'
+.ie n \{\
+. ds -- \(*W-
+. ds PI pi
+. if (\n(.H=4u)&(1m=24u) .ds -- \(*W\h'-12u'\(*W\h'-12u'-\" diablo 10 pitch
+. if (\n(.H=4u)&(1m=20u) .ds -- \(*W\h'-12u'\(*W\h'-8u'-\" diablo 12 pitch
+. ds L" ""
+. ds R" ""
+. ds C` ""
+. ds C' ""
+'br\}
+.el\{\
+. ds -- \|\(em\|
+. ds PI \(*p
+. ds L" ``
+. ds R" ''
+'br\}
+.\"
+.\" If the F register is turned on, we'll generate index entries on stderr for
+.\" titles (.TH), headers (.SH), subsections (.Sh), items (.Ip), and index
+.\" entries marked with X<> in POD. Of course, you'll have to process the
+.\" output yourself in some meaningful fashion.
+.if \nF \{\
+. de IX
+. tm Index:\\$1\t\\n%\t"\\$2"
+..
+. nr % 0
+. rr F
+.\}
+.\"
+.\" For nroff, turn off justification. Always turn off hyphenation; it makes
+.\" way too many mistakes in technical documents.
+.hy 0
+.if n .na
+.\"
+.\" Accent mark definitions (@(#)ms.acc 1.5 88/02/08 SMI; from UCB 4.2).
+.\" Fear. Run. Save yourself. No user-serviceable parts.
+. \" fudge factors for nroff and troff
+.if n \{\
+. ds #H 0
+. ds #V .8m
+. ds #F .3m
+. ds #[ \f1
+. ds #] \fP
+.\}
+.if t \{\
+. ds #H ((1u-(\\\\n(.fu%2u))*.13m)
+. ds #V .6m
+. ds #F 0
+. ds #[ \&
+. ds #] \&
+.\}
+. \" simple accents for nroff and troff
+.if n \{\
+. ds ' \&
+. ds ` \&
+. ds ^ \&
+. ds , \&
+. ds ~ ~
+. ds /
+.\}
+.if t \{\
+. ds ' \\k:\h'-(\\n(.wu*8/10-\*(#H)'\'\h"|\\n:u"
+. ds ` \\k:\h'-(\\n(.wu*8/10-\*(#H)'\`\h'|\\n:u'
+. ds ^ \\k:\h'-(\\n(.wu*10/11-\*(#H)'^\h'|\\n:u'
+. ds , \\k:\h'-(\\n(.wu*8/10)',\h'|\\n:u'
+. ds ~ \\k:\h'-(\\n(.wu-\*(#H-.1m)'~\h'|\\n:u'
+. ds / \\k:\h'-(\\n(.wu*8/10-\*(#H)'\z\(sl\h'|\\n:u'
+.\}
+. \" troff and (daisy-wheel) nroff accents
+.ds : \\k:\h'-(\\n(.wu*8/10-\*(#H+.1m+\*(#F)'\v'-\*(#V'\z.\h'.2m+\*(#F'.\h'|\\n:u'\v'\*(#V'
+.ds 8 \h'\*(#H'\(*b\h'-\*(#H'
+.ds o \\k:\h'-(\\n(.wu+\w'\(de'u-\*(#H)/2u'\v'-.3n'\*(#[\z\(de\v'.3n'\h'|\\n:u'\*(#]
+.ds d- \h'\*(#H'\(pd\h'-\w'~'u'\v'-.25m'\f2\(hy\fP\v'.25m'\h'-\*(#H'
+.ds D- D\\k:\h'-\w'D'u'\v'-.11m'\z\(hy\v'.11m'\h'|\\n:u'
+.ds th \*(#[\v'.3m'\s+1I\s-1\v'-.3m'\h'-(\w'I'u*2/3)'\s-1o\s+1\*(#]
+.ds Th \*(#[\s+2I\s-2\h'-\w'I'u*3/5'\v'-.3m'o\v'.3m'\*(#]
+.ds ae a\h'-(\w'a'u*4/10)'e
+.ds Ae A\h'-(\w'A'u*4/10)'E
+. \" corrections for vroff
+.if v .ds ~ \\k:\h'-(\\n(.wu*9/10-\*(#H)'\s-2\u~\d\s+2\h'|\\n:u'
+.if v .ds ^ \\k:\h'-(\\n(.wu*10/11-\*(#H)'\v'-.4m'^\v'.4m'\h'|\\n:u'
+. \" for low resolution devices (crt and lpr)
+.if \n(.H>23 .if \n(.V>19 \
+\{\
+. ds : e
+. ds 8 ss
+. ds o a
+. ds d- d\h'-1'\(ga
+. ds D- D\h'-1'\(hy
+. ds th \o'bp'
+. ds Th \o'LP'
+. ds ae ae
+. ds Ae AE
+.\}
+.rm #[ #] #H #V #F C
+.\" ========================================================================
+.\"
+.IX Title "RADIUS.CONF 5"
+.TH RADIUS.CONF 5 "2008-04-06" "INN 2.4.5" "InterNetNews Documentation"
+.SH "NAME"
+radius.conf \- Configuration for nnrpd RADIUS authenticator
+.SH "DESCRIPTION"
+.IX Header "DESCRIPTION"
+This describes the format and attributes of the configuration file for the
+nnrpd \s-1RADIUS\s0 authenticator. See \fIradius\fR\|(1) for more information about the
+authenticator program. The default location for this file is
+\&\fIradius.conf\fR in \fIpathetc\fR.
+.PP
+Blank lines and lines beginning with \f(CW\*(C`#\*(C'\fR are ignored, as is anything
+after a \f(CW\*(C`#\*(C'\fR on a line. All other lines should begin with a parameter
+name followed by a colon and the value of that key, except that each
+section of configuration for a particular server should be enclosed in:
+.PP
+.Vb 3
+\& server <name> {
+\& # parameters...
+\& }
+.Ve
+.PP
+where <name> is just some convenient label for that server.
+.PP
+The available parameters are:
+.IP "\fIradhost\fR" 4
+.IX Item "radhost"
+The hostname of the \s-1RADIUS\s0 server to use for authentication. This
+parameter must be set.
+.IP "\fIradport\fR" 4
+.IX Item "radport"
+The port to query on the \s-1RADIUS\s0 server. Defaults to 1645 if not set.
+.IP "\fIlochost\fR" 4
+.IX Item "lochost"
+The hostname or \s-1IP\s0 address making the request. The \s-1RADIUS\s0 server expects
+an \s-1IP\s0 address; a hostname will be translated into an \s-1IP\s0 address with
+\&\fIgethostbyname()\fR. If not given, this information isn't included in the
+request (not all \s-1RADIUS\s0 setups require this information).
+.IP "\fIlocport\fR" 4
+.IX Item "locport"
+The port the client being authenticated is connecting to. If not given,
+defaults to 119. This doesn't need to be set unless readers are
+connecting to a non-standard port.
+.IP "\fIsecret\fR" 4
+.IX Item "secret"
+The shared secret with the \s-1RADIUS\s0 server. If your secret includes spaces,
+tabs, or \f(CW\*(C`#\*(C'\fR, be sure to include it in double quotes. This parameter
+must be set.
+.IP "\fIprefix\fR" 4
+.IX Item "prefix"
+Prepend the value of this parameter to all usernames before passing them
+to the \s-1RADIUS\s0 server. Can be used to prepend something like \f(CW\*(C`news\-\*(C'\fR to
+all usernames in order to put news users into a different namespace from
+other accounts served by the same server. If not set, nothing is
+prepended.
+.IP "\fIsuffix\fR" 4
+.IX Item "suffix"
+Append the value of this parameter to all usernames before passing them to
+the \s-1RADIUS\s0 server. This is often something like \f(CW\*(C`@example.com\*(C'\fR,
+depending on how your \s-1RADIUS\s0 server is set up. If not set, nothing is
+appended.
+.IP "\fIignore-source\fR" 4
+.IX Item "ignore-source"
+Can be set to \f(CW\*(C`true\*(C'\fR or \f(CW\*(C`false\*(C'\fR. If set to false, the \s-1RADIUS\s0
+authenticator will check to ensure that the response it receives is from
+the same \s-1IP\s0 address as it sent the request to (for some added security).
+If set to true, it will skip this verification check (if your \s-1RADIUS\s0
+server has multiple \s-1IP\s0 addresses or if other odd things are going on, it
+may be perfectly normal for the response to come from a different \s-1IP\s0
+address).
+.SH "EXAMPLE"
+.IX Header "EXAMPLE"
+Here is a configuration for a news server named news.example.com,
+authenticating users against radius.example.com and appending
+\&\f(CW\*(C`@example.com\*(C'\fR to all client-supplied usernames before passing them to
+the \s-1RADIUS\s0 server:
+.PP
+.Vb 6
+\& server example {
+\& radhost: radius.example.com
+\& lochost: news.example.com
+\& secret: IamARADIUSsecRET
+\& suffix: @example.com
+\& }
+.Ve
+.PP
+The shared secret with the \s-1RADIUS\s0 server is \f(CW\*(C`IamARADIUSsecRET\*(C'\fR.
+.SH "HISTORY"
+.IX Header "HISTORY"
+This documentation was written by Russ Allbery <rra@stanford.edu> based on
+the comments in the sample radius.conf file by Yury B. Razbegin.
+.PP
+$Id: radius.conf.5 7880 2008-06-16 20:37:13Z iulius $
+.SH "SEE ALSO"
+.IX Header "SEE ALSO"
+\&\fIradius\fR\|(1)
--- /dev/null
+.\" Automatically generated by Pod::Man v1.37, Pod::Parser v1.32
+.\"
+.\" Standard preamble:
+.\" ========================================================================
+.de Sh \" Subsection heading
+.br
+.if t .Sp
+.ne 5
+.PP
+\fB\\$1\fR
+.PP
+..
+.de Sp \" Vertical space (when we can't use .PP)
+.if t .sp .5v
+.if n .sp
+..
+.de Vb \" Begin verbatim text
+.ft CW
+.nf
+.ne \\$1
+..
+.de Ve \" End verbatim text
+.ft R
+.fi
+..
+.\" Set up some character translations and predefined strings. \*(-- will
+.\" give an unbreakable dash, \*(PI will give pi, \*(L" will give a left
+.\" double quote, and \*(R" will give a right double quote. \*(C+ will
+.\" give a nicer C++. Capital omega is used to do unbreakable dashes and
+.\" therefore won't be available. \*(C` and \*(C' expand to `' in nroff,
+.\" nothing in troff, for use with C<>.
+.tr \(*W-
+.ds C+ C\v'-.1v'\h'-1p'\s-2+\h'-1p'+\s0\v'.1v'\h'-1p'
+.ie n \{\
+. ds -- \(*W-
+. ds PI pi
+. if (\n(.H=4u)&(1m=24u) .ds -- \(*W\h'-12u'\(*W\h'-12u'-\" diablo 10 pitch
+. if (\n(.H=4u)&(1m=20u) .ds -- \(*W\h'-12u'\(*W\h'-8u'-\" diablo 12 pitch
+. ds L" ""
+. ds R" ""
+. ds C` ""
+. ds C' ""
+'br\}
+.el\{\
+. ds -- \|\(em\|
+. ds PI \(*p
+. ds L" ``
+. ds R" ''
+'br\}
+.\"
+.\" If the F register is turned on, we'll generate index entries on stderr for
+.\" titles (.TH), headers (.SH), subsections (.Sh), items (.Ip), and index
+.\" entries marked with X<> in POD. Of course, you'll have to process the
+.\" output yourself in some meaningful fashion.
+.if \nF \{\
+. de IX
+. tm Index:\\$1\t\\n%\t"\\$2"
+..
+. nr % 0
+. rr F
+.\}
+.\"
+.\" For nroff, turn off justification. Always turn off hyphenation; it makes
+.\" way too many mistakes in technical documents.
+.hy 0
+.if n .na
+.\"
+.\" Accent mark definitions (@(#)ms.acc 1.5 88/02/08 SMI; from UCB 4.2).
+.\" Fear. Run. Save yourself. No user-serviceable parts.
+. \" fudge factors for nroff and troff
+.if n \{\
+. ds #H 0
+. ds #V .8m
+. ds #F .3m
+. ds #[ \f1
+. ds #] \fP
+.\}
+.if t \{\
+. ds #H ((1u-(\\\\n(.fu%2u))*.13m)
+. ds #V .6m
+. ds #F 0
+. ds #[ \&
+. ds #] \&
+.\}
+. \" simple accents for nroff and troff
+.if n \{\
+. ds ' \&
+. ds ` \&
+. ds ^ \&
+. ds , \&
+. ds ~ ~
+. ds /
+.\}
+.if t \{\
+. ds ' \\k:\h'-(\\n(.wu*8/10-\*(#H)'\'\h"|\\n:u"
+. ds ` \\k:\h'-(\\n(.wu*8/10-\*(#H)'\`\h'|\\n:u'
+. ds ^ \\k:\h'-(\\n(.wu*10/11-\*(#H)'^\h'|\\n:u'
+. ds , \\k:\h'-(\\n(.wu*8/10)',\h'|\\n:u'
+. ds ~ \\k:\h'-(\\n(.wu-\*(#H-.1m)'~\h'|\\n:u'
+. ds / \\k:\h'-(\\n(.wu*8/10-\*(#H)'\z\(sl\h'|\\n:u'
+.\}
+. \" troff and (daisy-wheel) nroff accents
+.ds : \\k:\h'-(\\n(.wu*8/10-\*(#H+.1m+\*(#F)'\v'-\*(#V'\z.\h'.2m+\*(#F'.\h'|\\n:u'\v'\*(#V'
+.ds 8 \h'\*(#H'\(*b\h'-\*(#H'
+.ds o \\k:\h'-(\\n(.wu+\w'\(de'u-\*(#H)/2u'\v'-.3n'\*(#[\z\(de\v'.3n'\h'|\\n:u'\*(#]
+.ds d- \h'\*(#H'\(pd\h'-\w'~'u'\v'-.25m'\f2\(hy\fP\v'.25m'\h'-\*(#H'
+.ds D- D\\k:\h'-\w'D'u'\v'-.11m'\z\(hy\v'.11m'\h'|\\n:u'
+.ds th \*(#[\v'.3m'\s+1I\s-1\v'-.3m'\h'-(\w'I'u*2/3)'\s-1o\s+1\*(#]
+.ds Th \*(#[\s+2I\s-2\h'-\w'I'u*3/5'\v'-.3m'o\v'.3m'\*(#]
+.ds ae a\h'-(\w'a'u*4/10)'e
+.ds Ae A\h'-(\w'A'u*4/10)'E
+. \" corrections for vroff
+.if v .ds ~ \\k:\h'-(\\n(.wu*9/10-\*(#H)'\s-2\u~\d\s+2\h'|\\n:u'
+.if v .ds ^ \\k:\h'-(\\n(.wu*10/11-\*(#H)'\v'-.4m'^\v'.4m'\h'|\\n:u'
+. \" for low resolution devices (crt and lpr)
+.if \n(.H>23 .if \n(.V>19 \
+\{\
+. ds : e
+. ds 8 ss
+. ds o a
+. ds d- d\h'-1'\(ga
+. ds D- D\h'-1'\(hy
+. ds th \o'bp'
+. ds Th \o'LP'
+. ds ae ae
+. ds Ae AE
+.\}
+.rm #[ #] #H #V #F C
+.\" ========================================================================
+.\"
+.IX Title "RC.NEWS 8"
+.TH RC.NEWS 8 "2008-04-06" "INN 2.4.5" "InterNetNews Documentation"
+.SH "NAME"
+rc.news \- Start or stop INN daemons
+.SH "SYNOPSIS"
+.IX Header "SYNOPSIS"
+\&\fBrc.news\fR [start | stop]
+.SH "DESCRIPTION"
+.IX Header "DESCRIPTION"
+\&\fBrc.news\fR can be used to start or stop \fBinnd\fR and supporting programs.
+It checks to make sure \s-1INN\s0 is not already running, handles cases of
+unclean shutdown, finishes up tasks which might have been interrupted by
+the preceeding shutdown, emails certain boot-time warnings to
+\&\fInewsmaster\fR (as set in \fIinn.conf\fR), and is generally safer and easier
+than starting and stopping everything directly. It needs to be run as the
+news user so that files in \fIpathrun\fR are created with the right ownership
+(though this is less important for \f(CW\*(C`rc.news stop\*(C'\fR), and therefore
+requires that \fIinndstart\fR be setuid root, see \fIinndstart\fR\|(8) for
+discussion.
+.PP
+Programs run and stopped by this script include:
+.IP "\(bu" 4
+Always: \fBinndstart\fR is run, and \fBinnd\fR is stopped.
+.IP "\(bu" 4
+If \fIdoinnwatch\fR is true in \fIinn.conf\fR: \fBinnwatch\fR is started and
+stopped.
+.IP "\(bu" 4
+If \fIdocnfsstat\fR is true in \fIinn.conf\fR: \fBovdb_init\fR is run;
+\&\fBovdb_server\fR and \fBovdb_monitor\fR are stopped.
+.IP "\(bu" 4
+If \fIrc.news.local\fR exists in \fIpathbin\fR: \fBrc.news.local\fR is run with
+argument \f(CW\*(C`start\*(C'\fR or \f(CW\*(C`stop\*(C'\fR (to perform site-specific startup or shutdown
+tasks).
+.SH "OPTIONS"
+.IX Header "OPTIONS"
+.ie n .IP """start""" 4
+.el .IP "\f(CWstart\fR" 4
+.IX Item "start"
+If the first argument is \f(CW\*(C`start\*(C'\fR, or no first argument is given,
+\&\fBrc.news\fR initiates \s-1INN\s0 startup.
+.ie n .IP """stop""" 4
+.el .IP "\f(CWstop\fR" 4
+.IX Item "stop"
+If the first argument is \f(CW\*(C`stop\*(C'\fR, \fBrc.news\fR initiates \s-1INN\s0 shutdown. It
+is recommended to throttle the server first as described in \fIctlinnd\fR\|(8).
+.SH "EXAMPLES"
+.IX Header "EXAMPLES"
+To start \s-1INN\s0 and leave certain error messages going to the terminal:
+.PP
+.Vb 1
+\& su \- news \-c ~news/bin/rc.news
+.Ve
+.PP
+To run \s-1INN\s0 at startup time from appropriate system boot scripts:
+.PP
+.Vb 1
+\& su \- news \-c ~news/bin/rc.news >/dev/console
+.Ve
+.PP
+To stop \s-1INN\s0 (throttling first):
+.PP
+.Vb 2
+\& ~news/bin/ctlinnd throttle reason
+\& su \- news \-c '~news/bin/rc.news stop'
+.Ve
+.SH "BUGS"
+.IX Header "BUGS"
+Running \f(CW\*(C`rc.news start\*(C'\fR as root is never the right thing to do, so we
+should at minimum check for this and error, or perhaps change effective
+user \s-1ID\s0.
+.SH "HISTORY"
+.IX Header "HISTORY"
+// \s-1FIXME:\s0 any attribution for rc.news itself?
+.PP
+This manual page written by Jeffrey M. Vinocur <jeff@litech.org> for
+InterNetNews.
+.PP
+$Id: rc.news.8 7880 2008-06-16 20:37:13Z iulius $
+.SH "SEE ALSO"
+.IX Header "SEE ALSO"
+\&\fIctlinnd\fR\|(8),
+\&\fIcnfsstat\fR\|(8),
+\&\fIinn.conf\fR\|(5),
+\&\fIinndstart\fR\|(8),
+\&\fIinnwatch\fR\|(8),
+\&\fIovdb\fR\|(5).
--- /dev/null
+.\" Automatically generated by Pod::Man v1.37, Pod::Parser v1.32
+.\"
+.\" Standard preamble:
+.\" ========================================================================
+.de Sh \" Subsection heading
+.br
+.if t .Sp
+.ne 5
+.PP
+\fB\\$1\fR
+.PP
+..
+.de Sp \" Vertical space (when we can't use .PP)
+.if t .sp .5v
+.if n .sp
+..
+.de Vb \" Begin verbatim text
+.ft CW
+.nf
+.ne \\$1
+..
+.de Ve \" End verbatim text
+.ft R
+.fi
+..
+.\" Set up some character translations and predefined strings. \*(-- will
+.\" give an unbreakable dash, \*(PI will give pi, \*(L" will give a left
+.\" double quote, and \*(R" will give a right double quote. \*(C+ will
+.\" give a nicer C++. Capital omega is used to do unbreakable dashes and
+.\" therefore won't be available. \*(C` and \*(C' expand to `' in nroff,
+.\" nothing in troff, for use with C<>.
+.tr \(*W-
+.ds C+ C\v'-.1v'\h'-1p'\s-2+\h'-1p'+\s0\v'.1v'\h'-1p'
+.ie n \{\
+. ds -- \(*W-
+. ds PI pi
+. if (\n(.H=4u)&(1m=24u) .ds -- \(*W\h'-12u'\(*W\h'-12u'-\" diablo 10 pitch
+. if (\n(.H=4u)&(1m=20u) .ds -- \(*W\h'-12u'\(*W\h'-8u'-\" diablo 12 pitch
+. ds L" ""
+. ds R" ""
+. ds C` ""
+. ds C' ""
+'br\}
+.el\{\
+. ds -- \|\(em\|
+. ds PI \(*p
+. ds L" ``
+. ds R" ''
+'br\}
+.\"
+.\" If the F register is turned on, we'll generate index entries on stderr for
+.\" titles (.TH), headers (.SH), subsections (.Sh), items (.Ip), and index
+.\" entries marked with X<> in POD. Of course, you'll have to process the
+.\" output yourself in some meaningful fashion.
+.if \nF \{\
+. de IX
+. tm Index:\\$1\t\\n%\t"\\$2"
+..
+. nr % 0
+. rr F
+.\}
+.\"
+.\" For nroff, turn off justification. Always turn off hyphenation; it makes
+.\" way too many mistakes in technical documents.
+.hy 0
+.if n .na
+.\"
+.\" Accent mark definitions (@(#)ms.acc 1.5 88/02/08 SMI; from UCB 4.2).
+.\" Fear. Run. Save yourself. No user-serviceable parts.
+. \" fudge factors for nroff and troff
+.if n \{\
+. ds #H 0
+. ds #V .8m
+. ds #F .3m
+. ds #[ \f1
+. ds #] \fP
+.\}
+.if t \{\
+. ds #H ((1u-(\\\\n(.fu%2u))*.13m)
+. ds #V .6m
+. ds #F 0
+. ds #[ \&
+. ds #] \&
+.\}
+. \" simple accents for nroff and troff
+.if n \{\
+. ds ' \&
+. ds ` \&
+. ds ^ \&
+. ds , \&
+. ds ~ ~
+. ds /
+.\}
+.if t \{\
+. ds ' \\k:\h'-(\\n(.wu*8/10-\*(#H)'\'\h"|\\n:u"
+. ds ` \\k:\h'-(\\n(.wu*8/10-\*(#H)'\`\h'|\\n:u'
+. ds ^ \\k:\h'-(\\n(.wu*10/11-\*(#H)'^\h'|\\n:u'
+. ds , \\k:\h'-(\\n(.wu*8/10)',\h'|\\n:u'
+. ds ~ \\k:\h'-(\\n(.wu-\*(#H-.1m)'~\h'|\\n:u'
+. ds / \\k:\h'-(\\n(.wu*8/10-\*(#H)'\z\(sl\h'|\\n:u'
+.\}
+. \" troff and (daisy-wheel) nroff accents
+.ds : \\k:\h'-(\\n(.wu*8/10-\*(#H+.1m+\*(#F)'\v'-\*(#V'\z.\h'.2m+\*(#F'.\h'|\\n:u'\v'\*(#V'
+.ds 8 \h'\*(#H'\(*b\h'-\*(#H'
+.ds o \\k:\h'-(\\n(.wu+\w'\(de'u-\*(#H)/2u'\v'-.3n'\*(#[\z\(de\v'.3n'\h'|\\n:u'\*(#]
+.ds d- \h'\*(#H'\(pd\h'-\w'~'u'\v'-.25m'\f2\(hy\fP\v'.25m'\h'-\*(#H'
+.ds D- D\\k:\h'-\w'D'u'\v'-.11m'\z\(hy\v'.11m'\h'|\\n:u'
+.ds th \*(#[\v'.3m'\s+1I\s-1\v'-.3m'\h'-(\w'I'u*2/3)'\s-1o\s+1\*(#]
+.ds Th \*(#[\s+2I\s-2\h'-\w'I'u*3/5'\v'-.3m'o\v'.3m'\*(#]
+.ds ae a\h'-(\w'a'u*4/10)'e
+.ds Ae A\h'-(\w'A'u*4/10)'E
+. \" corrections for vroff
+.if v .ds ~ \\k:\h'-(\\n(.wu*9/10-\*(#H)'\s-2\u~\d\s+2\h'|\\n:u'
+.if v .ds ^ \\k:\h'-(\\n(.wu*10/11-\*(#H)'\v'-.4m'^\v'.4m'\h'|\\n:u'
+. \" for low resolution devices (crt and lpr)
+.if \n(.H>23 .if \n(.V>19 \
+\{\
+. ds : e
+. ds 8 ss
+. ds o a
+. ds d- d\h'-1'\(ga
+. ds D- D\h'-1'\(hy
+. ds th \o'bp'
+. ds Th \o'LP'
+. ds ae ae
+. ds Ae AE
+.\}
+.rm #[ #] #H #V #F C
+.\" ========================================================================
+.\"
+.IX Title "READERS.CONF 5"
+.TH READERS.CONF 5 "2008-06-22" "INN 2.4.5" "InterNetNews Documentation"
+.SH "NAME"
+readers.conf \- Access control and configuration for nnrpd
+.SH "DESCRIPTION"
+.IX Header "DESCRIPTION"
+\&\fIreaders.conf\fR in \fIpathetc\fR specifies access control for \fInnrpd\fR\|(8). It
+controls who is allowed to connect as a news reader and what they're
+allowed to do after they connect. nnrpd reads this file when it starts
+up. This generally means that any changes take effect immediately on all
+subsequent connections, but \fBnnrpd\fR may have to be restarted if you use
+the \fB\-D\fR option. (The location \fIpathetc\fR/readers.conf is only the
+default; the same format applies to any file specified with \f(CW\*(C`nnrpd \-c\*(C'\fR.)
+.PP
+There are two types of entries in \fIreaders.conf\fR: parameter/value pairs
+and configuration groups. Blank lines and anything after a number sign
+(\f(CW\*(C`#\*(C'\fR) are ignored, unless the character \f(CW\*(C`#\*(C'\fR is escaped with \f(CW\*(C`\e\*(C'\fR. The
+maximum number of characters on each line is 8,191.
+.PP
+Parameter/value pairs consist of a keyword immediately followed by a
+colon, at least one whitespace character, and a value. The case of the
+parameter is significant (parameter should generally be in all lowercase),
+and a parameter may contain any characters except colon, \f(CW\*(C`#\*(C'\fR, and
+whitespace. An example:
+.PP
+.Vb 1
+\& hosts: *.example.com
+.Ve
+.PP
+Values that contain whitespace should be quoted with double quotes, as in:
+.PP
+.Vb 1
+\& hosts: "*.example.com, *.example.net"
+.Ve
+.PP
+If the parameter does not contain whitespace, such as:
+.PP
+.Vb 1
+\& hosts: *.example.com,*.example.net
+.Ve
+.PP
+it's not necessary to quote it, although you may wish to anyway for
+clarity.
+.PP
+There is no way to continue a line on the next line, and therefore no way
+to have a single parameter with a value longer than about 8,180
+characters.
+.PP
+Many parameters take a boolean value. For all such parameters, the value
+may be specified as \f(CW\*(C`true\*(C'\fR, \f(CW\*(C`yes\*(C'\fR, or \f(CW\*(C`on\*(C'\fR to turn it on and may be any
+of \f(CW\*(C`false\*(C'\fR, \f(CW\*(C`no\*(C'\fR, or \f(CW\*(C`off\*(C'\fR to turn it off. The case of these values is
+not significant.
+.PP
+There are two basic types of configuration groups, auth and access. The
+auth group provides mechanisms to establish the identity of the user, who
+they are. The access group determines, given the user's identity, what
+that user is permitted to do. Writing a \fIreaders.conf\fR file for your
+setup is a two-step process: first assigning an identity to each incoming
+connection using auth groups, and then giving each identity appropriate
+privileges with access group. We recommend \fInot\fR intermingling auth
+groups and access groups in the config file; it is often more sensible (in
+the absence of the \fIkey\fR parameter) to put all of the auth groups first,
+and all of the access groups below.
+.PP
+A user identity, as established by an auth group, looks like an e\-mail
+address; in other words, it's in the form \*(L"<username>@<domain>\*(R" (or
+sometimes just \*(L"<username>\*(R" if no domain is specified.
+.PP
+If \fInnrpdauthsender\fR is set in \fIinn.conf\fR, the user identity is also put
+into the Sender: header of posts made by that user. See the documentation
+of that option in \fIinn.conf\fR\|(5) for more details.
+.PP
+An auth group definition looks like:
+.PP
+.Vb 8
+\& auth <name> {
+\& hosts: <host\-wildmat>
+\& auth: <auth\-program>
+\& res: <res\-program>
+\& default: <defuser>
+\& default\-domain: <defdomain>
+\& # ...possibly other settings
+\& }
+.Ve
+.PP
+The <name> is used as a label for the group and is only for documentation
+purposes. (If your syslog configuration records the \f(CW\*(C`news.debug\*(C'\fR
+facility, the <name> will appear in the debugging output of nnrpd.
+Examining that output can be very helpful in understanding why your
+configuration doesn't do what you expect it to.)
+.PP
+A given auth group applies only to hosts whose name or \s-1IP\s0 address matches
+the wildmat expression given with the hosts: parameter (comma\-separated
+wildmat expressions allowed, but \f(CW\*(C`@\*(C'\fR is not supported). Rather than
+wildmat expressions, you may also use \s-1CIDR\s0 notation to match any \s-1IP\s0
+address in a netblock; for example, \*(L"10.10.10.0/24\*(R" will match any \s-1IP\s0
+address between 10.10.10.0 and 10.10.10.255 inclusive.
+.PP
+If compiled against the \s-1SSL\s0 libraries, an auth group with the require_ssl:
+parameter set to true only applies if the incoming connection is using
+\&\s-1SSL\s0.
+.PP
+For any connection from a host that matches that wildmat expression or
+netblock, each <res\-program> (multiple res: lines may be present in a
+block; they are run in sequence until one succeeds), if any, is run to
+determine the identity of the user just from the connection information.
+If all the resolvers fail, or if the res: parameter isn't present, the
+user is assigned an identity of \*(L"<defuser>@<defdomain>\*(R"; in other words,
+the values of the default: and default\-domain: parameters are used. If
+<res\-program> only returns a username, <defdomain> is used as the
+domain.
+.PP
+If the user later authenticates via the \s-1AUTHINFO\s0 \s-1USER/PASS\s0 commands, the
+provided username and password are passed to each <auth\-program> (multiple
+auth, perl_auth, or python_auth lines may be present in a block; they are
+run in sequence until one succeeds), if any. If one succeeds and returns
+a different identity than the one assigned at the time of the connection,
+it is matched against the available access groups again and the actions
+the user is authorized to do may change. The most common <auth\-program>
+to use is \fBckpasswd\fR, which supports several ways of checking passwords
+including using \s-1PAM\s0. See the \fIckpasswd\fR\|(8) man page for more details.
+.PP
+When matching auth groups, the last auth group in the file that matches a
+given connection and username/password combination is used.
+.PP
+An access group definition usually looks like:
+.PP
+.Vb 5
+\& access <name> {
+\& users: <identity\-wildmat>
+\& newsgroups: <group\-wildmat>
+\& # ...possibly other settings
+\& }
+.Ve
+.PP
+Again, <name> is just for documentation purposes. This says that all
+users whose identity matches <identity\-wildmat> can read and post to all
+newsgroups matching <group\-wildmat> (as before, comma-separated wildmat
+expressions are allowed, but \f(CW\*(C`@\*(C'\fR is not supported). Alternately, you can
+use the form:
+.PP
+.Vb 5
+\& access <name> {
+\& users: <identity\-wildmat>
+\& read: <read\-wildmat>
+\& post: <post\-wildmat>
+\& }
+.Ve
+.PP
+and matching users will be able to read any group that matches
+<read\-wildmat> and post to any group that matches <post\-wildmat>. You can
+also set several other things in the access group as well as override
+various \fIinn.conf\fR\|(5) parameters for just a particular group of users.
+.PP
+Just like with auth groups, when matching access groups the last matching
+one in the file is used to determine the user's permissions. There is
+an exception to this rule: if the auth group which matched the client
+contains a perl_access: or python_access: parameter, then the script
+given as argument is used to dynamically generate an access group.
+This new access group is then used to determine the access rights of
+the client; the access groups in the file are ignored.
+.PP
+There is one additional special case to be aware of. When forming
+particularly complex authentication and authorization rules, it is
+sometimes useful for the identities provided by a given auth group to only
+apply to particular access groups; in other words, rather than checking
+the identity against the users: parameter of every access group, it's
+checked against the users: parameter of only some specific access groups.
+This is done with the key: parameter. For example:
+.PP
+.Vb 5
+\& auth example {
+\& key: special
+\& hosts: *.example.com
+\& default: <SPECIAL>
+\& }
+.Ve
+.PP
+.Vb 5
+\& access example {
+\& key: special
+\& users: <SPECIAL>
+\& newsgroups: *
+\& }
+.Ve
+.PP
+In this case, the two key: parameters bind this auth group with this
+access group. For any incoming connection matching \*(L"*.example.com\*(R"
+(assuming there isn't any later auth group that also matches such hosts),
+no access group that doesn't have \*(L"key: special\*(R" will even be considered.
+Similarly, the above access group will only be checked if the user was
+authenticated with an auth group containing \*(L"key: special\*(R". This
+mechanism normally isn't useful; there is almost always a better way to
+achieve the same result.
+.PP
+Also note in the example that there's no default\-domain: parameter, which
+means that no domain is appended to the default username and the identity
+for such connections is just \*(L"<\s-1SPECIAL\s0>\*(R". Note that some additional
+add-ons to \s-1INN\s0 may prefer that authenticated identities always return a
+full e\-mail address (including a domain), so you may want to set up your
+system that way.
+.PP
+Below is the full list of allowable parameters for auth groups and access
+groups, and after that are some examples that may make this somewhat
+clearer.
+.SH "AUTH GROUP PARAMETERS"
+.IX Header "AUTH GROUP PARAMETERS"
+An access group without at least one of the res:, auth:, perl_auth:,
+python_auth:, or default: parameters makes no sense (and in practice will
+just be ignored).
+.IP "\fBhosts:\fR" 4
+.IX Item "hosts:"
+A comma-separated list of remote hosts, wildmat patterns matching either
+hostnames or \s-1IP\s0 addresses, or \s-1IP\s0 netblocks specified in \s-1CIDR\s0 notation. If
+a user connects from a host that doesn't match this parameter, this auth
+group will not match the connection and is ignored.
+.Sp
+Note that if you have a large number of patterns that can't be merged into
+broader patterns (such as a large number of individual systems scattered
+around the net that should have access), the hosts: parameter may exceed
+the maximum line length of 8,192 characters. In that case, you'll need to
+break that auth group into multiple auth groups, each with a portion of
+the hosts listed in its hosts: parameter, and each assigning the same user
+identity.
+.Sp
+All hosts match if this parameter is not given.
+.IP "\fBlocaladdress:\fR" 4
+.IX Item "localaddress:"
+A comma-separated list of local host or address patterns with the same
+syntax as the same as with the hosts: parameter. If this parameter is
+specified, its auth group will only match connections made to a matching
+local interface. (Obviously, this is only useful for servers with
+multiple interfaces.)
+.Sp
+All local addresses match if this parameter is not given.
+.IP "\fBres:\fR" 4
+.IX Item "res:"
+A simple command line for a user resolver (shell metacharacters are not
+supported). If a full path is not given, the program executed must be in
+the \fIpathbin\fR/auth/resolv directory. A resolver is an authentication
+program which attempts to figure out the identity of the connecting user
+using nothing but the connection information (in other words, the user
+has not provided a username and password). An examples of a resolver
+would be a program that assigns an identity from an ident callback or
+from the user's hostname.
+.Sp
+One auth group can have multiple res: parameters, and they will be tried
+in the order they're listed. The results of the first successful one
+will be used.
+.IP "\fBauth:\fR" 4
+.IX Item "auth:"
+A simple command line for a user authenticator (shell metacharacters are
+not supported). If a full path is not given, the program executed must be
+located in the \fIpathbin\fR/auth/passwd directory. An authenticator is a
+program used to handle a user-supplied username and password, via a
+mechanism such as \s-1AUTHINFO\s0 \s-1USER/PASS\s0. Like with res:, one auth group can
+have multiple auth: parameters; they will be tried in order and the
+results of the first successful one will be used. See also perl_auth:
+below.
+.Sp
+The most common authenticator to use is \fIckpasswd\fR\|(8); see its man page for
+more information.
+.IP "\fBperl_auth:\fR" 4
+.IX Item "perl_auth:"
+A path to a perl script for authentication. The perl_auth: parameter
+works exactly like auth:, except that it calls the named script using
+the perl hook rather then an external program. Multiple/mixed use of
+the auth, perl_auth, and python_auth parameters is permitted within any
+auth group; each line is tried in the order it appears. perl_auth:
+has more power than auth: in that it provides the authentication
+program with additional information about the client and the ability
+to return an error string and a username. This parameter is only
+valid if \s-1INN\s0 is compiled with Perl support (\fB\-\-with\-perl\fR passed to
+configure). More information may be found in \fIdoc/hook\-perl\fR.
+.IP "\fBpython_auth:\fR" 4
+.IX Item "python_auth:"
+A Python script for authentication. The \fIpython_auth\fR parameter works
+exactly like \fIauth\fR, except that it calls the named script (without its
+\&\f(CW\*(C`.py\*(C'\fR extension) using the Python hook rather then an external program.
+Multiple/mixed use of the \fIauth\fR, \fIperl_auth\fR, and \fIpython_auth\fR
+parameters is permitted within any auth group; each line is tried
+in the order it appears. \fIpython_auth\fR has more power than \fIauth\fR
+in that it provides the authentication program with additional information
+about the client and the ability to return an error string and a username.
+This parameter is only valid if \s-1INN\s0 is compiled with Python support
+(\fB\-\-with\-python\fR passed to \fBconfigure\fR). More information may be
+found in \fIdoc/hook\-python\fR.
+.IP "\fBdefault:\fR" 4
+.IX Item "default:"
+The default username for connections matching this auth group. This is
+the username assigned to the user at connection time if all resolvers fail
+or if there are no res: parameters. Note that it can be either a bare
+username, in which case default\-domain: (if present) is appended after
+an \f(CW\*(C`@\*(C'\fR, or a full identity string containing an \f(CW\*(C`@\*(C'\fR, in which case it
+will be used verbatim.
+.IP "\fBdefault\-domain:\fR" 4
+.IX Item "default-domain:"
+The default domain string for this auth group. If a user resolver or
+authenticator doesn't provide a domain, or if the default username is used
+and it doesn't contain a \f(CW\*(C`@\*(C'\fR, this domain is used to form the user
+identity. (Note that for a lot of setups, it's not really necessary for
+user identities to be qualified with a domain name, in which case there's
+no need to use this parameter.)
+.IP "\fBkey:\fR" 4
+.IX Item "key:"
+If this parameter is present, any connection matching this auth group will
+have its privileges determined only by the subset of access groups
+containing a matching key parameter.
+.IP "\fBrequire_ssl:\fR" 4
+.IX Item "require_ssl:"
+If set to true, an incoming connection only matches this auth group if
+it is encrypted using \s-1SSL\s0. This parameter is only valid if \s-1INN\s0 is
+compiled with \s-1SSL\s0 support (\fB\-\-with\-openssl\fR passed to configure).
+.IP "\fBperl_access:\fR" 4
+.IX Item "perl_access:"
+A path to a perl script for dynamically generating an access group. If
+an auth group matches successfully and contains a perl_access parameter,
+then the argument perl script will be used to create an access group.
+This group will then determine the access rights of the client,
+overriding any access groups in \fIreaders.conf\fR. If and only if a
+sucessful auth group contains the perl_access parameter, \fIreaders.conf\fR
+access groups are ignored and the client's rights are instead determined
+dynamically. This parameter is only valid if \s-1INN\s0 is compiled with Perl
+support (\fB\-\-with\-perl\fR passed to configure). More information may be
+found in the file \fIdoc/hook\-perl\fR.
+.IP "\fBpython_access:\fR" 4
+.IX Item "python_access:"
+A Python script for dynamically generating an access group. If
+an auth group matches successfully and contains a \fIpython_access\fR parameter,
+then the argument script (without its \f(CW\*(C`.py\*(C'\fR extension) will be used to
+create an access group. This group will then determine the access rights
+of the client, overriding any access groups in \fIreaders.conf\fR. If and only
+if a successful auth group contains the \fIpython_access\fR parameter, \fIreaders.conf\fR
+access groups are ignored and the client's rights are instead determined
+dynamically. This parameter is only valid if \s-1INN\s0 is compiled with Python
+support (\fB\-\-with\-python\fR passed to \fBconfigure\fR). More information may be
+found in the file \fIdoc/hook\-python\fR.
+.IP "\fBpython_dynamic:\fR" 4
+.IX Item "python_dynamic:"
+A Python script for applying access control dynamically on a per newsgroup
+basis. If an auth group matches successfully and contains a
+\&\fIpython_dynamic\fR parameter, then the argument script (without its
+\&\f(CW\*(C`.py\*(C'\fR extension) will be used to determine the clients rights each time
+the user attempts to view a newsgroup, or read or post an article. Access
+rights as determined by \fIpython_dynamic\fR override the values of access
+group parameters such as \fInewsgroups\fR, \fIread\fR and \fIpost\fR. This parameter
+is only valid if \s-1INN\s0 is compiled with Python support (\fB\-\-with\-python\fR
+passed to \fBconfigure\fR). More information may be found in the file
+\&\fIdoc/hook\-python\fR.
+.SH "ACCESS GROUP PARAMETERS"
+.IX Header "ACCESS GROUP PARAMETERS"
+.IP "\fBusers:\fR" 4
+.IX Item "users:"
+The privileges given by this access group apply to any user identity which
+matches this comma-separated list of wildmat patterns. If this parameter
+isn't given, the access group applies to all users (and is essentially
+equivalent to \f(CW\*(C`users: *\*(C'\fR).
+.IP "\fBnewsgroups:\fR" 4
+.IX Item "newsgroups:"
+Users that match this access group are allowed to read and post to all
+newsgroups matching this comma-separated list of wildmat patterns. The
+empty string is equivalent to \f(CW\*(C`newsgroups: *\*(C'\fR; if this parameter is
+missing, the connection will be rejected (unless read: and/or post: are
+used instead, see below).
+.IP "\fBread:\fR" 4
+.IX Item "read:"
+Like the newsgroups: parameter, but the client is only given permission to
+read the matching newsgroups. This parameter is often used with post:
+(below) to specify some read-only groups; it cannot be used in the same
+access group with a newsgroups: parameter. (If read: is used and post:
+is missing, the client will have only read-only access.)
+.IP "\fBpost:\fR" 4
+.IX Item "post:"
+Like the newsgroups: parameter, but the client is only given permission to
+post to the matching newsgroups. This parameter is often used with read:
+(above) to define the patterns for reading and posting separately (usually
+to give the user permission to read more newsgroups than they're permitted
+to post to). It cannot be used in the same access group with a
+newsgroups: parameter.
+.IP "\fBaccess:\fR" 4
+.IX Item "access:"
+A set of letters specifying the permissions granted to the client. The
+letters are chosen from the following set:
+.RS 4
+.IP "R" 3
+.IX Item "R"
+The client may read articles.
+.IP "P" 3
+.IX Item "P"
+The client may post articles.
+.IP "I" 3
+.IX Item "I"
+The client may inject articles with \s-1IHAVE\s0. Note that in order to
+inject articles with the \s-1IHAVE\s0 the user must also have \s-1POST\s0 permission
+(the \f(CW\*(C`P\*(C'\fR option).
+.IP "A" 3
+.IX Item "A"
+The client may post articles with Approved: headers (in other words, may
+approve articles for moderated newsgroups). By default, this is not
+allowed.
+.IP "N" 3
+.IX Item "N"
+The client may use the \s-1NEWNEWS\s0 command, overriding the global setting.
+.IP "L" 3
+.IX Item "L"
+The client may post to newsgroups that are set to disallow local posting
+(mode \f(CW\*(C`n\*(C'\fR in the \fIactive\fR\|(5) file).
+.RE
+.RS 4
+.Sp
+Note that if this parameter is given, \fIallownewnews\fR in \fIinn.conf\fR is
+ignored for connections matching this access group and the ability of the
+client to use \s-1NEWNEWS\s0 is entirely determined by the presence of \f(CW\*(C`N\*(C'\fR in
+the access string. If you want to support \s-1NEWNEWS\s0, make sure to include
+\&\f(CW\*(C`N\*(C'\fR in the access string when you use this parameter.
+.Sp
+Note that if this parameter is given and \f(CW\*(C`R\*(C'\fR isn't present in the access
+string, the client cannot read regardless of newsgroups: or read:
+parameters. Similarly, if this parameter is given and \f(CW\*(C`P\*(C'\fR isn't present,
+the client cannot post. This use of access: is deprecated and confusing;
+it's strongly recommended that if the access: parameter is used, \f(CW\*(C`R\*(C'\fR and
+\&\f(CW\*(C`P\*(C'\fR always be included in the access string and newsgroups:, read:, and
+post: be used to control access. (To grant read access but no posting
+access, one can have just a read: parameter and no post: parameter.)
+.RE
+.IP "\fBkey:\fR" 4
+.IX Item "key:"
+If this parameter is present, this access group is only considered when
+finding privileges for users matching auth groups with this same key:
+parameter.
+.IP "\fBreject_with:\fR" 4
+.IX Item "reject_with:"
+If this parameter is present, a client matching this block will be
+disconnected with a \*(L"Permission denied\*(R" message containing the contents
+(a \*(L"reason\*(R" string) of this parameter. Some newsreaders will then
+display the reason to the user.
+.IP "\fBmax_rate:\fR" 4
+.IX Item "max_rate:"
+If this parameter is present (and nonzero), it is used for \fBnnrpd\fR's
+rate-limiting code. The client will only be able to download at this
+speed (in bytes/second). Note that if \s-1SSL\s0 is being used, limiting
+is applied to the pre-encryption datastream.
+.IP "\fBlocaltime:\fR" 4
+.IX Item "localtime:"
+If a Date: header is not included in a posted article, \fInnrpd\fR\|(8) normally
+adds a new Date: header in \s-1UTC\s0. If this is set to true, the Date: header
+will be formatted in local time instead. This is a boolean value and the
+default is false.
+.IP "\fBnewsmaster:\fR" 4
+.IX Item "newsmaster:"
+Used as the contact address in the help message returned by \fInnrpd\fR\|(8), if
+the virtualhost: parameter is set to true.
+.IP "\fBstrippath:\fR" 4
+.IX Item "strippath:"
+If set to true, any Path: header provided by a user in a post is stripped
+rather than used as the beginning of the Path: header of the article.
+This is a boolean value and the default is false.
+.IP "\fBperlfilter:\fR" 4
+.IX Item "perlfilter:"
+If set to false, posts made by these users do not pass through the Perl
+filter even if it is otherwise enabled. This is a boolean value and the
+default is true.
+.IP "\fBpythonfilter:\fR" 4
+.IX Item "pythonfilter:"
+If set to false, posts made by these users do not pass through the Python
+filter even if it is otherwise enabled. This is a boolean value and the
+default is true.
+.IP "\fBvirtualhost:\fR" 4
+.IX Item "virtualhost:"
+Set this parameter to true in order to make \fBnnrpd\fR behave as if it is
+running on a server with a different name than it actually is. If you
+set this parameter to true, you must also set either pathhost: or domain:
+in the relevant access group in \fIreaders.conf\fR to something different
+than is set in \fIinn.conf\fR. All articles displayed to clients will then have
+their Path: and Xref: headers altered to appear to be from the server
+named in pathhost: or domain: (whichever is set), and posted articles will
+use that server name in the Path:, Message\-ID:, and X\-Trace: headers.
+.Sp
+Note that setting this parameter requires the server modify all posts
+before presenting them to the client and therefore may decrease
+performance slightly.
+.PP
+In addition, all of the following parameters are valid in access groups
+and override the global setting in \fIinn.conf\fR. See \fIinn.conf\fR\|(5) for the
+descriptions of these parameters:
+.PP
+.Vb 6
+\& addnntppostingdate, addnntppostinghost, backoff_auth, backoff_db,
+\& backoff_k, backoff_postfast, backoff_postslow, backoff_trigger,
+\& checkincludedtext, clienttimeout, complaints, domain,
+\& fromhost, localmaxartsize, moderatormailer, nnrpdauthsender,
+\& nnrpdcheckart, nnrpdoverstats, nnrpdposthost, nnrpdpostport, organization,
+\& pathhost, readertrack, spoolfirst, strippostcc.
+.Ve
+.SH "SUMMARY"
+.IX Header "SUMMARY"
+Here's a basic summary of what happens when a client connects:
+.IP "\(bu" 2
+All auth groups are scanned and the ones that don't match the client
+(due to hosts:, localaddress:, require_ssl:, etc) are eliminated.
+.IP "\(bu" 2
+The remaining auth groups are scanned from the last to the first, and an
+attempt is made to apply it to the current connection. This means running
+res: programs, if any, and otherwise applying default:. The first auth
+group (starting from the bottom) to return a valid user is kept as the
+active auth group.
+.IP "\(bu" 2
+If no auth groups yield a valid user (none have default: parameters or
+successful res: programs) but some of the auth groups have auth: lines
+(indicating a possibility that the user can authenticate and then obtain
+permissions), the connection is considered to have no valid auth group
+(which means that the access groups are ignored completely) but the
+connection isn't closed. Instead, 480 is returned for everything until
+the user authenticates.
+.IP "\(bu" 2
+When the user authenticates, the auth groups are rescanned, and only the
+matching ones which contain at least one auth, perl_auth, or
+python_auth line are considered. These auth groups are scanned from
+the last to the first, running auth: programs and perl_auth: or
+python_auth: scripts. The first auth group (starting from the bottom)
+to return a valid user is kept as the active auth group.
+.IP "\(bu" 2
+Regardless of how an auth group is established, as soon as one is, that
+auth group is used to assign a user identity by taking the result of the
+successful res, auth, perl_auth, or python_auth line (or the
+default: if necessary), and appending the default-domain if
+necessary. (If the perl_access: or python_access: parameter is
+present, see below.)
+.IP "\(bu" 2
+Finally, an access group is selected by scanning the access groups from
+bottom up and finding the first match. (If the established auth group
+contained a perl_access: or python_access line, the dynamically
+generated access group returned by the script is used instead.)
+User permissions are granted based on the established access group.
+.SH "EXAMPLES"
+.IX Header "EXAMPLES"
+Probably the simplest useful example of a complete \fIreaders.conf\fR,
+this gives permissions to read and post to all groups to any connections
+from the \*(L"example.com\*(R" domain, and no privileges for anyone connecting
+elsewhere:
+.PP
+.Vb 4
+\& auth example.com {
+\& hosts: "*.example.com, example.com"
+\& default: <LOCAL>
+\& }
+.Ve
+.PP
+.Vb 3
+\& access full {
+\& newsgroups: *
+\& }
+.Ve
+.PP
+Note that the access realm has no users: key and therefore applies to any
+user identity. The only available auth realm only matches hosts in the
+\&\*(L"example.com\*(R" domain, though, so any connections from other hosts will be
+rejected immediately.
+.PP
+If you have some systems that should only have read-only access to the
+server, you can modify the example above slightly by adding an additional
+auth and access group:
+.PP
+.Vb 4
+\& auth lab {
+\& hosts: "*.lab.example.com"
+\& default: <LAB>
+\& }
+.Ve
+.PP
+.Vb 4
+\& access lab {
+\& users: <LAB>
+\& read: *
+\& }
+.Ve
+.PP
+If those are put in the file after the above example, they'll take
+precedence (because they're later in the file) for any user coming from a
+machine in the lab.example.com domain, everyone will only have read
+access, not posting access.
+.PP
+Here's a similar example for a news server that accepts connections from
+anywhere but requires the user to specify a username and password. The
+username and password are first checked against an external database of
+usernames and passwords, and then against the system shadow password file:
+.PP
+.Vb 4
+\& auth all {
+\& auth: "ckpasswd \-d <pathdb in inn.conf>/newsusers"
+\& auth: "ckpasswd \-s"
+\& }
+.Ve
+.PP
+.Vb 4
+\& access full {
+\& users: *
+\& newsgroups: *
+\& }
+.Ve
+.PP
+When the user first connects, there are no res: keys and no default, so
+they don't receive any valid identity and the connection won't match any
+access groups (even ones with \f(CW\*(C`users: *\*(C'\fR). Such users receive nothing
+but authentication-required responses from nnrpd until they authenticate.
+.PP
+If they then later authenticate, the username and password are checked
+first by running \fBckpasswd\fR with the \fB\-d\fR option for an external dbm
+file of encrypted passwords, and then with the \fB\-s\fR option to check the
+shadow password database (note that this option may require ckpasswd to
+be setgid to a shadow group, and there are security considerations; see
+\&\fIckpasswd\fR\|(8) for details). If both of those fail, the user will continue
+to have no identity; otherwise, an identity will be assigned (usually
+the supplied username, perhaps with a domain appended, although an
+authenticator technically can provide a completely different username
+for the identity), and the access group will match, giving full access.
+.PP
+It may be educational to consider how to combine the above examples;
+general groups always go first. The order of the auth groups actually
+doesn't matter, since the \*(L"hosts: example.com\*(R" one only matches
+connections before username/password is sent, and the \*(L"auth: ckpasswd\*(R"
+one only matches after; order would matter if either group applied to
+both cases. The order of the access groups in this case does matter,
+provided the newsgroups: lines differ; the access group with no users:
+line needs to be first, with the \*(L"users: <\s-1LOCAL\s0>\*(R" group after.
+.PP
+Here's a very complicated example. This is for an organization that has
+an internal hierarchy \*(L"example.*\*(R" only available to local shell users, who
+are on machines where identd can be trusted. Dialup users must provide a
+username and password, which is then checked against \s-1RADIUS\s0. Remote users
+have to use a username and password that's checked against a database on
+the news server. Finally, the admin staff (users \*(L"joe\*(R" and \*(L"jane\*(R") can
+post anywhere (including the \*(L"example.admin.*\*(R" groups that are read-only
+for everyone else), and are exempted from the Perl filter. For an
+additional twist, posts from dialup users have their Sender: header
+replaced by their authenticated identity.
+.PP
+Since this organization has some internal moderated newsgroups, the admin
+staff can also post messages with Approved: headers, but other users
+cannot.
+.PP
+.Vb 5
+\& auth default {
+\& auth: "ckpasswd \-f <pathdb in inn.conf>/newsusers"
+\& default: <FAIL>
+\& default\-domain: example.com
+\& }
+.Ve
+.PP
+.Vb 7
+\& auth shell {
+\& hosts: *.shell.example.com
+\& res: ident
+\& auth: "ckpasswd \-s"
+\& default: <FAIL>
+\& default\-domain: shell.example.com
+\& }
+.Ve
+.PP
+.Vb 6
+\& auth dialup {
+\& hosts: *.dialup.example.com
+\& auth: radius
+\& default: <FAIL>
+\& default\-domain: dialup.example.com
+\& }
+.Ve
+.PP
+.Vb 5
+\& access shell {
+\& users: *@shell.example.com
+\& read: *
+\& post: "*, !example.admin.*"
+\& }
+.Ve
+.PP
+.Vb 5
+\& access dialup {
+\& users: *@dialup.example.com
+\& newsgroups: *,!example.*
+\& nnrpdauthsender: true
+\& }
+.Ve
+.PP
+.Vb 4
+\& access other {
+\& users: "*@example.com, !<FAIL>@example.com"
+\& newsgroups: *,!example.*
+\& }
+.Ve
+.PP
+.Vb 4
+\& access fail {
+\& users: "<FAIL>@*"
+\& newsgroups: !*
+\& }
+.Ve
+.PP
+.Vb 6
+\& access admin {
+\& users: "joe@*,jane@*"
+\& newsgroups: *
+\& access: "RPA"
+\& perlfilter: false
+\& }
+.Ve
+.PP
+Note the use of different domains to separate dialup from shell users
+easily. Another way to do that would be with key: parameters, but this
+way provides slightly more intuitive identity strings. Note also that the
+fail access group catches not only failing connections from external users
+but also failed authentication of shell and dialup users and dialup users
+before they've authenticated. The identity string given for, say, dialup
+users before \s-1RADIUS\s0 authentication has been attempted matches both the
+dialup access group and the fail access group, since it's
+\&\*(L"<\s-1FAIL\s0>@dialup.example.com\*(R", but the fail group is last so it takes
+precedence.
+.PP
+The shell auth group has an auth: parameter so that users joe and jane
+can, if they choose, use username and password authentication to gain
+their special privileges even if they're logged on as a different user on
+the shell machines (or if ident isn't working). When they first connect,
+they'd have the default access for that user, but they could then send
+\&\s-1AUTHINFO\s0 \s-1USER\s0 and \s-1AUTHINFO\s0 \s-1PASS\s0 (or \s-1AUTHINFO\s0 \s-1SIMPLE\s0) and get their
+extended access.
+.PP
+Also note that if the users joe and jane are using their own accounts,
+they get their special privileges regardless of how they connect, whether
+the dialups, the shell machines, or even externally with a username and
+password.
+.PP
+Finally, here's a very simple example of a configuration for a public
+server for a particular hierarchy.
+.PP
+.Vb 4
+\& auth default {
+\& hosts: *
+\& default: <PUBLIC>
+\& }
+.Ve
+.PP
+.Vb 4
+\& access default {
+\& users: <PUBLIC>
+\& newsgroups: example.*
+\& }
+.Ve
+.PP
+Notice that clients aren't allowed to read any other groups; this keeps
+them from getting access to administrative groups or reading control
+messages, just as a precaution. When running a public server like this,
+be aware that many public hierarchies will later be pulled down and
+reinjected into the main Usenet, so it's highly recommended that you also
+run a Perl or Python filter to reject any messages crossposted out of your
+local hierarchy and any messages containing a Supersedes: header. This
+will keep messages posted to your public hierarchy from hurting any of the
+rest of Usenet if they leak out.
+.SH "SECURITY CONSIDERATIONS"
+.IX Header "SECURITY CONSIDERATIONS"
+In general, separate passwords should be used for \s-1NNTP\s0 wherever
+possible; the \s-1NNTP\s0 protocol itself does not protect passwords from
+casual interception, and many implementations (including this one) do
+not \*(L"lock out\*(R" accounts or otherwise discourage password-guessing
+attacks. So it is best to ensure that a compromised password has
+minimal effects.
+.PP
+Authentication using the \s-1AUTHINFO\s0 \s-1USER/PASS\s0 commands passes unencrypted
+over the network. Extreme caution should therefore be used especially
+with system passwords (e.g. \f(CW\*(C`auth: ckpasswd \-s\*(C'\fR). Passwords can be
+protected by using \s-1NNTP\s0 over \s-1SSL\s0 or through ssh tunnels, and this usage
+can be enforced by a well-considered server configuration that only
+permits certain auth groups to be applied in certain cases. Here are
+some ideas:
+.IP "\(bu" 4
+To restrict connections on the standard nntp port (119) to use \s-1SSL\s0 for
+some (or all) of the auth groups to match, use the require_ssl:
+parameter.
+.IP "\(bu" 4
+If you consider your local network (but not the internet) secure, have
+some auth groups with a restrictive hosts: parameter; they would go
+above, with ones having global applicability below.
+.IP "\(bu" 4
+Consider running a \f(CW\*(C`nnrpd \-S\*(C'\fR (with \f(CW\*(C`\-D\*(C'\fR, or out of \*(L"super\-server\*(R"
+like \fBinetd\fR) on the \s-1NNTPS\s0 port (563) for clients that support \s-1SSL\s0. See
+\&\fInnrpd\fR\|(8) for more details about how to configure that. You
+can use the require_ssl: parameter, or \f(CW\*(C`\-c\*(C'\fR to specify an alternate
+\&\fIreaders.conf\fR if you want a substantially different configuration for
+this case.
+.IP "\(bu" 4
+If you want to restrict an auth group to only match loopback connections
+(for users running newsreaders on localhost or connecting via an ssh
+tunnel), use the localaddress: parameter.
+.SH "HISTORY"
+.IX Header "HISTORY"
+Written by Aidan Cully <aidan@panix.com> for InterNetNews. Substantially
+expanded by Russ Allbery <rra@stanford.edu>.
+.PP
+$Id: readers.conf.5 7895 2008-06-22 17:54:10Z iulius $
+.SH "SEE ALSO"
+.IX Header "SEE ALSO"
+\&\fIauth_krb5\fR\|(8), \fIauth_smb\fR\|(8), \fIckpasswd\fR\|(8), \fIinn.conf\fR\|(5), \fIinnd\fR\|(8), \fInewsfeeds\fR\|(5),
+\&\fInnrpd\fR\|(8), \fIuwildmat\fR\|(3).
--- /dev/null
+.\" $Revision: 5909 $
+.TH RNEWS 1
+.SH NAME
+rnews \- receive news from a UUCP connection
+.SH SYNOPSIS
+.B rnews
+[
+.BI \-h " host"
+]
+[
+.B \-N
+]
+[
+.BI \-P " port"
+]
+[
+.BI \-r " remote"
+]
+[
+.BI \-S " remote"
+]
+[
+.B \-U
+]
+[
+.B \-v
+]
+[
+.I input
+]
+.SH DESCRIPTION
+.I Rnews
+reads messages typically queued by a UUCP newsfeed and
+sends them to the InterNetNews server (either ``localhost'', or the
+value defined by the variable
+.IR <nnrpdposthost\ in\ inn.conf> .
+.PP
+The message is read from the specified input file, or standard input
+if no input is named.
+.PP
+When sent over UUCP, Usenet articles are typically joined in a single
+batch to reduce the UUCP overhead.
+Batches can also be compressed, to reduce the communication time.
+If a message does not start with a number sign (``#'') and an exclamation
+point, then the entire input is taken as a single news article.
+If it does start with with those two characters, then the first line is
+read and interpreted as a batch command.
+.PP
+If the command is ``#! rnews nnn'' where
+.I nnn
+is a number, then the next
+.I nnn
+bytes (starting with the next line) are read as a news article.
+.PP
+If the command is ``#! cunbatch'' then the rest of input is fed to the
+.IR compress (1)
+program with the ``\-d'' flag to uncompress it, and
+the output of this pipe is read as
+.IR rnews 's
+input.
+This is for historical compatibility \(em there is no program named
+.IR cunbatch .
+A compressed batch will start with a ``#! cunbatch'' line, then contain a
+series of articles separated by ``#! rnews nnn'' lines.
+If
+.I <DO_RNEWSPROGS in include/config.h>
+is defined and the command is any other word, then
+.I rnews
+will try to execute a program with that name in the directory
+.IR <pathbin\ in\ inn.conf>/bin/rnews.libexec .
+
+The batch will be fed into the program's standard input, and the
+standard output will be read back as input into
+.IR rnews .
+If
+.I <DO_RNEWS_SAVE_BAD in include/config.h>
+is defined and
+.I rnews
+detects any problems with an article such as a missing header, or
+an unintelligible reply from the server, it will save a copy of the article
+in the
+.I <pathincoming in inn.conf>/bad
+directory.
+.SH OPTIONS
+.TP
+.B \-h
+If the ``\fB\-h\fP'' flag is given, then
+.I rnews
+will log the Message-ID and host via
+.IR syslog (3)
+for each article offered to the server.
+Logging will only be done if the value is not an empty string.
+If ``\fB\-h\fP'' is not set, the environment variable
+.I <_ENV_UUCPHOST in include/paths.h>
+(typically
+.IR $UU_MACHINE )
+will be examined for a similar string.
+.TP
+.B \-N
+Normally, if unpacking the input fails it is re-spooled to
+.I <pathincoming in inn.conf>
+for another attempt later. If the ``\fB\-N\fP'' flag is used then no such
+re-spooling is done and rnews exits with status value ``9'' to indicate
+this.
+.TP
+.B \-P
+If the ``\fB\-P\fP'' flag is used, then the articles will be sent to the
+specified port on the remote host.
+.TP
+.B \-r
+If the ``\fB\-r\fP'' flag is used, then the articles will be sent to the
+named remote host instead of the default host.
+.TP
+.B \-S
+\&``\fB\-S\fP'' flag is equivalent to ``\fB\-r\fP'' flag.
+.TP
+.B \-U
+If the server is not available, the message is spooled into a new file
+created in the
+.I <pathincoming in inn.conf>
+directory.
+The ``\fB\-U\fP'' flag may be used to send all spooled messages to the
+server once it becomes available again, and can be invoked regularly
+by
+.IR cron (8).
+.TP
+.B \-v
+If the ``\fB\-v\fP'' flag is used, it will print a notice of all errors on the
+standard error, naming the input file (if known) and printing the first
+few characters of the input.
+Errors are always logged through
+.IR syslog (3).
+.SH BUGS
+.I Rnews
+cannot process articles that have embedded ``\e0'' characters in them.
+.SH HISTORY
+Written by Rich $alz <rsalz@uunet.uu.net> for InterNetNews.
+.de R$
+This is revision \\$3, dated \\$4.
+..
+.R$ $Id: rnews.1 5909 2002-12-03 05:17:18Z vinocur $
+.SH "SEE ALSO"
+inn.conf(5),
+innd(8).
--- /dev/null
+.\" Automatically generated by Pod::Man v1.37, Pod::Parser v1.32
+.\"
+.\" Standard preamble:
+.\" ========================================================================
+.de Sh \" Subsection heading
+.br
+.if t .Sp
+.ne 5
+.PP
+\fB\\$1\fR
+.PP
+..
+.de Sp \" Vertical space (when we can't use .PP)
+.if t .sp .5v
+.if n .sp
+..
+.de Vb \" Begin verbatim text
+.ft CW
+.nf
+.ne \\$1
+..
+.de Ve \" End verbatim text
+.ft R
+.fi
+..
+.\" Set up some character translations and predefined strings. \*(-- will
+.\" give an unbreakable dash, \*(PI will give pi, \*(L" will give a left
+.\" double quote, and \*(R" will give a right double quote. \*(C+ will
+.\" give a nicer C++. Capital omega is used to do unbreakable dashes and
+.\" therefore won't be available. \*(C` and \*(C' expand to `' in nroff,
+.\" nothing in troff, for use with C<>.
+.tr \(*W-
+.ds C+ C\v'-.1v'\h'-1p'\s-2+\h'-1p'+\s0\v'.1v'\h'-1p'
+.ie n \{\
+. ds -- \(*W-
+. ds PI pi
+. if (\n(.H=4u)&(1m=24u) .ds -- \(*W\h'-12u'\(*W\h'-12u'-\" diablo 10 pitch
+. if (\n(.H=4u)&(1m=20u) .ds -- \(*W\h'-12u'\(*W\h'-8u'-\" diablo 12 pitch
+. ds L" ""
+. ds R" ""
+. ds C` ""
+. ds C' ""
+'br\}
+.el\{\
+. ds -- \|\(em\|
+. ds PI \(*p
+. ds L" ``
+. ds R" ''
+'br\}
+.\"
+.\" If the F register is turned on, we'll generate index entries on stderr for
+.\" titles (.TH), headers (.SH), subsections (.Sh), items (.Ip), and index
+.\" entries marked with X<> in POD. Of course, you'll have to process the
+.\" output yourself in some meaningful fashion.
+.if \nF \{\
+. de IX
+. tm Index:\\$1\t\\n%\t"\\$2"
+..
+. nr % 0
+. rr F
+.\}
+.\"
+.\" For nroff, turn off justification. Always turn off hyphenation; it makes
+.\" way too many mistakes in technical documents.
+.hy 0
+.if n .na
+.\"
+.\" Accent mark definitions (@(#)ms.acc 1.5 88/02/08 SMI; from UCB 4.2).
+.\" Fear. Run. Save yourself. No user-serviceable parts.
+. \" fudge factors for nroff and troff
+.if n \{\
+. ds #H 0
+. ds #V .8m
+. ds #F .3m
+. ds #[ \f1
+. ds #] \fP
+.\}
+.if t \{\
+. ds #H ((1u-(\\\\n(.fu%2u))*.13m)
+. ds #V .6m
+. ds #F 0
+. ds #[ \&
+. ds #] \&
+.\}
+. \" simple accents for nroff and troff
+.if n \{\
+. ds ' \&
+. ds ` \&
+. ds ^ \&
+. ds , \&
+. ds ~ ~
+. ds /
+.\}
+.if t \{\
+. ds ' \\k:\h'-(\\n(.wu*8/10-\*(#H)'\'\h"|\\n:u"
+. ds ` \\k:\h'-(\\n(.wu*8/10-\*(#H)'\`\h'|\\n:u'
+. ds ^ \\k:\h'-(\\n(.wu*10/11-\*(#H)'^\h'|\\n:u'
+. ds , \\k:\h'-(\\n(.wu*8/10)',\h'|\\n:u'
+. ds ~ \\k:\h'-(\\n(.wu-\*(#H-.1m)'~\h'|\\n:u'
+. ds / \\k:\h'-(\\n(.wu*8/10-\*(#H)'\z\(sl\h'|\\n:u'
+.\}
+. \" troff and (daisy-wheel) nroff accents
+.ds : \\k:\h'-(\\n(.wu*8/10-\*(#H+.1m+\*(#F)'\v'-\*(#V'\z.\h'.2m+\*(#F'.\h'|\\n:u'\v'\*(#V'
+.ds 8 \h'\*(#H'\(*b\h'-\*(#H'
+.ds o \\k:\h'-(\\n(.wu+\w'\(de'u-\*(#H)/2u'\v'-.3n'\*(#[\z\(de\v'.3n'\h'|\\n:u'\*(#]
+.ds d- \h'\*(#H'\(pd\h'-\w'~'u'\v'-.25m'\f2\(hy\fP\v'.25m'\h'-\*(#H'
+.ds D- D\\k:\h'-\w'D'u'\v'-.11m'\z\(hy\v'.11m'\h'|\\n:u'
+.ds th \*(#[\v'.3m'\s+1I\s-1\v'-.3m'\h'-(\w'I'u*2/3)'\s-1o\s+1\*(#]
+.ds Th \*(#[\s+2I\s-2\h'-\w'I'u*3/5'\v'-.3m'o\v'.3m'\*(#]
+.ds ae a\h'-(\w'a'u*4/10)'e
+.ds Ae A\h'-(\w'A'u*4/10)'E
+. \" corrections for vroff
+.if v .ds ~ \\k:\h'-(\\n(.wu*9/10-\*(#H)'\s-2\u~\d\s+2\h'|\\n:u'
+.if v .ds ^ \\k:\h'-(\\n(.wu*10/11-\*(#H)'\v'-.4m'^\v'.4m'\h'|\\n:u'
+. \" for low resolution devices (crt and lpr)
+.if \n(.H>23 .if \n(.V>19 \
+\{\
+. ds : e
+. ds 8 ss
+. ds o a
+. ds d- d\h'-1'\(ga
+. ds D- D\h'-1'\(hy
+. ds th \o'bp'
+. ds Th \o'LP'
+. ds ae ae
+. ds Ae AE
+.\}
+.rm #[ #] #H #V #F C
+.\" ========================================================================
+.\"
+.IX Title "SASL.CONF 5"
+.TH SASL.CONF 5 "2008-04-06" "INN 2.4.5" "InterNetNews Documentation"
+.SH "NAME"
+sasl.conf \- SASL Configuration file for nnrpd.
+.SH "DESCRIPTION"
+.IX Header "DESCRIPTION"
+The file \fIsasl.conf\fR in \fIpathetc\fR specifies Simple Authentication
+and Security Layer (\s-1SASL\s0), defined in \s-1RFC\s0 2222, for nnrpd.
+Now nnrpd implements only Security Layer support, which is an extension
+of \s-1RFC\s0 2595. This means you can get \s-1SSL\s0 or \s-1TLS\s0 encrypted \s-1NNRP\s0 between
+your server and newsreaders. It requires OpenSSL 0.9.3 or newer from
+http://www.openssl.org/; it has been tested with versions 0.9.4 and 0.9.5.
+.SH "INSTALLATION"
+.IX Header "INSTALLATION"
+To use \s-1SSL\s0, a certificate and private key are needed that you can
+create using the openssl binary.
+Make certain that each keys are owned by your news user, news group,
+and are mode 0640 or 0660.
+.Sh "\s-1EXAMPLE\s0"
+.IX Subsection "EXAMPLE"
+.Vb 4
+\& openssl req \-new \-x509 \-nodes \-out /usr/local/news/lib/cert.pem\e
+\& \-days 366 \-keyout /usr/local/news/lib/cert.pem
+\& chown news:news /usr/local/news/lib/cert.pem
+\& chmod 640 /usr/local/news/lib/cert.pem
+.Ve
+.PP
+You also can make the keys as the root user with \f(CW\*(C`make cert\*(C'\fR.
+.SH "CONFIGURATION"
+.IX Header "CONFIGURATION"
+Comments begin with a number sign (\f(CW\*(C`#\*(C'\fR) and continue through the
+end of the line. Blank lines and comments are ignored.
+All other lines specify parameters, and should be of the form
+.PP
+.Vb 1
+\& <option>: <value>
+.Ve
+.PP
+where <option> is the name of the configuration option being set and
+<value> is the value that the configuration option is being set to.
+.PP
+Blank lines and lines beginning with (\f(CW\*(C`#\*(C'\fR) are ignored.
+For boolean options, the values \f(CW\*(C`yes\*(C'\fR, \f(CW\*(C`on\*(C'\fR, \f(CW\*(C`t\*(C'\fR,
+and \f(CW1\fR turn the option on; the values \f(CW\*(C`no\*(C'\fR, \f(CW\*(C`off\*(C'\fR,
+\&\f(CW\*(C`f\*(C'\fR, and \f(CW0\fR turn the option off.
+.IP "tls_cert_file" 4
+.IX Item "tls_cert_file"
+The path to a file containing the server's certificate.
+.IP "tls_key_file" 4
+.IX Item "tls_key_file"
+The path to a file containing the server's private key.
+.IP "tls_ca_path" 4
+.IX Item "tls_ca_path"
+The path to a directory containing the \s-1CA\s0's certificate.
+.IP "tls_ca_file" 4
+.IX Item "tls_ca_file"
+The path to a file containing the \s-1CA\s0's certificate.
+.SH "TO DO"
+.IX Header "TO DO"
+Implement methods of the authentication protocols of \s-1SASL\s0.
+.SH "HISTORY"
+.IX Header "HISTORY"
+Written by Kenichi \s-1OKADA\s0 <okada@opaopa.org> for InterNetNews.
+.SH "SEE ALSO"
+.IX Header "SEE ALSO"
+\&\fIinn.conf\fR\|(5), \fIinnd\fR\|(8), \fInnrpd\fR\|(8), \fIreaders.conf\fR\|(5)
--- /dev/null
+.TH SCANLOGS 8
+.SH NAME
+scanlogs \- summarize INN log files.
+.SH SYNOPSIS
+.B scanlogs
+[
+.B norotate
+]
+.SH DESCRIPTION
+.I Scanlogs
+summarizes the information recorded in the INN log files (see
+.IR newslog (5)).
+By default, it also rotates and cleans out the logs.
+It is normally invoked by the
+.IR news.daily (8)
+script.
+.SH KEYWORDS
+.PP
+The following keywords are accepted:
+.TP
+.I norotate
+Using this keyword disables the rotating and cleaning aspect of the log
+processing: the logs files are only scanned for information and no contents
+are altered.
+.PP
+If
+.I scanlogs
+is invoked more than once a day, the ``norotate'' keyword should be used
+to prevent premature log cleaning.
+.SH HISTORY
+Written by Landon Curt Noll <chongo@toad.com> and Rich $alz
+<rsalz@uunet.uu.net> for InterNetNews.
+.de R$
+This is revision \\$3, dated \\$4.
+..
+.R$ $Id: scanlogs.8 309 1998-01-28 04:08:10Z scrappy $
+.SH "SEE ALSO"
+innd(8),
+newslog(5),
+news.daily(8),
+nnrpd(8).
--- /dev/null
+.TH SEND-UUCP 8
+.SH NAME
+send-nntp, send-ihave \- send Usenet articles to remote site
+.SH SYNOPSIS
+.B send-nntp
+[
+.B \-d
+]
+.B sitename:hostname | sitename
+[
+.B sitename:hostname | sitename ..
+]
+.PP
+.B send-ihave
+[
+.B \-d
+]
+.B sitename:hostname | sitename
+[
+.B sitename:hostname | sitename ..
+]
+.SH DESCRIPTION
+The send-* utilities are scripts that process the batch files written
+by
+.IR innd (8)
+to send Usenet articles to a remote NNTP site.
+.PP
+The sites to be fed may be specified by giving
+.I sitename
+.I hostname
+pairs on the command line.
+.PP
+The
+.I sitename
+is the label the site has in the
+.I newsfeeds
+file, the
+.I hostname
+is the real hostname of the remote site, a FQDN (Fully Qualified Domain Name).
+Normally, the
+.I sitename
+and the
+.I hostname
+are the same, and as such don't have to be specified as sitename:hostname
+pairs but just as a sitename.
+.PP
+.I send-nntp
+starts an innxmit to send the articles to the remote site.
+.PP
+.I send-ihave
+encapsulates the articles in an
+.I ihave
+control message and uses
+.I inews
+to send the articles to a
+.I to.sitename
+pseudo-group. Using
+.I send-ihave
+is discouraged, nobody uses it anymore and even the author of this manpage
+is unsure as to how it actually works or used to work.
+.PP
+.I send-*
+expect that the batchfile for a site is named
+.IR <pathoutgoing\ in\ inn.conf>/sitename .
+To prevent batchfile corruption,
+.IR shlock (1)
+is used to ``lock'' these files.
+.SH OPTIONS
+.TP
+.B "\-d"
+The ``\-d'' flag causes
+.I nntpsend
+to send output to stdout rather than the log file
+.IR <pathlog\ in\ inn.conf>/<program-name>.log .
+.SH NOTES
+You should probably not use send-nntp, but
+.IR innfeed ,
+or if that is not possible,
+.IR nntpsend .
+.PP
+The usual flags for a batch file for send-nntp are ``\fBTf,Wfm\fP''.
+.SH "SEE ALSO"
+newsfeeds(5),
+nntpsend(8)
--- /dev/null
+.\" Automatically generated by Pod::Man v1.37, Pod::Parser v1.32
+.\"
+.\" Standard preamble:
+.\" ========================================================================
+.de Sh \" Subsection heading
+.br
+.if t .Sp
+.ne 5
+.PP
+\fB\\$1\fR
+.PP
+..
+.de Sp \" Vertical space (when we can't use .PP)
+.if t .sp .5v
+.if n .sp
+..
+.de Vb \" Begin verbatim text
+.ft CW
+.nf
+.ne \\$1
+..
+.de Ve \" End verbatim text
+.ft R
+.fi
+..
+.\" Set up some character translations and predefined strings. \*(-- will
+.\" give an unbreakable dash, \*(PI will give pi, \*(L" will give a left
+.\" double quote, and \*(R" will give a right double quote. \*(C+ will
+.\" give a nicer C++. Capital omega is used to do unbreakable dashes and
+.\" therefore won't be available. \*(C` and \*(C' expand to `' in nroff,
+.\" nothing in troff, for use with C<>.
+.tr \(*W-
+.ds C+ C\v'-.1v'\h'-1p'\s-2+\h'-1p'+\s0\v'.1v'\h'-1p'
+.ie n \{\
+. ds -- \(*W-
+. ds PI pi
+. if (\n(.H=4u)&(1m=24u) .ds -- \(*W\h'-12u'\(*W\h'-12u'-\" diablo 10 pitch
+. if (\n(.H=4u)&(1m=20u) .ds -- \(*W\h'-12u'\(*W\h'-8u'-\" diablo 12 pitch
+. ds L" ""
+. ds R" ""
+. ds C` ""
+. ds C' ""
+'br\}
+.el\{\
+. ds -- \|\(em\|
+. ds PI \(*p
+. ds L" ``
+. ds R" ''
+'br\}
+.\"
+.\" If the F register is turned on, we'll generate index entries on stderr for
+.\" titles (.TH), headers (.SH), subsections (.Sh), items (.Ip), and index
+.\" entries marked with X<> in POD. Of course, you'll have to process the
+.\" output yourself in some meaningful fashion.
+.if \nF \{\
+. de IX
+. tm Index:\\$1\t\\n%\t"\\$2"
+..
+. nr % 0
+. rr F
+.\}
+.\"
+.\" For nroff, turn off justification. Always turn off hyphenation; it makes
+.\" way too many mistakes in technical documents.
+.hy 0
+.if n .na
+.\"
+.\" Accent mark definitions (@(#)ms.acc 1.5 88/02/08 SMI; from UCB 4.2).
+.\" Fear. Run. Save yourself. No user-serviceable parts.
+. \" fudge factors for nroff and troff
+.if n \{\
+. ds #H 0
+. ds #V .8m
+. ds #F .3m
+. ds #[ \f1
+. ds #] \fP
+.\}
+.if t \{\
+. ds #H ((1u-(\\\\n(.fu%2u))*.13m)
+. ds #V .6m
+. ds #F 0
+. ds #[ \&
+. ds #] \&
+.\}
+. \" simple accents for nroff and troff
+.if n \{\
+. ds ' \&
+. ds ` \&
+. ds ^ \&
+. ds , \&
+. ds ~ ~
+. ds /
+.\}
+.if t \{\
+. ds ' \\k:\h'-(\\n(.wu*8/10-\*(#H)'\'\h"|\\n:u"
+. ds ` \\k:\h'-(\\n(.wu*8/10-\*(#H)'\`\h'|\\n:u'
+. ds ^ \\k:\h'-(\\n(.wu*10/11-\*(#H)'^\h'|\\n:u'
+. ds , \\k:\h'-(\\n(.wu*8/10)',\h'|\\n:u'
+. ds ~ \\k:\h'-(\\n(.wu-\*(#H-.1m)'~\h'|\\n:u'
+. ds / \\k:\h'-(\\n(.wu*8/10-\*(#H)'\z\(sl\h'|\\n:u'
+.\}
+. \" troff and (daisy-wheel) nroff accents
+.ds : \\k:\h'-(\\n(.wu*8/10-\*(#H+.1m+\*(#F)'\v'-\*(#V'\z.\h'.2m+\*(#F'.\h'|\\n:u'\v'\*(#V'
+.ds 8 \h'\*(#H'\(*b\h'-\*(#H'
+.ds o \\k:\h'-(\\n(.wu+\w'\(de'u-\*(#H)/2u'\v'-.3n'\*(#[\z\(de\v'.3n'\h'|\\n:u'\*(#]
+.ds d- \h'\*(#H'\(pd\h'-\w'~'u'\v'-.25m'\f2\(hy\fP\v'.25m'\h'-\*(#H'
+.ds D- D\\k:\h'-\w'D'u'\v'-.11m'\z\(hy\v'.11m'\h'|\\n:u'
+.ds th \*(#[\v'.3m'\s+1I\s-1\v'-.3m'\h'-(\w'I'u*2/3)'\s-1o\s+1\*(#]
+.ds Th \*(#[\s+2I\s-2\h'-\w'I'u*3/5'\v'-.3m'o\v'.3m'\*(#]
+.ds ae a\h'-(\w'a'u*4/10)'e
+.ds Ae A\h'-(\w'A'u*4/10)'E
+. \" corrections for vroff
+.if v .ds ~ \\k:\h'-(\\n(.wu*9/10-\*(#H)'\s-2\u~\d\s+2\h'|\\n:u'
+.if v .ds ^ \\k:\h'-(\\n(.wu*10/11-\*(#H)'\v'-.4m'^\v'.4m'\h'|\\n:u'
+. \" for low resolution devices (crt and lpr)
+.if \n(.H>23 .if \n(.V>19 \
+\{\
+. ds : e
+. ds 8 ss
+. ds o a
+. ds d- d\h'-1'\(ga
+. ds D- D\h'-1'\(hy
+. ds th \o'bp'
+. ds Th \o'LP'
+. ds ae ae
+. ds Ae AE
+.\}
+.rm #[ #] #H #V #F C
+.\" ========================================================================
+.\"
+.IX Title "SEND-UUCP 8"
+.TH SEND-UUCP 8 "2008-04-06" "INN 2.4.4" "InterNetNews Documentation"
+.SH "NAME"
+send\-uucp \- Send Usenet articles via UUCP
+.SH "SYNOPSIS"
+.IX Header "SYNOPSIS"
+\&\fBsend-uucp\fR [\fI\s-1SITE\s0\fR ...]
+.SH "DESCRIPTION"
+.IX Header "DESCRIPTION"
+The \fBsend-uucp\fR program processes batch files written by \fIinnd\fR\|(8) to send
+Usenet articles to \s-1UUCP\s0 sites. It reads a configuration file to control how
+it behaves with various sites. Normally, it's run periodically out of cron
+to put together batches and send them to remote \s-1UUCP\s0 sites.
+.SH "OPTIONS"
+.IX Header "OPTIONS"
+Any arguments provided to the program are interpreted as a list of sites
+specfied in \fIsend\-uucp.cf\fR for which batches should be generated. If no
+arguments are supplied then batches will be generated for all sites listed
+in that configuration file.
+.SH "CONFIGURATION"
+.IX Header "CONFIGURATION"
+The sites to which articles are to be sent must be configured in the
+configuration file \fIsend\-uucp.cf\fR. Each site is specified with a line of
+the form:
+.PP
+.Vb 1
+\& site[:host[:funnel]] [compressor [maxsize [batchtime]]]
+.Ve
+.IP "\fIsite\fR" 4
+.IX Item "site"
+The news site name being configured. This must match a site name
+from \fInewsfeeds\fR\|(5).
+.IP "\fIhost\fR" 4
+.IX Item "host"
+The \s-1UUCP\s0 host name to which batches should be sent for this site.
+If omitted, the news site name will be used as the \s-1UUCP\s0 host name.
+.IP "\fIfunnel\fR" 4
+.IX Item "funnel"
+In the case of a site configured as a funnel, \fBsend-uucp\fR needs to flush
+the channel (or exploder) being used as the target of the funnel instead of
+flushing the site. This is the way to tell \fBsend-uucp\fR the name of the
+channel or exploder to flush for this site. If not specified, default to
+flushing the site.
+.IP "\fIcompressor\fR" 4
+.IX Item "compressor"
+The compression method to use for batches. This should be one of compress,
+gzip or none. Arguments for the compression command may be specified by
+using \f(CW\*(C`_\*(C'\fR instead of spaces. For example, \f(CW\*(C`gzip_\-9\*(C'\fR. The default value is
+\&\f(CW\*(C`compress\*(C'\fR.
+.IP "\fImaxsize\fR" 4
+.IX Item "maxsize"
+The maximum size of a single batch before compression. The default value is
+500,000 bytes.
+.IP "\fIbatchtime\fR" 4
+.IX Item "batchtime"
+A comma separated list of hours during which batches should be generated for
+a given site. When \fBsend-uucp\fR runs, a site will only be processed if the
+current hour matches one of the hours in \fIbatchtime\fR. The default is no
+limitation on when to generate batches.
+.PP
+Fields are seperated by spaces and only the site name needs to be specified,
+with defaults being used for unspecified values. If the first character on
+a line is a \f(CW\*(C`#\*(C'\fR then the rest of the line is ignored.
+.SH "EXAMPLE"
+.IX Header "EXAMPLE"
+Here is an example send\-uucp.cf configuration file:
+.PP
+.Vb 8
+\& zoetermeer gzip 1048576 5,18,22
+\& hoofddorp gzip 1048576 5,18,22
+\& pa3ebv gzip 1048576 5,18,22
+\& drinkel gzip 1048576 5,6,18,20,22,0,2
+\& manhole compress 1048576 5,18,22
+\& owl compress 1048576
+\& able
+\& pern::MYFUNNEL!
+.Ve
+.PP
+This defines eight \s-1UUCP\s0 sites. The first four use gzip compression and the
+last three use compress. The first six use a batch size of 1MB, and the
+last site (able) uses the default of 500,000 bytes. The zoetermeer,
+hoofddorp, pa3ebv, and manhole sites will only have batches generated for
+them during the hours of 05:00, 18:00, and 22:00, and the drinkel site will
+only have batches generated during those hours and 20:00, 00:00, and 02:00.
+There are no restrictions on when batches will be generated for owl or able.
+.PP
+The pern site is configured as a funnel into \f(CW\*(C`MYFUNNEL!\*(C'\fR. \fBsend-uucp\fR will
+issue \f(CW\*(C`ctlinnd flush MYFUNNEL!\*(C'\fR instead of \f(CW\*(C`ctlinnd flush pern\*(C'\fR.
+.SH "FILES"
+.IX Header "FILES"
+.IP "\fIpathetc\fR/send\-uucp.cf" 4
+.IX Item "pathetc/send-uucp.cf"
+Configuration file specifying a list of sites to be processed.
+.SH "NOTES"
+.IX Header "NOTES"
+The usual flags used for a \s-1UUCP\s0 feed in the \fInewsfeeds\fR file are \f(CW\*(C`Tf,Wfb\*(C'\fR.
+.SH "SEE ALSO"
+.IX Header "SEE ALSO"
+\&\fIinnd\fR\|(8), \fInewsfeeds\fR\|(5), \fIuucp\fR\|(8)
+.SH "AUTHOR"
+.IX Header "AUTHOR"
+This program was originally written by Edvard Tuinder <ed@elm.net> and then
+maintained and extended by Miquel van Smoorenburg <miquels@cistron.nl>.
+Marco d'Itri <md@linux.it> cleaned up the code for inclusion in \s-1INN\s0. This
+manual page was written by Mark Brown <broonie@sirena.org.uk>.
--- /dev/null
+.\" Automatically generated by Pod::Man v1.37, Pod::Parser v1.32
+.\"
+.\" Standard preamble:
+.\" ========================================================================
+.de Sh \" Subsection heading
+.br
+.if t .Sp
+.ne 5
+.PP
+\fB\\$1\fR
+.PP
+..
+.de Sp \" Vertical space (when we can't use .PP)
+.if t .sp .5v
+.if n .sp
+..
+.de Vb \" Begin verbatim text
+.ft CW
+.nf
+.ne \\$1
+..
+.de Ve \" End verbatim text
+.ft R
+.fi
+..
+.\" Set up some character translations and predefined strings. \*(-- will
+.\" give an unbreakable dash, \*(PI will give pi, \*(L" will give a left
+.\" double quote, and \*(R" will give a right double quote. \*(C+ will
+.\" give a nicer C++. Capital omega is used to do unbreakable dashes and
+.\" therefore won't be available. \*(C` and \*(C' expand to `' in nroff,
+.\" nothing in troff, for use with C<>.
+.tr \(*W-
+.ds C+ C\v'-.1v'\h'-1p'\s-2+\h'-1p'+\s0\v'.1v'\h'-1p'
+.ie n \{\
+. ds -- \(*W-
+. ds PI pi
+. if (\n(.H=4u)&(1m=24u) .ds -- \(*W\h'-12u'\(*W\h'-12u'-\" diablo 10 pitch
+. if (\n(.H=4u)&(1m=20u) .ds -- \(*W\h'-12u'\(*W\h'-8u'-\" diablo 12 pitch
+. ds L" ""
+. ds R" ""
+. ds C` ""
+. ds C' ""
+'br\}
+.el\{\
+. ds -- \|\(em\|
+. ds PI \(*p
+. ds L" ``
+. ds R" ''
+'br\}
+.\"
+.\" If the F register is turned on, we'll generate index entries on stderr for
+.\" titles (.TH), headers (.SH), subsections (.Sh), items (.Ip), and index
+.\" entries marked with X<> in POD. Of course, you'll have to process the
+.\" output yourself in some meaningful fashion.
+.if \nF \{\
+. de IX
+. tm Index:\\$1\t\\n%\t"\\$2"
+..
+. nr % 0
+. rr F
+.\}
+.\"
+.\" For nroff, turn off justification. Always turn off hyphenation; it makes
+.\" way too many mistakes in technical documents.
+.hy 0
+.if n .na
+.\"
+.\" Accent mark definitions (@(#)ms.acc 1.5 88/02/08 SMI; from UCB 4.2).
+.\" Fear. Run. Save yourself. No user-serviceable parts.
+. \" fudge factors for nroff and troff
+.if n \{\
+. ds #H 0
+. ds #V .8m
+. ds #F .3m
+. ds #[ \f1
+. ds #] \fP
+.\}
+.if t \{\
+. ds #H ((1u-(\\\\n(.fu%2u))*.13m)
+. ds #V .6m
+. ds #F 0
+. ds #[ \&
+. ds #] \&
+.\}
+. \" simple accents for nroff and troff
+.if n \{\
+. ds ' \&
+. ds ` \&
+. ds ^ \&
+. ds , \&
+. ds ~ ~
+. ds /
+.\}
+.if t \{\
+. ds ' \\k:\h'-(\\n(.wu*8/10-\*(#H)'\'\h"|\\n:u"
+. ds ` \\k:\h'-(\\n(.wu*8/10-\*(#H)'\`\h'|\\n:u'
+. ds ^ \\k:\h'-(\\n(.wu*10/11-\*(#H)'^\h'|\\n:u'
+. ds , \\k:\h'-(\\n(.wu*8/10)',\h'|\\n:u'
+. ds ~ \\k:\h'-(\\n(.wu-\*(#H-.1m)'~\h'|\\n:u'
+. ds / \\k:\h'-(\\n(.wu*8/10-\*(#H)'\z\(sl\h'|\\n:u'
+.\}
+. \" troff and (daisy-wheel) nroff accents
+.ds : \\k:\h'-(\\n(.wu*8/10-\*(#H+.1m+\*(#F)'\v'-\*(#V'\z.\h'.2m+\*(#F'.\h'|\\n:u'\v'\*(#V'
+.ds 8 \h'\*(#H'\(*b\h'-\*(#H'
+.ds o \\k:\h'-(\\n(.wu+\w'\(de'u-\*(#H)/2u'\v'-.3n'\*(#[\z\(de\v'.3n'\h'|\\n:u'\*(#]
+.ds d- \h'\*(#H'\(pd\h'-\w'~'u'\v'-.25m'\f2\(hy\fP\v'.25m'\h'-\*(#H'
+.ds D- D\\k:\h'-\w'D'u'\v'-.11m'\z\(hy\v'.11m'\h'|\\n:u'
+.ds th \*(#[\v'.3m'\s+1I\s-1\v'-.3m'\h'-(\w'I'u*2/3)'\s-1o\s+1\*(#]
+.ds Th \*(#[\s+2I\s-2\h'-\w'I'u*3/5'\v'-.3m'o\v'.3m'\*(#]
+.ds ae a\h'-(\w'a'u*4/10)'e
+.ds Ae A\h'-(\w'A'u*4/10)'E
+. \" corrections for vroff
+.if v .ds ~ \\k:\h'-(\\n(.wu*9/10-\*(#H)'\s-2\u~\d\s+2\h'|\\n:u'
+.if v .ds ^ \\k:\h'-(\\n(.wu*10/11-\*(#H)'\v'-.4m'^\v'.4m'\h'|\\n:u'
+. \" for low resolution devices (crt and lpr)
+.if \n(.H>23 .if \n(.V>19 \
+\{\
+. ds : e
+. ds 8 ss
+. ds o a
+. ds d- d\h'-1'\(ga
+. ds D- D\h'-1'\(hy
+. ds th \o'bp'
+. ds Th \o'LP'
+. ds ae ae
+. ds Ae AE
+.\}
+.rm #[ #] #H #V #F C
+.\" ========================================================================
+.\"
+.IX Title "SENDINPATHS 8"
+.TH SENDINPATHS 8 "2008-04-06" "INN 2.4.5" "InterNetNews Documentation"
+.SH "NAME"
+sendinpaths \- Send Usenet Path statistics via e\-mail
+.SH "SYNOPSIS"
+.IX Header "SYNOPSIS"
+\&\fBsendinpaths\fR [\fB\-n\fR]
+.SH "DESCRIPTION"
+.IX Header "DESCRIPTION"
+\&\fBsendinpaths\fR checks \fIpathlog\fR/path for \fBninpaths\fR dump files, finds
+dump files generated in the past 30 days, makes sure they are valid by
+running \fBninpaths\fR on each one and making sure the exit status is zero,
+and passes them to \fBninpaths\fR to generate a cumulative report. That
+report is mailed to the e\-mail addresses configured at the beginning of
+this script (by default \*(L"pathsurvey@top1000.org\*(R" and
+\&\*(L"top1000@anthologeek.net\*(R").
+.PP
+When finished, \fBsendinpaths\fR deletes all dump files in \fIpathlog\fR/path
+that are older than 14 days (configurable at the beginning of the script).
+.PP
+For more information on how to set up \fBninpaths\fR, see \fIninpaths\fR\|(8).
+.SH "OPTIONS"
+.IX Header "OPTIONS"
+.IP "\fB\-n\fR" 4
+.IX Item "-n"
+Don't e\-mail the report; instead, just print it to standard output. Don't
+delete old dump files.
+.SH "SEE ALSO"
+.IX Header "SEE ALSO"
+\&\fIninpaths\fR\|(8)
+.SH "HISTORY"
+.IX Header "HISTORY"
+\&\fBsendinpaths\fR was written by Olaf Titz <olaf@bigred.inka.de>.
--- /dev/null
+.\" $Revision: 5794 $
+.TH SHLOCK 1
+.SH NAME
+shlock \- create lock files for use in shell scripts
+.SH SYNOPSIS
+.B shlock
+.BI \-p " pid"
+.BI \-f " name"
+[
+.B \-b
+]
+[
+.B \-u
+]
+[
+.B \-c
+]
+.SH DESCRIPTION
+.I Shlock
+tries to create a lock file named
+.I name
+and write the process ID
+.I pid
+into it.
+If the file already exists,
+.I shlock
+will read the process ID from the file and test to see if the process
+is currently running.
+If the process exists, then the file will not be created.
+.PP
+.I Shlock
+exits with a zero status if it was able to create the lock file, or
+non-zero if the file refers to currently-active process.
+.SH OPTIONS
+.TP
+.B \-b
+Process IDs are normally read and written in ASCII.
+If the ``\-b'' flag is used, then they will be written as a binary
+.IR int .
+For compatibility with other systems, the ``\-u'' flag is accepted as
+a synonym for ``\-b'' since binary locks are used by many UUCP packages.
+.TP
+.B \-c
+If the ``\-c'' flag is used, then
+.I shlock
+will not create a lock file, but will instead use the file to see if
+the lock is held by another program.
+If the lock is valid, the program will exit with a non-zero status; if
+the lock is not valid (i.e., invoking
+.I shlock
+without the flag would have succeeded), then the program will exit
+with a zero status.
+.SH EXAMPLES
+The following example shows how
+.I shlock
+would be used within a shell script:
+.RS
+.nf
+LOCK=<pathrun in inn.conf>/LOCK.send
+trap 'rm -f ${LOCK} ; exit 1' 1 2 3 15
+if shlock -p $$ -f ${LOCK} ; then
+ # Do appropriate work
+else
+ echo Locked by `cat ${LOCK}`
+f\&i
+.fi
+.RE
+.SH BUGS
+.I shlock
+assumes that it will not be used in an environment with multiple
+locks/unlocks in a short time (due to a race condition). That is,
+.I shlock
+is intended for daily or hourly jobs.
+.SH HISTORY
+Written by Rich $alz <rsalz@uunet.uu.net> after a description of HDB UUCP
+locking given by Peter Honeyman.
+.de R$
+This is revision \\$3, dated \\$4.
+..
+.R$ $Id: shlock.1 5794 2002-10-01 23:31:53Z vinocur $
+.SH "SEE ALSO"
+inn.conf(5)
--- /dev/null
+.\" $Revision: 5909 $
+.TH SHRINKFILE 1
+.SH NAME
+shrinkfile \- shrink a file on a line boundary
+.SH SYNOPSIS
+.B shrinkfile
+[
+.B \-n
+]
+[
+.BI \-m " maxsize"
+]
+[
+.BI \-s " size"
+]
+[
+.B \-v
+]
+.I file...
+.SH DESCRIPTION
+The
+.I shrinkfile
+program shrinks files to a given
+.I size
+if the size is larger than
+.IR maxsize ,
+preserving the data at the end of the file.
+Truncation is performed on line boundaries, where a line is a series
+of bytes ending with a newline, ``\en''.
+There is no line length restriction and files may contain any binary data.
+.PP
+Temporary files are created in the
+.I <pathtmp in inn.conf>
+directory.
+The ``TMPDIR'' environment variable may be used to specify a
+different directory.
+.PP
+A newline will be added to any non-empty file that does not end with a newline.
+The maximum file size will not be exceeded by this addition.
+.SH OPTIONS
+.TP
+.B \-s
+By default,
+.I size
+is assumed to be zero and files are truncated to zero bytes.
+By default,
+.I maxsize
+is the same as
+.IR size .
+If
+.I maxsize
+is less than
+.IR size ,
+.I maxsize
+is reset to
+.IR size .
+The ``\fB\-s\fP'' flag may be used to change the truncation size.
+Because the program truncates only on line boundaries, the final size
+may be smaller then the specified truncation size.
+The
+.I size
+and
+.I maxsize
+parameter may end with a ``k'', ``m'', or ``g'', indicating
+kilobyte (1024), megabyte (1048576) or gigabyte (1073741824) lengths.
+Uppercase letters are also allowed.
+The maximum file size is 2147483647 bytes.
+.TP
+.B \-v
+If the ``\fB\-v\fP'' flag is used, then
+.I shrinkfile
+will print a status line if a file was shrunk.
+.TP
+.B \-n
+If the ``\fB\-n\fP'' flag is used, then
+.I shrinkfile
+will exit 0 if any file is larger than
+.I maxsize
+and exit 1 otherwise.
+No files will be altered.
+.SH EXAMPLES
+.PP
+Example usage:
+.sp 1
+.RS
+.nf
+shrinkfile -s 4m curds
+shrinkfile -s 1g -v whey
+shrinkfile -s 500k -m 4m -v curds whey
+if shrinkfile -n -s 100m whey; then echo whey is way too big; fi
+.fi
+.RE
+.PP
+.SH HISTORY
+Written by Landon Curt Noll <chongo@toad.com> and Rich $alz
+<rsalz@uunet.uu.net> for InterNetNews.
+.de R$
+This is revision \\$3, dated \\$4.
+..
+.SH "SEE ALSO"
+inn.conf(5)
--- /dev/null
+.\" Automatically generated by Pod::Man v1.37, Pod::Parser v1.32
+.\"
+.\" Standard preamble:
+.\" ========================================================================
+.de Sh \" Subsection heading
+.br
+.if t .Sp
+.ne 5
+.PP
+\fB\\$1\fR
+.PP
+..
+.de Sp \" Vertical space (when we can't use .PP)
+.if t .sp .5v
+.if n .sp
+..
+.de Vb \" Begin verbatim text
+.ft CW
+.nf
+.ne \\$1
+..
+.de Ve \" End verbatim text
+.ft R
+.fi
+..
+.\" Set up some character translations and predefined strings. \*(-- will
+.\" give an unbreakable dash, \*(PI will give pi, \*(L" will give a left
+.\" double quote, and \*(R" will give a right double quote. \*(C+ will
+.\" give a nicer C++. Capital omega is used to do unbreakable dashes and
+.\" therefore won't be available. \*(C` and \*(C' expand to `' in nroff,
+.\" nothing in troff, for use with C<>.
+.tr \(*W-
+.ds C+ C\v'-.1v'\h'-1p'\s-2+\h'-1p'+\s0\v'.1v'\h'-1p'
+.ie n \{\
+. ds -- \(*W-
+. ds PI pi
+. if (\n(.H=4u)&(1m=24u) .ds -- \(*W\h'-12u'\(*W\h'-12u'-\" diablo 10 pitch
+. if (\n(.H=4u)&(1m=20u) .ds -- \(*W\h'-12u'\(*W\h'-8u'-\" diablo 12 pitch
+. ds L" ""
+. ds R" ""
+. ds C` ""
+. ds C' ""
+'br\}
+.el\{\
+. ds -- \|\(em\|
+. ds PI \(*p
+. ds L" ``
+. ds R" ''
+'br\}
+.\"
+.\" If the F register is turned on, we'll generate index entries on stderr for
+.\" titles (.TH), headers (.SH), subsections (.Sh), items (.Ip), and index
+.\" entries marked with X<> in POD. Of course, you'll have to process the
+.\" output yourself in some meaningful fashion.
+.if \nF \{\
+. de IX
+. tm Index:\\$1\t\\n%\t"\\$2"
+..
+. nr % 0
+. rr F
+.\}
+.\"
+.\" For nroff, turn off justification. Always turn off hyphenation; it makes
+.\" way too many mistakes in technical documents.
+.hy 0
+.if n .na
+.\"
+.\" Accent mark definitions (@(#)ms.acc 1.5 88/02/08 SMI; from UCB 4.2).
+.\" Fear. Run. Save yourself. No user-serviceable parts.
+. \" fudge factors for nroff and troff
+.if n \{\
+. ds #H 0
+. ds #V .8m
+. ds #F .3m
+. ds #[ \f1
+. ds #] \fP
+.\}
+.if t \{\
+. ds #H ((1u-(\\\\n(.fu%2u))*.13m)
+. ds #V .6m
+. ds #F 0
+. ds #[ \&
+. ds #] \&
+.\}
+. \" simple accents for nroff and troff
+.if n \{\
+. ds ' \&
+. ds ` \&
+. ds ^ \&
+. ds , \&
+. ds ~ ~
+. ds /
+.\}
+.if t \{\
+. ds ' \\k:\h'-(\\n(.wu*8/10-\*(#H)'\'\h"|\\n:u"
+. ds ` \\k:\h'-(\\n(.wu*8/10-\*(#H)'\`\h'|\\n:u'
+. ds ^ \\k:\h'-(\\n(.wu*10/11-\*(#H)'^\h'|\\n:u'
+. ds , \\k:\h'-(\\n(.wu*8/10)',\h'|\\n:u'
+. ds ~ \\k:\h'-(\\n(.wu-\*(#H-.1m)'~\h'|\\n:u'
+. ds / \\k:\h'-(\\n(.wu*8/10-\*(#H)'\z\(sl\h'|\\n:u'
+.\}
+. \" troff and (daisy-wheel) nroff accents
+.ds : \\k:\h'-(\\n(.wu*8/10-\*(#H+.1m+\*(#F)'\v'-\*(#V'\z.\h'.2m+\*(#F'.\h'|\\n:u'\v'\*(#V'
+.ds 8 \h'\*(#H'\(*b\h'-\*(#H'
+.ds o \\k:\h'-(\\n(.wu+\w'\(de'u-\*(#H)/2u'\v'-.3n'\*(#[\z\(de\v'.3n'\h'|\\n:u'\*(#]
+.ds d- \h'\*(#H'\(pd\h'-\w'~'u'\v'-.25m'\f2\(hy\fP\v'.25m'\h'-\*(#H'
+.ds D- D\\k:\h'-\w'D'u'\v'-.11m'\z\(hy\v'.11m'\h'|\\n:u'
+.ds th \*(#[\v'.3m'\s+1I\s-1\v'-.3m'\h'-(\w'I'u*2/3)'\s-1o\s+1\*(#]
+.ds Th \*(#[\s+2I\s-2\h'-\w'I'u*3/5'\v'-.3m'o\v'.3m'\*(#]
+.ds ae a\h'-(\w'a'u*4/10)'e
+.ds Ae A\h'-(\w'A'u*4/10)'E
+. \" corrections for vroff
+.if v .ds ~ \\k:\h'-(\\n(.wu*9/10-\*(#H)'\s-2\u~\d\s+2\h'|\\n:u'
+.if v .ds ^ \\k:\h'-(\\n(.wu*10/11-\*(#H)'\v'-.4m'^\v'.4m'\h'|\\n:u'
+. \" for low resolution devices (crt and lpr)
+.if \n(.H>23 .if \n(.V>19 \
+\{\
+. ds : e
+. ds 8 ss
+. ds o a
+. ds d- d\h'-1'\(ga
+. ds D- D\h'-1'\(hy
+. ds th \o'bp'
+. ds Th \o'LP'
+. ds ae ae
+. ds Ae AE
+.\}
+.rm #[ #] #H #V #F C
+.\" ========================================================================
+.\"
+.IX Title "SIMPLEFTP 1"
+.TH SIMPLEFTP 1 "2008-04-06" "INN 2.4.5" "InterNetNews Documentation"
+.SH "NAME"
+simpleftp \- Rudimentary FTP client
+.SH "SYNOPSIS"
+.IX Header "SYNOPSIS"
+\&\fBsimpleftp\fR \fIurl\fR [...]
+.SH "DESCRIPTION"
+.IX Header "DESCRIPTION"
+\&\fBsimpleftp\fR is a Perl script that provides basic support for
+fetching files with \s-1FTP\s0 in a batch oriented fashion. It takes one or more
+\&\s-1FTP\s0 URLs on the command line. The file(s) will be retrieved from the
+remote server and placed in the current directory with the same basename
+as on the remote; e.g., <ftp://ftp.isc.org/pub/usenet/CONFIG/active.gz>
+is stored as \fIactive.gz\fR in the current directory.
+.PP
+The script properly understands usernames, passwords and ports specified
+as follows:
+.PP
+.Vb 1
+\& ftp://user:password@host:port/path/file
+.Ve
+.SH "BUGS"
+.IX Header "BUGS"
+\&\fBsimpleftp\fR is an extremely poor substitute for more complete programs
+like the freely available \fBwget\fR or \fBncftp\fR utilities. It was written
+only to provide elementary support in \s-1INN\s0 for non-interactive fetching of
+the files in <ftp://ftp.isc.org/pub/pgpcontrol/> or
+<ftp://ftp.isc.org/pub/usenet/CONFIG/> without requiring
+administrators to install yet another package. Its shortcomings as a
+general purpose program are too numerous to mention, but one that stands
+out is that downloaded files by \fBsimpleftp\fR override existing files
+with the same name in the local directory.
+.SH "HISTORY"
+.IX Header "HISTORY"
+Tossed off by David C Lawrence <tale@isc.org> for InterNetNews.
+Rewritten to use Net::FTP by Julien Elie <julien@trigofacile.com>.
+.PP
+$Id: simpleftp.1 7880 2008-06-16 20:37:13Z iulius $
+.SH "SEE ALSO"
+.IX Header "SEE ALSO"
+\&\fIactsync\fR\|(8).
--- /dev/null
+.\" Automatically generated by Pod::Man v1.37, Pod::Parser v1.32
+.\"
+.\" Standard preamble:
+.\" ========================================================================
+.de Sh \" Subsection heading
+.br
+.if t .Sp
+.ne 5
+.PP
+\fB\\$1\fR
+.PP
+..
+.de Sp \" Vertical space (when we can't use .PP)
+.if t .sp .5v
+.if n .sp
+..
+.de Vb \" Begin verbatim text
+.ft CW
+.nf
+.ne \\$1
+..
+.de Ve \" End verbatim text
+.ft R
+.fi
+..
+.\" Set up some character translations and predefined strings. \*(-- will
+.\" give an unbreakable dash, \*(PI will give pi, \*(L" will give a left
+.\" double quote, and \*(R" will give a right double quote. \*(C+ will
+.\" give a nicer C++. Capital omega is used to do unbreakable dashes and
+.\" therefore won't be available. \*(C` and \*(C' expand to `' in nroff,
+.\" nothing in troff, for use with C<>.
+.tr \(*W-
+.ds C+ C\v'-.1v'\h'-1p'\s-2+\h'-1p'+\s0\v'.1v'\h'-1p'
+.ie n \{\
+. ds -- \(*W-
+. ds PI pi
+. if (\n(.H=4u)&(1m=24u) .ds -- \(*W\h'-12u'\(*W\h'-12u'-\" diablo 10 pitch
+. if (\n(.H=4u)&(1m=20u) .ds -- \(*W\h'-12u'\(*W\h'-8u'-\" diablo 12 pitch
+. ds L" ""
+. ds R" ""
+. ds C` ""
+. ds C' ""
+'br\}
+.el\{\
+. ds -- \|\(em\|
+. ds PI \(*p
+. ds L" ``
+. ds R" ''
+'br\}
+.\"
+.\" If the F register is turned on, we'll generate index entries on stderr for
+.\" titles (.TH), headers (.SH), subsections (.Sh), items (.Ip), and index
+.\" entries marked with X<> in POD. Of course, you'll have to process the
+.\" output yourself in some meaningful fashion.
+.if \nF \{\
+. de IX
+. tm Index:\\$1\t\\n%\t"\\$2"
+..
+. nr % 0
+. rr F
+.\}
+.\"
+.\" For nroff, turn off justification. Always turn off hyphenation; it makes
+.\" way too many mistakes in technical documents.
+.hy 0
+.if n .na
+.\"
+.\" Accent mark definitions (@(#)ms.acc 1.5 88/02/08 SMI; from UCB 4.2).
+.\" Fear. Run. Save yourself. No user-serviceable parts.
+. \" fudge factors for nroff and troff
+.if n \{\
+. ds #H 0
+. ds #V .8m
+. ds #F .3m
+. ds #[ \f1
+. ds #] \fP
+.\}
+.if t \{\
+. ds #H ((1u-(\\\\n(.fu%2u))*.13m)
+. ds #V .6m
+. ds #F 0
+. ds #[ \&
+. ds #] \&
+.\}
+. \" simple accents for nroff and troff
+.if n \{\
+. ds ' \&
+. ds ` \&
+. ds ^ \&
+. ds , \&
+. ds ~ ~
+. ds /
+.\}
+.if t \{\
+. ds ' \\k:\h'-(\\n(.wu*8/10-\*(#H)'\'\h"|\\n:u"
+. ds ` \\k:\h'-(\\n(.wu*8/10-\*(#H)'\`\h'|\\n:u'
+. ds ^ \\k:\h'-(\\n(.wu*10/11-\*(#H)'^\h'|\\n:u'
+. ds , \\k:\h'-(\\n(.wu*8/10)',\h'|\\n:u'
+. ds ~ \\k:\h'-(\\n(.wu-\*(#H-.1m)'~\h'|\\n:u'
+. ds / \\k:\h'-(\\n(.wu*8/10-\*(#H)'\z\(sl\h'|\\n:u'
+.\}
+. \" troff and (daisy-wheel) nroff accents
+.ds : \\k:\h'-(\\n(.wu*8/10-\*(#H+.1m+\*(#F)'\v'-\*(#V'\z.\h'.2m+\*(#F'.\h'|\\n:u'\v'\*(#V'
+.ds 8 \h'\*(#H'\(*b\h'-\*(#H'
+.ds o \\k:\h'-(\\n(.wu+\w'\(de'u-\*(#H)/2u'\v'-.3n'\*(#[\z\(de\v'.3n'\h'|\\n:u'\*(#]
+.ds d- \h'\*(#H'\(pd\h'-\w'~'u'\v'-.25m'\f2\(hy\fP\v'.25m'\h'-\*(#H'
+.ds D- D\\k:\h'-\w'D'u'\v'-.11m'\z\(hy\v'.11m'\h'|\\n:u'
+.ds th \*(#[\v'.3m'\s+1I\s-1\v'-.3m'\h'-(\w'I'u*2/3)'\s-1o\s+1\*(#]
+.ds Th \*(#[\s+2I\s-2\h'-\w'I'u*3/5'\v'-.3m'o\v'.3m'\*(#]
+.ds ae a\h'-(\w'a'u*4/10)'e
+.ds Ae A\h'-(\w'A'u*4/10)'E
+. \" corrections for vroff
+.if v .ds ~ \\k:\h'-(\\n(.wu*9/10-\*(#H)'\s-2\u~\d\s+2\h'|\\n:u'
+.if v .ds ^ \\k:\h'-(\\n(.wu*10/11-\*(#H)'\v'-.4m'^\v'.4m'\h'|\\n:u'
+. \" for low resolution devices (crt and lpr)
+.if \n(.H>23 .if \n(.V>19 \
+\{\
+. ds : e
+. ds 8 ss
+. ds o a
+. ds d- d\h'-1'\(ga
+. ds D- D\h'-1'\(hy
+. ds th \o'bp'
+. ds Th \o'LP'
+. ds ae ae
+. ds Ae AE
+.\}
+.rm #[ #] #H #V #F C
+.\" ========================================================================
+.\"
+.IX Title "SM 1"
+.TH SM 1 "2008-04-06" "INN 2.4.5" "InterNetNews Documentation"
+.SH "NAME"
+sm \- Command\-line interface to the INN storage manager
+.SH "SYNOPSIS"
+.IX Header "SYNOPSIS"
+\&\fBsm\fR [\fB\-dHiqRrS\fR] [\fItoken\fR ...]
+.SH "DESCRIPTION"
+.IX Header "DESCRIPTION"
+The \s-1INN\s0 storage manager is the subsystesm that stores and keeps track of
+all of the articles and what storage backend they're in. All stored
+articles are assigned a storage \s-1API\s0 token. \fBsm\fR is a command-line
+interface to that storage manager, primarily used to retrieve articles by
+those tokens but also to perform other operations on the storage
+subsystem.
+.PP
+\&\fItoken\fR is the token of an article (the same thing that's returned by
+\&\fBgrephistory\fR or stored in the history file). It looks something like:
+.PP
+.Vb 1
+\& @0502000005A4000000010000000000000000@
+.Ve
+.PP
+Any number of tokens can be given on the command line. If none are, \fBsm\fR
+reads tokens from standard input, one per line. The default operation is
+to retrieve and write to standard output the corresponding article for
+each token given.
+.SH "OPTIONS"
+.IX Header "OPTIONS"
+.IP "\fB\-d\fR, \fB\-r\fR" 4
+.IX Item "-d, -r"
+Rather than retrieving the specified article, remove the article. This
+will delete the article out of the news spool and it will not subsequently
+be retrievable by any part of \s-1INN\s0. It's equivalent to \f(CW\*(C`ctlinnd cancel\*(C'\fR
+except it takes a storage \s-1API\s0 token instead of a message \s-1ID\s0.
+.IP "\fB\-H\fR" 4
+.IX Item "-H"
+Retrieve only the header of the article rather than the entire article.
+This option cannot be used with \fB\-d\fR, \fB\-r\fR, \fB\-i\fR, or \fB\-S\fR.
+.IP "\fB\-i\fR" 4
+.IX Item "-i"
+Show the newsgroup name and article number associated with the token
+rather than the article itself. Note that for crossposted articles, only
+the first newsgroup and article number to which the article is associated
+will be returned.
+.IP "\fB\-q\fR" 4
+.IX Item "-q"
+Suppress all error messages except usage errors.
+.IP "\fB\-R\fR" 4
+.IX Item "-R"
+Display the raw article. This means that line endings won't be converted
+to native line endings and will be left as \s-1CRLF\s0 sequences, leading periods
+will still be escaped for sending over \s-1NNTP\s0, and the article will end in
+a \s-1CRLF\s0.CRLF sequence.
+.IP "\fB\-S\fR" 4
+.IX Item "-S"
+Write the article to standard output in the format used by rnews spool
+files. Multiple articles can be written in this format, and the resulting
+output can be fed to rnews (on another system, for example) to inject
+those articles into \s-1INN\s0. This option cannot be used with \fB\-d\fR, \fB\-r\fR,
+\&\fB\-H\fR, \fB\-i\fR, or \fB\-R\fR.
+.SH "EXIT STATUS"
+.IX Header "EXIT STATUS"
+If all operations were successful, \fBsm\fR exits with status 0. If an
+operation on any of the provided tokens fails, \fBsm\fR will exit with status
+1, even if the operations on other tokens were successful. In other
+words, if twenty tokens are fed to \f(CW\*(C`sm \-r\*(C'\fR on stdin, 19 articles were
+successfully removed, but the sixth article couldn't be found, \fBsm\fR will
+still exit with status 1.
+.PP
+This means that if you need to be sure whether a particular operation
+succeeded, you should run sm on one token at a time.
+.SH "HISTORY"
+.IX Header "HISTORY"
+Written by Katsuhiro Kondou <kondou@nec.co.jp> for InterNetNews.
+Rewritten in \s-1POD\s0 by Russ Allbery <rra@stanford.edu>.
+.PP
+$Id: sm.1 7880 2008-06-16 20:37:13Z iulius $
+.SH "SEE ALSO"
+.IX Header "SEE ALSO"
+\&\fIctlinnd\fR\|(8), \fIgrephistory\fR\|(1), \fIhistory\fR\|(5), \fIrnews\fR\|(1), \fIstorage.conf\fR\|(5).
--- /dev/null
+.TH STARTINNFEED 8 "Nov 7, 1997"
+.SH NAME
+startinnfeed \- setuid root program to start innfeed
+.SH SYNOPSIS
+.B startinnfeed
+.RB [innfeed-options]
+.sp
+.B startinnfeed
+.RB imapfeed
+.RB [imapfeed-options]
+.SH DESCRIPTION
+.B Startinnfeed
+sets all resources (files opened, memory usage) to unlimited. It then executes
+innfeed(8) with the options specified.
+
+If the first argument to
+.B startinnfeed
+is ``\fIimapfeed\fP'' then
+.B imapfeed
+will be executed (instead of innfeed(8)) with the remaining argument as
+options.
+.SH HISTORY
+Written by James Brister.
+.SH "SEE ALSO"
+.BR innfeed (8)
--- /dev/null
+.\" $Revision: 6124 $
+.TH STORAGE.CONF 5
+.SH NAME
+storage.conf \- configuration file for storage manager
+.SH DESCRIPTION
+The storage manager is a
+unified interface between INN and a variety of different storage method,
+allowing the news administrator to choose between different storage methods
+with different tradeoffs (or even use several at the same time for
+different newsgroups, or articles of different sizes). The rest of INN
+need not care what type of storage method was used for a given article;
+the storage manager will figure this out automatically when that article
+is retrieved via the storage API.
+.PP
+The
+.I <pathetc in inn.conf>/storage.conf
+file contains the rules to be used in assigning
+articles to different storage methods.
+.PP
+The file consists of a series of storage method entries.
+Blank lines and lines beginning with a number sign (``#'') are ignored.
+The maximum number of characters in each line is 255.
+The order of entries in this file is important, see below.
+.PP
+Each entry specifies a storage method and a set of rules. Articles that
+match all of the rules of a storage method entry will be stored using that
+storage method; if an article matches multiple storage method entries,
+the first will be used. Each entry is formatted as follows:
+.RS
+.nf
+
+method <methodname> {
+ class: <storage_class>
+ newsgroups: <wildmat>
+ size: <minsize>[,<maxsize>]
+ expires: <mintime>[,<maxtime>]
+ options: <options>
+ exactmatch: <bool>
+}
+
+.fi
+.RE
+If spaces or tabs are included in a value, that value must be quoted
+with ``"''.
+If either ``#'' or ``"'' are meant to be included verbatim in a value,
+they should be escaped with ``\\''.
+.PP
+<methodname> is the name of a storage method to use for articles that
+match the rules of this entry. The currently available storage methods
+are
+\&``timecaf'', ``timehash'', ``cnfs'', ``tradspool'' and ``trash''.
+See the STORAGE METHODS section below for more details.
+.PP
+The meanings of the keys in each entry are as follows:
+.TP
+.B class
+An identifier for this storage method entry. <storage_class> should be a
+number and should be unique across all of the entries in this file. It's
+used mainly for specifying expiration times by storage class as described in
+.IR expire.ctl (5).
+.TP
+.B newsgroups
+What newsgroups are stored using this storage method. <wildmat> is a
+.IR uwildmat (3)
+pattern that is matched against the newsgroups an article is posted to.
+If ``storeonxref'' in inn.conf is ``true'', this pattern will be matched
+against the newsgroup names in the ``Xref'' header; otherwise, it will be
+matched against newsgroup names in the ``Newsgroups'' header (see
+.IR inn.conf (5)
+for discussion of the differences between these possibilities). Poison
+wildmat expressions (expressions starting with ``@'') are allowed and can
+be used to exclude certain group patterns. ``!'' cannot be used, however.
+The <wildmat> pattern is matched in order. There is no default newsgroups
+pattern; if an entry should match all newsgroups, use an explicit
+\&``newsgroups: *''.
+.TP
+.B size
+A range of article sizes (in bytes) that should be stored using this
+storage method.
+If <maxsize> is ``0'' or not given, the upper size of articles is limited
+only by ``maxartsize'' in
+.IR inn.conf .
+The ``size'' field is optional and may be omitted entirely if you want
+articles of any size (that otherwise fulfill the requirements of this
+storage method entry) to be stored in this storage method.
+.TP
+.B expires
+A range of article expiration times that should be stored using this
+storage method. Be careful; this is less useful than it may appear at
+first. This is based
+.B only
+on the ``Expires'' header of the article, not on any local expiration
+policies or anything in
+.IR expire.ctl !
+If <mintime> is non-zero, then this entry
+.B will not match
+any article without an ``Expires'' header.
+This key is therefore only really useful for assigning articles with
+requested longer expire times to a separate storage method. Articles only
+match if the time until expiration (that is, the amount of time into the
+future that the ``Expires'' header of the article requests that it remain
+around) falls in the interval specified by <mintime> and <maxtime>. The
+format of these parameters is 0d0h0m0s (days, hours, minutes, and
+seconds into the future). If <maxtime> is ``0s'' or is not specified,
+there is no upper bound on expire times falling into this entry (note that
+this key has no effect on when the article will actually be expired, only
+on whether or not the article will be stored using this storage method).
+This field is also optional and may be omitted entirely if all articles
+with or without an ``Expires'' header (that otherwise fulfill the
+requirements of this storage method entry) should be stored according to
+it.
+.TP
+.B options
+This key is for passing special options to storage methods that require
+them (currently only ``cnfs''). See the STORAGE METHODS section below for
+a description of its use.
+.TP
+.B exactmatch
+If this key is set to ``true'', all newsgroups will be examined to see if
+they match newsgroups patterns. (Normally, any nonzero number of matching
+newsgroups is sufficient, provided no newsgroup matches a poison wildmat as
+described above.) This is a boolan value and ``true'', ``yes''
+and ``on'' are usable to enable this key. The case of these values is not
+significant. The default is false.
+.PP
+If an article matches all of the constraints of an entry, it is stored via
+that storage method and is associated with that <storage_class>. This
+file is scanned in order and the first matching entry is used to store the
+article.
+.PP
+If an article doesn't match any entry, either by being posted to a
+newsgroup that doesn't match any of the <wildmat> patterns or by being
+outside the size and expires ranges of all entries whose newsgroups
+pattern it does match, the article is not stored and is rejected by
+.IR innd (8).
+When this happens, the error message
+.RS
+.nf
+
+cant store article: no matching entry in storage.conf
+
+.fi
+.RE
+is logged to syslog. If you want to silently drop articles matching
+certain newsgroup patterns or size or expires ranges, assign them to the
+\&``trash'' storage method rather than having them not match any storage
+method entry.
+.SH STORAGE METHODS
+Currently, there are four storage methods available. Each method has its
+pros and cons; you can choose any mixture of them as is suitable for your
+environment. Note that each method has an attribute ``EXPENSIVESTAT'' which
+indicates whether checking the existence of an article is expensive or not.
+This is used to run
+.IR expireover (8).
+.TP
+.B cnfs
+The ``cnfs'' storage method stores articles in large cyclic buffers (CNFS
+stands for Cyclic News File System). It's by far the fastest of all
+storage methods (except for ``trash''), since it eliminates the overhead
+of dealing with a file system and creating new files. Articles are stored
+in CNFS buffers in arrival order, and when the buffer fills, it wraps
+around to the beginning and stores new articles over top of the oldest
+articles in the buffer. The expire time of articles stored in CNFS
+buffers is therefore entirely determined by how long it takes the buffer
+to wrap around, which depends on how quickly data is being stored in it.
+(This method is therefore said to have self-expire functionality.)
+\&``EXPENSIVESTAT'' is ``false'' for this method.
+CNFS has its own configuration file,
+.IR cycbuff.conf ,
+which describes some subtlties to the basic description given above.
+Storage method entries for the ``cnfs'' storage method must have an
+\&``options'' field specifying the metacycbuff into which articles
+matching that entry should be stored; see
+.IR cycbuff.conf (5)
+for details on metacycbuffs.
+.TP
+.B timecaf
+This method stores multiple articles in one file, whose name is based on
+the article's arrival time and the storage class. The file name will be
+.IR <patharticles\ in\ inn.conf>/timecaf-nn/bb/aacc.CF ,
+where ``nn'' is the hexadecimal value of <storage_class>, ``bb'' and
+\&``aacc'' are hexadecimal components of the arrival time, and ``CF'' is a
+hardcoded extension. (The arrival time, in seconds since the epoch, is
+converted to hexadecimal and interpreted as 0xaabbccdd, with ``aa'',
+``bb'', and ``cc'' used to build the path.) This method does not have
+self-expire functionality (meaning
+.IR expire (8)
+has to run periodically to delete old articles).
+\&``EXPENSIVESTAT'' is ``false'' for this method.
+.TP
+.B timehash
+This method is very similar to ``timecaf'' except that each article is
+stored in a separate file. The name of the file for a given article will
+be
+.IR <patharticles\ in\ inn.conf>/time-nn/bb/cc/yyyy-aadd ,
+where ``nn'' is the hexadecimal value of <storage_class>, ``yyyy'' is a
+hexadecimal sequence number, and ``bb'', ``cc'', and ``aadd'' are
+components of the arrival time in hexadecimal (the arrival time is
+interpreted as documented above under ``timecaf''). This method does not
+have self-expire functionality.
+\&``EXPENSIVESTAT'' is ``true'' for this method.
+.TP
+.B tradspool
+Traditional spool, or ``tradspool'', is the traditional news article
+storage format. Each article is stored in a file named:
+.IR <patharticles\ in\ inn.conf>/news/group/name/nnnnn ,
+where ``news/group/name'' is the name of the newsgroup to which the
+article was posted with each period changed to a slash, and ``nnnnn'' is
+the sequence number of the article in that newsgroup. For crossposted
+articles, the article is linked into each newsgroup to which it is
+crossposted (using either hard or symbolic links). This is the way
+versions of INN prior to 2.0 stored all articles, as well as being the
+article storage format used by C News and earlier news systems.
+This method does not have self-expire functionality.
+\&``EXPENSIVESTAT'' is ``true'' for this method.
+.TP
+.B trash
+This method silently discards all articles stored in it. Its only real
+uses are for testing and for silently discarding articles matching a
+particular storage method entry (for whatever reason). Articles stored in
+this method take up no disk space and can never be retrieved, so this
+method has self-expire functionality of a sort.
+\&``EXPENSIVESTAT'' is ``false'' for this method.
+.SH EXAMPLE
+The following sample storage.conf file would store all articles posted to
+alt.binaries.* in the ``BINARIES'' CNFS metacycbuff, all articles over
+roughly 50 KB in any other hierarchy in the ``LARGE'' CNFS metacycbuff,
+all other articles in alt.* in one timehash class, and all other articles
+in any newsgroups in a second timehash class, except for the internal.*
+hierarchy which is stored in traditional spool format.
+.RS
+.nf
+
+method tradspool {
+ class: 1
+ newsgroups: internal.*
+}
+
+method cnfs {
+ class: 2
+ newsgroups: alt.binaries.*
+ options: BINARIES
+}
+
+method cnfs {
+ class: 3
+ newsgroups: *
+ size: 50000
+ options: LARGE
+}
+
+method timehash {
+ class: 4
+ newsgroups: alt.*
+}
+
+method timehash {
+ class: 5
+ newsgroups: *
+}
+
+.fi
+.RE
+Notice that the last storage method entry will catch everything. This is
+a good habit to get into; make sure that you have at least one catch-all
+entry just in case something you didn't expect falls through the cracks.
+Notice also that the special rule for the internal.* hierarchy is first,
+so it will catch even articles crossposted to alt.binaries.* or over 50 KB
+in size.
+.SH HISTORY
+Written by Katsuhiro Kondou <kondou@nec.co.jp> for InterNetNews.
+.de R$
+This is revision \\$3, dated \\$4.
+..
+.R$ $Id: storage.conf.5 6124 2003-01-14 06:03:29Z rra $
+.SH "SEE ALSO"
+cycbuff.conf(5),
+expire.ctl(5),
+inn.conf(5),
+innd(8),
+newsfeeds(5),
+uwildmat(3).
--- /dev/null
+.\" Automatically generated by Pod::Man v1.37, Pod::Parser v1.32
+.\"
+.\" Standard preamble:
+.\" ========================================================================
+.de Sh \" Subsection heading
+.br
+.if t .Sp
+.ne 5
+.PP
+\fB\\$1\fR
+.PP
+..
+.de Sp \" Vertical space (when we can't use .PP)
+.if t .sp .5v
+.if n .sp
+..
+.de Vb \" Begin verbatim text
+.ft CW
+.nf
+.ne \\$1
+..
+.de Ve \" End verbatim text
+.ft R
+.fi
+..
+.\" Set up some character translations and predefined strings. \*(-- will
+.\" give an unbreakable dash, \*(PI will give pi, \*(L" will give a left
+.\" double quote, and \*(R" will give a right double quote. \*(C+ will
+.\" give a nicer C++. Capital omega is used to do unbreakable dashes and
+.\" therefore won't be available. \*(C` and \*(C' expand to `' in nroff,
+.\" nothing in troff, for use with C<>.
+.tr \(*W-
+.ds C+ C\v'-.1v'\h'-1p'\s-2+\h'-1p'+\s0\v'.1v'\h'-1p'
+.ie n \{\
+. ds -- \(*W-
+. ds PI pi
+. if (\n(.H=4u)&(1m=24u) .ds -- \(*W\h'-12u'\(*W\h'-12u'-\" diablo 10 pitch
+. if (\n(.H=4u)&(1m=20u) .ds -- \(*W\h'-12u'\(*W\h'-8u'-\" diablo 12 pitch
+. ds L" ""
+. ds R" ""
+. ds C` ""
+. ds C' ""
+'br\}
+.el\{\
+. ds -- \|\(em\|
+. ds PI \(*p
+. ds L" ``
+. ds R" ''
+'br\}
+.\"
+.\" If the F register is turned on, we'll generate index entries on stderr for
+.\" titles (.TH), headers (.SH), subsections (.Sh), items (.Ip), and index
+.\" entries marked with X<> in POD. Of course, you'll have to process the
+.\" output yourself in some meaningful fashion.
+.if \nF \{\
+. de IX
+. tm Index:\\$1\t\\n%\t"\\$2"
+..
+. nr % 0
+. rr F
+.\}
+.\"
+.\" For nroff, turn off justification. Always turn off hyphenation; it makes
+.\" way too many mistakes in technical documents.
+.hy 0
+.if n .na
+.\"
+.\" Accent mark definitions (@(#)ms.acc 1.5 88/02/08 SMI; from UCB 4.2).
+.\" Fear. Run. Save yourself. No user-serviceable parts.
+. \" fudge factors for nroff and troff
+.if n \{\
+. ds #H 0
+. ds #V .8m
+. ds #F .3m
+. ds #[ \f1
+. ds #] \fP
+.\}
+.if t \{\
+. ds #H ((1u-(\\\\n(.fu%2u))*.13m)
+. ds #V .6m
+. ds #F 0
+. ds #[ \&
+. ds #] \&
+.\}
+. \" simple accents for nroff and troff
+.if n \{\
+. ds ' \&
+. ds ` \&
+. ds ^ \&
+. ds , \&
+. ds ~ ~
+. ds /
+.\}
+.if t \{\
+. ds ' \\k:\h'-(\\n(.wu*8/10-\*(#H)'\'\h"|\\n:u"
+. ds ` \\k:\h'-(\\n(.wu*8/10-\*(#H)'\`\h'|\\n:u'
+. ds ^ \\k:\h'-(\\n(.wu*10/11-\*(#H)'^\h'|\\n:u'
+. ds , \\k:\h'-(\\n(.wu*8/10)',\h'|\\n:u'
+. ds ~ \\k:\h'-(\\n(.wu-\*(#H-.1m)'~\h'|\\n:u'
+. ds / \\k:\h'-(\\n(.wu*8/10-\*(#H)'\z\(sl\h'|\\n:u'
+.\}
+. \" troff and (daisy-wheel) nroff accents
+.ds : \\k:\h'-(\\n(.wu*8/10-\*(#H+.1m+\*(#F)'\v'-\*(#V'\z.\h'.2m+\*(#F'.\h'|\\n:u'\v'\*(#V'
+.ds 8 \h'\*(#H'\(*b\h'-\*(#H'
+.ds o \\k:\h'-(\\n(.wu+\w'\(de'u-\*(#H)/2u'\v'-.3n'\*(#[\z\(de\v'.3n'\h'|\\n:u'\*(#]
+.ds d- \h'\*(#H'\(pd\h'-\w'~'u'\v'-.25m'\f2\(hy\fP\v'.25m'\h'-\*(#H'
+.ds D- D\\k:\h'-\w'D'u'\v'-.11m'\z\(hy\v'.11m'\h'|\\n:u'
+.ds th \*(#[\v'.3m'\s+1I\s-1\v'-.3m'\h'-(\w'I'u*2/3)'\s-1o\s+1\*(#]
+.ds Th \*(#[\s+2I\s-2\h'-\w'I'u*3/5'\v'-.3m'o\v'.3m'\*(#]
+.ds ae a\h'-(\w'a'u*4/10)'e
+.ds Ae A\h'-(\w'A'u*4/10)'E
+. \" corrections for vroff
+.if v .ds ~ \\k:\h'-(\\n(.wu*9/10-\*(#H)'\s-2\u~\d\s+2\h'|\\n:u'
+.if v .ds ^ \\k:\h'-(\\n(.wu*10/11-\*(#H)'\v'-.4m'^\v'.4m'\h'|\\n:u'
+. \" for low resolution devices (crt and lpr)
+.if \n(.H>23 .if \n(.V>19 \
+\{\
+. ds : e
+. ds 8 ss
+. ds o a
+. ds d- d\h'-1'\(ga
+. ds D- D\h'-1'\(hy
+. ds th \o'bp'
+. ds Th \o'LP'
+. ds ae ae
+. ds Ae AE
+.\}
+.rm #[ #] #H #V #F C
+.\" ========================================================================
+.\"
+.IX Title "SUBSCRIPTIONS 5"
+.TH SUBSCRIPTIONS 5 "2008-04-06" "INN 2.4.5" "InterNetNews Documentation"
+.SH "NAME"
+subscriptions \- Default recommended subscriptions
+.SH "DESCRIPTION"
+.IX Header "DESCRIPTION"
+The \fIpathetc\fR/\fIsubscriptions\fR file contains a list of newsgroups that is
+returned by the \s-1NNTP\s0 command \s-1LIST\s0 \s-1SUBSCRIPTIONS\s0.
+.PP
+Clients that support this command and send it the first time they connect
+to a new news server use the returned list to initialize the list of
+subscribed newsgroups. The \fIsubscriptions\fR file therefore should contain
+groups intented for new users, for testing, or that contain FAQs and other
+useful information for first-time Usenet users.
+.PP
+The syntax of the \fIsubscriptions\fR file is trivial; it is a simple list of
+newsgroup names, one per line. The order of newsgroups may be
+significant; the news reading client may present the groups in that order
+to the user.
+.SH "EXAMPLE"
+.IX Header "EXAMPLE"
+A typical \fIsubscriptions\fR file may look like:
+.PP
+.Vb 9
+\& news.announce.newusers
+\& news.newusers.questions
+\& local.test
+\& local.general
+\& local.talk
+\& misc.test
+\& misc.test.moderated
+\& news.answers
+\& news.announce.newgroups
+.Ve
+.PP
+This gives the client the FAQs and question newsgroup for new users first,
+then a local newsgroup for testing and various commonly-read local
+discussion groups, followed by the world-wide test groups, all the FAQs,
+and announcements of new world-wide newsgroups. If there is a local new
+users group, one might want to list it first.
+.SH "HISTORY"
+.IX Header "HISTORY"
+Written by Bettina Fink <laura@hydrophil.de> for InterNetNews.
+.SH "SEE ALSO"
+.IX Header "SEE ALSO"
+\&\fInnrpd\fR\|(8).
--- /dev/null
+.TH TALLY.CONTROL 8
+.SH NAME
+tally.control \- keep track of newsgroup creations and deletions.
+.SH SYNOPSIS
+tally.control
+.SH DECSRIPTION
+tally.control is normally invoked by
+.IR scanlogs (8).
+It
+reads its standard input, which should be the
+.I newgroup.log
+and
+.I rmgroup.log
+log files.
+It updates the cumulative list of newsgroup creations and deletions,
+.IR control.log .
+.SH HISTORY
+Written by Landon Curt Noll <chongo@toad.com> and Rich $alz
+<rsalz@uunet.uu.net> for InterNetNews.
+.de R$
+This is revision \\$3, dated \\$4.
+..
+.R$ $Id: tally.control.8 5909 2002-12-03 05:17:18Z vinocur $
+.SH "SEE ALSO"
+newslog(5),
+news.daily(8),
+scanlogs(8),
+tally.unwanted(8),
+writelog(8).
--- /dev/null
+.\" Automatically generated by Pod::Man v1.37, Pod::Parser v1.32
+.\"
+.\" Standard preamble:
+.\" ========================================================================
+.de Sh \" Subsection heading
+.br
+.if t .Sp
+.ne 5
+.PP
+\fB\\$1\fR
+.PP
+..
+.de Sp \" Vertical space (when we can't use .PP)
+.if t .sp .5v
+.if n .sp
+..
+.de Vb \" Begin verbatim text
+.ft CW
+.nf
+.ne \\$1
+..
+.de Ve \" End verbatim text
+.ft R
+.fi
+..
+.\" Set up some character translations and predefined strings. \*(-- will
+.\" give an unbreakable dash, \*(PI will give pi, \*(L" will give a left
+.\" double quote, and \*(R" will give a right double quote. \*(C+ will
+.\" give a nicer C++. Capital omega is used to do unbreakable dashes and
+.\" therefore won't be available. \*(C` and \*(C' expand to `' in nroff,
+.\" nothing in troff, for use with C<>.
+.tr \(*W-
+.ds C+ C\v'-.1v'\h'-1p'\s-2+\h'-1p'+\s0\v'.1v'\h'-1p'
+.ie n \{\
+. ds -- \(*W-
+. ds PI pi
+. if (\n(.H=4u)&(1m=24u) .ds -- \(*W\h'-12u'\(*W\h'-12u'-\" diablo 10 pitch
+. if (\n(.H=4u)&(1m=20u) .ds -- \(*W\h'-12u'\(*W\h'-8u'-\" diablo 12 pitch
+. ds L" ""
+. ds R" ""
+. ds C` ""
+. ds C' ""
+'br\}
+.el\{\
+. ds -- \|\(em\|
+. ds PI \(*p
+. ds L" ``
+. ds R" ''
+'br\}
+.\"
+.\" If the F register is turned on, we'll generate index entries on stderr for
+.\" titles (.TH), headers (.SH), subsections (.Sh), items (.Ip), and index
+.\" entries marked with X<> in POD. Of course, you'll have to process the
+.\" output yourself in some meaningful fashion.
+.if \nF \{\
+. de IX
+. tm Index:\\$1\t\\n%\t"\\$2"
+..
+. nr % 0
+. rr F
+.\}
+.\"
+.\" For nroff, turn off justification. Always turn off hyphenation; it makes
+.\" way too many mistakes in technical documents.
+.hy 0
+.if n .na
+.\"
+.\" Accent mark definitions (@(#)ms.acc 1.5 88/02/08 SMI; from UCB 4.2).
+.\" Fear. Run. Save yourself. No user-serviceable parts.
+. \" fudge factors for nroff and troff
+.if n \{\
+. ds #H 0
+. ds #V .8m
+. ds #F .3m
+. ds #[ \f1
+. ds #] \fP
+.\}
+.if t \{\
+. ds #H ((1u-(\\\\n(.fu%2u))*.13m)
+. ds #V .6m
+. ds #F 0
+. ds #[ \&
+. ds #] \&
+.\}
+. \" simple accents for nroff and troff
+.if n \{\
+. ds ' \&
+. ds ` \&
+. ds ^ \&
+. ds , \&
+. ds ~ ~
+. ds /
+.\}
+.if t \{\
+. ds ' \\k:\h'-(\\n(.wu*8/10-\*(#H)'\'\h"|\\n:u"
+. ds ` \\k:\h'-(\\n(.wu*8/10-\*(#H)'\`\h'|\\n:u'
+. ds ^ \\k:\h'-(\\n(.wu*10/11-\*(#H)'^\h'|\\n:u'
+. ds , \\k:\h'-(\\n(.wu*8/10)',\h'|\\n:u'
+. ds ~ \\k:\h'-(\\n(.wu-\*(#H-.1m)'~\h'|\\n:u'
+. ds / \\k:\h'-(\\n(.wu*8/10-\*(#H)'\z\(sl\h'|\\n:u'
+.\}
+. \" troff and (daisy-wheel) nroff accents
+.ds : \\k:\h'-(\\n(.wu*8/10-\*(#H+.1m+\*(#F)'\v'-\*(#V'\z.\h'.2m+\*(#F'.\h'|\\n:u'\v'\*(#V'
+.ds 8 \h'\*(#H'\(*b\h'-\*(#H'
+.ds o \\k:\h'-(\\n(.wu+\w'\(de'u-\*(#H)/2u'\v'-.3n'\*(#[\z\(de\v'.3n'\h'|\\n:u'\*(#]
+.ds d- \h'\*(#H'\(pd\h'-\w'~'u'\v'-.25m'\f2\(hy\fP\v'.25m'\h'-\*(#H'
+.ds D- D\\k:\h'-\w'D'u'\v'-.11m'\z\(hy\v'.11m'\h'|\\n:u'
+.ds th \*(#[\v'.3m'\s+1I\s-1\v'-.3m'\h'-(\w'I'u*2/3)'\s-1o\s+1\*(#]
+.ds Th \*(#[\s+2I\s-2\h'-\w'I'u*3/5'\v'-.3m'o\v'.3m'\*(#]
+.ds ae a\h'-(\w'a'u*4/10)'e
+.ds Ae A\h'-(\w'A'u*4/10)'E
+. \" corrections for vroff
+.if v .ds ~ \\k:\h'-(\\n(.wu*9/10-\*(#H)'\s-2\u~\d\s+2\h'|\\n:u'
+.if v .ds ^ \\k:\h'-(\\n(.wu*10/11-\*(#H)'\v'-.4m'^\v'.4m'\h'|\\n:u'
+. \" for low resolution devices (crt and lpr)
+.if \n(.H>23 .if \n(.V>19 \
+\{\
+. ds : e
+. ds 8 ss
+. ds o a
+. ds d- d\h'-1'\(ga
+. ds D- D\h'-1'\(hy
+. ds th \o'bp'
+. ds Th \o'LP'
+. ds ae ae
+. ds Ae AE
+.\}
+.rm #[ #] #H #V #F C
+.\" ========================================================================
+.\"
+.IX Title "TDX-UTIL 8"
+.TH TDX-UTIL 8 "2008-04-06" "INN 2.4.5" "InterNetNews Documentation"
+.SH "NAME"
+tdx\-util \- Tradindexed overview manipulation utility
+.SH "SYNOPSIS"
+.IX Header "SYNOPSIS"
+\&\fBtdx-util\fR [\fB\-AFgio\fR] [\fB\-a\fR \fIarticle\fR] [\fB\-n\fR \fInewsgroup\fR]
+[\fB\-p\fR \fIpath\fR] [\fB\-R\fR \fIpath\fR]
+.SH "DESCRIPTION"
+.IX Header "DESCRIPTION"
+\&\fBtdx-util\fR is an administrative interface to the tradindexed overview
+method for \s-1INN\s0. It only works on tradindexed overview databases, not on
+any other type of \s-1INN\s0 overview. It allows the administrator to dump
+various information about the internal state of the overview, audit it for
+errors, and rebuild portions of the overview database.
+.PP
+The tradindexed overview method should lock properly and therefore it
+should be safe to run this utility and perform any operation it performs,
+including full repairs or per-group overview rebuilds, while the server is
+running. However, note that some of the operations performed by this
+utility can take an extended period of time and will hold locks in the
+overview database during that period, which depending on what the server
+is doing may cause the server to stall until \fBtdx-util\fR completes its
+operation.
+.PP
+The dump operations are \fB\-i\fR, which dumps the master index for the
+overview database, \fB\-g\fR, which dumps the index for an individual group,
+and \fB\-o\fR, which dumps the overview information for a particular group
+(including the additional metadata stored in the index). For \fB\-g\fR and
+\&\fB\-o\fR, the \fB\-n\fR option must also be given to specify a newsgroup to
+operate on.
+.PP
+To audit the entire overview database for problems, use \fB\-A\fR. Any
+problems found will be reported to standard error. There is not (yet) a
+corresponding option to correct the errors found.
+.PP
+To rebuild the database for a particular newsgroup, use \fB\-R\fR. The \fB\-R\fR
+option takes a path to a directory which contains all of the articles for
+that newsgroup, one per file. The names of the files must be the numbers
+of the articles in that group. (In other words, this directory must be a
+traditional spool directory for that group.) The \fB\-n\fR option must also
+be given to specify the newsgroup for which the overview is being rebuilt.
+.PP
+For all operations performed by \fBtdx-util\fR, a different overview database
+than the one specified in \fIinn.conf\fR may be specified using the \fB\-p\fR
+option.
+.SH "OPTIONS"
+.IX Header "OPTIONS"
+.IP "\fB\-A\fR" 4
+.IX Item "-A"
+Audit the entire overview database for problems. This runs the internal
+consistency checks built into the tradindexed overview implementation,
+checking such things as the validity and reachability of all group index
+entries, the format of the individual overview entries, the correspondance
+of index entries to overview data, and similar such things. No changes
+will be made to the database, but problems will be reported to standard
+error.
+.IP "\fB\-a\fR \fIarticle\fR" 4
+.IX Item "-a article"
+The article number to act on. Only useful in combination with the \fB\-o\fR
+option to dump overview information.
+.IP "\fB\-F\fR" 4
+.IX Item "-F"
+Audit the entire overview database for problems, fixing them as they're
+detected where possible. This runs the internal consistency checks built
+into the tradindexed overview implementation, checking such things as the
+validity and reachability of all group index entries, the format of the
+individual overview entries, the correspondance of index entries to
+overview data, and similar such things. The strategy used when fixing
+problems is to throw away data that's unrecoverable, so be warned that
+using this option may result in inaccessible articles if their overview
+data has been corrupted.
+.Sp
+To see what would be changed by \fB\-F\fR, run \fBtdx-util\fR with \fB\-A\fR first.
+.IP "\fB\-g\fR" 4
+.IX Item "-g"
+Dump the master index of the overview database. This contains similar
+information to the server active file, such as high and low water marks
+and moderation status, and is the information that nnrpd hands out to
+clients.
+.Sp
+The fields are, in order, the newsgroup name, the high water mark, the low
+water mark, the base article number (the point at which the group index
+begins), the count of articles in the group, the group status flag, the
+time (in seconds since epoch) when that newsgroup was deleted or 0 if it
+hasn't been, and the inode of the index file for that group.
+.IP "\fB\-i\fR" 4
+.IX Item "-i"
+Dump the index of a particular group. The fields are, in order, the
+article number, the offset of the data for that article in the overview
+data file for that group, the length of the overview data, the time (in
+seconds since epoch) when the article arrived on the server, the time (in
+seconds since epoch) when the article should expire based on its Expires
+header (or 0 if there is no Expires header), and the storage \s-1API\s0 token of
+the article.
+.Sp
+If this option is given, the \fB\-n\fR option must also be given to specify
+the newsgroup on which to act.
+.IP "\fB\-n\fR \fInewsgroup\fR" 4
+.IX Item "-n newsgroup"
+Specify the newsgroup on which to act, required for the \fB\-i\fR, \fB\-o\fR, and
+\&\fB\-R\fR options.
+.IP "\fB\-o\fR" 4
+.IX Item "-o"
+Dump the overview information for a newsgroup, in the same format as it
+would be returned to clients but with one modification. Appended to the
+end of each entry will be four additional pieces of data: the article
+number according to the index file for that group, the storage \s-1API\s0 token
+for that article, the arrival date for that article on the server in \s-1RFC\s0
+822 date format, and the expiration date for that article (from the
+Expires header) in \s-1RFC\s0 822 date format if there is any.
+.Sp
+If this option is given, the \fB\-n\fR option must also be given to specify
+the newsgroup on which to act. By default, all of the overview
+information for that newsgroup is dumped, but the \fB\-a\fR option may be
+given to restrict the dump to the information for a single article.
+.IP "\fB\-p\fR \fIpath\fR" 4
+.IX Item "-p path"
+Act on the overview database rooted in \fIpath\fR, overriding the overview
+path specified in \fIinn.conf\fR.
+.IP "\fB\-R\fR \fIpath\fR" 4
+.IX Item "-R path"
+Rebuild the overview for a given group from the articles stored in
+\&\fIpath\fR. The articles must be in the form of a traditional spool
+directory; in other words, each article must be in a separate file and the
+name of the file must match the article number of the article.
+.Sp
+If this option is given, the \fB\-n\fR option must also be given to specify
+the newsgroup on which to act.
+.SH "EXAMPLES"
+.IX Header "EXAMPLES"
+Dump the master index for the overview database in \fI/news/overview\fR,
+regardless of the overview path specified in \fIinn.conf\fR:
+.PP
+.Vb 1
+\& tdx\-util \-i \-p /news/overview
+.Ve
+.PP
+Dump the group index for example.test:
+.PP
+.Vb 1
+\& tdx\-util \-g \-n example.test
+.Ve
+.PP
+Dump the complete overview information for example.test:
+.PP
+.Vb 1
+\& tdx\-util \-o \-n example.test
+.Ve
+.PP
+Audit the entire overview database for any problems:
+.PP
+.Vb 1
+\& tdx\-util \-A
+.Ve
+.PP
+Rebuild the overview information for example.test from a traditional spool
+directory:
+.PP
+.Vb 1
+\& tdx\-util \-R /news/spool/articles/example/test \-n example.test
+.Ve
+.PP
+The last command may be useful for recovering from a bad crash or
+corrupted overview information for a particular group, if you are also
+using the tradspool article storage method.
+.SH "HISTORY"
+.IX Header "HISTORY"
+Written by Russ Allbery <rra@stanford.edu> for InterNetNews.
+.PP
+$Id: tdx-util.8 7880 2008-06-16 20:37:13Z iulius $
+.SH "SEE ALSO"
+.IX Header "SEE ALSO"
+\&\fImakehistory\fR\|(8)
--- /dev/null
+.\" Automatically generated by Pod::Man v1.37, Pod::Parser v1.32
+.\"
+.\" Standard preamble:
+.\" ========================================================================
+.de Sh \" Subsection heading
+.br
+.if t .Sp
+.ne 5
+.PP
+\fB\\$1\fR
+.PP
+..
+.de Sp \" Vertical space (when we can't use .PP)
+.if t .sp .5v
+.if n .sp
+..
+.de Vb \" Begin verbatim text
+.ft CW
+.nf
+.ne \\$1
+..
+.de Ve \" End verbatim text
+.ft R
+.fi
+..
+.\" Set up some character translations and predefined strings. \*(-- will
+.\" give an unbreakable dash, \*(PI will give pi, \*(L" will give a left
+.\" double quote, and \*(R" will give a right double quote. \*(C+ will
+.\" give a nicer C++. Capital omega is used to do unbreakable dashes and
+.\" therefore won't be available. \*(C` and \*(C' expand to `' in nroff,
+.\" nothing in troff, for use with C<>.
+.tr \(*W-
+.ds C+ C\v'-.1v'\h'-1p'\s-2+\h'-1p'+\s0\v'.1v'\h'-1p'
+.ie n \{\
+. ds -- \(*W-
+. ds PI pi
+. if (\n(.H=4u)&(1m=24u) .ds -- \(*W\h'-12u'\(*W\h'-12u'-\" diablo 10 pitch
+. if (\n(.H=4u)&(1m=20u) .ds -- \(*W\h'-12u'\(*W\h'-8u'-\" diablo 12 pitch
+. ds L" ""
+. ds R" ""
+. ds C` ""
+. ds C' ""
+'br\}
+.el\{\
+. ds -- \|\(em\|
+. ds PI \(*p
+. ds L" ``
+. ds R" ''
+'br\}
+.\"
+.\" If the F register is turned on, we'll generate index entries on stderr for
+.\" titles (.TH), headers (.SH), subsections (.Sh), items (.Ip), and index
+.\" entries marked with X<> in POD. Of course, you'll have to process the
+.\" output yourself in some meaningful fashion.
+.if \nF \{\
+. de IX
+. tm Index:\\$1\t\\n%\t"\\$2"
+..
+. nr % 0
+. rr F
+.\}
+.\"
+.\" For nroff, turn off justification. Always turn off hyphenation; it makes
+.\" way too many mistakes in technical documents.
+.hy 0
+.if n .na
+.\"
+.\" Accent mark definitions (@(#)ms.acc 1.5 88/02/08 SMI; from UCB 4.2).
+.\" Fear. Run. Save yourself. No user-serviceable parts.
+. \" fudge factors for nroff and troff
+.if n \{\
+. ds #H 0
+. ds #V .8m
+. ds #F .3m
+. ds #[ \f1
+. ds #] \fP
+.\}
+.if t \{\
+. ds #H ((1u-(\\\\n(.fu%2u))*.13m)
+. ds #V .6m
+. ds #F 0
+. ds #[ \&
+. ds #] \&
+.\}
+. \" simple accents for nroff and troff
+.if n \{\
+. ds ' \&
+. ds ` \&
+. ds ^ \&
+. ds , \&
+. ds ~ ~
+. ds /
+.\}
+.if t \{\
+. ds ' \\k:\h'-(\\n(.wu*8/10-\*(#H)'\'\h"|\\n:u"
+. ds ` \\k:\h'-(\\n(.wu*8/10-\*(#H)'\`\h'|\\n:u'
+. ds ^ \\k:\h'-(\\n(.wu*10/11-\*(#H)'^\h'|\\n:u'
+. ds , \\k:\h'-(\\n(.wu*8/10)',\h'|\\n:u'
+. ds ~ \\k:\h'-(\\n(.wu-\*(#H-.1m)'~\h'|\\n:u'
+. ds / \\k:\h'-(\\n(.wu*8/10-\*(#H)'\z\(sl\h'|\\n:u'
+.\}
+. \" troff and (daisy-wheel) nroff accents
+.ds : \\k:\h'-(\\n(.wu*8/10-\*(#H+.1m+\*(#F)'\v'-\*(#V'\z.\h'.2m+\*(#F'.\h'|\\n:u'\v'\*(#V'
+.ds 8 \h'\*(#H'\(*b\h'-\*(#H'
+.ds o \\k:\h'-(\\n(.wu+\w'\(de'u-\*(#H)/2u'\v'-.3n'\*(#[\z\(de\v'.3n'\h'|\\n:u'\*(#]
+.ds d- \h'\*(#H'\(pd\h'-\w'~'u'\v'-.25m'\f2\(hy\fP\v'.25m'\h'-\*(#H'
+.ds D- D\\k:\h'-\w'D'u'\v'-.11m'\z\(hy\v'.11m'\h'|\\n:u'
+.ds th \*(#[\v'.3m'\s+1I\s-1\v'-.3m'\h'-(\w'I'u*2/3)'\s-1o\s+1\*(#]
+.ds Th \*(#[\s+2I\s-2\h'-\w'I'u*3/5'\v'-.3m'o\v'.3m'\*(#]
+.ds ae a\h'-(\w'a'u*4/10)'e
+.ds Ae A\h'-(\w'A'u*4/10)'E
+. \" corrections for vroff
+.if v .ds ~ \\k:\h'-(\\n(.wu*9/10-\*(#H)'\s-2\u~\d\s+2\h'|\\n:u'
+.if v .ds ^ \\k:\h'-(\\n(.wu*10/11-\*(#H)'\v'-.4m'^\v'.4m'\h'|\\n:u'
+. \" for low resolution devices (crt and lpr)
+.if \n(.H>23 .if \n(.V>19 \
+\{\
+. ds : e
+. ds 8 ss
+. ds o a
+. ds d- d\h'-1'\(ga
+. ds D- D\h'-1'\(hy
+. ds th \o'bp'
+. ds Th \o'LP'
+. ds ae ae
+. ds Ae AE
+.\}
+.rm #[ #] #H #V #F C
+.\" ========================================================================
+.\"
+.IX Title "tst 3"
+.TH tst 3 "2008-04-06" "INN 2.4.5" "InterNetNews Documentation"
+.SH "NAME"
+tst \- ternary search trie functions
+.SH "SYNOPSIS"
+.IX Header "SYNOPSIS"
+\&\fB#include <inn/tst.h>\fR
+.PP
+\&\fBstruct tst;\fR
+.PP
+\&\fBstruct tst *tst_init(int \fR\fInode_line_width\fR\fB);\fR
+.PP
+\&\fBvoid tst_cleanup(struct tst *\fR\fItst\fR\fB);\fR
+.PP
+\&\fBint tst_insert(struct tst *\fR\fItst\fR\fB, const unsigned char *\fR\fIkey\fR\fB, void *\fR\fIdata\fR\fB, int \fR\fIoption\fR\fB, void **\fR\fIexist_ptr\fR\fB);\fR
+.PP
+\&\fBvoid *tst_search(struct tst *\fR\fItst\fR\fB, const unsigned char *\fR\fIkey\fR\fB);\fR
+.PP
+\&\fBvoid *tst_delete(struct tst *\fR\fItst\fR\fB, const unsigned char *\fR\fIkey\fR\fB);\fR
+.SH "DESCRIPTION"
+.IX Header "DESCRIPTION"
+\&\fBtst_init\fR allocates memory for members of \fIstruct tst\fR, and
+allocates the first \fInode_line_width\fR nodes. A \s-1NULL\s0 pointer is
+returned by \fBtst_init\fR if any part of the memory allocation fails. On
+success, a pointer to a \fIstruct tst\fR is returned.
+.PP
+The value for \fInode_line_width\fR must be chosen very carefully. One
+node is required for every character in the tree. If you choose a
+value that is too small, your application will spend too much time
+calling \fImalloc\fR\|(3) and your node space will be too spread out. Too large
+a value is just a waste of space.
+.PP
+\&\fBtst_cleanup\fR frees all memory allocated to nodes, internal structures,
+as well as \fItst\fR itself.
+.PP
+\&\fBtst_insert\fR inserts the string \fIkey\fR into the tree. Behavior when a
+duplicate key is inserted is controlled by \fIoption\fR. If \fIkey\fR is
+already in the tree then \fB\s-1TST_DUPLICATE_KEY\s0\fR is returned, and the
+data pointer for the existing key is placed in \fIexist_ptr\fR. If
+\&\fIoption\fR is set to \fB\s-1TST_REPLACE\s0\fR then the existing data pointer for
+the existing key is replaced by \fIdata\fR. Note that the old data
+pointer will still be placed in \fIexist_ptr\fR.
+.PP
+If a duplicate key is encountered and \fIoption\fR is not set to
+\&\fB\s-1TST_REPLACE\s0\fR then \fB\s-1TST_DUPLICATE_KEY\s0\fR is returned. If \fIkey\fR is
+zero length then \fB\s-1TST_NULL_KEY\s0\fR is returned. A successful insert or
+replace returns \fB\s-1TST_OK\s0\fR. A return value of \fB\s-1TST_ERROR\s0\fR indicates
+that a memory allocation error occurred while trying to grow the node
+free.
+.PP
+Note that the \fIdata\fR argument must never be \fB\s-1NULL\s0\fR. If it is, then
+calls to \fBtst_search\fR will fail for a key that exists because the
+data value was set to \fB\s-1NULL\s0\fR, which is what \fBtst_search\fR returns. If
+you just want a simple existence tree, use the \fBtst\fR pointer as the
+data pointer.
+.PP
+\&\fBtst_search\fR finds the string \fIkey\fR in the tree if it exists and
+returns the data pointer associated with that key.
+.PP
+If \fIkey\fR is not found then \fB\s-1NULL\s0\fR is returned, otherwise the data pointer
+associated with \fIkey\fR is returned.
+.PP
+\&\fBtst_delete\fR deletes the string \fIkey\fR from the tree if it exists and
+returns the data pointer assocaited with that key.
+.PP
+If \fIkey\fR is not found then \fB\s-1NULL\s0\fR is returned, otherwise the data
+pointer associated with \fIkey\fR is returned.
+.SH "HISTORY"
+.IX Header "HISTORY"
+Converted to \s-1POD\s0 from Peter A. Friend's ternary search trie
+documentation by Alex Kiernan <alex.kiernan@thus.net> for InterNetNews
+2.4.0.
+.PP
+$Id: tst.3 7880 2008-06-16 20:37:13Z iulius $
--- /dev/null
+.\" Automatically generated by Pod::Man v1.37, Pod::Parser v1.32
+.\"
+.\" Standard preamble:
+.\" ========================================================================
+.de Sh \" Subsection heading
+.br
+.if t .Sp
+.ne 5
+.PP
+\fB\\$1\fR
+.PP
+..
+.de Sp \" Vertical space (when we can't use .PP)
+.if t .sp .5v
+.if n .sp
+..
+.de Vb \" Begin verbatim text
+.ft CW
+.nf
+.ne \\$1
+..
+.de Ve \" End verbatim text
+.ft R
+.fi
+..
+.\" Set up some character translations and predefined strings. \*(-- will
+.\" give an unbreakable dash, \*(PI will give pi, \*(L" will give a left
+.\" double quote, and \*(R" will give a right double quote. \*(C+ will
+.\" give a nicer C++. Capital omega is used to do unbreakable dashes and
+.\" therefore won't be available. \*(C` and \*(C' expand to `' in nroff,
+.\" nothing in troff, for use with C<>.
+.tr \(*W-
+.ds C+ C\v'-.1v'\h'-1p'\s-2+\h'-1p'+\s0\v'.1v'\h'-1p'
+.ie n \{\
+. ds -- \(*W-
+. ds PI pi
+. if (\n(.H=4u)&(1m=24u) .ds -- \(*W\h'-12u'\(*W\h'-12u'-\" diablo 10 pitch
+. if (\n(.H=4u)&(1m=20u) .ds -- \(*W\h'-12u'\(*W\h'-8u'-\" diablo 12 pitch
+. ds L" ""
+. ds R" ""
+. ds C` ""
+. ds C' ""
+'br\}
+.el\{\
+. ds -- \|\(em\|
+. ds PI \(*p
+. ds L" ``
+. ds R" ''
+'br\}
+.\"
+.\" If the F register is turned on, we'll generate index entries on stderr for
+.\" titles (.TH), headers (.SH), subsections (.Sh), items (.Ip), and index
+.\" entries marked with X<> in POD. Of course, you'll have to process the
+.\" output yourself in some meaningful fashion.
+.if \nF \{\
+. de IX
+. tm Index:\\$1\t\\n%\t"\\$2"
+..
+. nr % 0
+. rr F
+.\}
+.\"
+.\" For nroff, turn off justification. Always turn off hyphenation; it makes
+.\" way too many mistakes in technical documents.
+.hy 0
+.if n .na
+.\"
+.\" Accent mark definitions (@(#)ms.acc 1.5 88/02/08 SMI; from UCB 4.2).
+.\" Fear. Run. Save yourself. No user-serviceable parts.
+. \" fudge factors for nroff and troff
+.if n \{\
+. ds #H 0
+. ds #V .8m
+. ds #F .3m
+. ds #[ \f1
+. ds #] \fP
+.\}
+.if t \{\
+. ds #H ((1u-(\\\\n(.fu%2u))*.13m)
+. ds #V .6m
+. ds #F 0
+. ds #[ \&
+. ds #] \&
+.\}
+. \" simple accents for nroff and troff
+.if n \{\
+. ds ' \&
+. ds ` \&
+. ds ^ \&
+. ds , \&
+. ds ~ ~
+. ds /
+.\}
+.if t \{\
+. ds ' \\k:\h'-(\\n(.wu*8/10-\*(#H)'\'\h"|\\n:u"
+. ds ` \\k:\h'-(\\n(.wu*8/10-\*(#H)'\`\h'|\\n:u'
+. ds ^ \\k:\h'-(\\n(.wu*10/11-\*(#H)'^\h'|\\n:u'
+. ds , \\k:\h'-(\\n(.wu*8/10)',\h'|\\n:u'
+. ds ~ \\k:\h'-(\\n(.wu-\*(#H-.1m)'~\h'|\\n:u'
+. ds / \\k:\h'-(\\n(.wu*8/10-\*(#H)'\z\(sl\h'|\\n:u'
+.\}
+. \" troff and (daisy-wheel) nroff accents
+.ds : \\k:\h'-(\\n(.wu*8/10-\*(#H+.1m+\*(#F)'\v'-\*(#V'\z.\h'.2m+\*(#F'.\h'|\\n:u'\v'\*(#V'
+.ds 8 \h'\*(#H'\(*b\h'-\*(#H'
+.ds o \\k:\h'-(\\n(.wu+\w'\(de'u-\*(#H)/2u'\v'-.3n'\*(#[\z\(de\v'.3n'\h'|\\n:u'\*(#]
+.ds d- \h'\*(#H'\(pd\h'-\w'~'u'\v'-.25m'\f2\(hy\fP\v'.25m'\h'-\*(#H'
+.ds D- D\\k:\h'-\w'D'u'\v'-.11m'\z\(hy\v'.11m'\h'|\\n:u'
+.ds th \*(#[\v'.3m'\s+1I\s-1\v'-.3m'\h'-(\w'I'u*2/3)'\s-1o\s+1\*(#]
+.ds Th \*(#[\s+2I\s-2\h'-\w'I'u*3/5'\v'-.3m'o\v'.3m'\*(#]
+.ds ae a\h'-(\w'a'u*4/10)'e
+.ds Ae A\h'-(\w'A'u*4/10)'E
+. \" corrections for vroff
+.if v .ds ~ \\k:\h'-(\\n(.wu*9/10-\*(#H)'\s-2\u~\d\s+2\h'|\\n:u'
+.if v .ds ^ \\k:\h'-(\\n(.wu*10/11-\*(#H)'\v'-.4m'^\v'.4m'\h'|\\n:u'
+. \" for low resolution devices (crt and lpr)
+.if \n(.H>23 .if \n(.V>19 \
+\{\
+. ds : e
+. ds 8 ss
+. ds o a
+. ds d- d\h'-1'\(ga
+. ds D- D\h'-1'\(hy
+. ds th \o'bp'
+. ds Th \o'LP'
+. ds ae ae
+. ds Ae AE
+.\}
+.rm #[ #] #H #V #F C
+.\" ========================================================================
+.\"
+.IX Title "uwildmat 3"
+.TH uwildmat 3 "2008-04-06" "INN 2.4.5" "InterNetNews Documentation"
+.SH "NAME"
+uwildmat, uwildmat_simple, uwildmat_poison \- Perform wildmat matching
+.SH "SYNOPSIS"
+.IX Header "SYNOPSIS"
+\&\fB#include <libinn.h>\fR
+.PP
+\&\fBbool uwildmat(const char *\fR\fItext\fR\fB, const char *\fR\fIpattern\fR\fB);\fR
+.PP
+\&\fBbool uwildmat_simple(const char *\fR\fItext\fR\fB, const char *\fR\fIpattern\fR\fB);\fR
+.PP
+\&\fBenum uwildmat uwildmat_poison(const char *\fR\fItext\fR\fB,
+const char *\fR\fIpattern\fR\fB);\fR
+.SH "DESCRIPTION"
+.IX Header "DESCRIPTION"
+\&\fBuwildmat\fR compares \fItext\fR against the wildmat expression \fIpattern\fR,
+returning true if and only if the expression matches the text. \f(CW\*(C`@\*(C'\fR has
+no special meaning in \fIpattern\fR when passed to \fBuwildmat\fR. Both \fItext\fR
+and \fIpattern\fR are assumed to be in the \s-1UTF\-8\s0 character encoding, although
+malformed \s-1UTF\-8\s0 sequences are treated in a way that attempts to be mostly
+compatible with single-octet character sets like \s-1ISO\s0 8859\-1. (In other
+words, if you try to match \s-1ISO\s0 8859\-1 text with these routines everything
+should work as expected unless the \s-1ISO\s0 8859\-1 text contains valid \s-1UTF\-8\s0
+sequences, which thankfully is somewhat rare.)
+.PP
+\&\fBuwildmat_simple\fR is identical to \fBuwildmat\fR except that neither \f(CW\*(C`!\*(C'\fR
+nor \f(CW\*(C`,\*(C'\fR have any special meaning and \fIpattern\fR is always treated as a
+single pattern. This function exists solely to support legacy interfaces
+like \s-1NNTP\s0's \s-1XPAT\s0 command, and should be avoided when implementing new
+features.
+.PP
+\&\fBuwildmat_poison\fR works similarly to \fBuwildmat\fR, except that \f(CW\*(C`@\*(C'\fR as the
+first character of one of the patterns in the expression (see below)
+\&\*(L"poisons\*(R" the match if it matches. \fBuwildmat_poison\fR returns
+\&\fB\s-1UWILDMAT_MATCH\s0\fR if the expression matches the text, \fB\s-1UWILDMAT_FAIL\s0\fR if
+it doesn't, and \fB\s-1UWILDMAT_POISON\s0\fR if the expression doesn't match because
+a poisoned pattern matched the text. These enumeration constants are
+defined in the \fBlibinn.h\fR header.
+.SH "WILDMAT EXPRESSIONS"
+.IX Header "WILDMAT EXPRESSIONS"
+A wildmat expression follows rules similar to those of shell filename
+wildcards but with some additions and changes. A wildmat \fIexpression\fR is
+composed of one or more wildmat \fIpatterns\fR separated by commas. Each
+character in the wildmat pattern matches a literal occurance of that same
+character in the text, with the exception of the following metacharacters:
+.IP "?" 8
+Matches any single character (including a single \s-1UTF\-8\s0 multibyte
+character, so \f(CW\*(C`?\*(C'\fR can match more than one byte).
+.IP "*\&" 8
+Matches any sequence of zero or more characters.
+.IP "\e" 8
+.IX Item ""
+Turns off any special meaning of the following character; the following
+character will match itself in the text. \f(CW\*(C`\e\*(C'\fR will escape any character,
+including another backslash or a comma that otherwise would separate a
+pattern from the next pattern in an expression. Note that \f(CW\*(C`\e\*(C'\fR is not
+special inside a character range (no metacharacters are).
+.IP "[...]" 8
+A character set, which matches any single character that falls within that
+set. The presence of a character between the brackets adds that character
+to the set; for example, \f(CW\*(C`[amv]\*(C'\fR specifies the set containing the
+characters \f(CW\*(C`a\*(C'\fR, \f(CW\*(C`m\*(C'\fR, and \f(CW\*(C`v\*(C'\fR. A range of characters may be specified
+using \f(CW\*(C`\-\*(C'\fR; for example, \f(CW\*(C`[0\-5abc]\*(C'\fR is equivalent to \f(CW\*(C`[012345abc]\*(C'\fR. The
+order of characters is as defined in the \s-1UTF\-8\s0 character set, and if the
+start character of such a range falls after the ending character of the
+range in that ranking the results of attempting a match with that pattern
+are undefined.
+.Sp
+In order to include a literal \f(CW\*(C`]\*(C'\fR character in the set, it must be the
+first character of the set (possibly following \f(CW\*(C`^\*(C'\fR); for example, \f(CW\*(C`[]a]\*(C'\fR
+matches either \f(CW\*(C`]\*(C'\fR or \f(CW\*(C`a\*(C'\fR. To include a literal \f(CW\*(C`\-\*(C'\fR character in the
+set, it must be either the first or the last character of the set.
+Backslashes have no special meaning inside a character set, nor do any
+other of the wildmat metacharacters.
+.IP "[^...]" 8
+A negated character set. Follows the same rules as a character set above,
+but matches any character \fBnot\fR contained in the set. So, for example,
+\&\f(CW\*(C`[^]\-]\*(C'\fR matches any character except \f(CW\*(C`]\*(C'\fR and \f(CW\*(C`\-\*(C'\fR.
+.PP
+In addition, \f(CW\*(C`!\*(C'\fR (and possibly \f(CW\*(C`@\*(C'\fR) have special meaning as the first
+character of a pattern; see below.
+.PP
+When matching a wildmat expression against some text, each comma-separated
+pattern is matched in order from left to right. In order to match, the
+pattern must match the whole text; in regular expression terminology, it's
+implicitly anchored at both the beginning and the end. For example, the
+pattern \f(CW\*(C`a\*(C'\fR matches only the text \f(CW\*(C`a\*(C'\fR; it doesn't match \f(CW\*(C`ab\*(C'\fR or \f(CW\*(C`ba\*(C'\fR
+or even \f(CW\*(C`aa\*(C'\fR. If none of the patterns match, the whole expression
+doesn't match. Otherwise, whether the expression matches is determined
+entirely by the rightmost matching pattern; the expression matches the
+text if and only if the rightmost matching pattern is not negated.
+.PP
+For example, consider the text \f(CW\*(C`news.misc\*(C'\fR. The expression \f(CW\*(C`*\*(C'\fR matches
+this text, of course, as does \f(CW\*(C`comp.*,news.*\*(C'\fR (because the second pattern
+matches). \f(CW\*(C`news.*,!news.misc\*(C'\fR does not match this text because both
+patterns match, meaning that the rightmost takes precedence, and the
+rightmost matching pattern is negated. \f(CW\*(C`news.*,!news.misc,*.misc\*(C'\fR does
+match this text, since the rightmost matching pattern is not negated.
+.PP
+Note that the expression \f(CW\*(C`!news.misc\*(C'\fR can't match anything. Either the
+pattern doesn't match, in which case no patterns match and the expression
+doesn't match, or the pattern does match, in which case because it's
+negated the expression doesn't match. \f(CW\*(C`*,!news.misc\*(C'\fR, on the other hand,
+is a useful pattern that matches anything except \f(CW\*(C`news.misc\*(C'\fR.
+.PP
+\&\f(CW\*(C`!\*(C'\fR has significance only as the first character of a pattern; anywhere
+else in the pattern, it matches a literal \f(CW\*(C`!\*(C'\fR in the text like any other
+non\-metacharacter.
+.PP
+If the \fBuwildmat_poison\fR interface is used, then \f(CW\*(C`@\*(C'\fR behaves the same as
+\&\f(CW\*(C`!\*(C'\fR except that if an expression fails to match because the rightmost
+matching pattern began with \f(CW\*(C`@\*(C'\fR, \fB\s-1UWILDMAT_POISON\s0\fR is returned instead of
+\&\fB\s-1UWILDMAT_FAIL\s0\fR.
+.PP
+If the \fBuwildmat_simple\fR interface is used, the matching rules are the
+same as above except that none of \f(CW\*(C`!\*(C'\fR, \f(CW\*(C`@\*(C'\fR, or \f(CW\*(C`,\*(C'\fR have any special
+meaning at all and only match those literal characters.
+.SH "BUGS"
+.IX Header "BUGS"
+All of these functions internally convert the passed arguments to const
+unsigned char pointers. The only reason why they take regular char
+pointers instead of unsigned char is for the convenience of \s-1INN\s0 and other
+callers that may not be using unsigned char everywhere they should. In a
+future revision, the public interface should be changed to just take
+unsigned char pointers.
+.SH "HISTORY"
+.IX Header "HISTORY"
+Written by Rich \f(CW$alz\fR <rsalz@uunet.uu.net> in 1986, and posted to Usenet
+several times since then, most notably in comp.sources.misc in
+March, 1991.
+.PP
+Lars Mathiesen <thorinn@diku.dk> enhanced the multi-asterisk failure
+mode in early 1991.
+.PP
+Rich and Lars increased the efficiency of star patterns and reposted it to
+comp.sources.misc in April, 1991.
+.PP
+Robert Elz <kre@munnari.oz.au> added minus sign and close bracket handling
+in June, 1991.
+.PP
+Russ Allbery <rra@stanford.edu> added support for comma-separated patterns
+and the \f(CW\*(C`!\*(C'\fR and \f(CW\*(C`@\*(C'\fR metacharacters to the core wildmat routines in July,
+2000. He also added support for \s-1UTF\-8\s0 characters, changed the default
+behavior to assume that both the text and the pattern are in \s-1UTF\-8\s0, and
+largely rewrote this documentation to expand and clarify the description
+of how a wildmat expression matches.
+.PP
+Please note that the interfaces to these functions are named \fBuwildmat\fR
+and the like rather than \fBwildmat\fR to distinguish them from the
+\&\fBwildmat\fR function provided by Rich \f(CW$alz\fR's original implementation.
+While this code is heavily based on Rich's original code, it has
+substantial differences, including the extension to support \s-1UTF\-8\s0
+characters, and has noticable functionality changes. Any bugs present in
+it aren't Rich's fault.
+.PP
+$Id: uwildmat.3 7880 2008-06-16 20:37:13Z iulius $
+.SH "SEE ALSO"
+.IX Header "SEE ALSO"
+\&\fIgrep\fR\|(1), \fIfnmatch\fR\|(3), \fIregex\fR\|(3), \fIregexp\fR\|(3).
--- /dev/null
+.TH WRITELOG 8
+.SH NAME
+writelog \- add a entry to an INN log file.
+.SH SYNOPSIS
+.B writelog
+.I name
+.I text...
+.SH DESCRIPTION
+.PP
+The
+.I writelog
+script is used to write a log entry or send it as mail.
+The
+.I name
+parameter specifies the name of the log file where the entry should
+be written.
+If it is the word ``mail'' then the entry is mailed to the news administrator,
+.IR <USER\ specified\ with\ \-\-with\-news\-master\ at\ configure> .
+The data that is written or sent consists of the
+.I text
+given on the command line, followed by standard input indented by
+four spaces.
+.IR Shlock (1)
+is used to avoid simultaneous updates to a single log file.
+.SH HISTORY
+Written by Landon Curt Noll <chongo@toad.com> and Rich $alz
+<rsalz@uunet.uu.net> for InterNetNews.
+.de R$
+This is revision \\$3, dated \\$4.
+..
+.R$ $Id: writelog.8 5909 2002-12-03 05:17:18Z vinocur $
+.SH "SEE ALSO"
+innd(8),
+innstat(8),
+news.daily(8),
+newslog(5),
+nnrpd(8),
+scanlogs(8).
--- /dev/null
+## $Id: Makefile 7458 2005-12-12 00:25:05Z eagle $
+##
+## This Makefile contains rules to generate the files derived from POD
+## source. Normal make commands at the top level of the source tree don't
+## recurse into this directory. These targets are only used by
+## maintainers.
+
+include ../../Makefile.global
+
+TEXT = ../../HACKING ../../INSTALL ../../NEWS ../../README ../hook-perl \
+ ../hook-python ../external-auth ../checklist
+
+MAN1 = ../man/convdate.1 ../man/fastrm.1 ../man/grephistory.1 \
+ ../man/inews.1 ../man/innconfval.1 ../man/innmail.1 \
+ ../man/pullnews.1 ../man/simpleftp.1 ../man/sm.1
+
+MAN3 = ../man/libauth.3 ../man/libinnhist.3 ../man/list.3 ../man/qio.3 \
+ ../man/tst.3 ../man/uwildmat.3
+
+MAN5 = ../man/active.5 ../man/active.times.5 ../man/control.ctl.5 \
+ ../man/cycbuff.conf.5 ../man/distrib.pats.5 ../man/expire.ctl.5 \
+ ../man/inn.conf.5 ../man/motd.news.5 ../man/newsfeeds.5 ../man/ovdb.5 \
+ ../man/passwd.nntp.5 ../man/radius.conf.5 ../man/readers.conf.5 \
+ ../man/sasl.conf.5 ../man/subscriptions.5
+
+MAN8 = ../man/auth_krb5.8 ../man/auth_smb.8 ../man/ckpasswd.8 \
+ ../man/domain.8 ../man/expireover.8 ../man/ident.8 ../man/innd.8 \
+ ../man/inndf.8 ../man/nnrpd.8 ../man/inndstart.8 ../man/innupgrade.8 \
+ ../man/mailpost.8 ../man/makehistory.8 ../man/ninpaths.8 \
+ ../man/ovdb_init.8 ../man/ovdb_monitor.8 ../man/ovdb_server.8 \
+ ../man/ovdb_stat.8 ../man/radius.8 ../man/rc.news.8 \
+ ../man/sendinpaths.8 ../man/tdx-util.8
+
+ALL = $(TEXT) $(MAN1) $(MAN3) $(MAN5) $(MAN8)
+
+all: $(ALL)
+
+clean:
+ rm -f $(ALL)
+
+../../HACKING: hacking.pod ; $(POD2TEXT) $? > $@
+../../INSTALL: install.pod ; $(POD2TEXT) $? > $@
+../../NEWS: news.pod ; $(POD2TEXT) $? > $@
+../../README: readme.pod ; $(POD2TEXT) $? > $@
+../hook-perl: hook-perl.pod ; $(POD2TEXT) $? > $@
+../hook-python: hook-python.pod ; $(POD2TEXT) $? > $@
+../external-auth: external-auth.pod ; $(POD2TEXT) $? > $@
+../checklist: checklist.pod ; $(POD2TEXT) $? > $@
+
+../man/convdate.1: convdate.pod ; $(POD2MAN) -s 1 $? > $@
+../man/fastrm.1: fastrm.pod ; $(POD2MAN) -s 1 $? > $@
+../man/grephistory.1: grephistory.pod ; $(POD2MAN) -s 1 $? > $@
+../man/inews.1: inews.pod ; $(POD2MAN) -s 1 $? > $@
+../man/innconfval.1: innconfval.pod ; $(POD2MAN) -s 1 $? > $@
+../man/innmail.1: innmail.pod ; $(POD2MAN) -s 1 $? > $@
+../man/pullnews.1: pullnews.pod ; $(POD2MAN) -s 1 $? > $@
+../man/simpleftp.1: simpleftp.pod ; $(POD2MAN) -s 1 $? > $@
+../man/sm.1: sm.pod ; $(POD2MAN) -s 1 $? > $@
+
+../man/libauth.3: libauth.pod ; $(POD2MAN) -s 3 $? > $@
+../man/libinnhist.3: libinnhist.pod ; $(POD2MAN) -s 3 $? > $@
+../man/list.3: list.pod ; $(POD2MAN) -s 3 $? > $@
+../man/qio.3: qio.pod ; $(POD2MAN) -s 3 $? > $@
+../man/tst.3: tst.pod ; $(POD2MAN) -s 3 $? > $@
+../man/uwildmat.3: uwildmat.pod ; $(POD2MAN) -s 3 $? > $@
+
+../man/active.5: active.pod ; $(POD2MAN) -s 5 $? > $@
+../man/active.times.5: active.times.pod ; $(POD2MAN) -s 5 $? > $@
+../man/control.ctl.5: control.ctl.pod ; $(POD2MAN) -s 5 $? > $@
+../man/cycbuff.conf.5: cycbuff.conf.pod ; $(POD2MAN) -s 5 $? > $@
+../man/distrib.pats.5: distrib.pats.pod ; $(POD2MAN) -s 5 $? > $@
+../man/expire.ctl.5: expire.ctl.pod ; $(POD2MAN) -s 5 $? > $@
+../man/inn.conf.5: inn.conf.pod ; $(POD2MAN) -s 5 $? > $@
+../man/motd.news.5: motd.news.pod ; $(POD2MAN) -s 5 $? > $@
+../man/newsfeeds.5: newsfeeds.pod ; $(POD2MAN) -s 5 $? > $@
+../man/ovdb.5: ovdb.pod ; $(POD2MAN) -s 5 $? > $@
+../man/passwd.nntp.5: passwd.nntp.pod ; $(POD2MAN) -s 5 $? > $@
+../man/radius.conf.5: radius.conf.pod ; $(POD2MAN) -s 5 $? > $@
+../man/readers.conf.5: readers.conf.pod ; $(POD2MAN) -s 5 $? > $@
+../man/sasl.conf.5: sasl.conf.pod ; $(POD2MAN) -s 5 $? > $@
+../man/subscriptions.5: subscriptions.pod ; $(POD2MAN) -s 5 $? > $@
+
+../man/auth_krb5.8: auth_krb5.pod ; $(POD2MAN) -s 8 $? > $@
+../man/auth_smb.8: auth_smb.pod ; $(POD2MAN) -s 8 $? > $@
+../man/ckpasswd.8: ckpasswd.pod ; $(POD2MAN) -s 8 $? > $@
+../man/domain.8: domain.pod ; $(POD2MAN) -s 8 $? > $@
+../man/expireover.8: expireover.pod ; $(POD2MAN) -s 8 $? > $@
+../man/ident.8: ident.pod ; $(POD2MAN) -s 8 $? > $@
+../man/innd.8: innd.pod ; $(POD2MAN) -s 8 $? > $@
+../man/inndf.8: inndf.pod ; $(POD2MAN) -s 8 $? > $@
+../man/inndstart.8: inndstart.pod ; $(POD2MAN) -s 8 $? > $@
+../man/innupgrade.8: innupgrade.pod ; $(POD2MAN) -s 8 $? > $@
+../man/mailpost.8: mailpost.pod ; $(POD2MAN) -s 8 $? > $@
+../man/makehistory.8: makehistory.pod ; $(POD2MAN) -s 8 $? > $@
+../man/ninpaths.8: ninpaths.pod ; $(POD2MAN) -s 8 $? > $@
+../man/nnrpd.8: nnrpd.pod ; $(POD2MAN) -s 8 $? > $@
+../man/ovdb_init.8: ovdb_init.pod ; $(POD2MAN) -s 8 $? > $@
+../man/ovdb_monitor.8: ovdb_monitor.pod ; $(POD2MAN) -s 8 $? > $@
+../man/ovdb_server.8: ovdb_server.pod ; $(POD2MAN) -s 8 $? > $@
+../man/ovdb_stat.8: ovdb_stat.pod ; $(POD2MAN) -s 8 $? > $@
+../man/radius.8: radius.pod ; $(POD2MAN) -s 8 $? > $@
+../man/rc.news.8: rc.news.pod ; $(POD2MAN) -s 8 $? > $@
+../man/sendinpaths.8: sendinpaths.pod ; $(POD2MAN) -s 8 $? > $@
+../man/tdx-util.8: tdx-util.pod ; $(POD2MAN) -s 8 $? > $@
--- /dev/null
+=head1 NAME
+
+active - List of newsgroups carried by the server
+
+=head1 DESCRIPTION
+
+The file I<pathdb>/active lists the newsgroups carried by INN. This file
+is generally maintained using ctlinnd(8) to create and remove groups, or
+by letting controlchan(8) do so on the basis of received control messages.
+This file should not be edited directly without throttling B<innd>, and
+must be reloaded using B<ctlinnd> before B<innd> is unthrottled. Editing
+it directly even with those precautions may make it inconsistent with the
+overview database and won't update F<active.times>, so B<ctlinnd> should
+be used to make modifications whenever possible.
+
+Each newsgroup should be listed only once. Each line specifies one group.
+The order of groups does not matter. Within each newsgroup, received
+articles for that group are assigned monotonically increasing numbers as
+unique names. If an article is posted to newsgroups not mentioned in this
+file, those newsgroups are ignored.
+
+If none of the newsgroups listed in the Newsgroups header of an article
+are present in this file, the article is either rejected (if I<wanttrash>
+is false in F<inn.conf>), or is filed into the newsgroup C<junk> and only
+propagated to sites that receive the C<junk> newsgroup (if I<wanttrash> is
+true).
+
+Each line of this file consists of four fields separated by a space:
+
+ <name> <high> <low> <flag>
+
+The first field is the name of the newsgroup. The newsgroup C<junk> is
+special, as mentioned above. The newsgroup C<control> and any newsgroups
+beginning with C<control.> are also special; control messages are filed
+into a control.* newsgroup named after the type of control message if that
+group exists, and otherwise are filed into the newsgroup C<control>
+(without regard to what newsgroups are listed in the Newsgroups header).
+If I<mergetogroups> is set to true in F<inn.conf>, newsgroups that begin
+with C<to.> are also treated specially; see innd(8).
+
+The second field is the highest article number that has been used in that
+newsgroup. The third field is the lowest article number in the group;
+this number is not guaranteed to be accurate, and should only be taken to
+be a hint. It is normally updated nightly as part of the expire process;
+see news.daily(8) and look for C<lowmark> or C<renumber> for more details.
+Note that because of article cancellations, there may be gaps in the
+numbering sequence. If the lowest article number is greater then the
+highest article number, then there are no articles in the newsgroup. In
+order to make it possible to update an entry in-place without rewriting
+the entire file, the second and third fields are padded out with leading
+zeros to make them a fixed width.
+
+The fourth field contains one of the following flags:
+
+ y Local postings are allowed.
+ m The group is moderated and all postings must be approved.
+ n No local postings are allowed, only articles from peers.
+ j Articles are filed in the junk group instead.
+ x No local postings and ignored for articles from peers.
+ =foo.bar Articles are filed in the group foo.bar instead.
+
+If a newsgroup has the C<j> flag, no articles will be filed in that
+newsgroup, and local postings to that group will be rejected. If an
+article for that newsgroup is received from a remote site, and it is not
+crossposted to some other valid group, it will be filed into the C<junk>
+newsgroup instead. This is different than simply not listing the group,
+since the article will still be accepted and can be propagated to other
+sites, and the C<junk> group can be made available to readers if wished.
+
+If the <flag> field begins with an equal sign, the newsgroup is an alias.
+Articles cannot be posted to that newsgroup, but they can be received from
+other sites. Any articles received from peers for that newsgroup are
+treated as if they were actually posted to the group named after the equal
+sign. Note that the Newsgroups header of the articles are not modified.
+(Alias groups are typically used during a transition and are typically
+created manually with ctlinnd(8).) An alias should not point to another
+alias.
+
+=head1 HISTORY
+
+Written by Rich $alz <rsalz@uunet.uu.net> for InterNetNews. Converted to
+POD by Russ Allbery <rra@stanford.edu>.
+
+$Id: active.pod 6773 2004-05-17 05:48:54Z rra $
+
+=head1 SEE ALSO
+
+active.times(5), controlchan(8), ctlinnd(8), inn.conf(5), innd(8),
+news.daily(8)
+
+=cut
--- /dev/null
+=head1 NAME
+
+active.times - List of local creation times of newsgroups
+
+=head1 DESCRIPTION
+
+The file I<pathdb>/active.times provides a chronological record of when
+newsgruops were created on the local server. This file is normally
+updated by B<innd> whenever a newgroup control message is processed or a
+C<ctlinnd newgroup> command is issued, and is used by B<nnrpd> to answer
+NEWGROUPS requests.
+
+Each line consists of three fields:
+
+ <name> <time> <creator>
+
+The first field is the name of the newsgroup. The second field is the
+time it was created, expressed as the number of seconds since the epoch.
+The third field is the e-mail addrses of the person who created the group,
+as specified in the control message or on the B<ctlinnd> command line, or
+the newsmaster specified at configure time if no creator argument was
+given to B<ctlinnd>.
+
+=head1 HISTORY
+
+Written by Rich $alz <rsalz@uunet.uu.net> for InterNetNews. Converted to
+POD by Russ Allbery <rra@stanford.edu>
+
+$Id: active.times.pod 6282 2003-04-06 21:50:07Z rra $
+
+=head1 SEE ALSO
+
+active(5), ctlinnd(8), inn.conf(5), innd(8), nnrpd(8)
+
+=cut
--- /dev/null
+=head1 NAME
+
+auth_krb5 - nnrpd Kerberos v5 authenticator
+
+=head1 SYNOPSIS
+
+B<auth_krb5> [B<-i> I<instance>]
+
+=head1 DESCRIPTION
+
+This program does authentication for B<nnrpd> against a Kerberos v5 KDC.
+This is NOT real Kerberos authentication using service tickets; instead, a
+username and password is used to attempt to obtain a Kerberos v5 TGT to
+confirm that they are valid. As such, this authenticator assumes that
+B<nnrpd> has been given the user's username and password, and therefore is
+not as secure as real Kerberos authentication. It generally should only
+be used with NNTP over SSL to protect the password from sniffing.
+
+=head1 OPTIONS
+
+=over 4
+
+=item B<-i> I<instance>
+
+If this option is given, I<instance> will be used as the instance of the
+principal received from B<nnrpd> and authentication will be done against
+that principal instead of the base principal. In other words, a principal
+like C<user>, when passed to B<auth_krb5> invoked with C<-i nntp>, will be
+transformed into C<user/nntp> before attempting Kerberos authentication.
+
+Since giving one's password to B<nnrpd> is not as secure as normal
+Kerberos authentication, this option supports a configuration where all
+users are given a separate instance just for news authentication with its
+own password, so their regular account password isn't exposed via NNTP.
+
+=back
+
+=head1 EXAMPLE
+
+The following readers.conf(5) fragment tells nnrpd to authenticate users
+by attempting to obtain Kerberos v5 TGTs for them, appending an instance
+of C<nntp> to usernames before doing so:
+
+ auth kerberos {
+ auth: "auth_krb5 -i nntp"
+ }
+
+ access kerberos {
+ users: "*/nntp"
+ newsgroups: example.*
+ }
+
+Access is granted to the example.* groups for all users who successfully
+authenticate.
+
+=head1 BUGS
+
+Currently, any username containing realm information (containing C<@>) is
+rejected. This is to prevent someone from passing in a username
+corresponding to a principal in another realm that they have access to and
+gaining access to the news server via it. However, this is also something
+that people may wish to do under some circumstances, so there should be a
+better way of handling it (such as, perhaps, a list of acceptable realms
+or a -r flag specifying the realm in which to attempt authentication).
+
+It's not clear the right thing to do when the username passed in contains
+a C</> and B<-i> was also given. Right now, B<auth_krb5> will create a
+malformed Kerberos principal with multiple instances and attempt to
+authenticate against it, which will fail but perhaps not with the best
+error message.
+
+=head1 HISTORY
+
+Originally written by Christopher P. Lindsey. This documentation was
+written by Russ Allbery <rra@stanford.edu> based on Christopher's original
+README file.
+
+$Id: auth_krb5.pod 5897 2002-12-03 03:41:06Z rra $
+
+=head1 SEE ALSO
+
+nnrpd(8), readers.conf(5)
+
+The latest version of Christopher's original B<nnrpkrb5auth> may be found
+on his web site at L<http://www.mallorn.com/tools/>.
+
+=cut
--- /dev/null
+=head1 NAME
+
+auth_smb - nnrpd Samba authenticator
+
+=head1 SYNOPSIS
+
+B<auth_smb> B<server> [B<backup_server>] B<domain>
+
+=head1 DESCRIPTION
+
+This program does authentication for B<nnrpd> against an SMB server. It
+passes the received username and password to B<server> for validation in
+the specified SMB B<domain>. A backup server may optionally be
+supplied; if it is missing, only B<server> is used.
+
+If authentication is successful, the original username is returned as
+the authentication identity. Brief errors, including incorrect password
+and failure contacting the server, are logged.
+
+=head1 EXAMPLE
+
+The following readers.conf(5) fragment grants access to users who can
+authenticate against an SMB server:
+
+ auth windows {
+ auth: "auth_smb pdc.example.com bdc.example.com USERS"
+ default-domain: "users.example.com"
+ }
+
+ access internal {
+ users: "*@users.example.com"
+ newsgroups: example.*
+ }
+
+Access is granted to the example.* groups after successful
+authentication.
+
+=head1 BUGS
+
+We should link against an external SMB library rather than maintain one
+within the INN source tree.
+
+=head1 HISTORY
+
+Originally written October 2000 by Krischan Jodies <krischan@jodies.cx>,
+based heavily on pam_smb v1.1.6 by David Airlie <airlied@samba.org>.
+This documentation was written by Jeffrey M. Vinocur <jeff@litech.org>.
+
+$Id: auth_smb.pod 5988 2002-12-12 23:02:14Z vinocur $
+
+=head1 SEE ALSO
+
+nnrpd(8), readers.conf(5)
+
+=cut
--- /dev/null
+=head1 Introduction
+
+$Id: checklist.pod 5909 2002-12-03 05:17:18Z vinocur $
+
+This is an installation checklist written by Rebecca Ore, intended to be
+the beginning of a different presentation of the information in INSTALL,
+since getting started with installing INN can be complex. Further
+clarifications, updates, and expansion are welcome.
+
+=head1 Setup
+
+=over 4
+
+=item *
+
+Make sure there is a "news" user (and a "news" group)
+
+=item *
+
+Create a home directory for news (perhaps F</usr/local/news/>) and make
+sure it (and subdirectories) are owned by "news", group "news".
+
+You want to be careful that things in that directory stay owned by
+"news" -- but you can't just C<chown -R news.news> after the install,
+because you may have binaries that are SUID root. You can do the build
+as any user, because C<make install> will set the permissions
+correctly. After that point, though, you may want to C<su news> to
+avoid creating any files as root. (For routine maintenance once INN is
+working, you can generally be root.)
+
+=item *
+
+If necessary, add F<~news/bin> to the news user's path and F<~news/man>
+to the news user's manpath in your shell config files. (You may want to
+do this, especially the second part, on your regular account; the
+manpages are very useful.)
+
+You can do this now or later, but you will certainly want the manpages
+to help with configuring INN.
+
+For bash, try:
+
+ PATH=~news/bin:$PATH
+ export PATH
+ MANPATH=~news/man:$MANPATH
+ export MANPATH
+
+or csh:
+
+ setenv PATH ~news/bin:$PATH
+ setenv MANPATH ~news/man:$MANPATH
+
+although if you don't already have MANPATH set, the above may give an
+error or override your defaults (making it so you can only read the news
+manpages); if C<echo $MANPATH> does not give some reasonable path,
+you'll need to look up what the default is for your system (such as
+F</usr/man> or F</usr/share/man>).
+
+=back
+
+=head1 Compile
+
+=over 4
+
+=item *
+
+Download the INN tarball and unpack.
+
+=item *
+
+Work out configure options (C<./configure --help> for a list). If you
+aren't working out of F</usr/local/news>, or want to put some files on a
+different partition, you can set the directories now (or later in
+F<inn.conf> if you change your mind).
+
+You probably want C<--with-perl>. If you're not using NetBSD with
+cycbuffs or OpenBSD, perhaps C<--with-tagged-hash>. You might want to
+compile in SSL and Berkeley DB, if your system supports them.
+
+ ./configure --with-perl ...
+ make
+
+ su
+ make install
+
+(If you do the last step as root, all of the ownerships and permissions
+will be correct.)
+
+=back
+
+=head1 Configure
+
+=over 4
+
+=item *
+
+Find F<INSTALL> and open a separate window for it. A printout is
+probably a good idea -- it's long but very helpful. Any time the
+instructions below ask you to make a decision, you can probably find
+help in INSTALL.
+
+=item *
+
+Now it's time to work on the files in F<~news/etc/>. Start with
+F<inn.conf>; you must fill in the default moderators address, your fully
+qualified domain names and path. Fill in all the blanks. Change the
+file descriptor limits to something like 500.
+
+=item *
+
+If using cycbuffs (the CNFS storage method), open F<cycbuff.conf> in one
+window and a shell in another to create the cycbuff as described in
+INSTALL. As you create them, record in cycbuff.conf the paths and
+sizes. Save paths and sizes in a separate text file on another machine
+in case you ever blow away the wrong file.
+
+Name the metacycbuff, then configure F<storage.conf>.
+
+=item *
+
+In F<storage.conf>, be sure that all sizes of articles can be
+accomodated. If you want to throw away large articles, do it explicitly
+by using the "trash" storage method.
+
+=item *
+
+The default options in F<expire.ctl> work fine if you have cycbuffs, if
+not, configure to suit.
+
+=item *
+
+Check over F<moderators> and F<control.ctl>.
+
+=item *
+
+Run F<~news/bin/inncheck> and fix anything noted.
+
+Inncheck gives a rough check on the appropriateness of the configuration
+files as you go. (It's the equivalent of C<perl -cw yourfile.pl> for
+perl scripts.)
+
+Note that inncheck is very conservative about permissions; there's no
+reason most of the config files can't be world-readable if you prefer
+that.
+
+=item *
+
+Import an active file (F<~news/db/active>) and run inncheck again.
+Change where noted (there's a gotcha in the ISC's active list 000000
+000000 (whatever number of zeros) should be 0000000 00000001).
+
+=item *
+
+Create empty initial db files. Be sure these end up owned by news.
+
+ cd ~news/db
+
+ touch newsgroups
+ touch active.times
+
+ touch history
+ ~news/bin/makedbz -i
+ mv history.n.hash history.hash
+ mv history.n.index history.index
+ mv history.n.dir history.dir
+
+ chmod 644 *
+
+=item *
+
+Create the cron jobs and make the changes to your system's
+F<syslog.conf> as noted in INSTALL. Also create the cron job for
+nntpsend if you've chosen that over innfeed.
+
+Create the log files.
+
+=item *
+
+For the time being, we can see if everything initially works without
+worrying about feeds or reader access.
+
+=back
+
+=head1 Run
+
+=over 4
+
+=item *
+
+Start inn by running ~news/bin/rc.news I<as the news user>.
+
+Check F<~news/log/news.notice> to see if everything went well, also use
+C<ps> to see if innd is running.
+
+C<telnet localhost 119> and you should see either a welcome banner or a
+"no permission to talk" message. If not, investigate.
+
+=item *
+
+C<man ctlinnd> now; you'll use C<ctlinnd reload> as you complete your
+configuration.
+
+=back
+
+=head1 Feeds
+
+All of this can be done while INN is running.
+
+=over 4
+
+=item *
+
+To get your incoming feeds working, edit F<incoming.conf>. When done,
+C<ctlinnd reload incoming.conf reason> (where "reason" is some text that
+will show up in the logs, anything will do).
+
+=item *
+
+To get your outgoing feeds working, decide whether to use innfeed or
+nntpsend. Edit F<newsfeeds> and either F<innfeed.conf> or
+F<nntpsend.ctl>.
+
+In newsfeeds, if using innfeed, use the option which doens't require you
+to do a separate innfeed configuration unless you know more than I do.
+
+Then C<ctlinnd reload newsfeeds reason>.
+
+=item *
+
+In readers.conf, remember that auth and access can be separated.
+
+Begin with auth. Your auth for password users could look like this:
+
+ auth "foreignokay" {
+ auth: "ckpasswd -d ~news/db/newsusers"
+ default: "<unauthenticated>"
+ }
+
+There is a perl script in the ckpasswd man page if you want to do
+authentications by password and have the appropriate libraries. Copy it
+to ~news/bin, name the file something like makepasswd.pl and change the
+internal paths to whatever you're using and wherever you're putting the
+newsusers database. The standard Apache C<htpasswd> tool also works
+just fine to create INN password files.
+
+Follow with the access stanzas. Something for people with passwords:
+
+ access "generalpeople" {
+ users: "*"
+ newsgroups: "*,!junk,!control,!control.*"
+ }
+
+And then something like one of the following two, depending on whether
+unauthenticated users get any access:
+
+ access "restrictive" {
+ users: "<unauthenticated>"
+ newsgroups: "!*"
+ }
+
+ access "readonly" {
+ users: "<unauthenticated>"
+ read: "local.*"
+ post: "!*"
+ }
+
+You don't need to reload anything after modifying F<readers.conf>; every
+time an nnrpd launches it reads its configuration from disk.
+
+=back
+
--- /dev/null
+=head1 NAME
+
+ckpasswd - nnrpd password authenticator
+
+=head1 SYNOPSIS
+
+B<ckpasswd> [B<-gs>] [B<-d> I<database>] [B<-f> I<filename>]
+[B<-u> I<username> B<-p> I<password>]
+
+=head1 DESCRIPTION
+
+B<ckpasswd> is the basic password authenticator for nnrpd, suitable for
+being run from an auth stanza in I<readers.conf>. See readers.conf(5) for
+more information on how to configure an nnrpd authenticator.
+
+B<ckpasswd> accepts a username and password from nnrpd and tells nnrpd(8)
+whether that's the correct password for that username. By default, when
+given no arguments, it tries to check the password using PAM if support
+for PAM was found when INN was built. Failing that, it tries to check the
+password against the password field returned by getpwnam(3). Note that
+these days most systems no longer make real passwords available via
+getpwnam(3) (some still do if and only if the program calling getpwnam(3)
+is running as root).
+
+When using PAM, B<ckpasswd> identifies itself as C<nnrpd>, not as
+C<ckpasswd>, and the PAM configuration must be set up accordingly. The
+details of PAM configuration are different on different operating systems
+(and even different Linux distributions); see L<EXAMPLES> below for help
+getting started, and look for a pam(7) or pam.conf(4) manual page on your
+system.
+
+When using any method other than PAM, B<ckpasswd> expects all passwords to
+be stored encrypted by the system crypt(3) function and calls crypt(3) on
+the supplied password before comparing it to the expected password. IF
+you're using a different password hash scheme (like MD5), you must use
+PAM.
+
+=head1 OPTIONS
+
+=over 4
+
+=item B<-d> I<database>
+
+Read passwords from a database (ndbm or dbm format depending on what your
+system has) rather than by using getpwnam(3). B<ckpasswd> expects
+I<database>.dir and I<database>.pag to exist and to be a database keyed by
+username with the encrypted passwords as the values.
+
+While INN doesn't come with a program intended specifically to create such
+databases, on most systems it's fairly easy to write a Perl script to do
+so. Something like:
+
+ #!/usr/bin/perl
+ use NDBM_File;
+ use Fcntl;
+ tie (%db, 'NDBM_File', '/path/to/database', O_RDWR|O_CREAT, 0640)
+ or die "Cannot open /path/to/database: $!\n";
+ $| = 1;
+ print "Username: ";
+ my $user = <STDIN>;
+ chomp $user;
+ print "Password: ";
+ my $passwd = <STDIN>;
+ chomp $passwd;
+ my @alphabet = ('.', '/', 0..9, 'A'..'Z', 'a'..'z');
+ my $salt = join '', @alphabet[rand 64, rand 64];
+ $db{$user} = crypt ($passwd, $salt);
+ untie %db;
+
+Note that this will echo back the password when typed; there are obvious
+improvements that could be made to this, but it should be a reasonable
+start. Sometimes a program like this will be available with the name
+B<dbmpasswd>.
+
+This option will not be available on systems without dbm or ndbm
+libraries.
+
+=item B<-f> I<filename>
+
+Read passwords from the given file rather than using getpwnam(3). The
+file is expected to be formatted like a system password file, at least
+vaguely. That means each line should look something like:
+
+ username:pdIh9NCNslkq6
+
+(and each line may have an additional colon after the encrypted password
+and additional data; that data will be ignored by B<ckpasswd>). Lines
+starting with a number sign (`#') are ignored. INN does not come with a
+utility to create the encrypted passwords, but B<htpasswd> (which comes
+with Apache) can do so and it's a quick job with Perl (see the example
+script under B<-d>). If using Apache's B<htpasswd> program, be sure to
+give it the B<-d> option so that it will use crypt(3).
+
+=item B<-g>
+
+Attempt to look up system group corresponding to username and return a
+string like "user@group" to be matched against in F<readers.conf>. This
+option is incompatible with the B<-d> and B<-f> options.
+
+=item B<-p> I<password>
+
+Use I<password> as the password for authentication rather than reading a
+password using the nnrpd authenticator protocol. This option is useful
+only for testing your authentication system (particularly since it
+involves putting a password on the command line), and does not work when
+B<ckpasswd> is run by B<nnrpd>. If this option is given, B<-u> must also
+be given.
+
+=item B<-s>
+
+Check passwords against the result of getspnam(3) instead of getpwnam(3).
+This function, on those systems that supports it, reads from /etc/shadow
+or similar more restricted files. If you want to check passwords supplied
+to nnrpd(8) against system account passwords, you will probably have to
+use this option on most systems.
+
+Most systems require special privileges to call getspnam(3), so in order
+to use this option you may need to make B<ckpasswd> setgid to some group
+(like group "shadow") or even setuid root. B<ckpasswd> has not been
+specifically audited for such uses! It is, however, a very small program
+that you should be able to check by hand for security.
+
+This configuration is not recommended if it can be avoided, for serious
+security reasons. See L<readers.confZ<>(5)/SECURITY CONSIDERATIONS> for
+discussion.
+
+=item B<-u> I<username>
+
+Authenticate as I<username>. This option is useful only for testing (so
+that you can test your authentication system easily) and does not work
+when B<ckpasswd> is run by B<nnrpd>. If this option is given, B<-p> must
+also be given.
+
+=back
+
+=head1 EXAMPLES
+
+See readers.conf(5) for examples of nnrpd(8) authentication configuration
+that uses B<ckpasswd> to check passwords.
+
+An example PAM configuration for F</etc/pam.conf> that tells B<ckpasswd>
+to check usernames and passwords against system accounts is:
+
+ nnrpd auth required pam_unix.so
+ nnrpd account required pam_unix.so
+
+Your system may want you to instead create a file named F<nnrpd> in
+F</etc/pam.d> with lines like:
+
+ auth required pam_unix.so
+ account required pam_unix.so
+
+This is only the simplest configuration. You may be able to include
+common shared files, and you may want to stack other modules, either to
+allow different authentication methods or to apply restrictions like lists
+of users who can't authenticate using B<ckpasswd>. The best guide is the
+documentation for your system and the other PAM configurations you're
+already using.
+
+To test to make sure that B<ckpasswd> is working correctly, you can run it
+manually and then give it the username (prefixed with C<ClientAuthname:>)
+and password (prefixed with C<ClientPassword:>) on standard input. For
+example:
+
+ (echo 'ClientAuthname: test' ; echo 'ClientPassword: testing') \
+ | ckpasswd -f /path/to/passwd/file
+
+will check a username of C<test> and a password of C<testing> against the
+username and passwords stored in F</path/to/passwd/file>. On success,
+B<ckpasswd> will print C<User:test> and exit with status 0. On failure,
+it will print some sort of error message and exit a non-zero status.
+
+=head1 HISTORY
+
+Written by Russ Allbery <rra@stanford.edu> for InterNetNews.
+
+$Id: ckpasswd.pod 7526 2006-08-12 22:31:11Z eagle $
+
+=head1 SEE ALSO
+
+readers.conf(5), nnrpd(8)
+
+Linux users who want to use PAM should read the Linux-PAM System
+Administrator's Guide at
+L<http://www.kernel.org/pub/linux/libs/pam/Linux-PAM-html/Linux-PAM_SAG.html>.
+
+=cut
--- /dev/null
+=head1 NAME
+
+control.ctl - Specify handling of Usenet control messages
+
+=head1 DESCRIPTION
+
+F<control.ctl> in I<pathetc> is used to determine what action is taken
+when a control message is received. It is read by B<controlchan>, which
+is normally invoked as a channel program by B<innd>. When F<control.ctl>
+is modified, B<controlchan> notices this automatically and reloads it.
+
+Blank lines and lines beginning with a number sign (C<#>) are ignored.
+All other lines should consist of four fields separated by colons:
+
+ <type>:<from>:<newsgroups>:<action>
+
+The first field, <type>, is the type of control message for which this
+line is valid. It should either be the name of a control message or the
+word C<all> to indicate that it applies to all control messages.
+
+The second field, <from>, is a shell-style pattern that matches the e-mail
+address of the person posting the message (with the address first
+converted to lowercase). The matching is done with rules equivalent to
+those of the shell's I<case> statement; see sh(1) for more details.
+
+If the control message is a newgroup or rmgroup, the third field,
+<newsgroups>, is a shell-style pattern matching the newsgroup affected by
+the control message. If the control message is a checkgroups, the third
+field is a shell-style pattern matching the newsgroups that should be
+processed for checking. If the control message is of any other type, the
+third field is ignored.
+
+The fourth field, <action>, specifies what action to take with control
+messages that match this line. The following actions are understood:
+
+=over 4
+
+=item B<doit>
+
+The action requested by the control message should be performed. For
+checkgroups messages, this means that the shell commands that should
+be run will be mailed to the news administrator (the argument to
+B<--with-news-master> given at configure time, C<usenet> by default); for
+other commands, this means that the change will be silently performed. If
+you always want notification of actions taken, use C<doit=mail> instead (see
+below).
+
+=item B<doifarg>
+
+If the control message has an argument, this is equivalent to B<doit>. If
+it does not have an argument, this is equivalent to B<mail>. This is only
+useful for entries for sendsys control messages, allowing a site to
+request its own F<newsfeeds> entry by posting a C<sendsys mysite> control
+message, but not allowing the entire F<newsfeeds> file to be sent. This
+was intended to partially counter so-called "sendsys bombs," where forged
+sendsys control messages were used to mailbomb people.
+
+Processing sendsys control messages is not recommended even with this
+work-around unless they are authenticated in some fashion. The risk of
+having news servers turned into anonymous mail bombing services is too
+high.
+
+=item B<doit>=I<file>
+
+The action is performed as in B<doit>, and additionally a log entry is
+written to the specified log file I<file>. If I<file> is the word
+C<mail>, the log entry is mailed to the news administrator instead. An
+empty string is equivalent to F</dev/null> and says to log nothing.
+
+If I<file> starts with a slash, it is taken as the absolute filename to
+use for the log file. Otherwise, the filename is formed by prepending
+I<pathlog> and a slash and appending C<.log>. In other words, an action
+of C<doit=newgroup> will log to I<pathlog>/newgroup.log.
+
+=item B<drop>
+
+No action is taken and the message is ignored.
+
+=item B<verify-*>
+
+If the action starts with the string C<verify->, as in:
+
+ verify-news.announce.newgroups
+
+then PGP verification of the control message will be done and the user ID
+of the key of the authenticated signer will be checked against the
+expected identity defined by the rest of the string
+(C<news.announce.newgroups> in the above example. This verification is
+done via B<pgpverify>; see pgpverify(8) for more details.
+
+If no logging is specified (with =I<file> as mentioned below), logging will
+be done the same as with B<doit> as described above.
+
+=item B<verify-*>=B<mail>
+
+PGP verification is done as for the B<verify-*> action described above, and
+notification of successful newgroup and rmgroup control messages and the
+output of checkgroups messages will be mailed to the news administrator.
+(In the case of checkgroups messages, this means that the shell script that
+should be run will be mailed to the administrator.)
+
+=item B<verify-*>=I<file>
+
+PGP verification is done as for the B<verify-*> action described above,
+and a log entry is written to the specified file as described in
+B<doit>=I<file> above. (In the case of checkgroups messages, this means
+that the shell script output of the checkgroups message will be written to
+that file.)
+
+=item B<log>
+
+A one-line log message is sent to standard error. B<innd> normally
+directs this to I<pathlog>/errlog.
+
+=item B<log>=I<file>
+
+A log entry is written to the specified log file, which is interpreted as
+in B<doit>=I<file> described above.
+
+=item B<mail>
+
+A mail message is sent to the news administrator without taking any other
+action.
+
+=back
+
+Processing of a checkgroups message will never actually change the
+F<active> file (the list of groups carried by the server). The difference
+between a B<doit> or B<verify> action and a B<mail> action for a
+checkgroups control message lies only in what e-mail is sent; B<doit> or
+B<verify> will mail the news administrator a shell script to create,
+delete, or modify newsgroups to match the checkgroups message, whereas
+B<mail> will just mail the entire message. In either case, the news
+administrator will have to take action to implement the checkgroups
+message, and if that mail is ignored, nothing will be changed.
+
+Lines are matched in order and the last matching line in the file will be
+used.
+
+Use of the B<verify> action for processing newgroup, rmgroup, and
+checkgroups messages is STRONGLY recommended. Abuse of control messages
+is rampant, and authentication via PGP signature is currently the only
+reliable way to be sure that a control message comes from who it claims to
+be from. Most major hierarchies are now issuing PGP-authenticated control
+messages.
+
+In order to use B<verify> actions, the PGP key ring of the news user must
+be populated with the PGP keys of the hierarchy maintainers whose control
+messages you want to honor. For more details on PGP-authenticated control
+messages and the URL for downloading the PGP keys of major hierarchies,
+see pgpverify(8).
+
+Control messages of type cancel are handled internally by B<innd> and
+cannot be affected by any of the mechanisms described here.
+
+=head1 EXAMPLE
+
+With the following three lines in F<control.ctl>:
+
+ newgroup:*:*:drop
+ newgroup:group-admin@isc.org:comp.*:verify-news.announce.newgroups
+ newgroup:kre@munnari.oz.au:aus.*:mail
+
+a newgroup coming from C<group-admin@isc.org> will be honored if it is for
+a newsgroup in the comp.* hierarchy and if it has a valid signature
+corresponding to the PGP key with a user ID of C<news.announce.newgroups>.
+If any newgroup claiming to be from C<kre@munnari.oz.au> for a newsgroup
+in the aus.* hierarchy is received, it too will be honored. All other
+newgroup messages will be ignored.
+
+=head1 WARNINGS
+
+The third argument for a line affecting checkgroups does B<not> affect
+whether the line matches. It is only used after a matching line is found,
+to filter out which newsgroups listed in the checkgroups will be
+processed. This means that a line like:
+
+ checkgroups:*:*binaries*:drop
+
+will cause B<all> checkgroups control messages to be dropped unless they
+match a line after this one in F<control.ctl>, not just ignore newsgroups
+containing C<binaries> in the name. The general rule is to never use C<*>
+in the second field for a line matching checkgroups messages. There is
+unfortunately no way to do what the author of a line like the above
+probably intended to do (yet).
+
+=head1 HISTORY
+
+Written by Rich $alz <rsalz@uunet.uu.net> for InterNetNews. Rewritten in
+POD by Russ Allbery <rra@stanford.edu>.
+
+$Id: control.ctl.pod 7569 2006-08-30 18:12:53Z eagle $
+
+=head1 SEE ALSO
+
+controlchan(8), inn.conf(5), innd(8), newsfeeds(5), pgpverify(8), sh(1).
+
+=cut
--- /dev/null
+=head1 NAME
+
+convdate - Convert time/date strings and numbers
+
+=head1 SYNOPSIS
+
+B<convdate> [B<-dhl>] [B<-c> | B<-n> | B<-s>] [I<date> ...]
+
+=head1 DESCRIPTION
+
+B<convdate> translates the date/time strings given on the command line,
+outputting the results one to a line. The input can either be a date in
+some format that parsedate(3) can parse or the number of seconds since
+epoch (if B<-c> is given). The output is either ctime(3) results, the
+number of seconds since epoch, or a Usenet Date: header, depending on the
+options given.
+
+=head1 OPTIONS
+
+=over 4
+
+=item B<-c>
+
+Each argument is taken to be the number of seconds since epoch (a time_t)
+rather than a date.
+
+=item B<-d>
+
+Output a valid Usenet Date: header instead of the results of ctime(3) for
+each date given on the command line. This is useful for testing the
+algorithm used to generate Date: headers for local posts. Normally, the
+date will be in UTC, but see the B<-l> option.
+
+=item B<-h>
+
+Print usage information and exit.
+
+=item B<-l>
+
+Only makes sense in combination with B<-d>. If given, Date: headers
+generated will use the local time zone instead of UTC.
+
+=item B<-n>
+
+Rather than outputting the results of ctime(3) or a Date: header, output
+each date given as the number of seconds since epoch (a time_t). This
+option doesn't make sense in combination with B<-d>.
+
+=item B<-s>
+
+Pass each given date to parsedate(3) and print the results of ctime(3) (or
+a Date: header if B<-d> is given). This is the default behavior.
+
+=back
+
+=head1 EXAMPLES
+
+Note that relative times or times with partial information use the current
+time to fill in the rest of the date, so dates like "12pm" are taken to be
+12pm of the day when convdate is run. This is a property of parsedate(3);
+see the man page for more information. Most of these examples are from
+the original man page dating from 1991 and were run in the -0400 time
+zone.
+
+ % convdate 'feb 10 10am'
+ Sun Feb 10 10:00:00 1991
+
+ % convdate 12pm 5/4/90
+ Fri Dec 13 00:00:00 1991
+ Fri May 4 00:00:00 1990
+
+Note that 12pm and 5/4/90 are two *separate* arguments and therefore
+result in two results. Note also that a date with no time is taken to be
+at midnight.
+
+ % convdate -n 'feb 10 10am' '12pm 5/4/90'
+ 666198000
+ 641880000
+
+ % convdate -c 666198000
+ Sun Feb 10 10:00:00 1991
+
+ctime(3) results are in the local time zone. Compare to:
+
+ % convdate -dc 666198000
+ Sun, 10 Feb 1991 15:00:00 +0000 (UTC)
+
+ % env TZ=PST8PDT convdate -dlc 666198000
+ Sun, 10 Feb 1991 07:00:00 -0800 (PST)
+
+ % env TZ=EST5EDT convdate -dlc 666198000
+ Sun, 10 Feb 1991 10:00:00 -0500 (EST)
+
+The system library functions generally use the environment variable TZ to
+determine (or at least override) the local time zone.
+
+=head1 HISTORY
+
+Written by Rich $alz <rsalz@uunet.uu.net>, rewritten and updated by Russ
+Allbery <rra@stanford.edu> for the B<-d> and B<-l> flags.
+
+$Id: convdate.pod 3362 2000-06-13 02:57:51Z rra $
+
+=head1 SEE ALSO
+
+parsedate(3).
--- /dev/null
+=head1 NAME
+
+cycbuff.conf - Configuration file for INN CNFS storage method
+
+=head1 DESCRIPTION
+
+This file defines the cyclical buffers that make up the storage pools for
+CNFS (Cyclic News File System). Some options controlling the behavior of
+the CNFS storage system can also be set here. F<cycbuff.conf> is required
+if the CNFS (Cyclic News File System) storage method is used. INN will
+look for it in I<pathetc> (as set in F<inn.conf>).
+
+For information about how to configure INN to use CNFS, see
+storage.conf(5).
+
+Blank lines and lines beginning with a hash sign (C<#>) are ignored. All
+other lines must be of one of the following forms:
+
+ cycbuffupdate:<interval>
+ refreshinterval:<interval>
+ cycbuff:<name>:<file>:<size>
+ metacycbuff:<name>:<buffer>[,<buffer>,...][:<mode>]
+
+(where items enclosed in [] are optional). Order is mostly not
+significant, but all I<cycbuff> lines must occur before all I<metacycbuff>
+lines. Long lines can be continued on the next line by ending the line
+with a backslash (C<\>).
+
+=over 4
+
+=item cycbuffupdate:<interval>
+
+Sets the number of articles are written before the cycbuff header is
+written back to disk to <interval>. Under most operating systems, the
+header doesn't have to be written to disk for the updated data to be
+available to other processes on the same system that are reading articles
+out of CNFS, but any accesses to the CNFS cycbuffs over NFS will only see
+the data present at the last write of the header. After a system crash,
+all updates since the last write of the CNFS header may be lost. The
+default value, if this line is omitted, is 25, meaning that the header is
+written to disk after every 25 articles stored in that cycbuff.
+
+=item refreshinterval:<interval>
+
+Sets the interval (in seconds) between re-reads of the cycbuff header to
+<interval>. This primarily affects B<nnrpd> and controls the frequency
+with which it updates its knowledge of the current contents of the CNFS
+cycbuffs. The default value, if this line is omitted, is 30.
+
+=item cycbuff:<name>:<file>:<size>
+
+Configures a particular CNFS cycbuff. <name> is a symbolic name for the
+buffer, to be used later in a metacycbuff line. It must be no longer than
+seven characters. <file> is the full path to the buffer file or block
+device, and must be no longer than 63 characters. <size> is the length of
+the buffer in kilobytes (1KB is 1024 bytes). If <file> is not a block
+device, it should be <size> * 1024 bytes long.
+
+=item metacycbuff:<name>:<buffer>[,<buffer>,...][:<mode>]
+
+Specifies a collection of CNFS buffers that make up a single logical
+storage location from the perspective of INN. Metacycbuffs are referred
+to in F<storage.conf> as storage locations for articles, so in order to
+actually put articles in a cycbuff, it has to be listed as part of some
+metacycbuff which is then referenced in F<storage.conf>.
+
+<name> is the symbolic name of the metacycbuff, referred to in the options
+field of cnfs entries in F<storage.conf>. <buffer> is the name of a
+cycbuff (the <name> part of a cycbuff line), and any number of cycbuffs
+may be specified, separated by commas.
+
+If there is more than one cycbuff in a metacycbuff, there are two ways
+that INN can distribute articles between the cycbuffs. The default mode,
+INTERLEAVE, stores the articles in each cycbuff in a round-robin fashion,
+one article per cycbuff in the order listed. If the cycbuffs are of
+wildly different sizes, this can cause some of them to roll over much
+faster than others, and it may not give the best performance depending on
+your disk layout. The other storage mode, SEQUENTIAL, instead writes to
+each cycbuff in turn until that cycbuff is full and then moves on to the
+next one, returning to the first and starting a new cycle when the last
+one is full. To specify a mode rather than leaving it at the default, add
+a colon and the mode (INTERLEAVE or SEQUENTIAL) at the end of the
+metacycbuff line.
+
+=back
+
+B<innd> only reads F<cycbuff.conf> on startup, so if you change anything
+in this file and want B<innd> to pick up the changes, you have to stop and
+restart it. C<ctlinnd reload all ''> is not sufficient.
+
+When articles are stored, the cycbuff into which they're stored is saved
+as part of the article token. In order for INN to retrieve articles from
+a cycbuff, that cycbuff must be listed in F<cycbuff.conf>. However, if
+INN should not write to a cycbuff, it doesn't need to be (and shouldn't
+be) listed in a metacycbuff.
+
+This provides an easy way to retire a cycbuff. Just remove it from its
+metacycbuff, leaving in the cycbuff line, and restart B<innd> (with, for
+example, C<ctlinnd xexec innd>). No new articles will be put into the
+cycbuff, but neither will any articles expire from it. After you no
+longer need the articles in the cycbuff, just remove it entirely from
+F<cycbuff.conf>. Then all of the articles will appear to have been
+deleted to INN, and the next nightly expire run will clean up any
+remaining references to them.
+
+Adding a new cycbuff just requires creating it (see below), adding a
+cycbuff line, adding it to a metacycbuff, and then restarting B<innd>.
+
+=head1 CREATING CYCBUFFS
+
+When creating a new cycbuff, there are two different methods for creating
+the buffers in which the articles will be stored.
+
+=over 4
+
+=item 1.
+
+Create a large file on top of a regular file system. The easiest way to
+do this is probably with dd(1), using a command like:
+
+ dd if=/dev/zero of=/path/to/cycbuff bs=1024 count=<size>
+
+where <size> is the size from the cycbuff line in F<cycbuff.conf>.
+F<INSTALL> contains a script that will generate these commands for you
+from your F<cycbuff.conf> file.
+
+This is the simplest method, but has the disadvantage that very large
+files on regular file systems can be fairly slow to access, particularly
+at the end of the file, and INN incurs unnecessary file system overhead
+when accessing the cycbuff.
+
+=item 2.
+
+Use block devices directly. If your operating system allows you to call
+mmap() on block devices (Solaris and recent versions of Linux do, FreeBSD
+at last report does not), this is the recommended method since you can
+avoid all of the native file system overhead. Note, however, that each
+cycbuff cannot be larger than 2GB with this method, so if you need a lot
+of spool space, you may have to go back to disk files.
+
+Partition the disk to make each partition equal to or smaller than 2GB.
+If you're using Solaris, set up your partitions to avoid the first
+cylinder of the disk (or otherwise the cycbuff header will overwrite the
+disk partition table and render the cycbuffs inaccessible). Then, create
+device files for each block device you're going to use.
+
+It's not recommended to use the block device files in F</dev>, since the
+news system doesn't have permission to write to them and changing the
+permissions of the system device files may affect something else.
+Instead, use mknod(1) to create a new set of block devices (in somewhere
+like I<pathspool>/cycbuffs that's only writable by the news user). To do
+this, run C<ls -Ll> on the devices in F</dev> that correspond to the block
+devices that you want to use. The major and minor device numbers are in
+the fifth and sixth columns (right before the date), respectively. Then
+run mknod like:
+
+ mknod <file> b <major> <minor>
+
+where <file> is the path to the device to create (matching the <file> part
+of the cycbuff line) and <major> and <minor> are the major and minor
+device numbers as discovered above.
+
+Here's a short script to do this when given the path to the system device
+file as an argument:
+
+ #!/bin/sh
+ base=`echo "$1" | sed 's%.*/%%'`
+ major=`ls -Ll "$1" | awk '{print $5}' | tr -d ,`
+ minor=`ls -Ll "$1" | awk '{print $6}`
+ mkdir -p /usr/local/news/spool/cycbuffs
+ mknod /usr/local/news/spool/cycbuffs/"$base" b "$major" "$minor"
+ chown news:news /usr/local/news/spool/cycbuffs/"$base"
+ chmod 644 /usr/local/news/spool/cycbuffs/"$base"
+
+Make sure that the created files are owned by the news user and news
+group, as specified at configure time (the default being C<news> for
+both). Also make sure that the permissions on the devices allow the news
+user to read and write, and if you want other users on the system to be
+able to use B<sm> to retrieve articles, make sure they're world-readable.
+
+=back
+
+Once you have everything configured properly and you start B<innd>, you
+should see messages in F<news.notice> that look like:
+
+ innd: CNFS-sm No magic cookie found for cycbuff ONE, initializing
+
+where ONE will be whatever you called your cycbuff.
+
+=head1 HISTORY
+
+Written by Katsuhiro Kondou <kondou@nec.co.jp> for InterNetNews.
+Rewritten into POD by Russ Allbery <rra@stanford.edu>.
+
+$Id: cycbuff.conf.pod 7860 2008-06-07 12:46:49Z iulius $
+
+=head1 SEE ALSO
+
+ctlinnd(8), innd(8), nnrpd(8), sm(1), storage.conf(5)
+
+=cut
--- /dev/null
+=head1 NAME
+
+distrib.pats - Default values for the Distribution header
+
+=head1 DESCRIPTION
+
+The file I<pathetc>/distrib.pats is used by B<nnrpd> to determine the
+default value of the Distribution header. Blank lines and lines beginning
+with a number sign (C<#>) are ignored. All other lines consist of three
+fields separated by a colon:
+
+ <weight>:<pattern>:<value>
+
+The first field is the weight to assign to this match. If a newsgroup
+matches multiple lines, the line with the highest weight is used. This
+should be an arbitrary integer greater than zero. The order of lines in
+the file is only important if groups have equal weight (in which case, the
+first matching line will be used).
+
+The second field is either the name of a newsgroup or a uwildmat(3)-style
+pattern to specify a set of newsgroups.
+
+The third field is the value that should be used for the Distribution
+header of a posted article, if this line was picked as the best match and
+no Distribution header was supplied by the user. It can be an empty
+string, specifying that no Distribution header should be added.
+
+When a post is received by B<nnrpd> that does not already contain a
+Distribution header, each newsgroup to which an article is posted will be
+checked against this file in turn, and the matching line with the highest
+weight will be used as the value of the Distribution header. If no lines
+match, or if the matching line has an empty string for its third field, no
+header will be added.
+
+=head1 HISTORY
+
+Written by Rich $alz <rsalz@uunet.uu.net> for InterNetNews. Converted to
+POD by Russ Allbery <rra@stanford.edu>.
+
+$Id: distrib.pats.pod 6282 2003-04-06 21:50:07Z rra $
+
+=head1 SEE ALSO
+
+inn.conf(5), nnrpd(8), uwildmat(3)
+
+=cut
--- /dev/null
+=head1 NAME
+
+domain - nnrpd domain resolver
+
+=head1 SYNOPSIS
+
+B<domain> B<domainname>
+
+=head1 DESCRIPTION
+
+This program can be used in F<readers.conf> to grant access based on the
+subdomain part of the remote hostname. In particular, it only returns
+success if the remote hostname ends in B<domainname>. (A leading dot on
+B<domainname> is optional; even without it, the argument must match on
+dot-separated boundaries). The "username" returned is whatever initial
+part of the remote hostname remains after B<domainname> is removed. It
+is an error if there is no initial part (that is, if the remote hostname
+is I<exactly> the specified B<domainname>).
+
+=head1 EXAMPLE
+
+The following readers.conf(5) fragment grants access to hosts with
+internal domain names:
+
+ auth internal {
+ res: "domain .internal"
+ default-domain: "example.com"
+ }
+
+ access internal {
+ users: "*@example.com"
+ newsgroups: example.*
+ }
+
+Access is granted to the example.* groups for all connections from hosts
+that resolve to hostnames ending in C<.internal>; a connection from
+"foo.internal" would match access groups as "foo@example.com".
+
+=head1 BUGS
+
+It seems the code does not confirm that the matching part is actually at
+the end of the remote hostname (e.g., "domain: example.com" would match
+the remote host "foo.example.com.org" by ignoring the trailing ".org"
+part).
+
+Does this resolver actually provide any useful functionality not
+available by using wildcards in the readers.conf(5) I<hosts> parameter?
+If so, the example above should reflect this functionality.
+
+=head1 HISTORY
+
+This documentation was written by Jeffrey M. Vinocur <jeff@litech.org>.
+
+$Id: domain.pod 5988 2002-12-12 23:02:14Z vinocur $
+
+=head1 SEE ALSO
+
+nnrpd(8), readers.conf(5)
+
+=cut
--- /dev/null
+=head1 NAME
+
+expire.ctl - Configuration file for article expiration
+
+=head1 DESCRIPTION
+
+The file I<pathetc>/expire.ctl is the default configuration file for
+B<expire> and B<expireover>, which read it at start-up. It serves two
+purposes: it defines how long history entries for expired or rejected
+articles are remembered, and it determines how long articles stored on the
+server are retained.
+
+Normally, if all of the storage methods used by the server are
+self-expiring (such as CNFS), all lines except the C</remember/> setting
+(described below) are ignored. This can be changed with the B<-N> option
+to B<expire> or B<expireover>.
+
+Black lines and lines beginning with a number sign (C<#>) are ignored.
+All other lines should be in one of two formats. The order of the file is
+significant, and the last matching entry will be used.
+
+The first format specifies how long to keep history entries for articles
+that aren't present in the news spool. These are articles that have
+either already expired, or articles which the server rejected (when
+I<remembertrash> is set to true in F<inn.conf>). There should be one and
+only one line in this format, which looks like:
+
+ /remember/:<days>
+
+where <days> is a decimal number that specifies the minimum number of days
+a history record for a given message ID is retained, regardless of whether
+the article is present in the spool. (History entries for articles still
+present in the spool are always retained.)
+
+The primary reason to retain a record of old articles is in case a peer
+offers old articles that were previously accepted but have already
+expired. Without a history record for such articles, the server would
+accept the article again and readers would see duplicate articles.
+Articles older than a certain number of days won't be accepted by the
+server at all (see I<artcutoff> in inn.conf(5) and the B<-c> flag in
+innd(8)), and this setting should probably match that time period (10 days
+by default) to ensure that the server never accepts duplicates.
+
+Most of the lines in this file will be in the second format, which
+consists of either four or five colon-separated fields:
+
+ <pattern>:<flag>:<min>:<default>:<max>
+
+if I<groupbaseexpiry> is true in F<inn.conf> (the default), and otherwise:
+
+ <classnum>:<min>:<default>:<max>
+
+All lines must be in the correct format given the current setting of
+I<groupbaseexpiry>, and therefore the two formats cannot co-exist in the
+same file.
+
+Normally, a rule matches a newsgroup through the combination of the
+<pattern> and <flag> fields. <pattern> is a uwildmat(3)-style pattern,
+specifying the newsgroups to which the line is applied. Note that the
+last matching entry will be used, so general patterns (such as defaults
+for all groups where <pattern> is C<*>) should appear at the beginning of
+the file before more specific settings.
+
+The <flag> field can be used to further limit newsgroups to which the line
+applies, and should be chosen from the following set:
+
+ M Only moderated groups
+ U Only unmoderated groups
+ A All groups
+ X Remove the article from all groups it appears in
+
+One of M, U, or A must be specified. X should be used in combination with
+one of the other letters, not by itself.
+
+An expiration policy is applied to every article in a newsgroup it
+matches. There is no way to set an expiration policy for articles
+crossposted to groups you don't carry that's different than other articles
+in the same group. Normally, articles are not completely deleted until
+they expire out of every group to which they were posted, but if an
+article is expired following a rule where <flag> contains X, it is deleted
+out of all newsgroups to which it was posted immediately.
+
+If I<groupbaseexpiry> is instead set to false, there is no <pattern> and
+<flag> field and the above does not apply. Instead, there is a single
+<classnum> field, which is either a number matching the storage class
+number specified in F<storage.conf> or C<*> to specify a default for all
+storage classes. All articles stored in a storage class will be expired
+following the instructions in the line with a matching <classnum>, and
+when articles are expired, they're always removed from all groups to which
+they were posted.
+
+The remaining three fields are the same in either format, and are used to
+determine how long an article should be kept. Each field should be either
+a decimal number of days (fractions like C<8.5> are allowed, but remember
+that articles are only removed when B<expire> or B<expireover> is run,
+normally once a day by B<news.daily>) or the word C<never>.
+
+The middle field, <default>, will be used as the expiration period for
+most articles. The other two fields, <min> and <max>, only come into
+play if the article requests a particular expiration date with an Expires
+header. Articles with an Expires header will be expired at the date given
+in that header, subject to the constraints that they will be retained at
+least <min> days and no longer than <max> days.
+
+If <min> is set to C<never>, no article matching that line will ever be
+expired. If <default> is set to C<never>, no article matching that line
+without an explicit Expires header will ever be expired. If <max> is
+set to C<never>, Expires headers will be honored no matter how far into
+the future they are.
+
+One should think of the fields as a lower bound, the default, and an upper
+bound. Since most articles do not have an Expires header, the second
+field is the most important and most commonly applied.
+
+Articles that do not match any expiration rule will not be expired, but
+this is considered an error and will result in a warning. There should
+always be a default line (a line with a <pattern> of C<*> and <flag> of
+C<A>, or a line with a <classnum> of C<*>), which can explicitly state
+that articles should never expire by default if that's the desired
+configuration. The default line should generally be the first line of the
+file (except for C</remember/>) so that other expiration rules can
+override it.
+
+It is often useful to honor the Expires header in articles, especially
+those in moderated groups. To do this, set <min> to zero, <default> to
+whatever normal expiration you wish, and <max> to C<never> or some large
+number, like 365 days for a maximum article life of a year.
+
+To ignore any Expires header, set all three fields to the same value.
+
+=head1 EXAMPLES
+
+When I<groupbaseexpiry> is true (the default):
+
+ # Keep expired article history for 10 days, matching artcutoff.
+ /remember/:10
+
+ # Most articles stay for two weeks, ignoring Expires.
+ *:A:14:14:14
+
+ # Accept Expires headers in moderated groups for up to a year and
+ # retain moderated groups for a bit longer.
+ *:M:1:30:365
+
+ # Keep local groups for a long time and local project groups forever.
+ example.*:A:90:90:90
+ example.project.*:A:never:never:never
+
+When I<groupbaseexpiry> is false, for class-based expiration:
+
+ # Keep expired article history for 10 days, matching artcutoff.
+ /remember/:10
+
+ # Set a default expiration of seven days.
+ *:7:7:7
+
+ # Class 0 is retained for two weeks.
+ 0:14:14:14
+
+=head1 HISTORY
+
+Written by Rich $alz <rsalz@uunet.uu.net> for InterNetNews. Converted to
+POD by Russ Allbery <rra@stanford.edu>.
+
+$Id: expire.ctl.pod 7207 2005-04-11 18:18:40Z rra $
+
+=head1 SEE ALSO
+
+expire(8), expireover(8), inn.conf(5), innd(8), news.daily(8),
+storage.conf(5), uwildmat(3)
+
+=cut
--- /dev/null
+=head1 NAME
+
+expireover - Expire entries from the news overview database
+
+=head1 SYNOPSIS
+
+B<expireover> [B<-ekNpqs>] [B<-f> I<file>] [B<-w> I<offset>]
+[B<-z> I<rmfile>] [B<-Z> I<lowmarkfile>]
+
+=head1 DESCRIPTION
+
+B<expireover> expires old entries from the news overview database. It
+reads in a list of newsgroups (by default from I<pathdb>/active, but a
+different file can be specified with the B<-f> option) and then removes
+from the overview database mentions of any articles that no longer exist
+in the news spool.
+
+If I<groupbaseexpiry> in I<inn.conf> is true, B<expireover> also removes
+old articles from the news spool according to the expiration rules in
+F<expire.ctl>. Otherwise it only removes overview entries for articles
+that have already been removed by some other process, and B<-e>, B<-k>,
+B<-N>, B<-p>, B<-q>, B<-w>, and B<-z> are all ignored.
+
+When I<groupbaseexpiry> is set, the default behavior of B<expireover> is
+to remove the article from the spool once it expires out of all of the
+newsgroups to which it was crossposted. The article is, however, removed
+from the overview database of each newsgroup as soon as it expires out of
+that individual newsgroup. The effect is that an article crossposted to
+several groups will be removed from the overview database from each group
+one-by-one as its age passes the expiration threshold for that group as
+set in F<expire.ctl>, and then when it expires out of the last newsgroup,
+it will be deleted from the news spool.
+
+Articles that are stored in self-expiring storage backends such as CNFS
+are normally treated differently and not expired until they expire out of
+the backend regardless of F<expire.ctl>. See B<-N>, however.
+
+By default, B<expireover> purges all overview information for newsgroups
+that have been removed from the server; this behavior is suppressed if
+B<-f> is given.
+
+=head1 OPTIONS
+
+=over 4
+
+=item B<-e>
+
+Remove articles from the news spool and all overview databases as soon as
+they expire out of any newsgroup to which they are posted, rather than
+retain them until they expire out of all newsgroups. B<-e> and B<-k>
+cannot be used at the same time. This flag is ignored if
+I<groupbaseexpiry> is false.
+
+=item B<-f> I<file>
+
+Use I<file> as the newsgroup list instead of I<pathdb>/active. I<file>
+can be C<-> to indicate standard input. Using this flag suppresses the
+normal purge of all overview information from newsgroups that have been
+removed from the server.
+
+=item B<-k>
+
+Retain all overview information for an article, as well as the article
+itself, until it expires out of all newsgroups to which it was posted.
+This can cause articles to stick around in a newsgroup for longer than the
+F<expire.ctl> rules indicate, when they're crossposted. B<-e> and B<-k>
+cannot be used at the same time. This flag is ignored if
+I<groupbaseexpiry> is false.
+
+=item B<-N>
+
+Apply F<expire.ctl> rules to expire articles even from storage methods
+that have self-expire functionality. This may remove articles from
+self-expiring storage methods before the articles "naturally" expire.
+This flag is ignored if I<groupbaseexpiry> is false.
+
+=item B<-p>
+
+By default, B<expireover> bases decisions on whether to remove an article
+on the arrival time on the server. This means that articles may be kept a
+little longer than if the decision were based on the article's posting
+date. If this option is given, expiration decisions are based on the
+article posting date instead. This flag is ignored if I<groupbaseexpiry>
+is false.
+
+=item B<-q>
+
+B<expireover> normally prints statistics at the end of the expiration
+process. B<-q> suppresses this report. This flag is ignored if
+I<groupbaseexpiry> is false.
+
+=item B<-s>
+
+B<expireover> normally only checks the existence of articles in the news
+spool if querying the storage method for that article to see if it still
+exists is considered "inexpensive." To always check the existence of all
+articles regardless of how resource-intensive this may be, use the B<-s>
+flag. See storage.conf(5) for more information about this metric.
+
+=item B<-w> I<offset>
+
+"Warps" time so that B<expireover> thinks that it's running at some time
+other than the current time. This is occasionally useful to force groups
+to be expired or not expired without changing F<expire.ctl> for the expire
+run. I<offset> should be a signed floating point number specifying the
+number of days difference from the current time to use as "now." This
+flag is ignored if I<groupbaseexpiry> is false.
+
+=item B<-z> I<rmfile>
+
+Don't remove articles immediately but instead write the path to the
+article or the token of the article to I<rmfile>, which is suitable input
+for fastrm(1). This can substantially speed up deletion of expired
+articles for those storage methods where each article is a single file
+(such as tradspool and timehash). See the description of I<delayrm> in
+news.daily(8) for more details. This flag is ignored if
+I<groupbaseexpiry> is false.
+
+=item B<-Z> I<lowmarkfile>
+
+Write the lowest article numbers for each newsgroup as it's expired to the
+specified file. This file is then suitable for C<ctlinnd lowmark>. See
+ctlinnd(8) for more information.
+
+=back
+
+=head1 EXAMPLES
+
+Normally B<expireover> is invoked from news.daily(8), which handles such
+things as processing the I<rmfile> and I<lowmarkfile> if necessary.
+Sometimes it's convenient to manually expire a particular newsgroup,
+however. This can be done with a command like:
+
+ echo example.test | expireover -f - -Z /usr/local/news/tmp/lowmark
+ ctlinnd lowmark /usr/local/news/tmp/lowmark
+
+This can be particularly useful if a lot of articles in a particular group
+have expired but the overview information is still present, causing some
+clients to see a lot of "this article may have been cancelled" messages
+when they first enter the newsgroup.
+
+=head1 HISTORY
+
+Written by Rob Robertson <rob@violet.berkeley.edu> and Rich $alz
+<rsalz@uunet.uu.net> (with help from Dave Lawrence <tale@uunet.uu.net>)
+for InterNetNews.
+
+$Id: expireover.pod 5909 2002-12-03 05:17:18Z vinocur $
+
+=head1 SEE ALSO
+
+active(5), ctlinnd(8), expire(8), expire.ctl(5), inn.conf(5),
+news.daily(8).
+
+=cut
--- /dev/null
+=head1 NNRPD External Authentication Support
+
+This is $Revision: 7141 $ dated $Date: 2005-03-17 03:42:46 -0800 (Thu, 17 Mar 2005) $.
+
+A fundamental part of the readers.conf(5)-based authorization mechanism
+is the interface to external authenticator and resolver programs. This
+interface is documented below.
+
+INN ships with a number of such programs (all written in C, although any
+language can be used). Code for them can be found in F<authprogs/> of
+the source tree; the authenticators are installed to
+I<pathbin>/auth/passwd, and the resolvers are installed to
+I<pathbin>/auth/resolv.
+
+=head1 Reading information from nnrpd
+
+When nnrpd spawns an external auth program, it passes information on
+standard input as a sequence of "key: value" lines. Each line ends with
+CRLF, and a line consisting of only "." indicates the end of the input.
+The order of the fields is not significant. Additional fields not
+mentioned below may be included; this should not be cause for alarm.
+
+(For robustness as well as ease of debugging, it is probably wise to
+accept line endings consisting only of LF, and to treat EOF as
+indicating the end of the input even if "." has not been received.)
+
+Code which reads information in the format discussed below and parses it
+into convenient structures is available authenticators and resolvers
+written in C; see libauth(3) for details. Use of the libauth library
+will make these programs substantially easier to write and more robust.
+
+=head2 For authenticators
+
+When nnrpd calls an authenticator, the lines it passes are
+
+ ClientAuthname: user\r\n
+ ClientPassword: pass\r\n
+
+where I<user> and I<pass> are the username and password provided by the
+client (e.g. using AUTHINFO). In addition, nnrpd generally also passes
+the fields mentioned as intended for resolvers; it rare instances this
+data may be useful for authenticators.
+
+=head2 For resolvers
+
+When nnrpd calls a resolver, the lines it passes are
+
+ ClientHost: hostname\r\n
+ ClientIP: IP-address\r\n
+ ClientPort: port\r\n
+ LocalIP: IP-address\r\n
+ LocalPort: port\r\n
+ .\r\n
+
+where I<hostname> indicates a string representing the hostname if
+available, I<IP-address> is a numeric IP address (dotted-quad for IPv4,
+equivalent for IPv6 if appropriate), and I<port> is a numeric port
+number. (The I<LocalIP> paramter may be useful for determining which
+interface was used for the incoming connection.)
+
+If information is not available, nnrpd will omit the corresponding
+fields. In particular, this applies to the unusual situation of nnrpd
+not being connected to a socket; TCP-related information is not
+available for standard input.
+
+=head1 Returning information to nnrpd
+
+=head2 Exit status and signals
+
+The external auth program must exit with a status of C<0> to indicate
+success; any other exit status indicates failure. (The non-zero exit
+value will be logged.)
+
+If the program dies due to catching a signal (for example, a
+segmentation fault occurs), this will be logged and treated as a
+failure.
+
+=head2 Returning a username and domain
+
+If the program succeeds, it must return a username string (optionally
+with a domain appended) by writing to standard output. The line it
+should write is exactly:
+
+ user:username\r\n
+
+where I<username> is the string that nnrpd should use in matching
+readers.conf access blocks.
+
+There should be no extra spaces in lines sent from the hook to nnrpd;
+C<user:aidan> is read by nnrpd as a different username than C<user:
+aidan>.
+
+=head1 Error messages
+
+As mentioned above, errors can be indicated by a non-zero exit value, or
+termination due to an unhandled signal; both cases are logged by nnrpd.
+However, external auth programs may wish to log error messages
+separately.
+
+Although nnrpd will syslog() anything an external auth program writes to
+standard error, it is generally better to use the F<messages.h>
+functions, such as warn() and die().
+
+Please use the ckpasswd.c program as an example for any authenticators
+you write, and ident.c as an example for any resolvers.
+
+=head1 HISTORY
+
+Written by Aidan Cully for InterNetNews. This documentation rewritten
+in POD by Jeffrey M. Vinocur <jeff@litech.org>.
+
+=cut
--- /dev/null
+=head1 NAME
+
+fastrm - Quickly remove a list of files
+
+=head1 SYNOPSIS
+
+B<fastrm> [B<-de>] [B<-u>|B<-u>I<N>] [B<-s>|B<-s>I<M>] [B<-c>|B<-c>I<I>]
+I<base-directory>
+
+=head1 DESCRIPTION
+
+B<fastrm> reads a list of either file names or storage API tokens, one per
+line, from its standard input and removes them. Storage API tokens are
+removed via the SMcancel() interface. B<fastrm> does not delete files
+safely or with an eye to security, but rather cuts every corner it can to
+delete files as fast as it can. It should therefore never be run on
+publically writable directories, or in any other environment where a
+hostile party may control the directory structure in which it is working.
+
+If a file name is not an absolute path name, it is considered to be
+relative to I<base-directory> as given on the command line. The
+I<base-directory> parameter must be a simple absolute pathname (it must
+not contain multiple consecutive slashes or references to the special
+directories C<.> or C<..>).
+
+B<fastrm> is designed to be faster than the typical C<| xargs rm> pipeline
+when given a sorted list of file names as input. For example, B<fastrm>
+will usually chdir(2) into a directory before removing files from it,
+meaning that if its input is sorted, most names passed to unlink(2) will
+be simple names. This can substantially reduce the operating system
+overhead from directory lookups.
+
+B<fastrm> assumes that its input is valid and that it is safe to call
+unlink(2) on every file name it is given. As a safety measure, however,
+B<fastrm> when running as root will check with stat(2) that a file name
+doesn't specify a directory before removing it. (In some operating
+systems, root is allowed to unlink directories, even directories which
+aren't empty, which can cause file system corruption.)
+
+The input to B<fastrm> should always be sorted -- or even better be in the
+order file names are output by find(1) -- if speed is an issue and the
+input isn't solely storage API tokens. (It deals fine with unsorted
+input, but is unlikely to be any faster in that case than a simple C<xargs
+rm> command.) Sorting may even slightly speed up the removal of storage
+API tokens due to caching effects, since sorting will tend to keep all of
+the tokens from a particular storage method together.
+
+Various additional optimizations for removing files can be turned on
+and/or tuned with options (see below). Which options will be most
+effective depends heavily on the underlying structure of the file system,
+the way in which directories are stored and searched, and similar, often
+underdocumented, operating system implementation details. The more
+sophisticated the underlying operating system and file system, the more
+likely that it will already perform the equivalent of these optimizations
+internally.
+
+=head1 OPTIONS
+
+=over 4
+
+=item B<-d>
+
+Don't remove any files. Instead, print a list of the files that would be
+removed to standard output. Each line contains either the current
+directory of B<fastrm> at the time it would do the unlink and the relative
+path name it would pass to unlink(2) as two fields separated by whitespace
+and a C</>, the absolute path name (as a single field) that would be
+passed to unlink(2), or the string C<Token> and the storage API token that
+would be removed.
+
+=item B<-e>
+
+Treat an empty input file as an error. This is most useful when B<fastrm>
+is last in a pipeline after a preceding sort(1) command, ensuring that
+B<fastrm> will fail if the sort fails.
+
+=item B<-c>I<I>
+
+Controls when B<fastrm> calls chdir(2). If the number of files to be
+unlinked from a given directory is at least I<I>, then B<fastrm> will
+change to that directory before unlinking those files. Otherwise, it will
+use either the absolute path names or a path name relative to the current
+directory (whichever is likely more efficient). The I<I> parameter is
+optional; if just B<-c> is given, B<-c1> is assumed, which will cause
+B<fastrm> to always chdir before calling unlink(2). The default is
+B<-c3>. Use B<-c0> to prevent B<fastrm> from ever using chdir(2).
+
+=item B<-s>I<M>
+
+When B<-s> is given and the number of files to remove in a directory is
+greater than I<M>, rather than remove files in the order given, B<fastrm>
+will open the directory and read it, unlinking files in the order that
+they appear in the directory. On systems with a per-process directory
+cache or that use a linear search to find files in a directory, this
+should make directory lookups faster. The I<M> parameter is optional; if
+just B<-s> is given, B<-s5> is assumed.
+
+When this option is in effect, B<fastrm> won't attempt to remove files
+that it doesn't see in the directory, possibly significantly speeding it
+up if most of the files to be removed have already been deleted. However,
+using this option requires B<fastrm> to do more internal work and it also
+assumes that the order of directory listings is stable in the presence of
+calls to unlink(2) between calls to readdir(3). This may be a dangerous
+assumption with some sophisticated file systems (and in general this
+option is only useful with file systems that use unindexed linear searches
+to find files in directories or when most of the files to be removed have
+already been deleted).
+
+This optimization is off by default.
+
+=item B<-u>I<N>
+
+Specifying this option promises that there are no symbolic links in the
+directory tree from which files are being removed. This allows B<fastrm>
+to make an additional optimization to its calls to chdir(2), constructing
+a relative path using C<../..> and the like to pass to chdir(2) rather
+than always using absolute paths. Since this reduces the number of
+directory lookups needed with deeply nested directory structures (such as
+that typically created by traditional news spool storage), it can be a
+significant optimization, but it breaks horribly in the presence of
+symbolic links to directories.
+
+When B<-u> is given, B<fastrm> will use at most I<N> levels of C<..>
+segments to construct paths. I<N> is optional; if just B<-u> is given,
+B<-u1> is assumed.
+
+This optimization is off by default.
+
+=back
+
+B<fastrm> also accepts B<-a> and B<-r> options, which do nothing at all
+except allow you to say C<fastrm -usa>, C<fastrm -ussr>, or C<fastrm
+-user>. These happen to often be convenient sets of options to use.
+
+=head1 EXIT STATUS
+
+B<fastrm> exits with a status of zero if there were no problems, and an
+exit status of 1 if something went wrong. Attempting to remove a file
+that does not exist is not considered a problem.
+
+=head1 EXAMPLES
+
+B<fastrm> is typically invoked by INN via expirerm(8) using a command
+like:
+
+ fastrm -e /usr/local/news/spool/articles < expire.list
+
+To enable all optimizations and see the affect on the order of removal
+caused by B<-s>, use:
+
+ fastrm -d -s -e -u ~news/spool/articles < expire.list
+
+If your file system has indexed directory lookups, but you have a deeply
+nested directory structure, you may want to use a set of flags like:
+
+ fastrm -e -u3 ~news/spool/articles < expire.list
+
+to strongly prefer relative paths but not to use readdir(2) to order the
+calls to unlink(2).
+
+You may want to edit expirerm(8) to change the flags passed to B<fastrm>.
+
+=head1 WARNINGS
+
+B<fastrm> cuts corners and does not worry about security, so it does not
+use chdir(2) safely and could be tricked into removing files other than
+those that were intended if run on a specially constructed file tree or a
+file tree that is being modified while it is running. It should therefore
+never be used with world-writable directories or any other directory that
+might be controlled or modified by an attacker.
+
+=head1 NOTES
+
+B<fastrm> defers opening the storage subsystem or attempting to parse any
+INN configuration files until it encounters a token in the list of files
+to remove. It's therefore possible to use B<fastrm> outside of INN as a
+general fast file removal program.
+
+=head1 HISTORY
+
+B<fastrm> was originally written by kre@munnari.oz.au. This manual page
+rewritten in POD by Russ Allbery <rra@stanford.edu> for InterNetNews.
+
+$Id: fastrm.pod 7422 2005-10-09 04:46:53Z eagle $
+
+=head1 SEE ALSO
+
+expirerm(8)
+
+=cut
--- /dev/null
+=head1 NAME
+
+grephistory - Query the INN history database
+
+=head1 SYNOPSIS
+
+B<grephistory> [B<-eilnqsv>] [B<-f> I<db>] [I<message-id>]
+
+=head1 DESCRIPTION
+
+B<grephistory> queries the INN history database for information about the
+specified message ID. If no flags are given, the program prints the
+storage API token of the corresponding article, or C</dev/null> if the
+article is listed in the history database but not stored on the server.
+If the message ID cannot be found in the database, B<grephistory> will
+print C<grephistory: not found> and exit with a non-zero status.
+
+Be sure to escape any special characters in the message ID from the shell.
+Single quotes are recommended for this purpose since many message IDs
+contain dollar signs.
+
+=head1 OPTIONS
+
+=over 4
+
+=item B<-e>
+
+Only print the storage token if the article is stored on the system. (In
+other words, suppress the C</dev/null> or C<not found> output for missing
+or remembered articles.)
+
+=item B<-f> I<db>
+
+Query the history database I<db> rather than the default history database.
+
+=item B<-i>
+
+Rather than expecting a message ID on the command line, B<grephistory>
+will read a list of message IDs on standard input, one per line. Leading
+and trailing whitespace is ignored, as are any malformed lines. It will
+print out standard output those message IDs which are not found in the
+history database. This is used when processing C<ihave> control messages.
+
+=item B<-l>
+
+Display the entire line from the history database, rather than just the
+storage API token.
+
+=item B<-n>
+
+If the message ID is present in the history database but has no storage
+API token, print C</dev/null> and exit successfully. This can happen if
+an article has been cancelled or expired, but history information has
+still been retained. This is the default behavior.
+
+=item B<-q>
+
+Don't print any message, but still exit with the appropriate status.
+
+=item B<-s>
+
+Rather than expecting a message ID on the command line, B<grephistory>
+will read a list of message IDs on standard input, one per line. Leading
+and trailing whitespace is ignored, as are any malformed lines. It will
+print on standard output the storage API tokens for any articles that are
+still available, one per line. This flag is used when processing
+C<sendme> control messages.
+
+=item B<-v>
+
+Print out the hash of the message ID for diagnostic purposes, as well as
+any other requested information. This flag is not useful with B<-s>.
+
+=head1 HISTORY
+
+Written by Rich $alz <rsalz@uunet.uu.net> for InterNetNews. Rewritten in
+POD by Russ Allbery <rra@stanford.edu>.
+
+$Id: grephistory.pod 7045 2004-12-19 19:37:45Z rra $
+
+=head1 SEE ALSO
+
+history(5), inn.conf(5)
+
+=cut
--- /dev/null
+=head1 Hacking INN
+
+This file is for people who are interested in making modifications to INN.
+Normal users can safely skip reading it. It is intended primarily as a
+guide, resource, and accumulation of tips for maintainers and
+contributors, and secondarily as documentation of some of INN's internals.
+
+This is $Revision: 7518 $ dated $Date: 2006-04-14 19:52:06 -0700 (Fri, 14 Apr 2006) $.
+
+First of all, if you plan on working on INN source, please start from the
+current development tree. There may be significant changes from the
+previous full release, so starting from development sources will make it
+considerably easier to integrate your work. You can get nightly snapshots
+of the current development source from ftp.isc.org in /isc/inn/snapshots
+(the snapshots named inn-CURRENT-*.tar.gz), or you can get the current CVS
+tree by using CVSup (see L<"Using CVSup">).
+
+=head1 Configuring and Portability
+
+All INN code should be written expecting ANSI C and POSIX. There is no
+need to attempt to support pre-ANSI compilers, and ANSI-only features such
+as <stdarg.h>, string concatenation, #elif, and token pasting may be used
+freely. So far as possible, INN is written to attempt to be portable to
+any system new enough that someone is likely to want to run a news server
+on it, but whenever possible this portability should be provided by
+checking for standard behavior in configure and supplying replacements for
+standard functions that are missing.
+
+When there is a conflict between ANSI C and C99, INN code should be
+written expecting C99 and autoconf used to patch up the differences.
+
+Try to avoid using #ifdef and the like in the middle of code as much as
+possible. Instead, try to isolate the necessary portability bits and
+include them in libinn or at least in conditional macros separate from the
+code. Trying to read code littered with conditional compilation
+directives is much more difficult.
+
+The shell script F<configure> at the top level of the source tree is
+generated by autoconf from F<configure.in>, and F<include/config.h.in> is
+generated by autoheader from F<configure.in> and F<include/acconfig.h>.
+At configure time, configure generates F<include/config.h> and several
+other files based on options it was given and what it discovers about the
+target system.
+
+All modifications to configure should instead be made to F<configure.in>.
+Similarly, modifications to F<include/config.h.in> should instead be made
+to F<include/acconfig.h>. The autoconf manual (available using info
+autoconf if you have autoconf and the GNU info utilities installed on your
+system) is a valuable reference when making any modifications.
+
+To regenerate configure, just run C<autoconf>. To regenerate
+F<include/config.h.in>, run:
+
+ autoheader -l include
+
+to tell it where to find acconfig.h. Please don't include patches to
+either F<configure> or F<include/config.h.in> when sending patches to INN;
+instead, note in your patch that those files must be regenerated.
+
+The generated files are checked into the CVS repository so that people
+working on INN don't have to have autoconf on their system, and to make
+packaging easier.
+
+At the time of this writing, autoconf 2.13 is required.
+
+The supporting files for autoconf are in the F<support> subdirectory,
+including the files F<config.guess> and F<config.sub> to determine the
+system name and and F<ltmain.sh> for libtool support. The latter file
+comes from the libtool distribution; the canonical version of the former
+two are available from ftp.gnu.org in /gnu/config. In addition,
+F<m4/libtool.m4> is just a copy of F<libtool.m4> from the libtool
+distribution. (Using libtool without using automake requires a few odd
+hacks.) These files used to be on a separate vendor branch so that we
+could make local modifications, but local modifications have not been
+necessary for some time. Now, new versions can just be checked in like
+any other file modifications.
+
+INN should not compile with libtool by default, only when requested, since
+otherwise normal compilations are quite slow. (Using libtool is not
+without some cost.) Basic compilation with libtool works fine as of this
+writing, with both static and shared compiles, but the dependencies aren't
+quite right for make -j using libtool.
+
+=head1 Documentation
+
+INN's documentation is currently somewhat in a state of flux. The vast
+majority is still in the form of man pages written directly in nroff.
+Some parts of the documentation have been rewritten in POD; that
+documentation can be found in F<doc/pod>. The canonical source for
+F<README>, F<INSTALL>, F<NEWS>, F<doc/hook-perl>, F<doc/hook-python>, and
+this file are also in POD.
+
+If you're modifying some part of INN's documentation and see that it has a
+POD version in F<doc/pod>, it's preferred if you can make the
+modifications to the POD source and then regenerate the derived files.
+For a quick introduction to POD, see the perlpod(1) man page on your
+system (it should be installed if you have Perl installed).
+
+When writing new documentation, write in whatever format you care to; if
+necessary, we can always convert it to POD or whatever else we want to
+use. Having the documentation exist in I<some> form is more important
+than what language you write it in. If you really don't have any
+particular preference, there's a slight preference currently for POD.
+
+If you use POD or regenerate POD documentation, please install something
+close to the latest versions of the POD processing utilities to avoid
+changes to the documentation depending on who generated it last. You can
+find the latest version on CPAN (ftp.perl.org or another mirror) in
+F<modules/by-module/Pod>. You'll need PodParser (for versions of Perl
+before 5.6.1; 5.6.1 and later come with a recent enough version) and the
+latest version of podlators. For versions of Perl earlier than 5.005,
+you'll also need File::Spec in F<modules/by-module/File>.
+
+podlators 1.25 or later will build INN's documentation without significant
+changes from the versions that are checked into the repository.
+
+There are Makefile rules in F<doc/pod/Makefile> to build all of the
+documentation whose master form is POD; if you add additional
+documentation, please add a rule there as well. Documentation should be
+generated by cd'ing to F<doc/pod> and typing C<make file> where C<file> is
+the relative path to the documentation file. This will get all of the
+various flags right for pod2text or pod2man.
+
+=head1 Error Handling
+
+INN has a set of generic error handling routines that should be used as
+much as possible so that the same syntax can be used for reporting errors
+everywhere in INN. The four basic functions are warn, syswarn, die, and
+sysdie; warn prints or logs a warning, and die does the same and then
+exits the current program. The sys* versions add a colon, a space, and
+the value of strerror(errno) to the end of the message, and should be used
+to report failing system calls.
+
+All of the actual error reporting is done via error handlers, and a
+program can register its own handlers in addition to or instead of the
+default one. The default error handler (error_log_stderr) prints to
+stderr, prepending the value of error_program_name if it's set to
+something other than NULL. Three other error handlers are available,
+error_log_syslog_crit, error_log_syslog_err, and error_log_syslog_warning,
+which log the message to syslog at LOG_CRIT, LOG_ERR, or LOG_WARNING
+priority, respectively.
+
+There is a different set of error handlers for warn/syswarn and
+die/sysdie. To set them, make calls like:
+
+ warn_set_handlers(2, error_log_stderr, error_log_syslog_warning);
+ die_set_handlers(2, error_log_stderr, error_log_syslog_err);
+
+The first argument is the number of handlers, and the remaining arguments
+are pointers to functions taking an int (the length of the formatted
+message), a const char * (the format), a va_list (the arguments), and an
+int that's 0 if warn or die was called and equal to the value of errno if
+syswarn or sysdie was called. The length of the formatted message is
+obtained by calling vsnprintf with the provided format and arguments, and
+therefore is reliable to use as the size of a buffer to malloc to hold the
+result of formatting the message provided that vsnprintf is used to format
+it (warning: the system vsprintf may produce more output under some
+circumstances, so always use vsnprintf).
+
+The error handler can do anything it wishes; each error handler is called
+in the sequence given. Error handlers shouldn't call warn or die unless
+great caution is taken to prevent infinite recursion. Also be aware that
+sysdie is called if malloc fails in xmalloc, so if the error handler needs
+to allocate memory, it must not use xmalloc or a related function to do
+so and it shouldn't call die to report failure. The default syslog
+handlers report memory allocation failure to stderr and exit.
+
+Finally, die and sysdie support an additional handler that's called
+immediate before exiting, takes no arguments, and returns an int which is
+used as the argument for exit. It can do any necessary global cleanup,
+call abort instead to generate a core dump or the like.
+
+The advantage of using this system everywhere in INN is that library code
+can use warn and die to report errors and each calling program can set up
+the error handlers as appropriate to make sure the errors go to the right
+place. The default handler is fine for interactive programs; for programs
+that run from interactive scripts, adding something like:
+
+ error_program_name = "program";
+
+to the beginning of main (where program is the name of the program) will
+make it easier to figure out which program the script calls is failing.
+For programs that may also be called non-interactively, like inndstart,
+one may want to set up handlers like:
+
+ warn_set_handlers(2, error_log_stderr, error_log_syslog_warning);
+ die_set_handlers(2, error_log_stderr, error_log_syslog_err);
+
+Finally, for daemons and other non-interactive programs, one may want to
+do:
+
+ warn_set_handlers(1, error_log_syslog_warning);
+ die_set_handlers(1, error_log_syslog_err);
+
+to report errors only via syslog. (Note that if you use syslog error
+handlers, the program should call openlog first thing to make sure they
+are logged with the right facility.)
+
+For historical reasons, error messages that are fatal to the news
+subsystem are logged at the LOG_CRIT priority, and therefore die in innd
+should use error_log_syslog_crit.
+
+=head1 Test Suite
+
+The test suite for INN is located in the tests directory and is just
+getting started. The test suite consists of a set of programs listed in
+F<tests/TESTS> and the scaffolding in the F<runtests> program.
+
+Adding new tests is very straightforward and very flexible. Just write a
+program that tests some part of INN, put it in a directory under tests
+named after the part of INN it's testing (all the tests so far are in lib
+because they're testing libinn routines), and have it output first a line
+containing the count of test cases in that file, and then for each test a
+line saying "ok n" or "not ok n" where n is the test case number. (If a
+test is skipped for some reason, such as a test of an optional feature
+that wasn't compiled into INN, the test program should output
+S<"ok n # skip">.) Add any rules necessary to build the test to
+tests/Makefile (note that for simplicity it doesn't recurse into
+subdirectories) and make sure it creates an executable ending in F<.t>.
+Then add the name of the test to tests/TESTS, without the .t ending.
+
+One naming convention: to distinguish more easily between e.g.
+lib/error.c (the implementation) and tests/lib/error-t.c (the test suite),
+we add -t to the end of the test file names. So tests/lib/error-t.c is
+the source that compiles into an executable tests/lib/error.t which is run
+by putting a line in tests/TESTS of just "lib/error".
+
+Note that tests don't have to be written in C; in fact, lib/xmalloc.t is
+just a shell script (that calls a supporting C program). Tests can be
+written in shell or Perl (but other languages should be avoided because
+someone who wants to run the test suite may not have it) and just have to
+follow the above output conventions.
+
+Additions to the test suite, no matter how simple, are very welcome.
+
+=head1 Makefiles
+
+All INN makefiles include Makefile.global at the top level, and only that
+makefile is a configure substitution target. This has the disadvantage
+that configure's normal support for building in a tree outside of the
+source tree doesn't work, but it has the significant advantage of making
+configure run much faster and allowing one to run make in any subdirectory
+and pick up all the definitions and settings from the top level
+configuration.
+
+All INN makefiles should also set $(top) to be the path to the top of the
+build directory (usually relative). This path is used to find various
+programs like fixscript and libtool so that the same macros (set in
+Makefile.global) can be used all over INN.
+
+The format of INN's makefiles is mostly standardized; the best examples of
+the format are probably F<frontends/Makefile> and F<backends/Makefile>, at
+least for directories with lots of separate programs. The ALL variable
+holds all the files that should be generated, EXTRA those additional files
+that were generated by configure, and SOURCES the C source files for
+generating tag information.
+
+There are a set of standard installation commands defined in make
+variables by Makefile.global, and these should be used for all file
+installations. See the comment blocks in Makefile.global.in for
+information on what commands are available and when they should be used.
+There are also variables set for each of the installation directories that
+INN uses, for use in building the list of installed paths to files.
+
+Each subdirectory makefile should have the targets all (the default),
+clean, clobber, install, tags, and profiled. The tags target generates vi
+tags files, and the profiled target generates a profiling version of the
+programs (although this hasn't been tested much recently). These rules
+should be present and empty in those directories where they don't apply.
+
+Be sure to test compiling with both static and dynamic libraries and make
+sure that all the libtool support works correctly. All linking steps, and
+the compile steps for all library source, should be done through
+$(LIBTOOL) (which will be set to empty in Makefile.global if libtool
+support isn't desired).
+
+=head1 Scripts
+
+INN comes with and installs a large number of different scripts, both
+Bourne shell and Perl, and also comes with support for Tcl scripts
+(although it doesn't come with any). Shell variables containing both
+configure-time information and configuration information from inn.conf are
+set by the innshellvars support libraries, so the only system-specific
+configuration that should have to be done is fixing the right path to the
+interpretor and adding a line to load the appropriate innshellvars.
+
+F<support/fixscript>, built by configure, does this. It takes a .in file
+and generates the final script (removing the .in) by fixing the path to
+the interpretor on the first line and replacing the second line, whatever
+it is, with code to load the innshellvars appropriate for that
+interpretor. (If invoked with -i, it just fixes the interpretor path.)
+
+Scripts should use innshellvars (via fixscript) to get the right path and
+the right variables whenever possible, rather than having configure
+substitute values in them. Any values needed at run-time should instead
+be available from all of the different innshellvars.
+
+See the existing scripts for examples of how this is done.
+
+=head1 Include Files
+
+Include files relevant to all of INN, or relevant to the two libraries
+built as part of INN (the utility libinn library and the libstorage
+library that contains all storage and overview functions) are found in the
+include directory; other include files relevant only to a portion of INN
+are found in the relevant directory.
+
+Practically all INN source files will start with:
+
+ #include "config.h"
+ #include "clibrary.h"
+
+The first picks up all defines generated by autoconf and is necessary for
+types that may not be present on all systems (uid_t, pid_t, size_t,
+int32_t, and the like). It therefore should be included before any other
+headers that use those types, as well as to get general configuration
+information.
+
+The second is portably equivalent to:
+
+ #include <sys/types.h>
+ #include <stdarg.h>
+ #include <stdio.h>
+ #include <stdlib.h>
+ #include <stddef.h>
+ #include <stdint.h>
+ #include <string.h>
+ #include <unistd.h>
+
+except that it doesn't include headers that are missing on a given system,
+replaces functions not found on the system with the INN equivalents,
+provides macros that INN assumes are available but which weren't found,
+and defines some additional portability things. Even if this is more
+headers than the source file actually needs, it's generally better to just
+include F<clibrary.h> rather than trying to duplicate the autoconf-driven
+hackery that it does to do things portably. The primary exception is for
+source files in lib that only define a single function and are used for
+portability; those may want to include only F<config.h> so that they can
+be easily used in other projects that use autoconf. F<config.h> is a
+fairly standard header name for this purpose.
+
+F<clibrary.h> does also include F<config.h>, but it's somewhat poor form
+to rely on this; it's better to explicitly list the header dependencies
+for the benefit of someone else reading the code.
+
+There are portable wrappers around several header files that have known
+portability traps or that need some fixing up on some platforms. Look in
+include/portable and familiarize yourself with them and use them where
+appropriate.
+
+Another frequently included header file is F<libinn.h>, which among other
+things defines xmalloc(), xrealloc(), xstrdup(), and xcalloc(), which are
+checked versions of the standard memory allocation routines that terminate
+the program if the memory allocation fails. These should generally always
+be used instead of the regular C versions. F<libinn.h> also provides
+various other utility functions that are frequently used.
+
+F<paths.h> includes a wide variety of paths determined at configure time,
+both default paths to various parts of INN and paths to programs. Don't
+just use the default paths, though, if they're also configurable in
+F<inn.conf>; instead, call ReadInnConf() and use the global innconf
+structure.
+
+Other files in include are interfaces to particular bits of INN library
+functionality or are used for other purposes; see the comments in each
+file.
+
+Eventually, the header files will be separated into installed header files
+and uninstalled header files; the latter are those headers that are used
+only for compiling INN and aren't useful for users of INN's libraries
+(such as clibrary.h). All of the installed headers will live in
+include/inn and be installed in a subdirectory named inn in the configured
+include directory. This conversion is still in progress.
+
+When writing header files, remember that C reserves all identifiers
+beginning with two underscores and all identifiers beginning with an
+underscore and a capital letter for the use of the implementation; don't
+use any identifiers with names like that. Additionally, any identifier
+beginning with an underscore and a lower-case letter is reserved in file
+scope, which means that such identifiers can only be used by INN for the
+name of structure members or function arguments in function prototypes.
+
+Try to pay attention to the impact of a header file on the program
+namespace, particularly for installed header files in include/inn. All
+symbols defined by a header file should ideally begin with INN_, inn_, or
+some other unique prefix indicating the subsystem that symbol is part of,
+to avoid accidental conflicts with symbols defined by the program that
+uses that header file.
+
+=head1 Coding Style
+
+INN has quite a variety of coding styles intermixed. As with all
+programs, it's preferrable when making minor modifications to keep the
+coding style of the code you're modifying. In INN, that will vary by
+file. (Over time we're trying to standardize on one coding style, so
+changing the region you worked on to fit the general coding style is also
+acceptable).
+
+If you're writing a substantial new piece of code, the prevailing
+"standard" INN coding style appears to be something like the following:
+
+=over 3
+
+=item *
+
+Write in regular ANSI C whenever possible. Use the normal ANSI and POSIX
+constructs and use autoconf or portability wrappers to fix things up
+beforehand so that the code itself can read like regular ANSI or POSIX
+code. Code should be written so that it works as expected on a modern
+platform and is fixed up with portability tricks for older platforms, not
+the other way around. You may assume an ANSI C compiler.
+
+Try to use const wherever appropriate. Don't use register; modern
+compilers will do as good of a job as you will in choosing what to put
+into a register. Don't bother with restrict (at least yet).
+
+=item *
+
+Use string handling functions that take counts for the size of the buffer
+whenever possible. This means using snprintf in preference to sprintf and
+using strlcpy and strlcat in preference to strcpy and strcat. Also, use
+strlcpy and strlcat instead of strncpy and strncat unless the behavior of
+the latter is specifically required, as it is much easier to audit uses of
+the former than the latter. (strlcpy is like strncpy except that it
+always nul-terminates and doesn't fill the rest of the buffer with nuls,
+making it more efficient. strlcat is like strncat except that it always
+nul-terminates and it takes the total size of the buffer as its third
+argument rather than just the amount of space left.) All of these
+functions are guaranteed to be available; there are replacements in lib
+for systems that don't have them.
+
+=item *
+
+Avoid #ifdef and friends whenever possible. Particularly avoid using them
+in the middle of code blocks. Try to hide all portability preprocessor
+magic in header files or in portability code in lib. When something just
+has to be done two completely different ways depending on the platform or
+compile options or the like, try to abstract that functionality out into a
+generic function and provide two separate implementations using #ifdef;
+then the main code can just call that function.
+
+If you do have to use preprocessor defines, note that if you always define
+them to either 0 or 1 (never use #define without a second argument), you
+can use the preprocessor define in a regular if statement rather than
+using #if or #ifdef. Make use of this instead of #ifdef when possible,
+since that way the compiler will still syntax-check the other branch for
+you and it makes it far easier to convert the code to use a run-time check
+if necessary. (Unfortunately, this trick can't be used if one branch may
+call functions unavailable on a particular platform.)
+
+=item *
+
+Avoid uses of fixed-width buffers except in performance-critical code, as
+it's harder to be sure that such code is correct and it tends to be less
+flexible later on. If you need a reusable, resizable memory buffer, one
+is provided in lib/buffer.c.
+
+=item *
+
+Avoid uses of static variables whenever possible, particularly in
+libraries, because it interferes with making the code re-entrant down the
+road and makes it harder to follow what's going on. Similarly, avoid
+using global variables whenever possible, and if they are required, try to
+wrap them into structures that could later be changed into arguments to
+the affected functions.
+
+=item *
+
+Roughly BSD style but with four-space indents. This means no space before
+the parens around function arguments, open brace on the same line as
+if/while/for, and close and open brace on the same line as else).
+
+=item *
+
+Introductory comments for functions or files are generally written as:
+
+ /*
+ ** Introductory comment.
+ */
+
+Other multiline comments in the source are generally written as:
+
+ /* This is a
+ multiline comment. */
+
+Comments before functions saying what they do are nice to have. In
+general, the RCS/CVS Id tag is on the first line of each source file since
+it's useful to know when a file was last modified.
+
+=item *
+
+Checks for NULL pointers are preferrably written out explicitly; in other
+words, use:
+
+ if (p != NULL)
+
+rather than:
+
+ if (p)
+
+to make it clearer what the code is assuming.
+
+=item *
+
+It's better to always put the body of an if statement on a separate line,
+even if it's only a single line. In other words, write:
+
+ if (p != NULL)
+ return p;
+
+and not:
+
+ if (p != NULL) return p;
+
+This is in part for a practical reason: some code coverage analysis tools
+like purecov will count the second example above as a single line and
+won't notice if the condition always evaluates the same way.
+
+=item *
+
+Plain structs make perfectly reasonable abstract data types; it's not
+necessary to typedef the struct to something else. Structs are actually
+very useful for opaque data structures, since you can predeclare them and
+then manipulate pointers to them without ever having to know what the
+contents look like. Please try to avoid typedefs except for function
+pointers or other extremely confusing data types, or for data types where
+we really gain some significant data abstraction from hiding the
+underlying data type. Also avoid using the _t suffix for any type; all
+types ending in _t are reserved by POSIX. For typedefs of function
+pointer types, a suffix of _func usually works.
+
+This style point is currently widely violated inside of INN itself; INN
+originally made extensive use of typedefs.
+
+=item *
+
+When noting something that should be improved later, add a comment
+containing "FIXME:" so that one can easily grep for such comments.
+
+=back
+
+INN's indentation style roughly corresponds to that produced by GNU indent
+2.2.6 with the following options:
+
+ -bad -bap -nsob -fca -lc78 -cd41 -cp1 -br -ce -cdw -cli0 -ss -npcs
+ -ncs -di1 -nbc -psl -brs -i4 -ci4 -lp -ts8 -nut -ip5 -lps -l78 -bbo
+ -hnl
+
+Unfortunately, indent currently doesn't get everything right (it has
+problems with spacing around struct pointer arguments in functions, wants
+to put in a space between a dereference of a function pointer and the
+arguments to the called function, misidentifies some macro calls as being
+type declarations, and fouls up long but simple case statements). It
+would be excellent if someday we could just run all of INN's code through
+indent routinely to enforce a consistant coding style, but indent isn't
+quite ready for that.
+
+For users of emacs cc-mode, use the "bsd" style but with:
+
+ (setq c-basic-offset 4)
+
+Finally, if possible, please don't use tabs in source files, since they
+can expand differently in different environments. In particular, please
+try not to use the mix of tabs and spaces that is the default in emacs.
+If you use emacs to edit INN code, you may want to put:
+
+ ; Use only spaces when indenting or centering, no tabs.
+ (setq-default indent-tabs-mode nil)
+
+in your ~/.emacs file.
+
+Note that this is only a rough guideline and the maintainers aren't style
+nazis; we're more interested in your code contribution than in how you
+write it.
+
+=head1 Using CVSup
+
+If you want to get updated INN source more easily or more quickly than by
+downloading nightly snapshots, or if you want to see the full CVS history,
+you may want to use CVSup to download the source. CVSup is a client and
+server designed for replicating CVS repositories between sites.
+
+Unfortunately, CVSup is written in Modula-3, so getting a working binary
+can be somewhat difficult. Binaries are available in the *BSD ports
+collection or (for a wide variety of different platforms) available from
+<ftp://ftp.freebsd.org/pub/FreeBSD/CVSup/binaries/> and its mirrors.
+Alternately, you can get a compiler from <http://m3.polymtl.ca/m3/> (this
+is more actively maintained than the DEC Modula-3 compiler) and the source
+from <ftp://ftp.freebsd.org/pub/FreeBSD/CVSup/>.
+
+After you have the CVSup client, you need to have space to download the
+INN repository and space for CVSup to store its data files. You also need
+to write a configuration file (a supfile) for CVSup. The following
+supfile will download the latest versions from the mainline source:
+
+ *default host=inn-cvs.isc.org
+ *default base=<data-location>
+ *default prefix=<download-location>
+ *default release=cvs
+ *default tag=.
+ *default delete use-rel-suffix
+ inn
+
+where <data-location> should be a directory where CVSup can put its data
+files and <download-location> is where the downloaded source will go (it
+will be put into a subdirectory named inn). If you want to pull down the
+entire CVS repository instead (warning: this is much larger than just the
+latest versions of the source), delete the C<*default tag=.> line. The
+best way to download the CVS repository is to download it into a portion
+of a locally-created CVS repository, so that then you can perform standard
+CVS operations (like cvs log) against the downloaded repository. Creating
+your own local CVS repository is outside the scope of this document.
+
+Note that only multiplexed mode is supported (this mode should be the
+default).
+
+For more general information on using CVSup, see the FreeBSD page on it at
+<http://www.freebsd.org/handbook/mirrors-cvsup.html>.
+
+=head1 Making a Release
+
+This is a checklist that INN maintainers should go through when preparing
+a new release of INN.
+
+=over 4
+
+=item 1.
+
+If making a major release, branch the source tree and create a new STABLE
+branch tag. This branch will be used for minor releases based on that
+major release and can be done a little while before the .0 release of that
+major release. At the same time as the branch is cut, tag the trunk with
+a STABLE-<version>-branch marker tag so that it's easy to refer to the
+trunk at the time of the branch.
+
+=item 2.
+
+Update doc/pod/news.pod and regenerate NEWS. Be more detailed for a minor
+release than for a major release. For a major release, also add
+information on how to upgrade from the last major release, including
+anything special to be aware of. (Minor releases shouldn't require any
+special care when upgrading.)
+
+=item 3.
+
+Make sure that support/config.sub and support/config.guess are the latest
+versions (from <ftp://ftp.gnu.org/gnu/config/>). See the instructions in
+L<"Configuring and Portability"> for details on how to update these files.
+
+=item 4.
+
+Make sure that samples/control.ctl is in sync with the master version at
+<ftp://ftp.isc.org/pub/usenet/CONFIG/control.ctl>.
+
+=item 5.
+
+Check out a copy of the release branch. It's currently necessary to run
+configure to generate Makefile.global. Then run C<make check-manifest>.
+The only differences should be files that are generated by configure; if
+there are any other differences, fix the MANIFEST.
+
+=item 6.
+
+Run C<make release>. Note that you need to have a copy of svn2cl from
+<http://ch.tudelft.nl/~arthur/svn2cl/> to do this; at least version 0.7
+is required. Start the ChangeLog at the time of the previous release.
+(Eventually, the script will be smart enough to do this for you.)
+
+=item 7.
+
+Make the resulting tar file available for testing in a non-listable
+directory on ftp.isc.org and announce its availability on inn-workers.
+Install it on at least one system and make sure that system runs fine for
+at least a few days. This is also a good time to send out a draft of the
+release announcement to inn-workers for proof-reading.
+
+=item 8.
+
+Generate a diff between this release and the previous release if feasible
+(always for minor releases, possibly not a good idea due to the length of
+the diff for major releases).
+
+=item 9.
+
+Move the release into the public area of the ftp site and update the
+inn.tar.gz link. Make an MD5 checksum of the release tarball and put it
+on the ftp site as well, and update the inn.tar.gz.md5 link. Put the diff
+up on the ftp site as well. Contact the ISC folks to get the release
+PGP-signed. Possibly move older releases off into the OLD directory.
+
+=item 10.
+
+Announce the new release on inn-announce and in news.software.nntp.
+
+=item 11.
+
+Tag the checked-out tree that was used for generating the release with a
+release tag (INN-<version>).
+
+=item 12.
+
+Bump the revision number in Makefile.global.in.
+
+=back
+
+=head1 References
+
+Some additional references that may be hard to find and may be of use to
+people working on INN:
+
+=over 4
+
+=item <http://www.eyrie.org/~eagle/nntp/>
+
+The home page for the IETF NNTP standardization effort, including links
+to the IETF NNTP working group archives and copies of the latest drafts
+of the new NNTP standard. The old archived mailing list traffic contains
+a lot of interesting discussion of why NNTP is the way it is.
+
+=item <http://www.imc.org/ietf-usefor/>
+
+The archives for the USEFOR IETF working group, the working group for the
+RFC 1036 replacement (the format of Usenet articles). Also contains a lot
+of references to other relevant work, such as the RFC 822 replacement
+work.
+
+=item <http://www.mibsoftware.com/userkt/inn/dev/>
+
+Forrest Cavalier provides several tools for following INN development at
+this page and elsewhere in the Usenet RKT. Under here is a web-accessible
+checked-out copy of the current INN source tree and pointers to how to use
+CVSup.
+
+=item <http://www.sas.com/standards/large.file/>
+
+The standards for large file support on Unix that are being generally
+implemented by vendors. INN sort of partially uses these, but a good full
+audit of the code to check them should really be done and there are
+occasional problems.
+
+=item <http://v6web.litech.org/ipv6primer/>
+
+A primer on IPv6 with pointers to the appropriate places for more
+technical details as needed, useful when working on IPv6 support in INN.
+
+=back
--- /dev/null
+=head1 INN Perl Filtering and Authentication Support
+
+This is $Revision: 7860 $ dated $Date: 2008-06-07 05:46:49 -0700 (Sat, 07 Jun 2008) $.
+
+This file documents INN's built-in support for Perl filtering and reader
+authentication. The code is based very heavily on work by Christophe
+Wolfhugel <wolf@pasteur.fr>, and his work was in turn inspired by the
+existing TCL support. Please send any bug reports to inn-bugs@isc.org,
+not to Christophe, as the code has been modified heavily since he
+originally wrote it.
+
+The Perl filtering support is described in more detail below. Basically,
+it allows you to supply a Perl function that is invoked on every article
+received by innd from a peer (the innd filter) or by nnrpd from a reader
+(the nnrpd filter). This function can decide whether to accept or reject
+the article, and can optionally do other, more complicated processing
+(such as add history entries, cancel articles, spool local posts into a
+holding area, or even modify the headers of locally submitted posts).
+The Perl authentication hooks allow you to replace or supplement the
+readers.conf mechanism used by nnrpd.
+
+For Perl filtering support, you need to have Perl version 5.004 or newer.
+Earlier versions of Perl will fail with a link error at compilation time.
+http://language.perl.com/info/software.html should have the latest Perl
+version.
+
+To enable Perl support, you have to specify B<--with-perl> when you run
+configure. See F<INSTALL> for more information.
+
+=head1 The innd Perl Filter
+
+When innd starts, it first loads the file _PATH_PERL_STARTUP_INND (defined
+in F<include/paths.h>, by default F<startup_innd.pl>) and then loads the
+file _PATH_PERL_FILTER_INND (also defined in F<include/paths.h>, by
+default F<filter_innd.pl>). Both of these files must be located in the
+directory specified by pathfilter in F<inn.conf>
+(F</usr/local/news/bin/filter> by default). The default directory for
+filter code can be specified at configure time by giving the flag
+B<--with-filter-dir> to configure.
+
+INN doesn't care what Perl functions you define in which files. The only
+thing that's different about the two files is when they're loaded.
+F<startup_innd.pl> is loaded only once, when innd first starts, and is
+never reloaded as long as innd is running. Any modifications to that file
+won't be noticed by innd; only stopping and restarting innd can cause it
+to be reloaded.
+
+F<filter_innd.pl>, on the other hand, can be reloaded on command (with
+C<ctlinnd reload filter.perl 'reason'>). Whenever F<filter_innd.pl> is loaded,
+including the first time at innd startup, the Perl function
+filter_before_reload() is called before it's reloaded and the function
+filter_after_reload() is called after it's reloaded (if the functions
+exist). Additionally, any code in either F<startup_innd.pl> or
+F<filter_innd.pl> at the top level (in other words, not inside a sub { })
+is automatically executed by Perl when the files are loaded.
+
+This allows one to do things like write out filter statistics whenever the
+filter is reloaded, load a cache into memory, flush cached data to disk,
+or other similar operations that should only happen at particular times or
+with manual intervention. Remember, any code not inside functions in
+F<startup_innd.pl> is executed when that file is loaded, and it's loaded
+only once when innd first starts. That makes it the ideal place to put
+initialization code that should only run once, or code to load data that
+was preserved on disk across a stop and restart of innd (perhaps using
+filter_mode() -- see below).
+
+As mentioned above, C<ctlinnd reload filter.perl 'reason'> (or C<ctlinnd reload
+all 'reason'>) will cause F<filter_innd.pl> to be reloaded. If the function
+filter_art() is defined after the file has been reloaded, filtering is
+turned on. Otherwise, filtering is turned off. (Note that due to the way
+Perl stores functions, once you've defined filter_art(), you can't
+undefine it just by deleting it from the file and reloading the filter.
+You'll need to replace it with an empty sub.)
+
+The Perl function filter_art() is the heart of a Perl filter. Whenever an
+article is received from a peer, via either IHAVE or TAKETHIS,
+filter_art() is called if Perl filtering is turned on. It receives no
+arguments, and should return a single scalar value. That value should be
+the empty string to indicate that INN should accept the article, or some
+rejection message to indicate that the article should be rejected.
+
+filter_art() has access to a global hash named %hdr, which contains all of
+the standard headers present in the article and their values. The
+standard headers are:
+
+ Also-Control, Approved, Bytes, Cancel-Key, Cancel-Lock,
+ Content-Base, Content-Disposition, Content-Transfer-Encoding,
+ Content-Type, Control, Date, Date-Received, Distribution, Expires,
+ Face, Followup-To, From, In-Reply-To, Injection-Date, Injection-Info,
+ Keywords, Lines, List-ID, Message-ID, MIME-Version, Newsgroups,
+ NNTP-Posting-Date, NNTP-Posting-Host, Organization, Originator,
+ Path, Posted, Posting-Version, Received, References, Relay-Version,
+ Reply-To, Sender, Subject, Supersedes, User-Agent,
+ X-Auth, X-Canceled-By, X-Cancelled-By, X-Complaints-To, X-Face,
+ X-HTTP-UserAgent, X-HTTP-Via, X-Mailer, X-Modbot, X-Modtrace,
+ X-Newsposter, X-Newsreader, X-No-Archive, X-Original-Message-ID,
+ X-Original-Trace, X-Originating-IP, X-PGP-Key, X-PGP-Sig,
+ X-Poster-Trace, X-Postfilter, X-Proxy-User, X-Submissions-To,
+ X-Trace, X-Usenet-Provider, Xref.
+
+Note that all the above headers are as they arrived, not modified by
+your INN (especially, the Xref: header, if present, is the one of
+the remote site which sent you the article, and not yours).
+
+For example, the Newsgroups: header of the article is accessible
+inside the Perl filter as C<$hdr{'Newsgroups'}>. In addition,
+C<$hdr{'__BODY__'}> will contain the full body of the article and
+C<$hdr{'__LINES__'}> will contain the number of lines in the body of the
+article.
+
+The contents of the %hdr hash for a typical article may therefore look
+something like this:
+
+ %hdr = (Subject => 'MAKE MONEY FAST!!',
+ From => 'Joe Spamer <him@example.com>',
+ Date => '10 Sep 1996 15:32:28 UTC',
+ Newsgroups => 'alt.test',
+ Path => 'news.example.com!not-for-mail',
+ Organization => 'Spammers Anonymous',
+ Lines => '5',
+ Distribution => 'usa',
+ 'Message-ID' => '<6.20232.842369548@example.com>',
+ __BODY__ => 'Send five dollars to the ISC, c/o ...',
+ __LINES__ => 5
+ );
+
+Note that the value of C<$hdr{Lines}> is the contents of the Lines: header
+of the article and may bear no resemblence to the actual length of the
+article. C<$hdr{__LINES__}> is the line count calculated by INN, and is
+guaranteed to be accurate.
+
+The %hdr hash should not be modified inside filter_art(). Instead, if any
+of the contents need to be modified temporarily during filtering (smashing
+case, for example), copy them into a seperate variable first and perform
+the modifications on the copy. Currently, C<$hdr{__BODY__}> is the only
+data that will cause your filter to die if you modify it, but in the
+future other keys may also contain live data. Modifying live INN data in
+Perl will hopefully only cause a fatal exception in your Perl code that
+disables Perl filtering until you fix it, but it's possible for it to
+cause article munging or even core dumps in INN. So always, always make a
+copy first.
+
+As mentioned above, if filter_art() returns the empty string (''), the
+article is accepted. Note that this must be the empty string, not 0 or
+undef. Otherwise, the article is rejected, and whatever scalar
+filter_art() returns (typically a string) will be taken as the reason why
+the article was rejected. This reason will be returned to the remote peer
+as well as logged to the news logs. (innreport, in its nightly report,
+will summarize the number of articles rejected by the Perl filter and
+include a count of how many articles were rejected with each reason
+string.)
+
+One other type of filtering is also supported. If Perl filtering is
+turned on and the Perl function filter_messageid() is defined, that
+function will be called for each message ID received from a peer (via
+either CHECK or IHAVE). The function receives a single argument, the
+message ID, and like filter_art() should return an empty string to accept
+the article or an error string to refuse the article. This function is
+called before any history lookups and for every article offered to innd
+with CHECK or IHAVE (before the actual article is sent). Accordingly, the
+message ID is the only information it has about the article (the %hdr hash
+will be empty). This code would sit in a performance-critical hot path in
+a typical server, and therefore should be as fast as possible, but it can
+do things like refuse articles from certain hosts or cancels for already
+rejected articles (if they follow the $alz convention) without having to
+take the network bandwidth hit of accepting the entire article first.
+
+Note that you cannot rely on filter_messageid() being called for every
+incoming article; articles sent via TAKETHIS without an earlier CHECK will
+never pass through filter_messageid() and will only go through
+filter_art().
+
+Finally, whenever ctlinnd throttle, ctlinnd pause, or ctlinnd go is run,
+the Perl function filter_mode() is called if it exists. It receives no
+arguments and returns no value, but it has access to a global hash %mode
+that contains three values:
+
+ Mode The current server mode (throttled, paused, or running)
+ NewMode The new mode the server is going to
+ reason The reason that was given to ctlinnd
+
+One possible use for this function is to save filter state across a
+restart of innd. There isn't any Perl function which is called when INN
+shuts down, but using filter_mode() the Perl filter can dump it's state to
+disk whenever INN is throttled. Then, if the news administrator follows
+the strongly recommended shutdown procedure of throttling the server
+before shutting it down, the filter state will be safely saved to disk and
+can be reloaded when innd restarts (possibly by F<startup_innd.pl>).
+
+The state of the Perl interpretor in which all of these Perl functions run
+is preserved over the lifetime of innd. In other words, it's permissible for
+the Perl code to create its own global Perl variables, data structures,
+saved state, and the like, and all of that will be available to
+filter_art() and filter_messageid() each time they're called. The only
+variable INN fiddles with (or pays any attention to at all) is %hdr, which
+is cleared after each call to filter_art().
+
+Perl filtering can be turned off with C<ctlinnd perl n> and back on again
+with C<ctlinnd perl y>. Perl filtering is turned off automatically if
+loading of the filter fails or if the filter code returns any sort of a
+fatal error (either due to Perl itself or due to a C<die> in the Perl code).
+
+=head1 Supported innd Callbacks
+
+innd makes seven functions available to any of its embedded Perl code.
+Those are:
+
+=over 4
+
+=item INN::addhist(I<messageid>, I<arrival>, I<articledate>, I<expire>, I<paths>)
+
+Adds I<messageid> to the history database. All of the arguments except
+the first one are optional; the times default to the current time and the
+paths field defaults to the empty string. (For those unfamiliar with the
+fields of a history(5) database entry, the I<arrival> is normally the time at
+which the server accepts the article, the I<articledate> is from the Date
+header of the article, the I<expire> is from the Expires header of the
+article, and the I<paths> field is the storage API token. All three times
+as measured as a time_t since the epoch.) Returns true on success, false
+otherwise.
+
+=item INN::article(I<messageid>)
+
+Returns the full article (as a simple string) identified by I<messageid>,
+or undef if it isn't found. Each line will end with a simple \n, but
+leading periods may still be doubled if the article is stored in wire
+format.
+
+=item INN::cancel(I<messageid>)
+
+Cancels I<messageid>. (This is equivalent to C<ctlinnd cancel>; it
+cancels the message on the local server, but doesn't post a cancel message
+or do anything else that affects anything other than the local server.)
+Returns true on success, false otherwise.
+
+=item INN::filesfor(I<messageid>)
+
+Returns the I<paths> field of the history entry for the given
+I<messageid>. This will be the storage API token for the message. If
+I<messageid> isn't found in the history database, returns undef.
+
+=item INN::havehist(I<messageid>)
+
+Looks up I<messageid> in the history database and returns true if it's
+found, false otherwise.
+
+=item INN::head(I<messageid>)
+
+Returns the header (as a simple string) of the article identified by
+I<messageid>, or undef if it isn't found. Each line will end with a
+simple \n (in other words, regardless of the format of article storage,
+the returned string won't be in wire format).
+
+=item INN::newsgroup(I<newsgroup>)
+
+Returns the status of I<newsgroup> (the last field of the active file
+entry for that newsgroup). See active(5) for a description of the
+possible values and their meanings (the most common are "y" for an
+unmoderated group and "m" for a moderated group). If I<newsgroup> isn't
+in the active file, returns undef.
+
+=back
+
+These functions can only be used from inside the innd Perl filter; they're
+not available in the nnrpd filter.
+
+=head1 Common Callbacks
+
+The following additional function is available from inside filters
+embedded in innd, and is also available from filters embedded in nnrpd
+(see below):
+
+=over 4
+
+=item INN::syslog(level, message)
+
+Logs a message via syslog(2). This is quite a bit more reliable and
+portable than trying to use Sys::Syslog from inside the Perl filter. Only
+the first character of the level argument matters; the valid letters are
+the first letters of ALERT, CRIT, ERR, WARNING, NOTICE, INFO, and DEBUG
+(case-insensitive) and specify the priority at which the message is
+logged. If a level that doesn't match any of those levels is given, the
+default priority level is LOG_NOTICE. The second argument is the message
+to log; it will be prefixed by "filter: " and logged to syslog with
+facility LOG_NEWS.
+
+=back
+
+=head1 The nnrpd Posting Filter
+
+Whenever Perl support is needed in nnrpd, it first loads the file
+_PATH_PERL_FILTER_NNRPD (defined in F<include/paths.h>, by default
+F<filter_nnrpd.pl>). This file must be located in the directory
+specified by pathfilter in F<inn.conf> (F</usr/local/news/bin/filter>
+by default). The default directory for filter code can be specified
+at configure time by giving the flag B<--with-filter-dir> to
+configure.
+
+If F<filter_nnrpd.pl> loads successfully and defines the Perl function
+filter_post(), Perl filtering is turned on. Otherwise, it's turned off.
+If filter_post() ever returns a fatal error (either from Perl or from a
+C<die> in the Perl code), Perl filtering is turned off for the life of that
+nnrpd process and any further posts made during that session won't go
+through the filter.
+
+While Perl filtering is on, every article received by nnrpd via the POST
+command is passed to the filter_post() Perl function before it is passed
+to INN (or mailed to the moderator of a moderated newsgroup). If
+filter_post() returns an empty string (''), the article is accepted and
+normal processing of it continues. Otherwise, the article is rejected and
+the string returned by filter_post() is returned to the client as the
+error message (with some exceptions; see below).
+
+filter_post() has access to a global hash %hdr, which contains all of the
+headers of the article. (Unlike the innd Perl filter, %hdr for the nnrpd
+Perl filter contains *all* of the headers, not just the standard ones. If
+any of the headers are duplicated, though, %hdr will contain only the
+value of the last occurance of the header. nnrpd will reject the
+article before the filter runs if any of the standard headers are
+duplicated.) It also has access to the full body of the article in the
+variable $body, and if the poster authenticated via AUTHINFO (or if either
+Perl authentication or a readers.conf authentication method is used and
+produces user information), it has access to the authenticated username of
+the poster in the variable $user.
+
+Unlike the innd Perl filter, the nnrpd Perl filter can modify the %hdr
+hash. In fact, if the Perl variable $modify_headers is set to true after
+filter_post() returns, the contents of the %hdr hash will be written back
+to the article replacing the original headers. filter_post() can
+therefore make any modifications it wishes to the headers and those
+modifications will be reflected in the article as it's finally posted.
+The article body cannot be modified in this way; any changes to $body will
+just be ignored.
+
+Be careful when using the ability to modify headers. filter_post() runs
+after all the normal consistency checks on the headers and after server
+supplied headers (like Message-ID: and Date:) are filled in. Deleting
+required headers or modifying headers that need to follow a strict format
+can result in nnrpd trying to post nonsense articles (which will probably
+then be rejected by innd). If $modify_headers is set, I<everything> in
+the %hdr hash is taken to be article headers and added to the article.
+
+If filter_post() returns something other than the empty string, this
+message is normally returned to the client as an error. There are two
+exceptions: If the string returned begins with "DROP", the post will be
+silently discarded and success returned to the client. If the string
+begins with "SPOOL", success is returned to the client, but the post is
+saved in a directory named "spam" under the directory specified by
+pathincoming in F<inn.conf> (in a directory named "spam/mod" if the post
+is to a moderated group). This is intended to allow manual inspection of
+the suspect messages; if they should be posted, they can be manually moved
+out of the subdirectory to the directory specified by pathincoming in
+F<inn.conf>, where they can be posted by running C<rnews -U>. If you use
+this functionality, make sure those directories exist.
+
+=head1 Changes to Perl Authentication Support for nnrpd
+
+The old authentication functionality has been combined with the new
+readers.conf mechanism by Erik Klavon <erik@eriq.org>; bug reports
+should however go to inn-bugs@isc.org, not Erik.
+
+The remainder of this section is an introduction to the new mechanism
+(which uses the perl_auth: and perl_access: F<readers.conf> parameters)
+with porting/migration suggestions for people familiar with the old
+mechanism (identifiable by the nnrpperlauth: parameter in F<inn.conf>).
+
+Other people should skip this section.
+
+The perl_auth parameter allows the use of Perl to authenticate a user.
+Scripts (like those from the old mechanism) are listed in F<readers.conf>
+using perl_auth in the same manner other authenticators are using auth:
+
+ perl_auth: "/path/to/script/auth1.pl"
+
+The file given as argument to perl_auth should contain the same
+procedures as before. The global hash %attributes remains the same,
+except for the removal of the "type" entry which is no longer needed
+in this modification and the addition of several new entries (port,
+intipaddr, intport) described below. The return array now only
+contains either two or three elements, the first of which is the NNTP
+return code. The second is an error string which is passed to the
+client if the error code indicates that the authentication attempt has
+failed. This allows a specific error message to be generated by the
+perl script in place of "Authentication failed". An optional third
+return element if present will be used to match the connection with
+the users: parameter in access groups and will also be the username
+logged. If this element is absent, the username supplied by the client
+during authentication will be used as was the previous behavior.
+
+The perl_access parameter (described below) is also new; it allows the
+dynamic generation of an access group for an incoming connection using
+a Perl script. If a connection matches an auth group which has a
+perl_access parameter, all access groups in readers.conf are ignored;
+instead the procedure described below is used to generate an access group.
+This concept is due to Jeffrey M. Vinocur.
+
+The new functionality should provide all of the existing capabilities
+of the Perl hook, in combination with the flexibility of readers.conf
+and the use of other authentication and resolving programs. To use
+Perl authentication code that predates the readers.conf mechanism, you
+would need to modify the code slightly (see below for the new
+specification) and supply a simple readers.conf file. If you don't want
+to modify your code, the samples directory has F<nnrpd_auth_wrapper.pl>
+and F<nnrpd_access_wrapper.pl> which should allow you to use your old
+code without needing to change it.
+
+However, before trying to use your old Perl code, you may want to
+consider replacing it entirely with non-Perl authentication. (With
+readers.conf and the regular authenticator and resolver programs, much
+of what once required Perl can be done directly.) Even if the
+functionality is not available directly, you may wish to write a new
+authenticator or resolver (which can be done in whatever language you
+prefer to work in).
+
+
+=head1 Perl Authentication Support for nnrpd
+
+Support for authentication via Perl is provided in nnrpd by the
+inclusion of a perl_auth: parameter in a F<readers.conf> auth
+group. perl_auth: works exactly like the auth: parameter in
+F<readers.conf>, except that it calls the script given as argument using
+the Perl hook rather then treating it as an external program.
+
+If the processing of readers.conf requires that a perl_auth: statement
+be used for authentication, Perl is loaded (if it has yet to be) and
+the file given as argument to the perl_auth: parameter is loaded as
+well. If a Perl function auth_init() is defined by that file, it is called
+immediately after the file is loaded. It takes no arguments and returns
+nothing.
+
+Provided the file loads without errors, auth_init() (if present) runs
+without fatal errors, and a Perl function authenticate() is defined,
+authenticate() will then be called. authenticate() takes no arguments,
+but it has access to a global hash %attributes which contains
+information about the connection as follows: C<$attributes{hostname}>
+will contain the hostname (or the IP address if it doesn't resolve) of
+the client machine, C<$attributes{ipaddress}> will contain its IP
+address (as a string), C<$attributes{port}> will contain the client
+port (as an integer), C<$attributes{interface}> contains the hostname
+of the interface the client connected on, C<$attributes{intipaddr}>
+contains the IP address (as a string) of the interface the client
+connected on, C<$attributes{intport}> contains the port (as an
+integer) on the interface the client connected on,
+C<$attributes{username}> will contain the provided username and
+C<$attributes{password}> the password.
+
+authenticate() should return a two or three element array. The first
+element is the NNTP response code to return to the client, the second
+element is an error string which is passed to the client if the
+response code indicates that the authentication attempt has failed. An
+optional third return element if present will be used to match the
+connection with the users: parameter in access groups and will also be
+the username logged. If this element is absent, the username supplied
+by the client during authentication will be used for matching and
+logging.
+
+The NNTP response code should probably be either 281 (authentication
+successful) or 502 (authentication unsuccessful). If the code
+returned is anything other than 281, nnrpd will print an
+authentication error message and drop the connection and exit.
+
+If authenticate() dies (either due to a Perl error or due to calling die),
+or if it returns anything other than the two or three element array
+described above, an internal error will be reported to the client, the
+exact error will be logged to syslog, and nnrpd will drop the
+connection and exit.
+
+
+=head1 Dynamic Generation of Access Groups
+
+A Perl script may be used to dynamically generate an access group
+which is then used to determine the access rights of the client. This
+occurs whenever the perl_access: is specified in an auth group which
+has successfully matched the client. Only one perl_access:
+statement is allowed in an auth group. This parameter should not be
+mixed with a python_access: statement in the same auth group.
+
+When a perl_access: parameter is encountered, Perl is loaded (if it
+has yet to be) and the file given as argument is loaded as
+well. Provided the file loads without errors, and a Perl function
+access() is defined, access() will then be called. access() takes no
+arguments, but it has access to a global hash %attributes which
+contains information about the connection as follows:
+C<$attributes{hostname}> will contain the hostname (or the IP address
+if it doesn't resolve) of the client machine,
+C<$attributes{ipaddress}> will contain its IP address (as a string),
+C<$attributes{port}> will contain the client port (as an integer),
+C<$attributes{interface}> contains the hostname of the interface the
+client connected on, C<$attributes{intipaddr}> contains the IP address
+(as a string) of the interface the client connected on,
+C<$attributes{intport}> contains the port (as an integer) on the
+interface the client connected on, C<$attributes{username}> will
+contain the provided username and domain (in username@domain form).
+
+access() returns a hash, containing the desired access parameters and
+values. Here is an untested example showing how to dynamically generate a
+list of newsgroups based on the client's username and domain.
+
+ my %hosts = ( "example.com" => "example.*", "isc.org" => "isc.*" );
+
+ sub access {
+ %return_hash = (
+ "max_rate" => "10000",
+ "addnntppostinghost" => "true",
+ # ...
+ );
+ if( defined $attributes{username} &&
+ $attributes{username} =~ /.*@(.*)/ )
+ {
+ $return_hash{"virtualhost"} = "true";
+ $return_hash{"path"} = $1;
+ $return_hash{"newsgroups"} = $hosts{$1};
+ } else {
+ $return_hash{"read"} = "*";
+ $return_hash{"post"} = "local.*"
+ }
+ return %return_hash;
+ }
+
+Note that both the keys and values are quoted strings. These values
+are to be returned to a C program and must be quoted strings. For
+values containing one or more spaces, it is not necessary to include
+extra quotes inside the string.
+
+While you may include the users: parameter in a dynamically generated
+access group, some care should be taken (unless your pattern is just
+* which is equivalent to leaving the parameter out). The group created
+with the values returned from the Perl script is the only one
+considered when nnrpd attempts to find an access group matching the
+connection. If a users: parameter is included and it doesn't match the
+connection, then the client will be denied access since there are no
+other access groups which could match the connection.
+
+If access() dies (either due to a Perl error or due to calling die),
+or if it returns anything other than a hash as described
+above, an internal error will be reported to the client, the exact error
+will be logged to syslog, and nnrpd will drop the connection and exit.
+
+=head1 Notes on Writing Embedded Perl
+
+All Perl evaluation is done inside an implicit eval block, so calling die
+in Perl code will not kill the innd or nnrpd process. Neither will Perl
+errors (such as syntax errors). However, such errors will have negative
+effects (fatal errors in the innd or nnrpd filter will cause filtering to
+be disabled, and fatal errors in the nnrpd authentication code will cause
+the client connection to be terminated).
+
+Calling exit directly, however, *will* kill the innd or nnrpd process, so
+don't do that. Similarly, you probably don't want to call fork (or any
+other function that results in a fork such as system, IPC::Open3::open3(),
+or any use of backticks) since there are possibly unflushed buffers that
+could get flushed twice, lots of open state that may not get closed
+properly, and innumerable other potential problems. In general, be aware
+that all Perl code is running inside a large and complicated C program,
+and Perl code that impacts the process as a whole is best avoided.
+
+You can use print and warn inside Perl code to send output to STDOUT or
+STDERR, but you probably shouldn't. Instead, open a log file and print to
+it instead (or, in the innd filter, use INN::syslog() to write messages
+via syslog like the rest of INN). If you write to STDOUT or STDERR, where
+that data will go depends on where the filter is running; inside innd, it
+will go to the news log or the errlog, and inside nnrpd it will probably
+go nowhere but could go to the client. The nnrpd filter takes some steps
+to try to keep output from going across the network connection to the
+client (which would probably result in a very confused client), but best
+not to take the chance.
+
+For similar reasons, try to make your Perl code -w clean, since Perl
+warnings are written to STDERR. (INN won't run your code under -w, but
+better safe than sorry, and some versions of Perl have some mandatory
+warnings you can't turn off.)
+
+You *can* use modules in your Perl code, just like you would in an
+ordinary Perl script. You can even use modules that dynamically load C
+code. Just make sure that none of the modules you use go off behind your
+back to do any of the things above that are best avoided.
+
+Whenever you make any modifications to the Perl code, and particularly
+before starting INN or reloading filter.perl with new code, you should run
+perl -wc on the file. This will at least make sure you don't have any
+glaring syntax errors. Remember, if there are errors in your code,
+filtering will be disabled, which could mean that posts you really wanted
+to reject will leak through and authentication of readers may be totally
+broken.
+
+The samples directory has example F<startup_innd.pl>, F<filter_innd.pl>,
+F<filter_nnrpd.pl>, and F<nnrpd_auth.pl> files that contain some
+simplistic examples. Look them over as a starting point when writing your
+own.
+
+=head1 Available Packages
+
+This is an unofficial list of known filtering packages at the time of
+publication. This is not an endorsement of these filters by the ISC or
+the INN developers, but is included as assistance in locating packages
+which make use of this filter mechanism.
+
+ CleanFeed Jeremy Nixon <jeremy@exit109.com>
+ <URL:http://www.exit109.com/~jeremy/news/cleanfeed.html>
+ A spam filter catching excessive multi-posting and a host of
+ other things. Uses filter_innd.pl exclusively, requires the MD5
+ Perl module. Probably the most popular and widely-used Perl
+ filter around.
+
+ Usenet II Filter Edward S. Marshall <emarshal@xnet.com>
+ <URL:http://www.xnet.com/~emarshal/inn/filter_nnrpd.pl>
+ Checks for "soundness" according to Usenet II guidelines in the
+ net.* hierarchy. Designed to use filter_nnrpd.pl.
+
+ News Gizmo Aidan Cully <aidan@panix.com>
+ <URL:http://www.panix.com/gizmo/>
+ A posting filter for helping a site enforce Usenet-II soundness,
+ and for quotaing the number of messages any user can post to
+ Usenet daily.
--- /dev/null
+=head1 INN Python Filtering and Authentication Support
+
+This file documents INN's built-in optional support for Python article
+filtering. It is patterned after the Perl and (now obsolete) TCL hooks
+previously added by Bob Heiney and Christophe Wolfhugel.
+
+For this filter to work successfully, you will need to have at least
+S<Python 1.5.2> installed. You can obtain it from L<http://www.python.org/>.
+
+The B<innd> Python interface and the original Python filtering documentation
+were written by Greg Andruk (nee Fluffy) <gerglery@usa.net>. The Python
+authentication and authorization support for B<nnrpd> as well as the original
+documentation for it were written by Ilya Etingof <ilya@glas.net> in
+December 1999.
+
+=head1 Installation
+
+Once you have built and installed Python, you can cause INN to use it by
+adding the B<--with-python> switch to your C<configure> command. You will
+need to have all the headers and libraries required for embedding Python
+into INN; they can be found in Python development packages, which include
+header files and static libraries.
+
+You will then be able to use Python authentication, dynamic access group
+generation and dynamic access control support in B<nnrpd> along with
+filtering support in B<innd>.
+
+See the ctlinnd(8) manual page to learn how to enable, disable and reload
+Python filters on a running server (especially C<ctlinnd mode>,
+C<ctlinnd python y|n> and C<ctlinnd reload filter.python 'reason'>).
+
+Also, see the F<filter_innd.py>, F<nnrpd_auth.py>, F<nnrpd_access.py>
+and F<nnrpd_dynamic.py> samples in your filters directory for
+a demonstration of how to get all this working.
+
+=head1 Writing an B<innd> Filter
+
+=head2 Introduction
+
+You need to create a F<filter_innd.py> module in INN's filter directory
+(see the I<pathfilter> setting in F<inn.conf>). A heavily-commented sample
+is provided; you can use it as a template for your own filter. There is
+also an F<INN.py> module there which is not actually used by INN; it is
+there so you can test your module interactively.
+
+First, define a class containing the methods you want to provide to B<innd>.
+Methods B<innd> will use if present are:
+
+=over 4
+
+=item __init__(I<self>)
+
+Not explicitly called by B<innd>, but will run whenever the filter module is
+(re)loaded. This is a good place to initialize constants or pick up where
+C<filter_before_reload> or C<filter_close> left off.
+
+=item filter_before_reload(I<self>)
+
+This will execute any time a C<ctlinnd reload all 'reason'> or C<ctlinnd reload
+filter.python 'reason'> command is issued. You can use it to save statistics or
+reports for use after reloading.
+
+=item filter_close(I<self>)
+
+This will run when a C<ctlinnd shutdown 'reason'> command is received.
+
+=item filter_art(I<self>, I<art>)
+
+I<art> is a dictionary containing an article's headers and body. This method
+is called every time B<innd> receives an article. The following can be
+defined:
+
+ Also-Control, Approved, Bytes, Cancel-Key, Cancel-Lock,
+ Content-Base, Content-Disposition, Content-Transfer-Encoding,
+ Content-Type, Control, Date, Date-Received, Distribution, Expires,
+ Face, Followup-To, From, In-Reply-To, Injection-Date, Injection-Info,
+ Keywords, Lines, List-ID, Message-ID, MIME-Version, Newsgroups,
+ NNTP-Posting-Date, NNTP-Posting-Host, Organization, Originator,
+ Path, Posted, Posting-Version, Received, References, Relay-Version,
+ Reply-To, Sender, Subject, Supersedes, User-Agent,
+ X-Auth, X-Canceled-By, X-Cancelled-By, X-Complaints-To, X-Face,
+ X-HTTP-UserAgent, X-HTTP-Via, X-Mailer, X-Modbot, X-Modtrace,
+ X-Newsposter, X-Newsreader, X-No-Archive, X-Original-Message-ID,
+ X-Original-Trace, X-Originating-IP, X-PGP-Key, X-PGP-Sig,
+ X-Poster-Trace, X-Postfilter, X-Proxy-User, X-Submissions-To,
+ X-Trace, X-Usenet-Provider, Xref, __BODY__, __LINES__.
+
+Note that all the above values are as they arrived, not modified
+by your INN (especially, the Xref: header, if present, is the one
+of the remote site which sent you the article, and not yours).
+
+These values will be buffer objects holding the contents of the
+same named article headers, except for the special C<__BODY__> and C<__LINES__>
+items. Items not present in the article will contain C<None>.
+
+C<art('__BODY__')> is a buffer object containing the article's entire body, and
+C<art('__LINES__')> is an int holding B<innd>'s reckoning of the number of lines
+in the article. All the other elements will be buffers with the contents
+of the same-named article headers.
+
+The Newsgroups: header of the article is accessible inside the Python
+filter as C<art['Newsgroups']>.
+
+If you want to accept an article, return C<None> or an empty string. To
+reject, return a non-empty string. The rejection strings will be shown to
+local clients and your peers, so keep that in mind when phrasing your
+rejection responses.
+
+=item filter_messageid(I<self>, I<msgid>)
+
+I<msgid> is a buffer object containing the ID of an article being offered by
+IHAVE or CHECK. Like with C<filter_art>, the message will be refused if
+you return a non-empty string. If you use this feature, keep it light
+because it is called at a rather busy place in B<innd>'s main loop. Also, do
+not rely on this function alone to reject by ID; you should repeat the
+tests in C<filter_art> to catch articles sent with TAKETHIS but no CHECK.
+
+=item filter_mode(I<self>, I<oldmode>, I<newmode>, I<reason>)
+
+When the operator issues a B<ctlinnd> C<pause>, C<throttle>, C<go>, C<shutdown>
+or C<xexec> command, this function can be used to do something sensible in accordance
+with the state change. Stamp a log file, save your state on throttle,
+etc. I<oldmode> and I<newmode> will be strings containing one of the values in
+(C<running>, C<throttled>, C<paused>, C<shutdown>, C<unknown>). I<oldmode> is
+the state B<innd> was in before B<ctlinnd> was run, I<newmode> is the state B<innd>
+will be in after the command finishes. I<reason> is the comment string
+provided on the B<ctlinnd> command line.
+
+=back
+
+=head2 How to Use these Methods with B<innd>
+
+To register your methods with B<innd>, you need to create an instance of your
+class, import the built-in INN module, and pass the instance to
+C<INN.set_filter_hook>. For example:
+
+ class Filter:
+ def filter_art(self, art):
+ ...
+ blah blah
+ ...
+
+ def filter_messageid(self, id):
+ ...
+ yadda yadda
+ ...
+
+ import INN
+ myfilter = Filter()
+ INN.set_filter_hook(myfilter)
+
+When writing and testing your Python filter, don't be afraid to make use
+of C<try:>/C<except:> and the provided C<INN.syslog> function. stdout and stderr
+will be disabled, so your filter will die silently otherwise.
+
+Also, remember to try importing your module interactively before loading
+it, to ensure there are no obvious errors. One typo can ruin your whole
+filter. A dummy F<INN.py> module is provided to facilitate testing outside
+the server. To test, change into your filter directory and use a command
+like:
+
+ python -ic 'import INN, filter_innd'
+
+You can define as many or few of the methods listed above as you want in
+your filter class (it is fine to define more methods for your own use; B<innd>
+will not be using them but your filter can). If you I<do> define the above
+methods, GET THE PARAMETER COUNTS RIGHT. There are checks in B<innd> to see
+whether the methods exist and are callable, but if you define one and get the
+parameter counts wrong, B<innd> WILL DIE. You have been warned. Be careful
+with your return values, too. The C<filter_art> and C<filter_messageid>
+methods have to return strings, or C<None>. If you return something like an
+int, B<innd> will I<not> be happy.
+
+=head2 A Note regarding Buffer Objects
+
+Buffer objects are cousins of strings, new in S<Python 1.5.2>. Using buffer
+objects may take some getting used to, but we can create buffers much faster
+and with less memory than strings.
+
+For most of the operations you will perform in filters (like C<re.search>,
+C<string.find>, C<md5.digest>) you can treat buffers just like strings, but
+there are a few important differences you should know about:
+
+ # Make a string and two buffers.
+ s = "abc"
+ b = buffer("def")
+ bs = buffer("abc")
+
+ s == bs # - This is false because the types differ...
+ buffer(s) == bs # - ...but this is true, the types now agree.
+ s == str(bs) # - This is also true, but buffer() is faster.
+ s[:2] == bs[:2] # - True. Buffer slices are strings.
+
+ # While most string methods will take either a buffer or a string,
+ # string.join (in the string module) insists on using only strings.
+ import string
+ string.join([str(b), s], '.') # Returns 'def.abc'.
+ '.'.join([str(b), s]) # Returns 'def.abc' too.
+ '.'.join([b, s]) # This raises a TypeError.
+
+ e = s + b # This raises a TypeError, but...
+
+ # ...these two both return the string 'abcdef'. The first one
+ # is faster -- choose buffer() over str() whenever you can.
+ e = buffer(s) + b
+ f = s + str(b)
+
+ g = b + '>' # This is legal, returns the string 'def>'.
+
+=head2 Functions Supplied by the Built-in B<innd> Module
+
+Besides C<INN.set_filter_hook> which is used to register your methods
+with B<innd> as it has already been explained above, the following functions
+are available from Python scripts:
+
+=over 4
+
+=item addhist(I<message-id>)
+
+=item article(I<message-id>)
+
+=item cancel(I<message-id>)
+
+=item havehist(I<message-id>)
+
+=item hashstring(I<string>)
+
+=item head(I<message-id>)
+
+=item newsgroup(I<groupname>)
+
+=item syslog(I<level>, I<message>)
+
+=back
+
+Therefore, not only can B<innd> use Python, but your filter can use some of
+B<innd>'s features too. Here is some sample Python code to show what you get
+with the previously listed functions.
+
+ import INN
+
+ # Python's native syslog module isn't compiled in by default,
+ # so the INN module provides a replacement. The first parameter
+ # tells the Unix syslogger what severity to use; you can
+ # abbreviate down to one letter and it's case insensitive.
+ # Available levels are (in increasing levels of seriousness)
+ # Debug, Info, Notice, Warning, Err, Crit, and Alert. (If you
+ # provide any other string, it will be defaulted to Notice.) The
+ # second parameter is the message text. The syslog entries will
+ # go to the same log files innd itself uses, with a 'python:'
+ # prefix.
+ syslog('warning', 'I will not buy this record. It is scratched.')
+ animals = 'eels'
+ vehicle = 'hovercraft'
+ syslog('N', 'My %s is full of %s.' % (vehicle, animals))
+
+ # Let's cancel an article! This only deletes the message on the
+ # local server; it doesn't send out a control message or anything
+ # scary like that. Returns 1 if successful, else 0.
+ if INN.cancel('<meow$123.456@solvangpastries.edu>'):
+ cancelled = "yup"
+ else:
+ cancelled = "nope"
+
+ # Check if a given message is in history. This doesn't
+ # necessarily mean the article is on your spool; cancelled and
+ # expired articles hang around in history for a while, and
+ # rejected articles will be in there if you have enabled
+ # remembertrash in inn.conf. Returns 1 if found, else 0.
+ if INN.havehist('<z456$789.abc@isc.org>'):
+ comment = "*yawn* I've already seen this article."
+ else:
+ comment = 'Mmm, fresh news.'
+
+ # Here we are running a local spam filter, so why eat all those
+ # cancels? We can add fake entries to history so they'll get
+ # refused. Returns 1 on success, 0 on failure.
+ cancelled_id = buffer('<meow$123.456@isc.org>')
+ if INN.addhist("<cancel." + cancelled_id[1:]):
+ thought = "Eat my dust, roadkill!"
+ else:
+ thought = "Darn, someone beat me to it."
+
+ # We can look at the header or all of an article already on spool,
+ # too. Might be useful for long-memory despamming or
+ # authentication things. Each is returned (if present) as a
+ # string object; otherwise you'll end up with an empty string.
+ artbody = INN.article('<foo$bar.baz@bungmunch.edu>')
+ artheader = INN.head('<foo$bar.baz@bungmunch.edu>')
+
+ # As we can compute a hash digest for a string, we can obtain one
+ # for artbody. It might be of help to detect spam.
+ digest = INN.hashstring(artbody)
+
+ # Finally, do you want to see if a given newsgroup is moderated or
+ # whatever? INN.newsgroup returns the last field of a group's
+ # entry in active as a string.
+ froupflag = INN.newsgroup('alt.fan.karl-malden.nose')
+ if froupflag == '':
+ moderated = 'no such newsgroup'
+ elif froupflag == 'y':
+ moderated = "nope"
+ elif froupflag == 'm':
+ moderated = "yep"
+ else:
+ moderated = "something else"
+
+=head1 Writing an B<nnrpd> Filter
+
+=head2 Changes to Python Authentication and Access Control Support for B<nnrpd>
+
+The old authentication and access control functionality has been
+combined with the new F<readers.conf> mechanism by Erik Klavon
+<erik@eriq.org>; bug reports should however go to <inn-bugs@isc.org>,
+not Erik.
+
+The remainder of this section is an introduction to the new mechanism
+(which uses the I<python_auth>, I<python_access>, and I<python_dynamic>
+F<readers.conf> parameters) with porting/migration suggestions for
+people familiar with the old mechanism (identifiable by the now
+deprecated I<nnrpperlauth> parameter in F<inn.conf>).
+
+Other people should skip this section.
+
+The I<python_auth> parameter allows the use of Python to authenticate a
+user. Authentication scripts (like those from the old mechanism) are
+listed in F<readers.conf> using I<python_auth> in the same manner other
+authenticators are using I<auth>:
+
+ python_auth: "nnrpd_auth"
+
+It uses the script named F<nnrpd_auth.py> (note that C<.py> is not present
+in the I<python_auth> value).
+
+Scripts should be placed as before in the filter directory (see the
+I<pathfilter> setting in F<inn.conf>). The new hook method C<authen_init>
+takes no arguments and its return value is ignored; its purpose is to
+provide a means for authentication specific initialization. The hook
+method C<authen_close> is the more specific analogue to the old C<close>
+method. These two method hooks are not required, contrary to
+C<authenticate>, the main method.
+
+The argument dictionary passed to C<authenticate> remains the same,
+except for the removal of the I<type> entry which is no longer needed
+in this modification and the addition of several new entries (I<port>,
+I<intipaddr>, I<intport>) described below. The return tuple now only
+contains either two or three elements, the first of which is the NNTP
+response code. The second is an error string which is passed to the
+client if the response code indicates that the authentication attempt
+has failed. This allows a specific error message to be generated by
+the Python script in place of the generic message C<Authentication
+failed>. An optional third return element, if present, will be used to
+match the connection with the I<user> parameter in access groups and
+will also be the username logged. If this element is absent, the
+username supplied by the client during authentication will be used, as
+was the previous behaviour.
+
+The I<python_access> parameter (described below) is new; it allows the
+dynamic generation of an access group of an incoming connection using
+a Python script. If a connection matches an auth group which has a
+I<python_access> parameter, all access groups in F<readers.conf> are
+ignored; instead the procedure described below is used to generate an
+access group. This concept is due to Jeffrey S<M. Vinocur> and you can
+add this line to F<readers.conf> in order to use the F<nnrpd_access.py>
+Python script in I<pathfilter>:
+
+ python_access: "nnrpd_access"
+
+In the old implementation, the authorization method allowed for access
+control on a per-group basis. That functionality is preserved in the
+new implementation by the inclusion of the I<python_dynamic> parameter in
+F<readers.conf>. The only change is the corresponding method name of
+C<dynamic> as opposed to C<authorize>. Additionally, the associated
+optional housekeeping methods C<dynamic_init> and C<dynamic_close>
+may be implemented if needed. In order to use F<nnrpd_dynamic.py> in
+I<pathfilter>, you can add this line to F<readers.conf>:
+
+ python_dynamic: "nnrpd_dynamic"
+
+This new implementation should provide all of the previous
+capabilities of the Python hooks, in combination with the flexibility
+of F<readers.conf> and the use of other authentication and resolving
+programs (including the Perl hooks!). To use Python code that predates
+the new mechanism, you would need to modify the code slightly (see
+below for the new specification) and supply a simple F<readers.conf>
+file. If you do not want to modify your code, the sample directory has
+F<nnrpd_auth_wrapper.py>, F<nnrpd_access_wrapper.py> and
+F<nnrpd_dynamic_wrapper.py> which should allow you to use your old
+code without needing to change it.
+
+However, before trying to use your old Python code, you may want to
+consider replacing it entirely with non-Python authentication. (With
+F<readers.conf> and the regular authenticator and resolver programs, much
+of what once required Python can be done directly.) Even if the
+functionality is not available directly, you may wish to write a new
+authenticator or resolver (which can be done in whatever language you
+prefer).
+
+=head2 Python Authentication Support for B<nnrpd>
+
+Support for authentication via Python is provided in B<nnrpd> by the
+inclusion of a I<python_auth> parameter in a F<readers.conf> auth
+group. I<python_auth> works exactly like the I<auth> parameter in
+F<readers.conf>, except that it calls the script given as argument
+using the Python hook rather then treating it as an external
+program. Multiple, mixed use of I<python_auth> with other I<auth>
+statements including I<perl_auth> is permitted. Each I<auth> statement
+will be tried in the order they appear in the auth group until either
+one succeeds or all are exhausted.
+
+If the processing of F<readers.conf> requires that a I<python_auth>
+statement be used for authentication, Python is loaded (if it has yet
+to be) and the file given as argument to the I<python_auth> parameter is
+loaded as well (do not include the C<.py> extension of this file in
+the value of I<python_auth>). If a Python object with a method
+C<authen_init> is hooked in during the loading of that file, then
+that method is called immediately after the file is loaded. If no
+errors have occurred, the method C<authenticate> is called. Depending
+on the NNTP response code returned by C<authenticate>, the authentication
+hook either succeeds or fails, after which the processing of the
+auth group continues as usual. When the connection with the client
+is closed, the method C<authen_close> is called if it exists.
+
+=head2 Dynamic Generation of Access Groups
+
+A Python script may be used to dynamically generate an access group
+which is then used to determine the access rights of the client. This
+occurs whenever the I<python_access> parameter is specified in an auth group
+which has successfully matched the client. Only one I<python_access>
+statement is allowed in an auth group. This parameter should not be
+mixed with a I<perl_access> statement in the same auth group.
+
+When a I<python_access> parameter is encountered, Python is loaded (if
+it has yet to be) and the file given as argument is loaded as well (do not
+include the C<.py> extension of this file in the value of I<python_access>).
+If a Python object with a method C<access_init> is hooked in during the
+loading of that file, then that method is called immediately after the
+file is loaded. If no errors have occurred, the method C<access> is
+called. The dictionary returned by C<access> is used to generate an
+access group that is then used to determine the access rights of the
+client. When the connection with the client is closed, the method
+C<access_close> is called, if it exists.
+
+While you may include the I<users> parameter in a dynamically generated
+access group, some care should be taken (unless your pattern is just
+C<*> which is equivalent to leaving the parameter out). The group created
+with the values returned from the Python script is the only one
+considered when B<nnrpd> attempts to find an access group matching the
+connection. If a I<users> parameter is included and it does not match the
+connection, then the client will be denied access since there are no
+other access groups which could match the connection.
+
+=head2 Dynamic Access Control
+
+If you need to have access control rules applied immediately without
+having to restart all the B<nnrpd> processes, you may apply access
+control on a per newsgroup basis using the Python dynamic hooks (as
+opposed to F<readers.conf>, which does the same on per user
+basis). These hooks are activated through the inclusion of the
+I<python_dynamic> parameter in a F<readers.conf> auth group. Only one
+I<python_dynamic> statement is allowed in an auth group.
+
+When a I<python_dynamic> parameter is encountered, Python is loaded (if
+it has yet to be) and the file given as argument is loaded as well (do not
+include the C<.py> extension of this file in the value of I<python_dynamic>).
+If a Python object with a method C<dynamic_init> is hooked in during the
+loading of that file, then that method is called immediately after the
+file is loaded. Every time a reader asks B<nnrpd> to read or post an
+article, the Python method C<dynamic> is invoked before proceeding with
+the requested operation. Based on the value returned by C<dynamic>, the
+operation is either permitted or denied. When the connection with the
+client is closed, the method C<access_close> is called if it exists.
+
+=head2 Writing a Python B<nnrpd> Authentication Module
+
+You need to create a F<nnrpd_auth.py> module in INN's filter directory
+(see the I<pathfilter> setting in F<inn.conf>) where you should define a
+class holding certain methods depending on which hooks you want to use.
+
+Note that you will have to use different Python scripts for authentication
+and access: the values of I<python_auth>, I<python_access> and I<python_dynamic>
+have to be distinct for your scripts to work.
+
+The following methods are known to B<nnrpd>:
+
+=over 4
+
+=item __init__(I<self>)
+
+Not explicitly called by B<nnrpd>, but will run whenever the auth module is
+loaded. Use this method to initialize any general variables or open
+a common database connection. This method may be omitted.
+
+=item authen_init(I<self>)
+
+Initialization function specific to authentication. This method may be
+omitted.
+
+=item authenticate(I<self>, I<attributes>)
+
+Called when a I<python_auth> statement is reached in the processing of
+F<readers.conf>. Connection attributes are passed in the I<attributes>
+dictionary. Returns a response code, an error string, and an optional
+string to be used in place of the client-supplied username (both for
+logging and for matching the connection with an access group).
+
+=item authen_close(I<self>)
+
+This method is invoked on B<nnrpd> termination. You can use it to save
+state information or close a database connection. This method may be omitted.
+
+=item access_init(I<self>)
+
+Initialization function specific to generation of an access group. This
+method may be omitted.
+
+=item access(I<self>, I<attributes>)
+
+Called when a I<python_access> statement is reached in the processing of
+F<readers.conf>. Connection attributes are passed in the I<attributes>
+dictionary. Returns a dictionary of values representing statements to
+be included in an access group.
+
+=item access_close(I<self>)
+
+This method is invoked on B<nnrpd> termination. You can use it to save
+state information or close a database connection. This method may be omitted.
+
+=item dynamic_init(I<self>)
+
+Initialization function specific to dynamic access control. This
+method may be omitted.
+
+=item dynamic(I<self>, I<attributes>)
+
+Called when a client requests a newsgroup, an article or attempts to
+post. Connection attributes are passed in the I<attributes> dictionary.
+Returns C<None> to grant access, or a non-empty string (which will be
+reported back to the client) otherwise.
+
+=item dynamic_close(I<self>)
+
+This method is invoked on B<nnrpd> termination. You can use it to save
+state information or close a database connection. This method may be omitted.
+
+=back
+
+=head2 The I<attributes> Dictionary
+
+The keys and associated values of the I<attributes> dictionary are
+described below.
+
+=over 4
+
+=item I<type>
+
+C<read> or C<post> values specify the authentication type; only valid
+for the C<dynamic> method.
+
+=item I<hostname>
+
+It is the resolved hostname (or IP address if resolution fails) of
+the connected reader.
+
+=item I<ipaddress>
+
+The IP address of the connected reader.
+
+=item I<port>
+
+The port of the connected reader.
+
+=item I<interface>
+
+The hostname of the local endpoint of the NNTP connection.
+
+=item I<intipaddr>
+
+The IP address of the local endpoint of the NNTP connection.
+
+=item I<intport>
+
+The port of the local endpoint of the NNTP connection.
+
+=item I<user>
+
+The username as passed with AUTHINFO command, or C<None> if not
+applicable.
+
+=item I<pass>
+
+The password as passed with AUTHINFO command, or C<None> if not
+applicable.
+
+=item I<newsgroup>
+
+The name of the newsgroup to which the reader requests read or post access;
+only valid for the C<dynamic> method.
+
+=back
+
+All the above values are buffer objects (see the notes above on what
+buffer objects are).
+
+=head2 How to Use these Methods with B<nnrpd>
+
+To register your methods with B<nnrpd>, you need to create an instance of
+your class, import the built-in B<nnrpd> module, and pass the instance to
+C<nnrpd.set_auth_hook>. For example:
+
+ class AUTH:
+ def authen_init(self):
+ ...
+ blah blah
+ ...
+
+ def authenticate(self, attributes):
+ ...
+ yadda yadda
+ ...
+
+ import nnrpd
+ myauth = AUTH()
+ nnrpd.set_auth_hook(myauth)
+
+When writing and testing your Python filter, don't be afraid to make use
+of C<try:>/C<except:> and the provided C<nnrpd.syslog> function. stdout and stderr
+will be disabled, so your filter will die silently otherwise.
+
+Also, remember to try importing your module interactively before loading
+it, to ensure there are no obvious errors. One typo can ruin your whole
+filter. A dummy F<nnrpd.py> module is provided to facilitate testing outside
+the server. It is not actually used by B<nnrpd> but provides the same set
+of functions as built-in B<nnrpd> module. This stub module may be used
+when debugging your own module. To test, change into your filter directory
+and use a command like:
+
+ python -ic 'import nnrpd, nnrpd_auth'
+
+=head2 Functions Supplied by the Built-in B<nnrpd> Module
+
+Besides C<nnrpd.set_auth_hook> used to pass a reference to the instance
+of authentication and authorization class to B<nnrpd>, the B<nnrpd> built-in
+module exports the following function:
+
+=over 4
+
+=item syslog(I<level>, I<message>)
+
+It is intended to be a replacement for a Python native syslog. It works
+like C<INN.syslog>, seen above.
+
+=back
+
+$Id: hook-python.pod 7926 2008-06-29 08:27:41Z iulius $
+
+=cut
--- /dev/null
+=head1 NAME
+
+ident - nnrpd ident resolver
+
+=head1 SYNOPSIS
+
+B<ident> [B<-p> I<port>] [B<-t>]
+
+=head1 DESCRIPTION
+
+This program attempts to resolve usernames for B<nnrpd> by using the
+ident protocol to query the remote host. It contacts the remote host
+using either IPv4 or IPv6 depending on which protocol was used for the
+incoming NNTP connection.
+
+=head1 OPTIONS
+
+=over 4
+
+=item B<-p> I<port>
+
+If this option is given, attempt to contact identd on the specified
+remote port (which can be a numeric or symbolic specification).
+Non-numeric values will be looked up using getservbyname(3). The
+default value is the result of C<getservbyname("ident")> if available,
+or port 113 otherwise.
+
+=item B<-t>
+
+If this option is given, the identity returned will never have a domain
+part. That is, if the remote server returns a result containing an C<@>
+character, B<ident> truncates the response at the C<@>. This is useful
+to allow the I<default-domain> parameter in F<reaers.conf> to override
+the domain supplied by the remote host (particularly if the supplied
+domain part is an unqualified local machine name rather than a full
+domain name).
+
+=back
+
+=head1 EXAMPLE
+
+The following readers.conf(5) fragment tells nnrpd to trust ident
+information for hosts on a local network, but to replace the domain
+returned from the ident query:
+
+ auth LAN {
+ hosts: "192.168/16"
+ res: "ident -t"
+ default-domain: "internal.example.com"
+ }
+
+ access LAN {
+ users: "*@internal.example.com"
+ newsgroups: example.*
+ }
+
+Access is granted to the example.* groups for all users on the local
+network whose machines respond to ident queries.
+
+=head1 HISTORY
+
+This documentation was written by Jeffrey M. Vinocur <jeff@litech.org>.
+
+$Id: ident.pod 5988 2002-12-12 23:02:14Z vinocur $
+
+=head1 SEE ALSO
+
+nnrpd(8), readers.conf(5)
+
+=cut
--- /dev/null
+=head1 NAME
+
+inews - Post a Usenet article to the local news server
+
+=head1 SYNOPSIS
+
+B<inews> [B<-ADhNORSVW>] [B<-acdeFfnortwx> I<value>] [B<-p> I<port>] [I<file>]
+
+=head1 DESCRIPTION
+
+B<inews> reads a Usenet news article, perhaps with headers, from I<file>
+or standard input if no file is given. It adds some headers and performs
+some consistency checks. If the article does not meet those checks, the
+article is rejected. If it passes the checks, B<inews> sends the article
+to the local news server as specified in F<inn.conf>.
+
+By default, if a file named F<.signature> exists in the home directory of
+the posting user, it is appended to the post, preceeded by a line that
+contains only C<-- >. Signatures are not allowed to be more than four
+lines long.
+
+Cancel messages can only be posted with B<inews> if the sender of the
+cancel message matches the sender of the original message being
+cancelled. The same check is also applied to Supersedes. Sender in this
+case means the contents of the Sender header if present, otherwise the
+From header.
+
+Control messages other than cancel messages are only allowed if B<inews>
+is being run by the news user or by a user in the news group and if the
+control message is recognized. If the article contains a Distribution
+header with a distribution that matches one of the bad distribution
+patterns in F<inn/options.h> (anything containing a period by default),
+the message will be rejected. The message will also be rejected if
+I<checkincludedtext> is true in F<inn.conf>, it contains more quoted text
+than original text, and it is over 40 lines long.
+
+If not provided, the Path header of an article is constructed as follows:
+The basic Path header will be "not-for-mail". If I<pathhost> is specified
+in F<inn.conf>, it will be added to the beginning Path. Otherwise, if
+I<server> is specified, the full domain of the local host will be added to
+the beginning of the Path. Then, if B<-x> was given, its value will be
+added to the beginning of the Path.
+
+If posting fails, a copy of the failed post will be saved in a file named
+F<dead.article> in the home directory of the user running B<inews>.
+B<inews> exits with a non-zero status if posting failed or with a zero
+status if posting was successful.
+
+=head1 OPTIONS
+
+Most of the options to B<inews> take a single value and set the
+corresponding header in the message that is posted. If the value is more
+than one word or contains any shell metacharacters, it must be quoted to
+protect it from the shell. Here are all the options that set header
+fields and the corresponding header:
+
+ -a Approved
+ -c Control
+ -d Distribution
+ -e Expires
+ -F References
+ -f From
+ -n Newsgroups
+ -o Organization
+ -r Reply-To
+ -t Subject
+ -w Followup-To
+ -x Path prefix
+
+The B<-x> argument will be added to the beginning of the normal Path
+header; it will not replace it.
+
+=over 4
+
+=item B<-A>, B<-V>, B<-W>
+
+Accepted for compatibility with C News. These options have no affect.
+
+=item B<-D>, B<-N>
+
+Perform the consistency checks and add headers where appropriate, but then
+print the article to standard output rather than sending it to the server.
+B<-N> is accepted as as synonym for compatibility with C News.
+
+=item B<-h>
+
+Normally, this flag should always be given. It indicates that the article
+consists of headers, a blank line, and then the message body. If it is
+omitted, the input is taken to be just the body of the message, and any
+desired headers have to be specified with command-line options as
+described above.
+
+=item B<-O>
+
+By default, an Organization header will be added if none is present in the
+article. To prevent adding the default (from I<organization> in
+F<inn.conf>), use this flag.
+
+=item B<-p> I<port>
+
+Connect to the specified port on the server rather than to the default
+(port 119).
+
+=item B<-R>
+
+Reject all control messages.
+
+=item B<-S>
+
+Do not attempt to append F<~/.signature> to the message, even if it
+exists.
+
+=back
+
+=head1 NOTES
+
+If the NNTP server requests authentication, B<inews> will try to read
+F<passwd.nntp> to get the username and password to use and will therefore
+need read access to that file. This is typically done by making that file
+group-readable and adding all users who should be able to use B<inews> to
+post to that server to the appropriate group.
+
+B<inews> used to do even more than it does now, and all of the remaining
+checks that are not dependent on the user running B<inews> should probably
+be removed in favor of letting the news server handle them.
+
+Since INN's B<inews> uses F<inn.conf> and some other corners of an INN
+installation, it's not very appropriate as a general stand-alone B<inews>
+program for general use on a system that's not running a news server.
+Other, more suitable versions of B<inews> are available as part of various
+Unix news clients or by themselves.
+
+=head1 HISTORY
+
+Written by Rich $alz <rsalz@uunet.uu.net> for InterNetNews. Rewritten in
+POD by Russ Allbery <rra@stanford.edu>.
+
+=head1 SEE ALSO
+
+inn.conf(5), rnews(1)
+
+=cut
--- /dev/null
+=head1 NAME
+
+inn.conf - Configuration data for InterNetNews programs
+
+=head1 DESCRIPTION
+
+F<inn.conf> in I<pathetc> is the primary general configuration file for
+all InterNetNews programs. Settings which control the general operation
+of various programs, as well as the paths to all portions of the news
+installation, are found here. The INNCONF environment variable, if set,
+specifies an alternate path to F<inn.conf>.
+
+This file is intended to be fairly static. Any changes made to it will
+generally not affect any running programs until they restart. Unlike
+nearly every other configuration file, F<inn.conf> cannot be reloaded
+dynamically using ctlinnd(8); innd(8) must be stopped and restarted for
+relevant changes to F<inn.conf> to take effect (C<ctlinnd xexec innd> is
+the fastest way to do this.)
+
+Blank lines and lines starting with a number sign (C<#>) are ignored. All
+other lines specify parameters, and should be of the following form:
+
+ <name>: <value>
+
+(Any amount of whitespace can be put after the colon and is optional.) If
+the value contains embedded whitespace or any of the characers C<[]<>"\:>,
+it must be enclosed in double quotes (""). A backslash (C<\>) can be used
+to escape quotes and backslashes inside double quotes. <name> is
+case-sensitive; C<server> is not the same as C<Server> or C<SERVER>.
+(F<inn.conf> parameters are generally all in lowercase.)
+
+If <name> occurs more than once in the file, the first value is used.
+Some parameters specified in the file may be overridden by environment
+variables. Most parameters have default values if not specified in
+F<inn.conf>; those defaults are noted in the description of each
+parameter.
+
+Many parameters take a boolean value. For all such parameters, the value
+may be specified as C<true>, C<yes>, or C<on> to turn it on and may be any
+of C<false>, C<no>, or C<off> to turn it off. The case of these values is
+significant.
+
+This documentation is extremely long and organized as a reference manual
+rather than as a tutorial. If this is your first exposure to INN and
+these parameters, it would be better to start by reading other man pages
+and referring to this one only when an F<inn.conf> parameter is explicitly
+mentioned. Those parameters which need to be changed when setting up a
+new server are discussed in F<INSTALL>.
+
+=head1 PARAMETERS
+
+=head2 General Settings
+
+These parameters are used by a wide variety of different components of
+INN.
+
+=over 4
+
+=item I<domain>
+
+This should be the domain name of the local host. It should not have a
+leading period, and it should not be a full host address. It is used only
+if the GetFQDN() routine in libinn(3) cannot get the fully-qualified
+domain name by using either the gethostname(3) or gethostbyname(3) calls.
+The check is very simple; if either routine returns a name with a period
+in it, then it is assumed to have the full domain name. As this parameter
+is rarely used, do not use it to affect the righthand side of
+autogenerated Message-IDs; see instead I<virtualhost> and I<domain> in
+L<readers.conf>. The default value is unset.
+
+=item I<innflags>
+
+The flags to pass to innd on startup. See innd(8) for details on the
+possible flags. The default value is unset.
+
+=item I<mailcmd>
+
+The path to the program to be used for mailing reports and control
+messages. The default is I<pathbin>/innmail. This should not normally
+need to be changed.
+
+=item I<mta>
+
+The command to use when mailing postings to moderators and for the use of
+innmail(1). The message, with headers and an added To: header, will be
+piped into this program. The string C<%s>, if present, will be replaced
+by the e-mail address of the moderator. It's strongly recommended for
+this command to include C<%s> on the command line rather than use the
+addresses in the To: and Cc: headers of the message, since the latter
+approach allows the news server to be abused as a mechanism to send mail
+to arbitrary addresses and will result in unexpected behavior. There is
+no default value for this parameter; it must be set in F<inn.conf> or a
+fatal error message will be logged via syslog.
+
+For most systems, C</usr/lib/sendmail -oi -oem %s> (adjusted for the
+correct path to sendmail) is a good choice.
+
+=item I<pathhost>
+
+What to put into the Path: header to represent the local site. This is
+added to the Path: header of all articles that pass through the system,
+including locally posted articles, and is also used when processing some
+control messages and when naming the server in status reports. There is
+no default value; this parameter must be set in F<inn.conf> or INN will
+not start. A good value to use is the fully-qualified hostname of the
+system.
+
+=item I<server>
+
+The name of the default NNTP server. If I<nnrpdposthost> is not set and
+UNIX domain sockets are not supported, nnrpd(8) tries to hand off
+locally-posted articles through an INET domain socket to this server.
+actsync(8), nntpget(8), and getlist(8) also use this value as the default
+server to connect to. In the latter cases, the value of the NNTPSERVER
+environment variable, if it exists, overrides this. The default value is
+unset.
+
+=back
+
+=head2 Feed Configuration
+
+These parameters govern incoming and outgoing feeds: what size of
+articles are accepted, what filtering and verification is performed on
+them, whether articles in groups not carried by the server are still
+stored and propagated, and other similar settings.
+
+=over 4
+
+=item I<artcutoff>
+
+Articles older than this number of days are dropped. This setting should
+probably match the setting on the C</remember/> line in F<expire.ctl>.
+The default value is C<10>.
+
+=item I<bindaddress>
+
+Which IP address innd(8) should bind itself to. This must be in
+dotted-quad format (nnn.nnn.nnn.nnn). If set to C<all> or not set, innd
+defaults to listening on all interfaces. The value of the
+INND_BIND_ADDRESS environment variable, if set, overrides this setting.
+The default value is unset.
+
+=item I<bindaddress6>
+
+Like I<bindaddress> but for IPv6 sockets. If only one of the I<bindaddress>
+and I<bindaddress6> parameters is used, then only the socket for the
+corresponding address family is created. If both parameters are used
+then two sockets are created. If neither of them is used, the list of
+sockets to listen on will be determined by the system library
+I<getaddrinfo(3)> function. The value of the INND_BIND_ADDRESS6, if set,
+overrides this setting. The default value is unset.
+
+Note that you will generally need to put double quotes ("") around this
+value if you set it, since IPv6 addresses contain colons.
+
+=item I<hiscachesize>
+
+If set to a value other than C<0>, a hash of recently received message IDs
+is kept in memory to speed history lookups. The value is the amount of
+memory to devote to the cache in kilobytes. The cache is only used for
+incoming feeds and a small cache can hold quite a few message IDs, so
+large values aren't necessarily useful unless you have incoming feeds that
+are badly delayed. A good value for a system with more than one incoming
+feed is C<256>; systems with only one incoming feed should probably leave
+this at C<0>. The default value is C<0>.
+
+=item I<ignorenewsgroups>
+
+Whether newsgroup creation control messages (newgroup and rmgroup) should
+be fed as if they were posted to the newsgroup they are creating or
+deleting rather than to the newsgroups listed in the Newsgroups: header.
+If this parameter is set, the newsgroup affected by the control message
+will be extracted from the Control: header and the article will be fed as
+if its Newsgroups: header contained solely that newsgroup. This is useful
+for routing control messages to peers when they are posted to irrelevant
+newsgroups that shouldn't be matched against the peer's desired newsgroups
+in F<newsfeeds>. This is a boolean value and the default is false.
+
+=item I<immediatecancel>
+
+When using the timecaf storage method, article cancels are normally just
+cached to be cancelled, not cancelled immediately. If this is set to
+true, they will instead by cancelled as soon as the cancel is processed.
+This is a boolean value and the default is false.
+
+This setting is ignored unless the timecaf storage method is used.
+
+=item I<linecountfuzz>
+
+If set to something other than C<0>, the line count of the article is
+checked against the Lines: header of the article (if present) and the
+artice is rejected if the values differ by more than this amount. A
+reasonable setting is C<5>, which is the standard maximum signature length
+plus one (some injection software calculates the Lines: header before
+adding the signature). The default value is C<0>, which tells INN not to
+check the Lines: header of incoming articles.
+
+=item I<maxartsize>
+
+The maximum size of article (headers and body) that will be accepted by
+the server, in bytes. A value of C<0> allows any size of article, but
+note that B<innd> will crash if system memory is exceeded. The default
+value is C<1000000> (approximately 1 MB). See also I<localmaxartsize>.
+
+=item I<maxconnections>
+
+The maximum number of incoming NNTP connections innd(8) will accept. The
+default value is C<50>.
+
+=item I<pathalias>
+
+If set, this value is prepended to the Path: header of accepted posts
+(before I<pathhost>) if it doesn't already appear in the Path: header.
+The main purpose of this parameter is to configure all news servers within
+a particular organization to add a common identity string to the
+Path: header. The default value is unset.
+
+=item I<pathcluster>
+
+If set, this value is appended to the Path: header of accepted posts
+(after I<pathhost>) if it isn't already present as the last element
+of the Path: header. The main purpose of this parameter is to make
+several news servers appear as one server. The default value is unset.
+
+Note that the Path: header reads right to left, so appended means inserted
+at the leftmost side of the Path: header.
+
+=item I<pgpverify>
+
+Whether to enable PGP verification of control messages other than cancel.
+This is a boolean value and the default is based on whether configure found
+pgp, pgpv, or gpgv.
+
+=item I<port>
+
+What TCP port innd(8) should listen on. The default value is C<119>, the
+standard NNTP port.
+
+=item I<refusecybercancels>
+
+Whether to refuse all articles whose message IDs start with
+C<E<lt>cancel.>. This message ID convention is widely followed by spam
+cancellers, so the vast majority of such articles will be cancels of spam.
+This check, if enabled, is done before the history check and the message
+ID is not written to the history file. This is a boolean value and the
+default is false.
+
+This is a somewhat messy, inefficient, and inexact way of refusing spam
+cancels. A much better way is to ask all of your upstream peers to not
+send to you any articles with C<cyberspam> in the Path: header (usually
+accomplished by having them mark C<cyberspam> as an alias for your machine
+in their feed configuration). The filtering enabled by this parameter is
+hard-coded; general filtering of message IDs can be done via the embedded
+filtering support.
+
+=item I<remembertrash>
+
+By default, innd(8) records rejected articles in history so that, if
+offered the same article again, it can be refused before it is sent. If
+you wish to disable this behavior, set this to false. This can cause a
+substantial increase in the amount of bandwidth consumed by incoming news
+if you have several peers and reject a lot of articles, so be careful with
+it. Even if this is set to true, INN won't log some rejected articles to
+history if there's reason to believe the article might be accepted if
+offered by a different peer, so there is usually no reason to set this to
+false (although doing so can decrease the size of the history file). This
+is a boolean value and the default is true.
+
+=item I<sourceaddress>
+
+Which local IP address to bind to for outgoing NNTP sockets (used by
+innxmit(8) among other programs, but I<not> innfeed(8) -- see
+I<bindaddress> in innfeed.conf(5) for that). This must be in dotted-quad
+format (nnn.nnn.nnn.nnn). If set to C<all> or not set, the operating
+system will choose the source IP address for outgoing connections. The
+default value is unset.
+
+=item I<sourceaddress6>
+
+Like I<sourceaddress> but for IPv6 sockets.
+
+=item I<verifycancels>
+
+Set this to true to enable a simplistic check on all cancel messages,
+attempting to verify (by simple header comparison) that the cancel message
+is from the same person as the original post. This can't be done if the
+cancel arrives before the article does, and is extremely easy to spoof.
+While this check may once have served a purpose, it's now essentially
+security via obscurity, commonly avoided by abusers, and probably not
+useful. This is a boolean value, and the default is false.
+
+=item I<wanttrash>
+
+Set this to true if you want to file articles posted to unknown newsgroups
+(newsgroups not in the F<active> file) into the C<junk> newsgroup rather
+than rejecting them. This is sometimes useful for a transit news server
+that needs to propagate articles in all newsgroups regardless if they're
+carried locally. This is a boolean value and the default is false.
+
+=item I<wipcheck>
+
+If INN is offered an article by a peer on one channel, it will return
+deferral responses (code 436) to all other offers of that article for this
+many seconds. (After this long, if the peer that offered the article
+still hasn't sent it, it will be accepted from other channels.) The
+default value is C<5> and probably doesn't need to be changed.
+
+=item I<wipexpire>
+
+How long, in seconds, to keep track of message IDs offered on a channel
+before expiring articles that still haven't been sent. The default value
+is C<10> and probably doesn't need to be changed.
+
+=item I<dontrejectfiltered>
+
+Normally innd(8) rejects incoming articles when directed to do so by any
+enabled article filters (Perl, Python, and TCL). However, this parameter
+causes such articles I<not> to be rejected; instead filtering can be
+applied on outbound articles. If this parameter is set, all articles will
+be accepted on the local machine, but articles rejected by the filter will
+I<not> be fed to any peers specified in F<newsfeeds> with the C<Af> flag.
+
+=back
+
+=head2 Article Storage
+
+These parameters affect how articles are stored on disk.
+
+=over 4
+
+=item I<cnfscheckfudgesize>
+
+If set to a value other than C<0>, the claimed size of articles in CNFS
+cycbuffs is checked against I<maxartsize> plus this value, and if larger,
+the CNFS cycbuff is considered corrupt. This can be useful as a sanity
+check after a system crash, but be careful using this parameter if you
+have changed I<maxartsize> recently. The default value is C<0>.
+
+=item I<enableoverview>
+
+Whether to write out overview data for articles. If set to false, INN
+will run much faster, but reading news from the system will be impossible
+(the server will be for news transit only). If this option is set to
+true, I<ovmethod> must also be set. This is a boolean value and the
+default is true.
+
+=item I<groupbaseexpiry>
+
+Whether to enable newsgroup-based expiry. If set to false, article expiry
+is done based on storage class of storing method. If set to true (and
+overview information is available), expiry is done by newsgroup name.
+This affects the format of F<expire.ctl>. This is a boolean value and the
+default is true.
+
+=item I<mergetogroups>
+
+Whether to file all postings to C<to.*> groups in the pseudonewsgroup
+C<to>. If this is set to true, the newsgroup C<to> must exist in the
+F<active> file or INN will not start. (See the discussion of C<to.>
+groups in innd(8) under CONTROL MESSAGES.) This is a boolean value and
+the default is false.
+
+=item I<overcachesize>
+
+How many cache slots to reserve for open overview files. If INN is
+writing overview files (see I<enableoverview>), I<ovmethod> is set to
+C<tradindexed>, and this is set to a value other than C<0>, INN will keep
+around and open that many recently written-to overview files in case more
+articles come in for those newsgroups. Every overview cache slot consumes
+two file descriptors, so be careful not to set this value too high. You
+may be able to use the C<limit> command to see how many open file
+descriptors your operating system allows. innd(8) also uses an open file
+descriptor for each incoming feed and outgoing channel or batch file, and
+if it runs out of open file descriptors it may throttle and stop accepting
+new news. The default value is C<15> (which is probably way too low if
+you have a large number of file descriptors available).
+
+This setting is ignored unless I<ovmethod> is set to C<tradindexed>.
+
+=item I<ovgrouppat>
+
+If set, restricts the overview data stored by INN to only the newsgroups
+matching this comma-separated list of wildmat expressions. Newsgroups not
+matching this setting may not be readable, and if I<groupbaseexpiry> is
+set to true and the storage method for these newsgroups does not have
+self-expire functionality, storing overview data will fail.
+The default is unset.
+
+=item I<ovmethod>
+
+Which overview storage method to use. Currently supported values are
+C<tradindexed>, C<buffindexed>, and C<ovdb>. There is no default value;
+this parameter must be set if I<enableoverview> is true (the default).
+
+=over 4
+
+=item C<buffindexed>
+
+Stores overview data and index information into buffers, which are
+preconfigured files defined in F<buffinedexed.conf>. C<buffindexed> never
+consumes additional disk space beyond that allocated to these buffers.
+
+=item C<tradindexed>
+
+Uses two files per newsgroup, one containing the overview data and one
+containing the index. Fast for readers, but slow to write to.
+
+=item C<ovdb>
+
+Stores data into a Berkeley DB database. See the ovdb(5) man page.
+
+=back
+
+=item I<hismethod>
+
+Which history storage method to use. The only currently supported
+value is C<hisv6>. There is no default value; this parameter must
+be set.
+
+=over 4
+
+=item C<hisv6>
+
+Stores history data in the INN history v6 format: history(5) text
+file and a number of dbz(3) database files; this may be in true history
+v6 format, or tagged hash format, depending on the build
+options. Separation of these two is a project which has not yet been
+undertaken.
+
+=back
+
+=item I<storeonxref>
+
+If set to true, articles will be stored based on the newsgroup names in
+the Xref: header rather than in the Newsgroups: header. This affects what
+the patterns in F<storage.conf> apply to. The primary interesting effect
+of setting this to true is to enable filing of all control messages
+according to what storage class the control pseudogroups are filed in
+rather than according to the newsgroups the control messages are posted
+to. This is a boolean value and the default is true.
+
+=item I<useoverchan>
+
+Whether to innd(8) should create overview data internally through
+libstorage(3). If set to false, innd creates overview data by itself. If
+set to true, innd does not create; instead overview data must be created
+by overchan(8) from an appropriate entry in F<newsfeeds>. Setting to true
+may be useful, if innd cannot keep up with incoming feed and the
+bottleneck is creation of overview data within innd. This is a boolean
+value and the default is false.
+
+=item I<wireformat>
+
+Only used with the tradspool storage method, this says whether to write
+articles in wire format. Wire format means storing articles with C<\r\n> at
+the end of each line and with periods at the beginning of lines doubled,
+the article format required by the NNTP protocol. Articles stored in this
+format are suitable for sending directly to a network connection without
+requiring conversion, and therefore setting this to true can make the
+server more efficient. The primary reason not to set this is if you have
+old existing software that looks around in the spool and doesn't
+understand how to read wire format. Storage methods other than tradspool
+always store articles in wire format. This is a boolean value and the
+default is false.
+
+=item I<xrefslave>
+
+Whether to act as the slave of another server. If set, INN attempts to
+duplicate exactly the article numbering of the server feeding it by
+looking at the Xref: header of incoming articles and assigning the same
+article numbers to articles as was noted in the Xref: header from the
+upstream server. The result is that clients should be able to point at
+either server interchangeably (using some load balancing scheme, for
+example) and see the same internal article numbering. Servers with this
+parameter set should generally only have one upstream feed, and should
+always have I<nnrpdposthost> set to hand locally posted articles off to
+the master server. The upstream should be careful to always feed articles
+in order (innfeed(8) can have problems with this in the event of a
+backlog). This is a boolean value and the default is false.
+
+=item I<nfswriter>
+
+For servers writing articles, determine whether the article spool is
+on NFS storage. If set, INN attempts to flush articles to the spool
+in a more timely manner, rather than relying on the operating system
+to flush things such as the CNFS article bitmaps. You should only set
+this parameter if you are attempting to use a shared NFS spool on a
+machine acting as a single writer within a cluster. This is a boolean
+value and the default is false.
+
+=item I<nfsreader>
+
+For servers reading articles, determine whether the article spool is
+on NFS storage. If set, INN will attempt to force articles and
+overviews to be read directly from the NFS spool rather than from
+cached copies. You should only set this parameter if you are
+attempting to use a shared NFS spool on a machine acting a reader a
+cluster. This is a boolean value and the default is false.
+
+=item I<nfsreaderdelay>
+
+For servers reading articles, determine whether the article spool is
+on NFS storage. If I<nfsreader> is set, INN will use the value of
+I<nfsreaderdelay> to delay the apparent arrival time of articles to
+clients by this amount; this value should be tuned based on the NFS
+cache timeouts locally. This default is 60 (1 minute).
+
+=item I<msgidcachesize>
+
+How many cache slots to reserve for Message ID to storage token
+translations. When serving overview data to clients (NEWNEWS, XOVER
+etc.), nnrpd(8) can cache the storage token associated with a Message
+ID and save the cost of looking it up in the history file; for some
+configurations setting this parameter can save more than 90% of the
+wall clock time for a session. The default value is 10000.
+
+=item I<tradindexedmmap>
+
+Whether to attempt to mmap() tradindexed overviews articles. Setting
+this to true will give better performance on most systems, but some
+systems have problems with mmap(). If this is set to false, overviews
+will be read into memory before being sent to readers. This is a
+boolean value and the default is true.
+
+=back
+
+=head2 Reading
+
+These parameters affect the behavior of INN for readers. Most of them are
+used by nnrpd(8). There are some special sets of settings that are broken
+out separately after the initial alphabetized list.
+
+=over 4
+
+=item I<allownewnews>
+
+Whether to allow use of the NEWNEWS command by clients. This command used
+to put a heavy load on the server in older versions of INN, but is now
+reasonably efficient, at least if only one newsgroup is specified by the
+client. This is a boolean value and the default is true. If you use the
+I<access> parameter in F<readers.conf>, be sure to read about the way it
+overrides I<allownewnews>.
+
+=item I<articlemmap>
+
+Whether to attempt to mmap() articles. Setting this to true will give
+better performance on most systems, but some systems have problems with
+mmap(). If this is set to false, articles will be read into memory before
+being sent to readers. This is a boolean value and the default is false.
+
+=item I<clienttimeout>
+
+How long (in seconds) a client connection can be idle before it exits.
+When setting this parameter, be aware that some newsreaders use the same
+connection for reading and posting and don't deal well with the connection
+timing out while a post is being composed. If the system isn't having a
+problem with too many long-lived connections, it may be a good idea to
+increase this value to C<3600> (an hour). The default value is C<600>
+(ten minutes).
+
+=item I<initialtimeout>
+
+How long (in seconds) B<nnrpd> will wait for the first command from a
+reader connection before dropping the connection. This is a defensive
+timeout intended to protect the news server from badly behaved reader
+clients that open and abandon a multitude of connections without every
+closing them. The default value is C<10> (ten seconds), which may need to
+be increased if many clients connect via slow network links.
+
+=item I<nnrpdcheckart>
+
+Whether B<nnrpd> should check the existence of an article before listing
+it as present in response to an NNTP command. The primary use of this
+setting is to prevent nnrpd from returning information about articles
+which are no longer present on the server but which still have overview
+data available. Checking the existence of articles before returning
+overview information slows down the overview commands, but reduces the
+number of "article is missing" errors seen by the client. This is a
+boolean value and the default is true.
+
+=item I<nnrpperlauth>
+
+This parameter is now obsolete; see "Changes to Perl Authentication
+Support for nnrpd" in F<doc/hook-perl>.
+
+=item I<nnrppythonauth>
+
+This parameter is now obsolete; see "Changes to Python Authentication and
+Access Control Support for nnrpd" in F<doc/hook-python>.
+
+=item I<noreader>
+
+Normally, innd(8) will fork a copy of nnrpd(8) for all incoming
+connections from hosts not listed in F<incoming.conf>. If this parameter
+is set to true, those connections will instead be rejected with a 502
+error code. This should be set to true for a transit-only server that
+doesn't support readers, or if nnrpd is running in daemon mode or being
+started out of inetd. This is a boolean value and the default is false.
+
+=item I<readerswhenstopped>
+
+Whether to allow readers to connect even if the server is paused or
+throttled. This is only applicable if nnrpd(8) is spawned from innd(8)
+rather than run out of inetd or in daemon mode. This is a boolean value
+and the default is false.
+
+=item I<readertrack>
+
+Whether to enable the tracking system for client behavior. Tracked
+information is recorded to I<pathlog>/tracklogs/log-ID, where ID is
+determined by nnrpd's PID and launch time.) Currently the information
+recorded includes initial connection and posting; only information about
+clients listed in F<nnrpd.track> is recorded. This is a boolean value and
+the default is false.
+
+=item I<nnrpdflags>
+
+When nnrpd(8) is spawned from innd(8), these flags are passed as
+arguments to the nnrpd process. This setting does not affect instances
+of nnrpd that are started in daemon mode, or instances that are started
+via another listener process such as inetd(8) or xinetd(8). Shell
+quoting and metacharacters are not supported. This is a string value
+and the default is unset.
+
+=item I<nnrpdloadlimit>
+
+If set to a value other than C<0>, connections to nnrpd will be refused
+if the system load average is higher than this value. The default value
+is C<16>.
+
+=back
+
+INN has optional support for generating keyword information automatically
+from article body text and putting that information in overview for the
+use of clients that know to look for it. The following parameters control
+that feature.
+
+This may be too slow if you're taking a substantial feed, and probably
+will not be useful for the average news reader; enabling this is not
+recommended unless you have some specific intention to take advantage of
+it.
+
+=over 4
+
+=item I<keywords>
+
+Whether the keyword generation support should be enabled. This is a
+boolean value and the default is false.
+
+FIXME: Currently, support for keyword generation is configured into INN
+semi-randomly (based on whether configure found the regex library); it
+should be an option to configure and that option should be mentioned here.
+
+=item I<keyartlimit>
+
+Articles larger than this value in bytes will not have keywords generated
+for them (since it would take too long to do so). The default value is
+C<100000> (approximately 100 KB).
+
+=item I<keylimit>
+
+Maximum number of bytes allocated for keyword data. If there are more
+keywords than will fit into this many bytes when separated by commas, the
+rest are discarded. The default value is C<512>.
+
+=item I<keymaxwords>
+
+Maximum number of keywords that will be generated for an article. (The
+keyword generation code will attempt to discard "noise" words, so the
+number of keywords actually writen into the overview will usually be
+smaller than this even if the maximum number of keywords is found.) The
+default value is C<250>.
+
+=back
+
+=head2 Posting
+
+These parameters are only used by nnrpd(8), inews(1), and other programs
+that accept or generate postings. There are some special sets of settings
+that are broken out separately after the initial alphabetized list.
+
+=over 4
+
+=item I<addnntppostingdate>
+
+Whether to add an NNTP-Posting-Date: header to all local posts. This is a
+boolean value and the default is true. Note that INN either does not add
+this header or adds the name or IP address of the client. There is no
+intrinsic support for obfuscating the name of the client. That has to be
+done with a user-written Perl filter, if desired.
+
+=item I<addnntppostinghost>
+
+Whether to add an NNTP-Posting-Host: header to all local posts giving the
+FQDN or IP address of the system from which the post was received. This
+is a boolean value and the default is true.
+
+=item I<checkincludedtext>
+
+Whether to check local postings for the ratio of new to quoted text and
+reject them if that ratio is under 50%. Included text is recognized by
+looking for lines beginning with C<E<gt>>, C<|>, or C<:>. This is a
+boolean value and the default is false.
+
+=item I<complaints>
+
+The value of the X-Complaints-To: header added to all local posts. The
+default is the newsmaster's e-mail address. (If the newsmaster, selected
+at configure time and defaulting to C<usenet>, doesn't contain C<@>, the
+address will consist of the newsmaster, a C<@>, and the value of
+I<fromhost>.)
+
+=item I<fromhost>
+
+Contains a domain used to construct e-mail addresses. The address of the
+local news administrator will be given as <user>@I<fromhost>, where <user>
+is the newsmaster user set at compile time (C<usenet> by default). This
+setting will also be used by mailpost(8) to fully qualify addresses and by
+inews(1) to generate the Sender: header (and From: header if missing).
+The value of the FROMHOST environment variable, if set, overrides this
+setting. The default is the fully-qualified domain name of the local
+host.
+
+=item I<localmaxartsize>
+
+The maximum article size (in bytes) for locally posted articles. Articles
+larger than this will be rejected. A value of C<0> allows any size of
+article, but note that B<nnrpd> and B<innd> will crash if system memory is
+exceeded. See also I<maxartsize>, which applies to all articles including
+those posted locally. The default value is C<1000000> (approximately 1
+MB).
+
+=item I<moderatormailer>
+
+The address to which to send submissions for moderated groups. It is only
+used if the F<moderators> file doesn't exist, or if the moderated group to
+which an article is posted is not matched by any entry in that file, and
+takes the same form as an entry in the F<moderators> file. In most cases,
+C<%s@moderators.isc.org> is a good value for this parameter (C<%s> is
+expanded into a form of the newsgroup name). See moderators(5) for more
+details about the syntax. The default is unset. If this parameter isn't
+set and an article is posted to a moderated group that does not have a
+matching entry in the F<moderators> file, the posting will be rejected
+with an error.
+
+=item I<nnrpdauthsender>
+
+Whether to generate a Sender: header based on reader authentication. If
+this parameter is set, a Sender: header will be added to local posts
+containing the identity assigned by F<readers.conf>. If the assigned
+identity does not include an C<@>, the reader's hostname is used. If this
+parameter is set but no identity is be assigned, the Sender: header will
+be removed from all posts even if the poster includes one. This is a
+boolean value and the default is false.
+
+=item I<nnrpdposthost>
+
+If set, nnrpd(8) and rnews(1) will pass all locally posted articles to the
+specified host rather than trying to inject them locally. See also
+I<nnrpdpostport>. This should always be set if I<xrefslave> is true. The
+default value is unset.
+
+=item I<nnrpdpostport>
+
+The port on the remote server to connect to to post when I<nnrpdposthost>
+is used. The default value is C<119>.
+
+=item I<organization>
+
+What to put in the Organization: header if it is left blank by the poster.
+The value of the ORGANIZATION environment variable, if set, overrides this
+setting. The default is unset, which tells INN not to insert an
+Organization: header.
+
+=item I<spoolfirst>
+
+If true, nnrpd(8) will spool new articles rather than attempting to send
+them to innd(8). If false, nnrpd will spool articles only if it receives
+an error trying to send them to innd. Setting this to true can be useful
+if nnrpd must respond as fast as possible to the client; however, when
+set, articles will not appear to readers until they are given to innd.
+nnrpd won't do this; C<rnews -U> must be run periodically to take the
+spooled articles and post them. This is a boolean value and the default
+is false.
+
+=item I<strippostcc>
+
+Whether to strip To:, Cc:, and Bcc: headers out of all local posts via
+nnrpd(8). The primary purpose of this setting is to prevent abuse of the
+news server by posting to a moderated group and including To: or Cc:
+headers in the post so that the news server will send the article to
+arbitrary addresses. INN now protects against this abuse in other ways
+provided I<mta> is set to a command that includes C<%s> and honors it, so
+this is generally no longer needed. This is a boolean value and the
+default is false.
+
+=back
+
+nnrpd(8) has support for controlling high-volume posters via an
+exponential backoff algorithm, as configured by the following parameters.
+
+Exponential posting backoff works as follows: News clients are indexed by
+IP address (or username, see I<backoffauth> below). Each time a post is
+received from an IP address, the time of posting is stored (along with the
+previous sleep time, see below). After a configurable number of posts in
+a configurable period of time, nnrpd(8) will activate posting backoff and
+begin to sleep for increasing periods of time before actually posting
+anything. Posts will still be accepted, but at an increasingly reduced
+rate.
+
+After backoff has been activated, the length of time to sleep is computed
+based on the difference in time between the last posting and the current
+posting. If this difference is less than I<backoffpostfast>, the new
+sleep time will be 1 + (previous sleep time * I<backoffk>). If this
+difference is less than I<backoffpostslow> but greater than
+I<backoffpostfast>, then the new sleep time will equal the previous sleep
+time. If this difference is greater than I<backoffpostslow>, the new
+sleep time is zero and posting backoff is deactivated for this poster.
+
+Exponential posting backoff will not be enabled unless I<backoffdb> is set
+and I<backoffpostfast> and I<backoffpostslow> are set to something other
+than their default values.
+
+Here are the parameters that control exponential posting backoff:
+
+=over 4
+
+=item I<backoffauth>
+
+Whether to index posting backoffs by user rather than by source IP
+address. You must be using authentication in nnrpd(8) for a value of true
+to have any meaning. This is a boolean value and the default is false.
+
+=item I<backoffdb>
+
+The path to a directory, writeable by the news user, that will contain the
+backoff database. There is no default for this parameter; you must
+provide a path to a creatable or writeable directory to enable exponential
+backoff.
+
+=item I<backoffk>
+
+The amount to multiply the previous sleep time by if the user is still
+posting too quickly. A value of C<2> will double the sleep time for each
+excessive post. The default value is C<1>.
+
+=item I<backoffpostfast>
+
+Postings from the same identity that arrive in less than this amount of
+time (in seconds) will trigger increasing sleep time in the backoff
+algorithm. The default value is C<0>.
+
+=item I<backoffpostslow>
+
+Postings from the same identity that arrive in greater than this amount of
+time (in seconds) will reset the backoff algorithm. Another way to look
+at this constant is to realize that posters will be allowed to generate at
+most 86400/I<backoffpostslow> posts per day. The default value is C<1>.
+
+=item I<backofftrigger>
+
+This many postings are allowed before the backoff algorithm is triggered.
+The default value is C<10000>.
+
+=back
+
+=head2 Monitoring
+
+These parameters control the behavior of innwatch(8), the program that
+monitors INN and informs the news administrator if anything goes wrong
+with it.
+
+=over 4
+
+=item I<doinnwatch>
+
+Whether to start innwatch(8) from rc.news. This is a boolean value, and
+the default is true.
+
+=item I<innwatchbatchspace>
+
+Free space in I<pathoutgoing>, in inndf(8) output units (normally
+kilobytes), at which innd(8) will be throttled by innwatch(8), assuming a
+default F<innwatch.ctl>. The default value is C<800>.
+
+=item I<innwatchlibspace>
+
+Free space in I<pathdb>, in inndf(8) output units (normally kilobytes), at
+which innd(8) will be throttled by innwatch(8), assuming a default
+F<innwatch.ctl>. The default value is C<25000>.
+
+=item I<innwatchloload>
+
+Load average times 100 at which innd(8) will be restarted by innwatch(8)
+(undoing a previous pause or throttle), assuming a default
+F<innwatch.ctl>. The default value is C<1000> (that is, a load average of
+10.00).
+
+=item I<innwatchhiload>
+
+Load average times 100 at which innd(8) will be throttled by innwatch(8),
+assuming a default F<innwatch.ctl>. The default value is C<2000> (that
+is, a load average of 20.00).
+
+=item I<innwatchpauseload>
+
+Load average times 100 at which innd(8) will be paused by innwatch(8),
+assuming a default F<innwatch.ctl>. The default value is C<1500> (that
+is, a load average of 15.00).
+
+=item I<innwatchsleeptime>
+
+How long (in seconds) innwatch(8) will sleep between each check of INN.
+The default value is C<600>.
+
+=item I<innwatchspoolnodes>
+
+Free inodes in I<patharticles> at which innd(8) will be throttled by
+innwatch(8), assuming a default F<innwatch.ctl>. The default value is
+C<200>.
+
+=item I<innwatchspoolspace>
+
+Free space in I<patharticles> and I<pathoverview>, in inndf(8) output
+units (normally kilobytes), at which innd(8) will be throttled by
+innwatch(8), assuming a default F<innwatch.ctl>. The default value is
+C<8000>.
+
+=back
+
+=head2 Logging
+
+These parameters control what information INN logs.
+
+=over 4
+
+=item I<docnfsstat>
+
+Whether to start cnfsstat(8) when innd(8) is started. cnfsstat will log
+the status of all CNFS cycbuffs to syslog on a periodic basis (frequency
+is the default for C<cnfsstat -l>, currently 600 seconds). This is a
+boolean value and the default is false.
+
+=item I<logartsize>
+
+Whether the size of accepted articles (in bytes) should be written to the
+article log file. This is useful for flow rate statistics and is
+recommended. This is a boolean value and the default is true.
+
+=item I<logcancelcomm>
+
+Set this to true to log C<ctlinnd cancel> commands to syslog. This is a
+boolean value and the default is false.
+
+=item I<logcycles>
+
+How many old logs scanlogs(8) keeps. scanlogs(8) is generally run by
+news.daily(8) and will archive compressed copies of this many days worth
+of old logs. The default value is C<3>.
+
+=item I<logipaddr>
+
+Whether the verified name of the remote feeding host should be logged to
+the article log for incoming articles rather than the last entry in the
+Path: header. The only reason to ever set this to false is due to some
+interactions with F<newsfeeds> flags; see newsfeeds(5) for more
+information. This is a boolean value and the default is true.
+
+=item I<logsitename>
+
+Whether the names of the sites to which accepted articles will be sent
+should be put into the article log file. This is useful for debugging and
+statistics and can be used by newsrequeue(8). This is a boolean value and
+the default is true.
+
+=item I<nnrpdoverstats>
+
+Whether nnrpd overview statistics should be logged via syslog. This can
+be useful for measuring overview performance. This is a boolean value and
+the default is false.
+
+=item I<nntpactsync>
+
+How many articles to process on an incoming channel before logging the
+activity. The default value is C<200>.
+
+FIXME: This is a rather unintuitive name for this parameter.
+
+=item I<nntplinklog>
+
+Whether to put the storage API token for accepted articles (used by
+nntplink) in the article log. This is a boolean value and the default is
+false.
+
+=item I<stathist>
+
+Where to write history statistics for analysis with
+F<contrib/stathist.pl>; this can be modified with ctlinnd(8) while innd is
+running. Logging does not occur unless a path is given, and there is no
+default value.
+
+=item I<status>
+
+How frequently (in seconds) innd(8) should write out a status report. The
+report is written to I<pathhttp>/inn_status.html. If this is set to C<0> or
+C<false>, status reporting is disabled. The default value is C<0>.
+
+=item I<timer>
+
+How frequently (in seconds) innd(8) should report performance timings to
+syslog. If this is set to C<0>, performance timing is disabled. Enabling
+this is highly recommended, and innreport(8) can produce a nice summary of
+the timings. If set to C<0>, performance timings in nnrpd(8) are also
+disabled, although nnrpd always reports statistics on exit and therefore
+any non-zero value is equivalent for it. The default value is C<0>.
+
+=back
+
+=head2 System Tuning
+
+The following parameters can be modified to tune the low-level operation
+of INN. In general, you shouldn't need to modify any of them except
+possibly I<rlimitnofile> unless the server is having difficulty.
+
+=over 4
+
+=item I<badiocount>
+
+How many read or write failures until a channel is put to sleep or
+closed. The default value is C<5>.
+
+=item I<blockbackoff>
+
+Each time an attempted write returns EAGAIN or EWOULDBLOCK, innd(8) will
+wait for an increasing number of seconds before trying it again. This is
+the multiplier for the sleep time. If you're having trouble with channel
+feeds not keeping up, it may be good to change this value to C<2> or C<3>,
+since then when the channel fills INN will try again in a couple of
+seconds rather than waiting two minutes. The default value is C<120>.
+
+=item I<chaninacttime>
+
+The time (in seconds) to wait between noticing inactive channels. The
+default value is C<600>.
+
+=item I<chanretrytime>
+
+How many seconds to wait before a channel restarts. The default value is
+C<300>.
+
+=item I<datamovethreshold>
+
+The threshold for deciding whether to move already-read data to the top of
+buffer or extend the buffer. The buffer described here is used for reading
+NNTP data. Increasing this value may improve performance, but it should
+not be increased on Systems with insufficient memory. Permitted values
+are between C<0> and C<1048576> (out of range values are treated as
+C<1048576>) and the default value is C<8192>.
+
+=item I<icdsynccount>
+
+How many article writes between updating the active and history files.
+The default value is C<10>.
+
+=item I<keepmmappedthreshold>
+
+When using buffindexed, retrieving overview data (that is, responding to
+XOVER or running expireover) causes mmapping of all overview data blocks
+which include requested overview data for newsgroup. But for high volume
+newsgroups like control.cancel, this may cause too much mmapping at once
+leading to system resource problems. To avoid this, if the amount to be
+mmapped exceeds I<keepmmappedthreshold> (in KB), buffindexed mmap's just
+one overview block (8 KB). This parameter is specific to buffindexed
+overview storage method. The default value is C<1024> (1 MB).
+
+=item I<maxcmdreadsize>
+
+If set to anything other than C<0>, maximum buffer size (in bytes) for
+reading NNTP command will have this value. It should not be large on
+systems which are slow to process and store articles, as that would lead
+to innd(8) spending a long time on each channel and keeping other channels
+waiting. The default value is BUFSIZ defined in stdio.h (C<1024> in most
+environments, see setbuf(3)).
+
+=item I<maxforks>
+
+How many times to attempt a fork(2) before giving up. The default value
+is C<10>.
+
+=item I<nicekids>
+
+If set to anything other than C<0>, all child processes of innd(8) will
+have this nice(2) value. This is usually used to give all child processes
+of innd(8) a lower priority (higher nice value) so that innd(8) can get
+the lion's share of the CPU when it needs it. The default value is C<4>.
+
+=item I<nicenewnews>
+
+If set to anything greater than C<0>, all nnrpd(8) processes that receive
+and process a NEWNEWS command will nice(2) themselves to this value
+(giving other nnrpd processes a higher priority). The default value is
+C<0>. Note that this value will be ignored if set to a lower value than
+I<nicennrpd> (or I<nicekids> if nnrpd(8) is spawned from innd(8)).
+
+=item I<nicennrpd>
+
+If set to anything greater than C<0>, all nnrpd(8) processes will nice(1)
+themselves to this value. This gives other news processes a higher
+priority and can help overchan(8) keep up with incoming news (if that's
+the object, be sure overchan(8) isn't also set to a lower priority via
+I<nicekids>). The default value is C<0>, which will cause nnrpd(8)
+processes spawned from innd(8) to use the value of I<nicekids>, while
+nnrpd(8) run as a daemon will use the system default priority. Note that
+for nnrpd(8) processes spawned from innd(8), this value will be ignored if
+set to a value lower than I<nicekids>.
+
+=item I<pauseretrytime>
+
+Wait for this many seconds before noticing inactive channels.
+Wait for this many seconds before innd processes articles when it's paused
+or the number of channel write failures exceeds I<badiocount>. The
+default value is C<300>.
+
+=item I<peertimeout>
+
+How long (in seconds) an innd(8) incoming channel may be inactive before
+innd closes it. The default value is C<3600> (an hour).
+
+=item I<rlimitnofile>
+
+The maximum number of file descriptors that innd(8) or innfeed(8) can have
+open at once. If innd(8) or innfeed(8) attempts to open more file
+descriptors than this value, it is possible the program may throttle or
+otherwise suffer reduced functionality. The number of open file
+descriptors is roughly the maximum number of incoming feeds and outgoing
+batches for innd(8) and the number of outgoing streams for innfeed(8). If
+this parameter is set to a negative value, the default limit of the
+operating system will be used; this will normally be adequate on systems
+other than Solaris. Nearly all operating systems have some hard maximum
+limit beyond which this value cannot be raised, usually either 128, 256,
+or 1024. The default value of this parameter is C<-1>. Setting it to
+C<256> on Solaris systems is highly recommended.
+
+=back
+
+=head2 Paths and File Names
+
+=over 4
+
+=item I<patharchive>
+
+Where to store archived news. The default value is I<pathspool>/archive.
+
+=item I<patharticles>
+
+The path to where the news articles are stored (for storage methods other
+than CNFS). The default value is I<pathspool>/articles.
+
+=item I<pathbin>
+
+The path to the news binaries. The default value is I<pathnews>/bin.
+
+=item I<pathcontrol>
+
+The path to the files that handle control messages. The code for handling
+each separate type of control message is located here. Be very careful
+what you put in this directory with a name ending in C<.pl>, as it can
+potentially be a severe security risk. The default value is
+I<pathbin>/control.
+
+=item I<pathdb>
+
+The path to the database files used and updated by the server (currently,
+F<active>, F<active.times>, F<history> and its indices, and
+F<newsgroups>). The default value is I<pathnews>/db.
+
+=item I<pathetc>
+
+The path to the news configuration files. The default value is
+I<pathnews>/etc.
+
+=item I<pathfilter>
+
+The path to the Perl, Tcl, and Python filters. The default value is
+I<pathbin>/filter.
+
+=item I<pathhttp>
+
+Where any HTML files (such as periodic status reports) are placed. If the
+news reports should be available in real-time on the web, the files in
+this directory should be served by a web server. The default value is
+the value of I<pathlog>.
+
+=item I<pathincoming>
+
+Location where incoming batched news is stored. The default value is
+I<pathspool>/incoming.
+
+=item I<pathlog>
+
+Where the news log files are written. The default value is
+I<pathnews>/log.
+
+=item I<pathnews>
+
+The home directory of the news user and usually the root of the news
+hierarchy. There is no default; this parameter must be set in F<inn.conf>
+or INN will refuse to start.
+
+=item I<pathoutgoing>
+
+Default location for outgoing feed files. The default value is
+I<pathspool>/outgoing.
+
+=item I<pathoverview>
+
+The path to news overview files. The default value is
+I<pathspool>/overview.
+
+=item I<pathrun>
+
+The path to files required while the server is running and run-time state
+information. This includes lock files and the sockets for communicating
+with innd(8). This directory and the control sockets in it should be
+protected from unprivileged users other than the news user. The default
+value is I<pathnews>/run.
+
+=item I<pathspool>
+
+The root of the news spool hierarchy. This used mostly to set the
+defaults for other parameters, and to determine the path to the backlog
+directory for innfeed(8). The default value is I<pathnews>/spool.
+
+=item I<pathtmp>
+
+Where INN puts temporary files. For security reasons, this is not the
+same as the system temporary files directory (INN creates a lot of
+temporary files with predictable names and does not go to particularly
+great lengths to protect against symlink attacks and the like; this
+is safe provided that normal users can't write into its temporary
+directory). The default value is set at configure time and defaults to
+I<pathnews>/tmp.
+
+=back
+
+=head1 EXAMPLE
+
+Here is a very minimalist example that only sets those parameters that are
+required.
+
+ mta: /usr/lib/sendmail -oi -oem %s
+ ovmethod: tradindexed
+ pathhost: news.example.com
+ pathnews: /usr/local/news
+ hismethod: hisv6
+
+For a more comprehensive example, see the sample F<inn.conf> distributed
+with INN and installed as a starting point; it contains all of the default
+values for reference.
+
+=head1 HISTORY
+
+Written by Rich $alz <rsalz@uunet.uu.net> for InterNetNews and since
+modified, updated, and reorganized by innumerable other people.
+
+$Id: inn.conf.pod 7751 2008-04-06 14:35:40Z iulius $
+
+=head1 SEE ALSO
+
+inews(1), innd(8), innwatch(8), nnrpd(8), rnews(1).
+
+Nearly every program in INN uses this file to one degree or another. The
+above are just the major and most frequently mentioned ones.
--- /dev/null
+=head1 NAME
+
+innconfval - Get configuration parameters from inn.conf
+
+=head1 SYNOPSIS
+
+B<innconfval> [B<-pstv>] [B<-i> I<file>] [I<parameter> ...]
+
+B<innconfval> B<-C> [B<-i> I<file>]
+
+=head1 DESCRIPTION
+
+B<innconfval> normally prints the values of the parameters specified on
+the command line. By default, it just prints the parameter values, but if
+B<-p>, B<-s>, or B<-t> are given, it instead prints the parameter and
+value in the form of a variable assignment in Perl, Bourne shell, or Tcl
+respectively. If no parameters are specifically requested, B<innconfval>
+prints out all parameter values (this isn't particularly useful unless one
+of B<-p>, B<-s>, or B<-t> were specified).
+
+All parameters are taken from F<inn.conf> except for I<version>, which is
+always the version string of INN.
+
+If given the B<-C> option, B<innconfval> instead checks F<inn.conf>,
+reporting any problems found to standard error. B<innconfval> will exit
+with status 0 if no problems are found and with status 1 otherwise.
+
+=head1 OPTIONS
+
+=over 4
+
+=item B<-C>
+
+Check F<inn.conf> rather than printing out the values of parameters.
+
+=item B<-i> I<file>
+
+Use I<file> as the source configuration file rather than F<inn.conf>.
+I<file> must be a valid F<inn.conf> file and will be parsed the same as
+F<inn.conf> would be.
+
+=item B<-p>
+
+Print out parameters as Perl assignment statements. The variable name
+will be the same as the F<inn.conf> parameter, and string values will be
+enclosed in single quotes with appropriate escaping. Boolean values will
+be mapped to C<true> or C<false>, and string parameters that are set to
+NULL will be mapped to empty strings.
+
+=item B<-s>
+
+Print out parameters as Bourne shell assignment statements. The variable
+name will be the F<inn.conf> parameter name in all capitals, and all
+variables will be exported. String values will be enclosed in single
+quotes with appropriate escaping, and boolean values will be mapped to
+C<true> or C<false>. String parameters that are set to NULL will be
+mapped to empty strings.
+
+=item B<-t>
+
+Print out parameters as Tcl assignment statements. The variable name will
+be the same as the F<inn.conf> parameter name but with C<inn_> prepended,
+and string variables will be escaped appropriately. Boolean values will
+be mapped to C<true> or C<false> and string parameters that are set to
+NULL will be mapped to empty strings.
+
+=item B<-v>
+
+Print INN's version. This is equivalent to C<innconfval version>.
+
+=back
+
+=head1 HISTORY
+
+Written by Rich $alz <rsalz@uunet.uu.net> for InterNetNews.
+
+$Id: innconfval.pod 5962 2002-12-08 19:52:13Z rra $
+
+=head1 SEE ALSO
+
+inn.conf(5)
+
+=cut
--- /dev/null
+=head1 NAME
+
+innd - InterNetNews daemon
+
+=head1 SYNOPSIS
+
+B<innd> [B<-aCdfNrsu>] [B<-c> I<days>] [B<-H> I<count>] [B<-i> I<count>]
+[B<-I> I<address>] [B<-l> I<size>] [B<-m> I<mode>] [B<-n> I<flag>]
+[B<-o> I<count>] [B<-p> I<fd>] [B<-P> I<port>] [B<-t> I<timeout>]
+[B<-T> I<count>] [B<-X> I<seconds>]
+
+=head1 DESCRIPTION
+
+B<innd>, the InterNetNews daemon, handles all incoming NNTP feeds,
+coordinates the storage, retransmission, and overview generation for all
+accepted articles, and manages the active(5) and history(5) databases. It
+handles incoming connections on the NNTP port, and also creates and
+listens to a local Unix-domain stream socket in order to receive articles
+from local processes such as nnrpd(8) and rnews(1).
+
+As the master daemon, B<innd> should generally be started at boot and be
+always running. It listens to a Unix-domain datagram socket for commands
+to control its activites, commands that can be sent using ctlinnd(8). The
+current status of B<innd> can be obtained by running C<ctlinnd mode>, or
+for more detailed output, innstat(8).
+
+B<innd> can be in one of three operating modes: running, paused, or
+throttled. Running is the normal mode; when the server is throttled, it
+closes connections and rejects new ones. Paused is like a temporary
+throttle, suspending B<innd>'s activities but not causing the server to
+shut down existing connections. The mode is normally changed via
+ctlinnd(8), either by various automated processes (such as nightly article
+expiration) or manually by the news administrator, but B<innd> will also
+throttle itself if it encounters ENOSPC errors in writing data or an
+excessive number of I/O errors (among other problems).
+
+B<innd> normally takes care of spawning nnrpd(8) to handle connections
+from news reading clients, but it can be run on a separate port from
+nnrpd(8) so that feed connections and news reading connections are handled
+separately (this can often be faster). Normally, B<innd> listens on port
+119, the assigned port for NNTP; if it is desireable to run B<innd> and
+nnrpd(8) on separate ports, it's recommended that nnrpd(8) be given port
+119 (since many news reading clients connect only to that port) and that
+port 433 be used for B<innd>.
+
+The primary configuration files that control B<innd>'s activities are
+F<incoming.conf>, which specifies what remote sites B<innd> will accept
+connections from, F<newsfeeds>, which specifies what is to be done with
+incoming articles besides storing them, and F<inn.conf>, which sets a wide
+variety of configuration parameters. Some parameters in inn.conf(5) can
+also be set with command-line flags; for these, the command-line flags
+take precedence if used.
+
+B<innd> should normally not run directly. It must run as the news user or
+all sorts of file ownership problems may result, and normally the port it
+listens on (119 or 433) is privileged and must be opened by root.
+Instead, B<innd> should normally be started via inndstart(8), a small
+setuid-root program that opens the appropriate port, cleans up the
+environment, changes to the news user, and then runs B<innd>, passing
+along any command-line arguments.
+
+To use IPv6, B<innd> must be started by B<inndstart>.
+
+=head1 OPTIONS
+
+For the options below that override F<inn.conf> settings, see inn.conf(5)
+for the default values if neither the F<inn.conf> setting nor the
+command-line option is given.
+
+=over 4
+
+=item B<-a>
+
+By default, if a host connects to B<innd> but is not listed in
+F<incoming.conf>, the connection is handed off to B<nnrpd> (or rejected if
+I<noreader> is set in F<inn.conf>). If B<-a> is given, F<incoming.conf>
+is ignored and any host can connect and transfer articles. This flag
+should never be used with an accessible server connected to Usenet; it
+would open the server up for all sorts of abuse.
+
+=item B<-c> I<days>
+
+B<innd> normally rejects any article that is older (in days) than the
+value of I<artcutoff> in F<inn.conf>. This option, if given, overrides
+the value of that setting. If I<days> is 0, this check is suppressed and
+B<innd> will accept articles regardless of how old they are.
+
+=item B<-C>
+
+This flag tells B<innd> to accept and propagate but not actually process
+cancel or supersede messages. This is intended for sites concerned about
+abuse of cancels, or that wish to use another cancel mechanism with
+stronger authentication.
+
+=item B<-d>, B<-f>
+
+B<innd> normally puts itself into the background, points its standard
+output and error to log files, and disassociates itself from the
+terminal. Using B<-d> prevents all of this, resulting in log messages
+being written to standard output; this is generally useful only for
+debugging. Using B<-f> prevents the backgrounding and disassociation but
+still redirects output; it may be useful if you want to monitor B<innd>
+with a program that would be confused by forks.
+
+=item B<-H> I<count>, B<-T> I<count>, B<-X> I<seconds>
+
+These flags control the number of connections per minute that are allowed.
+This code is meant to protect your server from newsreader clients that
+make too many connections per minute (and therefore these flags are
+probably only useful when B<innd> is spawning B<nnrpd>). You probably
+should not use these options unless you're having problems. The table
+used for this check is fixed at 128 entries and is used as a ring; the
+size was chosen to make calculating the index easy and to be fairly sure
+that it won't run out of space. In practice, it is unlikely that even
+half the table will be used at any given moment.
+
+The B<-H> flag limits the number of times a host is allowed to connect to
+the server per the time interval given by B<-X>. The default is C<2>.
+
+The B<-T> flag limits the total number of incoming connections per the
+time interval given by B<-X>. The maximum value is C<128>, and the
+default is C<60>.
+
+=item B<-i> I<count>
+
+B<innd> normally allows a maximum number of concurrent NNTP connections
+given by the value of I<maxconnections> in F<inn.conf>. This option, if
+given, overrides the value of that setting. If I<count> is C<0>, this
+check is suppressed.
+
+=item B<-I> I<address>
+
+Normally if B<innd> itself binds to a port, it lets the operating system
+pick the source IP address (unless I<bindaddress> is set in F<inn.conf>).
+If this option is given, it specifies the IP address that INN should bind
+as. This is only relevant for servers with multiple local IP addresses.
+The IP address must be in dotted quad (C<nnn.nnn.nnn.nnn>) format.
+
+This option is rarely useful since B<innd> should not be binding to a
+port itself. Instead, use inndstart(8) and its analgous B<-I> option.
+
+=item B<-l> I<size>
+
+B<innd> normally rejects any article larger than the value of
+I<maxartsize> in F<inn.conf>. This option, if given, overrides the value
+of that setting and specifies a maximum article size of I<size>. If
+I<size> is C<0>, this check is suppressed.
+
+=item B<-m> I<mode>
+
+Normally B<innd> starts in the C<running> mode. If this option is given,
+it specifies what mode B<innd> should start in. I<mode> should begin with
+one of C<g>, C<p>, or C<t>, and the starting mode will be set to
+C<running>, C<paused>, or C<throttled>, respectively, based on that
+initial letter. (C<g> is short for C<go>.)
+
+=item B<-N>
+
+If this option is given, any filters (Perl, Tcl, or Python) are disabled
+before B<innd> starts (normally, filters default to being enabled). The
+filters can be enabled after B<innd> has started with ctlinnd(8).
+
+=item B<-n> I<flag>
+
+Whether B<innd> allows (and hands off to B<nnrpd>) reader connections
+while paused or throttled is normally determined by the value of
+I<readerswhenstopped> in F<inn.conf>). This option, if given, overrides
+that value. If I<flag> is C<n>, B<innd> will not allow readers if it is
+paused or throttled. If I<flag> is C<y>, readers will be allowed
+regardless of B<innd>'s operating mode.
+
+=item B<-o> I<count>
+
+This flag limits the number of file descriptors that are available for
+outgoing file feeds. The default is the number of available file
+descriptors minus some reserved for internal use (which could potentially
+starve B<innd> of descriptors to use for accepting new connections). If
+B<innd> has more file feeds than I<count>, some of them will be buffered
+and only written out periodically.
+
+Normally you never need to use this option, since the number of outgoing
+feeds is fixed, being the number of file feeds configured in F<newsfeeds>,
+and is generally small (particularly given that innfeed(8) is now used for
+most outgoing feeds at large sites).
+
+=item B<-p> I<fd>
+
+If this flag is given, B<innd> expects the file descriptor given by I<fd>
+to already be open and bound to the appropriate local port and to be
+suitable for listening to for incoming connections. This is how
+B<inndstart> tells B<innd> which open file descriptor is the network
+connection. If this flag is not given, B<innd> will attempt to open its
+network socket itself. B<inndstart> always passes this flag to B<innd>.
+
+=item B<-P> I<port>
+
+The port B<innd> should listen on is normally given by the value of
+I<port> in F<inn.conf>. This option, if given, overrides that value and
+specifies the port that B<innd> should bind to. This option is rarely
+useful since B<innd> normally does not bind itself; instead the analgous
+B<-P> option to inndstart(8) should be used. Since B<innd> should never
+be run as root, I<port> has to be a non-privileged port (one larger than
+1024).
+
+=item B<-r>
+
+Instructs B<innd> to renumber the F<active> file after starting, just as
+if a C<ctlinnd renumber> command were sent.
+
+=item B<-s>
+
+Just check the syntax of the F<newsfeeds> file and exit. B<innd> will
+exit with a non-zero status if any errors are found; the actual errors
+will be reported via syslog(3).
+
+=item B<-t> I<seconds>
+
+Normally, B<innd> will flush any changes to history and the active file
+after 300 seconds of inactivity. This option changes that timeout to
+I<seconds>.
+
+=item B<-u>
+
+The news log (the trace information for every article accepted by B<innd>)
+is normally buffered. This option changes the log to be unbuffered.
+
+=back
+
+=head1 CONTROL MESSAGES
+
+Arriving articles that have a Control: header are called "control
+messages". Except for cancel messages, these messages are handled by
+controlchan(8) via a feed set up in F<newsfeeds>.
+
+(Cancel messages update the history database, so they must be handled
+internally; the cost of syncing, locking, then unlocking would be too high
+given the number of cancel messages that are received. Note that if an
+article is cancelled before it is received by the news server, it will
+be rejected when it arrives since the history database has been updated;
+it is useful for rejecting spam before it arrives.)
+
+The distribution of control messages is different than that of standard
+articles. Control messages are normally filed into the pseudo-newsgroup
+named C<control> regardless of which newsgroup they were actually posted
+to. If, however, a C<control.>I<command> newsgroup exists that matches
+the control command, the control message will be filed into that group
+instead. For example, a newgroup control message will be filed in
+C<control.newgroup> if that group exists; otherwise, it will be filed in
+C<control>.
+
+If you want to specifically feed all control messages to a given site
+regardless of whether the control messages would affect the newsgroups
+you're feeding that site, you can put the appropriate control newsgroup in
+the subscription list. For example, to feed all cancel messages to a
+given remote site (normally a bad idea), add C<control.cancel> to its
+subscription list. Normally it's best to exclude the control newsgroups
+from feeds to keep from sending your peers more control messages than they
+care about. That's why the F<newsfeeds> pattern C<!control,!control.*>
+is as often as not specified (adding this pattern do not prevent control
+messages which affect the newsgroups fed to a site from being sent to it).
+
+checkgroups, newgroup and rmgroup control messages receive additional special
+treatment. If one of these control messages is approved and posted to the
+newsgroup being created or removed (or to the admin group to which the
+checkgroups is posted), the message will be sent to all sites
+whose subscription patterns would cause them to receive articles posted to
+that group. For example, if a newgroup control message for a nonexistent
+newsgroup C<news.admin.meow> is received, it will be sent to any site
+whose subscription pattern would cause it to receive C<news.admin.meow> if
+that newsgroup existed (such as a pattern of C<news.admin.*>). For this
+reason, it is correct to post newgroup messages to the newsgroup that the
+control message would create. It is I<not> generally correct to crosspost
+newgroup messages to some "well-propagated" newsgroup; not only will this
+not actually improve their propagation to sites that want such control
+messages, but it will also cause sites that do not want those control
+messages to receive them. Therefore, assuming that a newgroup control
+message is sent to the group C<news.admin.meow> (specified in the
+Newsgroups: header) in order to create the group C<news.admin.meow>,
+the sites with the following subscription patterns will receive it:
+
+ *,@news.*
+ news.*
+ news.*,!control,!control.*
+ control,control.*
+
+but the sites with the following subscription patterns will not receive it:
+
+ *,@news.*,!control,!control.*
+ comp.*,@news.*
+
+If a control message is posted to a group whose name ends with the four
+characters C<.ctl>, this suffix is stripped off and the control message is
+propagated as if it were posted to the base group. For example, a cancel
+message posted to C<news.admin.ctl> will be sent to all sites that
+subscribe to C<control.cancel> (or C<control> if that newsgroup doesn't
+exist) or C<news.admin>. This behavior is present for historical
+compatibility reasons and should be considered obsolete; support for the
+C<.ctl> suffix may be removed in a future version of INN.
+
+Finally, articles posted to newsgroups beginning with C<to.> are treated
+specially. Provided that either that newsgroup exists in the F<active> file
+or I<mergetogroups> is set in F<inn.conf>, the remainder of the newsgroup
+is taken to be a site name, as configured in F<newsfeeds>, and the article
+is sent to that site. If I<mergetogroups> is set, the article will be
+filed in the group named C<to> (which must exist in the F<active> file). For
+example, with I<mergetogroups> set, an article posted to C<to.uunet> will
+be filed in C<to> and sent to the site C<uunet>.
+
+=head1 PROTOCOL DIFFERENCES
+
+B<innd> implements the NNTP commands defined in RFC 977, with the
+following differences:
+
+=over 4
+
+=item 1.
+
+The LIST command may be followed by an optional ACTIVE, ACTIVE.TIMES, or
+NEWSGROUPS. There is only basic support for LIST in B<innd> since feeding
+peers normally don't need it; see nnrpd(8) for full support.
+
+=item 2.
+
+The AUTHINFO USER and AUTHINFO PASS commands are implemented, although the
+authentication is currently limited to matching a password for a given
+peer specified in F<incoming.conf>. These are based on the reference Unix
+implementation.
+
+=item 3.
+
+A new command, MODE READER, is implemented. This command will cause the
+server to pass the connection to B<nnrpd>.
+
+=item 4.
+
+The streaming extension (MODE STREAM, CHECK, and TAKETHIS) is fully
+supported.
+
+=item 5.
+
+A batch transfer command, XBATCH I<byte-count>, is provided. This command
+will read I<byte-count> bytes and store them for later processing by
+rnews(1) (which must be run separately, probably from cron). See
+innxbatch(8) and F<backends/sendxbatches> for more details on this
+extension.
+
+=item 6.
+
+B<innd> implements a limited subset of the protocol useful for
+transferring news. The only other commands implemented are HEAD, HELP,
+IHAVE, STAT, and QUIT. The remaining commands are mostly only useful for
+readers and are implemented by nnrpd(8).
+
+=back
+
+=head1 HEADER MODIFICATIONS
+
+B<innd> modifies as few article headers as possible, although it could be
+better in this area.
+
+Empty headers and headers that consist of nothing but whitespace are
+dropped.
+
+The local site's name (as set with the I<pathhost> parameter in
+F<inn.conf>) and an exclamation point are prepended to the Path: header,
+provided the first site name in the Path: header is different from the
+local one. In addition, I<pathalias> and I<pathcluster> may be similarly
+respectively prepended and appended to the Path: header; see inn.conf(5)
+for the details.
+
+The Xref: header is removed and a new one created.
+
+A Lines: header will be added if the article was missing one.
+
+B<innd> does not rewrite incorrect headers. For example, it will not
+replace an incorrect Lines header, though it may reject such an article
+depending on the value of I<linecountfuzz> in F<inn.conf>.
+
+=head1 CANCEL FEEDS
+
+In order to efficiently apply a large number of local cancels (such as
+from processing NoCeMs or from some other external source), INN supports a
+special feed mode available only to connections to the local Unix domain
+socket (not to connections to any network sockets).
+
+To enter this mode, connect to the Unix domain socket (I<pathrun>/nntpin)
+and send the command MODE CANCEL. The response will have code C<284>.
+Every subsequent line sent on that connection should consist of a single
+message ID. An attempt will be made to cancel that message ID, and the
+server will reply C<289> for success or C<484> for failure. (Failure can
+occur, for example, if the server is paused or throttled, or the
+Message-ID is corrupt. Failure does I<not> occur if the article to be
+cancelled does not exist.)
+
+=head1 LOGGING
+
+B<innd> reports all incoming articles in its log file (I<pathlog>/news).
+This is a text file with a variable number of space-separated fields in
+one of the following formats:
+
+ mon dd hh:mm:ss.mmm + feed <message-id> site ...
+ mon dd hh:mm:ss.mmm j feed <message-id> site ...
+ mon dd hh:mm:ss.mmm c feed <message-id> Cancelling <message-id>
+ mon dd hh:mm:ss.mmm - feed <message-id> reason
+ mon dd hh:mm:ss.mmm ? feed <message-id> reason
+
+There may also be hostname and/or size fields after the message ID
+depending on the settings of I<nntplinklog> and I<logartsize> in
+F<inn.conf>.
+
+The first three fields are the date and time to millisecond resolution.
+The fifth field is the site that sent the article (based on the Path
+header) and the sixth field is the article's message ID; they will be a
+question mark if the information is not available.
+
+The fourth field indicates whether the article was accepted or not. If it
+is a plus sign, then the article was accepted. If it is the letter C<j>
+then the article was accepted, but all of the newsgroups to which the
+article was posted were set to mode C<j> in the active file (or not listed
+in the active file and I<wanttrash> was set in F<inn.conf>) so the article
+was filed into the C<junk> newsgroup. In both of these cases, the article
+has been accepted and the C<site ...> field contains the space-separated
+list of sites to which the article is being sent.
+
+If the fourth field is the letter C<c>, then a cancel message was accepted
+before the original article arrived, and a history entry for the cancelled
+message was created so that B<innd> will reject that message if it arrives
+later.
+
+If the fourth field is a minus sign, then the article was rejected. The
+reasons for rejection generated by B<innd> include:
+
+ "%s" header too long
+ "%s" wants to cancel <%s> by "%s"
+ Article exceeds local limit of %s bytes
+ Article posted in the future -- "%s"
+ Bad "%s" header
+ Can't write history
+ Duplicate
+ Duplicate "%s" header
+ EOF in headers
+ Linecount %s != %s +- %s
+ Missing %s header
+ No body
+ No colon-space in "%s" header
+ No space
+ Space before colon in "%s" header
+ Too old -- "%s"
+ Unapproved for "%s"
+ Unwanted newsgroup "%s"
+ Unwanted distribution "%s"
+ Whitespace in "Newsgroups" header -- "%s"
+
+where C<%s>, above, is replaced by more specific information. (The Perl,
+Python, andr Tcl filters, if used, may reject articles with other
+reasons.)
+
+If the fourth field is the letter C<?>, the article contains strange
+strings, such as CR without LF or LF without CR. (These characters should
+never occur in isolation, only together as CRLF to indicate the end of a
+line.) This log message is just informational, to give an idea of how
+widespread such articles are; B<innd> does not reject such articles.
+
+Note that when I<wanttrash> is set to true in F<inn.conf> and an article
+is received that isn't posted to any valid newsgroups, it will be accepted
+and logged with two lines, a C<j> line and a minus sign line.
+
+B<innd> also makes extensive reports through syslog(3). The first word of
+the log message will be the name of the site if the entry is site-specific
+(such as a "connected" message). The first word will be C<SERVER> if the
+message relates to the server itself, such as when a read error occurs.
+
+If the second word is the four letters C<cant>, then an error is being
+reported. (The absence of an apostrophe is intentional; it makes it
+easier to grep from the command line and easier to find error messages in
+FAQs using a search engine.) In this case, the next two words generally
+name the system call or library routine that failed and the object upon
+which the action was being performed. The rest of the line may contain
+other information.
+
+In other cases, the second word attempts to summarize what change has been
+made, while the rest of the line gives more specific information. The
+word C<internal> generally indicates an internal logic error.
+
+=head1 SIGNALS
+
+B<innd> will catch SIGTERM and SIGHUP and shut down. If B<-d> is used,
+SIGINT will also be caught and will result in an orderly shutdown.
+
+B<innd> will catch the SIGUSR1 signal and recreate the control channel
+used by ctlinnd(8).
+
+=head1 BUGS
+
+B<innd> normally attempts to strip IP options from incoming connections,
+since it uses IP-based authentication and source routing can confuse that.
+However, this doesn't work on all systems, and it doesn't work at all in
+the presence of IPv6 support (and is disabled in that case). Hence, if
+using B<innd> with IPv6 support, make sure that your kernel or router
+disables source routing.
+
+=head1 HISTORY
+
+Written by Rich $alz <rsalz@uunet.uu.net> for InterNetNews.
+
+$Id: innd.pod 7748 2008-04-06 13:49:56Z iulius $
+
+=head1 SEE ALSO
+
+active(5), ctlinnd(8), dbz(3), history(5), incoming.conf(5), inn.conf(5),
+newsfeeds(5), nnrpd(8), rnews(1), syslog(3).
+
+=cut
--- /dev/null
+=head1 NAME
+
+inndf - Report free disk, inodes, and overview information
+
+=head1 SYNOPSIS
+
+B<inndf> [B<-Fhi>] [B<-f> I<filename>] I<directory> [I<directory> ...]
+
+B<inndf> B<-n>
+
+B<inndf> B<-o>
+
+=head1 DESCRIPTION
+
+B<inndf> was originally a replacement for C<df | awk> in innwatch.ctl(5)
+and innstat(8), and now also reports various other usage information about
+INN's storage that df(1) doesn't understand. B<inndf> doesn't sync, forks
+less, and is generally less complicated than df(1).
+
+Its default behavior is to report free kilobytes (not disk blocks), or
+free inodes if B<-i> is used, in the file systems holding the directories
+given on the command line. (A kilobyte in this case is 1024 bytes.) If
+only one directory is given, the output will be a simple number; if more
+than one directory is given, the output will be formatted for human
+readability.
+
+If I<enableoverview> is set to true in F<inn.conf>, B<inndf> can also be
+used to get information about the overview database. With the B<-n>
+option, it reports a count of the total number of overview records stored.
+With B<-o>, it reports the percentage of space used in the overview
+database (for those overview methods where this is meaningful data).
+
+=head1 OPTIONS
+
+=over 4
+
+=item B<-f> I<filename>
+
+I<filename> should contain a list of directories to use in addition to
+those given by the arguments, one per line. Blank lines and anything
+after C<#> on any line are ignored.
+
+=item B<-F>
+
+Like B<-f> execpt that the filename is I<pathetc>/filesystems and it is
+not an error if this file doesn't exist. (This option is used primarily
+by such things as innstat(8), so that the news administrator can add
+additional file systems to check to I<pathetc>/filesystems without having
+to modify the script.)
+
+=item B<-h>
+
+Print a usage message and exit.
+
+=item B<-i>
+
+Report the number of free inodes rather than the amount of free disk
+space.
+
+=item B<-n>
+
+Report the total number of records in the overview database. Note that
+crossposted articles will have one overview record for each newsgroup
+they're posted to.
+
+=item B<-o>
+
+Report the percentage usage of the overview database space. This is only
+meaningful for overview methods that pre-allocate a certain amount of
+space rather than grow to accomodate more records. Currently, this flag
+is only useful for the buffindexed overview method.
+
+=back
+
+=head1 EXAMPLES
+
+Print the free kilobytes in /news/spool as a simple number:
+
+ inndf /news/spool
+
+Report the free inodes in /usr/local/news and /news/spool in a format
+designed for human readability:
+
+ inndf -i /usr/local/news /news/spool
+
+The same, but also add in all file systems in I<pathetc>/filesystems:
+
+ inndf -i -F /usr/local/news /news/spool
+
+Print out the number of overview records and the percentage space used by
+a buffindexed overview database:
+
+ inndf -no
+
+=head1 HISTORY
+
+B<inndf> was written by Ian Dickinson <idickins@fore.com>. This manual
+page was written by Swa Frantzen <Swa.Frantzen@belgium.eu.net>. Thanks
+also to the following folks for ports, patches, and comments:
+
+ Mahesh Ramachandran <rr@eel.ufl.edu>
+ Chuck Swiger <chuck@its.com>
+ Sang-yong Suh <sysuh@kigam.re.kr>
+ Brad Dickey <bdickey@haverford.edu>
+ Taso N. Devetzis <devetzis@snet.net>
+ Wei-Yeh Lee <weiyeh@columbia.edu>
+ Jeff Garzik <jeff.garzik@spinne.com>
+
+and to all the other folks I met and worked with during my 10 years as a
+newsadmin.
+
+Katsuhiro Kondou added the B<-n> and B<-o> options. Russ Allbery added
+reporting of percentage free disk space. Support for B<-f> and B<-F> was
+added by Fabien Tassin <fta@sofaraway.org>.
+
+=head1 SEE ALSO
+
+df(1), innwatch.ctl(5), innstat(8).
+
+=cut
--- /dev/null
+=head1 NAME
+
+inndstart - Start innd
+
+=head1 SYNOPSIS
+
+B<inndstart> [B<-P> I<port>] [B<-I> I<address>] [I<innd-options>]
+
+=head1 DESCRIPTION
+
+The purpose of B<inndstart> is to raise system file descriptor limits,
+open the privileged news transfer port, and then start innd(8), passing it
+the open file descriptor for the news port. B<inndstart> is used since
+only privileged programs can perform those two operations and since
+B<innd> should not run with elevated privileges. It is installed setuid
+root and drops privileges to the news user (as set at configure time)
+before running B<innd>.
+
+Normally there is no need to run B<inndstart> directly. Instead, run
+rc.news(8) as the news user, and it will handle running B<inndstart>
+appropriately for you.
+
+Since B<inndstart> is setuid root, it is extremely restrictive about who
+can run it and what it is willing to do. See L<"SECURITY"> for the full
+details.
+
+B<inndstart> can only be run by the news user; if run by any other user,
+it will abort. It will also only bind to ports 119, 433, or a port number
+given at configure time with B<--with-innd-port> among those ports below
+1024, although it can bind to any port above 1024. This is to prevent
+various security exploits possible by binding to arbitrary privileged
+ports.
+
+Before running B<innd>, B<inndstart> cleans out the environment and sets
+only those environment variables listed in L<"ENVIRONMENT">.
+
+=head1 OPTIONS
+
+=over 4
+
+=item B<-P> I<port>
+
+Bind to I<port> instead of whatever is specified by I<port> in
+F<inn.conf>. Note that this is subject to the constraints mentioned
+above.
+
+=item B<-I> I<address>
+
+Bind as I<address> instead of whatever is specified by I<bindaddress> in
+F<inn.conf>. The default behavior is to bind to INADDR_ANY, and that's
+what's desired almost all the time. This option, and the F<inn.conf>
+parameter, may be useful if the machine has multiple interface cards and
+B<innd> should only be listening on a particular one.
+
+=back
+
+All other options given on the command line are passed verbatim to
+B<innd>. In addition, B<inndstart> will give the B<-p> option to B<innd>,
+specifying the file descriptor of the open network socket.
+
+=head1 SECURITY
+
+B<inndstart> is setuid root, and therefore an expected point of attack.
+It has therefore been carefully written with security in mind. In a
+normal INN installation, it is installed setuid root and executable only
+by users in the news group.
+
+Ideally, everything about B<inndstart>'s operations would be hard-coded so
+that it could not be modified. Fighting against this desire, however, is
+the ideal that as much of INN's operation as possible should be
+configurable at run-time using F<inn.conf>, and the news system should be
+able to an alternate inn.conf by setting INNCONF to the path to that file
+before starting any programs. The configuration data therefore can't be
+trusted.
+
+The security model used is:
+
+=over 2
+
+=item *
+
+B<inndstart> can only be executed by the news user and news group, as
+determined at configure time and compiled into B<inndstart> as constants.
+Similarly, B<inndstart> will always setuid() and setgid() to those users
+before running B<innd>. This is to prevent a user other than news but in
+the news group from using B<inndstart> to leverage that access into access
+to the news account.
+
+=item *
+
+As mentioned above, B<inndstart> will only bind to a very limited subset
+of ports below 1024. There are various attacks that can be performed
+using random low-numbered ports, including exploits of the rsh(1) family
+of commands on some systems.
+
+=item *
+
+B<inndstart> does as little as possible as root, dropping privileges
+before performing any operations that do not require elevated privileges.
+
+=back
+
+This program therefore gives the news user the ability to revoke system
+file descriptor limits and bind to the news port, and nothing else.
+
+=head1 DIAGNOSTICS
+
+B<inndstart> may log the following messages to syslog and print them to
+stderr.
+
+=over 4
+
+=item can't bind: %s
+
+(Fatal) Unable to bind to the designated port. This usually means that
+something else is already running on the news port. Check with
+netstat(8) and make sure that inetd(8) doesn't think it's running a
+service on the same port you're trying to run B<innd> on.
+
+=item can't bind to restricted port %d
+
+(Fatal) B<inndstart> was told to bind to a low numbered port (under 1024)
+other than 119, 433, or a port number given at configure time. This is
+not allowed for security reasons. If you're running B<innd> on a port
+other than 119 or 433, you need to give the --with-innd-port flag to
+C<configure> when you compile INN.
+
+=item can't exec %s: %s
+
+(Fatal) B<inndstart> was unable to execute B<innd>. Make sure that
+I<pathbin> is set correctly in inn.conf and that B<innd> is located in
+that directory and is executable by the news user.
+
+=item can't getgrnam(%s)
+
+(Fatal) Unable to determine the GID for the compiled-in news group.
+Perhaps the news group is not listed in F</etc/group>.
+
+=item can't getpwnam(%s)
+
+(Fatal) Unable to determine the UID for the compiled-in news user.
+Perhaps the news user is not listed in F</etc/passwd>.
+
+=item can't open socket: %s
+
+(Fatal) Something went wrong in creating the network socket. Chances are
+your system is out of resources of some kind.
+
+=item can't set file descriptor limit to %d: %s
+
+(Warning) Unable to set the system file descriptor limit to the specified
+value; the limit was left unchanged. Perhaps that value is too high for
+your system. Try changing I<rlimitnofile> in F<inn.conf> to a smaller
+value.
+
+=item can't set SO_REUSEADDR: %s
+
+(Warning) B<inndstart> attempts to set SO_REUSEADDR using setsockopt(2) so
+that if B<innd> exits, it can be restarted again immediately without
+waiting for the port to time out. For some reason, this failed, and that
+option was not set on the port.
+
+=item can't seteuid to %d: %s
+
+(Fatal) Unable to change the effective UID. If B<inndstart> has the
+correct permissions (setuid root) and seteuid to root (UID 0) is failing,
+this may mean that your system has seteuid(2) but doesn't have support for
+POSIX saved UIDs. If this is the case, please report this to the INN
+maintainers.
+
+=item can't setgid to %d: %s
+
+(Fatal) Dropping privileges to the news group failed for some reason.
+
+=item can't setgroups (is inndstart setuid root?): %s
+
+(Warning) Dropping all supplemental groups except the news group failed
+for some reason, and the process group membership was left unchanged.
+This almost always indicates that B<inndstart> isn't setuid root as it has
+to be to do what it does. Make sure that B<inndstart> is setuid root,
+owned by group news, and mode 4710.
+
+=item can't setuid to %d: %s
+
+(Fatal) Dropping privileges to the news user failed for some reason.
+
+=item invalid address %s
+
+(Fatal) B<-I> was specified on the command line, but the argument wasn't a
+valid address. Addresses must be given as numeric IP addresses.
+
+=item invalid bindaddress in inn.conf (%s)
+
+(Fatal) The I<bindaddress> specified in F<inn.conf> could not be converted
+to an IP address. See inn.conf(5) for more information about valid
+values.
+
+=item invalid port %s (must be a number)
+
+(Fatal) B<-P> was specified on the command line, but the argument wasn't a
+valid port. Ports must be port numbers; service names are not allowed.
+
+=item missing address after -I
+
+(Fatal) B<-I> was given on the command line, but no address was given
+after the option.
+
+=item missing port after -P
+
+(Fatal) B<-P> was given on the command line, but no port was given after
+the option.
+
+=item must be run by user %s (%d), not %d
+
+(Fatal) Someone other than the news user attempted to run B<inndstart>.
+B<inndstart> may only be run by the news user for security reasons.
+
+=back
+
+=head1 EXAMPLES
+
+Normally, B<inndstart> is never run directly. However, a simple way to
+just restart B<innd> (if it is not running) without running any other
+auxilliary programs or performing any of the other checks done by
+rc.news(8) is to just run:
+
+ inndstart
+
+as the news user.
+
+To start B<innd> on port 433, passing it the C<-c21> option, use:
+
+ inndstart -P433 -c21
+
+=head1 ENVIRONMENT
+
+One environment variable affects the operation of B<inndstart> itself:
+
+=over 8
+
+=item INNCONF
+
+The full path to the inn.conf(5) file to read, rather than the default.
+This can be used to run multiple copies of INN on the same machine with
+different settings.
+
+=back
+
+When executing B<innd>, B<inndstart> cleans out the entire environmnent
+and sets only the following variables:
+
+=over 8
+
+=item BIND_INADDR
+
+Passed verbatim from B<inndstart>'s environment. This is used by various
+programs to override the I<bindaddress> parameter in F<inn.conf> and
+therefore must be in B<innd>'s environment for programs like innfeed(8).
+
+=item HOME
+
+Set to I<pathnews> from F<inn.conf>.
+
+=item LOGNAME
+
+Set to the news master, as determined at configure time.
+
+=item PATH
+
+Set to I<pathbin> from F<inn.conf>, I<pathetc> from F<inn.conf>, and then
+F</bin>, F</usr/bin>, and F</usr/ucb> in that order.
+
+=item SHELL
+
+Set to the path to the system Bourne shell as determined by configure
+(probably F</bin/sh>).
+
+=item TMPDIR
+
+Set to I<pathtmp> from inn.conf.
+
+=item TZ
+
+Passed verbatim from B<inndstart>'s environment.
+
+=item USER
+
+Set to the news master, as determined at configure time.
+
+=back
+
+=head1 FILES
+
+=over 4
+
+=item inn.conf
+
+Read for I<pathnews>, I<pathbin>, I<pathtmp>, I<rlimitnofile>,
+I<bindaddress>, and I<port>.
+
+=item I<pathbin>/innd
+
+The binary that is executed as B<innd> and passed the open network socket.
+
+=back
+
+=head1 HISTORY
+
+Written by Russ Allbery E<lt>rra@stanford.eduE<gt> for InterNetNews.
+
+$Id: inndstart.pod 5909 2002-12-03 05:17:18Z vinocur $
+
+=head1 SEE ALSO
+
+inn.conf(5), innd(8)
+
+=cut
--- /dev/null
+=head1 NAME
+
+innmail - Simple mail-sending program
+
+=head1 SYNOPSIS
+
+B<innmail> [B<-h>] [B<-s> I<subject>] I<address> [I<address> ...]
+
+=head1 DESCRIPTION
+
+B<innmail> is a Perl script intended to provide the non-interactive
+mail-sending functionality of mail(1) while avoiding nasty security
+problems. It takes the body of a mail message on standard input and sends
+it to the specified addresses by invoking the value of I<mta> in
+F<inn.conf>.
+
+At least one address (formatted for the MTA specified in F<inn.conf>, if it
+matters) is required. B<innmail> will sanitize the addresses so that they
+contain only alphanumerics and the symbols C<@>, C<.>, C<->, C<+>, C<_>,
+and C<%>.
+
+B<innmail> was written to be suitable for the I<mailcmd> setting in
+F<inn.conf>.
+
+=head1 OPTIONS
+
+=over 4
+
+=item B<-h>
+
+Gives usage information.
+
+=item B<-s> I<subject>
+
+Sets the Subject: header of the message. A warning is issued if this
+option is omitted.
+
+=back
+
+=head1 EXAMPLES
+
+This sends a one-line message to the local user C<joe>:
+
+ echo "A one-line message." | innmail -s "Simple message" joe
+
+B<innmail> by default is used by INN for sending nightly reports and
+control message reports.
+
+=head1 BUGS
+
+B<innmail> fails on addresses that begin with C<->, although one might
+hope that the news server will not need to contact any such addresses.
+
+There are many "correct" addresses that will be silently modified by the
+sanitization process. A news administrator should be careful to use
+particularly sane addresses if they may be passed to B<innmail>.
+
+=head1 HISTORY
+
+B<innmail> was written by James Brister <brister@vix.com> for
+InterNetNews. This manual page was originally written by Jeffrey
+M. Vinocur.
+
+=head1 SEE ALSO
+
+inn.conf(5), mail(1).
+
+=cut
--- /dev/null
+=head1 NAME
+
+innupgrade - Upgrade INN configuration files
+
+=head1 SYNOPSIS
+
+B<innupgrade> I<directory>
+
+B<innupgrade> [B<-t> I<type>] B<-f> I<file>
+
+=head1 DESCRIPTION
+
+B<innupgrade> is intended to be run during a major upgrade of INN to fix
+the configuration files with any required changes. If given a directory,
+it will scan that directory for any files that it has updates defined for,
+try to perform those updates, and replace the files with updated versions
+if applying the updates resulted in any changes. The old versions of the
+files will be saved with a C<.OLD> extension.
+
+If the B<-f> flag is used, only that file will be updated. If the file
+name doesn't match the standard file name of an INN configuration file,
+the optional B<-t> flag may be given to specify the type. See
+L<"EXAMPLES"> for an example of this.
+
+Currently, B<innupgrade> knows how to apply the following updates:
+
+=over 2
+
+=item *
+
+F<inn.conf>: Quote values with whitespace and comment out keys with no
+values, required for the change in configuration parsers introduced in INN
+2.4. The new format is not backward compatible with the previous parser,
+since the previous parser will include the double-quotes in the value of
+the parameter.
+
+=back
+
+Normally, B<innupgrade> should be run on the I<pathetc> directory after
+any upgrade of INN other than a patch release (any upgrade that changes
+the first or second version numbers). This may occur automatically during
+the upgrade process.
+
+=head1 OPTIONS
+
+=over 4
+
+=item B<-f> I<file>
+
+Only act on I<file> rather than working on an entire directory.
+
+=item B<-t> I<type>
+
+For a file specified with B<-f>, parse it and upgrade it as if it were
+named I<type>. Used for upgrading files with the same syntax as normal
+INN configuration files but with different names. Only makes sense in
+combination with B<-f>.
+
+=back
+
+=head1 EXAMPLES
+
+Upgrade any configuration files found in F</usr/local/news/etc>:
+
+ innupgrade /usr/local/news/etc
+
+Upgrade only F</news/etc/inn.conf>:
+
+ innupgrade -f /news/etc/inn.conf
+
+Upgrade a file named F<inn-special.conf> that should have the same syntax
+as F<inn.conf>:
+
+ innupgrade -t inn.conf -f inn-special.conf
+
+Any upgrade rules that apply to F<inn.conf> will be applied to the
+alternate file.
+
+=head1 HISTORY
+
+Written by Russ Allbery <rra@stanford.edu> for InterNetNews.
+
+$Id: innupgrade.pod 5909 2002-12-03 05:17:18Z vinocur $
+
+=cut
--- /dev/null
+=head1 Welcome to INN 2.4!
+
+Please read this document thoroughly before trying to install INN. You'll
+be glad you did.
+
+If you are upgrading from a major release of INN prior to 2.3, it is
+recommended that you make copies of your old configuration files and use
+them as guides for doing a clean installation and configuration of 2.4.
+Many config files have changed, some have been added, and some have been
+removed. You'll find it much easier to start with a fresh install than to
+try to update your old installation. This is particularly true if you're
+upgrading from a version of INN prior to 2.0.
+
+If you are upgrading from INN 2.3 or later, you may be able to just update
+the binaries, scripts, and man pages by running:
+
+ make update
+
+after building INN and then comparing the new sample configuration files
+with your current ones to see if anything has changed. If you take this
+route, the old binaries, scripts, and man pages will be saved with an
+extension of C<.OLD> so that you can easily back out. Be sure to
+configure INN with the same options that you used previously if you take
+this approach (in particular, INN compiled with B<--enable-largefiles>
+can't read the data structures written by INN compiled without that flag,
+and vice versa). If you don't remember what options you used but you have
+your old build tree, look at the comments at the beginning of
+F<config.status>.
+
+If you made ckpasswd setuid root so that you could use system passwords,
+you'll have to do that again after make update. (It's much better to use
+PAM instead if you can.)
+
+If you use C<make update> to upgrade from INN 2.3, also look at the new
+sample configuration files in F<samples> to see if there are new options
+of interest to you. In particular, F<control.ctl> has been updated and
+F<inn.conf> has various new options.
+
+For more information about recent changes, see F<NEWS>.
+
+=head1 Supported Systems
+
+As much as possible, INN is written in portable C and should work on any
+Unix platform. It does, however, make extensive use of mmap(2) and
+certain other constructs that may be poorly or incompletely implemented,
+particularly on very old operating systems.
+
+INN has been confirmed to work on the following operating systems:
+
+ AIX 4.3
+ FreeBSD 2.2.x and up
+ HP-UX 10.20 and up
+ Linux 2.x (tested with libc 5.4, glibc 2.0 and up)
+ Mac OS X 10.2 and up
+ NetBSD 1.6 and up
+ OpenBSD 2.8 and up
+ SCO 5.0.4 (tested with gcc 2.8.1, cc)
+ Solaris 2.5.x and up
+ UnixWare 7.1
+ UX/4800 R11 and up
+
+If you have gotten INN working on an operating system other than the ones
+listed above, please let us know at <inn-bugs@isc.org>.
+
+=head1 Before You Begin
+
+INN requires several other packages be installed in order to be fully
+functional (or in some cases, to work at all):
+
+=over 2
+
+=item *
+
+In order to build INN, you will need a C compiler that understands ANSI C.
+If you are trying to install INN on an operating system that doesn't have
+an ANSI C compiler (such as SunOS), installing gcc is recommended. You
+can get it from <ftp://ftp.gnu.org/gnu/gcc/> or its mirrors. INN is
+tested with gcc more thoroughly than with any other compiler, so even if
+you have another compiler available, you may wish to use gcc instead.
+
+=item *
+
+Currently, in order to build INN, you will need an implementation of yacc.
+GNU bison (from <ftp://ftp.gnu.org/gnu/bison/> or its mirrors) will work
+fine. We hope to remove this requirement in the future.
+
+=item *
+
+INN requires at least Perl 5.004_03 to build and to run several
+subsystems. INN is tested primarily with newer versions of Perl, so it's
+generally recommended that you install the latest stable distribution of
+Perl before compiling INN. For instructions on obtaining and installing
+Perl, see <http://www.perl.com/pub/language/info/software.html>. Note
+that you may need to use the same compiler and options (particularly
+largefile support) for Perl and INN.
+
+If you're using a version of Perl prior to 5.6.0, you may need to make
+sure that the Perl versions of your system header files have been
+generated in order for Sys::Syslog to work properly (used by various
+utility programs, including controlchan). To do this, run the following
+two commands:
+
+ # cd /usr/include
+ # h2ph * sys/*
+
+An even better approach is to install Perl 5.6.1 or later, which have a
+fixed version of Sys::Syslog that doesn't require this (as well as many
+other improvements over earlier versions of Perl).
+
+=item *
+
+The INN Makefiles use the syntax C<include FILE>, rather than the syntax
+expected by some BSDish systems of C<.include E<lt>FILEE<gt>>. If your
+system expects the latter syntax, the recommended solution is to install
+GNU make from <ftp://ftp.gnu.org/make/>. You may have GNU make already
+installed as gmake, in which case using gmake rather than make to build
+INN should be sufficient.
+
+=item *
+
+If you want to enable support for authenticated control messages (this is
+not required, but is highly recommended for systems carrying public Usenet
+hierarchies) then you will need to install some version of PGP. The
+recommended version is GnuPG, since it's actively developed, supports
+OpenPGP, is freely available and free to use for any purpose (in the US
+and elsewhere), and (as of version 1.0.4 at least) supports the RSA
+signatures used by most current control message senders.
+
+Alternately, you can install PGP from <http://www.pgp.com/> or one of the
+international versions of it. Be warned, however, that the licensing
+restrictions on PGP inside the United States are extremely unclear; it's
+possible that if you are installing INN for a company in the U.S., even if
+the news server is not part of the business of that company, you would
+need to purchase a commercial license for PGP. For an educational or
+non-profit organization, this shouldn't be a problem.
+
+=item *
+
+If you want to use either the Python embedded hooks, you'll need to have a
+suitable versions of Python installed. See F<doc/hook-python> for more
+information.
+
+=item *
+
+Many of INN's optional features require other packages (primarily
+libraries) be installed. If you wish to use any of these optional
+features, you will need to install those packages first. Here is a table
+of configure options enabling optional features and the software and
+versions you'll need:
+
+ --with-perl Perl 5.004_03 or higher, 5.6.1+ recommended
+ --with-python Python 1.5.2 or higher
+ --with-berkeleydb BerkeleyDB 2.0 or higher, 4.2+ recommended
+ --with-openssl OpenSSL 0.9.6 or higher
+ --with-sasl SASL 2.x or higher
+ --with-kerberos MIT Kerberos v5 1.2.x or higher
+
+If any of these libraries (other than Perl or Python) are built shared and
+installed in locations where your system doesn't search for shared
+libraries by default, you may need to encode the paths to those shared
+libraries in the INN binaries. For more information on shared library
+paths, see:
+
+ <http://www.eyrie.org/~eagle/notes/rpath.html>
+
+For most systems, setting the environment variable LD_RUN_PATH to a
+colon-separated list of additional directories in which to look for shared
+libraries before building INN will be sufficient.
+
+=back
+
+=head1 Unpacking the Distribution
+
+Released versions of INN are available from ftp.isc.org in F</isc/inn>.
+New major releases will be announed on <inn-announce@isc.org> (see
+F<README>) when they're made.
+
+If you want more a more cutting-edge version, you can obtain current
+snapshots from from ftp.isc.org in directory F</isc/inn/snapshots>. These
+are snapshots of the INN CVS tree taken daily; there are two snapshots
+made each night (one of the current development branch, and one of the
+stable branch consisting of bug fixes to the previous major release).
+They are stored in date format; in other words the snapshots from April
+6th, 2000, would be named F<inn-CURRENT-20000406.tar.gz> and
+F<inn-STABLE-20000406.tar.gz>. Choose the newest file of whichever branch
+you prefer. (Note that the downloading, configuring, and compiling steps
+can be done while logged in as any user.)
+
+The distribution is in gzip compressed tar archive format. To extract it,
+execute:
+
+ gunzip -c <inn-src-file> | tar -xf -
+
+Extracting the source distribution will create a directory named
+F<< inn-<version> >> or F<< inn-<BRANCH>-<date> >> where the source
+resides.
+
+=head1 Installing INN
+
+Before beginning installation, you should make sure that there is a user
+on your system named C<news>, and that this user's primary group is set to
+a group called C<news>. You can change these with the B<--with-news-user>
+and B<--with-news-group> options to configure (see below). The home
+directory of this user should be set to the directory under which you wish
+to install INN (F</usr/local/news> is the default and is a good choice).
+INN will install itself as this user and group. You can change these if
+you want, but these are the defaults and it's easier to stick with them on
+a new installation.
+
+By default, INN sends reports to the user C<usenet>. This account isn't
+used for any other purposes. You can change it with the
+B<--with-news-master> option to configure (see below).
+
+WARNING: By default, INN installs various configuration files as
+group-writeable, and in general INN is not hardened from a security
+standpoint against an attack by someone who is already in the news group.
+In general, you should consider membership in the news group as equivalent
+to access to the news account. You should not rely on being able to keep
+anyone with access to the news GID from converting that into access to the
+news UID. The recommended configuration is to have the only member of the
+group C<news> be the user C<news>.
+
+Installing INN so that all of its files are under a single directory tree,
+rather than scattering binaries, libraries, and man pages throughout the
+file system, is strongly recommended. It helps keep everything involved
+in the operation of INN together as a unit and will make the installation
+instructions easier to follow.
+
+As a side note, whenever doing anything with a running news server, first
+log in as this user. That way, you can ensure that all files created by
+any commands you run are created with the right ownership to be readable
+by the server. Particularly avoid doing anything in the news spool itself
+as root, and make sure you fix the ownership of any created files if you
+have to. INN doesn't like files in the news spool owned by a user other
+than the news user. However, since certain binaries need to be setuid
+root, indiscriminate use of C<chown news> is not the solution. (If you
+don't like to log in to system accounts, careful use of C<chmod g+s> on
+directories and a umask of C<002> or C<007> may suffice.)
+
+INN uses GNU autoconf and a generated configure script to make
+configuration rather painless. Unless you have a rather abnormal setup,
+configure should be able to completely configure INN for your system. If
+you want to change the defaults, you can invoke the configure script with
+one or more command line options. Type:
+
+ ./configure --help
+
+for a full list of supported options. Some of the most commonly used
+options are:
+
+=over 4
+
+=item B<--prefix>=PATH
+
+Sets the installation prefix for INN. The default is F</usr/local/news>.
+All of INN's programs and support files will be installed under this
+directory. This should match the home directory of your news user (it
+will make installation and maintenance easier). It is not recommended to
+set this to F</usr>; if you decide to do that anyway, make sure to point
+INN's temporary directory at a directory that isn't world-writeable (see
+B<--with-tmp-dir> below).
+
+=item B<--with-db-dir>=PATH
+
+Sets the prefix for INN database files. The default is F<PREFIX/db>,
+where PREFIX is F</usr/local/news> unless overridden with the option
+above. The history and active files will be stored in this directory, and
+writes to those files are an appreciable percentage of INN's disk
+activity. The history file can also be quite large (requiring up to 2 GB
+or more during nightly expire), so this is a common portion of INN to move
+to a different file system.
+
+=item B<--with-spool-dir>=PATH
+
+Sets the prefix for the news spool (when using any storage method other
+than CNFS) and the overview spool. The default is F<PREFIX/spool>. This
+is another common portion of INN to move to a different file system (often
+F</news>).
+
+=item B<--with-tmp-dir>=PATH
+
+Sets the directory in which INN will create temporary files. This should
+under no circumstances be the same as the system temporary directory or
+otherwise be set to a world-writeable directory, since INN doesn't take
+care to avoid symlink attacks and other security problems possible with a
+world-writeable directory. This directory should be reserved for the
+exclusive use of INN and only writeable by the news user. Usage is
+generally light, so this is unlikely to need a separate partition.
+
+It's also possible to set the paths for most other sections of the INN
+installation independently; see C<./configure --help> and look for the
+B<--with-*-dir>=PATH options.
+
+=item B<--enable-largefiles>
+
+Enables large file support. This is not enabled by default, even on
+platforms that support it, because it changes the format of INN's on-disk
+databases (making it difficult to upgrade an earlier INN installation) and
+can significantly increase the size of some of the history database files.
+Large file support is not necessary unless your history database is so
+large that it exceeds 2 GB or you want to use CNFS buffers larger than 2
+GB.
+
+The history, tradindexed and buffindexed overview, CNFS, and timecaf
+databases written by an INN built with this option are incompatible with
+those written by an INN without this option.
+
+=item B<--enable-tagged-hash>
+
+Use tagged hash table for the history database. The tagged hash format
+uses much less memory but is somewhat slower. This option is recommended
+if you have less than 256 MB of RAM on your news server. If you install
+INN without tagged hash (the default) and expire takes an excessive amount
+of time, you should make sure the RAM in your system satisfies the
+following formula:
+
+ ram > 10 * tablesize
+
+ ram: Amount of system RAM (in bytes)
+ tablesize: 3rd field on the 1st line of history.dir (bytes)
+
+If you don't have at least that much RAM, try rebuilding INN with tagged
+hash enabled.
+
+NOTE: B<--enable-largefiles> cannot be used with B<--enable-tagged-hash>.
+
+=item B<--with-perl>
+
+Enables support for embedded Perl, allowing you to install filter scripts
+written in Perl. Highly recommended, because many really good spam
+filters are written in Perl. See F<doc/hook-perl> for all the details.
+
+Even if you do not use this option, INN still requires Perl as mentioned
+above.
+
+=item B<--with-python>
+
+Enables support for Python, allowing you to install filter and
+authentication scripts written in Python. You will need Python 1.5.2 or
+later installed on your system to enable this option. See
+F<doc/hook-python> for all the details. Note that there is an
+incompatibility between INN and Python 2.0 when Python is compiled with
+cycle garbage collection; this problem was reported fixed in Python 2.1a1.
+
+=item B<--with-innd-port>=PORT
+
+By default, inndstart(8) refuses to bind to any port under 1024 other than
+119 and 433 for security reasons (to prevent attacks on rsh(1)-based
+commands and replacing standard system daemons). If you want to run innd
+on a different port under 1024, you'll need to tell configure what port
+you intend to use. (You'll also still need to set the port number in
+F<inn.conf> or give it to inndstart on the command line.)
+
+=item B<--with-syslog-facility>=FACILITY
+
+Specifies the syslog facility that INN programs should log to. The
+default is LOG_NEWS unless configure detects that your system doesn't
+understand that facility, in which case it uses LOG_LOCAL1. This flag
+overrides the automatic detection. Be sure to specify a facility not used
+by anything else on your system (one of LOG_LOCAL0 through LOG_LOCAL7, for
+example).
+
+=item B<--enable-libtool>
+
+INN has optional support for libtool to generate shared versions of INN's
+libraries. This can significantly decrease the size of the various
+binaries that come with a complete INN installation. You can also choose
+to use libtool even when only building static libraries; a libtool build
+may be somewhat more portable on weird systems. libtool builds aren't the
+default because they take somewhat longer. See C<./configure --help> for
+the various available options related to libtool builds.
+
+Please note that INN's shared library interface is not stable and may
+change drastically in future releases. For this reason, it's also not
+properly versioned and won't be until some degree of stability is
+guaranteed, and the relevant header files are not installed. Only INN
+should use INN's shared libraries, and you should only use the shared
+libraries corresponding to the version of INN that you're installing.
+
+Also, when updating an existing version of INN, INN tries to save backup
+copies of all files so that you can revert to the previous installed
+version. Unfortunately, when using shared libraries, this confuses
+ldconfig on some systems (such as Linux) and the symbolic links for the
+libraries may point to the .OLD versions. If this happens, you can either
+fix the links by hand or remove the .OLD versions and re-run ldconfig.
+
+=item B<--enable-uucp-rnews>
+
+If this option is given to configure, rnews will be installed setuid news,
+owned by group uucp, and mode 4550. This will allow the UUCP subsystem
+to run rnews to process UUCP batches of news articles. Prior to INN 2.3,
+installing rnews setuid news was standard; since most sites no longer use
+UUCP, it is no longer the default as of INN 2.3 and must be requested at
+configure time. You probably don't want to use this option unless your
+server accepts UUCP news batches.
+
+=item B<--enable-setgid-inews>
+
+If this option is given to configure, inews will be installed setgid news
+and world-executable so that non-privileged users on the news server
+machine can use inews to post articles locally (somewhat faster than
+opening a new network connection). For standalone news servers, by far
+the most common configuration now, there's no need to use this option;
+even if you have regular login accounts on your news server, INN's inews
+can post fine via a network connection to your running news server and
+doesn't need to use the local socket (which is what setgid enables it to
+do). Installing inews setgid was the default prior to INN 2.3.
+
+=item B<--with-berkeleydb=PATH>
+
+Enables support for Berkeley DB (2.x or 3.x), which means that it will
+then be possible to use the ovdb overview method if you wish. Enabling
+this configure option doesn't mean you'll be required to use ovdb, but it
+does require that Berkeley DB be installed on your system (including the
+header files, not just the runtime libraries). If a path is given, it
+sets the installed directory of Berkeley DB (configure will search for it
+in some standard locations, but if you have it installed elsewhere, you
+may need this option).
+
+=item B<--with-openssl=PATH>
+
+Enables support for SSL for news reading, which means it will be possible
+to have SSL or TLS encrypted NNTP connections between your server and
+newsreaders. This option requires OpenSSL be installed on your system
+(including the header files, not just the runtime libraries). If a path
+is given, it sets the installed directory of OpenSSL. After compiling and
+installing INN with this option, you'll still need to make a certificate
+and private key to use SSL. See below for details on how to do that.
+
+=item B<--enable-ipv6>
+
+Enables support for IPv6 in innd, innfeed, nnrpd, and several of the
+supporting programs. This option should be considered developmental at
+present. For more information see F<doc/IPv6-info> (and if you have any
+particularly good or bad news to report, please let us know at
+<inn-bugs@isc.org>).
+
+=back
+
+For the most common installation, a standalone news server, a suggested
+set of options is:
+
+ ./configure --with-perl
+
+provided that you have the necessary version of Perl installed.
+(Compiling with an embedded Perl interpretor will allow you to use one of
+the available excellent spam filters if you so choose.)
+
+If the configure program runs successfully, then you are ready to build
+the distribution. From the root of the INN source tree, type:
+
+ make
+
+At this point you can step away from the computer for a little while and
+have a quick snack while INN compiles. On a decently fast system it
+should only take five or ten minutes at the most to build.
+
+Once the build has completed successfully, you are ready to install INN
+into its final home. Type:
+
+ make install
+
+You will need to run this command as root so that INN can create the
+directories it needs, change ownerships (if you did not compile as the
+news user) and install a couple of setuid wrapper programs needed to raise
+resource limits and allow innd to bind to ports under 1024. This step
+will install INN under the install directory (F</usr/local/news>, unless
+you specified something else to the configure script).
+
+If you are configuring SSL support for newsreaders, you must make a
+certificate and private key at least once. Type:
+
+ make cert
+
+as root in order to do this.
+
+You are now ready for the really fun part: configuring your copy of INN!
+
+=head1 Choosing an Article Storage Format
+
+The first thing to decide is how INN should store articles on your system.
+There are four different methods for you to choose from, each of which has
+its own advantages and disadvantages. INN can support all four at the
+same time, so you can store certain newsgroups in one method and other
+newsgroups in another method.
+
+The supported storage formats are:
+
+=over 4
+
+=item tradspool
+
+This is the storage method used by all versions of INN previous to 2.0.
+Articles are stored as individual text files whose names are the same as
+the article number. The articles are divided up into directories based on
+the newsgroup name. For example, article 12345 in news.software.nntp
+would be stored as F<news/software/nntp/12345> relative to the root of the
+article spool.
+
+Advantages: Widely used and well-understood storage mechanism, can read
+article spools written by older versions of INN, compatible with all
+third-party INN add-ons, provides easy and direct access to the articles
+stored on your server and makes writing programs that fiddle with the news
+spool very easy, and gives you fine control over article retention times.
+
+Disadvantages: Takes a very fast file system and I/O system to keep up
+with current Usenet traffic volumes due to file system overhead. Groups
+with heavy traffic tend to create a bottleneck because of inefficiencies
+in storing large numbers of article files in a single directory. Requires
+a nightly expire program to delete old articles out of the news spool, a
+process that can slow down the server for several hours or more.
+
+=item timehash
+
+Articles are stored as individual files as in tradspool, but are divided
+into directories based on the arrival time to ensure that no single
+directory contains so many files as to cause a bottleneck.
+
+Advantages: Heavy traffic groups do not cause bottlenecks, and fine
+control of article retention time is still possible.
+
+Disadvantages: The ability to easily find all articles in a given
+newsgroup and manually fiddle with the article spool is lost, and INN
+still suffers from speed degredation due to file system overhead (creating
+and deleting individual files is a slow operation).
+
+=item timecaf
+
+Similar to timehash, articles are stored by arrival time, but instead of
+writing a separate file for each article, multiple articles are put in the
+same file.
+
+Advantages: Roughly four times faster than timehash for article writes,
+since much of the file system overhead is bypassed, while still retaining
+the same fine control over article retention time.
+
+Disadvantages: Even worse than timehash, and similar to cnfs (below),
+using this method means giving up all but the most careful manually
+fiddling with your article spool. As one of the newer and least widely
+used storage types, timecaf has not been as thoroughly tested as the other
+methods.
+
+=item cnfs
+
+CNFS stores articles sequentially in pre-configured buffer files. When
+the end of the buffer is reached, new articles are stored from the
+beginning of the buffer, overwriting older articles.
+
+Advantages: Blazingly fast because no file creations or deletions are
+necessary to store an article. Unlike all other storage methods, does not
+require manual article expiration; old articles are deleted to make room
+for new ones when the buffers get too full. Also, with CNFS your server
+will never throttle itself due to a full spool disk, and groups are
+restricted to just the buffer files you give them so that they can never
+use more than the amount of disk space you allocate to them.
+
+Disadvantages: Article retention times are more difficult to control
+because old articles are overwritten automatically. Attacks on Usenet,
+such as flooding or massive amounts of spam, can result in wanted articles
+expiring much faster than you intended (with no warning).
+
+=back
+
+Some general recommendations: If you are installing a transit news server
+(one that just accepts news and sends it out again to other servers and
+doesn't support any readers), use CNFS exclusively and don't worry about
+any of the other storage methods. Otherwise, put high-volume groups and
+groups whose articles you don't need to keep around very long (binaries
+groups, *.jobs*, news.lists.filters, etc.) in CNFS buffers, and use
+timehash, timecaf, or tradspool (if you have a fast I/O subsystem or need
+to be able to go through the spool manually) for everything else. You'll
+probably find it most convenient to keep special hierarchies like local
+hierarchies and hierarchies that should never expire in tradspool.
+
+If your news server will be supporting readers, you'll also need to choose
+an overview storage mechanism (by setting I<ovmethod> in F<inn.conf>).
+There are three overview mechanisms to choose from: tradindexed,
+buffindexed, and ovdb. tradindexed is very fast for readers, but it has
+to update two files for each incoming article and can be quite slow to
+write. buffindexed can keep up with a large feed more easily, since it
+uses large buffers to store all overview information, but it's somewhat
+slower for readers (although not as slow as the unified overview in INN
+2.2). ovdb stores overview data in a Berkeley DB database; it's fast and
+very robust, but requires more disk space. See the ovdb(5) man page for
+more information on it.
+
+Note that ovdb has not been as widely tested as the other overview
+mechanisms and should be considered experimental. tradindexed is the best
+tested and most widely used of the overview implementations.
+
+If buffindexed is chosen, you will need to create the buffers for it to
+use (very similar to creating CNFS buffers) and list the available buffers
+in F<buffindexed.conf>. See buffindexed.conf(5) for more information.
+
+=head1 Configuring INN
+
+All documentation from this point on assumes that you have set up the news
+user on your system as suggested in L<Installing INN> so that the root of
+your INN installation is F<~news/>. If you've moved things around by
+using options with C<configure>, you'll need to adjust the instructions to
+account for that.
+
+All of INN's configuration files are located in F<~news/etc>. Unless
+noted otherwise, any files referred to below are in this directory. When
+you first install INN, a sample of each file (containing lots of comments)
+is installed in F<~news/etc>; refer to these for concrete examples of
+everything discussed in this section.
+
+All of INN's configuration files, all of the programs that come with it,
+and some of its library routines have documentation in the form of man
+pages. These man pages were installed in F<~news/man> as part of the INN
+installation process and are the most complete reference to how INN works.
+You're strongly encouraged to refer to the man pages frequently while
+configuring INN, and for quick reference afterwards. Any detailed
+questions about individual configuration files or the behavior of specific
+programs should be answered in them. You may want to add F<~news/man> to
+your MANPATH environment variable; otherwise, you may have to use a
+command like:
+
+ man -M ~news/man inn.conf
+
+to see the inn.conf(5) man page (for example).
+
+Before we begin, it is worth mentioning the wildmat pattern matching
+syntax used in many configuration files. These are simple wildcard
+matches using the asterisk (C<*>) as the wildcard character, much like the
+simple wildcard expansion used by Unix shells.
+
+In many cases, wildmat patterns can be specified in a comma-separated list
+to indicate a list of newsgroups. When used in this fashion, each pattern
+is checked in turn to see if it matches, and the last pattern in the line
+that matches the group name is used. Patterns beginning with C<!> mean to
+exclude groups matching that pattern. For example:
+
+ *, !comp.*, comp.os.*
+
+In this case, we're saying we match everything (C<*>), except that we
+don't match anything under comp (C<!comp.*>), unless it is actually under
+the comp.os hierarchy (C<comp.os.*>). This is because non-comp groups
+will match only the first pattern (so we want them), comp.os groups will
+match all three patterns (so we want them too, because the third pattern
+counts in this case), and all other comp groups will match the first and
+second patterns and will be excluded by the second pattern.
+
+Some uses of wildmat patterns also support "poison" patterns (patterns
+starting with C<@>). These patterns behave just like C<!> patterns when
+checked against a single newsgroup name. Where they become special is for
+articles crossposted to multiple newsgroups; normally, such an article
+will be considered to match a pattern if any of the newsgroups it is
+posted to matches the pattern. If any newsgroup the article is posted to
+matches an expression beginning with C<@>, however, that article will not
+match the pattern even if other newsgroups to which it was posted match
+other expressions.
+
+See uwildmat(3) for full details on wildmat patterns.
+
+In all INN configuration files, blank lines and lines beginning with a
+C<#> symbol are considered comments and are ignored. Be careful, not all
+files permit comments to begin in the middle of the line.
+
+=head2 inn.conf
+
+The first, and most important file is F<inn.conf>. This file is organized
+as a series of parameter-value pairs, one per line. The parameter is
+first, followed by a colon and one or more whitespace characters, and then
+the value itself. For some parameters the value is a string or a number;
+for others it is true or false. (True values can be written as C<yes>,
+C<true>, or C<on>, whichever you prefer. Similarly, false values can be
+written as C<no>, C<false>, or C<off>.)
+
+F<inn.conf> contains dozens of changeable parameters (see inn.conf(5) for
+full details), but only a few really need to be edited during normal
+operation:
+
+=over 4
+
+=item allownewnews
+
+If set to true then INN will support the NEWNEWS command for news readers.
+While this can be an expensive operation, its speed has been improved
+considerably as of INN 2.3 and it's probably safe to turn on without
+risking excessive server load. The default is true. (Note that the
+I<access:> setting in F<readers.conf> overrides this value; see
+readers.conf(5) for more details.)
+
+=item complaints
+
+Used to set the value of the X-Complaints-To: header, which is added to
+all articles posted locally. The usual value would be something like
+C<abuse@example.com> or C<postmaster@example.com>. If not specified, the
+newsmaster email address will be used.
+
+=item hiscachesize
+
+The amount of memory (in kilobytes) to allocate for a cache of recently
+used history file entries. Setting this to 0 disables history caching.
+History caching can greatly increase the number of articles per second
+that your server is capable of processing. A value of C<256> is a good
+default choice.
+
+=item logipaddr
+
+If set to true (the default), INN will log the IP address (or hostname, if
+the host is listed in F<incoming.conf> with a hostname) of the remote host
+from which it received an article. If set to false, the trailing Path:
+header entry is logged instead. If you are using controlchan (see below)
+and need to process ihave/sendme control messages (this is very, very
+unlikely, so if you don't know what this means, don't worry about it),
+make sure you set this to false, since controlchan needs a site name, not
+an IP address.
+
+=item organization
+
+Set this to the name of your organization as you want it to appear in the
+Organization: header of all articles posted locally and not already
+containing that header. This will be overridden by the value of the
+ORGANIZATION environment variable (if it exists). If neither this
+parameter nor the environment variable or set, no Organization: header
+will be added to posts which lack one.
+
+=item pathhost
+
+This is the name of your news server as you wish it to appear in the Path:
+header of all postings which travel through your server (this includes
+local posts and incoming posts that you forward out to other sites). If
+this parameter is unspecified, the fully-qualified domain name (FQDN) of
+the machine will be used instead. Please use the FQDN of your server or
+an alias for your server unless you have a very good reason not to; a
+future version of the news RFCs may require this.
+
+=item rlimitnofile
+
+If set to a non-negative value (the default is C<-1>), INN (both innd and
+innfeed) will try to raise the maximum number of open file descriptors to
+this value when it starts. This may be needed if you have lots of
+incoming and outgoing feeds. Note that the maximum value for this setting
+is very operating-system-dependent, and you may have to reconfigure your
+system (possibly even recompile your kernel) to increase it. See L<File
+Descriptor Limits> for complete details.
+
+=back
+
+There are tons of other possible settings; you may want to read through
+inn.conf(5) to get a feel for your options. Don't worry if you don't
+understand the purpose of most of them right now. Some of the settings
+are only needed for very obscure things, and with more experience running
+your news server the rest will make more sense.
+
+=head2 newsfeeds
+
+F<newsfeeds> determines how incoming articles are redistributed to your
+peers and to other INN processes. F<newsfeeds> is very versatile and
+contains dozens of options; we will touch on just the basics here.
+The manpage contains more detailed information.
+
+F<newsfeeds> is organized as a series of feed entries. Each feed entry is
+composed of four fields separated by colons. Entries may span multiple
+lines by using a backslash (C<\>) to indicate that the next line is a
+continuation of the current line. (Note that comments don't interact with
+backslashes in the way you might expect. A commented-out line ending in a
+backslash will still be considered continued on the next line, possibly
+resulting in more commented out than you intended or bizarre syntax
+errors. In general, it's best to avoid commenting out lines in the middle
+of continuation lines.)
+
+The first field in an entry is the name of the feed. It must be unique,
+and for feeds to other news servers it is usually set to the actual
+hostname of the remote server (this makes things easier). The name can
+optionally be followed by a slash and a comma-separated exclude list. If
+the feed name or any of the names in the exclude list appear in the Path
+line of an article, then that article will not be forwarded to the feed as
+it is assumed that it has passed through that site once already. The
+exclude list is useful when a news server's hostname is not the same as
+what it puts in the Path header of its articles, or when you don't want a
+feed to receive articles from a certain source.
+
+The second field specifies a set of desired newsgroups and distribution
+lists, given as newsgroup-pattern/distribution-list. The distribution
+list is not described here; see newsfeeds(5) for information (it's not
+used that frequently in practice). The newsgroup pattern is a
+wildmat-style pattern list as described above (supporting C<@>).
+
+The third field is a comma-separated list of flags that determine both the
+type of feed entry and sets certain parameters for the entry. See
+newsfeeds(5) for information on the flag settings; you can do a surprising
+amount with them. The three most common patterns, and the ones mainly
+used for outgoing news feeds to other sites, are C<Tf,Wnm> (to write out a
+batch file of articles to be sent, suitable for processing by nntpsend and
+innxmit), C<Tm> (to send the article to a funnel feed, used with innfeed),
+and C<Tc,Wnm*> (to collect a funnel feed and send it via a channel feed to
+an external program, used to send articles to innfeed).
+
+The fourth field is a multi-purpose parameter whose meaning depends on the
+settings of the flags in the third field. To get a feel for it using the
+examples above, for file feeds (C<Tf>) it's the name of the file to write,
+for funnel feeds (C<Tm>) it's the name of the feed entry to funnel into,
+and for channel feeds (C<Tc>) it's the name of the program to run and feed
+references to articles.
+
+Now that you have a rough idea of the file layout, we'll begin to add the
+actual feed entries. First, we'll set up the special ME entry. This entry
+is required and serves two purposes: the newsgroup pattern specified here
+is prepended to the newsgroup list of all other feeds, and the
+distribution pattern for this entry determines what distributions (from
+the Distribution: header of incoming articles) are accepted from remote
+sites by your server. The example in the sample newsfeeds file is a good
+starting point. If you are going to create a local hierarchy that should
+not be distributed off of your system, it may be useful to exclude it from
+the default subscription pattern, but default subscription patterns are
+somewhat difficult to use right so you may want to just exclude it
+specifically from every feed instead.
+
+The ME entry tends to confuse a lot of people, so this point is worth
+repeating: the newsgroup patterns set the default subscription for
+I<outgoing> feeds, and the distribution patterns set the acceptable
+Distribution: header entries for I<incoming> articles. This is confusing
+enough that it may change in later versions of INN.
+
+There are two basic ways to feed articles to remote sites. The most
+common for large sites and particularly for transit news servers is
+innfeed(8), which sends articles to remote sites in real time (the article
+goes out to all peers that are supposed to receive it immediately after
+your server accepts it). For smaller sites, particularly sites where the
+only outgoing messages will be locally posted articles, it's more common
+to batch outgoing articles and send them every ten minutes or so from cron
+using nntpsend(8) and innxmit(8). Batching gives you more control and
+tends to be extremely stable and reliable, but it's much slower and can't
+handle high volume very well.
+
+Batching outgoing posts is easy to set up; for each peer, add an entry to
+newsfeeds that looks like:
+
+ remote.example.com/news.example.com\
+ :<newsgroups>\
+ :Tf,Wnm:
+
+where <newsgroups> is the wildmat pattern for the newsgroups that site
+wants. In this example, the actual name of the remote site is
+"remote.example.com", but it puts "news.example.com" in the Path: header.
+If the remote site puts its actual hostname in the Path: header, you won't
+need the C</news.example.com> part.
+
+This entry will cause innd to write out a file in F<~news/spool/outgoing>
+named F<remote.example.com> and containing the Message-ID and storage
+token of each message to send to that site. (The storage token is INN's
+internal pointer to where an article is stored; to retrieve an article
+given its storage token, use sm(8)). F<innxmit> knows how to read files
+of this format and send those articles to the remote site. For
+information on setting it up to run periodically, see L<Setting Up the
+Cron Jobs> below. You will also need to set up a config file for
+F<nntpsend>; see the man page for nntpsend.ctl(5) for more information.
+
+If instead you want to use F<innfeed> to send outgoing messages
+(recommended for sites with more than a couple of peers), you need some
+slightly more complex magic. You still set up a separate entry for each
+of your peers, but rather than writing out batch files, they all "funnel"
+into a special innfeed entry. That special entry collects all of the
+separate funnel feeds and sends the data through a special sort of feed to
+an external program (F<innfeed> in this case); this is a "channel" feed.
+
+First, the special channel feed entry for F<innfeed> that will collect all
+the funnel feeds:
+
+ innfeed!\
+ :!*\
+ :Tc,Wnm*:/usr/local/news/bin/startinnfeed -y
+
+(adjust the path to startinnfeed(1) if you installed it elsewhere). Note
+that we don't feed this entry any articles directly (its newsgroup pattern
+is C<!*>). Note also that the name of this entry ends in an exclamation
+point. This is a standard convention for all special feeds; since the
+delimiter for the Path: header is C<!>, no site name containing that
+character can ever match the name of a real site.
+
+Next, set up entries for each remote site to which you will be feeding
+articles. All of these entries should be of the form:
+
+ remote.example.com/news.example.com\
+ :<newsgroups>\
+ :Tm:innfeed!
+
+specifying that they funnel into the C<innfeed!> feed. As in the previous
+example for batching, "remote.example.com" is the actual name of the
+remote peer, "news.example.com" is what it puts in the Path: header (if
+different than the actual name of the server), and <newsgroups> is the
+wildmat pattern of newsgroups to be sent.
+
+As an alternative to NNTP, INN may also feed news out to an IMAP server,
+by using imapfeed(8), which is almost identical to F<innfeed>. The
+F<startinnfeed> process can be told to start F<imapfeed> instead of
+F<innfeed>. The feed entry for this is as follows:
+
+ imapfeed!\
+ :!*\
+ :Tc,Wnm*,S16384:/usr/local/news/bin/startinnfeed imapfeed
+
+And set up entries for each remote site like:
+
+ remote.example.com/news.example.com\
+ :<newsgroups>\
+ :Tm:imapfeed!
+
+For more information on F<imapfeed>, look at the
+F<innfeed/imap_connection.c>. For more information on IMAP in general,
+see RFC 2060.
+
+Finally, there is a special entry for controlchan(8), which processes
+newsgroup control messages, that should always be in F<newsfeeds> unless
+you never want to honor any control messages. This entry should look
+like:
+
+ controlchan!\
+ :!*,control,control.*,!control.cancel\
+ :Tc,Wnsm:/usr/local/news/bin/controlchan
+
+(modified for the actual path to F<controlchan> if you put it somewhere
+else). See L<Processing Control Messages> for more details.
+
+For those of you upgrading from earlier versions of INN, note that the
+functionality of overchan(8) and F<crosspost> is now incorporated into INN
+and neither of those programs is necessary. Unfortunately, F<crosspost>
+currently will not work even with the tradspool storage method. You can
+still use F<overchan> if you make sure to set I<useoverchan> to true in
+F<inn.conf> so that innd doesn't write overview data itself, but be
+careful: innd may accept articles faster than overchan can process the
+data.
+
+=head2 incoming.conf
+
+F<incoming.conf> file specifies which machines are permitted to connect to
+your host and feed it articles. Remote servers you peer with should be
+listed here. Connections from hosts not listed in this file will (if you
+don't allow readers) be rejected or (if you allow readers) be handed off
+to nnrpd and checked against the access restrictions in F<readers.conf>.
+
+Start with the sample F<incoming.conf> and, for each remote peer, add an
+entry like:
+
+ peer remote.example.com { }
+
+This uses the default parameters for that feed and allows incoming
+connections from a machine named "remote.example.com". If that peer could
+be connecting from several different machines, instead use an entry like:
+
+ peer remote.example.com {
+ hostname: "remote.example.com, news.example.com"
+ }
+
+This will allow either "remote.example.com" or "news.example.com" to feed
+articles to you. (In general, you should add new peer lines for each
+separate remote site you peer with, and list multiple host names using the
+I<hostname> key if one particular remote site uses multiple servers.)
+
+You can restrict the newsgroups a remote site is allowed to send you,
+using the same sort of pattern that newsfeeds(5) uses. For example, if
+you want to prevent "example.com" hosts from sending you any articles in
+the C<local.*> hierarchy (even if they're crossposted to other groups),
+change the above to:
+
+ peer remote.example.com {
+ patterns: "*, @local.*"
+ hostname: "remote.example.com, news.example.com"
+ }
+
+Note, however, that restricting what a remote side can send you will
+I<not> reduce your incoming bandwidth usage. The remote site will still
+send you the entire article; INN will just reject it rather than saving it
+to disk. To reduce bandwidth, you have to contact your peers and ask them
+not to send you the traffic you don't want.
+
+There are various other things you can set, including the maximum number
+of connections the remote host will be allowed. See incoming.conf(5) for
+all the details.
+
+Note for those familiar with older versions of INN: this file replaces
+the old F<hosts.nntp> configuration file.
+
+=head2 cycbuff.conf
+
+F<cycbuff.conf> is only required if CNFS is used. If you aren't using
+CNFS, skip this section.
+
+CNFS stores articles in logical objects called I<metacycbuffs>. Each
+metacycbuff is in turn composed of one or more physical buffers called
+I<cycbuffs>. As articles are written to the metacycbuff, each article is
+written to the next cycbuff in the list in a round-robin fashion (unless
+C<sequential> mode is specified, in which case each cycbuff is filled
+before moving on to the next). This is so that you can distribute the
+individual cycbuffs across multiple physical disks and balance the load
+between them.
+
+There are two ways to create your cycbuffs:
+
+=over 4
+
+=item 1.
+
+Use a block device directly. This will probably give you the most speed
+since it avoids the file system overhead of large files, but it requires
+your OS support mmap(2) on a block device. Solaris supports this, as do
+late Linux 2.4 kernels. FreeBSD does not at last report. Also on many
+PC-based Unixes it is difficult to create more than eight partitions,
+which may limit your options.
+
+=item 2.
+
+Use a real file on a filesystem. This will probably be a bit slower than
+using a block device directly, but it should work on any Unix system.
+
+=back
+
+If you're having doubts, use option #2; it's easier to set up and should
+work regardless of your operating system.
+
+Now you need to decide on the sizes of your cycbuffs and metacycbuffs.
+You'll probably want to separate the heavy-traffic groups
+(C<alt.binaries.*> and maybe a few other things like C<*.jobs*> and
+C<news.lists.filters>) into their own metacycbuff so that they don't
+overrun the server and push out articles on the more useful groups. If
+you have any local groups that you want to stay around for a while then
+you should put them in their own metacycbuff as well, so that they don't
+get pushed out by other traffic. (Or you might store them in one of the
+other storage methods, such as tradspool.)
+
+For each metacycbuff, you now need to determine how many cycbuffs will
+make up the metacycbuff, the size of those cycbuffs, and where they will
+be stored. Some OSes do not support files larger than 2 GB, which will
+limit the size you can make a single cycbuff, but you can still combine
+many cycbuffs into each metacycbuff. Older versions of Linux are known to
+have this limitation; FreeBSD does not. Some OSes that support large
+files don't support direct access to block devices for large partitions
+(Solaris prior to Solaris 7, or not running in 64-bit mode, is in this
+category); on those OSes, if you want cycbuffs over 2 GB, you'll have to
+use regular files. If in doubt, keep your cycbuffs smaller than 2 GB.
+Also, when laying out your cycbuffs, you will want to try to arrange them
+across as many physical disks as possible (or use a striped disk array and
+put them all on that).
+
+In order to use any cycbuff larger than 2 GB, you need to build INN with
+the B<--enable-largefiles> option. See L<Installing INN> for more
+information and some caveats.
+
+For each cycbuff you will be creating, add a line to F<cycbuff.conf> like
+the following:
+
+ cycbuff:NAME:/path/to/buffer:SIZE
+
+NAME must be unique and must be at most seven characters long. Something
+simple like "BUFF00", "BUFF01", etc. is a decent choice, or you may want
+to use something that includes the SCSI target and slice number of the
+partition. SIZE is the buffer size in kilobytes (if you're trying to stay
+under 2 GB, keep your sizes below C<2097152>).
+
+Now, you need to tell INN how to group your cycbuffs into metacycbuffs.
+This is similar to creating cycbuff entries:
+
+ metacycbuff:BUFFNAME:CYCBUFF,CYCBUFF,CYCBUFF
+
+BUFFNAME is the name of the metacycbuff and must be unique and at most
+eight characters long. These should be a bit more meaningful than the
+cycbuff names since they will be used in other config files as well. Try
+to name them after what will be stored in them; for example, if this
+metacycbuff will hold alt.binaries postings, "BINARIES" would be a good
+choice. The last part of the entry is a comma-separated list of all of
+the cycbuffs that should be used to build this metacycbuff. Each cycbuff
+should only appear in one metacycbuff line, and all metacycbuff lines must
+occur after all cycbuff lines in the file.
+
+If you want INN to fill each cycbuff before moving on to the next one
+rather than writing to them round-robin, add C<:SEQUENTIAL> to the end of
+the metacycbuff line. This may give noticeably better performance when
+using multiple cycbuffs on the same spindle (such as partitions or slices
+of a larger disk), but will probably give worse performance if your
+cycbuffs are spread out across a lot of spindles.
+
+By default, CNFS data is flushed to disk every 25 articles. If you're
+running a small server with a light article load, this could mean losing
+quite a few articles in a crash. You can change this interval by adding a
+cycbuffupdate line to your F<cycbuff.conf> file; see cycbuff.conf(5) for
+more details.
+
+Finally, you have to create the cycbuffs. See L<Creating the Article
+Spool> for more information on how to do that.
+
+=head2 storage.conf
+
+F<storage.conf> determines where incoming articles will be stored (what
+storage method, and in the case of CNFS, what metacycbuff). Each entry in
+the file defines a storage class for articles. The first matching storage
+class is used to store the article; if no storage class matches, INN will
+reject that article. (This is almost never what you want, so make sure
+this file ends in a catch-all entry that will match everything.)
+
+A storage class definition looks like this:
+
+ method <methodname> {
+ newsgroups: <wildmat>
+ class: <storage_class>
+ size: <minsize>[,<maxsize>]
+ expires: <mintime>[,<maxtime>]
+ options: <options>
+ }
+
+<methodname> is the name of the storage method to use to store articles in
+this class ("cnfs", "timehash", "timecaf", "tradspool", or the special
+method "trash" that accepts the article and throws it away).
+
+The first parameter is a wildmat pattern in the same format used by the
+newsfeeds(5) file, and determines what newsgroups are accepted by this
+storage class.
+
+The second parameter is a unique number identifying this storage class and
+should be between 0 and 255. It can be used to control article
+expiration, and for timehash and timecaf will set the top-level directory
+in which articles accepted by this storage class are stored. The easiest
+way to deal with this parameter is to just number all storage classes in
+F<storage.conf> sequentially. The assignment of a particular number to a
+storage class is arbitrary but I<permanent> (since it is used in storage
+tokens).
+
+The third parameter can be used to accept only articles in a certain size
+range into this storage class. A <maxsize> of C<0> (or a missing
+<maxsize>) means no upper limit (and of course a <minsize> of C<0> would
+mean no lower limit, because all articles are more than zero bytes long).
+If you don't want to limit the size of articles accepted by this storage
+class, leave this parameter out entirely.
+
+The fourth parameter you probably don't want to use; it lets you assign
+storage classes based on the Expires: header of incoming articles. The
+exact details are in storage.conf(5). It's very easy to use this
+parameter incorrectly; leave it out entirely unless you've read the man
+page and know what you're doing.
+
+The fifth parameter is the options parameter. Currently only CNFS uses
+this field; it should contain the name of the metacycbuff used to store
+articles in this storage class.
+
+If you're using CNFS exclusively, just create one storage class for each
+metacycbuff that you have defined in F<cycbuff.conf> and set the
+newsgroups pattern according to what newsgroups should be stored in that
+buffer.
+
+If you're using timehash or timecaf, the storage class IDs are used to
+store articles in separate directory trees, which you can take advantage
+of to put particular storage classes on different disks. Also, currently
+storage class is the only way to specify expiration time, so you will need
+to divide up your newsgroups based on how long you want to retain articles
+in those groups and create a storage class for each such collection of
+newsgroups. Make note of the storage class IDs you assign as they will be
+needed when you edit F<expire.ctl> a bit later.
+
+=head2 expire.ctl
+
+F<expire.ctl> sets the expiration policy for articles stored on the
+server. Be careful, since the default configuration will expire most
+articles after 10 days; in most circumstances this deletion is
+I<permanent>, so read this whole section carefully if you want to keep
+local hierarchies forever. (See archive(8) for a way to automate backups
+of important articles.)
+
+Only one entry is required for all storage classes; it looks like:
+
+ /remember/:10
+
+This entry says how long to keep the Message-IDs for articles that have
+already expired in the history file so that the server doesn't accept them
+again. Occasionally, fairly old articles will get regurgitated somewhere
+and offered to you again, so even after you've expired articles from your
+spool, you want to keep them around in your history file for a little
+while to ensure you don't get duplicates.
+
+INN will reject any articles more than a certain number of days old (the
+I<artcutoff> parameter in F<inn.conf>, defaulting to C<10>); the number on
+the C</remember/> line should match that.
+
+CNFS makes no further use of F<expire.ctl>, since articles stored in CNFS
+buffers expire automatically when the buffer runs out of free space (but
+see the C<-N> option in expireover(8) if you really want to expire them
+earlier). For other storage methods, there are two different syntaxes of
+this file, depending on I<groupbaseexpiry> in F<inn.conf>. If it is set
+to false, F<expire.ctl> takes entries of the form:
+
+ <storage_class>:<keep>:<default>:<purge>
+
+<storage_class> is the number assigned to a storage class in
+F<storage.conf>. <default> is the number of days to keep normal articles
+in that storage class (decimal values are allowed). For articles that
+don't have an Expires: header, those are the only two values that matter.
+For articles with an Expires: header, the other two values come into play;
+the date given in the Expires: header of an article will be honored,
+subject to the contraints set by <keep> and <purge>. All articles in this
+storage class will be kept for at least <keep> days, regardless of their
+Expires: headers, and all articles in this storage class will be expired
+after <purge> days, even if their Expires: headers specify a longer life.
+
+All three of these fields can also contain the special keyword C<never>.
+If <default> is C<never>, only articles with explicit Expires: headers
+will ever be expired. If <keep> is C<never>, articles with explicit
+Expires: headers will be kept forever. Setting <purge> to C<never> says
+to honor Expires: headers even if they specify dates far into the future.
+(Note that if <keep> is set to C<never>, all articles with Expires:
+headers are kept forever and the value of <purge> is not used.)
+
+If the value of C<groupbaseexpiry> is true, F<expire.ctl> takes entries of
+the form:
+
+ <wildmat>:<flag>:<keep>:<default>:<purge>
+
+<wildmat> is a wildmat expression (C<!> and C<@> not permitted, and only a
+single expression, not a comma-separated set of them). Each expiration
+line applies to groups matching the wildmat expression. <flag> is C<M>
+for moderated groups, C<U> for unmoderated groups, and C<A> for groups
+with any moderation status; the line only matches groups with the
+indicated expiration status. All of the other fields have the same
+meaning as above.
+
+=head2 readers.conf
+
+Provided that I<noreader> is set to false in F<inn.conf>, any connection
+from a host that doesn't match an entry in F<incoming.conf> (as well as
+any connection from a host that does match such an entry, but has issued a
+MODE READER command) will be handed off to nnrpd(8), the part of INN that
+supports newsreading clients. nnrpd uses F<readers.conf> to determine
+whether a given connection is allowed to read news, and if so what
+newsgroups the client can read and post to.
+
+There are a variety of fairly complicated things that one can do with
+F<readers.conf>, things like run external authentication programs that can
+query RADIUS servers. See readers.conf(5) and the example file for all
+the gory details. Here's an example of probably the simplest reasonable
+configuration, one that only allows clients in the example.com domain to
+read from the server and allows any host in that domain to read and post
+to all groups:
+
+ auth "example.com" {
+ hosts: "example.com, *.example.com"
+ default: "<user>"
+ default-domain: "example.com"
+ }
+
+ access "all" {
+ users: "*@example.com"
+ newsgroups: "*"
+ }
+
+If you're running a server for one particular domain, want to allow all
+hosts within that domain to read and post to any group on the server, and
+want to deny access to anyone outside that domain, just use the above and
+change C<example.com> in the above to your domain and you're all set.
+Lots of examples of more complicated things are in the sample file.
+
+=head1 Creating the Article Spool (CNFS only)
+
+If you are using actual files as your CNFS buffers, you will need to
+pre-create those files, ensuring they're the right size. The easiest way
+to do this is with dd. For each cycbuff in F<cycbuff.conf>, create the
+buffer with the following commands (as the news user):
+
+ dd if=/dev/zero of=/path/to/buffer bs=1k count=BUFFERSIZE
+ chmod 664 /path/to/buffer
+
+Substitute the correct path to the buffer and the size of the buffer as
+specified in F<cycbuff.conf>. This will create a zero-filled file of the
+correct size; it may take a while, so be prepared to wait.
+
+Here's a command that will print out the dd(1) commands that you should
+run:
+
+ awk -F: \
+ '/^cy/ { printf "dd if=/dev/zero of=%s bs=1k count=%s\n", $3, $4 }' \
+ ~news/etc/cycbuff.conf
+
+If you are using block devices, you don't technically have to do anything
+at all (since INN is capable of using the devices in F</dev>), but you
+probably want to create special device files for those devices somewhere
+for INN's private use. It s more convenient to keep all of INN's stuff
+together, but more importantly, the device files used by INN really should
+be owned by the news user and group, and you may not want to do that with
+the files in F</dev>.
+
+To create the device files for INN, use mknod(8) with a type of C<b>,
+getting the major and minor device numbers from the existing devices in
+F</dev>. There's a small shell script in cycbuff.conf(5) that may help
+with this. Make sure to create the device files in the location INN
+expects them (specified in F<cycbuff.conf>).
+
+Solaris users please note: on Solaris, do not use block devices that
+include the first cylinder of the disk. Solaris doesn't protect the
+superblock from being overwritten by an application writing to block
+devices and includes it in the first cylinder of the disk, so unless you
+use a slice that starts with cylinder 1 instead of 0, INN will invalidate
+the partition table when it tries to initialize the cycbuff and all
+further accesses will fail until you repartition.
+
+=head1 Creating the Database Files
+
+At this point, you need to set up the news database directory
+(F<~news/db>). This directory will hold the active(5) file (the list of
+newsgroups you carry), the active.times(5) file (the creator and creation
+time of newsgroups created since the server was initialized), the
+newsgroups(5) file (descriptions for all the newsgroups you carry), and
+the history(5) file (a record of every article the server currently has or
+has seen in the past few days, used to decide whether to accept or refuse
+new incoming messages).
+
+Before starting to work on this, make sure you're logged on as the news
+user, since all of these files need to be owned by that user. This is a
+good policy to always follow; if you are doing any maintenance work on
+your news server, log on as the news user. Don't do maintenance work as
+root. Also make sure that F<~news/bin> is in the default path of the news
+user (and while you're at it, make sure F<~news/man> is in the default
+MANPATH) so that you can run INN maintenance commands without having to
+type the full path.
+
+If you already have a server set up (if you're upgrading, or setting up a
+new server based on an existing server), copy F<active> and F<newsgroups>
+from that server into F<~news/db>. Otherwise, you'll need to figure out
+what newsgroups you want to carry and create new active and newsgroups
+files for them. If you plan to carry a full feed, or something close to
+that, go to <ftp://ftp.isc.org/pub/usenet/CONFIG/> and download F<active>
+and F<newsgroups> from there; that will start you off with reasonably
+complete files. If you plan to only carry a small set of groups, the
+default minimal F<active> file installed by INN is a good place to start;
+you can create additional groups after the server is running by using
+C<ctlinnd newgroup>. (Another option is to use actsync(8) to synchronize
+your newsgroup list to that of another server.)
+
+C<control> and C<junk> must exist as newsgroups in your active file for
+INN to start, and creating pseudogroups for the major types of control
+messages is strongly encouraged for all servers that aren't standalone.
+If you don't want these groups to be visible to clients, do I<not> delete
+them; simply hide them in F<readers.conf>. C<to> must also exist as a
+newsgroup if you have mergetogroups set in F<inn.conf>.
+
+Next, you need to create an empty history database. To do this, type:
+
+ cd ~news/db
+ touch history
+ makedbz -i
+
+When it finishes, rename the files it created to remove the C<.n> in the
+file names and then make sure the file permissions are correct on all the
+files you've just created:
+
+ chmod 644 *
+
+Your news database files are now ready to go.
+
+=head1 Configuring syslog
+
+While some logs are handled internally, INN also logs a wide variety of
+information via syslog. INN's nightly report programs know how to roll
+and summarize those syslog log files, but when you first install INN you
+need to set them up.
+
+If your system understands the C<news> syslog facility, INN will use it;
+otherwise, it will log to C<local1>. Nearly every modern system has a
+C<news> syslog facility so you can safely assume that yours does, but if
+in doubt take a look at the output from running C<configure>. You should
+see a line that looks like:
+
+ checking log level for news... LOG_NEWS
+
+If that says LOG_LOCAL1 instead, change the below instructions to use
+C<local1> instead of C<news>.
+
+Edit F</etc/syslog.conf> on your system and add lines that look like the
+following:
+
+ news.crit /usr/local/news/log/news.crit
+ news.err /usr/local/news/log/news.err
+ news.notice /usr/local/news/log/news.notice
+
+(Change the path names as necessary if you installed INN in a different
+location than F</usr/local/news>.) These lines I<must> be tab-delimited,
+so don't copy and paste from these instructions. Type it in by hand and
+make sure you use a tab, or you'll get mysterious failures. You'll also
+want to make sure that news log messages don't fill your other log files
+(INN generates a lot of log traffic); so for every entry in
+F</etc/syslog.conf> that starts with C<*>, add C<;news.none> to the end of
+the first column. For example, if you have a line like:
+
+ *.err /dev/console
+
+change it to:
+
+ *.err;news.none /dev/console
+
+(You can choose not to do this for the higher priority log messages, if
+you want to make sure they go to your normal high-priority log files as
+well as INN's. Don't bother with anything lower priority than C<crit>,
+though. C<news.err> isn't interesting enough to want to see all the
+time.) Now, make sure that the news log files exist; syslog generally
+won't create files automatically. Enter the following commands:
+
+ touch /usr/local/news/log/news.crit
+ touch /usr/local/news/log/news.err
+ touch /usr/local/news/log/news.notice
+ chown news /usr/local/news/log/news.*
+ chgrp news /usr/local/news/log/news.*
+
+(again adjusting the paths if necessary for your installation). Finally,
+send a HUP signal to syslogd to make it re-read its configuration file.
+
+=head1 Setting Up the Cron Jobs
+
+INN requires a special cron job to be set up on your system to run
+news.daily(8) which performs daily server maintenance tasks such as
+article expiration and the processing and rotation of the server logs.
+Since it will slow the server down while it is running, it should be run
+during periods of low server usage, such as in the middle of the night.
+To run it at 3am, for example, add the following entry to the news user's
+crontab file:
+
+ 0 3 * * * /usr/local/news/bin/news.daily expireover lowmark
+
+or, if your system does not have per-user crontabs, put the following line
+into your system crontab instead:
+
+ 0 3 * * * su -c "/usr/local/news/bin/news.daily expireover lowmark" news
+
+If you're using any non-CNFS storage methods, add C<delayrm> to the above
+option list for news.daily.
+
+The news user obviously must be able to run cron jobs. On Solaris, this
+means that it must have a valid F</etc/shadow> entry and must not be
+locked (although it may be a non-login account). There may be similar
+restrictions with other operating systems.
+
+If you use the batching method to send news, also set up a cron job to run
+nntpsend(8) every ten minutes. nntpsend will run innxmit for all
+non-empty pending batch files to send pending news to your peers. That
+cron entry should look something like:
+
+ 0,10,20,30,40,50 * * * * /usr/local/news/bin/nntpsend
+
+The pathnames and user ID used above are the installation defaults; change
+them to match your installation if you used something other than the
+defaults.
+
+The parameters passed to news.daily in the above example are the most
+common (and usually the most efficient) ones to use. More information on
+what these parameters do can be found in the news.daily(8) man page.
+
+=head1 File Descriptor Limits
+
+INN likes to use a lot of file descriptors, particularly if you have a lot
+of peers. Depending on what your system defaults are, you may need to
+make sure the default limit is increased for INN (particularly for innd
+and innfeed). This is vital on Solaris, which defaults (at least as of
+2.6) to an absurdly low limit of 64 file descriptors per process.
+
+One way to increase the number of file descriptors is to set
+I<rlimitnofile> in F<inn.conf> to a higher value. This will cause both
+startinnfeed and inndstart (the setuid root wrapper scripts that start
+innfeed and innd, respectively) to increase the file descriptor limits
+before they run the regular INN programs. Note, however, that INN won't
+be able to increase the limits above the hard limits set by your operating
+system; on some systems, that hard limit is normally 256 file descriptors
+(Linux, for example). On others, like Solaris, it's 1024. Increasing the
+limit beyond that value may require serious system configuration work.
+(On some operating systems, it requires patching and recompiling the
+kernel. On Solaris it can be changed in F</etc/system>, but for 2.6 or
+earlier the limit cannot be increased beyond 1024 without breaking
+select(2) and thereby breaking all of INN. For current versions of Linux,
+you may be able to change the maximum by writing to
+F</proc/sys/fs/file-max>.)
+
+256 file descriptors will probably be enough for all but the largest
+sites. There is no harm in setting the limits higher than you actually
+need (provided they're set to something lower than or equal to your system
+hard limit). C<256> is therefore a reasonable value to try.
+
+If you're installing INN on a Solaris system, particularly if you're
+installing it on a dedicated news server machine, it may be easier to just
+increase the default file descriptor limit across the board for all
+processes. You can do that by putting the line:
+
+ set rlim_fd_cur = 256
+
+in F</etc/system> and rebooting. You can increase it all the way to
+C<1024> (and may need to if you have a particularly large site), but that
+can cause RPC and some stdio applications to break. It therefore probably
+isn't a good idea on a machine that isn't dedicated to INN.
+
+=head1 Starting and Stopping the System
+
+INN is started via the shell script F<rc.news>. This must be run as the
+news user and not as root. To start INN on system boot, you therefore
+want to put something like:
+
+ su - news -c /usr/local/news/bin/rc.news
+
+in the system boot scripts. If innd is stopped or killed, you can restart
+it by running rc.news by hand as the news user.
+
+The rc.news script may also be used to shut down INN, with the C<stop>
+option:
+
+ su - news -c '/usr/local/news/bin/rc.news stop'
+
+In the F<contrib> directory of this source tree is a sample init script
+for people using System V-style init.d directories.
+
+=head1 Processing Newsgroup Control Messages
+
+Control messages are specially-formatted messages that tell news servers
+to take various actions. Cancels (commands to delete messages) are
+handled internally by INN, and all other control messages are processed by
+controlchan. controlchan should be run out of F<newsfeeds> if you want
+your news server to process any control messages; see L<Configuring INN>
+for specific instructions.
+
+The actions of controlchan are determined by F<control.ctl>, which lists
+who can perform what actions. The primary control messages to be
+concerned with are C<newgroup> (to create a newsgroup), C<rmgroup> (to
+remove a newsgroup), and C<checkgroups> (to compare the list of groups
+carried in a hierarchy to a canonical list). INN comes with a
+F<control.ctl> file that processes control messages in most major public
+hierarchies; if you don't want to act on all those control messages, you
+should remove from that file all entries for hierarchies you don't want to
+carry.
+
+You can tell INN to just authenticate control messages based on the From
+header of the message, but this is obviously perilous and control messages
+are widely forged. Many hierarchies sign all of their control messages
+with PGP, allowing news servers to verify their authenticity, and checking
+those signatures for hierarchies that use them is highly recommended.
+controlchan knows how to do this (using pgpverify) without additional
+configuration, but you do have to provide it with a public key ring
+containing the public keys of all of the hierarchy administrators whose
+control messages you want to check.
+
+INN expects the public key ring to either be in the default location for a
+PGP public key ring for the news user (generally ~news/.gnupg for GnuPG
+and ~news/.pgp for old PGP implementations), or in pathetc/pgp
+(/usr/local/news/etc/pgp by default). The latter is the recommended path.
+To add a key to that key ring, use:
+
+ gpg --import --homedir=/usr/local/news/etc/pgp <file>
+
+where <file> is a file containing the hierarchy key. Change the homedir
+setting to point to pathetc/pgp if you have INN installed in a non-default
+location. If you're using the old-style PGP program, an equivalent
+command is:
+
+ env PGPPATH=/usr/local/news/etc/pgp pgp <file>
+
+You can safely answer "no" to questions about whether you want to sign,
+trust, or certify keys.
+
+The URLs from which you can get hierarchy keys are noted in comments in
+F<control.ctl>. L<ftp://ftp.isc.org/pub/pgpcontrol/PGPKEYS> tries to
+collect the major hierarchy keys.
+
+If you are using GnuPG, please note that the first user ID on the key will
+be the one that's used by INN for verification and must match the key
+listed in F<control.ctl>. If a hierarchy key has multiple user IDs, you
+may have to remove all the user IDs except the one that matches the
+F<control.ctl> entry using C<gpg --edit-key> and the C<delkey> command.
--- /dev/null
+=head1 NAME
+
+libauth - routines for writing nnrpd resolvers and authenticators
+
+=head1 SYNOPSIS
+
+ #include "libauth.h"
+
+ struct res_info {
+ struct sockaddr *client;
+ struct sockaddr *local;
+ char *clienthostname;
+ };
+
+ struct auth_info {
+ char *username;
+ char *password;
+ };
+
+ struct auth_info *get_auth_info(FILE *);
+ struct res_info *get_res_info (FILE *);
+
+ void free_auth_info(struct auth_info*);
+ void free_res_info (struct res_info*);
+
+=head1 DESCRIPTION
+
+These functions provide a convenient C frontend to the nnrpd external
+authentication interface documented in F<doc/external-auth>. Use of
+this library is B<not> required; in particular, external resolvers and
+authenticators written in languages other than C will need to implement
+the necessary functionality themselves.
+
+The get_auth_info() and get_res_info() functions allocate sufficient
+memory for a B<struct auth_info> or B<struct res_info> and any necessary
+fields, and return a pointer to the struct with the fields filled in
+from information supplied by nnrpd (the B<FILE*> parameter generally
+should be C<stdin>). Both functions return NULL on error. The caller
+is responsible for deallocating the memory by using the functions below.
+
+The string fields of both structs are straightforward. The B<client>
+and B<local> fields of B<struct res_info> actually point to instances of
+B<struct sockaddr_in> (or B<struct sockaddr_in6> if IPv6 support is
+compiled in).
+
+The free_auth_info() and free_res_info() functions free the struct
+passed in as argument and all necessary fields.
+
+=head1 BUGS
+
+In many cases, nnrpd provides more information than is normally useful
+(for example, even when calling an authenticator, the resolver
+information is often provided.) On the other hand, in certain cases it
+provides less information than might be expected (for example, if nnrpd
+is reading from stdin rather than a socket). The implementation is
+capable of handling at least the first of these issues, but that
+functionality is not exposed in the interface.
+
+At present, F<libauth.h> and its implementation are located in
+F<authprogs/>; perhaps they should be moved to F<include/> and F<lib/>,
+respectively?
+
+=head1 HISTORY
+
+Written by Jeffrey M. Vinocur <jeff@litech.org> for InterNetNews.
+
+$Id: libauth.pod 5988 2002-12-12 23:02:14Z vinocur $
+
+=head1 SEE ALSO
+
+nnrpd(8), readers.conf(5), F<doc/external-auth>
+
+=cut
--- /dev/null
+=head1 NAME
+
+his - routines for managing INN history
+
+=head1 SYNOPSIS
+
+B<#include E<lt>inn/history.hE<gt>>
+
+B<struct history;>
+
+B<struct histstats {>
+B< int hitpos;>
+B< int hitneg;>
+B< int misses;>
+B< int dne;>
+B<};>
+
+B<#define HIS_RDONLY ...>
+B<#define HIS_RDWR ...>
+B<#define HIS_CREAT ...>
+B<#define HIS_ONDISK ...>
+B<#define HIS_INCORE ...>
+B<#define HIS_MMAP ...>
+
+B<enum {>
+B< HISCTLG_PATH,>
+B< HISCTLS_PATH,>
+B< HISCTLS_SYNCCOUNT,>
+B< HISCTLS_NPAIRS,>
+B< HISCTLS_IGNOREOLD,>
+B< HISCTLS_STATINTERVAL>
+B<};>
+
+B<struct history *HISopen(const char *>I<path>B<, const char *>I<method>B<, int >I<flags>B<);>
+
+B<bool HISclose(struct history *>I<history>B<);>
+
+B<bool HISsync(struct history *>I<history>B<);>
+
+B<void HISsetcache(struct history *>I<history>B<, size_t >I<size>B<);>
+
+B<bool HISlookup(struct history *>I<history>B<, const char *>I<key>B<, time_t *>I<arrived>B<, time_t *>I<posted>B<, time_t *>I<expires>B<, TOKEN *>I<token>B<);>
+
+B<bool HIScheck(struct history *>I<history>B<, const char *>I<key>B<);>
+
+B<bool HISwrite(struct history *>I<history>B<, const char *>I<key>B<, time_t >I<arrived>B<, time_t >I<posted>B<, time_t >I<expires>B<, const TOKEN *>I<token>B<);>
+
+B<bool HISremember(struct history *>I<history>B<, const char *>I<key>B<, time_t >I<arrived>B<);>
+
+B<bool HISreplace(struct history *>I<history>B<, const char *>I<key>B<, time_t >I<arrived>B<, time_t >I<posted>B<, time_t >I<expires>B<, const TOKEN *>I<token>B<);>
+
+B<bool HISexpire(struct history *>I<history>B<, const char *>I<path>B<, const char *>I<reason>B<, bool >I<writing>B<, void *>I<cookie>B<, time_t >I<threshold>B<, bool (*>I<exists>B<)(void *cookie, time_t arrived, time_t posted, time_t expires, const TOKEN *token));>
+
+B<bool HISwalk(struct history *>I<history>B<, const char *>I<reason>B<, void *>I<cookie>B<, bool (*>I<callback>B<)(void *cookie, time_t arrived, time_t posted, time_t expires, const TOKEN *token));>
+
+B<struct histstats HISstats(struct history *>I<history>B<);>
+
+B<const char *HISerror(struct history *>I<history>B<);>
+
+B<bool HISctl(struct history *>I<history>B<, int >I<request>B<, void *>I<val>B<);>
+
+=head1 DESCRIPTION
+
+These functions provide provide access to the INN history
+database. They maintain key/value pairs in an opaque database whilst
+providing for expiry of outdated information.
+
+The history structure is an opaque handle returned from HISopen.
+
+The B<HISopen> function opens the history file designated by I<path>
+using the mode I<flags> using the specified I<method>. I<flags> may be
+B<HIS_RDONLY> to indicate that read-only access to the history
+database is desired, or B<HIS_RDWR> for read/write access. History
+methods are defined at build time; the history method currently
+available is "hisv6". On success a newly initialised history handle is
+returned, or B<NULL> on failure.
+
+B<HIS_ONDISK>, B<HIS_INCORE> and B<HIS_MMAP> may be logically ORed
+into I<flags> to provide a hint to the underlying history manager as
+to how it should handle its data files; B<HIS_ONDISK> indicates that
+the caller would like as much of the data to be kept on disk (and out
+of memory), B<HIS_INCORE> indicates that the data files should be kept
+in main memory where possible and B<HIS_MMAP> that the files should be
+mmap()ed into the processes address space. B<HIS_INCORE> is typically
+used where a mass rebuild of the history database is being performed;
+the underlying history manager may assume that the caller will call
+B<HISsync>() to sync the data files to disk.
+
+The B<HIS_CREAT> flag indicates that the history database should be
+initialised as new; if any options which affect creation of the
+database need to be set an anonymous history handle should be created
+by calling B<HISopen> with I<path> set to B<NULL>, any options set
+using B<HISctl>, then the database opened by calling B<HISctl> with
+B<HISCTLS_PATH>.
+
+The B<HISclose> function closes the handle I<history> and deallocates
+any resources associated with it. It returns B<false> on failure or
+B<true> on success.
+
+The B<HISsync> function synchronises any outstanding transactions
+associated with I<history> to disk.
+
+B<HISsetcache> associates a cache used for speeding up HIScheck with
+I<history>. The cache will occupy approximately I<size> bytes.
+
+B<HISlookup> retrieves a token from I<history> based on the passed
+I<key> (normally the Message-ID). If no entry with an associated token
+can be found, B<HISlookup> will return B<false>. If a token is found
+I<arrived>, I<expires>, and I<posted> are filled in with the message
+arrival, expiry, and posting times respectively (or zero, if the time
+component is not available), in addition to I<token> being set to the
+retrieved token and a function return value of B<true>. Any of
+arrived, expires, posted, or token may be B<NULL> in which case that
+component is not returned to the caller, without affecting the return
+value.
+
+B<HIScheck> checks the database I<history> for I<key> (normally the
+Message-ID); if I<key> has previously been set via B<HISwrite>,
+B<HIScheck> returns B<true>, else B<false>.
+
+B<HISwrite> writes a new entry to the database I<history> associated
+with I<key>. I<arrived>, I<posted>, and I<expired> specify the arrival,
+posting, and expiry time respectively; I<posted> and I<expired> may be
+specifed as <= 0 in which case that component shall be treated as
+absent in the database. I<token> is associated with the specified
+I<key>. B<HISwrite> returns B<true> on success, or B<false> on
+failure. The behaviour when I<key> is not unique with respect to the
+existing entries in I<history> is unspecified.
+
+B<HISremember> writes a new entry to the database I<history>
+associated with I<key>, merely remembering that this I<key> has been
+seen, together with its arrival time I<arrived>. B<HISremember>
+returns B<true> on success, or B<false> on failure. The behaviour when
+I<key> is not unique with respect to the existing entries in
+I<history> is unspecified.
+
+B<HISreplace> replaces an existing entry in the database I<history>,
+associated with I<key>. I<arrived>, I<posted>, I<expired> specify the
+arrival, posting and expiry time respectively; I<posted> and
+I<expired> may be specifed as <= 0 in which case that component shall
+be treated as absent in the database. I<token> is associated with the
+specified I<key>; if B<NULL> then the history database merely
+remembers that this I<key> has been seen, together with its arrival
+time. B<HISreplace> returns B<true> on success, or B<false> on
+failure.
+
+B<HISexpire> expires the history database associated with I<history>,
+creating a new, replacement, database in the same location if I<path>
+is B<NULL>, or in I<path> if not B<NULL>; if I<path> is not B<NULL>
+then the replacement of the old history database with the new one is
+assumed to be performed out of band by the caller. The I<writing> flag
+is normally passed as B<true>, if you wish to inhibit writing of the
+new database (and so merely see the callbacks), I<writing> may be set
+B<false>.
+
+If the underlying history mechanism needs to pause the server, the
+I<reason> string is used as the argument to the `ctlinnd pause'
+command, and as such the server should be reserved by the caller prior
+to calling B<HISexpire>; if the caller wishes to inhibit pausing of
+the server, passing B<NULL> will achieve this. If I<reason> is not
+B<NULL>, then on successful return from B<HISexpire> the server will
+be left paused and the caller should unpause it.
+
+The history database is scanned and entries with an associated storage
+token are passed to the discrimination function I<exists>.
+
+If I<exists>() returns B<false> it indicates that stored entity
+associated with token is no longer available (or no longer required),
+and therefore the associated history entry may be expired once it
+meets the I<threshold> constraint. If I<exists>() returns B<true> the
+entry is kept as-is in the newly expired history database.
+
+The I<exists> function is passed the arrival, posting and expiry
+times, in addition to the token associated with the entry. Note that
+posting and/or expiry may be zero, but that the token will never be
+B<NULL> (such entries are handled solely via the threshold
+mechanism). The storage token passed to the discrimination function
+may updated if required (for example, as might be needed by a
+hierachical storage management implementation).
+
+Entries in the database with an arrival time less than I<threshold>
+with no token associated with them are deleted from the database.
+
+The parameter I<cookie> is passed to the discrimination function, and
+may be used for any purpose required by the caller.
+
+If the discrimination function attempts to access the underlying
+database (for read or write) during the callback, the behaviour is
+unspecified.
+
+B<HISwalk> provides an iteration function for the specified I<history>
+database. For every entry in the history database, I<callback> is
+invoked, passing the I<cookie>, arrival, posting, and expiry times, in
+addition to the token associated with the entry. If the I<callback>()
+returns B<false> the iteration is aborted and B<HISwalk> returns
+B<false> to the caller.
+
+To process the entire database in the presence of a running server,
+I<reason> may be passed; if this argument is not B<NULL>, it is used
+as an an argument to the `ctlinnd (reserve|pause|go)' commands. If
+I<reason> is B<NULL> and the server is running, the behaviour of
+B<HISwalk> is undefined.
+
+If the callback function attempts to access the underlying database
+during the callback, the behaviour is unspecified.
+
+B<HISstats> returns statistics on the history cache mechanism; given a
+handle I<history>, the return value is a I<struct histstats>
+detailing:
+
+=over 4
+
+=item C<hitpos>
+
+The number of times an item was found directly in the cache and known
+to exist in the underlying history manager.
+
+=item C<hitneg>
+
+The number of times an item was found directly in the cache and known
+not to exist in the underlying history manager.
+
+=item C<misses>
+
+The number of times an item was not found directly in the cache, but
+on retrieval from the underlying history manager was found to exist.
+
+=item C<dne>
+
+The number of times an item was not found directly in the cache, but
+on retrieval from the underlying history manager was found not to exist.
+
+=back
+
+Note that the history cache is only checked by B<HIScheck> and only
+affected by B<HIScheck>, B<HISwrite>, B<HISremember> and
+B<HISreplace>. Following a call to B<HISstats> the history statistics
+associated with I<history> are cleared.
+
+B<HISerror> returns a string describing the most recent error
+associated with I<history>; the format and content of these strings is
+history manager dependent. Note that on setting an error, the history
+API will call the B<warn> function from libinn(3).
+
+B<HISctl> provides a control interface to the underlying history
+manager. The I<request> argument determines the type of the request
+and the meaning of the I<val> argument. The values for I<request> are:
+
+=over 4
+
+=item C<HISCTLG_PATH> (const char **)
+
+Get the base file path which the history handle represents. I<val>
+should be a pointer to a location of type B<const char *>. The
+result must not later be passed to free(3).
+
+
+=item C<HISCTLS_PATH> (const char *)
+
+Set the base file path which this history handle should use; typically
+this is used after an anonymous handle has been created using
+B<HISopen(NULL, ...)>. I<val> should be a value of type B<const char
+*> and will be copied before being stored internally.
+
+=item C<HISCTLS_SYNCCOUNT> (size_t *)
+
+Set an upper bound on how many history operations may be pending in
+core before being synced to permanent storage; B<0> indicates
+unlimited. I<val> should be a pointer to a value of type B<size_t> and
+will not be modified by the call.
+
+=item C<HISCTLS_NPAIRS> (size_t *)
+
+Set a hint to the to the underlying history manager as to how many
+entries there are expected to be in the history database; B<0>
+indicates that an automatic or default sizing should be made. I<val>
+should be a pointer to a value of type B<size_t> and will not be
+modified by the call.
+
+=item C<HISCTLS_IGNOREOLD> (bool *)
+
+Instruct the underlying history manager to ignore existing database
+when creating new ones; typically this option may be set to B<true> if
+the administrator believes that the existing history database is
+corrupt and that ignoring it may help. I<val> should be a pointer to a
+value of type B<bool> and will not be modified by the call.
+
+=item C<HISCTLS_STATINTERVAL> (time_t *)
+
+For the history v6 and tagged hash managers, set the interval, in
+seconds, between stat(2)s of the history files checking for replaced
+files (as happens during expire); this option is typically used by
+nnrpd(8) like applications. I<val> should be a pointer to a value of
+type B<time_t> and will not be modified by the call.
+
+=head1 HISTORY
+
+Written by Alex Kiernan <alexk@demon.net> for InterNetNews 2.4.0.
+
+$Id: libinnhist.pod 5909 2002-12-03 05:17:18Z vinocur $
--- /dev/null
+=head1 NAME
+
+list - list routines
+
+=head1 SYNOPSIS
+
+B<#include E<lt>inn/list.hE<gt>>
+
+struct node {
+ struct node *succ;
+ struct node *pred;
+};
+
+struct list {
+ struct node *head;
+ struct node *tail;
+ struct node *tailpred;
+};
+
+B<void list_new(struct list *>I<list>B<);>
+
+B<struct node *list_addhead(struct list *>I<list>B<, struct node *>I<node>B<);>
+
+B<struct node *list_addtail(struct list *>I<list>B<, struct node *>I<node>B<);>
+
+B<struct node *list_head(struct list *>I<list>B<);>
+
+B<struct node *list_tail(struct list *>I<list>B<);>
+
+B<struct node *list_succ(struct node *>I<node>B<);>
+
+B<struct node *list_pred(struct node *>I<node>B<);>
+
+B<struct node *list_remhead(struct list *>I<list>B<);>
+
+B<struct node *list_remtail(struct list *>I<list>B<);>
+
+B<struct node *list_remove(struct node *>I<node>B<);>
+
+B<struct node *list_insert(struct list *>I<list>B<, struct node *>I<node>B<, struct node *>I<pred>B<);>
+
+B<bool list_isempty(struct list *>I<list>B<);>
+
+=head1 DESCRIPTION
+
+B<list_new> initialises the list header I<list> so as to create an
+empty list.
+
+B<list_addhead> adds I<node> to the head of I<list>, returning the node
+just added.
+
+B<list_addtail> adds I<node> to the tail of I<list>, returning the node
+just added.
+
+B<list_head> returns a pointer to the the node at the head of I<list>
+or B<NULL> if the list is empty.
+
+B<list_tail> returns a pointer to the the node at the tail of I<list>
+or B<NULL> if the list is empty.
+
+B<list_succ> returns the next (successor) node on the list after
+I<node> or B<NULL> if I<node> was the final node.
+
+B<list_pred> returns the previous (predecessor) node on the list before
+I<node> or B<NULL> if I<node> was the first node.
+
+B<list_remhead> removes the first node from I<list> and returns it to
+the caller. If the list is empty B<NULL> is returned.
+
+B<list_remtail> removes the last node from I<list> and returns it to
+the caller. If the list is empty B<NULL> is returned.
+
+B<list_remove> removes I<node> from the list it is on and returns it
+to the caller.
+
+B<list_insert> inserts I<node> onto I<list> after the node I<pred>. If
+I<pred> is B<NULL> then I<node> is added to the head of I<list>.
+
+=head1 HISTORY
+
+Written by Alex Kiernan <alex.kiernan@thus.net> for InterNetNews 2.4.0.
+
+$Id: list.pod 6168 2003-01-21 06:27:32Z alexk $
--- /dev/null
+=head1 NAME
+
+mailpost - Feed an e-mail message into a newsgroup
+
+=head1 SYNOPSIS
+
+B<mailpost> [B<-hn>] [B<-a> I<addr>] [B<-b> I<database>] [B<-c> I<wait-time>]
+[B<-d> I<distribution>] [B<-f> I<addr>] [B<-m> I<mailing-list>]
+[B<-o> I<output-command>] [B<-p> I<port>] [B<-r> I<addr>]
+[B<-x> I<header>[B<:>I<header>...]] I<newsgroups>
+
+=head1 DESCRIPTION
+
+The B<mailpost> program reads a properly formatted e-mail message from stdin
+and feeds it to B<inews> for posting to a news server. I<newsgroups> is a
+whitespace-separated list of group names to which to post the article
+(at least one newsgroup must be specified).
+
+Before feeding the article to B<inews>, it checks that the article has not
+been seen before, and it changes some headers (cleans up some address
+headers, removes X-Trace: and X-Complaints-To:, and puts C<X-> in front
+of unknown headers).
+
+If the article has been seen before (B<mailpost> records the Message-ID of
+each article it handles), then the article will be silently dropped. Other
+errors will cause the article to be mailed to the newsmaster (selected
+at configure time and defaulting to C<usenet>).
+
+Normally, B<mailpost> is run by sendmail(8) via an alias entry:
+
+ local-mail-wreck-bikes: "|<pathbin in inn.conf>/mailpost
+ -b /var/tmp -d local local.mail.rec.bicycles.racing"
+
+Instead of F</var/tmp>, the mail spool directory can be specified,
+or any other directory where the B<mailpost> process has write access.
+
+=head1 OPTIONS
+
+=over 4
+
+=item B<-a> I<addr>
+
+If the B<-a> flag is used, the value given is added to the article
+as an Approved: header.
+
+=item B<-b> I<database>
+
+If the B<-b> flag is used, then it defines the location of the database
+used to store the Message-IDs of articles sent on. This is to prevent articles
+looping around if a news-to-mail gateway sends them back here. This option may
+be required if the B<mailpost> process does not have write access to the news
+temporary directory. The default value is I<pathtmp> as set in F<inn.conf>.
+
+=item B<-c> I<wait-time>
+
+The B<-c> flag indicates a length of time to sleep before posting. If
+duplicate messages are received in this interval (by any instance of
+B<mailpost> using the same database), the article is only posted once, but
+with Newsgroups: header modified to crosspost the article to all indicated
+groups. The units for I<wait-time> are seconds; a reasonable value may be
+anywhere from tens to hundreds of seconds, or even higher, depending on how
+long mail can be delayed on its way to your system.
+
+=item B<-d> I<distribution>
+
+If the B<-d> flag is used, the value given is added to the article as a
+Distribution: header.
+
+=item B<-f> I<addr>
+
+The B<-f> flag is a synonym for the B<-r> flag.
+
+=item B<-h>
+
+Print usage information and exit.
+
+=item B<-m> I<mailing-list>
+
+If the B<-m> flag is used, the value given is added to the article in a
+Mailing-List: header, if such a header doesn't already exist.
+
+=item B<-n>
+
+If the B<-n> flag is used, neither an article is posted nor a mail is sent
+in case an error occurs. Everything is written to the standard output.
+
+=item B<-o> I<output-command>
+
+Specifies the program to which the resulting article processed by B<mailpost>
+should be sent. For debugging purpose, C<-o cat> can be used. The default
+value is C<inews -S -h>.
+
+=item B<-p> I<port>
+
+Specifies the port on which B<nnrpd> is listening, used for article posting.
+If given, B<-p> is passed along to B<inews>.
+
+=item B<-r> I<addr>
+
+A heuristic is used to determine a reasonable value for the Path: header.
+The B<-r> flag indicates what to use if no other value can be determined.
+
+=item B<-x> I<header>[B<:>I<header>...]
+
+A colon-separated list of additional headers which should be treated as
+known headers; these headers will be passed through to B<inews> without
+having C<X-> prepended.
+
+Known headers are:
+
+ Approved
+ Content-*
+ Date
+ Distribution
+ From
+ Mailing-List
+ Message-ID
+ MIME-*
+ References
+ Return-Path
+ Sender
+ Subject
+
+=back
+
+=head1 FILES
+
+=over 4
+
+=item I<pathbin>/mailpost
+
+The Perl script itself used to feed an e-mail message to a newsgroup.
+
+=item I<pathtmp>/mailpost-msgid.dir and I<pathtmp>/mailpost-msgid.pag
+
+The default database files which record previously seen Message-IDs.
+
+=back
+
+=head1 HISTORY
+
+Written by Paul Vixie long ago and then hacked up by James Brister for INN
+integration.
+
+$Id: mailpost.pod 7795 2008-04-26 08:28:08Z iulius $
+
+=head1 SEE ALSO
+
+active(5), inews(1), inn.conf(5), nnrpd(8), uwildmat(3).
+
+=cut
+
--- /dev/null
+=head1 NAME
+
+makehistory - Initialize or rebuild INN history database
+
+=head1 SYNOPSIS
+
+B<makehistory> [B<-abeFIOx>] [B<-f> I<filename>] [B<-l> I<count>]
+[B<-T> I<tmpdir>] [B<-s> I<size>]
+
+=head1 DESCRIPTION
+
+B<makehistory> rebuilds the history(5) text file, which contains a list of
+Message-IDs of articles already seen by the server. It can also be used
+to rebuild the overview database. Note that the dbz(3) indexes for the
+history file are rebuilt by makedbz(8), not by B<makehistory> as in
+earlier versions of INN.
+
+The default location of the history text file is I<pathdb>/history; to
+specify an alternate location, use the B<-f> flag.
+
+By default, B<makehistory> will scan the entire spool, using the storage
+manager, and write a history line for every article. To also generate
+overview information, use the B<-O> flag.
+
+WARNING: If you're trying to rebuild the overview database, be sure to
+stop innd(8) and delete or zero out the existing database before you start
+for the best results. An overview rebuild should not be done while the
+server is running. Unless the existing overview is deleted, you may end
+up with problems like out-of-order overview entries, excessively large
+overview buffers, and the like.
+
+If I<ovmethod> in F<inn.conf> is C<ovdb>, you must have the ovdb processes
+running while rebuilding overview. ovdb needs them available while
+writing overview entries. You can start them by hand separate from the
+rest of the server by running B<ovdb_init>; see ovdb_init(8) for more
+details.
+
+=head1 OPTIONS
+
+=over 4
+
+=item B<-a>
+
+Append to the history file rather than generating a new one. If you
+append to the main history file, make sure innd(8) is throttled or not
+running, or you can corrupt the history.
+
+=item B<-b>
+
+Delete any messages found in the spool that do not have valid Message-ID
+headers in them.
+
+=item B<-e>
+
+Compute Bytes headers which is used for overview data. This option is valid
+only if B<-O> flag is specified and F<overview.fmt> includes C<Bytes:>.
+
+=item B<-f> I<filename>
+
+Rather than writing directly to I<pathdb>/history, instead write to
+I<filename>.
+
+=item B<-F>
+
+Fork a separate process to flush overview data to disk rather than doing
+it directly. The advantage of this is that it allows B<makehistory> to
+continue to collect more data from the spool while the first batch of data
+is being written to the overview database. The disadvantage is that up to
+twice as much temporary disk space will be used for the generated overview
+data. This option only makes sense in combination with B<-O>. With
+C<buffindexed>, the C<overchan> program is invoked to write overview.
+
+=item B<-I>
+
+Don't store overview data for articles numbered lower than the lowest
+article number in F<active>. This is useful if there are for whatever
+reason old articles on disk that shouldn't be available to readers or put
+into the overview database.
+
+=item B<-l> I<count>
+
+This option specifies how many articles to process before writing the
+accumulated overview information out to the overview database. The
+default is C<10000>. Since overview write performance is faster with
+sorted data, each "batch" gets sorted. Increasing the batch size
+with this option may further improve write performance, at the cost
+of longer sort times. Also, temporary space will be needed to store
+the overview batches. At a rough estimate, about 300 * I<count> bytes
+of temporary space will be required (not counting temp files created
+by sort(1)). See the description of the B<-T> option for how to
+specify the temporary storage location. This option has no effect
+with C<buffindexed>, because C<buffindexed> does not need sorted
+overview and no batching is done.
+
+=item B<-s> I<size>
+
+Size the history database for approximately I<size> pairs. Accurately
+specifying the size is an optimization that will create a more
+efficient database. (The size should be the estimated eventual size
+of the F<history> file, typically the size of the old file, in lines.)
+
+=item B<-O>
+
+Create the overview database as well as the history file. Overview
+information is only required if the server supports readers; it is not
+needed for a transit-only server (see I<enableoverview> in inn.conf(5)).
+If you are using the C<buffindexed> overview storage method, erase all of
+your overview buffers before running B<makehistory> with B<-O>.
+
+=item B<-T> I<tmpdir>
+
+If B<-O> is given, B<makehistory> needs a location to write temporary
+overview data. By default, it uses I<pathtmp>, set in F<inn.conf>, but if
+this option is given, the provided I<tmpdir> is used instead. This is
+also used for temporary files created by sort(1) (which is invoked in the
+process of writing overview information since sorted overview information
+writes faster). By default, sort usually uses your system temporary
+directory; see the sort(1) man page on your system to be sure.
+
+=item B<-x>
+
+If this option is given, B<makehistory> won't write out history file
+entries. This is useful mostly for building overview without generating
+a new history file.
+
+=back
+
+=head1 EXAMPLES
+
+Here's a typical example of rebuilding the entire history and overview
+database, removing broken articles in the news spool. This uses the
+default temporary file locations and should be done while innd isn't
+running (or is throttled).
+
+ makehistory -b -f history.n -O -l 30000 -I
+
+This will rebuild the overview (if using C<buffindexed>, erase the
+existing overview buffers before running this command) and leave a new
+history file as C<history.n> in I<pathdb>. To preserve all of the history
+entries from the old history file that correspond to rejected articles or
+expired articles, follow the above command with:
+
+ cd /usr/local/news/db
+ awk 'NF == 2 { print }' < history >> history.n
+
+(replacing the path with your I<pathdb>, if it isn't the default). Then
+look over the new history file for problems and run:
+
+ makedbz -s `wc -l < history` -f history.n
+
+Then rename all of the files matching C<history.n.*> to C<history.*>,
+replacing the current history database and indexes. After that, it's safe
+to unthrottle innd.
+
+For a simpler example:
+
+ makehistory -b -f history.n -I -O
+
+will scan the spool, removing broken articles and generating history and
+overview entries for articles missing from history.
+
+To just rebuild overview:
+
+ makehistory -O -x -F
+
+=head1 FILES
+
+=over 4
+
+=item inn.conf
+
+Read for I<pathdb>, I<pathtmp>, and other settings.
+
+=item I<pathdb>/history
+
+This is the default output file for B<makehistory>.
+
+=item I<pathtmp>
+
+Where temporary files are written unless B<-T> is given.
+
+=back
+
+=head1 HISTORY
+
+Originally written by Rich $alz <rsalz@uunet.uu.net> for InterNetNews and
+updated by various other people since.
+
+$Id: makehistory.pod 6400 2003-07-12 19:26:58Z rra $
+
+=head1 SEE ALSO
+
+dbz(3), active(5), history(5), inn.conf(5), ctlinnd(8), innd(8),
+makedbz(8), ovdb_init(8), overview.fmt(5).
+
+=cut
--- /dev/null
+=head1 NAME
+
+motd.news - Message of the day information for readers
+
+=head1 DESCRIPTION
+
+This file, found in I<pathetc>/motd.news, contains local information for
+news readers in a free-form format. The entire file is returned verbatim
+to any client that issues the LIST MOTD command. This might be used for
+new information, notification of upcoming downtime, or similar purposes.
+
+Be aware that use of the LIST MOTD command is not widespread and most news
+clients will never ask for this file.
+
+If this file is missing, it is not an error. The server will just send
+the client an empty response.
+
+=head1 HISTORY
+
+Rewritten in POD by Russ Allbery <rra@stanford.edu> for InterNetNews.
+
+$Id: motd.news.pod 6574 2003-12-27 06:25:05Z rra $
+
+=head1 SEE ALSO
+
+inn.conf(5)
--- /dev/null
+=head1 Changes in 2.4.5
+
+=over 2
+
+=item *
+
+Fixed the "alarm signal" around C<SSL_read> in B<nnrpd>: it allows
+a proper disconnection of news clients which were previously hanging
+when posting an article through a SSL connection. Moreover, the
+I<clienttimeout> parameter now works on SSL connections. Thanks to
+Matija Nalis for the patch.
+
+=item *
+
+SO_KEEPALIVE is now implemented for SSL TCP connections on systems
+which support it, allowing system detection and closing the dead
+TCP SSL connections automatically after system-specified time. Thanks
+to Matija Nalis for the patch.
+
+=item *
+
+Fixed a segmentation fault when an article of a size greater than remaining
+stack is retrieved via SSL. Thanks to Chris Caputo for this patch.
+
+=item *
+
+Fixed a few segfaults and bugs which affected both Python B<innd> and B<nnrpd>
+hooks. They no longer check the existence of methods not used by the hooked
+script. An issue with Python exception handling was also fixed, as well as
+a segfault fixed by Russ Allbery which happened whenever one closes and then
+reopens Python in the same process. Julien Elie also fixed a bug when reloading
+Python filters (they were not always correctly reloaded) and a segfault when
+generating access groups with embedded Python filters for B<nnrpd>.
+Many thanks to David Hlacik for its bug reports.
+
+=item *
+
+The F<nnrpd.py> stub file in order to test Python B<nnrpd> hooks, as
+mentioned in their documentation, is now installed; only F<INN.py> was
+previously installed in I<pathfilter>. Also fixed a bug in F<INN.py>
+and add missing methods to it.
+
+=item *
+
+Fixed a long-standing bug in B<innreport> which prevented it from
+correctly reporting B<nnrpd> and B<innfeed> log messages.
+
+=item *
+
+Fixed a hang in Perl hooks on (at least) HP/PA since S<Perl 5.10>.
+
+=item *
+
+Fixed a compilation problem on some platforms because of AF_INET6 which
+was not inside a HAVE_INET6 block in B<innfeed>.
+
+=item *
+
+Fixed a bug in B<innfeed> which contained thrice the same IPs for each
+peer; it unnecessarily slowed the peer IP rotation for B<innfeed>. Thanks,
+S<D. Stussy>, for having seen that. Miquel van Smoorenburg provided the patch.
+
+=item *
+
+A new I<heavily> improved version of B<pullnews> is shipped with this
+INN release. This new version is provided by Geraint Edwards. He added
+no more than S<16 flags>, fixed some bugs and integrated the B<backupfeed>
+contrib script by Kai Henningsen, adding again S<6 other> flags. A
+long-standing but very minor bug in the B<-g> option was especially fixed
+and items from the to-do list implemented. Many thanks again to Geraint Edwards.
+
+=item *
+
+New headers are accessible through Perl and Python B<innd> filtering
+hooks. You will find the exact list in the INN Python Filtering and
+Authentication Hooks documentation (F<doc/hook-python>) and in Python
+samples. Thanks to Matija Nalis for this addition of new useful headers.
+
+=item *
+
+New samples for Python B<nnrpd> hooks are shipped with INN: F<nnrpd_access.py>
+for access control and F<nnrpd_dynamic.py> for dynamic access control. The
+F<nnrpd_auth.py> script is now only used for authorization control. See the
+F<readers.conf> man page for more information (especially the I<python_auth>,
+I<python_access> and I<python_dynamic> parameters). The documention about
+INN Python Filtering and Authentication Hooks has also been improved by
+Julien Elie.
+
+=back
+
+=head1 Changes in 2.4.4
+
+=over 2
+
+=item *
+
+Fixed incomplete checking of packet sizes in the B<ctlinnd> interface in
+the no-Unix-domain-sockets case. This is a potential buffer overflow in
+dead code since basically all systems INN builds on support Unix domain
+sockets these days. Also track the buffer size more correctly in the
+client side of this interface for the Unix domain socket case.
+
+=item *
+
+Group blocks in F<incoming.conf> are now correctly parsed and no longer
+cause segfaults when loading this file.
+
+=item *
+
+Fixed a problem with B<innfeed> continuously segfaulting on amd64 hardware
+(and possibly on lots of 64-bit platforms). Many thanks to Ollivier Robert
+for his patch and also to Kai Gallasch for having reported the problem and
+provided the FreeBSD server to debug it.
+
+=item *
+
+B<scanlogs> now rotates B<innfeed>'s log file, which prevents B<innfeed>
+from silently dying when its log file reaches S<2 GB>.
+
+=item *
+
+S<Perl 5.10> support has been added to INN thanks to Jakub Bogusz.
+
+=item *
+
+Some news clients hang when posting an article through a SSL connection:
+it seems that B<nnrpd>'s SSL routines make it wrongly wait for data
+completion. In order to fix the problem, the select() wait is now
+just bypassed. However, the IDLE timer stat is currently not collected
+for such connections. Thanks to Kachun Lee for this workaround.
+
+=item *
+
+Fixed a bug in the display of the used compressor (C<cunbatch> was used
+if arguments were passed to B<gzip> or B<bzip2>).
+
+=item *
+
+Fixed a bug in B<mailpost> and B<pullnews> which prevented useful error
+messages to be seen. Also add the B<-x> flag to B<pullnews> in order
+to insert Xref: headers in articles which lack one.
+
+=item *
+
+If compiling with S<Berkeley DB>, use its ndbm compatibility layer for
+B<ckpasswd> in preference to searching for a traditional dbm library.
+INN also supports S<Berkeley DB 4.4>, 4.5 and 4.6 thanks to Marco d'Itri.
+
+=item *
+
+B<ovdb_init> now properly closes stdin/out/err when it becomes a daemon.
+The issue was reported by Viktor Pilpenok and fixed by Marco d'Itri.
+
+=item *
+
+Added support for Diablo quickhash and hashfeed algorithms.
+It allows to distribute the messages among several peers (new B<Q> flag
+for F<newsfeeds>). Thanks to Miquel van Smoorenburg for this
+implementation in INN.
+
+=item *
+
+B<innd> now listen on separate sockets for IPv4 and IPv6 connections
+if the IPV6_V6ONLY socket option is available. There might also be
+operating systems that still have separate IPv4 and IPv6 TCP implementations,
+and advanced features like TCP SACK might not be available on v6 sockets.
+Thanks to Miquel van Smoorenburg for this patch.
+
+=item *
+
+The two configuration options I<bindaddress> and I<bindaddress6> can now
+be set on a per-peer basis for B<innfeed>. Setting I<bindaddress6>
+to C<none> tells B<innfeed> to never attempt an IPv6 connection to that
+host. Thanks to Miquel van Smoorenburg for this patch.
+
+=item *
+
+Added a I<nnrpdflags> parameter to F<inn.conf> (modeled on the concept of
+I<innflags>) to permit passing of command line arguments to instances of
+B<nnrpd> spawned from B<innd>.
+
+=item *
+
+A new F<inn.conf> parameter called I<pathcluster> has been added:
+it allows to append a common name to the Path: header
+on all incoming articles. I<pathhost> and I<pathalias> (if set)
+are still appended to the path as usual, but I<pathcluster>
+is always appended as the last element (e.g. on the leftmost
+side of the Path: header). Thanks to Miquel van Smoorenburg for
+this feature.
+
+=item *
+
+B<simpleftp> has been rewritten to use C<Net::FTP>. Indeed, F<ftp.pl>
+is no longer shipped with S<Perl 5> and the script did not work.
+
+=item *
+
+B<perl-nocem> will now check for a timeout and re-open the socket
+if required. Additionally, B<perl-nocem> will switch to
+cancel_ctlinnd in case cancel_nntp fails after sending
+the Message-ID. Thanks to Christoph Biedl for the patch. A more
+detailed documentation has also been written for perl-nocem(8).
+
+=item *
+
+The RADIUS configuration is now wrapped in a C<server {}> block in
+F<radius.conf>.
+
+=item *
+
+Checkgroups when there is nothing to change no longer result in sending
+a blank mail to administrators. Besides, no mail is sent by B<controlchan>
+for the creation of a newsgroup when the action is C<no change>.
+
+=item *
+
+Checkgroups are now properly propagated even though the news server
+does not carry the groups they are posted to.
+
+=item *
+
+B<controlchan> and B<docheckgroups> now handle wire format messages
+so that articles from the spool can be directly fed to them.
+
+=item *
+
+Newgroup control messages for existing groups now change their description.
+If a mail is sent to administrators, it reminds them to update their
+F<newsgroups> file. It also warns when there are missing or obsolete
+descriptions. Furthermore, the F<newsgroups> file is now written prettier
+(from one to three tabulations between the name of the group and its
+short description) and to.* groups cannot be created.
+
+=item *
+
+The sample F<control.ctl> file has been extensively updated.
+
+=item *
+
+Fixed empty LISTGROUP replies which were not terminated. Thanks to
+David Canzi for the patch.
+
+=item *
+
+In response to a LIST [file] command, if the file does not exist,
+we assume it is not maintained and return C<503> instead of C<215> and
+an empty file. Moreover, capability to LIST ACTIVE.TIMES for a wildmat
+pattern as its third argument has been added in order to select wanted
+newsgroups.
+
+=item *
+
+B<inews> now tries to authenticate if it does not receive a C<200> return
+code after MODE READER. Indeed, it might be able to post even with
+a C<201> return code and also with another codes like C<440> or C<480>.
+
+=item *
+
+If creating a new F<history> file, set the ownership and mode appropriately.
+B<inncheck> also expects fewer things to be private to the news user. Most
+of the configuration files will never contain private information like
+passwords.
+
+=item *
+
+Other minor bug fixes and documentation improvements.
+
+=back
+
+=head1 Changes in 2.4.3
+
+=over 2
+
+=item *
+
+Previous versions of INN had an optimization for handling XHDR Newsgroups
+that used the Xref: header from overview. While this does make the command
+much faster, it doesn't produce accurate results and breaks the NNTP
+protocol, so this optimization has been removed.
+
+=item *
+
+Fixed a bug in B<innd> that allowed it to accept articles with duplicated
+headers if the header occurred an odd number of times. Modified the
+programs for rebuilding overview to use the last Xref: header if there
+are multiple ones to avoid problems with spools that contain such invalid
+articles.
+
+=item *
+
+Fixed yet another problem with verifying that a user has permissions to
+approve posts to a moderated group. Thanks, Jens Schlegel.
+
+=item *
+
+Increase the send and receive buffer on the Unix domain socket used by
+B<ctlinnd>. This should allow longer replies (particularly for B<innstat>) on
+platforms with very low default Unix domain socket buffer sizes.
+
+=item *
+
+B<rnews>'s handling of articles with nul characters, NNTP errors, header
+problems, and deferrals has been significantly improved.
+
+=item *
+
+Thomas Parmelan added support to B<send-uucp> for specifying the funnel or
+exploder site to flush for feeds managed through one and fixed a problem
+with picking up old stranded work files.
+
+=item *
+
+Many other more minor bug fixes, optimization improvements, and
+documentation fixes.
+
+=back
+
+=head1 Changes in 2.4.2
+
+=over 2
+
+=item *
+
+INN is now licensed under a less restrictive license (about as minimally
+restrictive as possible shy of public domain), and the clause similar to
+the old BSD advertising clause has been dropped.
+
+=item *
+
+C<make install> and C<make update> now always install the newly built binaries,
+rather than only installing them if the modification times are newer.
+This is the behavior that people expect. C<make install> now also
+automatically builds a new (empty) history database if one doesn't already
+exist.
+
+=item *
+
+The embedded Tcl filter code has been disabled (and will be removed
+entirely in the next major release of INN). It hasn't worked for some
+time and causes B<innd> crashes if compiled in (even if not used). If
+someone wants to step forward and maintain it, I recommend starting from
+scratch and emulating the Perl and Python filters.
+
+=item *
+
+B<ctlinnd> should now successfully handle messages from INN up to the maximum
+allowable packet size in the protocol, fixing problems sites with many
+active peers were having with B<innstat> output.
+
+=item *
+
+Overview generation has been fixed in both B<makehistory> and B<innd> to follow
+the rules in the latest NNTP draft rather than just replacing special
+characters with spaces. This means that the unfolding of folded header
+lines will not introduce additional, incorrect whitespace in the overview
+data.
+
+=item *
+
+B<nnrpd> now uniformly responds with a C<480> or C<502> status code to attempts
+to read a newsgroup to which the user does not have access, depending on
+whether the user has authenticated. Previously, it returned a C<411> status
+code, claiming the group didn't exist, which confuses the reactive
+authentication capability of news readers.
+
+=item *
+
+If a user is not authorized to approve articles (using the C<A> I<access>
+control in F<readers.conf>), articles that include Approved: headers will be
+rejected even if posted to unmoderated groups. Some other site may
+consider that group to be moderated.
+
+=item *
+
+The configuration parser used for F<readers.conf> and others now correctly
+handles C<#> inside quoted strings and is more robust against unmatched
+double quotes.
+
+=item *
+
+Messages mailed to moderators had two spaces after the colons in the
+headers, rather than one. This bug has been fixed.
+
+=item *
+
+A bug that could cause heap corruption and random crashes in B<innd> if INN
+were compiled with Python support has been fixed.
+
+=item *
+
+Some problems with B<innd>'s tracking of article size and enforcement of the
+configured maximum article size have been fixed.
+
+=item *
+
+B<pgpverify> will now correctly verify signatures generated by GnuPG and
+better supports GnuPG as the PGP implementation.
+
+=item *
+
+INN's code should now be more 64-bit clean in its handling of size_t,
+pointer differences, and casting of pointers, correcting problems that
+showed up on 64-bit platforms like AMD64.
+
+=item *
+
+Improved the error reporting in the history database code, in B<inews>, in
+B<controlchan>, and in B<expire>.
+
+=item *
+
+Many other, more minor bugs have also been fixed.
+
+=back
+
+=head1 Changes in 2.4.1
+
+=over 2
+
+=item *
+
+SECURITY: Handle the special filing of control messages into per-type
+newsgroups more robustly. This closes a potentially exploitable buffer
+overflow. Thanks to Dan Riley for his excellent bug report.
+
+=item *
+
+Fixed article handling in B<innd> so that articles without a Path: header
+(arising from peers sending malformatted articles or injecting
+malformatted articles through rnews) would not cause B<innd> to crash. (This
+was not exploitable.)
+
+=item *
+
+Fixed a serious bug in XPAT handling, thanks to Tommy van Leeuwen.
+
+=item *
+
+C<configure> now looks for B<sendmail> only in F</usr/sbin> and F</usr/lib>, not on
+the user's path. This should reduce the need for B<--with-sendmail> if your
+preferred sendmail is in a standard location.
+
+=item *
+
+The robustness of the tradindexed overview method has been further
+increased, handling more edge cases arising from corrupted databases and
+oddly-named newsgroups.
+
+=item *
+
+B<innd> now never decreases the high water mark of a newsgroup when
+renumbering, which should help ameliorate overview and F<active> file
+synchronization problems.
+
+=item *
+
+Do not close and reopen the F<history> file on B<ctlinnd> reload when the server
+is paused or throttled. This was breaking B<ctlinnd> reload all during a
+server pause.
+
+=item *
+
+Various minor portability and compilation issues fixed. Substantial
+numbers of compiler warnings have been cleaned up, thanks largely to work
+by Ilya Kovalenko.
+
+=item *
+
+Multiple other more minor bugs have been fixed.
+
+=item *
+
+Documentation and man pages have been clarified and updated.
+
+=back
+
+=head1 Upgrading from 2.3 to 2.4
+
+The F<inn.conf> parser has changed between S<INN 2.3> and 2.4. Due to that
+change, options in F<inn.conf> that contain whitespace or a few other
+special characters must be quoted with double quotes, and empty parameters
+(parameters with no value) are not allowed. S<INN 2.4> comes with a script,
+B<innupgrade>, run automatically during C<make update>, that will attempt
+to fix any problems that it finds with your F<inn.conf> file, saving the
+original as F<inn.conf.OLD>.
+
+This change is the beginning of standardization of parsing and syntax
+across all of INN's configuration files.
+
+The history subsystem now has a standard API that allows other backends to
+be used. Because of this, you now need to specify the history method in
+F<inn.conf>. Adding:
+
+ hismethod: hisv6
+
+will tell INN to use the same history backend as was used in previous
+versions. B<innupgrade> should take care of this for you.
+
+ovdb is known to have some locking and timing issues related to how B<nnrpd>
+shuts down (or fails to shut down) the overview databases. If you have
+stability problems with ovdb, try setting I<readserver> to C<true> in
+F<ovdb.conf>. This will funnel all ovdb reads through a single process
+with a cleaner interface to the underlying S<Berkeley DB> database.
+
+If you use Perl authentication for B<nnrpd> (if I<nnrpdperlauth> in
+F<inn.conf> is C<true>), there have been major changes. See "Changes to
+Perl Authentication Support for nnrpd" in F<doc/hook-perl> for details.
+
+Similarly, if you use Python authentication for B<nnrpd> (if
+I<nnrpdpythonauth> in F<inn.conf> is C<true>), there have been major changes.
+See "Changes to Python Authentication and Access Control Support for
+nnrpd" in F<doc/hook-python> for details.
+
+If you use B<send-uucp>, it has been completely rewritten and now takes a
+configuration file to specify its behavior. See its man page for more
+information. If you use B<sendbatch>, it is no longer included in INN
+since the new B<send-uucp> can handle all of the same functionality.
+
+The wildmat API has been renamed (to uwildmat and friends; see uwildmat(3)
+for the interfaces) to distinguish it from Rich $alz's original version,
+since it now supports UTF-8. This may require changes in other software
+packages that link against INN's libraries.
+
+If you are upgrading from a version prior to S<INN 2.3>, see L<Upgrading
+from 2.2 to 2.3>.
+
+=head1 Changes in 2.4.0
+
+=over 2
+
+=item *
+
+IPv6 support has been added, disabled by default. If you have IPv6
+connectivity, build with B<--enable-ipv6> to try it. There are no known
+bugs, but please report any problems you find (or even successes, if you
+use an unusual platform). There are a few changes of interest; further
+information is available in F<doc/IPv6-info>.
+
+=item *
+
+The tradindexed overview method has been completely rewritten and should
+be considerably more robust in the face of system crashes. A new utility,
+B<tdx-util>, is provided to examine the contents of the overview database,
+repair inconsistencies, and rebuild the overview for particular groups
+from a tradspool news spool. See tdx-util(8) for more details.
+
+=item *
+
+The Perl and Python authentication hooks for readers have been extensively
+overhauled and integrated better with F<readers.conf>. See the Changes
+sections in F<doc/hook-perl> and F<doc/hook-python> for more details.
+
+=item *
+
+B<nnrpd> now optionally supports article injection via IHAVE, see
+readers.conf(5). Any articles injected this way must have Date, From,
+Message-ID, Newsgroups, Path, and Subject headers. X-Trace and
+X-Complaints-To headers will be added if the appropriate options are set
+in F<readers.conf>, but other headers will not be modified/inserted (e.g.
+NNTP-Posting-Host, NNTP-Posting-Date, Organization, Lines, Cc, Bcc, and To
+headers).
+
+=item *
+
+B<nnrpd> now handles arbitrarily long lines in POST and IHAVE; administrators
+who want to limit the length of lines in locally posted articles will need
+to add this to their local filters instead.
+
+=item *
+
+B<nnrpd> no longer handles the poorly-specified S<RFC 977> optional fourth
+argument to the NEWGROUPS command specifying the "distributions" that the
+command was supposed to apply to.
+
+Clients that use that argument will break. There are not believed to be
+any such clients, and it's easy enough to just filter the returned list of
+newsgroups (which is generally fairly short) to achieve the same results.
+
+=item *
+
+B<nnrpd> no longer accepts UTC as a synonym for GMT for NEWGROUPS or NEWNEWS.
+This usage was never portable, and was rejected by the NNTP working group.
+It is being removed now in the hope that it will be caught before anyone
+starts to rely on it.
+
+=item *
+
+B<innfeed> supports a new peer parameter, I<backlog-feed-first>, that if set
+to C<true> feeds any backlog to a peer before new articles, see
+innfeed.conf(5). When used in combination with I<max-connections> set to C<1>,
+this can be used to enforce in-order delivery of messages to a peer that
+is doing Xref slaving, avoiding cases where a higher-numbered message is
+received before a lower-numbered message in the same group.
+
+=item *
+
+Several other, more minor protocol issues have been fixed: connections
+rejected due to the connection rate limiting in B<innd> receive C<400> replies
+instead of C<504> or C<505>, and ARTICLE without an argument will always either
+retrieve the current article or return a C<423> error, never advance the
+current article number to the next valid article.
+
+See F<doc/compliance-nntp> for all of the known issues with INN's
+compliance with the current NNTP draft.
+
+=item *
+
+All accesses to the F<history> file for all parts of INN now go through a
+generic API like the storage and overview subsystems do. This will
+eventually allow new history implementations to be dropped in without
+affecting the rest of INN, and will significantly improve the
+encapsulation of the history subsystem. See the libinnhist(3) man page
+for the details of the interface.
+
+=item *
+
+INN now uses a new parser for the F<inn.conf> file. This means that
+parameters containing whitespace or other special characters must now be
+quoted; see inn.conf(5). It fixes the long-standing bug that certain
+values must be included in F<inn.conf> even if using the defaults for the
+use of shell or Perl scripts, and it will serve as the basis for
+standardizing and cleaning up the configuration file parsing in other
+parts of INN. B<innupgrade> is run during C<make update> and should convert
+an existing F<inn.conf> file for you.
+
+=item *
+
+B<send-uucp> has been replaced by a completely rewritten version from
+Marco d'Itri, Edvard Tuinder, and Miquel van Smoorenburg, which uses a
+configuration file that specifies batch sizes, compression methods, and
+hours during which batches should be generated. The old B<sendbatch>
+script has been retired, since B<send-uucp> can now handle everything
+that it did.
+
+=item *
+
+Two C<configure> options have changed names: B<--with-tmp-path> is now
+B<--with-tmp-dir>, and B<--with-largefiles> is now B<--enable-largefiles>, to
+improve consistency and better match the C<autoconf> option guidelines.
+
+=item *
+
+Variables can now be used in the F<newsfeeds> file to make it easier to
+specify many similar feeds or feed patterns. See the newsfeeds(5) man
+page for details.
+
+=item *
+
+Local connections to INN support a new special mode, MODE CANCEL, that
+allows efficient batch cancellation of messages. This is intended to be
+the preferred interface for external spam and abuse filters like NoCeM.
+See "CANCEL FEEDS" in innd(8) for details.
+
+=item *
+
+Two new options, I<nfsreader> and I<nfswriter>, have been added to
+F<inn.conf> to aid in building NFS based shared reader/writer platforms.
+On the writer server configure I<nfswriter> to C<true> and on all of the readers
+configure I<nfsreader> to C<true>; these options add calls to force data out to
+the NFS server and force it to be read directly from the NFS server at the
+appropriate moments. Note that it has only been tested on S<Solaris 8>,
+using CNFS as the storage mechanism and tradindexed as the overview
+method.
+
+=item *
+
+A new option, I<tradindexedmmap>, has been added to F<inn.conf>. If set
+to C<true> (the default), then the tradindexed overview method will use
+mmap() to access its overview data (in 2.3 you couldn't control this; it
+always used mmap).
+
+=item *
+
+Thanks to code contributed by CMU, B<innfeed> can now feed an IMAP server as
+well as other NNTP servers. See the man page for innfeed(8) for more
+information.
+
+=item *
+
+An authenticator, B<auth_smb>, that checks a username and password against
+a remote Samba server is now included. See auth_smb(8) for details.
+
+=item *
+
+The wildmat functions in INN now support UTF-8, in a way that should allow
+them to still work with most simple 8-bit character sets in widespread
+use. As part of this change, some additional wildmat interfaces are now
+available and the names have changed (to uwildmat, where C<u> is for
+Unicode). See uwildmat(3) for the details.
+
+=item *
+
+The interface between external authenticators and B<nnrpd> is now properly
+documented, in F<doc/external-auth>. A library implementing this
+interface in C is provided, which should make it easier to write
+additional authenticators resolvers. See libauth(3) for details, and any
+of the existing programs in F<authprogs/> for examples.
+
+=item *
+
+Most (if not all) of the temporary file creation in INN now uses functions
+that create temporary files properly and safely.
+
+=back
+
+=head1 Changes in 2.3.5
+
+=over 2
+
+=item *
+
+Clients using POST are no longer permitted to provide an Injector-Info:
+header.
+
+=item *
+
+Fixed a bug causing posts with Followup-To: set to a moderated group to be
+rejected if the posting user didn't have permission to approve postings.
+
+=item *
+
+Fixed bugs in B<inncheck> with setuid rnews or setgid inews, in I<innconfval>
+with F<inn.conf> parameters containing shell metacharacters but no spaces,
+and in F<parsedate.y> with some versions of B<yacc>. Fixed a variety of
+size-related printf format warnings (e.g., C<%d> vs. C<%ld>) thanks to the work
+of Winfried Szukalski.
+
+=back
+
+=head1 Changes in 2.3.4
+
+=over 2
+
+=item *
+
+LIST ACTIVE no longer returns data when given a single group argument if
+the client is not authorized to read that group.
+
+=item *
+
+XHDR and XPAT weren't correctly parsing article headers, resulting in
+searches for the header "newsgroup" matching the header "newsgroups".
+
+=item *
+
+Made CNFS more robust against crashes by actually syncing the cycbuff
+headers to disk as was originally intended. Fixed a memory leak in the
+tradspool code.
+
+=item *
+
+Two bugs in B<pgpverify> when using GnuPG were fixed: it now correctly checks
+for B<gpgv> (rather than B<pgp>) when told to use GnuPG and expects the keyring
+to be F<pubring.gpg> (not F<pubring.pgp>).
+
+=item *
+
+Substantial updates to the sample provided F<control.ctl> file.
+
+=item *
+
+Compilation fixes with S<Perl 5.8.0>, S<Berkeley DB 4.x>, current versions of
+Linux (including with large file support), and Tru64. B<inndf> fixes for
+ReiserFS.
+
+=item *
+
+Various bugs in the header handling in B<nnrpd> have been fixed, including
+hangs when using virtual domains and improper processing of folded headers
+under certain circumstances.
+
+=item *
+
+Other minor bug fixes and documentation improvements.
+
+=back
+
+=head1 Changes in 2.3.3
+
+=over 2
+
+=item *
+
+B<pgpverify> now supports using GnuPG to check signatures (rather than PGP)
+without the B<pgpgpg> wrapper. GnuPG can check both old-style RSA signatures
+and new OpenPGP signatures and is recommended over S<PGP 2.6>. If you have
+GnuPG installed, B<pgpverify> will use it rather than PGP, which means that
+you may have to create a new key ring for GnuPG to use to verify signatures
+if you were previously using PGP.
+
+=item *
+
+Users can no longer post articles containing Approved: headers to
+moderated groups by default; they must be specifically given that
+permission with the I<access> parameter in F<readers.conf>. See the man page
+for more details.
+
+=item *
+
+Two bugs in repacking overview index files and a reliability bug with
+writing overview data were all fixed in the tradindexed overview method,
+hopefully making it somewhat more reliable, particularly for B<makehistory>.
+
+=item *
+
+If F<rc.news.local> exists in the INN binary directory, it will be run with
+the start or stop argument whenever B<rc.news> is run. This is available
+as a hook for local startup and shutdown code.
+
+=item *
+
+The default history table hash sizes were increased because a too-small
+value can cause serious performance problems (whereas a too-large hash
+just wastes a bit of disk space).
+
+=item *
+
+The sample F<control.ctl> file has been extensively updated.
+
+=item *
+
+Wildmat exclusions (C<@> and C<!>) should now work properly in F<storage.conf>
+newsgroup patterns.
+
+=item *
+
+The implementation of the B<-w> flag for B<expireover> was fixed; previously,
+the value given to B<-w> to change B<expireover>'s notion of the current time
+was scaled by too much.
+
+=item *
+
+Various other more minor bug fixes, standards compliance fixes, and
+documentation improvements.
+
+=back
+
+=head1 Changes in 2.3.2
+
+=over 2
+
+=item *
+
+B<innxmit> can again handle regular filenames as input as well as storage API
+tokens (allowing it to be used to import an old traditional spool).
+
+=item *
+
+Several problems with tagged-hash history files have been fixed thanks to
+the debugging efforts of Andrew Gierth and Sang-yong Suh.
+
+=item *
+
+A very long-standing (since S<INN 1.0>!) NNTP protocol bug in B<nnrpd> was
+fixed. The response to an ARTICLE command retrieving a message by Message-ID
+should have the Message-ID as the third word of the response, not the
+fourth. Fixing this is reported to I<possibly> cause problems with some
+Netscape browsers, but other news servers correctly follow the protocol.
+
+=item *
+
+Some serious performance problems with expiration of tradspool should now
+be at least somewhat alleviated. tradspool and timehash now know how to
+output file names for removal rather than tokens, and B<fastrm>'s ability to
+remove regular files has been restored. This should bring expiration
+times for tradspool back to within a factor of two of pre-storage-API
+expiration times.
+
+=item *
+
+Added a sample F<subscriptions> file and documentation for it and B<innmail>.
+
+=back
+
+=head1 Changes in 2.3.1
+
+=over 2
+
+=item *
+
+B<inews> no longer downloads the F<active> file, no longer tries to send
+postings to moderated groups to the moderator directly, and in general
+duplicates less of the functionality of B<nnrpd>, instead letting B<nnrpd>
+handle it. This fixes the problem of B<inews> not working properly for users
+other than news without being setgid.
+
+=item *
+
+Added a man page for B<ckpasswd>.
+
+=item *
+
+A serious bug in the embedded Perl authentication hooks was fixed, thanks
+to Jan Rychter.
+
+=item *
+
+The annoying compilation problem with embedded Perl filtering on Linux
+systems without libgdbm installed should be fixed.
+
+=item *
+
+INN now complains loudly at C<configure> time if the configured path for
+temporary files is world-writeable, since this configuration can be a
+security hole.
+
+=item *
+
+Many other varied bug fixes and documentation fixes of all sorts.
+
+=back
+
+=head1 Upgrading from 2.2 to 2.3
+
+There may be additional things to watch out for not listed here; if you
+run across any, please let <inn-bugs@isc.org> know about them.
+
+Simply doing a C<make update> is not sufficient to upgrade; the history and
+overview information will also have to be regenerated, since the formats
+of both files have changed between 2.2 and 2.3. Regardless of whether you
+were using the storage API or traditional spool under 2.2, you'll need to
+rebuild your overview and history files. You will also need to add a
+F<storage.conf> file, if you weren't using the storage API under S<INN 2.2>. A
+good default F<storage.conf> file for 2.2 users would be:
+
+ method tradspool {
+ newsgroups: *
+ class: 0
+ }
+
+Create this F<storage.conf> file before rebuilding history or overview.
+
+If you want to allow readers, or if you want to expire based on newsgroup
+name, you need to tell INN to generate overview data and pick an overview
+method by setting I<ovmethod> in F<inn.conf>. See F<INSTALL> and inn.conf(5)
+for more details.
+
+The code that generates the dbz index files has been split into a separate
+program, B<makedbz>. B<makehistory> still generates the base F<history> file
+and the overview information, but some of its options have been changed.
+To rebuild the history and overview files, use something like:
+
+ makehistory -b -f history.n -O -T /usr/local/news/tmp -l 600000
+
+(change the F</usr/local/news/tmp> path to some directory that has plenty of
+temporary space, and leave off B<-O> if you're running a transit-only server
+and don't intend to expire based on group name, and therefore don't need
+overview.) Or if your overview is buffindexed, use:
+
+ makehistory -b -f history.n -O -F
+
+Both will generate a new history file as F<history.n> and rebuild overview
+at the same time. If you want to preseve a record of expired Message-IDs
+in the history file, run:
+
+ awk 'NF==2 { print; }' < history >> history.n
+
+to append them to the new history file you created above. Look over the
+new history file and make sure it looks right, then generate the new index
+files and move them into place:
+
+ makedbz -s `wc -l < history.n` -f history.n
+ mv history.n history
+ mv history.n.dir history.dir
+ mv history.n.hash history.hash
+ mv history.n.index history.index
+
+(Rather than F<.hash> and F<.index> files, you may have a F<.pag> file if you're
+using tagged hash.)
+
+For reader machines, F<nnrp.access> has been replaced by F<readers.conf>.
+There currently isn't a program to convert between the old format and the
+new format (if you'd like to contribute one, it would be welcomed
+gratefully). The new file is unfortunately considerably more complex as
+a result of its new capabilities; please carefully read the example
+F<readers.conf> provided and the man page when setting up your initial
+configuration. The provided commented-out examples cover the most common
+installation (IP-based authentication for all machines on the local
+network).
+
+INN makes extensive use of mmap(2) for the new overview mechanisms, so at
+the present time NFS-mounting the spool and overview on multiple reader
+machines from one central server probably isn't feasible in this version.
+mmap tends to interact poorly with NFS (at the least, NFS clients won't
+see updates to the mapped files in situations where they should). (The
+preferred way to fix this would, rather than backing out the use of mmap
+or making it optional, to add support for Diablo-style header feeds and
+pull-on-demand of articles from a master server.)
+
+The flags for B<overchan> have changed, plus you probably don't want to
+run B<overchan> at all any more. Letting B<innd> write overview data itself
+results in somewhat slower performance, but is more reliable and has a
+better failure mode under high loads. Writing overview data directly is
+the default, so in a normal upgrade from 2.2 to 2.3 you'll want to comment
+out or remove your B<overchan> entry in F<newsfeeds> and set I<useoverchan> to
+C<false> in F<inn.conf>.
+
+B<crosspost> is no longer installed, and no longer works (even with
+traditional spool). If you have an entry for B<crosspost> in F<newsfeeds>,
+remove it.
+
+If you're importing a traditional spool from a pre-storage API INN server,
+it's strongly recommended that you use NNTP to feed the articles to your
+new server rather than trying to build overview and history directly from
+the old spool. It's more reliable and ensures that everything gets put
+into the right place. The easiest way to do this is to generate, on your
+old server, a list of all of your existing article files and then feed
+that list to B<innxmit>. Further details can be found in the FAQ at
+L<http://www.eyrie.org/~eagle/faqs/inn.html>.
+
+If you are using a version of Cleanfeed that still has a line in it like:
+
+ $lines = $hdr{'__BODY__'} =~ tr/\n/\n/;
+
+you will need to change this line to:
+
+ $lines = $hdr{'__LINES__'};
+
+to work with S<INN 2.3> or later. This is due to an internal optimization of
+the interface to embedded filters that's new in S<INN 2.3>.
+
+=head1 Changes in 2.3.0
+
+=over 2
+
+=item *
+
+New F<readers.conf> file (replaces F<nnrp.access>) which allows more
+flexible specification of access restrictions. Included in the sample
+implementations is a RADIUS-based authenticator.
+
+=item *
+
+Unified overview has been replaced with an overview API, and there are now
+three separate overview implementations to choose from. One (tradindexed)
+is very like traditional overview but uses an additional index file. The
+second (buffindexed) uses large buffers rather than separate files for
+each group and can handle a higher incoming article rate while still being
+fast for readers. The third (ovdb) uses S<Berkeley DB> to store overview
+information (so you need to have S<Berkeley DB> installed to use it). The
+I<ovmethod> key in F<inn.conf> chooses the overview method to use.
+
+Note that ovdb has not been as widely tested as the other overview
+mechanisms and should be considered experimental.
+
+=item *
+
+All article storage and retrieval is now done via the storage API.
+Traditional spool is now available as a storage type under the storage
+API. (Note that the current traditional spool implementation causes
+nightly expire to be extremely slow for a large number of articles, so
+it's not recommended that you use the tradspool storage method for the
+majority of a large spool.)
+
+=item *
+
+The timecaf storage method has been added, similar to timehash but storing
+multiple articles in a single file. See F<INSTALL> for details on it.
+
+=item *
+
+INN now supports embedded Python filters as well as Perl and Tcl filters,
+and supports Python authentication hooks.
+
+=item *
+
+There is preliminary support for news reading over SSL, using OpenSSL.
+
+=item *
+
+To simplify anti-abuse filtering, and to be more compliant with news
+standards and proposed standards, INN now treats as control messages only
+articles containing a Control: header. A Subject: line beginning with
+C<cmsg > is no longer sufficient for a message to be considered a control
+message, and the Also-Control: header is no longer supported.
+
+=item *
+
+The INN build system no longer uses subst. (This will be transparent to
+most users; it's an improvement and modernization of how INN is
+configured.)
+
+=item *
+
+The build and installation system has been substantially overhauled.
+C<make update> now updates scripts as well as binaries and documentation,
+there is better support for parallel builds (C<make -j>), there is less
+C<make> recursion, and far more of the system-dependent configuration is
+handled directly by C<autoconf>. libtool build support (including shared
+library support) should be better than previous releases.
+
+=back
+
+=head1 Changes in 2.2.3
+
+=over 2
+
+=item *
+
+B<inews> is not installed setgid news and B<rnews> is not installed setuid root
+by default any more. If you need the old permissions, you have to give a
+flag to configure. See F<INSTALL> for more details.
+
+=item *
+
+Fixed a security hole when I<verifycancels> was enabled in F<inn.conf> (not the
+default).
+
+=item *
+
+Message-IDs are now limited to 250 octets to prevent interoperability
+problems with other servers.
+
+=item *
+
+Embedded Perl filters now work with S<Perl 5.6.0>.
+
+=item *
+
+Lots of bug fixes and changes for security paranoia.
+
+=back
+
+=head1 Changes in 2.2.2
+
+=over 2
+
+=item *
+
+Various minor bug fixes and a Y2K bug fix. The Y2K bug is in version
+version 2.2.1 only and will show up after S<Jan 1st>, 2000 when a news reader
+issues a NEWNEWS command for a date prior to the year 2000.
+
+=back
+
+=head1 Changes in 2.2.1
+
+=over 2
+
+=item *
+
+Various bug fixes, mostly notably fixes for potential buffer overflow
+security vulnerabilities.
+
+=back
+
+=head1 Changes in 2.2.0
+
+=over 2
+
+=item *
+
+New F<storage.conf> file (replaces F<storage.ctl>).
+
+=item *
+
+New (optional) way of handling non-cancel control messages (B<controlchan>)
+that serializes them and prevents server overload from control message
+storms.
+
+=item *
+
+Support for B<actsyncd> to fetch F<active> file with B<ftp>; configured by default
+to use L<ftp://ftp.isc.org/pub/usenet/CONFIG/active.Z> if you run B<actsyncd>.
+Be sure to read the manual page for B<actsync> to configure an F<actsync.ign>
+file for your site, and test B<simpleftp> if you do not C<configure> with B<wget>
+or B<ncftp>. Also see L<ftp://ftp.isc.org/pub/usenet/CONFIG/README>.
+
+=item *
+
+Some options to C<configure> are now moved to F<inn.conf> (I<merge-to-groups> and
+I<pgp-verify>, without the hyphen).
+
+=item *
+
+B<inndf>, a portable version of df(1), is supplied.
+
+=item *
+
+New B<cnfsstat> program to show stats of CNFS buffers.
+
+=item *
+
+B<news2mail> and B<mailpost> programs for gatewaying news to mail and mail to
+news are supplied.
+
+=item *
+
+B<pullnews> program for doing a sucking feed is provided (not meant for large
+feeds).
+
+=item *
+
+The B<innshellvars.csh.in> script is obsolete (and lives in the F<obsolete>
+directory, for now).
+
+=back
+
+=cut
--- /dev/null
+=head1 NAME
+
+newsfeeds - Determine where Usenet articles are sent
+
+=head1 DESCRIPTION
+
+The file I<pathetc>/newsfeeds specifies how incoming articles should be
+distributed to other programs and files on the server. It is parsed by
+the InterNetNews server innd(8) when it starts up, or when directed to by
+ctlinnd(8). B<innd> doesn't send articles to remote sites itself, so
+F<newsfeeds> doesn't directly determine which remote news servers articles
+are sent to. Instead, it specifies what batch files should be created or
+which programs should be run (and what information should be sent to
+them), and then this information is used by programs like innxmit(8) and
+innfeed(8) to feed articles to remote sites.
+
+The F<newsfeeds> file isn't used solely to set up feeding accepted
+articles to remote sites but also to pass them (or bits of information
+about them) to any local programs or files that want that data. For
+example, controlchan(8), a daemon that processes incoming control
+messages, runs out of F<newsfeeds>, as could a news to mail gateway.
+
+The file is interpreted as a set of lines, parsed according to the
+following rules: If a line ends with a backslash, the backslash, the
+newline, and any whitespace at the start of the next line is deleted.
+This is repeated until the entire "logical" line is collected. If the
+logical line is blank or starts with a number sign (C<#>), it is ignored.
+
+All other lines are interpreted as feed entries. An entry should consist
+of four colon-separated fields; two of the fields may have optional
+sub-fields, marked off by a slash. Fields or sub-fields that take
+multiple parameters should be separated by a comma. Extra whitespace can
+cause problems and should be avoided. Except for the site names, case is
+significant. The format of an entry is:
+
+ sitename[/exclude,exclude,...]\
+ :pattern,pattern...[/distribution,distribution...]\
+ :flag,flag...\
+ :parameter
+
+Each field is described below.
+
+The I<sitename> is the name of the site to which a news article can be
+sent. It is used for writing log entries and for determining if an
+article should be forwarded to a site. (A "site" is the generic term for
+some destination of newsfeed data; it often corresponds to a remote news
+peer, but doesn't have to. For example, a local archiving program run
+from F<newsfeeds> is also a "site.") If I<sitename> already appears in
+the article's Path: header, then the article will not be sent to the site.
+The name is usually whatever the remote site uses to identify itself in
+the Path: header, but can be almost any word.
+
+Be careful, though, to avoid having the I<sitename> accidentally match a
+Path: header entry unintentionally. For this reason, special local
+entries (such as archivers or gateways) should probably end with an
+exclamation point to make sure that they do not have the same name as any
+real site. For example, C<gateway> is an obvious name for the local entry
+that forwards articles out to a mailing list. If a site with the name
+C<gateway> posts an article, when the local site receives the article it
+will see the name in the Path and not send the article to its own
+C<gateway> entry. Since C<gateway!> can't appear as an individual Path:
+entry since C<!> is a delimiter in the Path: header, that would be a
+better thing to use for I<sitename>.
+
+(Another way to avoid this problem is with the C<Ap> flag; see the
+description below.)
+
+If an entry has an exclusion sub-field, the article will not be sent to
+that site if any of I<exclude> appear in the Path: header. (It's
+sometimes convenient to have the I<sitename> be an abbreviated form of the
+name of the remote site, since all the I<sitename>s to which an article
+is sent are written to the log and using shorter I<sitename>s can
+therefore improve performance for large servers. In this case, the Path:
+header entries of those sites should be given as I<exclude> entries and
+the C<Ap> flag used so that the abbreviated I<sitename> doesn't
+accidentally match some other Path: header entry.)
+
+The same I<sitename> can be used more than once and the appropriate action
+will be taken for each entry that should receive the article, but this is
+recommended only for program feeds to avoid confusion. Case is not
+significant in site names.
+
+The comma-separated I<pattern> specifies which groups to send to the site;
+it is interpreted to build a "subscription list" for the site. The
+default subscription is to get all groups carried by the server. It is a
+uwildmat(3) pattern supporting poison (C<@>) wildcards; see the uwildmat(3)
+man page for full details on the pattern matching language. I<pattern>
+will be matched against every newsgroup carried by the server and all
+newsgroups that match will be added to the subscription list for the site.
+
+Normally, a given article (or information about it) is sent to a site if
+any of the newsgroups to which the article was posted are in that site's
+subscription list. If a newsgroup matches a C<@> pattern in I<pattern>,
+then not only is it not added to the subscription list, but any articles
+crossposted to that newsgroup also will not be sent to that site even if
+other newsgroups to which it was crossposted are in that site's
+subscription list. This is called a poison pattern (because matching
+groups are "poisoned").
+
+For example, to receive all comp.* groups, but only comp.sources.unix
+within the sources newsgroups, the following I<pattern> can be used:
+
+ comp.*,!comp.sources.*,comp.sources.unix
+
+Note that the trailing C<.*> is required; the pattern has to match the
+whole newsgroup name. C<comp.sources.*> could be written C<comp.sources*>
+and would exclude the newsgroup comp.sources (if it exists) as well as the
+groups in the comp.sources.* hierarchy, but note that this would also
+exclude a newsgroup named comp.sources-only (whereas the above pattern
+would add that group to the site subscription list since it matches
+C<comp.*> and none of the other patterns.
+
+For another example, to feed alt.* and misc.* to a given site but not any
+articles posted to alt.binaries.warez (even if they're also crossposted to
+other alt.* or misc.* groups), the following pattern can be used:
+
+ alt.*,@alt.binaries.warez,misc.*
+
+Note, however, that if you reversed the C<alt.*> and <@alt.binaries.warez>
+entries, this pattern would be equivalent to C<alt.*,misc.*>, since the
+last matching pattern determines whether a given newsgroup matches and the
+poison logic only applies if the poison entry is the last matching entry.
+
+Control messages follow slightly different propagation rules than normal
+articles; see innd(8) for the details. Note that most subscriptions
+should have C<!junk,!control,!control.*> in their pattern list due to those
+propagation rules (and since junk is a special internal newsgroup; see
+I<wanttrash> in inn.conf(5) for more details on what it's used for) and
+that the best way to keep control messages local to a site is with a
+distribution.
+
+A subscription can be further modified by specifying distributions that
+the site should or should not receive. The default is to send all
+articles to all sites that subscribe to any of the groups where it has
+been posted, but if an article has a Distribution: header and any
+I<distribution>s are specified, then they are checked according to the
+following rules:
+
+=over 4
+
+=item 1.
+
+If the Distribution: header matches any of the values in the sub-field,
+the article is sent.
+
+=item 2.
+
+If a I<distribution> starts with an exclamation point, and it matches the
+Distribution: header, the article is not sent.
+
+=item 3.
+
+If the Distribution: header does not match any I<distribution> in the
+site's entry and no negations were used, the article is not sent.
+
+=item 4.
+
+If the Distribution: header does not match any I<distribution> in the
+site's entry and any I<distribution> started with an exclamation point,
+the article is sent.
+
+=back
+
+If an article has more than one distribution specified, then each one is
+handled according according to the above rules. If any of the specified
+distributions indicate that the article should be sent, it is; if none do,
+it is not sent. In other words, the rules are used as a logical or.
+
+It is almost definitely a mistake to have a single feed that specifies
+distributions that start with an exclamation point along with some that
+don't.
+
+Distributions are text words, not patterns; entries like C<*> or C<all>
+have no special meaning.
+
+The I<flag> field is described in L<"FLAG VALUES">. The interpretation of
+the I<parameter> field depends on the type of feed and is explained in
+more detail in L<"FEED TYPES">. It can be omitted for some types of
+feeds.
+
+The site named C<ME> is special. There must be exactly one such entry,
+and it should be the first entry in the file. If the C<ME> entry has an
+exclusion sub-field, incoming articles are rejected completely if any of
+the names specified in that exclusion sub-field appear in their Path:
+headers. If the C<ME> entry has a subscription list, that list is
+prepended to the subscription list of all other entries. For example,
+C<*,!control,!control.*,!junk,!foo.*> could be used to set the default subscription
+list for all other feeds so that local postings are not propagated unless
+C<foo.*> explicitly appears in the site's subscription list. This feature
+tends to be somewhat confusing since the default subscription is prepended
+and can be overridden by other patterns.
+
+If the C<ME> entry has a distribution sub-field, only articles that match
+that distribution list are accepted and all other articles are rejected.
+A common use for this is to put something like C</!local> in the C<ME>
+entry to reject local postings from other misconfigured sites.
+
+Finally, it is also possible to set variables in F<newsfeeds> and use them
+later in the file. A line starting with C<$> sets a variable. For
+example:
+
+ $LOCALGROUPS=local.*,example.*
+
+This sets the variable C<LOCALGROUPS> to C<local.*,example.*>. This
+variable can later be used elsewhere in the file, such as in a site entry
+like:
+
+ news.example.com:$LOCALGROUPS:Tf,Wnm:
+
+which is then completely equivalent to:
+
+ news.example.com:local.*,example.*:Tf,Wnm:
+
+Variables aren't solely simple substitution. If either C<!> or C<@>
+immediately preceeds the variable and the value of the variable contains
+commas, that character will be duplicated before each comma. This
+somewhat odd-sounding behavior is designed to make it easier to use
+variables to construct feed patterns. The utility becomes more obvious
+when you observe that the line:
+
+ news.example.net:*,@$LOCALGROUPS:Tf,Wnm:
+
+is therefore equivalent to:
+
+ news.example.net:*,@local.*,@example.*:Tf,Wnm:
+
+which (as explained below) excludes all of the groups in $LOCALGROUPS from
+the feed to that site.
+
+=head1 FLAG VALUES
+
+The I<flags> parameter specifies miscellaneous parameters, including the
+type of feed, what information should be sent to it, and various
+limitations on what articles should be sent to a site. They may be
+specified in any order and should be separated by commas. Flags that take
+values should have the value immediately after the flag letter with no
+whitespace. The valid flags are:
+
+=over 4
+
+=item B<E<lt>> I<size>
+
+An article will only be sent to this site if it is less than I<size> bytes
+long. The default is no limit.
+
+=item B<E<gt>> I<size>
+
+An article will only be sent to this site if it is greater than I<size>
+bytes long. The default is no limit.
+
+=item B<A> I<checks>
+
+An article will only be sent to this site if it meets the requirements
+specified in I<checks>, which should be chosen from the following set.
+I<checks> can be multiple letters if appropriate.
+
+=over 3
+
+=item c
+
+Exclude all kinds of control messages.
+
+=item C
+
+Only send control messages, not regular articles.
+
+=item d
+
+Only send articles with a Distribution header. Combined with a particular
+distribution value in the I<distribution> part of the site entry, this can
+be used to limit articles sent to a site to just those with a particuliar
+distribution.
+
+=item e
+
+Only send articles where every newsgroup listed in the Newsgroups: header
+exists in the active file.
+
+=item f
+
+Don't send articles rejected by filters. This is only useful when
+I<dontrejectfiltered> is set in F<inn.conf>. With that variable set, this
+lets one accept all articles but not propagate filtered ones to some
+sites.
+
+=item o
+
+Only send articles for which overview data was stored.
+
+=item O
+
+Send articles to this site that don't have an X-Trace: header, even if the
+C<O> flag is also given.
+
+=item p
+
+Only check the exclusions against the Path: header of articles; don't
+check the site name. This is useful if your site names aren't the same as
+the Path: entries added by those remote sites, or for program feeds where
+the site name is arbitrary and unrelated to the Path: header.
+
+=back
+
+If both C<c> and C<C> are given, the last specified one takes precedence.
+
+=item B<B> I<high>/I<low>
+
+If a site is being fed by a file, channel, or exploder (see below), the
+server will normally start trying to write the information as soon as
+possible. Providing a buffer may give better system performance and help
+smooth out overall load if a large batch of news comes in. The value of
+the this flag should be two numbers separated by a slash. I<high>
+specifies the point at which the server can start draining the feed's I/O
+buffer, and I<low> specifies when to stop writing and begin buffering
+again; the units are bytes. The default is to do no buffering, sending
+output as soon as it is possible to do so.
+
+=item B<C> I<count>
+
+If this flag is specified, an article will only be sent to this site if
+the number of groups it is posted to, plus the square of the number of
+groups followups would appear in, is no more than I<count>. C<30> is a
+good value for this flag, allowing crossposts to up to 29 groups when
+followups are set to a single group or poster and only allowing crossposts
+to 5 groups when followups aren't set.
+
+=item B<F> I<name>
+
+Specifies the name of the file that should be used if it's necessary to
+begin spooling for the site (see below). If I<name> is not an absolute
+path, it is taken to be relative to I<pathoutgoing> in F<inn.conf>. If
+I<name> is a directory, the file F<togo> in that directory will be used as
+the file name.
+
+=item B<G> I<count>
+
+If this flag is specified, an article will only be sent to this site if it
+is posted to no more than I<count> newsgroups. This has the problem of
+filtering out many FAQs, as well as newsgroup creation postings and
+similar administrative announcements. Either the B<C> flag or the B<U>
+flag is a better solution.
+
+=item B<H> I<count>
+
+If this flag is specified, an article will only be sent to this site if it
+has I<count> or fewer sites in its Path: line. This flag should only be
+used as a rough guide because of the loose interpretation of the Path:
+header; some sites put the poster's name in the header, and some sites
+that might logically be considered to be one hop become two because they
+put the posting workstation's name in the header. The default value for
+I<count> if not specified is one. (Also see the B<O> flag, which is
+sometimes more appropriate for some uses of this flag.)
+
+=item B<I> I<size>
+
+The flag specifies the size of the internal buffer for a file feed. If
+there are more file feeds than allowed by the system, they will be
+buffered internally in least-recently-used order. If the internal buffer
+grows bigger then I<size> bytes, however, the data will be written out to
+the appropriate file. The default value is 16 KB.
+
+=item B<N> I<status>
+
+Restricts the articles sent to this site to those in newsgroups with the
+moderation status given by I<status>. If I<status> is C<m>, only articles
+in moderated groups are sent; if I<status> is C<u>, only articles in
+unmoderated groups are sent.
+
+=item B<O> I<originator>
+
+If this flag is specified, an article will only be sent to this site if it
+contains an X-Trace: header and the first field of this header matches
+I<originator>. I<originator> is a uwildmat(3) expression without commas or
+a list of such expressions, separated by C</>. The article is never sent
+if the first character of the pattern begins with C<@> and the rest of the
+pattern matches. One use of this flag is to restrict the feed to locally
+generated posts by using an I<originator> pattern that matches the
+X-Trace: header added by the local server.
+
+=item B<P> I<priority>
+
+The nice priority that this channel or program feed should receive. This
+should be a positive number between 0 and 20 and is the priority that the
+new process will run with. This flag can be used to raise the priority to
+normal if you're using the I<nicekids> parameter in F<inn.conf>.
+
+=item B<Q> I<hashfeed>
+
+Specifies the I<hashfeed> match expression for this site. It must be in
+the form C<value/mod> or C<start-end/mod>. The Message-ID of the article
+is hashed using MD5, which results in a 128-bit hash. The lowest
+S<32 bits> are then taken by default as the hashfeed value (which is an
+integer). If the hashfeed value modulus C<mod> plus one equals C<value> or
+is between C<start> and C<end>, the article will be fed to this site. All
+these numbers must be integers.
+
+It is a deterministic way to control the flow of articles and to split a feed.
+For instance:
+
+ Q1/2 Feeds about 50% of all articles to this site.
+ Q2/2 Feeds the other 50% of all articles.
+
+Another example with three sites:
+
+ Q1-3/10 Feeds about 30% of all articles.
+ Q4-5/10 Feeds about 20% of all articles.
+ Q6-10/10 Feeds about 50% of all articles.
+
+If this flag is specified multiple times, the contents will be
+logically C<OR>ed together (just one match is needed).
+
+You can use an extended syntax of the form C<value/mod_offset> or
+C<start-end/mod_offset>. As MD5 generates a 128-bit return value,
+it is possible to specify from which byte-offset the 32-bit integer
+used by hashfeed starts. The default value for C<offset> is C<_0>
+and thirteen overlapping values from C<_0> to C<_12> can be used.
+Only up to four totally independent values exist: C<_0>, C<_4>,
+C<_8> and C<_12>.
+
+Therefore, it allows to a generate a second level of deterministic
+distribution. Indeed, if a news server is fed C<Q1/2>, it can go on
+splitting thanks to C<Q1-3/9_4> for instance.
+
+The algorithm is compatible with the one used by S<Diablo 5.1> and up.
+If you want to use the legacy quickhashing method used by Diablo
+before 5.1, you can put an C<@> sign just after the B<Q> flag (for
+instance C<Q@1-3/10>, but the distribution of the messages is not
+perfect with this legacy method whose use is discouraged and for
+which offsets cannot be used).
+
+=item B<S> I<size>
+
+If the amount of data queued for the site gets to be larger than I<size>
+bytes, the server will switch to spooling, appending to a file specified
+by the B<F> flag, or I<pathoutgoing>/I<sitename> if B<F> is not specified.
+Spooling usually happens only for channel or exploder feeds, when the
+spawned program isn't keeping up with its input.
+
+=item B<T> I<type>
+
+This flag specifies the type of feed for this site. I<type> should be a
+letter chosen from the following set:
+
+ c Channel
+ f File
+ l Log entry only
+ m Funnel (multiple entries feed into one)
+ p Program
+ x Exploder
+
+Each feed is described below in L<"FEED TYPES">. The default is B<Tf>,
+for a file feed.
+
+=item B<U> I<count>
+
+If this flag is specified, an article will only be sent to this site if
+followups to this article would be posted to no more than I<count>
+newsgroups. (Also see B<C> for a more complex way of handling this.)
+
+=item B<W> I<items>
+
+For a file, channel, or exploder feed, this flag controls what information
+will be sent to this site. For a program feed, only the asterisk (C<*>)
+has any effect. I<items> should be chosen from the following set:
+
+=over 3
+
+=item b
+
+Size of the article (in wire format, meaning with CRLF at the end of each
+line, periods doubled at the beginning of lines, and ending in a line with
+a single period) in bytes.
+
+=item e
+
+The time the article will expire as seconds since epoch if it has an
+Expires: header, C<0> otherwise.
+
+=item f
+
+The storage API token of the article (the same as C<n>). The article can
+be retrieved given the storage API token by using sm(8).
+
+=item g
+
+The newsgroup the article is in; if cross-posted, then the first of the
+groups to which the article was posted that this site gets. (The
+difference from C<G> is that this sends the newsgroup to which the article
+was posted even if it is a control message.)
+
+=item h
+
+The history hash key of the article (derived from the message ID).
+
+=item m
+
+The message ID of the article.
+
+=item n
+
+The storage API token of the article. The article can be retrieved given
+the storage API token by using sm(8).
+
+=item p
+
+The time the article was posted a seconds since epoch.
+
+=item s
+
+The site that fed the article to the server. This is taken from either
+the Path: header or the IP address of the sending site depending on the
+value of I<logipaddr> in F<inn.conf>. If I<logipaddr> is true and the IP
+address is C<0.0.0.0> (meaning that the article was fed from localhost by
+a program like rnews(8)), the Path: header value will be sent instead.
+
+=item t
+
+The time the article was received as seconds since epoch.
+
+=item Z<>*
+
+The names of the appropriate funnel entries, or all sites that get the
+article (see below for more details).
+
+=item D
+
+The value of the Distribution: header of the article, or C<?> if there is
+no such header in the article.
+
+=item G
+
+Where the article is stored. If the newsgroup is crossposted, this is
+generally the first of the groups to which it was posted that this site
+receives; however, control messages are filed in control or control.*
+(which is the difference between this item and C<g>).
+
+=item H
+
+All of the headers, followed by a blank line. The Xref header will
+already be present, and a Bytes header containing the article's size in
+bytes as in the C<b> item will be added to the headers. If used, this
+should be the only item in the list.
+
+=item N
+
+The value of the Newsgroups: header.
+
+=item P
+
+The value of the Path: header.
+
+=item O
+
+Overview data for the article.
+
+=item R
+
+Information needed for replication (the Xref header without the site
+name).
+
+=back
+
+More than one letter can be given. If multiple items are specified, they
+will be written in the order specified separated by spaces. (C<H> should
+be the only item if given, but if it's not a newline will be sent before
+the beginning of the headers.) The default is B<Wn>.
+
+The C<H> and C<O> items are intended for use by programs that create news
+overview databases or require similar information. B<WnteO> is the flag
+to generate input needed by the overchan(8) program.
+
+The asterisk (C<*>) has special meaning. Normally it expands to a
+space-separated list of all sites that received the current article. If,
+however, this site is a target of a funnel feed (in other words, if it is
+named by other sites which have the B<Tm> flag), then the asterisk expands
+to the names of the funnel feeds that received the article. Similarly, if
+the site is a program feed, an asterisk in the I<parameter> field will be
+expanded into the list of funnel feeds that received the article. A
+program feed cannot get the site list unless it is the target of other
+B<Tm> feeds.
+
+=back
+
+=head1 FEED TYPES
+
+B<innd> provides four basic types of feeds: log, file, program, and
+channel. An exploder is a special type of channel. In addition, several
+entries can feed into the same feed; these are funnel feeds, which refer
+to an entry that is one of the other types. Funnel feeds are partially
+described above with the description of the B<W*> flag. A funnel feed
+gets every article that would be sent to any of the feeds that funnel into
+it and normally include the B<W*> flag in their flags so that the program
+processing that feed knows which sites received which articles. The most
+common funnel feed is innfeed(8).
+
+Note that the term "feed" is technically a misnomer, since the server
+doesn't transfer articles itself and only writes data to a file, program,
+or log telling another program to transfer the articles.
+
+The simplest feed is a log feed (B<Tl>). Other than a mention in the news
+log file, I<pathlog>/news, no data is written out. This is equivalent to
+a B<Tf> entry writing to F</dev/null>, except that no file is ever opened.
+Flushing a log feed does nothing.
+
+A file feed (B<Tf>) is the next simplest type of feed. When the site
+should receive an article, the specified data is written out to the file
+named by the I<parameter> field. If I<parameter> is not an absolute path,
+it is taken to be relative to I<pathoutgoing> in F<inn.conf>. If
+I<parameter> is not given, it defaults to I<pathoutgoing>/I<sitename>.
+The file name should be unique (two file feeds should not ever point to
+the same file).
+
+File feeds are designed for use by external programs that periodically
+process the written data. To cooperate with B<innd> properly, such
+external programs should first rename the batch file and then send a flush
+command for that site to B<innd> using ctlinnd(8). B<innd> will then
+write out any buffered data, close the file, and reopen it (under the
+original name), and the program can process the data in the renamed file
+at its leisure. File feeds are most frequently used in combination with
+nntpsend(8).
+
+A program feed (B<Tp>) spawns a given program for every article that the
+site receives. The I<paramter> field must be the command line to execute,
+and should contain one instance of C<%s>, which will be replaced by the
+storage API token of the article (the actual article can be retrieved by
+the program using sm(8)). The program will not receive anything on
+standard input (unlike earlier versions of INN, where the article is sent
+to the program on stdin), and standard output and error from the program
+will be set to the error log (I<pathlog>/errlog). B<innd> will try to
+avoid spawning a shell if the command has no shell meta-characters; this
+feature can be defeated if necessary for some reason by appending a
+semi-colon to the end of the command. The full path name of the program
+to be run must be specified unless the command will be run by the shell
+(and it is strongly recommended that the full path name always be
+specified regardless).
+
+If a program feed is the target of a funnel, and if B<W*> appears in the
+flags of the site, a single asterisk may be present in the I<parameter>
+and will be replaced by a space-separated list of names of the sites
+feeding into the funnel which received the relevant article. If the site
+is not the target of a funnel, or if the B<W*> flag is not used, the
+asterisk has no special meaning.
+
+Flushing a program feed does nothing.
+
+For a channel (B<Tc>) or exploder (B<Tx>) feed, the I<parameter> field
+again names the process to start. As with program feeds, the full path to
+the program must be specified. However, rather than spawning the program
+for every article, it is spawned once and then whenever the site receives
+an article, the data specified by the site flags is written to the
+standard input of the spawned program. Standard output and error are set
+as with program feeds. If the process exits, it will be restarted
+automatically. If the process cannot be started, the server will spool
+input to a file named I<pathoutgoing>/I<sitename> and will try to start
+the process again later.
+
+When a channel or exploder feed is flushed, the server closes its end of
+the pipe to the program's standard input. Any pending data that has not
+been written will be spooled; see the description of the B<S> flag above.
+The server will then spawn a new instance of the program. No signal is
+sent to the program; it is up to the program handling a channel or
+exploder feed to notice end of file on its standard input and exit
+appropriately.
+
+Exploders are a special type of channel feed. In addition to the channel
+feed behavior described above, exploders can also be sent command lines.
+These lines start with an exclamation point and their interpretation is up
+to the exploder. The following commands are generated automatically by
+the server:
+
+ !newgroup group
+ !rmgroup group
+ !flush
+ !flush site
+
+These commands are sent whenever the ctlinnd(8) command of the same name
+is received by the server. In addition, the ctlinnd(8) C<send> command
+can be used to send an arbitrary command line to an exploder. The primary
+exploder is buffchan(8).
+
+Finally, B<Tm> feeds are the input to a funnel. The I<parameter> field of
+the site should name the site handling articles for all of the funnel
+inputs.
+
+=head1 EXAMPLES
+
+All of the following examples assume that INN was installed with a prefix
+of F</usr/local/news>; if you installed it somewhere else, modify the
+paths as appropriate.
+
+The syntax of the F<newsfeeds> file is so complex because you can specify
+a staggering variety of feeds. INN is capable of interacting with a wide
+variety of programs that do various things with news articles. Far and
+away the most common two entries in F<newsfeeds>, however, are file feeds
+for nntpsend(8) and funnel feeds for innfeed(8).
+
+The former look like this:
+
+ feed.example.com:*,!control,!control.*,!junk:Tf,Wnm:
+
+which generates a file named I<pathoutgoing>/feed.example.com containing
+one line per article consisting of the storage API token, a space, and the
+message ID.
+
+The latter look like this:
+
+ feed.example.com:*,!control,!control.*,!junk:Tm:innfeed!
+
+Very similar, except that this is the input to a funnel feed named
+C<innfeed!>. One could also write this as:
+
+ example/feed.example.com:*,!control,!control.*,!junk:Ap,Tm:innfeed!
+
+(note the B<Ap> so that articles that contain just C<example> in the Path:
+header will still be sent), which is completely equivalent except that
+this will be logged in I<pathlog>/news as going to the site C<example>
+rather than C<feed.example.com>.
+
+The typical feed entry for innfeed(8) is a good example of a channel feed
+that's the target of various funnel feeds:
+
+ innfeed!:!*:Tc,Wnm*:/usr/local/news/bin/startinnfeed -y
+
+Note that the I<pattern> for this feed is just C<!*> so that it won't
+receive any articles directly. The feed should only receive those
+articles that would go to one of the funnel feeds that are feeding into
+it. innfeed(8) (spawned by B<startinnfeed>) will receive one line per
+article on its standard input containing the storage API token, the
+message ID, and a space-separated list of sites that should receive that
+article.
+
+Here's a more esoteric example of a channel feed:
+
+ watcher!:*:Tc,Wbnm\
+ :exec awk '$1 > 1000000 { print "BIG", $2, $3 }' > /dev/console
+
+This receives the byte size of each article along with the storage API
+token and message ID, and prints to the console a line for every article
+that's over a million bytes. This is actually rather a strange way to
+write this since INN can do the size check itself; the following is
+equivalent:
+
+ watcher!:*:Tc,>1000000,Wbnm\
+ :exec awk '{ print "BIG", $2, $3}' > /dev/console
+
+Here's a cute, really simple news to mail gateway that also serves as an
+example of a fairly fancy program feed:
+
+ mailer!:!*:W*,Tp\
+ :sm %s | innmail -s "News article" *
+
+Remember that C<%s> is replaced by the storage API token, so this
+retrieves the article and pipes it into B<innmail> (which is safer than
+programs like Mail(1) because it doesn't parse the body for tilde
+commands) with a given subject line. Note the use of C<*> in the command
+line and B<W*> in the flags; this entry is designed to be used as the
+target of funnel feeds such as:
+
+ peter@example.com:news.software.nntp:Tm:mailer!
+ sue@example.com:news.admin.misc:Tm:mailer!
+
+Suppose that the server receives an article crossposted between
+news.admin.misc and news.software.nntp. The server will notice that the
+article should be sent to the site C<peter@example.com> and the site
+C<bob@example.com>, both of which funnel into C<mailer!>, so it will look
+at the C<mailer!> site and end up executing the command line:
+
+ sm @...@ | innmail -s "News article" peter@example.com sue@example.com
+
+which will mail the article to both Peter and Sue.
+
+Finally, another very useful example of a channel feed: the standard
+entry for controlchan(8).
+
+ controlchan!\
+ :!*,control,control.*,!control.cancel/!collabra-internal\
+ :Tc,Wnsm:/usr/local/news/bin/controlchan
+
+This program only wants information about articles posted to a control
+newsgroup other than control.cancel, which due to the sorting of control
+messages described in innd(8) will send it all control messages except for
+cancel messages. In this case, we also exclude any article with a
+distribution of C<collabra-internal>. B<controlchan> gets the storage
+API token, the name of the sending site (for processing old-style ihave
+and sendme control messages, be sure to read about I<logipaddr> in
+controlchan(8)), and the message ID for each article.
+
+For many other examples, including examples of the special C<ME> site
+entry, see the example F<newsfeeds> file distributed with INN. Also see the
+install documentation that comes with INN for information about setting up
+the standard newsfeeds entries used by most sites.
+
+=head1 HISTORY
+
+Written by Rich $alz <rsalz@uunet.uu.net> for InterNetNews. Reformatted
+and rewritten in POD by Russ Allbery <rra@stanford.edu>.
+
+$Id: newsfeeds.pod 7741 2008-04-06 09:51:47Z iulius $
+
+=head1 SEE ALSO
+
+active(5), buffchan(8), controlchan(8), ctlinnd(8), inn.conf(5), innd(8),
+innfeed(8), innxmit(8), nntpsend(8), uwildmat(3).
+
+=cut
--- /dev/null
+=head1 NAME
+
+ninpaths - Report Usenet Path statistics (new inpaths)
+
+=head1 SYNOPSIS
+
+B<ninpaths> B<-p> B<-d> I<dumpfile>
+
+B<ninpaths> B<-r> I<site> B<-u> I<dumpfile> [B<-u> I<dumpfile> ...] B<-v>
+I<level>
+
+=head1 DESCRIPTION
+
+This is an efficient and space-saving inpaths reporting program. It works
+as follows: you feed it the Path lines via an INN channel feed or some
+other similar method, and from time to time the program writes all its
+internal counters accumulated so far to a dump file. Another instance of
+the program picks up all the dump files, adds them up and formats them
+into the report. The purpose of the final report is to summarize the
+frequency of occurrence of sites in the Path headers of articles.
+
+Some central sites accumulate the Path data from many news servers running
+this program or one like it, and then report statistics on the most
+frequently seen news servers in Usenet article Path lines. The
+B<sendinpaths> shell script can be run once a month to mail the
+accumulated statistics to such a site and remove the old dump files.
+
+You can get a working setup by doing the following:
+
+=over 4
+
+=item 1.
+
+Create a directory at I<pathlog>/path (replacing I<pathlog> here and in
+all steps that follow with the full path to your INN log directory).
+
+=item 2.
+
+Set up a channel feed using an entry like:
+
+ inpaths!:*:Tc,WP:ninpaths -p -d <pathlog>/path/inpaths.%d
+
+if your version of INN supports WP (2.0 and later all do). Replace
+<pathlog> with the full path to your INN log directory.
+
+=item 3.
+
+Enter into your news user crontab something like:
+
+ 6 6 * * * ctlinnd flush inpaths!
+
+(the actual time doesn't matter). This will force B<ninpaths> to generate
+a dump file once a day.
+
+=item 4.
+
+Once per month, run the B<sendinpaths> script, which collects the dumps,
+makes a report, and then deletes the old dumps. (You can generate a
+report without mailing it and without deleting it with C<sendinpaths -n>.)
+
+=back
+
+=head1 OPTIONS
+
+=over 4
+
+=item B<-d> I<dumpfile>
+
+Save dumps in I<dumpfile>. Any C<%d> in I<dumpfile> will be replaced with
+the current system time when the dump is made. This option should be used
+with B<-p>.
+
+=item B<-p>
+
+Read Path lines from standard input.
+
+=item B<-r> I<site>
+
+Generate a report for I<site>. Generally I<site> should be the value of
+I<pathhost> from F<inn.conf>.
+
+=item B<-u> I<dumpfile>
+
+Read data from I<dumpfile>. This option can be repeated to read data from
+multiple dump files.
+
+=item B<-v> I<level>
+
+Set the verbosity level of the report. Valid values for I<level> are 0,
+1, and 2, with 2 being the default.
+
+=back
+
+=head1 NOTES
+
+If your INN doesn't have the WP feed flag (1.5 does not, 1.6 does, 1.7 I
+don't know, 2.0 and later all do), use the following newsfeeds entry:
+
+ inpaths!:*:Tc,WH:ginpaths
+
+where B<ginpaths> is the following script:
+
+ #!/bin/sh
+ exec egrep '^Path: ' | ninpaths -p -d <pathlog>/path/inpaths.%d
+
+replacing <pathlog> as above.
+
+=head1 SEE ALSO
+
+newsfeeds(5), sendinpaths(8)
+
+This is a slightly modified version of Olaf Titz's original ninpaths
+program, which is posted to alt.sources and kept on his WWW archive under
+L<http://sites.inka.de/~bigred/sw/>.
+
+=head1 HISTORY
+
+B<ninpaths> was written by Olaf Titz <olaf@bigred.inka.de>.
+
+The idea and some implementation details for ninpaths come from the
+original inpaths program, but most of the code has been rewritten for
+clarity. This program is in the public domain.
+
+=cut
--- /dev/null
+=head1 NAME
+
+nnrpd - NNTP server for reader clients
+
+=head1 SYNOPSIS
+
+B<nnrpd> [B<-DfnoSt>] [B<-b> I<address>] [B<-c> I<configfile>]
+[B<-g> I<shadowgroup>>] [B<-i> I<initial>] [B<-I> I<instance>] [B<-p> I<port>]
+[B<-P> I<prefork>] [B<-r> I<reason>] [B<-s> I<padding>]
+
+=head1 DESCRIPTION
+
+B<nnrpd> is an NNTP server for newsreaders. It accepts commands on its
+standard input and responds on its standard output. It is normally
+invoked by innd(8) with those descriptors attached to a remote client
+connection. B<nnrpd> also supports running as a standalone daemon.
+
+Unlike innd(8) B<nnrpd> supports all NNTP commands for user-oriented
+reading and posting. B<nnrpd> uses the F<readers.conf> file to control
+who is authorized to access the Usenet database.
+
+On exit, B<nnrpd> will report usage statistics through syslog(3).
+
+B<nnrpd> only reads config files (both F<readers.conf> and F<inn.conf>)
+when it is spawned. You can therefore never change the behavior of a
+client that's already connected. If B<nnrpd> is run from B<innd> (the
+default) or from inetd(8), xinetd(8), or some equivalent, a new B<nnrpd>
+process is spawned for every connection and therefore any changes to
+configuration files will be immediately effective for all new
+connections. If you are instead running B<nnrpd> with the B<-D> option,
+any configuration changes won't take effect until B<nnrpd> is restarted.
+
+The F<inn.conf> setting I<nnrpdflags> can be used to pass any of the
+options below to instances of B<nnrpd> that are spawned directly from
+B<innd>. Many options only make sense when B<-D> is used, so these
+options should not be used with I<nnrpdflags>. See also the discussion
+of I<nnrpdflags> in inn.conf(5).
+
+When I<nnrpdloadlimit> in F<inn.conf> is not 0, it will also reject
+connections if the load average is greater than that value (typically 16).
+B<nnrpd> can also prevent high-volume posters from abusing your
+resources. See the discussion of exponential backoff in inn.conf(5).
+
+=head1 OPTIONS
+
+=over 4
+
+=item B<-b> I<address>
+
+The B<-b> parameter instructs B<nnrpd> to bind to the specified IP
+address when started as a standalone daemon using the B<-D> flag. This
+has to be a valid IPv4 or IPv6 address belonging to an interface of
+the local host. It can also be ::0 (although the default is 0.0.0.0
+if unspecified).
+
+=item B<-c> I<configfile>
+
+By default, B<nnrpd> reads the F<readers.conf> to determine how to
+authenticate connections. The B<-c> flag specifies an alternate file
+for this purpose. If the file name isn't fully qualified, it is taken
+to be relative to I<pathetc> in F<inn.conf> (this is useful to have
+several instances of B<nnrpd> running on different ports or IP
+addresses with different settings.)
+
+=item B<-D>
+
+If specified, this parameter causes B<nnrpd> to operate as a
+daemon. That is, it detaches itself and runs in the background,
+forking a process for every connection. By default B<nnrpd> listens on
+the NNTP port (119), so either innd(8) has to be started on another
+port or B<nnrpd> B<-p> parameter. Note that with this parameter,
+B<nnrpd> continues running until killed. This means that it reads
+F<inn.conf> once on startup and never again until restarted. B<nnrpd>
+should therefore be restarted if inn.conf is changed.
+
+When started in daemon mode, B<nnrpd> will write its PID into a file in
+the I<pathrun> directory. The file will be named F<nnrpd-%d.pid>, where
+C<%d> is replaced with the port that B<nnrpd> is configured to listen on
+(119 unless the B<-p> option is given).
+
+=item B<-f>
+
+If specified, B<nnrpd> does not detach itself and runs in the
+foreground when started as a standalone daemon using the B<-D> flag.
+
+=item B<-g> I<shadowgroup>
+
+On systems that have a shadow password file, B<nnrpd> tries to add the
+group I<shadow> as a supplementary group if it is running in
+standalone mode. On many systems, members of that group have read
+permission for the shadow password file. The B<-g> parameter instructs
+B<nnrpd> to try to add the named group as a supplementary group on
+shadow systems instead of I<shadow>. This only works if
+C<HAVE_GETSPNAM> in F<include/config.h> is defined and B<nnrpd> is
+running in standalone mode since this call only works when B<nnrpd> is
+started as root.
+
+=item B<-i> I<initial>
+
+Specify an initial command to B<nnrpd>. When used, I<initial> is taken
+as if it were the first command received by B<nnrpd>.
+
+=item B<-I> I<instance>
+
+If specified I<instance> is used as an additional static portion
+within MessageIDs generated by B<nnrpd>; typically this option would
+be used where a cluster of machines exist with the same virtual
+hostname and must be disambiguated during posts.
+
+=item B<-n>
+
+The B<-n> flag turns off resolution of IP addresses to names. If you
+only use IP-based restrictions in F<readers.conf> and can handle IP
+addresses in your logs, using this flag may result in some additional
+speed.
+
+=item B<-o>
+
+The B<-o> flag causes all articles to be spooled instead of sending
+them to innd(8). B<rnews> with the B<-U> flag should be invoked from
+cron on a regular basis to take care of these articles. This flag is
+useful if innd(8) in accepting articles and B<nnrpd> is started
+standalone or using inetd(8).
+
+=item B<-p> I<port>
+
+The B<-p> parameter instructs B<nnrpd> to listen on I<port> when
+started as a standalone daemon using the B<-D> flag.
+
+=item B<-P> I<prefork>
+
+The B<-P> parameter instructs B<nnrpd> to prefork I<prefork> children
+awaiting connections when started as a standalone daemon using the
+B<-D> flag.
+
+=item B<-r> I<reason>
+
+If the B<-r> flag is used, then B<nnrpd> will reject the incoming
+connection giving I<reason> as the text. This flag is used by innd(8)
+when it is paused or throttled.
+
+=item B<-s> I<padding>
+
+As each command is received, B<nnrpd> tries to change its C<argv>
+array so that ps(1) will print out the command being executed. To get
+a full display, the B<-s> flag may be used with a long string as its
+argument, which will be overwritten when the program changes its
+title.
+
+=item B<-S>
+
+If specified, B<nnrpd> will start a negotiation for SSL session as
+soon as connected. To use this flag, C<--with-openssl> must have been
+specified at C<configure> time.
+
+=item B<-t>
+
+If the B<-t> flag is used then all client commands and initial
+responses will be traced by reporting them in syslog. This flag is set
+by innd(8) under the control of the ctlinnd(8) C<trace> command, and
+is toggled upon receipt of a C<SIGHUP>; see signal(2).
+
+=back
+
+=head1 SSL SUPPORT
+
+If INN is built with C<--with-openssl>, B<nnrpd> will support news reading
+over TLS (also known as SSL). For clients that use the STARTTLS command,
+no special configuration is needed beyond creating a TLS/SSL certificate
+for the server. You should do this in exactly the same way that you would
+generate a certificate for a web server.
+
+If you're happy with a self-signed certificate (which will generate
+warnings with some news reader clients), you can create and install one in
+the default path by running C<make cert> after C<make install> when
+installing INN, or by running the following commands:
+
+ openssl req -new -x509 -nodes -out /usr/local/news/lib/cert.pem \
+ -days 366 -keyout /usr/local/news/lib/key.pem
+ chown news:news /usr/local/news/lib/cert.pem
+ chmod 640 /usr/local/news/lib/cert.pem
+ chown news:news /usr/local/news/lib/key.pem
+ chmod 600 /usr/local/news/lib/key.pem
+
+Replace the paths with something appropriate to your INN installation.
+This will create a self-signed certificate that will expire in a year.
+The B<openssl> program will ask you a variety of questions about your
+organization. Enter the fully qualified domain name of the server as the
+name the certificate is for.
+
+Most news clients currently do not use the STARTTLS command, however, and
+instead expect to connect to a separate port (563) and start an SSL
+negotiation immediately. B<innd> does not, however, know how to listen
+for connections to that port and then spawn B<nnrpd> the way that it does
+for regular reader connections. You will therefore need to arrange for
+B<nnrpd> to listen on that port through some other means. This can be
+done with the B<-D> flag (and C<-P 563>), but the easiest way is probably
+to add a line like:
+
+ nntps stream tcp nowait news /usr/lib/news/bin/nnrpd nnrpd -S
+
+to F</etc/inetd.conf> or the equivalent on your system and let B<inetd>
+run B<nnrpd>. (Change the path to B<nnrpd> to match your installation if
+needed.) You may need to replace C<nntps> with C<563> if C<nntps> isn't
+defined in F</etc/services> on your system.
+
+=head1 PROTOCOL DIFFERENCES
+
+B<nnrpd> implements the NNTP commands defined in RFC 977, with the
+following differences:
+
+=over 4
+
+=item 1.
+
+The C<slave> command is not implemented. This command has never been
+fully defined.
+
+=item 2.
+
+The C<list> command may be followed by the optional word C<active.times>,
+C<distributions>, C<distrib.pats>, C<moderators>, C<newsgroups>,
+C<subscriptions>, or C<Ioverview.fmt> to get a list of when newsgroups
+where created, a list of valid distributions, a file specifying default
+distribution patterns, moderators list, a one-per-line description of the
+current set of newsgroups, a list of the automatic group subscriptions, or
+a listing of the F<overview.fmt> file.
+
+The command C<list active> is equivalent to the C<list> command. This
+is a common extension.
+
+=item 3.
+
+The C<xhdr>, C<authinfo user> and C<authinfo pass> commands are
+implemented. These are based on the reference Unix implementation. See
+RFC 2980.
+
+=item 4.
+
+A new command, C<xpat header range|MessageID pat [morepat...]>, is
+provided. The first argument is the case-insensitive name of the header
+to be searched. The second argument is either an article range or a
+single Message-ID, as specified in RFC 977. The third argument is a
+C<uwildmat>(3)-style pattern; if there are additional arguments they are
+joined together separated by a single space to form the complete pattern.
+This command is similar to the C<xhdr> command. It returns a C<221>
+response code, followed by the text response of all article numbers that
+match the pattern.
+
+=item 5.
+
+The C<listgroup group> command is provided. This is a comment extension.
+It is equivalent to the C<group> command, except that the reply is a
+multi-line response containing the list of all article numbers in the
+group.
+
+=item 6.
+
+The C<xgtitle [group]> command is provided. This extension is used by
+ANU-News. It returns a C<282> reply code, followed by a one-line
+description of all newsgroups thatmatch the pattern. The default is the
+current group.
+
+=item 7.
+
+The C<xover [range]> command is provided. It returns a C<224> reply code,
+followed by the overview data for the specified range; the default is to
+return the data for the current article.
+
+=item 8.
+
+The C<xpath MessageID> command is provided; see innd(8).
+
+=item 9.
+
+The C<date> command is provided; this is based on the draft NNTP protocol
+revision (draft-ietf-nntpext-imp-04.txt). It returns a one-line response
+code of C<111> followed by the GMT date and time on the server in the form
+C<YYYYMMDDhhmmss>.
+
+=back
+
+=head1 HISTORY
+
+Written by Rich $alz <rsalz@uunet.uu.net> for InterNetNews. Overview
+support added by Rob Robertston <rob@violet.berkeley.edu> and Rich in
+January, 1993. Exponential backoff (for posting) added by Dave Hayes in
+Febuary 1998.
+
+$Id: nnrpd.pod 7751 2008-04-06 14:35:40Z iulius $
+
+=head1 SEE ALSO
+
+ctlinnd(8), innd(8), inn.conf(5), signal(2), uwildmat(3).
--- /dev/null
+=head1 NAME
+
+ovdb - Overview storage method for INN
+
+=head1 DESCRIPTION
+
+Ovdb is a storage method that uses the BerkeleyDB library to store
+overview data. It requires version 2.6.x or later of the BerkeleyDB
+library, but has mostly been tested with version 3 and 4.
+
+Ovdb makes use of the full transaction/logging/locking functionality of
+the BerkeleyDB environment. BerkeleyDB may be downloaded from
+L<http://www.sleepycat.com> and is needed to build the ovdb backend.
+
+=head1 UPGRADING
+
+This is version 2 of ovdb. If you have a database created with a previous
+version of ovdb (such as the one shipped with INN 2.3.0) your database
+will need to be upgraded using ovdb_init(8). See the man page
+ovdb_init(8) for upgrade instructions.
+
+=head1 INSTALLATION
+
+To build ovdb support into INN, specify the option C<--with-berkeleydb>
+when running the configure script. By default, configure will search for
+a BerkeleyDB tree in several likely locations, and choose the highest
+version (based on the name of the directory, e.g., F<BerkeleyDB.3.0>) that
+it finds. There will be a message in the configure output indicating the
+chosen pathname.
+
+You can override this pathname by adding a path to the option, e.g.,
+C<--with-berkeleydb=/usr/BerkeleyDB.3.1>. This directory is expected to
+have subdirectories F<include> and F<lib>, containing F<db.h>, and the
+library itself, respectively.
+
+The ovdb database will take up more disk space for a given spool than the
+other overview methods. Plan on needing at least 1.1 KB for every article
+in your spool (not counting crossposts). So, if you have 5 million
+articles, you'll need at least 5.5 GB of disk space for ovdb. With
+BerkeleyDB 2.x, the db files are 'grow only'; the library will not shrink
+them, even if data is removed. So, reserving extra space above the
+estimate is a good idea. Plus, you'll need additional space for
+transaction logs: at least 100 MB. By default the transaction logs go in
+the same directory as the database. To improve performance, they can be
+placed on a different disk -- see the DB_CONFIG section.
+
+=head1 CONFIGURATION
+
+To enable ovdb, set the I<ovmethod> parameter in F<inn.conf> to C<ovdb>.
+The ovdb database is stored in the directory specified by the
+I<pathoverview> paramter in F<inn.conf>. This is the "DB_HOME" directory.
+To start out, this directory should be empty (other than an optional
+F<DB_CONFIG> file; see L<DB_CONFIG> for details) and B<innd> (or
+B<makehistory>) will create the files as necessary in that directory.
+Make sure the directory is owned by the news user.
+
+Other parameters for configuring ovdb are in the ovdb.conf(5)
+configuration file. See also the sample F<ovdb.conf>.
+
+=over 4
+
+=item cachesize
+
+Size of the memory pool cache, in kilobytes. The cache will have a
+backing store file in the DB directory which will be at least as big. In
+general, the bigger the cache, the better. Use C<ovdb_stat -m> to see
+cache hit percentages. To make a change of this parameter take effect,
+shut down and restart INN (be sure to kill all of the nnrpds when shutting
+down). Default is 8000, which is adequate for small to medium sized
+servers. Large servers will probably need at least 20000.
+
+=item numdbfiles
+
+Overview data is split between this many files. Currently, B<innd> will
+keep all of the files open, so don't set this too high or B<innd> may run
+out of file descriptors. B<nnrpd> only opens one at a time, regardless.
+May be set to one, or just a few, but only do that if your OS supports
+large (>2G) files. Changing this parameter has no effect on an
+already-established database. Default is 32.
+
+=item txn_nosync
+
+If txn_nosync is set to false, BerkeleyDB flushes the log after every
+transaction. This minimizes the number of transactions that may be lost
+in the event of a crash, but results in significantly degraded
+performance. Default is true.
+
+=item useshm
+
+If useshm is set to true, BerkeleyDB will use shared memory instead of
+mmap for its environment regions (cache, lock, etc). With some platforms,
+this may improve performance. Default is false. This parameter is
+ignored if you have BerkeleyDB 2.x
+
+=item shmkey
+
+Sets the shared memory key used by BerkeleyDB when 'useshm' is true.
+BerkeleyDB will create several (usually 5) shared memory segments, using
+sequentially numbered keys starting with 'shmkey'. Choose a key that does
+not conflict with any existing shared memory segments on your system.
+Default is 6400. This parameter is only used with BerkeleyDB 3.1 or
+newer.
+
+=item pagesize
+
+Sets the page size for the DB files (in bytes). Must be a power of 2.
+Best choices are 4096 or 8192. The default is 8192. Changing this
+parameter has no effect on an already-established database.
+
+=item minkey
+
+Sets the minimum number of keys per page. See the BerkeleyDB
+documentation for more info. Default is based on page size:
+
+ default_minkey = MAX(2, pagesize / 2048 - 1)
+
+The lowest allowed minkey is 2. Setting minkey higher than the default is
+not recommended, as it will cause the databases to have a lot of overflow
+pages. Changing this parameter has no effect on an already-established
+database.
+
+=item maxlocks
+
+Sets the BerkeleyDB "lk_max" parameter, which is the maxmium number of
+locks that can exist in the database at the same time. Default is 4000.
+
+=item nocompact
+
+The nocompact parameter affects expireover's behavior. The expireover
+function in ovdb can do its job in one of two ways: by simply deleting
+expired records from the database, or by re-writing the overview records
+into a different location leaving out the expired records. The first
+method is faster, but it leaves 'holes' that result in space that can not
+immediately be reused. The second method 'compacts' the records by
+rewriting them.
+
+If this parameter is set to 0, expireover will compact all newsgroups; if
+set to 1, expireover will not compact any newsgroups; and if set to a
+value greater than one, expireover will only compact groups that have less
+than that number of articles.
+
+Experience has shown that compacting has minimal effect (other than
+making expireover take longer) so the default is now 1. This parameter
+will probably be removed in the future.
+
+=item readserver
+
+Normally, each nnrpd process directly accesses the BerkeleyDB environment.
+The process of attaching to the database (and detaching when finished) is
+fairly expensive, and can result in high loads in situations when there
+are lots of reader connections of relatively short duration.
+
+When the readserver parameter is "true", the nnrpds will access overview
+via a helper server (B<ovdb_server> -- which is started by B<ovdb_init>).
+This can also result in cleaner shutdowns for the database, improving
+stability and avoiding deadlocks and corrupted databases. If you are
+experiencing any instability in ovdb, try setting this parameter to true.
+Default is false.
+
+=item numrsprocs
+
+This parameter is only used when I<readserver> is true. It sets the
+number of ovdb_server processes. As each ovdb_server can process only one
+transaction at a time, running more servers can improve reader response
+times. Default is 5.
+
+=item maxrsconn
+
+This parameter is only used when I<readserver> is true. It sets a maximum
+number of readers that a given ovdb_server process will serve at one time.
+This means the maximum number of readers for all of the ovdb_server
+processes is (numrsprocs * maxrsconn). Default is 0, which means an
+umlimited number of connections is allowed.
+
+=back
+
+=head1 DB_CONFIG
+
+A file called F<DB_CONFIG> may be placed in the database directory to
+customize where the various database files and transaction logs are
+written. By default, all of the files are written in the "DB_HOME"
+directory. One way to improve performance is to put the transaction logs
+on a different disk. To do this, put:
+
+ DB_LOG_DIR /path/to/logs
+
+in the F<DB_CONFIG> file. If the pathname you give starts with a /, it is
+treated as an absolute path; otherwise, it is relative to the "DB_HOME"
+directory. Make sure that any directories you specify exist and have
+proper ownership/mode before starting INN, because they won't be created
+automatically. Also, don't change the DB_CONFIG file while anything that
+uses ovdb is running.
+
+Another thing that you can do with this file is to split the overview
+database across multiple disks. In the F<DB_CONFIG> file, you can list
+directories that BerkeleyDB will search when it goes to open a database.
+
+For example, let's say that you have I<pathoverview> set to
+F</mnt/overview> and you have four additional file systems created on
+F</mnt/ov?>. You would create a file "/mnt/overview/DB_CONFIG" containing
+the following lines:
+
+ set_data_dir /mnt/overview
+ set_data_dir /mnt/ov1
+ set_data_dir /mnt/ov2
+ set_data_dir /mnt/ov3
+ set_data_dir /mnt/ov4
+
+(For BerkeleyDB 2.x, replace C<set_data_dir> with C<DB_DATA_DIR>.)
+
+Distribute your ovNNNNN files into the four filesystems. (say, 8 each).
+When called upon to open a database file, the db library will look for it
+in each of the specified directories (in order). If said file is not
+found, one will be created in the first of those directories.
+
+Whenever you change DB_CONFIG or move database files around, make sure all
+news processes that use the database are shut down first (including
+nnrpds).
+
+The DB_CONFIG functionality is part of BerkeleyDB itself, rather than
+something provided by ovdb. See the BerkeleyDB documentation for complete
+details for the version of BerkeleyDB that you're running.
+
+=head1 RUNNING
+
+When starting the news system, B<rc.news> will invoke B<ovdb_init>.
+B<ovdb_init> must be run before using the database. It performs the
+following tasks:
+
+=over 4
+
+=item *
+
+Creates the database environment, if necessary.
+
+=item *
+
+If the database is idle, it performs a normal recovery. The recovery will
+remove stale locks, recreate the memory pool cache, and repair any damage
+caused by a system crash or improper shutdown.
+
+=item *
+
+Starts the DB housekeeping processes (B<ovdb_monitor>) if they're not
+already running.
+
+=back
+
+And when stopping INN, B<rc.news> kills the ovdb_monitor processes after
+the other INN processes have been shut down.
+
+=head1 DIAGNOSTICS
+
+Problems relating to ovdb are logged to news.err with "OVDB" in the error
+message.
+
+INN programs that use overview will fail to start up if the ovdb_monitor
+processes aren't running. Be sure to run B<ovdb_init> before running
+anything that accesses overview.
+
+Also, INN programs that use overview will fail to start up if the user
+running them is not the "news" user.
+
+If a program accessing the database crashes, or otherwise exits uncleanly,
+it might leave a stale lock in the database. This lock could cause other
+processes to deadlock on that stale lock. To fix this, shut down all news
+processes (using C<kill -9> if necessary) and then restart. B<ovdb_init>
+should perform a recovery operation which will remove the locks and repair
+damage caused by killing the deadlocked processes.
+
+=head1 FILES
+
+=over 4
+
+=item inn.conf
+
+The I<ovmethod> and I<pathoverview> parameters are relevant to ovdb.
+
+=item ovdb.conf
+
+Optional configuration file for tuning. See L<CONFIGURATION> above.
+
+=item I<pathoverview>
+
+Directory where the database goes. BerkeleyDB calls it the 'DB_HOME'
+directory.
+
+=item I<pathoverview>/DB_CONFIG
+
+Optional file to configure the layout of the database files.
+
+=item I<pathrun>/ovdb.sem
+
+A file that gets locked by every process that is accessing the database.
+This is used by B<ovdb_init> to determine whether the database is active
+or quiescent.
+
+=item I<pathrun>/ovdb_monitor.pid
+
+Contains the process ID of B<ovdb_monitor>.
+
+=back
+
+=head1 TO DO
+
+Implement a way to limit how many databases can be open at once (to reduce
+file descriptor usage); maybe using something similar to the cache code in
+ov3.c
+
+=head1 HISTORY
+
+Written by Heath Kehoe <hakehoe@avalon.net> for InterNetNews
+
+=head1 SEE ALSO
+
+inn.conf(5), innd(8), nnrpd(8), ovdb_init(8), ovdb_monitor(8),
+ovdb_stat(8)
+
+BerkeleyDB documentation: in the F<docs> directory of the BerkeleyDB
+source distribution, or on the Sleepycat web page:
+L<http://www.sleepycat.com/>.
+
+=cut
--- /dev/null
+=head1 NAME
+
+ovdb_init - Prepare ovdb database for use
+
+=head1 SYNOPSYS
+
+ovdb_init [C<-u>|C<-r>]
+
+=head1 DESCRIPTION
+
+This command must be run before any other process can access the
+overview database. It performs the following steps:
+
+=over 4
+
+=item 1
+
+Creates the database environment, if necessary
+
+=item 2
+
+If the database is idle (and if the C<-u> option is not specified),
+it performs a normal recovery. The recovery will remove stale locks,
+recreate the memory pool cache, and repair any damage caused by a system
+crash or improper shutdown.
+
+=item 3
+
+If the C<-u> option is specified, it performs any necessary upgrades
+to the database. See the UPGRADING section below.
+
+=item 4
+
+Starts the DB housekeeping processes (ovdb_monitor) if they're not
+already running. (Unless the C<-r> option is specified).
+
+=item 5
+
+Starts the ovdb readserver (ovdb_server) processes if C<readserver>
+in F<ovdb.conf> is C<true>, and if they're not
+already running. (Unless the C<-r> option is specified).
+
+=back
+
+Returns exit status of 0 if all steps were completed successfully.
+In the event of an error, messages are written to syslog and/or stderr.
+
+If a recovery was attempted but it failed, the database may be
+damaged beyond repair, requiring a rebuild with makehistory(8).
+
+This command is normally invoked automatically by rc.news(8).
+
+It is OK to run this command multiple times.
+
+=head1 OPTIONS
+
+=over 4
+
+=item C<-r>
+
+Perform recovery only. C<ovdb_monitor> is not started.
+
+=item C<-u>
+
+Perform any needed upgrades. Recovery is not attempted.
+C<ovdb_monitor> is started if the upgrade succeeded.
+
+=back
+
+=head1 UPGRADING
+
+There are two situations in which the database will need to be
+upgraded:
+
+=over 4
+
+=item *
+
+You upgrade the BerkeleyDB library to a newer version, for example
+from 2.7.7 to 3.1.17. In this case, the BerkeleyDB db->upgrade()
+method is used.
+
+=item *
+
+You upgrade ovdb to a newer major version; i.e., ovdb-1.0 to ovdb-2.0.
+
+=back
+
+In both of these cases, the database is upgraded in-place; and the
+upgrade can not be undone. Do not interrupt the upgrade process once
+it has started, because there is a risk of irrepairable corruption.
+The upgrade may take several minutes to complete.
+If an upgrade does get interrupted, try running the upgrade again.
+
+Here's an example procedure to upgrade a database created with BerkeleyDB
+2.7.7 to use BerkeleyDB 3.1.17:
+
+=over 4
+
+=item 1
+
+Build and install the BerkeleyDB 3.1.17
+
+=item 2
+
+Run configure in the INN source tree and make sure it picks up the
+right BerkeleyDB directory (e.g., /usr/local/BerkeleyDB.3.1)
+
+=item 3
+
+Do a C<make>
+
+=item 4
+
+Shut down INN (e.g., with C<rc.news stop>). Be sure to kill all nnrpds as
+well.
+
+=item 5
+
+Do a C<make update> to install the new binaries.
+
+=item 6
+
+Run C<ovdb_init -u> as the news user.
+
+=item 7
+
+Start INN with C<rc.news>
+
+=back
+
+It is OK to specify C<-u> even if no upgrades are needed.
+
+=head1 HISTORY
+
+Written by Heath Kehoe E<lt>hakehoe@avalon.netE<gt> for InterNetNews.
+
+=head1 SEE ALSO
+
+ovdb(5), makehistory(8)
+
+=cut
--- /dev/null
+=head1 NAME
+
+ovdb_monitor - Database maintenance
+
+=head1 SYNOPSYS
+
+Use C<ovdb_init> to start ovdb_monitor
+
+=head1 DESCRIPTION
+
+When started (by C<ovdb_init>), C<ovdb_monitor> forks three processes
+that perform routine database maintenance tasks. These are:
+transaction checkpointing, deadlock detection, and transaction log
+removal. The process ID of the parent is written to
+F<I<pathrun>/ovdb_monitor.pid>. This PID is used by other INN
+commands to verify that ovdb_monitor is running.
+
+To shut down ovdb_monitor, send a TERM signal to the process ID
+in F<I<pathrun>/ovdb_monitor.pid> . The parent process will shut
+down the three children and wait for their exit before exiting itself.
+
+=head1 HISTORY
+
+Written by Heath Kehoe E<lt>hakehoe@avalon.netE<gt> for InterNetNews.
+
+=head1 SEE ALSO
+
+ovdb(5), ovdb_init(8)
+
+=cut
--- /dev/null
+=head1 NAME
+
+ovdb_server - overview 'helper' server
+
+=head1 SYNOPSYS
+
+Use C<ovdb_init> to start ovdb_server
+
+=head1 DESCRIPTION
+
+If the C<readserver> parameter in F<ovdb.conf> is true,
+C<ovdb_init> will start C<ovdb_server>.
+
+C<ovdb_server> opens the overview database, and accesses it
+on behalf of the nnrpd reader processes.
+
+To shut down ovdb_server, send a TERM signal to the process ID
+in F<I<pathrun>/ovdb_server.pid> . The parent process will shut
+down its children and wait for their exit before exiting itself.
+
+=head1 HISTORY
+
+Written by Heath Kehoe E<lt>hakehoe@avalon.netE<gt> for InterNetNews.
+
+=head1 SEE ALSO
+
+ovdb(5), ovdb_init(8)
+
+=cut
--- /dev/null
+=head1 NAME
+
+ovdb_stat - Display information from the ovdb database
+
+=head1 SYNOPSYS
+
+B<ovdb_stat> B<-Hgci> [B<-r> I<artnumrange>] newsgroup [newsgroup ...]
+
+B<ovdb_stat> B<-Hklmtv> [B<-d> I<database>]
+
+=head1 DESCRIPTION
+
+B<ovdb_stat> displays information from the ovdb database: BerkeleyDB
+statistics, newsgroup data, and overview records; and optionally
+outputs in HTML format.
+
+=head1 OPTIONS
+
+=over 4
+
+=item B<-g>
+
+Newsgroup himark, lowmark, article count, and flag for the given newsgroups
+(as stored in the ovdb "groupinfo" database) are displayed.
+
+=item B<-c>
+
+Similar to B<-g>, except the himark, lowmark, and count are calculated
+by actually scanning the overview records and counting them.
+This can be a lengthy operation on groups with lots of articles.
+
+=item B<-i>
+
+Internal data regarding the given newsgroups are displayed.
+
+=item B<-r> I<artnumrange>
+
+Overview records are retrieved. The I<artnumrange> parameter may be
+a single article number, or a range of articles in the format C<low-hi>.
+
+=item B<-H>
+
+Output is presented in HTML format.
+
+=item B<-k>
+
+Displays lock region statistics, as returned by the BerkeleyDB lock_stat()
+call.
+
+=item B<-l>
+
+Displays log region statistics, as returned by the BerkeleyDB log_stat()
+call.
+
+=item B<-m>
+
+Displays global memory pool statistics, as returned by the
+BerkeleyDB memp_stat() call.
+
+=item B<-M>
+
+Same as B<-m>, and also displays memory pool statistics for each
+database file.
+
+=item B<-t>
+
+Displays log region statistics, as returned by the BerkeleyDB txn_stat()
+call.
+
+=item B<-v>
+
+Displays ovdb version, and BerkeleyDB version.
+
+=item B<-d> I<database>
+
+Displays information about the given database, as returned by the
+BerkeleyDB db->stat() call. This operation may take a long time
+on busy systems (several minutes or more).
+
+=back
+
+=head1 WARNINGS
+
+ovdb_stat may be safely killed with the INT, TERM, or HUP signals.
+It catches those signals and exits cleanly.
+Do not kill ovdb_stat with other signals, unless absolutely necessary,
+because it may leave stale locks in the DB environment.
+
+=head1 HISTORY
+
+Written by Heath Kehoe E<lt>hakehoe@avalon.netE<gt> for InterNetNews.
+
+=head1 SEE ALSO
+
+ovdb(5)
+
+=cut
--- /dev/null
+=head1 NAME
+
+passwd.nntp - passwords for connecting to remote NNTP servers
+
+=head1 DESCRIPTION
+
+The file F<passwd.nntp> in I<pathetc> contains host / name / password
+triplets for use when authenticating client programs to NNTP servers.
+This file is normally interpreted by NNTPsendpassword() in libinn(3).
+Blank lines and lines beginning with a number sign (C<#>) are ignored.
+All other lines should consist of three or four fields separated by
+colons:
+
+ host:name:password
+ host:name:password:style
+
+The first field is the name of a host, and is matched in a
+case-insensitive manner. (No detailed matching, such as comparing IP
+addresses, is done.)
+
+The second field is a user name, and the third is a password. If either
+the username or password is empty, then that portion of the
+authentication will not occur. (For example, when connecting to a
+remote INN for peering, only the password is needed.)
+
+The optional fourth field specifies the type of authentication to use.
+At present, the only recognized "authentication style" is C<authinfo>;
+this is also the default. It means that NNTP "authinfo" commands are
+used to authenticate to the remote host. (The C<authinfo> command is a
+common extension to RFC 977.)
+
+For example:
+
+ ## UUNET needs a password, MIT doesn't.
+ mit.edu:bbn::authinfo
+ uunet.uu.net:bbn:yoyoma:authinfo
+
+This file should not be world-readable.
+
+=head1 HISTORY
+
+Written by Rich $alz <rsalz@uunet.uu.net> for InterNetNews. This is
+revision $Revision: 5089 $, dated $Date: 2002-02-03 11:03:41 -0800 (Sun, 03 Feb 2002) $.
+
+$Id: passwd.nntp.pod 5089 2002-02-03 19:03:41Z vinocur $
+
+=head1 SEE ALSO
+
+inn.conf(5), innd(8), libinn(3).
+
+=cut
--- /dev/null
+=head1 NAME
+
+pullnews - Pull news from multiple news servers and feed it to another
+
+=head1 SYNOPSIS
+
+B<pullnews> [B<-hnqRx>] [B<-b> I<fraction>] [B<-c> I<config>] [B<-C> I<width>]
+[B<-d> I<level>] [B<-f> I<fraction>] [B<-F> I<fakehop>] [B<-g> I<groups>]
+[B<-G> I<newsgroups>] [B<-H> I<headers>] [B<-k> I<checkpt>] [B<-l> I<logfile>]
+[B<-m> I<header_pats>] [B<-M> I<num>] [B<-N> I<timeout>] [B<-p> I<port>]
+[B<-P> I<hop_limit>] [B<-Q> I<level>] [B<-r> I<file>] [B<-s> I<to-server>[:I<port>]]
+[B<-S> I<max-run>] [B<-t> I<retries>] [B<-T> I<connect-pause>] [B<-w> I<num>]
+[B<-z> I<article-pause>] [B<-Z> I<group-pause>] [I<from-server> ...]
+
+=head1 REQUIREMENTS
+
+The C<Net::NNTP> module must be installed. This module is available as part
+of the libnet distribution and comes with recent versions of Perl. For
+older versions of Perl, you can download it from L<http://www.cpan.org/>.
+
+=head1 DESCRIPTION
+
+B<pullnews> reads a config file in the running user's home directory
+(normally called F<~/.pullnews>) and connects to the upstream servers
+given there as a reader client. By default, it connects to all servers
+listed in the configuration file, but you can limit B<pullnews> to
+specific servers by listing them on the command line: a whitespace-separated
+list of server names can be specified, like I<from-server> for one of them.
+For each server it connects to, it pulls over articles and feeds them to the
+destination server via the IHAVE or POST commands. This means that the system
+B<pullnews> is run on must have feeding access to the destination news server.
+
+B<pullnews> is designed for very small sites that do not want to bother
+setting up traditional peering and is not meant for handling large feeds.
+
+=head1 OPTIONS
+
+=over 4
+
+=item B<-b> I<fraction>
+
+Backtrack on server numbering reset. Specify the proportion (C<0.0> to C<1.0>)
+of a group's articles to pull when the server's article number is less than
+our high for that group. When I<fraction> is C<1.0>, pull all the articles on
+a renumbered server. The default is to do nothing.
+
+=item B<-c> I<config>
+
+Normally, the config file is stored in F<~/.pullnews> for the user running
+B<pullnews>. If B<-c> is given, I<config> will be used as the config file
+instead. This is useful if you're running B<pullnews> as a system user on
+an automated basis out of cron rather than as an individual user.
+
+See L<CONFIG FILE> below for the format of this file.
+
+=item B<-C> I<width>
+
+Use I<width> characters per line for the progress table. The default value
+is C<50>.
+
+=item B<-d> I<level>
+
+Set the debugging level to the integer I<level>; more debugging output
+will be logged as this increases. The default value is C<0>.
+
+=item B<-f> I<fraction>
+
+This changes the proportion of articles to get from each group to
+I<fraction> and should be in the range C<0.0> to C<1.0> (C<1.0> being
+the default).
+
+=item B<-F> I<fakehop>
+
+Prepend I<fakehop> as a host to the Path: header of articles fed.
+
+=item B<-g> I<groups>
+
+Specify a collection of groups to get. I<groups> is a list of
+newsgroups separated by commas (only commas, no spaces). Each group must
+be defined in the config file, and only the remote hosts that carry those
+groups will be contacted. Note that this is a simple list of groups, not
+a wildmat expression, and wildcards are not supported.
+
+=item B<-G> I<newsgroups>
+
+Add the comma-separated list of groups I<newsgroups> to each server in the
+configuration file (see also B<-g> and B<-w>).
+
+=item B<-h>
+
+Print a usage message and exit.
+
+=item B<-H> I<headers>
+
+Remove these named headers (colon-separated list) from fed articles.
+
+=item B<-k> I<checkpt>
+
+Checkpoint (save) the config file every I<checkpt> articles
+(default is C<0>, that is to say at the end of the session).
+
+=item B<-l> I<logfile>
+
+Log progress/stats to I<logfile> (default is C<stdout>).
+
+=item B<-m> I<header_pats>
+
+Feed an article based on header matching. The argument is a number of
+whitespace-separated tuples (each tuple being a colon-separated header and
+regular expression). For instance:
+
+ Hdr1:regexp1 !Hdr2:regexp2
+
+specifies that the article will be passed only if the C<Hdr1:> header
+matches C<regexp1> and the C<Hdr2:> header does not match C<regexp2>.
+
+=item B<-M> I<num>
+
+Specify the maximum number of articles (per group) to process.
+The default is to process all new articles. See also B<-f>.
+
+=item B<-n>
+
+Do nothing but read articles S<-- does> not feed articles downstream,
+writes no B<rnews> file, does not update the config file.
+
+=item B<-N> I<timeout>
+
+Specify the timeout length, as I<timeout> seconds,
+when establishing an NNTP connection.
+
+=item B<-p> I<port>
+
+Connect to the destination news server on a port other than the default of
+C<119>. This option does not change the port used to connect to the source
+news servers.
+
+=item B<-P> I<hop_limit>
+
+Restrict feeding an article based on the number of hops it has already made.
+Count the hops in the Path: header (I<hop_count>), feeding the article only
+when I<hop_limit> is C<+num> and I<hop_count> is more than I<num>;
+or I<hop_limit> is C<-num> and I<hop_count> is less than I<num>.
+
+=item B<-q>
+
+Print out less status information while running.
+
+=item B<-Q> I<level>
+
+Set the quietness level (C<-Q 2> is equivalent to C<-q>). The higher this
+value, the less gets logged. The default is C<0>.
+
+=item B<-r> I<file>
+
+Rather than feeding the downloaded articles to a destination server, instead
+create a batch file that can later be fed to a server using B<rnews>. See
+rnews(1) for more information about the batch file format.
+
+=item B<-R>
+
+Be a reader (use MODE READER and POST commands) to the downstream
+server. The default is to use the IHAVE command.
+
+=item B<-s> I<to-server>[:I<port>]
+
+Normally, B<pullnews> will feed the articles it retrieves to the news
+server running on localhost. To connect to a different host, specify a
+server with the B<-s> flag. You can also specify the port with this same
+flag or use B<-p>.
+
+=item B<-S> I<max-run>
+
+Specify the maximum time I<max-run> in seconds for B<pullnews> to run.
+
+=item B<-t> I<retries>
+
+The maximum number (I<retries>) of attempts to connect to a server
+(see also B<-T>). The default is C<0>.
+
+=item B<-T> I<connect-pause>
+
+Pause I<connect-pause> seconds between connection retries (see also B<-t>).
+The default is C<1>.
+
+=item B<-w> I<num>
+
+Set each group's high watermark (last received article number) to I<num>.
+If I<num> is negative, calculate S<I<Current>+I<num>> instead (i.e. get the last
+I<num> articles). Therefore, a I<num> of C<0> will re-get all articles on the
+server; whereas a I<num> of C<-0> will get no old articles, setting the
+watermark to I<Current> (the most recent article on the server).
+
+=item B<-x>
+
+If the B<-x> flag is used, an Xref: header is added to any article
+that lacks one. It can be useful for instance if articles are fed
+to a news server which has I<xrefslave> set in F<inn.conf>.
+
+=item B<-z> I<article-pause>
+
+Sleep I<article-pause> seconds between articles. The default is C<0>.
+
+=item B<-Z> I<group-pause>
+
+Sleep I<group-pause> seconds between groups. The default is C<0>.
+
+=back
+
+=head1 CONFIG FILE
+
+The config file for B<pullnews> is divided into blocks, one block for each
+remote server to connect to. A block begins with the host line, which
+must have no leading whitespace and contains just the hostname of the
+remote server, optionally followed by authentication details (username
+and password for that server).
+
+Following the host line should be one or more newsgroup lines which start
+with whitespace followed by the name of a newsgroup to retrieve. Only one
+newsgroup should be listed on each line.
+
+B<pullnews> will update the config file to include the time the group was
+last checked and the highest numbered article successfully retrieved and
+transferred to the destination server. It uses this data to avoid doing
+duplicate work the next time it runs.
+
+The full syntax is:
+
+ <host> [<username> <password>]
+ <group> [<time> <high>]
+ <group> [<time> <high>]
+
+where the <host> line must not have leading whitespace and the <group>
+lines must.
+
+A typical configuration file would be:
+
+ # Format group date high
+ data.pa.vix.com
+ rec.bicycles.racing 908086612 783
+ rec.humor.funny 908086613 18
+ comp.programming.threads
+ nnrp.vix.com pull sekret
+ comp.std.lisp
+
+Note that an earlier run of B<pullnews> has filled in details about the
+last article downloads from the two rec.* groups. The two comp.* groups
+were just added by the user and have not yet been checked.
+
+The nnrp.vix.com server requires authentication, and B<pullnews> will use
+the username C<pull> and the password C<sekret>.
+
+=head1 FILES
+
+=over 4
+
+=item I<pathbin>/pullnews
+
+The Perl script itself used to pull news from upstream servers and feed
+it to another news server.
+
+=item I<$HOME>/.pullnews
+
+The default config file. It is in the running user's home directory
+(normally called F<~/.pullnews>).
+
+=back
+
+=head1 HISTORY
+
+B<pullnews> was written by James Brister for INN. The documentation was
+rewritten in POD by Russ Allbery <rra@stanford.edu>.
+
+Geraint A. Edwards greatly improved B<pullnews>, adding no more than S<16 new>
+recognized flags, fixing some bugs and integrating the B<backupfeed>
+contrib script by Kai Henningsen, adding again S<6 other> flags.
+
+$Id: pullnews.pod 7853 2008-05-27 19:07:45Z iulius $
+
+=head1 SEE ALSO
+
+incoming.conf(5), rnews(1).
+
+=cut
--- /dev/null
+=head1 NAME
+
+qio - Quick I/O routines for reading files
+
+=head1 SYNOPSIS
+
+B<#include E<lt>inn/qio.hE<gt>>
+
+B<QIOSTATE *QIOopen(const char *>I<name>B<);>
+
+B<QIOSTATE *QIOfdopen(int> I<fd>B<);>
+
+B<void QIOclose(QIOSTATE *>I<qp>B<);>
+
+B<char *QIOread(QIOSTATE *>I<qp>B<);>
+
+B<int QIOfileno(QIOSTATE *>I<qp>B<);>
+
+B<size_t QIOlength(QIOSTATE *>I<qp>B<);>
+
+B<int QIOrewind(QIOSTATE *>I<qp>B<);>
+
+B<off_t QIOtell(QIOSTATE *>I<qp>B<);>
+
+B<bool QIOerror(QIOSTATE *>I<qp>B<);>
+
+B<bool QIOtoolong(QIOSTATE *>I<qp>B<);>
+
+=head1 DESCRIPTION
+
+The routines described in this manual page are part of libinn(3). They
+are used to provide quick read access to files; the QIO routines use
+buffering adapted to the block size of the device, similar to stdio, but
+with a more convenient syntax for reading newline-terminated lines. QIO
+is short for "Quick I/O" (a bit of a misnomer, as QIO provides read-only
+access to files only).
+
+The QIOSTATE structure returned by B<QIOopen> and B<QIOfdopen> is the
+analog to stdio's FILE structure and should be treated as a black box by
+all users of these routines. Only the above API should be used.
+
+B<QIOopen> opens the given file for reading. For regular files, if your
+system provides that information and the size is reasonable, QIO will use
+the block size of the underlying file system as its buffer size;
+otherwise, it will default to a buffer of 8 KB. Returns a pointer to use
+for subsequent calls, or NULL on error. B<QIOfdopen> performs the same
+operation except on an already-open file descriptor (I<fd> must designate
+a file open for reading).
+
+B<QIOclose> closes the open file and releases any resources used by the
+QIOSTATE structure. The QIOSTATE pointer should not be used again after
+it has been passed to this function.
+
+B<QIOread> reads the next newline-terminated line in the file and returns
+a pointer to it, with the trailing newline replaced by nul. The returned
+pointer is a pointer into a buffer in the QIOSTATE object and therefore
+will remain valid until B<QIOclose> is called on that object. If EOF is
+reached, an error occurs, or if the line is longer than the buffer size,
+NULL is returned instead. To distinguish between the error cases, use
+B<QIOerror> and B<QIOtoolong>.
+
+B<QIOfileno> returns the descriptor of the open file.
+
+B<QIOlength> returns the length in bytes of the last line returned by
+B<QIOread>. Its return value is only defined after a successful call to
+B<QIOread>.
+
+B<QIOrewind> sets the read pointer back to the beginning of the file and
+reads the first block of the file in anticipation of future reads. It
+returns 0 if successful and -1 on error.
+
+B<QIOtell> returns the current value of the read pointer (the lseek(2)
+offset at which the next line will start).
+
+B<QIOerror> returns true if there was an error in the last call to
+B<QIOread>, false otherwise. B<QIOtoolong> returns true if there was an
+error and the error was that the line was too long. If B<QIOread> returns
+NULL, these functions should be called to determine what happened. If
+B<QIOread> returned NULL and B<QIOerror> is false, EOF was reached. Note
+that if B<QIOtoolong> returns true, the next call to B<QIOread> will try
+to read the remainder of the line and will likely return a partial line;
+users of this library should in general treat long lines as fatal errors.
+
+=head1 EXAMPLES
+
+This block of code opens F</etc/motd> and reads it a line at a time,
+printing out each line preceeded by its offset in the file.
+
+ QIOSTATE *qp;
+ off_t offset;
+ char *p;
+
+ qp = QIOopen("/etc/motd");
+ if (qp == NULL) {
+ perror("Open error");
+ exit(1);
+ }
+ for (p = QIOread(qp); p != NULL; p = QIOread(qp))
+ printf("%ld: %s\n", (unsigned long) QIOtell(qp), p);
+ if (QIOerror(qp)) {
+ perror("Read error");
+ exit(1);
+ }
+ QIOclose(qp);
+
+=head1 HISTORY
+
+Written by Rich $alz <rsalz@uunet.uu.net> for InterNetNews. Updated by
+Russ Allbery <rra@stanford.edu>.
+
+$Id: qio.pod 5909 2002-12-03 05:17:18Z vinocur $
--- /dev/null
+=head1 NAME
+
+radius.conf - Configuration for nnrpd RADIUS authenticator
+
+=head1 DESCRIPTION
+
+This describes the format and attributes of the configuration file for the
+nnrpd RADIUS authenticator. See radius(1) for more information about the
+authenticator program. The default location for this file is
+F<radius.conf> in I<pathetc>.
+
+Blank lines and lines beginning with C<#> are ignored, as is anything
+after a C<#> on a line. All other lines should begin with a parameter
+name followed by a colon and the value of that key, except that each
+section of configuration for a particular server should be enclosed in:
+
+ server <name> {
+ # parameters...
+ }
+
+where <name> is just some convenient label for that server.
+
+The available parameters are:
+
+=over 4
+
+=item I<radhost>
+
+The hostname of the RADIUS server to use for authentication. This
+parameter must be set.
+
+=item I<radport>
+
+The port to query on the RADIUS server. Defaults to 1645 if not set.
+
+=item I<lochost>
+
+The hostname or IP address making the request. The RADIUS server expects
+an IP address; a hostname will be translated into an IP address with
+gethostbyname(). If not given, this information isn't included in the
+request (not all RADIUS setups require this information).
+
+=item I<locport>
+
+The port the client being authenticated is connecting to. If not given,
+defaults to 119. This doesn't need to be set unless readers are
+connecting to a non-standard port.
+
+=item I<secret>
+
+The shared secret with the RADIUS server. If your secret includes spaces,
+tabs, or C<#>, be sure to include it in double quotes. This parameter
+must be set.
+
+=item I<prefix>
+
+Prepend the value of this parameter to all usernames before passing them
+to the RADIUS server. Can be used to prepend something like C<news-> to
+all usernames in order to put news users into a different namespace from
+other accounts served by the same server. If not set, nothing is
+prepended.
+
+=item I<suffix>
+
+Append the value of this parameter to all usernames before passing them to
+the RADIUS server. This is often something like C<@example.com>,
+depending on how your RADIUS server is set up. If not set, nothing is
+appended.
+
+=item I<ignore-source>
+
+Can be set to C<true> or C<false>. If set to false, the RADIUS
+authenticator will check to ensure that the response it receives is from
+the same IP address as it sent the request to (for some added security).
+If set to true, it will skip this verification check (if your RADIUS
+server has multiple IP addresses or if other odd things are going on, it
+may be perfectly normal for the response to come from a different IP
+address).
+
+=back
+
+=head1 EXAMPLE
+
+Here is a configuration for a news server named news.example.com,
+authenticating users against radius.example.com and appending
+C<@example.com> to all client-supplied usernames before passing them to
+the RADIUS server:
+
+ server example {
+ radhost: radius.example.com
+ lochost: news.example.com
+ secret: IamARADIUSsecRET
+ suffix: @example.com
+ }
+
+The shared secret with the RADIUS server is C<IamARADIUSsecRET>.
+
+=head1 HISTORY
+
+This documentation was written by Russ Allbery <rra@stanford.edu> based on
+the comments in the sample radius.conf file by Yury B. Razbegin.
+
+$Id: radius.conf.pod 6736 2004-05-16 23:06:08Z rra $
+
+=head1 SEE ALSO
+
+radius(1)
+
+=cut
--- /dev/null
+=head1 NAME
+
+radius - nnrpd RADIUS password authenticator
+
+=head1 SYNOPSIS
+
+B<radius> [B<-h>] [B<-f> I<config>]
+
+=head1 DESCRIPTION
+
+B<radius> is an nnrpd authenticator, accepting a username and password
+from nnrpd (given to nnrpd by a reader connection) and attempting to
+authenticate that username and password against a RADIUS server. See
+readers.conf(5) for more information on how to configure an nnrpd
+authenticator. It is useful for a site that already does user
+authentication via RADIUS and wants to authenticate news reading
+connections as well.
+
+By default, B<radius> reads I<pathetc>/radius.conf for configuration
+information, but a different configuration file can be specified with
+B<-f>. See radius.conf(5) for a description of the configuration file.
+
+=head1 OPTIONS
+
+=over 4
+
+=item B<-f> I<config>
+
+Read I<config> instead of I<pathetc>/radius.conf for configuration
+information.
+
+=item B<-h>
+
+Print out a usage message and exit.
+
+=back
+
+=head1 EXAMPLE
+
+The following readers.conf(5) fragment tells nnrpd to authenticate all
+connections using this authenticator:
+
+ auth radius {
+ auth: radius
+ default: <FAIL>
+ default-domain: example.com
+ }
+
+C<@example.com> will be appended to the user-supplied identity, and if
+RADIUS authentication failes, the user will be assigned an identity of
+C<E<lt>FAILE<gt>@example.com>.
+
+=head1 BUGS
+
+It has been reported that this authenticator doesn't work with Ascend
+RADIUS servers, but does work with Cistron RADIUS servers. It's also
+believed to work with Livingston's RADIUS server. Contributions to make
+it work better with different types of RADIUS servers would be gratefully
+accepted.
+
+This code has not been audited against the RADIUS protocol and may not
+implement it correctly.
+
+=head1 HISTORY
+
+The RADIUS authenticator was originally written by Aidan Cully. This
+documentation was written by Russ Allbery <rra@stanford.edu>.
+
+$Id: radius.pod 5894 2002-12-01 19:44:18Z rra $
+
+=head1 SEE ALSO
+
+nnrpd(8), radius.conf(5), readers.conf(5)
+
+RFC 2865, Remote Authentication Dial In User Service.
+
+=cut
--- /dev/null
+=head1 NAME
+
+rc.news - Start or stop INN daemons
+
+=head1 SYNOPSIS
+
+B<rc.news> [start | stop]
+
+=head1 DESCRIPTION
+
+B<rc.news> can be used to start or stop B<innd> and supporting programs.
+It checks to make sure INN is not already running, handles cases of
+unclean shutdown, finishes up tasks which might have been interrupted by
+the preceeding shutdown, emails certain boot-time warnings to
+I<newsmaster> (as set in F<inn.conf>), and is generally safer and easier
+than starting and stopping everything directly. It needs to be run as the
+news user so that files in I<pathrun> are created with the right ownership
+(though this is less important for C<rc.news stop>), and therefore
+requires that F<inndstart> be setuid root, see inndstart(8) for
+discussion.
+
+Programs run and stopped by this script include:
+
+=over 4
+
+=item *
+
+Always: B<inndstart> is run, and B<innd> is stopped.
+
+=item *
+
+If I<doinnwatch> is true in F<inn.conf>: B<innwatch> is started and
+stopped.
+
+=item *
+
+If I<docnfsstat> is true in F<inn.conf>: B<ovdb_init> is run;
+B<ovdb_server> and B<ovdb_monitor> are stopped.
+
+=item *
+
+If F<rc.news.local> exists in I<pathbin>: B<rc.news.local> is run with
+argument C<start> or C<stop> (to perform site-specific startup or shutdown
+tasks).
+
+=back
+
+=head1 OPTIONS
+
+=over 4
+
+=item C<start>
+
+If the first argument is C<start>, or no first argument is given,
+B<rc.news> initiates INN startup.
+
+=item C<stop>
+
+If the first argument is C<stop>, B<rc.news> initiates INN shutdown. It
+is recommended to throttle the server first as described in ctlinnd(8).
+
+=back
+
+=head1 EXAMPLES
+
+To start INN and leave certain error messages going to the terminal:
+
+ su - news -c ~news/bin/rc.news
+
+To run INN at startup time from appropriate system boot scripts:
+
+ su - news -c ~news/bin/rc.news >/dev/console
+
+To stop INN (throttling first):
+
+ ~news/bin/ctlinnd throttle reason
+ su - news -c '~news/bin/rc.news stop'
+
+=head1 BUGS
+
+Running C<rc.news start> as root is never the right thing to do, so we
+should at minimum check for this and error, or perhaps change effective
+user ID.
+
+=head1 HISTORY
+
+// FIXME: any attribution for rc.news itself?
+
+This manual page written by Jeffrey M. Vinocur <jeff@litech.org> for
+InterNetNews.
+
+$Id: rc.news.pod 5908 2002-12-03 04:41:36Z vinocur $
+
+=head1 SEE ALSO
+
+ctlinnd(8),
+cnfsstat(8),
+inn.conf(5),
+inndstart(8),
+innwatch(8),
+ovdb(5).
+
+=cut
+
--- /dev/null
+=head1 NAME
+
+readers.conf - Access control and configuration for nnrpd
+
+=head1 DESCRIPTION
+
+F<readers.conf> in I<pathetc> specifies access control for nnrpd(8). It
+controls who is allowed to connect as a news reader and what they're
+allowed to do after they connect. nnrpd reads this file when it starts
+up. This generally means that any changes take effect immediately on all
+subsequent connections, but B<nnrpd> may have to be restarted if you use
+the B<-D> option. (The location I<pathetc>/readers.conf is only the
+default; the same format applies to any file specified with C<nnrpd -c>.)
+
+There are two types of entries in F<readers.conf>: parameter/value pairs
+and configuration groups. Blank lines and anything after a number sign
+(C<#>) are ignored, unless the character C<#> is escaped with C<\>. The
+maximum number of characters on each line is 8,191.
+
+Parameter/value pairs consist of a keyword immediately followed by a
+colon, at least one whitespace character, and a value. The case of the
+parameter is significant (parameter should generally be in all lowercase),
+and a parameter may contain any characters except colon, C<#>, and
+whitespace. An example:
+
+ hosts: *.example.com
+
+Values that contain whitespace should be quoted with double quotes, as in:
+
+ hosts: "*.example.com, *.example.net"
+
+If the parameter does not contain whitespace, such as:
+
+ hosts: *.example.com,*.example.net
+
+it's not necessary to quote it, although you may wish to anyway for
+clarity.
+
+There is no way to continue a line on the next line, and therefore no way
+to have a single parameter with a value longer than about 8,180
+characters.
+
+Many parameters take a boolean value. For all such parameters, the value
+may be specified as C<true>, C<yes>, or C<on> to turn it on and may be any
+of C<false>, C<no>, or C<off> to turn it off. The case of these values is
+not significant.
+
+There are two basic types of configuration groups, auth and access. The
+auth group provides mechanisms to establish the identity of the user, who
+they are. The access group determines, given the user's identity, what
+that user is permitted to do. Writing a F<readers.conf> file for your
+setup is a two-step process: first assigning an identity to each incoming
+connection using auth groups, and then giving each identity appropriate
+privileges with access group. We recommend I<not> intermingling auth
+groups and access groups in the config file; it is often more sensible (in
+the absence of the I<key> parameter) to put all of the auth groups first,
+and all of the access groups below.
+
+A user identity, as established by an auth group, looks like an e-mail
+address; in other words, it's in the form "<username>@<domain>" (or
+sometimes just "<username>" if no domain is specified.
+
+If I<nnrpdauthsender> is set in F<inn.conf>, the user identity is also put
+into the Sender: header of posts made by that user. See the documentation
+of that option in inn.conf(5) for more details.
+
+An auth group definition looks like:
+
+ auth <name> {
+ hosts: <host-wildmat>
+ auth: <auth-program>
+ res: <res-program>
+ default: <defuser>
+ default-domain: <defdomain>
+ # ...possibly other settings
+ }
+
+The <name> is used as a label for the group and is only for documentation
+purposes. (If your syslog configuration records the C<news.debug>
+facility, the <name> will appear in the debugging output of nnrpd.
+Examining that output can be very helpful in understanding why your
+configuration doesn't do what you expect it to.)
+
+A given auth group applies only to hosts whose name or IP address matches
+the wildmat expression given with the hosts: parameter (comma-separated
+wildmat expressions allowed, but C<@> is not supported). Rather than
+wildmat expressions, you may also use CIDR notation to match any IP
+address in a netblock; for example, "10.10.10.0/24" will match any IP
+address between 10.10.10.0 and 10.10.10.255 inclusive.
+
+If compiled against the SSL libraries, an auth group with the require_ssl:
+parameter set to true only applies if the incoming connection is using
+SSL.
+
+For any connection from a host that matches that wildmat expression or
+netblock, each <res-program> (multiple res: lines may be present in a
+block; they are run in sequence until one succeeds), if any, is run to
+determine the identity of the user just from the connection information.
+If all the resolvers fail, or if the res: parameter isn't present, the
+user is assigned an identity of "<defuser>@<defdomain>"; in other words,
+the values of the default: and default-domain: parameters are used. If
+<res-program> only returns a username, <defdomain> is used as the
+domain.
+
+If the user later authenticates via the AUTHINFO USER/PASS commands, the
+provided username and password are passed to each <auth-program> (multiple
+auth, perl_auth, or python_auth lines may be present in a block; they are
+run in sequence until one succeeds), if any. If one succeeds and returns
+a different identity than the one assigned at the time of the connection,
+it is matched against the available access groups again and the actions
+the user is authorized to do may change. The most common <auth-program>
+to use is B<ckpasswd>, which supports several ways of checking passwords
+including using PAM. See the ckpasswd(8) man page for more details.
+
+When matching auth groups, the last auth group in the file that matches a
+given connection and username/password combination is used.
+
+An access group definition usually looks like:
+
+ access <name> {
+ users: <identity-wildmat>
+ newsgroups: <group-wildmat>
+ # ...possibly other settings
+ }
+
+Again, <name> is just for documentation purposes. This says that all
+users whose identity matches <identity-wildmat> can read and post to all
+newsgroups matching <group-wildmat> (as before, comma-separated wildmat
+expressions are allowed, but C<@> is not supported). Alternately, you can
+use the form:
+
+ access <name> {
+ users: <identity-wildmat>
+ read: <read-wildmat>
+ post: <post-wildmat>
+ }
+
+and matching users will be able to read any group that matches
+<read-wildmat> and post to any group that matches <post-wildmat>. You can
+also set several other things in the access group as well as override
+various inn.conf(5) parameters for just a particular group of users.
+
+Just like with auth groups, when matching access groups the last matching
+one in the file is used to determine the user's permissions. There is
+an exception to this rule: if the auth group which matched the client
+contains a perl_access: or python_access: parameter, then the script
+given as argument is used to dynamically generate an access group.
+This new access group is then used to determine the access rights of
+the client; the access groups in the file are ignored.
+
+There is one additional special case to be aware of. When forming
+particularly complex authentication and authorization rules, it is
+sometimes useful for the identities provided by a given auth group to only
+apply to particular access groups; in other words, rather than checking
+the identity against the users: parameter of every access group, it's
+checked against the users: parameter of only some specific access groups.
+This is done with the key: parameter. For example:
+
+ auth example {
+ key: special
+ hosts: *.example.com
+ default: <SPECIAL>
+ }
+
+ access example {
+ key: special
+ users: <SPECIAL>
+ newsgroups: *
+ }
+
+In this case, the two key: parameters bind this auth group with this
+access group. For any incoming connection matching "*.example.com"
+(assuming there isn't any later auth group that also matches such hosts),
+no access group that doesn't have "key: special" will even be considered.
+Similarly, the above access group will only be checked if the user was
+authenticated with an auth group containing "key: special". This
+mechanism normally isn't useful; there is almost always a better way to
+achieve the same result.
+
+Also note in the example that there's no default-domain: parameter, which
+means that no domain is appended to the default username and the identity
+for such connections is just "<SPECIAL>". Note that some additional
+add-ons to INN may prefer that authenticated identities always return a
+full e-mail address (including a domain), so you may want to set up your
+system that way.
+
+Below is the full list of allowable parameters for auth groups and access
+groups, and after that are some examples that may make this somewhat
+clearer.
+
+=head1 AUTH GROUP PARAMETERS
+
+An access group without at least one of the res:, auth:, perl_auth:,
+python_auth:, or default: parameters makes no sense (and in practice will
+just be ignored).
+
+=over 4
+
+=item B<hosts:>
+
+A comma-separated list of remote hosts, wildmat patterns matching either
+hostnames or IP addresses, or IP netblocks specified in CIDR notation. If
+a user connects from a host that doesn't match this parameter, this auth
+group will not match the connection and is ignored.
+
+Note that if you have a large number of patterns that can't be merged into
+broader patterns (such as a large number of individual systems scattered
+around the net that should have access), the hosts: parameter may exceed
+the maximum line length of 8,192 characters. In that case, you'll need to
+break that auth group into multiple auth groups, each with a portion of
+the hosts listed in its hosts: parameter, and each assigning the same user
+identity.
+
+All hosts match if this parameter is not given.
+
+=item B<localaddress:>
+
+A comma-separated list of local host or address patterns with the same
+syntax as the same as with the hosts: parameter. If this parameter is
+specified, its auth group will only match connections made to a matching
+local interface. (Obviously, this is only useful for servers with
+multiple interfaces.)
+
+All local addresses match if this parameter is not given.
+
+=item B<res:>
+
+A simple command line for a user resolver (shell metacharacters are not
+supported). If a full path is not given, the program executed must be in
+the I<pathbin>/auth/resolv directory. A resolver is an authentication
+program which attempts to figure out the identity of the connecting user
+using nothing but the connection information (in other words, the user
+has not provided a username and password). An examples of a resolver
+would be a program that assigns an identity from an ident callback or
+from the user's hostname.
+
+One auth group can have multiple res: parameters, and they will be tried
+in the order they're listed. The results of the first successful one
+will be used.
+
+=item B<auth:>
+
+A simple command line for a user authenticator (shell metacharacters are
+not supported). If a full path is not given, the program executed must be
+located in the I<pathbin>/auth/passwd directory. An authenticator is a
+program used to handle a user-supplied username and password, via a
+mechanism such as AUTHINFO USER/PASS. Like with res:, one auth group can
+have multiple auth: parameters; they will be tried in order and the
+results of the first successful one will be used. See also perl_auth:
+below.
+
+The most common authenticator to use is ckpasswd(8); see its man page for
+more information.
+
+=item B<perl_auth:>
+
+A path to a perl script for authentication. The perl_auth: parameter
+works exactly like auth:, except that it calls the named script using
+the perl hook rather then an external program. Multiple/mixed use of
+the auth, perl_auth, and python_auth parameters is permitted within any
+auth group; each line is tried in the order it appears. perl_auth:
+has more power than auth: in that it provides the authentication
+program with additional information about the client and the ability
+to return an error string and a username. This parameter is only
+valid if INN is compiled with Perl support (B<--with-perl> passed to
+configure). More information may be found in F<doc/hook-perl>.
+
+=item B<python_auth:>
+
+A Python script for authentication. The I<python_auth> parameter works
+exactly like I<auth>, except that it calls the named script (without its
+C<.py> extension) using the Python hook rather then an external program.
+Multiple/mixed use of the I<auth>, I<perl_auth>, and I<python_auth>
+parameters is permitted within any auth group; each line is tried
+in the order it appears. I<python_auth> has more power than I<auth>
+in that it provides the authentication program with additional information
+about the client and the ability to return an error string and a username.
+This parameter is only valid if INN is compiled with Python support
+(B<--with-python> passed to B<configure>). More information may be
+found in F<doc/hook-python>.
+
+=item B<default:>
+
+The default username for connections matching this auth group. This is
+the username assigned to the user at connection time if all resolvers fail
+or if there are no res: parameters. Note that it can be either a bare
+username, in which case default-domain: (if present) is appended after
+an C<@>, or a full identity string containing an C<@>, in which case it
+will be used verbatim.
+
+=item B<default-domain:>
+
+The default domain string for this auth group. If a user resolver or
+authenticator doesn't provide a domain, or if the default username is used
+and it doesn't contain a C<@>, this domain is used to form the user
+identity. (Note that for a lot of setups, it's not really necessary for
+user identities to be qualified with a domain name, in which case there's
+no need to use this parameter.)
+
+=item B<key:>
+
+If this parameter is present, any connection matching this auth group will
+have its privileges determined only by the subset of access groups
+containing a matching key parameter.
+
+=item B<require_ssl:>
+
+If set to true, an incoming connection only matches this auth group if
+it is encrypted using SSL. This parameter is only valid if INN is
+compiled with SSL support (B<--with-openssl> passed to configure).
+
+=item B<perl_access:>
+
+A path to a perl script for dynamically generating an access group. If
+an auth group matches successfully and contains a perl_access parameter,
+then the argument perl script will be used to create an access group.
+This group will then determine the access rights of the client,
+overriding any access groups in F<readers.conf>. If and only if a
+sucessful auth group contains the perl_access parameter, F<readers.conf>
+access groups are ignored and the client's rights are instead determined
+dynamically. This parameter is only valid if INN is compiled with Perl
+support (B<--with-perl> passed to configure). More information may be
+found in the file F<doc/hook-perl>.
+
+=item B<python_access:>
+
+A Python script for dynamically generating an access group. If
+an auth group matches successfully and contains a I<python_access> parameter,
+then the argument script (without its C<.py> extension) will be used to
+create an access group. This group will then determine the access rights
+of the client, overriding any access groups in F<readers.conf>. If and only
+if a successful auth group contains the I<python_access> parameter, F<readers.conf>
+access groups are ignored and the client's rights are instead determined
+dynamically. This parameter is only valid if INN is compiled with Python
+support (B<--with-python> passed to B<configure>). More information may be
+found in the file F<doc/hook-python>.
+
+=item B<python_dynamic:>
+
+A Python script for applying access control dynamically on a per newsgroup
+basis. If an auth group matches successfully and contains a
+I<python_dynamic> parameter, then the argument script (without its
+C<.py> extension) will be used to determine the clients rights each time
+the user attempts to view a newsgroup, or read or post an article. Access
+rights as determined by I<python_dynamic> override the values of access
+group parameters such as I<newsgroups>, I<read> and I<post>. This parameter
+is only valid if INN is compiled with Python support (B<--with-python>
+passed to B<configure>). More information may be found in the file
+F<doc/hook-python>.
+
+=back
+
+=head1 ACCESS GROUP PARAMETERS
+
+=over 4
+
+=item B<users:>
+
+The privileges given by this access group apply to any user identity which
+matches this comma-separated list of wildmat patterns. If this parameter
+isn't given, the access group applies to all users (and is essentially
+equivalent to C<users: *>).
+
+=item B<newsgroups:>
+
+Users that match this access group are allowed to read and post to all
+newsgroups matching this comma-separated list of wildmat patterns. The
+empty string is equivalent to C<newsgroups: *>; if this parameter is
+missing, the connection will be rejected (unless read: and/or post: are
+used instead, see below).
+
+=item B<read:>
+
+Like the newsgroups: parameter, but the client is only given permission to
+read the matching newsgroups. This parameter is often used with post:
+(below) to specify some read-only groups; it cannot be used in the same
+access group with a newsgroups: parameter. (If read: is used and post:
+is missing, the client will have only read-only access.)
+
+=item B<post:>
+
+Like the newsgroups: parameter, but the client is only given permission to
+post to the matching newsgroups. This parameter is often used with read:
+(above) to define the patterns for reading and posting separately (usually
+to give the user permission to read more newsgroups than they're permitted
+to post to). It cannot be used in the same access group with a
+newsgroups: parameter.
+
+=item B<access:>
+
+A set of letters specifying the permissions granted to the client. The
+letters are chosen from the following set:
+
+=over 3
+
+=item R
+
+The client may read articles.
+
+=item P
+
+The client may post articles.
+
+=item I
+
+The client may inject articles with IHAVE. Note that in order to
+inject articles with the IHAVE the user must also have POST permission
+(the C<P> option).
+
+=item A
+
+The client may post articles with Approved: headers (in other words, may
+approve articles for moderated newsgroups). By default, this is not
+allowed.
+
+=item N
+
+The client may use the NEWNEWS command, overriding the global setting.
+
+=item L
+
+The client may post to newsgroups that are set to disallow local posting
+(mode C<n> in the active(5) file).
+
+=back
+
+Note that if this parameter is given, I<allownewnews> in F<inn.conf> is
+ignored for connections matching this access group and the ability of the
+client to use NEWNEWS is entirely determined by the presence of C<N> in
+the access string. If you want to support NEWNEWS, make sure to include
+C<N> in the access string when you use this parameter.
+
+Note that if this parameter is given and C<R> isn't present in the access
+string, the client cannot read regardless of newsgroups: or read:
+parameters. Similarly, if this parameter is given and C<P> isn't present,
+the client cannot post. This use of access: is deprecated and confusing;
+it's strongly recommended that if the access: parameter is used, C<R> and
+C<P> always be included in the access string and newsgroups:, read:, and
+post: be used to control access. (To grant read access but no posting
+access, one can have just a read: parameter and no post: parameter.)
+
+=item B<key:>
+
+If this parameter is present, this access group is only considered when
+finding privileges for users matching auth groups with this same key:
+parameter.
+
+=item B<reject_with:>
+
+If this parameter is present, a client matching this block will be
+disconnected with a "Permission denied" message containing the contents
+(a "reason" string) of this parameter. Some newsreaders will then
+display the reason to the user.
+
+=item B<max_rate:>
+
+If this parameter is present (and nonzero), it is used for B<nnrpd>'s
+rate-limiting code. The client will only be able to download at this
+speed (in bytes/second). Note that if SSL is being used, limiting
+is applied to the pre-encryption datastream.
+
+=item B<localtime:>
+
+If a Date: header is not included in a posted article, nnrpd(8) normally
+adds a new Date: header in UTC. If this is set to true, the Date: header
+will be formatted in local time instead. This is a boolean value and the
+default is false.
+
+=item B<newsmaster:>
+
+Used as the contact address in the help message returned by nnrpd(8), if
+the virtualhost: parameter is set to true.
+
+=item B<strippath:>
+
+If set to true, any Path: header provided by a user in a post is stripped
+rather than used as the beginning of the Path: header of the article.
+This is a boolean value and the default is false.
+
+=item B<perlfilter:>
+
+If set to false, posts made by these users do not pass through the Perl
+filter even if it is otherwise enabled. This is a boolean value and the
+default is true.
+
+=item B<pythonfilter:>
+
+If set to false, posts made by these users do not pass through the Python
+filter even if it is otherwise enabled. This is a boolean value and the
+default is true.
+
+=item B<virtualhost:>
+
+Set this parameter to true in order to make B<nnrpd> behave as if it is
+running on a server with a different name than it actually is. If you
+set this parameter to true, you must also set either pathhost: or domain:
+in the relevant access group in F<readers.conf> to something different
+than is set in F<inn.conf>. All articles displayed to clients will then have
+their Path: and Xref: headers altered to appear to be from the server
+named in pathhost: or domain: (whichever is set), and posted articles will
+use that server name in the Path:, Message-ID:, and X-Trace: headers.
+
+Note that setting this parameter requires the server modify all posts
+before presenting them to the client and therefore may decrease
+performance slightly.
+
+=back
+
+In addition, all of the following parameters are valid in access groups
+and override the global setting in F<inn.conf>. See inn.conf(5) for the
+descriptions of these parameters:
+
+ addnntppostingdate, addnntppostinghost, backoff_auth, backoff_db,
+ backoff_k, backoff_postfast, backoff_postslow, backoff_trigger,
+ checkincludedtext, clienttimeout, complaints, domain,
+ fromhost, localmaxartsize, moderatormailer, nnrpdauthsender,
+ nnrpdcheckart, nnrpdoverstats, nnrpdposthost, nnrpdpostport, organization,
+ pathhost, readertrack, spoolfirst, strippostcc.
+
+=head1 SUMMARY
+
+Here's a basic summary of what happens when a client connects:
+
+=over 2
+
+=item *
+
+All auth groups are scanned and the ones that don't match the client
+(due to hosts:, localaddress:, require_ssl:, etc) are eliminated.
+
+=item *
+
+The remaining auth groups are scanned from the last to the first, and an
+attempt is made to apply it to the current connection. This means running
+res: programs, if any, and otherwise applying default:. The first auth
+group (starting from the bottom) to return a valid user is kept as the
+active auth group.
+
+=item *
+
+If no auth groups yield a valid user (none have default: parameters or
+successful res: programs) but some of the auth groups have auth: lines
+(indicating a possibility that the user can authenticate and then obtain
+permissions), the connection is considered to have no valid auth group
+(which means that the access groups are ignored completely) but the
+connection isn't closed. Instead, 480 is returned for everything until
+the user authenticates.
+
+=item *
+
+When the user authenticates, the auth groups are rescanned, and only the
+matching ones which contain at least one auth, perl_auth, or
+python_auth line are considered. These auth groups are scanned from
+the last to the first, running auth: programs and perl_auth: or
+python_auth: scripts. The first auth group (starting from the bottom)
+to return a valid user is kept as the active auth group.
+
+=item *
+
+Regardless of how an auth group is established, as soon as one is, that
+auth group is used to assign a user identity by taking the result of the
+successful res, auth, perl_auth, or python_auth line (or the
+default: if necessary), and appending the default-domain if
+necessary. (If the perl_access: or python_access: parameter is
+present, see below.)
+
+=item *
+
+Finally, an access group is selected by scanning the access groups from
+bottom up and finding the first match. (If the established auth group
+contained a perl_access: or python_access line, the dynamically
+generated access group returned by the script is used instead.)
+User permissions are granted based on the established access group.
+
+=back
+
+=head1 EXAMPLES
+
+Probably the simplest useful example of a complete F<readers.conf>,
+this gives permissions to read and post to all groups to any connections
+from the "example.com" domain, and no privileges for anyone connecting
+elsewhere:
+
+ auth example.com {
+ hosts: "*.example.com, example.com"
+ default: <LOCAL>
+ }
+
+ access full {
+ newsgroups: *
+ }
+
+Note that the access realm has no users: key and therefore applies to any
+user identity. The only available auth realm only matches hosts in the
+"example.com" domain, though, so any connections from other hosts will be
+rejected immediately.
+
+If you have some systems that should only have read-only access to the
+server, you can modify the example above slightly by adding an additional
+auth and access group:
+
+ auth lab {
+ hosts: "*.lab.example.com"
+ default: <LAB>
+ }
+
+ access lab {
+ users: <LAB>
+ read: *
+ }
+
+If those are put in the file after the above example, they'll take
+precedence (because they're later in the file) for any user coming from a
+machine in the lab.example.com domain, everyone will only have read
+access, not posting access.
+
+Here's a similar example for a news server that accepts connections from
+anywhere but requires the user to specify a username and password. The
+username and password are first checked against an external database of
+usernames and passwords, and then against the system shadow password file:
+
+ auth all {
+ auth: "ckpasswd -d <pathdb in inn.conf>/newsusers"
+ auth: "ckpasswd -s"
+ }
+
+ access full {
+ users: *
+ newsgroups: *
+ }
+
+When the user first connects, there are no res: keys and no default, so
+they don't receive any valid identity and the connection won't match any
+access groups (even ones with C<users: *>). Such users receive nothing
+but authentication-required responses from nnrpd until they authenticate.
+
+If they then later authenticate, the username and password are checked
+first by running B<ckpasswd> with the B<-d> option for an external dbm
+file of encrypted passwords, and then with the B<-s> option to check the
+shadow password database (note that this option may require ckpasswd to
+be setgid to a shadow group, and there are security considerations; see
+ckpasswd(8) for details). If both of those fail, the user will continue
+to have no identity; otherwise, an identity will be assigned (usually
+the supplied username, perhaps with a domain appended, although an
+authenticator technically can provide a completely different username
+for the identity), and the access group will match, giving full access.
+
+It may be educational to consider how to combine the above examples;
+general groups always go first. The order of the auth groups actually
+doesn't matter, since the "hosts: example.com" one only matches
+connections before username/password is sent, and the "auth: ckpasswd"
+one only matches after; order would matter if either group applied to
+both cases. The order of the access groups in this case does matter,
+provided the newsgroups: lines differ; the access group with no users:
+line needs to be first, with the "users: <LOCAL>" group after.
+
+Here's a very complicated example. This is for an organization that has
+an internal hierarchy "example.*" only available to local shell users, who
+are on machines where identd can be trusted. Dialup users must provide a
+username and password, which is then checked against RADIUS. Remote users
+have to use a username and password that's checked against a database on
+the news server. Finally, the admin staff (users "joe" and "jane") can
+post anywhere (including the "example.admin.*" groups that are read-only
+for everyone else), and are exempted from the Perl filter. For an
+additional twist, posts from dialup users have their Sender: header
+replaced by their authenticated identity.
+
+Since this organization has some internal moderated newsgroups, the admin
+staff can also post messages with Approved: headers, but other users
+cannot.
+
+ auth default {
+ auth: "ckpasswd -f <pathdb in inn.conf>/newsusers"
+ default: <FAIL>
+ default-domain: example.com
+ }
+
+ auth shell {
+ hosts: *.shell.example.com
+ res: ident
+ auth: "ckpasswd -s"
+ default: <FAIL>
+ default-domain: shell.example.com
+ }
+
+ auth dialup {
+ hosts: *.dialup.example.com
+ auth: radius
+ default: <FAIL>
+ default-domain: dialup.example.com
+ }
+
+ access shell {
+ users: *@shell.example.com
+ read: *
+ post: "*, !example.admin.*"
+ }
+
+ access dialup {
+ users: *@dialup.example.com
+ newsgroups: *,!example.*
+ nnrpdauthsender: true
+ }
+
+ access other {
+ users: "*@example.com, !<FAIL>@example.com"
+ newsgroups: *,!example.*
+ }
+
+ access fail {
+ users: "<FAIL>@*"
+ newsgroups: !*
+ }
+
+ access admin {
+ users: "joe@*,jane@*"
+ newsgroups: *
+ access: "RPA"
+ perlfilter: false
+ }
+
+Note the use of different domains to separate dialup from shell users
+easily. Another way to do that would be with key: parameters, but this
+way provides slightly more intuitive identity strings. Note also that the
+fail access group catches not only failing connections from external users
+but also failed authentication of shell and dialup users and dialup users
+before they've authenticated. The identity string given for, say, dialup
+users before RADIUS authentication has been attempted matches both the
+dialup access group and the fail access group, since it's
+"<FAIL>@dialup.example.com", but the fail group is last so it takes
+precedence.
+
+The shell auth group has an auth: parameter so that users joe and jane
+can, if they choose, use username and password authentication to gain
+their special privileges even if they're logged on as a different user on
+the shell machines (or if ident isn't working). When they first connect,
+they'd have the default access for that user, but they could then send
+AUTHINFO USER and AUTHINFO PASS (or AUTHINFO SIMPLE) and get their
+extended access.
+
+Also note that if the users joe and jane are using their own accounts,
+they get their special privileges regardless of how they connect, whether
+the dialups, the shell machines, or even externally with a username and
+password.
+
+Finally, here's a very simple example of a configuration for a public
+server for a particular hierarchy.
+
+ auth default {
+ hosts: *
+ default: <PUBLIC>
+ }
+
+ access default {
+ users: <PUBLIC>
+ newsgroups: example.*
+ }
+
+Notice that clients aren't allowed to read any other groups; this keeps
+them from getting access to administrative groups or reading control
+messages, just as a precaution. When running a public server like this,
+be aware that many public hierarchies will later be pulled down and
+reinjected into the main Usenet, so it's highly recommended that you also
+run a Perl or Python filter to reject any messages crossposted out of your
+local hierarchy and any messages containing a Supersedes: header. This
+will keep messages posted to your public hierarchy from hurting any of the
+rest of Usenet if they leak out.
+
+=head1 SECURITY CONSIDERATIONS
+
+In general, separate passwords should be used for NNTP wherever
+possible; the NNTP protocol itself does not protect passwords from
+casual interception, and many implementations (including this one) do
+not "lock out" accounts or otherwise discourage password-guessing
+attacks. So it is best to ensure that a compromised password has
+minimal effects.
+
+Authentication using the AUTHINFO USER/PASS commands passes unencrypted
+over the network. Extreme caution should therefore be used especially
+with system passwords (e.g. C<auth: ckpasswd -s>). Passwords can be
+protected by using NNTP over SSL or through ssh tunnels, and this usage
+can be enforced by a well-considered server configuration that only
+permits certain auth groups to be applied in certain cases. Here are
+some ideas:
+
+=over 4
+
+=item *
+
+To restrict connections on the standard nntp port (119) to use SSL for
+some (or all) of the auth groups to match, use the require_ssl:
+parameter.
+
+=item *
+
+If you consider your local network (but not the internet) secure, have
+some auth groups with a restrictive hosts: parameter; they would go
+above, with ones having global applicability below.
+
+=item *
+
+Consider running a C<nnrpd -S> (with C<-D>, or out of "super-server"
+like B<inetd>) on the NNTPS port (563) for clients that support SSL. See
+nnrpd(8) for more details about how to configure that. You
+can use the require_ssl: parameter, or C<-c> to specify an alternate
+F<readers.conf> if you want a substantially different configuration for
+this case.
+
+=item *
+
+If you want to restrict an auth group to only match loopback connections
+(for users running newsreaders on localhost or connecting via an ssh
+tunnel), use the localaddress: parameter.
+
+=back
+
+=head1 HISTORY
+
+Written by Aidan Cully <aidan@panix.com> for InterNetNews. Substantially
+expanded by Russ Allbery <rra@stanford.edu>.
+
+$Id: readers.conf.pod 7895 2008-06-22 17:54:10Z iulius $
+
+=head1 SEE ALSO
+
+auth_krb5(8), auth_smb(8), ckpasswd(8), inn.conf(5), innd(8), newsfeeds(5),
+nnrpd(8), uwildmat(3).
+
+=cut
--- /dev/null
+=head1 Welcome to INN 2.4!
+
+This work is sponsored by Internet Systems Consortium.
+
+Please see F<INSTALL> for installation instructions, F<NEWS> for what's
+changed from the previous release, and F<LICENSE> for the copyright,
+license, and distribution terms.
+
+=head1 What is INN?
+
+INN (InterNetNews), originally written by Rich Salz, is an extremely
+flexible and configurable Usenet / netnews news server. For a complete
+description of the protocols behind Usenet and netnews, see RFC 1036 and
+RFC 977 (or their replacements). In brief, netnews is a set of protocols
+for exchanging messages between a decentralized network of news servers.
+News articles are organized into newsgroups, which are themselves
+organized into hierarchies. Each individual news server stores locally
+all articles it has received for a given newsgroup, making access to
+stored articles extremely fast. Netnews does not require any central
+server; instead, each news server passes along articles it receives to all
+of the news servers it peers with, those servers pass the articles along
+to their peers, and so on, resulting in "flood fill" propagation of news
+articles.
+
+A news server performs three basic functions: it accepts articles from
+other servers and stores them on disk, sends articles it has received out
+to other servers, and offers stored news articles to readers on demand.
+It additionally has to perform some periodic maintenance tasks, such as
+deleting older articles to make room for new ones.
+
+Originally, a news server would just store all of the news articles it had
+received in a file system. Users could then read news by reading the
+article files on disk (or more commonly using news reading software that
+did this efficiently). These days, news servers are almost always
+stand-alone systems and news reading is supported via network connections.
+A user who wants to read a newsgroup opens that newsgroup in their
+newsreader software, which opens a network connection to the news server
+and sends requests for articles and related information. The protocol
+that a newsreader uses to talk to a news server and that a news server
+uses to talk to another news server over TCP/IP is called NNTP (Network
+News Transport Protocol).
+
+INN supports accepting articles via either NNTP connections or via UUCP.
+B<innd>, the heart of INN, handles NNTP feeding connections directly;
+UUCP newsfeeds use B<rnews> (included in INN) to hand articles off to
+innd. Other parts of INN handle feeding articles out to other news
+servers, most commonly B<innfeed> (for real-time outgoing feeds) or
+B<nntpsend> and B<innxmit> (used to send batches of news created by innd
+to a remote site via TCP/IP). INN can also handle outgoing UUCP feeds.
+
+The part of INN that handles connections from newsreaders is nnrpd.
+
+Also included in INN are a wide variety of supporting programs to handle
+periodic maintenance and recovery from crashes, process special control
+messages, maintain the list of active newsgroups, and generate and record
+a staggering variety of statistics and summary information on the usage
+and performance of the server.
+
+INN also supports an extremely powerful filtering system that allows the
+server administrator to reject unwanted articles (such as spam and other
+abuses of Usenet).
+
+INN is free software, supported by Internet Systems Consortium and
+volunteers around the world. See L<"Supporting the INN Effort"> below.
+
+=head1 Prerequisites
+
+Compiling INN requires an ANSI C compiler (gcc is recommended). INN was
+originally written in K&R C, but supporting pre-ANSI compilers has become
+enough of a headache that a lot of the newer parts of INN will no longer
+compile with a non-ANSI compiler. gcc itself will compile with most
+vendor non-ANSI compilers, however, so if you're stuck with one,
+installing gcc is highly recommended. Not only will it let you build INN,
+it will make installing lots of other software much easier. You may also
+need GNU make (particularly if your system make is BSD-derived), although
+most SysV make programs should work fine. Compiling INN also currently
+requires a yacc implementation (bison will do fine).
+
+INN uses GNU autoconf to probe the capabilities of your system, and
+therefore should compile on nearly any Unix system. It does, however,
+make extensive use of mmap(), which can cause problems on some older
+operating systems. See F<INSTALL> for a list of systems it is known to
+work on. If you encounter problems compiling or running INN, or if you
+successfully run INN on a platform that isn't listed in F<INSTALL>, please
+let us know (see L<"Reporting Bugs"> below).
+
+Perl 5.003 or later is required to build INN. Perl 5.004 is required if
+you want the embedded Perl filter support (which is highly recommended;
+some excellent spam filters have been written for INN). Since all
+versions of Perl previous to 5.004 are buggy (including security problems)
+and have fewer features, installing Perl 5.004 or later is recommended.
+
+If you want to enable PGP verification of control messages (highly
+recommended), you will need to have a PGP implementation installed. See
+F<INSTALL> for more details.
+
+=head1 Getting Started
+
+A news server can be a fairly complicated piece of software to set up just
+because of the wide variety of pieces that have to be configured (who is
+authorized to read from the server, what newsgroups it carries, and how
+the articles are stored on disk at a bare minimum, and if the server isn't
+completely stand-alone -- and very few servers are -- both incoming and
+outgoing feeds have to be set up and tested). Be prepared to take some
+time to understand what's going on and how all the pieces fit together.
+If you have any specific suggestions for documentation, or comments about
+things that are unclear, please send them to the INN maintainers (see
+L<"Reporting Bugs"> below).
+
+See F<INSTALL> for step-by-step instructions for setting up and
+configuring a news server.
+
+INN also comes with a very complete set of man pages; there is a man page
+for every configuration file and program that comes with INN. (If you
+find one that doesn't have a man page, that's a bug. Please do report
+it.) When trying to figure out some specific problem, reading the man
+pages for all of the configuration files involved is a very good start.
+
+=head1 Reporting Bugs
+
+We're interested in all bug reports. Not just on the programs, but on the
+documentation too. Please send I<all> such reports to
+
+ inn-bugs@isc.org
+
+(patches are certainly welcome, see below). Even if you post to Usenet,
+please CC the above address. All other INN mail should go to
+
+ inn@isc.org
+
+(please do I<not> send bug reports to this address).
+
+If you have general "how do I do this" questions or problems configuring
+your server that you don't believe are due to a bug in INN, you should
+post them to news.software.nntp. A lot of experienced INN users,
+including several of the INN maintainers, read that newsgroup regularly.
+Please don't send general questions to the above addresses; those
+addresses are specifically for INN, and the INN maintainers usually won't
+have time to answer general questions.
+
+=head1 Contributing Code
+
+If you have a patch or a utility that you'd like to be considered for
+inclusion into INN, please mail it to
+
+ inn-patches@isc.org
+
+in the body of the message (not as an attachment), or put it on a
+webpage and send a link. Patches included with a bug report as
+described above should follow the same procedure, but need not be sent
+to both addresses (either will do).
+
+Have fun!
+
+=head1 Mailing Lists
+
+There are various INN-related mailing lists you can join or send messages
+to if you like. Some of them you must be a member of before you can send
+mail to them (thank the spammers for that policy), and one of them is
+read-only (no postings allowed).
+
+=over 24
+
+=item inn-announce@isc.org
+
+Where announcements about INN are set (only maintainers may post).
+
+=item inn-workers@isc.org
+
+Discussion of INN development (postings by members only).
+
+=item inn-patches@isc.org
+
+Where to send patches for consideration for inclusion into INN (open
+posting).
+
+=item inn-committers@isc.org
+
+CVS commit messages for INN are sent to this list (only the automated
+messages are sent here, no regular posting).
+
+=item inn-bugs@isc.org
+
+Where to send bug reports (open posting). If you're an INN expert and
+have the time to help out other users, we encourage you to join this
+mailing list to answer questions. (You may also want to read the
+newsgroup news.software.nntp, which gets a lot of INN-related questions.)
+
+=back
+
+To join these lists, send a subscription request to the C<-request>
+address. The addresses for the above lists are:
+
+ inn-announce-request@isc.org
+ inn-workers-request@isc.org
+ inn-patches-request@isc.org
+ inn-committers-request@isc.org
+ inn-bugs-request@isc.org
+
+=head1 Who's Responsible / Who to Thank
+
+See F<CONTRIBUTORS> for a long list of past contributors as well as people
+from the inn-workers mailing list who have dedicated a lot of time and
+effort to getting this new version together. They deserve a big round of
+applause. They've certainly got our thanks.
+
+This product includes software developed by UUNET Technologies, Inc. and
+by the University of California, Berkeley and its contributors.
+
+Last, but certainly not least, Rich Salz, the original author of INN
+deserves a lion's share of the credit for writing INN in the first place
+and making it the most popular news server software on the planet (no NNTP
+yet to the moon, but we plan to be there first).
+
+=head1 Related Packages
+
+INN users may also be interested in the following software packages that
+work with INN or are based on it. Please note that none of this software
+is developed or maintained by ISC; we don't support it and generally can't
+answer questions about it.
+
+=over 4
+
+=item CleanFeed
+
+URL: <http://www.bofh.it/~md/cleanfeed/>
+
+CleanFeed is an extremely powerful spam filter, probably the most widely
+used spam filter on Usenet currently. It catches excessive multiposting
+and a host of other things, and is highly configurable. Note that it
+requires that INN be built with Perl support (the B<--with-perl> option to
+configure).
+
+=item GUP (Group Update Program)
+
+URL: <ftp://ftp.debian.org/debian/pool/main/g/gup/>
+
+GUP provides a way for your peers to update their newsfeeds entries as
+they want without having to ask you to edit the configuration file all the
+time. It's useful when feeding peers who take limited and very specific
+feeds that change periodically.
+
+=item inflow
+
+URL: <http://www.switch.ch/netnews/wg/netnews-wg.html>
+
+inflow generates graphs of news flow statistics in real time from INN's
+logs (things like articles accepted per peer, volume accepted per peer,
+and the like).
+
+=item News-Portal
+
+URL: <http://floh.gartenhaus.net/newsportal/>
+
+A PHP-based web news reader that works as a front-end to a regular news
+server such as INN and lets people read and post without learning a news
+reader.
+
+=item PersonalINN
+
+URL: <http://www.ritual.org/summer/pinn/>
+
+PersonalINN is a version of INN modified for personal use and with a
+friendly GUI built on top of it. It is available for NeXTSTEP or OPENSTEP
+only, unfortunately.
+
+=item suck
+
+URL: <http://home.comcast.net/~bobyetman/index.html>
+
+suck is a separate package for downloading a news feed via a reading
+connection (rather than via a direct NNTP or UUCP feed) and sending
+outgoing local posts via POST. It's intended primarily for personal or
+small-organization news servers who get their news via an ISP and are too
+small to warrant setting up a regular news feed.
+
+=item newsx
+
+URL: <http://www.kvaleberg.com/newsx.html>
+
+Serving the same purpose as suck, newsx is a separate package for
+downloading a news feed via a reading connectino and sending outgoing
+local posts via POST. Some people find suck easier to configure and use,
+and some people find newsx easier. If you have problems with one, try the
+other.
+
+=back
+
+=head1 Supporting the INN Effort
+
+Note that INN is supported by Internet Systems Consortium, and although it
+is free for use and redistribution and incorporation into vendor products
+and export and anything else you can think of, it costs money to produce.
+That money comes from ISPs, hardware and software vendors, companies who
+make extensive use of the software, and generally kind-hearted folk such
+as yourself.
+
+Internet Systems Consortium has also commissioned a DHCP server
+implementation and handles the official support/release of BIND. You can
+learn more about the ISC's goals and accomplishments from the web page at
+<http://www.isc.org/>.
+
+ Russ Allbery
+ Katsuhiro Kondou
+ <inn@isc.org>
--- /dev/null
+=head1 NAME
+
+sasl.conf - SASL Configuration file for nnrpd.
+
+=head1 DESCRIPTION
+
+The file F<sasl.conf> in I<pathetc> specifies Simple Authentication
+and Security Layer (SASL), defined in RFC 2222, for nnrpd.
+Now nnrpd implements only Security Layer support, which is an extension
+of RFC 2595. This means you can get SSL or TLS encrypted NNRP between
+your server and newsreaders. It requires OpenSSL 0.9.3 or newer from
+http://www.openssl.org/; it has been tested with versions 0.9.4 and 0.9.5.
+
+=head1 INSTALLATION
+
+To use SSL, a certificate and private key are needed that you can
+create using the openssl binary.
+Make certain that each keys are owned by your news user, news group,
+and are mode 0640 or 0660.
+
+=head2 EXAMPLE
+
+ openssl req -new -x509 -nodes -out /usr/local/news/lib/cert.pem\
+ -days 366 -keyout /usr/local/news/lib/cert.pem
+ chown news:news /usr/local/news/lib/cert.pem
+ chmod 640 /usr/local/news/lib/cert.pem
+
+You also can make the keys as the root user with C<make cert>.
+
+=head1 CONFIGURATION
+
+Comments begin with a number sign (C<#>) and continue through the
+end of the line. Blank lines and comments are ignored.
+All other lines specify parameters, and should be of the form
+
+ <option>: <value>
+
+where <option> is the name of the configuration option being set and
+<value> is the value that the configuration option is being set to.
+
+Blank lines and lines beginning with (C<#>) are ignored.
+For boolean options, the values C<yes>, C<on>, C<t>,
+and C<1> turn the option on; the values C<no>, C<off>,
+C<f>, and C<0> turn the option off.
+
+=over 4
+
+=item tls_cert_file
+
+The path to a file containing the server's certificate.
+
+=item tls_key_file
+
+The path to a file containing the server's private key.
+
+=item tls_ca_path
+
+The path to a directory containing the CA's certificate.
+
+=item tls_ca_file
+
+The path to a file containing the CA's certificate.
+
+=back
+
+=head1 TO DO
+
+Implement methods of the authentication protocols of SASL.
+
+=head1 HISTORY
+
+Written by Kenichi OKADA E<lt>okada@opaopa.orgE<gt> for InterNetNews.
+
+=head1 SEE ALSO
+
+inn.conf(5), innd(8), nnrpd(8), readers.conf(5)
+
+=cut
--- /dev/null
+=head1 NAME
+
+sendinpaths - Send Usenet Path statistics via e-mail
+
+=head1 SYNOPSIS
+
+B<sendinpaths> [B<-n>]
+
+=head1 DESCRIPTION
+
+B<sendinpaths> checks I<pathlog>/path for B<ninpaths> dump files, finds
+dump files generated in the past 30 days, makes sure they are valid by
+running B<ninpaths> on each one and making sure the exit status is zero,
+and passes them to B<ninpaths> to generate a cumulative report. That
+report is mailed to the e-mail addresses configured at the beginning of
+this script (by default "pathsurvey@top1000.org" and
+"top1000@anthologeek.net").
+
+When finished, B<sendinpaths> deletes all dump files in I<pathlog>/path
+that are older than 14 days (configurable at the beginning of the script).
+
+For more information on how to set up B<ninpaths>, see ninpaths(8).
+
+=head1 OPTIONS
+
+=over 4
+
+=item B<-n>
+
+Don't e-mail the report; instead, just print it to standard output. Don't
+delete old dump files.
+
+=back
+
+=head1 SEE ALSO
+
+ninpaths(8)
+
+=head1 HISTORY
+
+B<sendinpaths> was written by Olaf Titz <olaf@bigred.inka.de>.
+
+=cut
--- /dev/null
+=head1 NAME
+
+simpleftp - Rudimentary FTP client
+
+=head1 SYNOPSIS
+
+B<simpleftp> I<url> [...]
+
+=head1 DESCRIPTION
+
+B<simpleftp> is a Perl script that provides basic support for
+fetching files with FTP in a batch oriented fashion. It takes one or more
+FTP URLs on the command line. The file(s) will be retrieved from the
+remote server and placed in the current directory with the same basename
+as on the remote; e.g., L<ftp://ftp.isc.org/pub/usenet/CONFIG/active.gz>
+is stored as F<active.gz> in the current directory.
+
+The script properly understands usernames, passwords and ports specified
+as follows:
+
+ ftp://user:password@host:port/path/file
+
+=head1 BUGS
+
+B<simpleftp> is an extremely poor substitute for more complete programs
+like the freely available B<wget> or B<ncftp> utilities. It was written
+only to provide elementary support in INN for non-interactive fetching of
+the files in L<ftp://ftp.isc.org/pub/pgpcontrol/> or
+L<ftp://ftp.isc.org/pub/usenet/CONFIG/> without requiring
+administrators to install yet another package. Its shortcomings as a
+general purpose program are too numerous to mention, but one that stands
+out is that downloaded files by B<simpleftp> override existing files
+with the same name in the local directory.
+
+=head1 HISTORY
+
+Tossed off by David C Lawrence <tale@isc.org> for InterNetNews.
+Rewritten to use Net::FTP by Julien Elie <julien@trigofacile.com>.
+
+$Id: simpleftp.pod 7739 2008-04-06 09:38:31Z iulius $
+
+=head1 SEE ALSO
+
+actsync(8).
+
+=cut
--- /dev/null
+=head1 NAME
+
+sm - Command-line interface to the INN storage manager
+
+=head1 SYNOPSIS
+
+B<sm> [B<-dHiqRrS>] [I<token> ...]
+
+=head1 DESCRIPTION
+
+The INN storage manager is the subsystesm that stores and keeps track of
+all of the articles and what storage backend they're in. All stored
+articles are assigned a storage API token. B<sm> is a command-line
+interface to that storage manager, primarily used to retrieve articles by
+those tokens but also to perform other operations on the storage
+subsystem.
+
+I<token> is the token of an article (the same thing that's returned by
+B<grephistory> or stored in the history file). It looks something like:
+
+ @0502000005A4000000010000000000000000@
+
+Any number of tokens can be given on the command line. If none are, B<sm>
+reads tokens from standard input, one per line. The default operation is
+to retrieve and write to standard output the corresponding article for
+each token given.
+
+=head1 OPTIONS
+
+=over 4
+
+=item B<-d>, B<-r>
+
+Rather than retrieving the specified article, remove the article. This
+will delete the article out of the news spool and it will not subsequently
+be retrievable by any part of INN. It's equivalent to C<ctlinnd cancel>
+except it takes a storage API token instead of a message ID.
+
+=item B<-H>
+
+Retrieve only the header of the article rather than the entire article.
+This option cannot be used with B<-d>, B<-r>, B<-i>, or B<-S>.
+
+=item B<-i>
+
+Show the newsgroup name and article number associated with the token
+rather than the article itself. Note that for crossposted articles, only
+the first newsgroup and article number to which the article is associated
+will be returned.
+
+=item B<-q>
+
+Suppress all error messages except usage errors.
+
+=item B<-R>
+
+Display the raw article. This means that line endings won't be converted
+to native line endings and will be left as CRLF sequences, leading periods
+will still be escaped for sending over NNTP, and the article will end in
+a CRLF.CRLF sequence.
+
+=item B<-S>
+
+Write the article to standard output in the format used by rnews spool
+files. Multiple articles can be written in this format, and the resulting
+output can be fed to rnews (on another system, for example) to inject
+those articles into INN. This option cannot be used with B<-d>, B<-r>,
+B<-H>, B<-i>, or B<-R>.
+
+=back
+
+=head1 EXIT STATUS
+
+If all operations were successful, B<sm> exits with status 0. If an
+operation on any of the provided tokens fails, B<sm> will exit with status
+1, even if the operations on other tokens were successful. In other
+words, if twenty tokens are fed to C<sm -r> on stdin, 19 articles were
+successfully removed, but the sixth article couldn't be found, B<sm> will
+still exit with status 1.
+
+This means that if you need to be sure whether a particular operation
+succeeded, you should run sm on one token at a time.
+
+=head1 HISTORY
+
+Written by Katsuhiro Kondou <kondou@nec.co.jp> for InterNetNews.
+Rewritten in POD by Russ Allbery <rra@stanford.edu>.
+
+$Id: sm.pod 6683 2004-03-06 18:31:58Z rra $
+
+=head1 SEE ALSO
+
+ctlinnd(8), grephistory(1), history(5), rnews(1), storage.conf(5).
+
+=cut
--- /dev/null
+=head1 NAME
+
+subscriptions - Default recommended subscriptions
+
+=head1 DESCRIPTION
+
+The I<pathetc>/F<subscriptions> file contains a list of newsgroups that is
+returned by the NNTP command LIST SUBSCRIPTIONS.
+
+Clients that support this command and send it the first time they connect
+to a new news server use the returned list to initialize the list of
+subscribed newsgroups. The F<subscriptions> file therefore should contain
+groups intented for new users, for testing, or that contain FAQs and other
+useful information for first-time Usenet users.
+
+The syntax of the F<subscriptions> file is trivial; it is a simple list of
+newsgroup names, one per line. The order of newsgroups may be
+significant; the news reading client may present the groups in that order
+to the user.
+
+=head1 EXAMPLE
+
+A typical F<subscriptions> file may look like:
+
+ news.announce.newusers
+ news.newusers.questions
+ local.test
+ local.general
+ local.talk
+ misc.test
+ misc.test.moderated
+ news.answers
+ news.announce.newgroups
+
+This gives the client the FAQs and question newsgroup for new users first,
+then a local newsgroup for testing and various commonly-read local
+discussion groups, followed by the world-wide test groups, all the FAQs,
+and announcements of new world-wide newsgroups. If there is a local new
+users group, one might want to list it first.
+
+=head1 HISTORY
+
+Written by Bettina Fink <laura@hydrophil.de> for InterNetNews.
+
+=head1 SEE ALSO
+
+nnrpd(8).
+
+=cut
--- /dev/null
+=head1 NAME
+
+tdx-util - Tradindexed overview manipulation utility
+
+=head1 SYNOPSIS
+
+B<tdx-util> [B<-AFgio>] [B<-a> I<article>] [B<-n> I<newsgroup>]
+[B<-p> I<path>] [B<-R> I<path>]
+
+=head1 DESCRIPTION
+
+B<tdx-util> is an administrative interface to the tradindexed overview
+method for INN. It only works on tradindexed overview databases, not on
+any other type of INN overview. It allows the administrator to dump
+various information about the internal state of the overview, audit it for
+errors, and rebuild portions of the overview database.
+
+The tradindexed overview method should lock properly and therefore it
+should be safe to run this utility and perform any operation it performs,
+including full repairs or per-group overview rebuilds, while the server is
+running. However, note that some of the operations performed by this
+utility can take an extended period of time and will hold locks in the
+overview database during that period, which depending on what the server
+is doing may cause the server to stall until B<tdx-util> completes its
+operation.
+
+The dump operations are B<-i>, which dumps the master index for the
+overview database, B<-g>, which dumps the index for an individual group,
+and B<-o>, which dumps the overview information for a particular group
+(including the additional metadata stored in the index). For B<-g> and
+B<-o>, the B<-n> option must also be given to specify a newsgroup to
+operate on.
+
+To audit the entire overview database for problems, use B<-A>. Any
+problems found will be reported to standard error. There is not (yet) a
+corresponding option to correct the errors found.
+
+To rebuild the database for a particular newsgroup, use B<-R>. The B<-R>
+option takes a path to a directory which contains all of the articles for
+that newsgroup, one per file. The names of the files must be the numbers
+of the articles in that group. (In other words, this directory must be a
+traditional spool directory for that group.) The B<-n> option must also
+be given to specify the newsgroup for which the overview is being rebuilt.
+
+For all operations performed by B<tdx-util>, a different overview database
+than the one specified in F<inn.conf> may be specified using the B<-p>
+option.
+
+=head1 OPTIONS
+
+=over 4
+
+=item B<-A>
+
+Audit the entire overview database for problems. This runs the internal
+consistency checks built into the tradindexed overview implementation,
+checking such things as the validity and reachability of all group index
+entries, the format of the individual overview entries, the correspondance
+of index entries to overview data, and similar such things. No changes
+will be made to the database, but problems will be reported to standard
+error.
+
+=item B<-a> I<article>
+
+The article number to act on. Only useful in combination with the B<-o>
+option to dump overview information.
+
+=item B<-F>
+
+Audit the entire overview database for problems, fixing them as they're
+detected where possible. This runs the internal consistency checks built
+into the tradindexed overview implementation, checking such things as the
+validity and reachability of all group index entries, the format of the
+individual overview entries, the correspondance of index entries to
+overview data, and similar such things. The strategy used when fixing
+problems is to throw away data that's unrecoverable, so be warned that
+using this option may result in inaccessible articles if their overview
+data has been corrupted.
+
+To see what would be changed by B<-F>, run B<tdx-util> with B<-A> first.
+
+=item B<-g>
+
+Dump the master index of the overview database. This contains similar
+information to the server active file, such as high and low water marks
+and moderation status, and is the information that nnrpd hands out to
+clients.
+
+The fields are, in order, the newsgroup name, the high water mark, the low
+water mark, the base article number (the point at which the group index
+begins), the count of articles in the group, the group status flag, the
+time (in seconds since epoch) when that newsgroup was deleted or 0 if it
+hasn't been, and the inode of the index file for that group.
+
+=item B<-i>
+
+Dump the index of a particular group. The fields are, in order, the
+article number, the offset of the data for that article in the overview
+data file for that group, the length of the overview data, the time (in
+seconds since epoch) when the article arrived on the server, the time (in
+seconds since epoch) when the article should expire based on its Expires
+header (or 0 if there is no Expires header), and the storage API token of
+the article.
+
+If this option is given, the B<-n> option must also be given to specify
+the newsgroup on which to act.
+
+=item B<-n> I<newsgroup>
+
+Specify the newsgroup on which to act, required for the B<-i>, B<-o>, and
+B<-R> options.
+
+=item B<-o>
+
+Dump the overview information for a newsgroup, in the same format as it
+would be returned to clients but with one modification. Appended to the
+end of each entry will be four additional pieces of data: the article
+number according to the index file for that group, the storage API token
+for that article, the arrival date for that article on the server in RFC
+822 date format, and the expiration date for that article (from the
+Expires header) in RFC 822 date format if there is any.
+
+If this option is given, the B<-n> option must also be given to specify
+the newsgroup on which to act. By default, all of the overview
+information for that newsgroup is dumped, but the B<-a> option may be
+given to restrict the dump to the information for a single article.
+
+=item B<-p> I<path>
+
+Act on the overview database rooted in I<path>, overriding the overview
+path specified in F<inn.conf>.
+
+=item B<-R> I<path>
+
+Rebuild the overview for a given group from the articles stored in
+I<path>. The articles must be in the form of a traditional spool
+directory; in other words, each article must be in a separate file and the
+name of the file must match the article number of the article.
+
+If this option is given, the B<-n> option must also be given to specify
+the newsgroup on which to act.
+
+=back
+
+=head1 EXAMPLES
+
+Dump the master index for the overview database in F</news/overview>,
+regardless of the overview path specified in F<inn.conf>:
+
+ tdx-util -i -p /news/overview
+
+Dump the group index for example.test:
+
+ tdx-util -g -n example.test
+
+Dump the complete overview information for example.test:
+
+ tdx-util -o -n example.test
+
+Audit the entire overview database for any problems:
+
+ tdx-util -A
+
+Rebuild the overview information for example.test from a traditional spool
+directory:
+
+ tdx-util -R /news/spool/articles/example/test -n example.test
+
+The last command may be useful for recovering from a bad crash or
+corrupted overview information for a particular group, if you are also
+using the tradspool article storage method.
+
+=head1 HISTORY
+
+Written by Russ Allbery <rra@stanford.edu> for InterNetNews.
+
+$Id: tdx-util.pod 7047 2004-12-19 19:41:49Z rra $
+
+=head1 SEE ALSO
+
+makehistory(8)
+
+=cut
+
--- /dev/null
+=head1 NAME
+
+tst - ternary search trie functions
+
+=head1 SYNOPSIS
+
+B<#include E<lt>inn/tst.hE<gt>>
+
+B<struct tst;>
+
+B<struct tst *tst_init(int >I<node_line_width>B<);>
+
+B<void tst_cleanup(struct tst *>I<tst>B<);>
+
+B<int tst_insert(struct tst *>I<tst>B<, const unsigned char *>I<key>B<, void *>I<data>B<, int >I<option>B<, void **>I<exist_ptr>B<);>
+
+B<void *tst_search(struct tst *>I<tst>B<, const unsigned char *>I<key>B<);>
+
+B<void *tst_delete(struct tst *>I<tst>B<, const unsigned char *>I<key>B<);>
+
+=head1 DESCRIPTION
+
+B<tst_init> allocates memory for members of I<struct tst>, and
+allocates the first I<node_line_width> nodes. A NULL pointer is
+returned by B<tst_init> if any part of the memory allocation fails. On
+success, a pointer to a I<struct tst> is returned.
+
+The value for I<node_line_width> must be chosen very carefully. One
+node is required for every character in the tree. If you choose a
+value that is too small, your application will spend too much time
+calling malloc(3) and your node space will be too spread out. Too large
+a value is just a waste of space.
+
+B<tst_cleanup> frees all memory allocated to nodes, internal structures,
+as well as I<tst> itself.
+
+B<tst_insert> inserts the string I<key> into the tree. Behavior when a
+duplicate key is inserted is controlled by I<option>. If I<key> is
+already in the tree then B<TST_DUPLICATE_KEY> is returned, and the
+data pointer for the existing key is placed in I<exist_ptr>. If
+I<option> is set to B<TST_REPLACE> then the existing data pointer for
+the existing key is replaced by I<data>. Note that the old data
+pointer will still be placed in I<exist_ptr>.
+
+If a duplicate key is encountered and I<option> is not set to
+B<TST_REPLACE> then B<TST_DUPLICATE_KEY> is returned. If I<key> is
+zero length then B<TST_NULL_KEY> is returned. A successful insert or
+replace returns B<TST_OK>. A return value of B<TST_ERROR> indicates
+that a memory allocation error occurred while trying to grow the node
+free.
+
+Note that the I<data> argument must never be B<NULL>. If it is, then
+calls to B<tst_search> will fail for a key that exists because the
+data value was set to B<NULL>, which is what B<tst_search> returns. If
+you just want a simple existence tree, use the B<tst> pointer as the
+data pointer.
+
+B<tst_search> finds the string I<key> in the tree if it exists and
+returns the data pointer associated with that key.
+
+If I<key> is not found then B<NULL> is returned, otherwise the data pointer
+associated with I<key> is returned.
+
+B<tst_delete> deletes the string I<key> from the tree if it exists and
+returns the data pointer assocaited with that key.
+
+If I<key> is not found then B<NULL> is returned, otherwise the data
+pointer associated with I<key> is returned.
+
+=head1 HISTORY
+
+Converted to POD from Peter A. Friend's ternary search trie
+documentation by Alex Kiernan <alex.kiernan@thus.net> for InterNetNews
+2.4.0.
+
+$Id: tst.pod 6104 2003-01-02 09:16:09Z alexk $
--- /dev/null
+=head1 NAME
+
+uwildmat, uwildmat_simple, uwildmat_poison - Perform wildmat matching
+
+=head1 SYNOPSIS
+
+B<#include E<lt>libinn.hE<gt>>
+
+B<bool uwildmat(const char *>I<text>B<, const char *>I<pattern>B<);>
+
+B<bool uwildmat_simple(const char *>I<text>B<, const char *>I<pattern>B<);>
+
+B<enum uwildmat uwildmat_poison(const char *>I<text>B<,
+const char *>I<pattern>B<);>
+
+=head1 DESCRIPTION
+
+B<uwildmat> compares I<text> against the wildmat expression I<pattern>,
+returning true if and only if the expression matches the text. C<@> has
+no special meaning in I<pattern> when passed to B<uwildmat>. Both I<text>
+and I<pattern> are assumed to be in the UTF-8 character encoding, although
+malformed UTF-8 sequences are treated in a way that attempts to be mostly
+compatible with single-octet character sets like ISO 8859-1. (In other
+words, if you try to match ISO 8859-1 text with these routines everything
+should work as expected unless the ISO 8859-1 text contains valid UTF-8
+sequences, which thankfully is somewhat rare.)
+
+B<uwildmat_simple> is identical to B<uwildmat> except that neither C<!>
+nor C<,> have any special meaning and I<pattern> is always treated as a
+single pattern. This function exists solely to support legacy interfaces
+like NNTP's XPAT command, and should be avoided when implementing new
+features.
+
+B<uwildmat_poison> works similarly to B<uwildmat>, except that C<@> as the
+first character of one of the patterns in the expression (see below)
+"poisons" the match if it matches. B<uwildmat_poison> returns
+B<UWILDMAT_MATCH> if the expression matches the text, B<UWILDMAT_FAIL> if
+it doesn't, and B<UWILDMAT_POISON> if the expression doesn't match because
+a poisoned pattern matched the text. These enumeration constants are
+defined in the B<libinn.h> header.
+
+=head1 WILDMAT EXPRESSIONS
+
+A wildmat expression follows rules similar to those of shell filename
+wildcards but with some additions and changes. A wildmat I<expression> is
+composed of one or more wildmat I<patterns> separated by commas. Each
+character in the wildmat pattern matches a literal occurance of that same
+character in the text, with the exception of the following metacharacters:
+
+=over 8
+
+=item ?
+
+Matches any single character (including a single UTF-8 multibyte
+character, so C<?> can match more than one byte).
+
+=item *Z<>
+
+Matches any sequence of zero or more characters.
+
+=item \
+
+Turns off any special meaning of the following character; the following
+character will match itself in the text. C<\> will escape any character,
+including another backslash or a comma that otherwise would separate a
+pattern from the next pattern in an expression. Note that C<\> is not
+special inside a character range (no metacharacters are).
+
+=item [...]
+
+A character set, which matches any single character that falls within that
+set. The presence of a character between the brackets adds that character
+to the set; for example, C<[amv]> specifies the set containing the
+characters C<a>, C<m>, and C<v>. A range of characters may be specified
+using C<->; for example, C<[0-5abc]> is equivalent to C<[012345abc]>. The
+order of characters is as defined in the UTF-8 character set, and if the
+start character of such a range falls after the ending character of the
+range in that ranking the results of attempting a match with that pattern
+are undefined.
+
+In order to include a literal C<]> character in the set, it must be the
+first character of the set (possibly following C<^>); for example, C<[]a]>
+matches either C<]> or C<a>. To include a literal C<-> character in the
+set, it must be either the first or the last character of the set.
+Backslashes have no special meaning inside a character set, nor do any
+other of the wildmat metacharacters.
+
+=item [^...]
+
+A negated character set. Follows the same rules as a character set above,
+but matches any character B<not> contained in the set. So, for example,
+C<[^]-]> matches any character except C<]> and C<->.
+
+=back
+
+In addition, C<!> (and possibly C<@>) have special meaning as the first
+character of a pattern; see below.
+
+When matching a wildmat expression against some text, each comma-separated
+pattern is matched in order from left to right. In order to match, the
+pattern must match the whole text; in regular expression terminology, it's
+implicitly anchored at both the beginning and the end. For example, the
+pattern C<a> matches only the text C<a>; it doesn't match C<ab> or C<ba>
+or even C<aa>. If none of the patterns match, the whole expression
+doesn't match. Otherwise, whether the expression matches is determined
+entirely by the rightmost matching pattern; the expression matches the
+text if and only if the rightmost matching pattern is not negated.
+
+For example, consider the text C<news.misc>. The expression C<*> matches
+this text, of course, as does C<comp.*,news.*> (because the second pattern
+matches). C<news.*,!news.misc> does not match this text because both
+patterns match, meaning that the rightmost takes precedence, and the
+rightmost matching pattern is negated. C<news.*,!news.misc,*.misc> does
+match this text, since the rightmost matching pattern is not negated.
+
+Note that the expression C<!news.misc> can't match anything. Either the
+pattern doesn't match, in which case no patterns match and the expression
+doesn't match, or the pattern does match, in which case because it's
+negated the expression doesn't match. C<*,!news.misc>, on the other hand,
+is a useful pattern that matches anything except C<news.misc>.
+
+C<!> has significance only as the first character of a pattern; anywhere
+else in the pattern, it matches a literal C<!> in the text like any other
+non-metacharacter.
+
+If the B<uwildmat_poison> interface is used, then C<@> behaves the same as
+C<!> except that if an expression fails to match because the rightmost
+matching pattern began with C<@>, B<UWILDMAT_POISON> is returned instead of
+B<UWILDMAT_FAIL>.
+
+If the B<uwildmat_simple> interface is used, the matching rules are the
+same as above except that none of C<!>, C<@>, or C<,> have any special
+meaning at all and only match those literal characters.
+
+=head1 BUGS
+
+All of these functions internally convert the passed arguments to const
+unsigned char pointers. The only reason why they take regular char
+pointers instead of unsigned char is for the convenience of INN and other
+callers that may not be using unsigned char everywhere they should. In a
+future revision, the public interface should be changed to just take
+unsigned char pointers.
+
+=head1 HISTORY
+
+Written by Rich $alz <rsalz@uunet.uu.net> in 1986, and posted to Usenet
+several times since then, most notably in comp.sources.misc in
+March, 1991.
+
+Lars Mathiesen <thorinn@diku.dk> enhanced the multi-asterisk failure
+mode in early 1991.
+
+Rich and Lars increased the efficiency of star patterns and reposted it to
+comp.sources.misc in April, 1991.
+
+Robert Elz <kre@munnari.oz.au> added minus sign and close bracket handling
+in June, 1991.
+
+Russ Allbery <rra@stanford.edu> added support for comma-separated patterns
+and the C<!> and C<@> metacharacters to the core wildmat routines in July,
+2000. He also added support for UTF-8 characters, changed the default
+behavior to assume that both the text and the pattern are in UTF-8, and
+largely rewrote this documentation to expand and clarify the description
+of how a wildmat expression matches.
+
+Please note that the interfaces to these functions are named B<uwildmat>
+and the like rather than B<wildmat> to distinguish them from the
+B<wildmat> function provided by Rich $alz's original implementation.
+While this code is heavily based on Rich's original code, it has
+substantial differences, including the extension to support UTF-8
+characters, and has noticable functionality changes. Any bugs present in
+it aren't Rich's fault.
+
+$Id: uwildmat.pod 5533 2002-08-10 18:51:37Z rra $
+
+=head1 SEE ALSO
+
+grep(1), fnmatch(3), regex(3), regexp(3).
+
+=cut
--- /dev/null
+Path: bounce-back
+From: group-admin@isc.org (David C Lawrence)
+Newsgroups: news.announce.newusers
+Subject: cmsg newgroup news.announce.newusers moderated
+Control: newgroup news.announce.newusers moderated
+Approved: newgroups-request@isc.org
+Message-ID: <868485430.2655@isc.org>
+Date: Wed, 09 Jul 1997 21:57:10 GMT
+Lines: 8
+X-Info: ftp://ftp.isc.org/pub/pgpcontrol/README.html
+ ftp://ftp.isc.org/pub/pgpcontrol/README
+X-PGP-Sig: 2.6.2 Subject,Control,Message-ID,Date,From,Sender
+ iQCVAwUBM8QJNsJdOtO4janBAQGkUAP6AlzO065jDQFrG20/b3/SaOm4WGQBly5D
+ pXlVJdYBqPAG3HvxVqAdKM7y6ixM7Mml4OdfK0JeVCH03nqeGuBc51sTDIZ6kyAx
+ +YHlNSnp/JJnpDuJCfXZjwNl4kWImucGgwI5BxrQco8re949Cg5m5TFXiwYMiR/+
+ AjKZCTtmV1Y=
+ =uSbd
+
+news.announce.newusers is a moderated newsgroup which has existed
+since the mid-1980s.
+
+Group submission address: netannounce@deshaw.com
+Moderator contact address: netannounce@deshaw.com (Mark Moraes)
+
+For your newsgroups file:
+news.announce.newusers Explanatory postings for new users. (Moderated)
--- /dev/null
+## $Id: Makefile 7727 2008-04-06 07:59:46Z iulius $
+
+include ../Makefile.global
+
+top = ..
+CFLAGS = $(GCFLAGS)
+
+ALL = convdate expire expireover expirerm fastrm grephistory \
+ makedbz makehistory prunehistory
+
+SOURCES = convdate.c expire.c expireover.c fastrm.c grephistory.c \
+ makedbz.c makehistory.c prunehistory.c
+
+all: $(ALL)
+
+warnings:
+ $(MAKE) COPT='$(WARNINGS)' all
+
+install: all
+ for F in convdate fastrm grephistory ; do \
+ $(LI_XPUB) $$F $D$(PATHBIN)/$$F ; \
+ done
+ for F in expire expireover makedbz makehistory prunehistory ; do \
+ $(LI_XPRI) $$F $D$(PATHBIN)/$$F ; \
+ done
+ $(CP_XPRI) expirerm $D$(PATHBIN)/expirerm
+
+clean:
+ rm -f *.o $(ALL)
+ rm -f profiled expirep
+ rm -rf .libs
+
+clobber distclean: clean
+ rm -f tags
+
+tags ctags: $(SOURCES)
+ $(CTAGS) $(SOURCES)
+
+
+## Compilation rules.
+
+BOTH = $(LIBHIST) $(LIBSTORAGE) $(LIBINN)
+
+LINK = $(LIBLD) $(LDFLAGS) -o $@
+INNLIBS = $(LIBINN) $(LIBS)
+STORELIBS = $(BOTH) $(EXTSTORAGELIBS) $(LIBS)
+
+FIX = $(FIXSCRIPT)
+
+$(FIXSCRIPT):
+ @echo Run configure before running make. See INSTALL for details.
+ @exit 1
+
+convdate: convdate.o $(LIBINN) ; $(LINK) convdate.o $(INNLIBS)
+expire: expire.o $(BOTH) ; $(LINK) expire.o $(STORELIBS)
+expireover: expireover.o $(BOTH) ; $(LINK) expireover.o $(STORELIBS)
+fastrm: fastrm.o $(BOTH) ; $(LINK) fastrm.o $(STORELIBS)
+grephistory: grephistory.o $(BOTH) ; $(LINK) grephistory.o $(STORELIBS)
+makedbz: makedbz.o $(LIBINN) ; $(LINK) makedbz.o $(INNLIBS)
+makehistory: makehistory.o $(BOTH) ; $(LINK) makehistory.o $(STORELIBS)
+prunehistory: prunehistory.o $(BOTH) ; $(LINK) prunehistory.o $(STORELIBS)
+
+expirerm: expirerm.in $(FIX) ; $(FIX) expirerm.in
+
+$(LIBINN): ; (cd ../lib ; $(MAKE))
+$(LIBSTORAGE): ; (cd ../storage ; $(MAKE))
+$(LIBHIST): ; (cd ../history ; $(MAKE))
+
+
+## Profiling. These rules have not been checked for a while and may need
+## some work.
+
+profiled: expirep
+ date >$@
+
+expirep: expire.c
+ rm -f expire.o
+ $(MAKEPROFILING) expire
+ mv expire expirep
+ rm -f expire.o
+
+
+## Dependencies. Default list, below, is probably good enough.
+
+depend: Makefile $(SOURCES)
+ $(MAKEDEPEND) '$(CFLAGS)' $(SOURCES)
+
+# DO NOT DELETE THIS LINE -- make depend depends on it.
+convdate.o: convdate.c ../include/config.h ../include/inn/defines.h \
+ ../include/inn/system.h ../include/clibrary.h ../include/config.h \
+ ../include/inn/messages.h ../include/inn/defines.h ../include/libinn.h
+expire.o: expire.c ../include/config.h ../include/inn/defines.h \
+ ../include/inn/system.h ../include/clibrary.h ../include/config.h \
+ ../include/inn/history.h ../include/inn/defines.h \
+ ../include/inn/innconf.h ../include/inn/messages.h \
+ ../include/inndcomm.h ../include/libinn.h ../include/paths.h \
+ ../include/storage.h
+expireover.o: expireover.c ../include/config.h ../include/inn/defines.h \
+ ../include/inn/system.h ../include/clibrary.h ../include/config.h \
+ ../include/inn/innconf.h ../include/inn/defines.h \
+ ../include/inn/messages.h ../include/inn/qio.h ../include/libinn.h \
+ ../include/ov.h ../include/storage.h ../include/inn/history.h \
+ ../include/paths.h ../include/storage.h
+fastrm.o: fastrm.c ../include/config.h ../include/inn/defines.h \
+ ../include/inn/system.h ../include/clibrary.h ../include/config.h \
+ ../include/inn/innconf.h ../include/inn/defines.h \
+ ../include/inn/messages.h ../include/inn/qio.h ../include/libinn.h \
+ ../include/storage.h
+grephistory.o: grephistory.c ../include/clibrary.h ../include/config.h \
+ ../include/inn/defines.h ../include/inn/system.h \
+ ../include/inn/history.h ../include/inn/defines.h \
+ ../include/inn/innconf.h ../include/inn/messages.h ../include/libinn.h \
+ ../include/paths.h ../include/storage.h
+makedbz.o: makedbz.c ../include/config.h ../include/inn/defines.h \
+ ../include/inn/system.h ../include/clibrary.h ../include/config.h \
+ ../include/dbz.h ../include/libinn.h ../include/inn/innconf.h \
+ ../include/inn/defines.h ../include/inn/messages.h ../include/inn/qio.h \
+ ../include/libinn.h ../include/paths.h ../include/storage.h
+makehistory.o: makehistory.c ../include/config.h ../include/inn/defines.h \
+ ../include/inn/system.h ../include/clibrary.h ../include/config.h \
+ ../include/portable/wait.h ../include/config.h ../include/inn/buffer.h \
+ ../include/inn/defines.h ../include/inn/history.h \
+ ../include/inn/innconf.h ../include/inn/messages.h ../include/inn/qio.h \
+ ../include/inn/wire.h ../include/libinn.h ../include/ov.h \
+ ../include/storage.h ../include/inn/history.h ../include/paths.h \
+ ../include/storage.h
+prunehistory.o: prunehistory.c ../include/config.h \
+ ../include/inn/defines.h ../include/inn/system.h ../include/clibrary.h \
+ ../include/config.h ../include/inn/history.h ../include/inn/defines.h \
+ ../include/inn/innconf.h ../include/inn/messages.h ../include/libinn.h \
+ ../include/paths.h
--- /dev/null
+/* $Id: convdate.c 6372 2003-05-31 19:48:28Z rra $
+**
+** Convert date strings and numbers to numbers and strings.
+*/
+
+#include "config.h"
+#include "clibrary.h"
+#include <ctype.h>
+#include <time.h>
+
+#include "inn/messages.h"
+#include "libinn.h"
+
+static const char usage[] = "\
+Usage: convdate -n [date ...]\n\
+ convdate [-dl] -c [time ...]\n\
+ convdate [-dl] [-s] [date ...]\n\
+\n\
+convdate -n converts a date (in any format parseable by parsedate) to the\n\
+number of seconds since epoch. convdate -s does the same, but converts\n\
+to a date string. convdate -c converts seconds since epoch to a date\n\
+string. The default output is the output of ctime (normally the same format\n\
+as returned by the date command). If -d is given, the output is formatted\n\
+as a valid Usenet article Date header. If -l is given with -d, format the\n\
+time in local time rather than UTC. If no options are given, the -s\n\
+behavior is the default; if no dates are given, the current time is used.\n";
+
+/* Whether to format the output as a Date header. */
+static bool date_format = false;
+
+/* Whether to use local time instead of UTC. */
+static bool date_local = false;
+
+
+/*
+** Return true if the given string is entirely digits.
+*/
+static bool
+isdigits(const char *p)
+{
+ for (; *p; p++)
+ if (!CTYPE(isdigit, *p))
+ return false;
+ return true;
+}
+
+
+/*
+** Print date corresponding to the provided time_t. By default, the output
+** of ctime is printed, but if date_format is true, makedate is used
+** instead. If date_local is true, format in local time; otherwise, use
+** UTC. Returns success.
+*/
+static bool
+print_date(time_t date)
+{
+ char date_buffer[128];
+ char *result;
+
+ if (date_format) {
+ if (!makedate(date, date_local, date_buffer, sizeof(date_buffer))) {
+ warn("can't format %ld", (long) date);
+ return false;
+ } else {
+ printf("%s\n", date_buffer);
+ }
+ } else {
+ result = ctime(&date);
+ if (result == NULL) {
+ warn("can't format %ld", (long) date);
+ return false;
+ } else {
+ printf("%s", result);
+ }
+ }
+ return true;
+}
+
+
+/*
+** The core function. Given a string representing a date (in some format
+** given by the mode) and a mode ('s', 'n', or 'c', corresponding to the
+** basic three options to the program), convert the date and print the
+** output. date may be NULL, in which case the current date is used.
+** Returns true if conversion was successful, false otherwise.
+*/
+static bool
+convdate(const char *date, char mode)
+{
+ time_t seconds;
+
+ /* Convert the given date to seconds or obtain the current time. */
+ if (date == NULL) {
+ seconds = time(NULL);
+ } else if (mode == 'c') {
+ if (!isdigits(date)) {
+ warn("\"%s\" doesn't look like a number", date);
+ return false;
+ } else {
+ seconds = (time_t) atol(date);
+ }
+ } else {
+ seconds = parsedate((char *) date, NULL);
+ if (seconds == (time_t) -1) {
+ warn("can't convert \"%s\"", date);
+ return false;
+ }
+ }
+
+ /* Output the resulting date. */
+ if (mode == 'n') {
+ printf("%ld\n", (long) seconds);
+ return true;
+ } else {
+ return print_date(seconds);
+ }
+}
+
+
+int
+main(int argc, char *argv[])
+{
+ int option, status;
+ char *date;
+ char mode = '\0';
+
+ message_program_name = "convdate";
+
+ /* Parse options. */
+ while ((option = getopt(argc, argv, "cdhlns")) != EOF) {
+ switch (option) {
+ case 'h':
+ printf("%s\n", usage);
+ exit(0);
+ break;
+ case 'd':
+ date_format = true;
+ break;
+ case 'l':
+ date_local = true;
+ break;
+ case 'c':
+ case 'n':
+ case 's':
+ if (mode != 0) die("only one of -c, -n, or -s is allowed");
+ mode = option;
+ break;
+ default:
+ fprintf(stderr, "%s", usage);
+ exit(1);
+ break;
+ }
+ }
+ if (mode == '\0')
+ mode = 's';
+ argc -= optind;
+ argv += optind;
+
+ /* Perform the desired action for each provided argument. */
+ if (argc == 0) {
+ exit(convdate(NULL, mode) ? 0 : 1);
+ } else {
+ for (date = *argv, status = 0; date != NULL; date = *++argv)
+ status += (convdate(date, mode) ? 0 : 1);
+ exit(status);
+ }
+}
--- /dev/null
+/* $Id: expire.c 6135 2003-01-19 01:15:40Z rra $
+**
+** Expire news articles.
+*/
+
+#include "config.h"
+#include "clibrary.h"
+#include <ctype.h>
+#include <errno.h>
+#include <pwd.h>
+#include <sys/stat.h>
+#include <syslog.h>
+#include <time.h>
+
+#include "inn/history.h"
+#include "inn/innconf.h"
+#include "inn/messages.h"
+#include "inndcomm.h"
+#include "libinn.h"
+#include "paths.h"
+#include "storage.h"
+
+
+typedef struct _EXPIRECLASS {
+ time_t Keep;
+ time_t Default;
+ time_t Purge;
+ bool Missing;
+ bool ReportedMissing;
+} EXPIRECLASS;
+
+/*
+** Expire-specific stuff.
+*/
+#define MAGIC_TIME 49710.
+
+static bool EXPtracing;
+static bool EXPusepost;
+static bool Ignoreselfexpire = false;
+static FILE *EXPunlinkfile;
+static EXPIRECLASS EXPclasses[NUM_STORAGE_CLASSES+1];
+static char *EXPreason;
+static time_t EXPremember;
+static time_t Now;
+static time_t RealNow;
+
+/* Statistics; for -v flag. */
+static char *EXPgraph;
+static int EXPverbose;
+static long EXPprocessed;
+static long EXPunlinked;
+static long EXPallgone;
+static long EXPstillhere;
+static struct history *History;
+static char *NHistory;
+
+static void CleanupAndExit(bool Server, bool Paused, int x);
+
+static int EXPsplit(char *p, char sep, char **argv, int count);
+
+enum KR {Keep, Remove};
+
+\f
+
+/*
+** Open a file or give up.
+*/
+static FILE *
+EXPfopen(bool Unlink, const char *Name, const char *Mode, bool Needclean,
+ bool Server, bool Paused)
+{
+ FILE *F;
+
+ if (Unlink && unlink(Name) < 0 && errno != ENOENT)
+ syswarn("cannot remove %s", Name);
+ if ((F = fopen(Name, Mode)) == NULL) {
+ syswarn("cannot open %s in %s mode", Name, Mode);
+ if (Needclean)
+ CleanupAndExit(Server, Paused, 1);
+ else
+ exit(1);
+ }
+ return F;
+}
+
+
+/*
+** Split a line at a specified field separator into a vector and return
+** the number of fields found, or -1 on error.
+*/
+static int EXPsplit(char *p, char sep, char **argv, int count)
+{
+ int i;
+
+ if (!p)
+ return 0;
+
+ while (*p == sep)
+ ++p;
+
+ if (!*p)
+ return 0;
+
+ if (!p)
+ return 0;
+
+ while (*p == sep)
+ ++p;
+
+ if (!*p)
+ return 0;
+
+ for (i = 1, *argv++ = p; *p; )
+ if (*p++ == sep) {
+ p[-1] = '\0';
+ for (; *p == sep; p++)
+ ;
+ if (!*p)
+ return i;
+ if (++i == count)
+ /* Overflow. */
+ return -1;
+ *argv++ = p;
+ }
+ return i;
+}
+
+
+/*
+** Parse a number field converting it into a "when did this start?".
+** This makes the "keep it" tests fast, but inverts the logic of
+** just about everything you expect. Print a message and return false
+** on error.
+*/
+static bool EXPgetnum(int line, char *word, time_t *v, const char *name)
+{
+ char *p;
+ bool SawDot;
+ double d;
+
+ if (strcasecmp(word, "never") == 0) {
+ *v = (time_t)0;
+ return true;
+ }
+
+ /* Check the number. We don't have strtod yet. */
+ for (p = word; ISWHITE(*p); p++)
+ ;
+ if (*p == '+' || *p == '-')
+ p++;
+ for (SawDot = false; *p; p++)
+ if (*p == '.') {
+ if (SawDot)
+ break;
+ SawDot = true;
+ }
+ else if (!CTYPE(isdigit, (int)*p))
+ break;
+ if (*p) {
+ warn("bad '%c' character in %s field on line %d", *p, name, line);
+ return false;
+ }
+ d = atof(word);
+ if (d > MAGIC_TIME)
+ *v = (time_t)0;
+ else
+ *v = Now - (time_t)(d * 86400.);
+ return true;
+}
+
+
+/*
+** Parse the expiration control file. Return true if okay.
+*/
+static bool EXPreadfile(FILE *F)
+{
+ char *p;
+ int i;
+ int j;
+ bool SawDefault;
+ char buff[BUFSIZ];
+ char *fields[7];
+
+ /* Scan all lines. */
+ EXPremember = -1;
+ SawDefault = false;
+
+ for (i = 0; i <= NUM_STORAGE_CLASSES; i++) {
+ EXPclasses[i].ReportedMissing = false;
+ EXPclasses[i].Missing = true;
+ }
+
+ for (i = 1; fgets(buff, sizeof buff, F) != NULL; i++) {
+ if ((p = strchr(buff, '\n')) == NULL) {
+ warn("line %d too long", i);
+ return false;
+ }
+ *p = '\0';
+ p = strchr(buff, '#');
+ if (p)
+ *p = '\0';
+ else
+ p = buff + strlen(buff);
+ while (--p >= buff) {
+ if (isspace((int)*p))
+ *p = '\0';
+ else
+ break;
+ }
+ if (buff[0] == '\0')
+ continue;
+ if ((j = EXPsplit(buff, ':', fields, ARRAY_SIZE(fields))) == -1) {
+ warn("too many fields on line %d", i);
+ return false;
+ }
+
+ /* Expired-article remember line? */
+ if (strcmp(fields[0], "/remember/") == 0) {
+ if (j != 2) {
+ warn("invalid format on line %d", i);
+ return false;
+ }
+ if (EXPremember != -1) {
+ warn("duplicate /remember/ on line %d", i);
+ return false;
+ }
+ if (!EXPgetnum(i, fields[1], &EXPremember, "remember"))
+ return false;
+ continue;
+ }
+
+ /* Storage class line? */
+ if (j == 4) {
+ /* Is this the default line? */
+ if (fields[0][0] == '*' && fields[0][1] == '\0') {
+ if (SawDefault) {
+ warn("duplicate default on line %d", i);
+ return false;
+ }
+ j = NUM_STORAGE_CLASSES;
+ SawDefault = true;
+ } else {
+ j = atoi(fields[0]);
+ if ((j < 0) || (j >= NUM_STORAGE_CLASSES))
+ warn("bad storage class %d on line %d", j, i);
+ }
+
+ if (!EXPgetnum(i, fields[1], &EXPclasses[j].Keep, "keep")
+ || !EXPgetnum(i, fields[2], &EXPclasses[j].Default, "default")
+ || !EXPgetnum(i, fields[3], &EXPclasses[j].Purge, "purge"))
+ return false;
+ /* These were turned into offsets, so the test is the opposite
+ * of what you think it should be. If Purge isn't forever,
+ * make sure it's greater then the other two fields. */
+ if (EXPclasses[j].Purge) {
+ /* Some value not forever; make sure other values are in range. */
+ if (EXPclasses[j].Keep && EXPclasses[j].Keep < EXPclasses[j].Purge) {
+ warn("keep time longer than purge time on line %d", i);
+ return false;
+ }
+ if (EXPclasses[j].Default && EXPclasses[j].Default < EXPclasses[j].Purge) {
+ warn("default time longer than purge time on line %d", i);
+ return false;
+ }
+ }
+ EXPclasses[j].Missing = false;
+ continue;
+ }
+
+ /* Regular expiration line -- right number of fields? */
+ if (j != 5) {
+ warn("bad format on line %d", i);
+ return false;
+ }
+ continue; /* don't process this line--per-group expiry is done by expireover */
+ }
+
+ return true;
+}
+
+/*
+** Should we keep the specified article?
+*/
+static enum KR EXPkeepit(const TOKEN *token, time_t when, time_t Expires)
+{
+ EXPIRECLASS class;
+
+ class = EXPclasses[token->class];
+ if (class.Missing) {
+ if (EXPclasses[NUM_STORAGE_CLASSES].Missing) {
+ /* no default */
+ if (!class.ReportedMissing) {
+ warn("class definition for %d missing from control file,"
+ " assuming it should never expire", token->class);
+ EXPclasses[token->class].ReportedMissing = true;
+ }
+ return Keep;
+ } else {
+ /* use the default */
+ class = EXPclasses[NUM_STORAGE_CLASSES];
+ EXPclasses[token->class] = class;
+ }
+ }
+ /* Bad posting date? */
+ if (when > (RealNow + 86400)) {
+ /* Yes -- force the article to go to right now */
+ when = Expires ? class.Purge : class.Default;
+ }
+ if (EXPverbose > 2) {
+ if (EXPverbose > 3)
+ printf("%s age = %0.2f\n", TokenToText(*token), (Now - when) / 86400.);
+ if (Expires == 0) {
+ if (when <= class.Default)
+ printf("%s too old (no exp)\n", TokenToText(*token));
+ } else {
+ if (when <= class.Purge)
+ printf("%s later than purge\n", TokenToText(*token));
+ if (when >= class.Keep)
+ printf("%s earlier than min\n", TokenToText(*token));
+ if (Now >= Expires)
+ printf("%s later than header\n", TokenToText(*token));
+ }
+ }
+
+ /* If no expiration, make sure it wasn't posted before the default. */
+ if (Expires == 0) {
+ if (when >= class.Default)
+ return Keep;
+
+ /* Make sure it's not posted before the purge cut-off and
+ * that it's not due to expire. */
+ } else {
+ if (when >= class.Purge && (Expires >= Now || when >= class.Keep))
+ return Keep;
+ }
+ return Remove;
+
+}
+
+
+/*
+** An article can be removed. Either print a note, or actually remove it.
+** Also fill in the article size.
+*/
+static void
+EXPremove(const TOKEN *token)
+{
+ /* Turn into a filename and get the size if we need it. */
+ if (EXPverbose > 1)
+ printf("\tunlink %s\n", TokenToText(*token));
+
+ if (EXPtracing) {
+ EXPunlinked++;
+ printf("%s\n", TokenToText(*token));
+ return;
+ }
+
+ EXPunlinked++;
+ if (EXPunlinkfile) {
+ fprintf(EXPunlinkfile, "%s\n", TokenToText(*token));
+ if (!ferror(EXPunlinkfile))
+ return;
+ syswarn("cannot write to -z file (will ignore it for rest of run)");
+ fclose(EXPunlinkfile);
+ EXPunlinkfile = NULL;
+ }
+ if (!SMcancel(*token) && SMerrno != SMERR_NOENT && SMerrno != SMERR_UNINIT)
+ warn("cannot unlink %s", TokenToText(*token));
+}
+
+/*
+** Do the work of expiring one line.
+*/
+static bool
+EXPdoline(void *cookie UNUSED, time_t arrived, time_t posted, time_t expires,
+ TOKEN *token)
+{
+ time_t when;
+ bool HasSelfexpire = false;
+ bool Selfexpired = false;
+ ARTHANDLE *article;
+ enum KR kr;
+ bool r;
+
+ if (innconf->groupbaseexpiry || SMprobe(SELFEXPIRE, token, NULL)) {
+ if ((article = SMretrieve(*token, RETR_STAT)) == (ARTHANDLE *)NULL) {
+ HasSelfexpire = true;
+ Selfexpired = true;
+ } else {
+ /* the article is still alive */
+ SMfreearticle(article);
+ if (innconf->groupbaseexpiry || !Ignoreselfexpire)
+ HasSelfexpire = true;
+ }
+ }
+ if (EXPusepost && posted != 0)
+ when = posted;
+ else
+ when = arrived;
+ EXPprocessed++;
+
+ if (HasSelfexpire) {
+ if (Selfexpired || token->type == TOKEN_EMPTY) {
+ EXPallgone++;
+ r = false;
+ } else {
+ EXPstillhere++;
+ r = true;
+ }
+ } else {
+ kr = EXPkeepit(token, when, expires);
+ if (kr == Remove) {
+ EXPremove(token);
+ EXPallgone++;
+ r = false;
+ } else {
+ EXPstillhere++;
+ r = true;
+ }
+ }
+
+ return r;
+}
+
+\f
+
+/*
+** Clean up link with the server and exit.
+*/
+static void
+CleanupAndExit(bool Server, bool Paused, int x)
+{
+ FILE *F;
+
+ if (Server) {
+ ICCreserve("");
+ if (Paused && ICCgo(EXPreason) != 0) {
+ syswarn("cannot unpause server");
+ x = 1;
+ }
+ }
+ if (Server && ICCclose() < 0) {
+ syswarn("cannot close communication link to server");
+ x = 1;
+ }
+ if (EXPunlinkfile && fclose(EXPunlinkfile) == EOF) {
+ syswarn("cannot close -z file");
+ x = 1;
+ }
+
+ /* Report stats. */
+ if (EXPverbose) {
+ printf("Article lines processed %8ld\n", EXPprocessed);
+ printf("Articles retained %8ld\n", EXPstillhere);
+ printf("Entries expired %8ld\n", EXPallgone);
+ if (!innconf->groupbaseexpiry)
+ printf("Articles dropped %8ld\n", EXPunlinked);
+ }
+
+ /* Append statistics to a summary file */
+ if (EXPgraph) {
+ F = EXPfopen(false, EXPgraph, "a", false, false, false);
+ fprintf(F, "%ld %ld %ld %ld %ld\n",
+ (long)Now, EXPprocessed, EXPstillhere, EXPallgone,
+ EXPunlinked);
+ fclose(F);
+ }
+
+ SMshutdown();
+ HISclose(History);
+ if (EXPreason != NULL)
+ free(EXPreason);
+
+ if (NHistory != NULL)
+ free(NHistory);
+ closelog();
+ exit(x);
+}
+
+/*
+** Print a usage message and exit.
+*/
+static void
+Usage(void)
+{
+ fprintf(stderr, "Usage: expire [flags] [expire.ctl]\n");
+ exit(1);
+}
+
+
+/*
+** Change to the news user if possible, and if not, die. Used for operations
+** that may create new database files so as not to mess up the ownership.
+*/
+static void
+setuid_news(void)
+{
+ struct passwd *pwd;
+
+ pwd = getpwnam(NEWSUSER);
+ if (pwd == NULL)
+ die("can't resolve %s to a UID (account doesn't exist?)", NEWSUSER);
+ if (getuid() == 0)
+ setuid(pwd->pw_uid);
+ if (getuid() != pwd->pw_uid)
+ die("must be run as %s", NEWSUSER);
+}
+
+
+int
+main(int ac, char *av[])
+{
+ int i;
+ char *p;
+ FILE *F;
+ char *HistoryText;
+ const char *NHistoryPath = NULL;
+ const char *NHistoryText = NULL;
+ char *EXPhistdir;
+ char buff[SMBUF];
+ bool Server;
+ bool Bad;
+ bool IgnoreOld;
+ bool Writing;
+ bool UnlinkFile;
+ bool val;
+ time_t TimeWarp;
+ size_t Size = 0;
+
+ /* First thing, set up logging and our identity. */
+ openlog("expire", L_OPENLOG_FLAGS | LOG_PID, LOG_INN_PROG);
+ message_program_name = "expire";
+
+ /* Set defaults. */
+ Server = true;
+ IgnoreOld = false;
+ Writing = true;
+ TimeWarp = 0;
+ UnlinkFile = false;
+
+ if (!innconf_read(NULL))
+ exit(1);
+
+ HistoryText = concatpath(innconf->pathdb, _PATH_HISTORY);
+
+ umask(NEWSUMASK);
+
+ /* find the default history file directory */
+ EXPhistdir = xstrdup(HistoryText);
+ p = strrchr(EXPhistdir, '/');
+ if (p != NULL) {
+ *p = '\0';
+ }
+
+ /* Parse JCL. */
+ while ((i = getopt(ac, av, "f:h:d:g:iNnpr:s:tv:w:xz:")) != EOF)
+ switch (i) {
+ default:
+ Usage();
+ /* NOTREACHED */
+ case 'd':
+ NHistoryPath = optarg;
+ break;
+ case 'f':
+ NHistoryText = optarg;
+ break;
+ case 'g':
+ EXPgraph = optarg;
+ break;
+ case 'h':
+ HistoryText = optarg;
+ break;
+ case 'i':
+ IgnoreOld = true;
+ break;
+ case 'N':
+ Ignoreselfexpire = true;
+ break;
+ case 'n':
+ Server = false;
+ break;
+ case 'p':
+ EXPusepost = true;
+ break;
+ case 'r':
+ EXPreason = xstrdup(optarg);
+ break;
+ case 's':
+ Size = atoi(optarg);
+ break;
+ case 't':
+ EXPtracing = true;
+ break;
+ case 'v':
+ EXPverbose = atoi(optarg);
+ break;
+ case 'w':
+ TimeWarp = (time_t)(atof(optarg) * 86400.);
+ break;
+ case 'x':
+ Writing = false;
+ break;
+ case 'z':
+ EXPunlinkfile = EXPfopen(true, optarg, "a", false, false, false);
+ UnlinkFile = true;
+ break;
+ }
+ ac -= optind;
+ av += optind;
+ if ((ac != 0 && ac != 1))
+ Usage();
+
+ /* if EXPtracing is set, then pass in a path, this ensures we
+ * don't replace the existing history files */
+ if (EXPtracing || NHistoryText || NHistoryPath) {
+ if (NHistoryPath == NULL)
+ NHistoryPath = innconf->pathdb;
+ if (NHistoryText == NULL)
+ NHistoryText = _PATH_HISTORY;
+ NHistory = concatpath(NHistoryPath, NHistoryText);
+ }
+ else {
+ NHistory = NULL;
+ }
+
+ time(&Now);
+ RealNow = Now;
+ Now += TimeWarp;
+
+ /* Change users if necessary. */
+ setuid_news();
+
+ /* Parse the control file. */
+ if (av[0]) {
+ if (strcmp(av[0], "-") == 0)
+ F = stdin;
+ else
+ F = EXPfopen(false, av[0], "r", false, false, false);
+ } else {
+ char *path;
+
+ path = concatpath(innconf->pathetc, _PATH_EXPIRECTL);
+ F = EXPfopen(false, path, "r", false, false, false);
+ free(path);
+ }
+ if (!EXPreadfile(F)) {
+ fclose(F);
+ die("format error in expire.ctl");
+ }
+ fclose(F);
+
+ /* Set up the link, reserve the lock. */
+ if (Server) {
+ if (EXPreason == NULL) {
+ snprintf(buff, sizeof(buff), "Expiring process %ld",
+ (long) getpid());
+ EXPreason = xstrdup(buff);
+ }
+ }
+ else {
+ EXPreason = NULL;
+ }
+
+ if (Server) {
+ /* If we fail, leave evidence behind. */
+ if (ICCopen() < 0) {
+ syswarn("cannot open channel to server");
+ CleanupAndExit(false, false, 1);
+ }
+ if (ICCreserve((char *)EXPreason) != 0) {
+ warn("cannot reserve server");
+ CleanupAndExit(false, false, 1);
+ }
+ }
+
+ History = HISopen(HistoryText, innconf->hismethod, HIS_RDONLY);
+ if (!History) {
+ warn("cannot open history");
+ CleanupAndExit(Server, false, 1);
+ }
+
+ /* Ignore failure on the HISctl()s, if the underlying history
+ * manager doesn't implement them its not a disaster */
+ HISctl(History, HISCTLS_IGNOREOLD, &IgnoreOld);
+ if (Size != 0) {
+ HISctl(History, HISCTLS_NPAIRS, &Size);
+ }
+
+ val = true;
+ if (!SMsetup(SM_RDWR, (void *)&val) || !SMsetup(SM_PREOPEN, (void *)&val)) {
+ warn("cannot set up storage manager");
+ CleanupAndExit(Server, false, 1);
+ }
+ if (!SMinit()) {
+ warn("cannot initialize storage manager: %s", SMerrorstr);
+ CleanupAndExit(Server, false, 1);
+ }
+ if (chdir(EXPhistdir) < 0) {
+ syswarn("cannot chdir to %s", EXPhistdir);
+ CleanupAndExit(Server, false, 1);
+ }
+
+ Bad = HISexpire(History, NHistory, EXPreason, Writing, NULL,
+ EXPremember, EXPdoline) == false;
+
+ if (UnlinkFile && EXPunlinkfile == NULL)
+ /* Got -z but file was closed; oops. */
+ Bad = true;
+
+ /* If we're done okay, and we're not tracing, slip in the new files. */
+ if (EXPverbose) {
+ if (Bad)
+ printf("Expire errors: history files not updated.\n");
+ if (EXPtracing)
+ printf("Expire tracing: history files not updated.\n");
+ }
+
+ if (!Bad && NHistory != NULL) {
+ snprintf(buff, sizeof(buff), "%s.n.done", NHistory);
+ fclose(EXPfopen(false, buff, "w", true, Server, false));
+ CleanupAndExit(Server, false, Bad ? 1 : 0);
+ }
+
+ CleanupAndExit(Server, !Bad, Bad ? 1 : 0);
+ /* NOTREACHED */
+ abort();
+}
--- /dev/null
+/* $Id: expireover.c 6708 2004-05-16 19:59:26Z rra $
+**
+** Expire the overview database.
+**
+** This program handles the nightly expiration of overview information. If
+** groupbaseexpiry is true, this program also handles the removal of
+** articles that have expired. It's separate from the process that scans
+** and expires the history file.
+*/
+
+#include "config.h"
+#include "clibrary.h"
+#include <errno.h>
+#include <pwd.h>
+#include <signal.h>
+#include <syslog.h>
+#include <time.h>
+
+#include "inn/innconf.h"
+#include "inn/messages.h"
+#include "inn/qio.h"
+#include "libinn.h"
+#include "ov.h"
+#include "paths.h"
+#include "storage.h"
+
+static const char usage[] = "\
+Usage: expireover [-ekNpqs] [-w offset] [-z rmfile] [-Z lowmarkfile]\n";
+
+/* Set to 1 if we've received a signal; expireover then terminates after
+ finishing the newsgroup that it's working on (this prevents corruption of
+ the overview by killing expireover). */
+static volatile sig_atomic_t signalled = 0;
+
+
+/*
+** Handle a fatal signal and set signalled. Restore the default signal
+** behavior after receiving a signal so that repeating the signal will kill
+** the program immediately.
+*/
+static RETSIGTYPE
+fatal_signal(int sig)
+{
+ signalled = 1;
+ xsignal(sig, SIG_DFL);
+}
+
+
+/*
+** Change to the news user if possible, and if not, die. Used for operations
+** that may create new database files so as not to mess up the ownership.
+*/
+static void
+setuid_news(void)
+{
+ struct passwd *pwd;
+
+ pwd = getpwnam(NEWSUSER);
+ if (pwd == NULL)
+ die("can't resolve %s to a UID (account doesn't exist?)", NEWSUSER);
+ if (getuid() == 0)
+ setuid(pwd->pw_uid);
+ if (getuid() != pwd->pw_uid)
+ die("must be run as %s", NEWSUSER);
+}
+
+
+int
+main(int argc, char *argv[])
+{
+ int option, low;
+ char *line, *p;
+ QIOSTATE *qp;
+ bool value;
+ OVGE ovge;
+ char *active_path = NULL;
+ char *lowmark_path = NULL;
+ char *path;
+ FILE *lowmark = NULL;
+ bool purge_deleted = false;
+ bool always_stat = false;
+ struct history *history;
+
+ /* First thing, set up logging and our identity. */
+ openlog("expireover", L_OPENLOG_FLAGS | LOG_PID, LOG_INN_PROG);
+ message_program_name = "expireover";
+
+ /* Set up some default options for group-based expiration, although none
+ of these will be used if groupbaseexpiry isn't true. */
+ ovge.earliest = false;
+ ovge.keep = false;
+ ovge.ignoreselfexpire = false;
+ ovge.usepost = false;
+ ovge.quiet = false;
+ ovge.timewarp = 0;
+ ovge.filename = NULL;
+ ovge.delayrm = false;
+
+ /* Parse the command-line options. */
+ while ((option = getopt(argc, argv, "ef:kNpqsw:z:Z:")) != EOF) {
+ switch (option) {
+ case 'e':
+ ovge.earliest = true;
+ break;
+ case 'f':
+ active_path = xstrdup(optarg);
+ break;
+ case 'k':
+ ovge.keep = true;
+ break;
+ case 'N':
+ ovge.ignoreselfexpire = true;
+ break;
+ case 'p':
+ ovge.usepost = true;
+ break;
+ case 'q':
+ ovge.quiet = true;
+ break;
+ case 's':
+ always_stat = true;
+ break;
+ case 'w':
+ ovge.timewarp = (time_t) (atof(optarg) * 86400.);
+ break;
+ case 'z':
+ ovge.filename = optarg;
+ ovge.delayrm = true;
+ break;
+ case 'Z':
+ lowmark_path = optarg;
+ break;
+ default:
+ fprintf(stderr, "%s", usage);
+ exit(1);
+ }
+ }
+ if (ovge.earliest && ovge.keep)
+ die("-e and -k cannot be specified at the same time");
+
+ /* Initialize innconf. */
+ if (!innconf_read(NULL))
+ exit(1);
+
+ /* Change to the news user if necessary. */
+ setuid_news();
+
+ /* Initialize the lowmark file, if one was requested. */
+ if (lowmark_path != NULL) {
+ if (unlink(lowmark_path) < 0 && errno != ENOENT)
+ syswarn("can't remove %s", lowmark_path);
+ lowmark = fopen(lowmark_path, "a");
+ if (lowmark == NULL)
+ sysdie("can't open %s", lowmark_path);
+ }
+
+ /* Set up the path to the list of newsgroups we're going to use and open
+ that file. This could be stdin. */
+ if (active_path == NULL) {
+ active_path = concatpath(innconf->pathdb, _PATH_ACTIVE);
+ purge_deleted = true;
+ }
+ if (strcmp(active_path, "-") == 0) {
+ qp = QIOfdopen(fileno(stdin));
+ if (qp == NULL)
+ sysdie("can't reopen stdin");
+ } else {
+ qp = QIOopen(active_path);
+ if (qp == NULL)
+ sysdie("can't open active file (%s)", active_path);
+ }
+ free(active_path);
+
+ /* open up the history manager */
+ path = concatpath(innconf->pathdb, _PATH_HISTORY);
+ history = HISopen(path, innconf->hismethod, HIS_RDONLY);
+ free(path);
+
+ /* Initialize the storage manager. We only need to initialize it in
+ read/write mode if we're not going to be writing a separate file for
+ the use of fastrm. */
+ if (!ovge.delayrm) {
+ value = true;
+ if (!SMsetup(SM_RDWR, &value))
+ die("can't setup storage manager read/write");
+ }
+ value = true;
+ if (!SMsetup(SM_PREOPEN, &value))
+ die("can't setup storage manager");
+ if (!SMinit())
+ die("can't initialize storage manager: %s", SMerrorstr);
+
+ /* Initialize and configure the overview subsystem. */
+ if (!OVopen(OV_READ | OV_WRITE))
+ die("can't open overview database");
+ if (innconf->groupbaseexpiry) {
+ time(&ovge.now);
+ if (!OVctl(OVGROUPBASEDEXPIRE, &ovge))
+ die("can't configure group-based expire");
+ }
+ if (!OVctl(OVSTATALL, &always_stat))
+ die("can't configure overview stat behavior");
+
+ /* We want to be careful about being interrupted from this point on, so
+ set up our signal handlers. */
+ xsignal(SIGTERM, fatal_signal);
+ xsignal(SIGINT, fatal_signal);
+ xsignal(SIGHUP, fatal_signal);
+
+ /* Loop through each line of the input file and process each group,
+ writing data to the lowmark file if desired. */
+ line = QIOread(qp);
+ while (line != NULL && !signalled) {
+ p = strchr(line, ' ');
+ if (p != NULL)
+ *p = '\0';
+ p = strchr(line, '\t');
+ if (p != NULL)
+ *p = '\0';
+ if (!OVexpiregroup(line, &low, history))
+ warn("can't expire %s", line);
+ else if (lowmark != NULL && low != 0)
+ fprintf(lowmark, "%s %u\n", line, low);
+ line = QIOread(qp);
+ }
+ if (signalled)
+ warn("received signal, exiting");
+
+ /* If desired, purge all deleted newsgroups. */
+ if (!signalled && purge_deleted)
+ if (!OVexpiregroup(NULL, NULL, history))
+ warn("can't expire deleted newsgroups");
+
+ /* Close everything down in an orderly fashion. */
+ QIOclose(qp);
+ OVclose();
+ SMshutdown();
+ HISclose(history);
+ if (lowmark != NULL)
+ if (fclose(lowmark) == EOF)
+ syswarn("can't close %s", lowmark_path);
+
+ return 0;
+}
--- /dev/null
+#! /bin/sh
+# fixscript will replace this line with code to load innshellvars
+
+## Remove articles listed by expire -z.
+## Remove all files specified in the input file.
+
+MAIL="${MAILCMD} -s 'Problem removing expired files' ${NEWSMASTER}"
+
+#RMPROC="xargs rm"
+RMPROC="fastrm -e ${SPOOL}"
+
+if [ -z "$1" ] ; then
+ echo "Expire called with zero list of files on `hostname`" \
+ | eval ${MAIL}
+ exit 0
+fi
+if [ ! -f $1 ] ; then
+ echo "Expire called with no files to expire on `hostname`" \
+ | eval ${MAIL}
+ exit 0
+fi
+
+eval "cd ${SPOOL} \
+ && ${RMPROC} <$1 \
+ && mv $1 ${MOST_LOGS}/expire.list"
+if [ -f $1 ] ; then
+ echo "Expire had problems removing articles on `hostname`" \
+ | eval ${MAIL}
+ exit 1
+fi
--- /dev/null
+/* $Id: fastrm.c 6155 2003-01-19 19:58:25Z rra $
+**
+** Delete a list of filenames or tokens from stdin.
+**
+** Originally written by <kre@munnari.oz.au> (to only handle files)
+**
+** Files that can't be unlinked because they didn't exist are considered
+** okay. Any error condition results in exiting with non-zero exit
+** status. Input lines in the form @...@ are taken to be storage API
+** tokens. Input filenames should be fully qualified. For maximum
+** efficiency, input filenames should be sorted; fastrm will cd into each
+** directory to avoid additional directory lookups when removing a lot of
+** files in a single directory.
+*/
+
+#include "config.h"
+#include "clibrary.h"
+#include <ctype.h>
+#include <dirent.h>
+#include <errno.h>
+#include <sys/stat.h>
+#include <syslog.h>
+
+#include "inn/innconf.h"
+#include "inn/messages.h"
+#include "inn/qio.h"
+#include "libinn.h"
+#include "storage.h"
+
+/* We reject any path names longer than this. */
+#define MAX_DIR_LEN 2048
+
+/* Data structure for a list of files in a single directory. */
+typedef struct filelist {
+ int count;
+ int size;
+ char *dir;
+ char **files;
+} filelist;
+
+/* All relative paths are relative to this directory. */
+static char *base_dir = NULL;
+
+/* The absolute path of the current working directory. */
+static char current_dir[MAX_DIR_LEN];
+
+/* The prefix for the files that we're currently working with. We sometimes
+ also use this as working space for forming file names to remove, so give
+ ourselves a bit of additional leeway just in case. */
+static char prefix_dir[MAX_DIR_LEN * 2];
+static int prefix_len;
+
+/* Some threshold values that govern the optimizations that we are willing
+ to perform. chdir_threshold determines how many files to be removed we
+ want in a directory before we chdir to that directory. sort_threshold
+ determines how many files must be in a directory before we use readdir to
+ remove them in order. relative_threshold determines how many levels of
+ "../" we're willing to try to use to move to the next directory rather
+ than just calling chdir with the new absolute path. */
+static int chdir_threshold = 3;
+static int relative_threshold = 0;
+static int sort_threshold = 0;
+
+/* True if we should only print what we would do, not actually do it. */
+static bool debug_only = false;
+
+/* A string used for constructing relative paths. */
+static const char dotdots[] = "../../../../";
+
+/* The number of errors encountered, used to determine exit status. */
+static int error_count = 0;
+
+/* Whether the storage manager has been initialized. */
+static bool sm_initialized = false;
+
+/* True if unlink may be able to remove directories. */
+static bool unlink_dangerous = false;
+
+
+
+/*
+** Sorting predicate for qsort and bsearch.
+*/
+static int
+file_compare(const void *a, const void *b)
+{
+ const char *f1, *f2;
+
+ f1 = *((const char **) a);
+ f2 = *((const char **) b);
+ return strcmp(f1, f2);
+}
+
+
+/*
+** Create a new filelist.
+*/
+static filelist *
+filelist_new(char *dir)
+{
+ filelist *new;
+
+ new = xmalloc(sizeof(filelist));
+ new->count = 0;
+ new->size = 0;
+ new->dir = dir;
+ new->files = NULL;
+ return new;
+}
+
+
+/*
+** Insert a file name into a list of files (unsorted).
+*/
+static void
+filelist_insert(filelist *list, char *name)
+{
+ if (list->count == list->size) {
+ list->size = (list->size == 0) ? 16 : list->size * 2;
+ list->files = xrealloc(list->files, list->size * sizeof(char *));
+ }
+ list->files[list->count++] = xstrdup(name);
+}
+
+
+/*
+** Find a file name in a sorted list of files.
+*/
+static char *
+filelist_lookup(filelist *list, const char *name)
+{
+ char **p;
+
+ p = bsearch(&name, list->files, list->count, sizeof(char *),
+ file_compare);
+ return (p == NULL ? NULL : *p);
+}
+
+
+/*
+** Empty a list of files, freeing all of the names but keeping the
+** structure intact.
+*/
+static void
+filelist_empty(filelist *list)
+{
+ int i;
+
+ for (i = 0; i < list->count; i++)
+ free(list->files[i]);
+ list->count = 0;
+}
+
+
+/*
+** Free a list of files.
+*/
+static void
+filelist_free(filelist *list)
+{
+ filelist_empty(list);
+ if (list->files != NULL)
+ free(list->files);
+ if (list->dir != NULL)
+ free(list->dir);
+ free(list);
+}
+
+
+/*
+** Exit handler for die. Shut down the storage manager before exiting.
+*/
+static int
+sm_cleanup(void)
+{
+ SMshutdown();
+ return 1;
+}
+
+
+/*
+** Initialize the storage manager. This includes parsing inn.conf, which
+** fastrm doesn't need for any other purpose.
+*/
+static void
+sm_initialize(void)
+{
+ bool value;
+
+ if (!innconf_read(NULL))
+ exit(1);
+ value = true;
+ if (!SMsetup(SM_RDWR, &value) || !SMsetup(SM_PREOPEN, &value))
+ die("can't set up storage manager");
+ if (!SMinit())
+ die("can't initialize storage manager: %s", SMerrorstr);
+ sm_initialized = true;
+ message_fatal_cleanup = sm_cleanup;
+}
+
+
+/*
+** Get a line from a given QIO stream, returning a pointer to it. Warn
+** about and then skip lines that are too long. Returns NULL at EOF or on
+** an error.
+*/
+static char *
+get_line(QIOSTATE *qp)
+{
+ static int count;
+ char *p;
+
+ p = QIOread(qp);
+ count++;
+ while (QIOtoolong(qp) || (p != NULL && strlen(p) >= MAX_DIR_LEN)) {
+ warn("line %d too long", count);
+ error_count++;
+ while (p == NULL && QIOtoolong(qp))
+ p = QIOread(qp);
+ p = QIOread(qp);
+ }
+ if (p == NULL) {
+ if (QIOerror(qp)) {
+ syswarn("read error");
+ error_count++;
+ }
+ return NULL;
+ }
+ return p;
+}
+
+
+/*
+** Read lines from stdin (including the first that may have been there
+** from our last time in) until we reach EOF or until we get a line that
+** names a file not in the same directory as the previous lot. Remember
+** the file names in the directory we're examining and return the list.
+*/
+static filelist *
+process_line(QIOSTATE *qp, int *queued, int *deleted)
+{
+ static char *line = NULL;
+ filelist *list = NULL;
+ char *p;
+ char *dir = NULL;
+ int dlen = -1;
+
+ *queued = 0;
+ *deleted = 0;
+
+ if (line == NULL)
+ line = get_line(qp);
+
+ for (; line != NULL; line = get_line(qp)) {
+ if (IsToken(line)) {
+ (*deleted)++;
+ if (debug_only) {
+ printf("Token %s\n", line);
+ continue;
+ }
+ if (!sm_initialized)
+ sm_initialize();
+ if (!SMcancel(TextToToken(line)))
+ if (SMerrno != SMERR_NOENT && SMerrno != SMERR_UNINIT) {
+ warn("can't cancel %s", line);
+ error_count++;
+ }
+ } else {
+ if (list == NULL) {
+ p = strrchr(line, '/');
+ if (p != NULL) {
+ *p++ = '\0';
+ dlen = strlen(line);
+ dir = xstrdup(line);
+ } else {
+ p = line;
+ dlen = -1;
+ dir = NULL;
+ }
+ list = filelist_new(dir);
+ } else {
+ if ((dlen < 0 && strchr(line, '/'))
+ || (dlen >= 0 && (line[dlen] != '/'
+ || strchr(line + dlen + 1, '/')
+ || strncmp(dir, line, dlen))))
+ return list;
+ }
+ filelist_insert(list, line + dlen + 1);
+ (*queued)++;
+ }
+ }
+ return list;
+}
+
+
+/*
+** Copy n leading segments of a path.
+*/
+static void
+copy_segments(char *to, const char *from, int n)
+{
+ char c;
+
+ for (c = *from++; c != '\0'; c = *from++) {
+ if (c == '/' && --n <= 0)
+ break;
+ *to++ = c;
+ }
+ *to = '\0';
+}
+
+
+/*
+** Return the count of path segments in a file name (the number of
+** slashes).
+*/
+static int
+slashcount(char *name)
+{
+ int i;
+
+ for (i = 0; *name != '\0'; name++)
+ if (*name == '/')
+ i++;
+ return i;
+}
+
+
+/*
+** Unlink a file, reporting errors if the unlink fails for a reason other
+** than the file not existing doesn't exist. Be careful to avoid unlinking
+** a directory if unlink_dangerous is true.
+*/
+static void
+unlink_file(const char *file)
+{
+ struct stat st;
+
+ /* On some systems, unlink will remove directories if used by root. If
+ we're running as root, unlink_dangerous will be set, and we need to
+ make sure that the file isn't a directory first. */
+ if (unlink_dangerous) {
+ if (stat(file, &st) < 0) {
+ if (errno != ENOENT) {
+ if (*file == '/')
+ syswarn("can't stat %s", file);
+ else
+ syswarn("can't stat %s in %s", file, current_dir);
+ error_count++;
+ }
+ return;
+ }
+ if (S_ISDIR(st.st_mode)) {
+ if (*file == '/')
+ syswarn("%s is a directory", file);
+ else
+ syswarn("%s in %s is a directory", file, current_dir);
+ error_count++;
+ return;
+ }
+ }
+
+ if (debug_only) {
+ if (*file != '/')
+ printf("%s / ", current_dir);
+ printf("%s\n", file);
+ return;
+ }
+
+ if (unlink(file) < 0 && errno != ENOENT) {
+ if (*file == '/')
+ syswarn("can't unlink %s", file);
+ else
+ syswarn("can't unlink %s in %s", file, current_dir);
+ }
+}
+
+
+/*
+** A wrapper around chdir that dies if chdir fails for a reason other than
+** the directory not existing, returns false if the directory doesn't
+** exist (reporting an error), and otherwise returns true. It also checks
+** to make sure that filecount is larger than chdir_threshold, and if it
+** isn't it instead just sets prefix_dir and prefix_len to point to the new
+** directory without changing the working directory.
+*/
+static bool
+chdir_checked(const char *path, int filecount)
+{
+ if (filecount < chdir_threshold) {
+ strlcpy(prefix_dir, path, sizeof(prefix_dir));
+ prefix_len = strlen(path);
+ } else {
+ prefix_len = 0;
+ if (chdir(path) < 0) {
+ if (errno != ENOENT)
+ sysdie("can't chdir from %s to %s", current_dir, path);
+ else {
+ syswarn("can't chdir from %s to %s", current_dir, path);
+ return false;
+ }
+ }
+ }
+ return true;
+}
+
+
+/*
+** Set our environment (process working directory, and global vars) to
+** reflect a change of directory to dir (relative to base_dir if dir is not
+** an absolute path). We're likely to want to do different things
+** depending on the amount of work to do in dir, so we also take the number
+** of files to remove in dir as the second argument. Return false if the
+** directory doesn't exist (and therefore all files in it have already been
+** removed; otherwise, return true.
+*/
+static bool
+setup_dir(char *dir, int filecount)
+{
+ char *p, *q, *absolute;
+ char path[MAX_DIR_LEN];
+ int base_depth, depth;
+
+ /* Set absolute to the absolute path to the new directory. */
+ if (dir == NULL)
+ absolute = base_dir;
+ else if (*dir == '/')
+ absolute = dir;
+ else if (*dir == '\0') {
+ strlcpy(path, "/", sizeof(path));
+ absolute = path;
+ } else {
+ /* Strip off leading "./". */
+ while (dir[0] == '.' && dir[1] == '/')
+ for (dir += 2; *dir == '/'; dir++)
+ ;
+
+ /* Handle any leading "../", but only up to the number of segments
+ in base_dir. */
+ base_depth = slashcount(base_dir);
+ while (base_depth > 0 && strncmp(dir, "../", 3) == 0)
+ for (base_depth--, dir += 3; *dir == '/'; dir++)
+ ;
+ if (base_depth <= 0)
+ die("too many ../'s in path %s", dir);
+ copy_segments(path, base_dir, base_depth + 1);
+ if (strlen(path) + strlen(dir) + 2 > MAX_DIR_LEN)
+ die("path %s too long", dir);
+ strlcat(path, "/", sizeof(path));
+ strlcat(path, dir, sizeof(path));
+ absolute = path;
+ }
+
+ /* Find the first point of difference between absolute and current_dir.
+ If there is no difference, we're done; we're changing to the same
+ directory we were in (this is probably some sort of error, but can
+ happen with odd relative paths). */
+ for (p = absolute, q = current_dir; *p == *q; p++, q++)
+ if (*p == '\0')
+ return true;
+
+ /* If we reached the end of current_dir and there's more left of
+ absolute, we're changing to a subdirectory of where we were. */
+ if (*q == '\0' && *p == '/') {
+ p++;
+ if (!chdir_checked(p, filecount))
+ return false;
+ if (prefix_len == 0)
+ strlcpy(current_dir, absolute, sizeof(current_dir));
+ return true;
+ }
+
+ /* Otherwise, if we were promised that we have a pure tree (in other
+ words, no symbolic links to directories), see if it's worth going up
+ the tree with ".." and then down again rather than chdir to the
+ absolute path. relative_threshold determines how many levels of ".."
+ we're willing to use; the default of 1 seems fractionally faster than
+ 2 and 0 indicates to always use absolute paths. Values larger than 3
+ would require extending the dotdots string, but are unlikely to be
+ worth it.
+
+ FIXME: It's too hard to figure out what this code does. It needs to be
+ rewritten. */
+ if (p != '\0' && relative_threshold > 0) {
+ depth = slashcount(q);
+ if (depth <= relative_threshold) {
+ while (p > absolute && *--p != '/')
+ ;
+ p++;
+ strlcpy(prefix_dir, dotdots + 9 - depth * 3, sizeof(prefix_dir));
+ strlcat(prefix_dir, p, sizeof(prefix_dir));
+ if (!chdir_checked(prefix_dir, filecount))
+ return false;
+
+ /* Now patch up current_dir to reflect where we are. */
+ if (prefix_len == 0) {
+ while (q > current_dir && *--q != '/')
+ ;
+ q[1] = '\0';
+ strlcat(current_dir, p, sizeof(current_dir));
+ }
+ return true;
+ }
+ }
+
+ /* All else has failed; just use the absolute path. This includes the
+ case where current_dir is a subdirectory of absolute, in which case
+ it may be somewhat faster to use chdir("../..") or the like rather
+ than the absolute path, but this case rarely happens when the user
+ cares about speed (it usually doesn't happen with sorted input). So
+ we don't bother. */
+ if (!chdir_checked(absolute, filecount))
+ return false;
+ if (prefix_len == 0)
+ strlcpy(current_dir, absolute, sizeof(current_dir));
+ return true;
+}
+
+
+/*
+** Process a filelist of files to be deleted, all in the same directory.
+*/
+static void
+unlink_filelist(filelist *list, int filecount)
+{
+ bool sorted;
+ DIR *dir;
+ struct dirent *entry;
+ char *file;
+ int i;
+
+ /* If setup_dir returns false, the directory doesn't exist and we're
+ already all done. */
+ if (!setup_dir(list->dir, filecount)) {
+ filelist_free(list);
+ return;
+ }
+
+ /* We'll use prefix_dir as a buffer to write each file name into as we
+ go, so get it set up. */
+ if (prefix_len == 0)
+ file = prefix_dir;
+ else {
+ prefix_dir[prefix_len++] = '/';
+ file = prefix_dir + prefix_len;
+ *file = '\0';
+ }
+
+ /* If we're not sorting directories or if the number of files is under
+ the threshold, just remove the files. */
+ if (sort_threshold == 0 || filecount < sort_threshold) {
+ for (i = 0; i < list->count; i++) {
+ strlcpy(file, list->files[i], sizeof(prefix_dir) - prefix_len);
+ unlink_file(prefix_dir);
+ }
+ filelist_free(list);
+ return;
+ }
+
+ /* We have enough files to remove in this directory that it's worth
+ optimizing. First, make sure the list of files is sorted. It's not
+ uncommon for the files to already be sorted, so check first. */
+ for (sorted = true, i = 1; sorted && i < list->count; i++)
+ sorted = (strcmp(list->files[i - 1], list->files[i]) <= 0);
+ if (!sorted)
+ qsort(list->files, list->count, sizeof(char *), file_compare);
+
+ /* Now, begin doing our optimized unlinks. The technique we use is to
+ open the directory containing the files and read through it, checking
+ each file in the directory to see if it's one of the files we should
+ be removing. The theory is that we want to minimize the amount of
+ time the operating system spends doing string compares trying to find
+ the file to be removed in the directory. This is often an O(n)
+ operation. Note that this optimization may slightly slow more
+ effecient operating systems. */
+ dir = opendir(prefix_len == 0 ? "." : prefix_dir);
+ if (dir == NULL) {
+ if (prefix_len > 0 && prefix_dir[0] == '/')
+ warn("can't open directory %s", prefix_dir);
+ else
+ warn("can't open directory %s in %s",
+ (prefix_len == 0) ? "." : prefix_dir, current_dir);
+ error_count++;
+ filelist_free(list);
+ return;
+ }
+ for (i = 0, entry = readdir(dir); entry != NULL; entry = readdir(dir))
+ if (filelist_lookup(list, entry->d_name) != NULL) {
+ i++;
+ strlcpy(file, entry->d_name, sizeof(prefix_dir) - prefix_len);
+ unlink_file(prefix_dir);
+ if (i == list->count)
+ break;
+ }
+ closedir(dir);
+ filelist_free(list);
+}
+
+
+/*
+** Check a path to see if it's okay (not likely to confuse us). This
+** ensures that it doesn't contain elements like "./" or "../" and doesn't
+** contain doubled slashes.
+*/
+static bool
+bad_path(const char *p)
+{
+ if (strlen(p) >= MAX_DIR_LEN)
+ return true;
+ while (*p) {
+ if (p[0] == '.' && (p[1] == '/' || (p[1] == '.' && p[2] == '/')))
+ return true;
+ while (*p && *p != '/')
+ p++;
+ if (p[0] == '/' && p[1] == '/')
+ return true;
+ if (*p == '/')
+ p++;
+ }
+ return false;
+}
+
+
+/*
+** Main routine. Parse options, initialize the storage manager, and
+** initalize various global variables, and then go into a loop calling
+** process_line and unlink_filelist as needed.
+*/
+int
+main(int argc, char *argv[])
+{
+ const char *name;
+ char *p, **arg;
+ QIOSTATE *qp;
+ filelist *list;
+ int filecount, deleted;
+ bool empty_error = false;
+
+ /* Establish our identity. Since we use the storage manager, we need to
+ set up syslog as well, although we won't use it ourselves. */
+ name = argv[0];
+ if (*name == '\0')
+ name = "fastrm";
+ else {
+ p = strrchr(name, '/');
+ if (p != NULL)
+ name = p + 1;
+ }
+ message_program_name = name;
+ openlog(name, LOG_CONS | LOG_PID, LOG_INN_PROG);
+
+ /* If we're running as root, unlink may remove directories. */
+ unlink_dangerous = (geteuid() == 0);
+
+ /* Unfortunately, we can't use getopt, because several of our options
+ take optional arguments. Bleh. */
+ arg = argv + 1;
+ while (argc >= 2 && **arg == '-') {
+ p = *arg;
+ while (*++p) {
+ switch (*p) {
+ default:
+ die("invalid option -- %c", *p);
+ case 'a':
+ case 'r':
+ continue;
+ case 'c':
+ chdir_threshold = 1;
+ if (!CTYPE(isdigit, p[1]))
+ continue;
+ chdir_threshold = atoi(p + 1);
+ break;
+ case 'd':
+ debug_only = true;
+ continue;
+ case 'e':
+ empty_error = true;
+ continue;
+ case 's':
+ sort_threshold = 5;
+ if (!CTYPE(isdigit, p[1]))
+ continue;
+ sort_threshold = atoi(p + 1);
+ break;
+ case 'u':
+ relative_threshold = 1;
+ if (!CTYPE(isdigit, p[1]))
+ continue;
+ relative_threshold = atoi(p + 1);
+ if (relative_threshold >= (int) strlen(dotdots) / 3)
+ relative_threshold = strlen(dotdots) / 3 - 1;
+ break;
+ }
+ break;
+ }
+ argc--;
+ arg++;
+ }
+ if (argc != 2)
+ die("usage error, wrong number of arguments");
+
+ /* The remaining argument is the base path. Make sure it's valid and
+ not excessively large and then change to it. */
+ base_dir = *arg;
+ if (*base_dir != '/' || bad_path(base_dir))
+ die("bad base path %s", base_dir);
+ strlcpy(current_dir, base_dir, sizeof(current_dir));
+ if (chdir(current_dir) < 0)
+ sysdie("can't chdir to base path %s", current_dir);
+
+ /* Open our input stream and then loop through it, building filelists
+ and processing them until done. */
+ qp = QIOfdopen(fileno(stdin));
+ if (qp == NULL)
+ sysdie("can't reopen stdin");
+ while ((list = process_line(qp, &filecount, &deleted)) != NULL) {
+ empty_error = false;
+ unlink_filelist(list, filecount);
+ }
+ if (deleted > 0)
+ empty_error = false;
+
+ /* All done. */
+ SMshutdown();
+ if (empty_error)
+ die("no files to remove");
+ exit(error_count > 0 ? 1 : 0);
+}
--- /dev/null
+/* $Id: grephistory.c 7041 2004-12-19 08:33:49Z rra $
+**
+** Get data from history database.
+*/
+
+#include "clibrary.h"
+#include <syslog.h>
+#include <sys/stat.h>
+
+#include "inn/history.h"
+#include "inn/innconf.h"
+#include "inn/messages.h"
+#include "libinn.h"
+#include "paths.h"
+#include "storage.h"
+
+/*
+** Read stdin for list of Message-ID's, output list of ones we
+** don't have. Or, output list of files for ones we DO have.
+*/
+static void
+IhaveSendme(struct history *h, char What)
+{
+ char *p;
+ char *q;
+ char buff[BUFSIZ];
+
+ while (fgets(buff, sizeof buff, stdin) != NULL) {
+ time_t arrived, posted, expires;
+ TOKEN token;
+
+ for (p = buff; ISWHITE(*p); p++)
+ ;
+ if (*p != '<')
+ continue;
+ for (q = p; *q && *q != '>' && !ISWHITE(*q); q++)
+ ;
+ if (*q != '>')
+ continue;
+ *++q = '\0';
+
+ if (!HIScheck(h, p)) {
+ if (What == 'i')
+ printf("%s\n", p);
+ continue;
+ }
+
+ /* Ihave -- say if we want it, and continue. */
+ if (What == 'i') {
+ continue;
+ }
+
+ if (HISlookup(h, p, &arrived, &posted, &expires, &token))
+ printf("%s\n", TokenToText(token));
+ }
+}
+
+
+/*
+** Print a usage message and exit.
+*/
+static void
+Usage(void)
+{
+ fprintf(stderr, "Usage: grephistory [flags] MessageID\n");
+ exit(1);
+}
+
+
+int
+main(int ac, char *av[])
+{
+ int i;
+ const char *History;
+ char *key;
+ char What;
+ struct history *history;
+ time_t arrived, posted, expires;
+ TOKEN token;
+ unsigned int Verbosity = 0;
+
+ /* First thing, set up logging and our identity. */
+ openlog("grephistory", L_OPENLOG_FLAGS | LOG_PID, LOG_INN_PROG);
+ message_program_name = "grephistory";
+
+ /* Set defaults. */
+ if (!innconf_read(NULL))
+ exit(1);
+
+ History = concatpath(innconf->pathdb, _PATH_HISTORY);
+
+ What = '?';
+
+ /* Parse JCL. */
+ while ((i = getopt(ac, av, "vf:eilnqs")) != EOF)
+ switch (i) {
+ default:
+ Usage();
+ /* NOTREACHED */
+ case 'v':
+ Verbosity++;
+ break;
+ case 'f':
+ History = optarg;
+ break;
+ case 'e':
+ case 'i':
+ case 'l':
+ case 'n':
+ case 'q':
+ case 's':
+ if (What != '?') {
+ die("only one [eilnqs] flag allowed");
+ }
+ What = (char)i;
+ break;
+ }
+ ac -= optind;
+ av += optind;
+
+ history = HISopen(History, innconf->hismethod, HIS_RDONLY);
+ if (history == NULL)
+ die("cannot open history");
+
+ /* Set operating mode. */
+ switch (What) {
+ case '?':
+ What = 'n';
+ break;
+ case 'i':
+ case 's':
+ IhaveSendme(history, What);
+ HISclose(history);
+ exit(0);
+ /* NOTREACHED */
+ }
+
+ /* All modes other than -i -l want a Message-ID. */
+ if (ac != 1)
+ Usage();
+
+ key = av[0];
+ if (*key == '[') {
+ warn("accessing history by hash isn't supported");
+ HISclose(history);
+ exit(1);
+ } else {
+ /* Add optional braces if not already present. */
+ if (*key != '<')
+ key = concat("<", key, ">", (char *) 0);
+ }
+
+ if (!HIScheck(history, key)) {
+ if (What == 'n') {
+ if (Verbosity > 0)
+ die("not found (hash is %s)", HashToText(HashMessageID(key)));
+ else
+ die("not found");
+ }
+ }
+ else if (What != 'q') {
+ if (HISlookup(history, key, &arrived, &posted, &expires, &token)) {
+ if (What == 'l') {
+ printf("[]\t%ld~-~%ld\t%s\n", (long)arrived, (long)posted,
+ TokenToText(token));
+ }
+ else {
+ if (Verbosity > 0)
+ printf("%s (hash is %s)\n", TokenToText(token),
+ HashToText(HashMessageID(key)));
+ else
+ printf("%s\n", TokenToText(token));
+ }
+ }
+ else if (What == 'n')
+ printf("/dev/null\n");
+ }
+ HISclose(history);
+ return 0;
+}
--- /dev/null
+/* $Id: makedbz.c 6135 2003-01-19 01:15:40Z rra $
+**
+** Rebuild dbz file for history db.
+*/
+
+#include "config.h"
+#include "clibrary.h"
+#include <errno.h>
+#include <pwd.h>
+#include <syslog.h>
+
+#include "dbz.h"
+#include "inn/innconf.h"
+#include "inn/messages.h"
+#include "inn/qio.h"
+#include "libinn.h"
+#include "paths.h"
+#include "storage.h"
+
+/* FIXME: once we figure out how to integrate this stuff with the
+ * history API this external visibility of internal voodoo should
+ * go */
+#define HIS_FIELDSEP '\t'
+
+char *TextFile = NULL;
+char *HistoryDir = NULL;
+char *HISTORY = NULL;
+
+/*
+** Remove the DBZ files for the specified base text file.
+*/
+static void
+RemoveDBZFiles(char *p)
+{
+ char buff[SMBUF];
+
+ snprintf(buff, sizeof(buff), "%s.dir", p);
+ if (unlink(buff) && errno != ENOENT)
+ syswarn("cannot unlink %s", buff);
+#ifdef DO_TAGGED_HASH
+ snprintf(buff, sizeof(buff), "%s.pag", p);
+ if (unlink(buff) && errno != ENOENT)
+ syswarn("cannot unlink %s", buff);
+#else
+ snprintf(buff, sizeof(buff), "%s.index", p);
+ if (unlink(buff) && errno != ENOENT)
+ syswarn("cannot unlink %s", buff);
+ snprintf(buff, sizeof(buff), "%s.hash", p);
+ if (unlink(buff) && errno != ENOENT)
+ syswarn("cannot unlink %s", buff);
+#endif
+}
+
+
+/*
+** Count lines in the history text. A long-winded way of saying "wc -l"
+*/
+static off_t
+Countlines(void)
+{
+ QIOSTATE *qp;
+ off_t count;
+
+ /* Open the text file. */
+ qp = QIOopen(TextFile);
+ if (qp == NULL)
+ sysdie("cannot open %s", TextFile);
+
+ /* Loop through all lines in the text file. */
+ count = 0;
+ for (; QIOread(qp) != NULL;)
+ count++;
+ if (QIOerror(qp))
+ sysdie("cannot read %s near line %lu", TextFile,
+ (unsigned long) count);
+ if (QIOtoolong(qp))
+ sysdie("line %lu of %s is too long", (unsigned long) count,
+ TextFile);
+
+ QIOclose(qp);
+ return count;
+}
+
+
+/*
+** Rebuild the DBZ file from the text file.
+*/
+static void
+Rebuild(off_t size, bool IgnoreOld, bool Overwrite)
+{
+ QIOSTATE *qp;
+ char *p;
+ char *save;
+ off_t count;
+ off_t where;
+ HASH key;
+ char temp[SMBUF];
+ dbzoptions opt;
+
+ if (chdir(HistoryDir) < 0)
+ sysdie("cannot chdir to %s", HistoryDir);
+
+ /* If we are ignoring the old database and the user didn't specify a table
+ size, determine one ourselves from the size of the text history file.
+ Note that this will still use the defaults in dbz if the text file is
+ empty, since size will still be left set to 0. */
+ if (IgnoreOld == true && size == 0) {
+ size = Countlines();
+ size += (size / 10);
+ if (size > 0)
+ warn("no size specified, using %ld", (unsigned long) size);
+ }
+
+ /* Open the text file. */
+ qp = QIOopen(TextFile);
+ if (qp == NULL)
+ sysdie("cannot open %s", TextFile);
+
+ /* If using the standard history file, force DBZ to use history.n. */
+ if (strcmp(TextFile, HISTORY) == 0 && !Overwrite) {
+ snprintf(temp, sizeof(temp), "%s.n", HISTORY);
+ if (link(HISTORY, temp) < 0)
+ sysdie("cannot create temporary link to %s", temp);
+ RemoveDBZFiles(temp);
+ p = temp;
+ }
+ else {
+ temp[0] = '\0';
+ /*
+ ** only do removedbz files if a. we're using something besides
+ ** $pathdb/history, or b. we're ignoring the old db.
+ */
+ if (strcmp(TextFile, HISTORY) != 0 || IgnoreOld)
+ RemoveDBZFiles(TextFile);
+ p = TextFile;
+ }
+
+ /* Open the new database, using the old file if desired and possible. */
+ dbzgetoptions(&opt);
+ opt.pag_incore = INCORE_MEM;
+#ifndef DO_TAGGED_HASH
+ opt.exists_incore = INCORE_MEM;
+#endif
+ dbzsetoptions(opt);
+ if (IgnoreOld) {
+ if (!dbzfresh(p, dbzsize(size))) {
+ syswarn("cannot do dbzfresh");
+ if (temp[0])
+ unlink(temp);
+ exit(1);
+ }
+ }
+ else {
+ if (!dbzagain(p, HISTORY)) {
+ syswarn("cannot do dbzagain");
+ if (temp[0])
+ unlink(temp);
+ exit(1);
+ }
+ }
+
+ /* Loop through all lines in the text file. */
+ count = 0;
+ for (where = QIOtell(qp); (p = QIOread(qp)) != NULL; where = QIOtell(qp)) {
+ count++;
+ if ((save = strchr(p, HIS_FIELDSEP)) == NULL) {
+ warn("bad line #%lu: %.40s", (unsigned long) count, p);
+ if (temp[0])
+ unlink(temp);
+ exit(1);
+ }
+ *save = '\0';
+ switch (*p) {
+ case '[':
+ if (strlen(p) != ((sizeof(HASH) * 2) + 2)) {
+ warn("invalid length for hash %s, skipping", p);
+ continue;
+ }
+ key = TextToHash(p+1);
+ break;
+ default:
+ warn("invalid message ID %s in history text", p);
+ continue;
+ }
+ switch (dbzstore(key, where)) {
+ case DBZSTORE_EXISTS:
+ warn("duplicate message ID %s in history text", p);
+ break;
+ case DBZSTORE_ERROR:
+ syswarn("cannot store %s", p);
+ if (temp[0])
+ unlink(temp);
+ exit(1);
+ default:
+ break;
+ }
+ }
+ if (QIOerror(qp)) {
+ syswarn("cannot read %s near line %lu", TextFile,
+ (unsigned long) count);
+ if (temp[0])
+ unlink(temp);
+ exit(1);
+ }
+ if (QIOtoolong(qp)) {
+ warn("line %lu is too long", (unsigned long) count);
+ if (temp[0])
+ unlink(temp);
+ exit(1);
+ }
+
+ /* Close files. */
+ QIOclose(qp);
+ if (!dbzclose()) {
+ syswarn("cannot close history");
+ if (temp[0])
+ unlink(temp);
+ exit(1);
+ }
+
+ if (temp[0])
+ unlink(temp);
+}
+
+static void
+Usage(void)
+{
+ fprintf(stderr, "Usage: makedbz [-f histfile] [-s numlines] [-i] [-o]\n");
+ exit(1);
+}
+
+
+/*
+** Change to the news user if possible, and if not, die. Used for operations
+** that may create new database files, so as not to mess up the ownership.
+*/
+static void
+setuid_news(void)
+{
+ struct passwd *pwd;
+
+ pwd = getpwnam(NEWSUSER);
+ if (pwd == NULL)
+ die("can't resolve %s to a UID (account doesn't exist?)", NEWSUSER);
+ if (getuid() == 0)
+ setuid(pwd->pw_uid);
+ if (getuid() != pwd->pw_uid)
+ die("must be run as %s", NEWSUSER);
+}
+
+
+int
+main(int argc, char **argv)
+{
+ bool Overwrite;
+ bool IgnoreOld;
+ off_t size = 0;
+ int i;
+ char *p;
+
+ /* First thing, set up logging and our identity. */
+ openlog("makedbz", L_OPENLOG_FLAGS | LOG_PID, LOG_INN_PROG);
+ message_program_name = "makedbz";
+
+ /* Set defaults. */
+ if (!innconf_read(NULL))
+ exit(1);
+ TextFile = concatpath(innconf->pathdb, _PATH_HISTORY);
+ HISTORY = concatpath(innconf->pathdb, _PATH_HISTORY);
+ HistoryDir = innconf->pathdb;
+ IgnoreOld = false;
+ Overwrite = false;
+
+ while ((i = getopt(argc, argv, "s:iof:")) != EOF) {
+ switch (i) {
+ default:
+ Usage();
+ case 'f':
+ TextFile = optarg;
+ break;
+ case 's':
+ size = atol(optarg);
+ IgnoreOld = true;
+ break;
+ case 'o':
+ Overwrite = true;
+ break;
+ case 'i':
+ IgnoreOld = true;
+ break;
+ }
+ }
+
+ argc -= optind;
+ argv += optind;
+ if (argc) {
+ Usage();
+ }
+
+ if ((p = strrchr(TextFile, '/')) == NULL) {
+ /* find the default history file directory */
+ HistoryDir = innconf->pathdb;
+ } else {
+ *p = '\0';
+ HistoryDir = xstrdup(TextFile);
+ *p = '/';
+ }
+
+ if (chdir(HistoryDir) < 0)
+ sysdie("cannot chdir to %s", HistoryDir);
+
+ /* Change users if necessary. */
+ setuid_news();
+
+ Rebuild(size, IgnoreOld, Overwrite);
+ closelog();
+ exit(0);
+}
--- /dev/null
+/* $Id: makehistory.c 7468 2005-12-12 03:23:21Z eagle $
+**
+** Rebuild history/overview databases.
+*/
+
+#include "config.h"
+#include "clibrary.h"
+#include "portable/wait.h"
+#include <assert.h>
+#include <errno.h>
+#include <pwd.h>
+#include <syslog.h>
+
+#include "inn/buffer.h"
+#include "inn/history.h"
+#include "inn/innconf.h"
+#include "inn/messages.h"
+#include "inn/qio.h"
+#include "inn/wire.h"
+#include "libinn.h"
+#include "ov.h"
+#include "paths.h"
+#include "storage.h"
+
+static const char usage[] = "\
+Usage: makehistory [-bOIax] [-f file] [-l count] [-s size] [-T tmpdir]\n\
+\n\
+ -b delete bad articles from spool\n\
+ -e read entire articles to compute proper byte count\n\
+ -f write history entries to file (default $pathdb/history)\n\
+ -s size size new history database for approximately size entries\n\
+ -a open output history file in append mode\n\
+ -O create overview entries for articles\n\
+ -I do not create overview for articles numbered below lowmark\n\
+ -l count size of overview updates (default 10000)\n\
+ -x don't write history entries\n\
+ -T tmpdir use directory tmpdir for temporary files\n\
+ -F fork when writing overview\n";
+
+
+/*
+** Information about the schema of the news overview files.
+*/
+typedef struct _ARTOVERFIELD {
+ char *Headername;
+ int HeadernameLength;
+ bool NeedHeadername;
+ const char *Header;
+ int HeaderLength;
+ bool HasHeader;
+} ARTOVERFIELD;
+
+#define DEFAULT_SEGSIZE 10000;
+
+bool NukeBadArts;
+char *SchemaPath = NULL;
+char *ActivePath = NULL;
+char *HistoryPath = NULL;
+struct history *History;
+FILE *Overchan;
+bool DoOverview;
+bool Fork;
+bool Cutofflow = false;
+char *TmpDir;
+int OverTmpSegSize, OverTmpSegCount;
+FILE *OverTmpFile;
+char *OverTmpPath = NULL;
+bool NoHistory;
+OVSORTTYPE sorttype;
+int RetrMode;
+
+TIMEINFO Now;
+
+/* Misc variables needed for the overview creation code. */
+static char MESSAGEID[] = "Message-ID";
+static char EXPIRES[] = "Expires";
+static char DATE[] = "Date";
+static char XREF[] = "Xref";
+static ARTOVERFIELD *ARTfields; /* overview fields listed in overview.fmt */
+static size_t ARTfieldsize;
+static ARTOVERFIELD *Datep = (ARTOVERFIELD *)NULL;
+static ARTOVERFIELD *Msgidp = (ARTOVERFIELD *)NULL;
+static ARTOVERFIELD *Expp = (ARTOVERFIELD *)NULL;
+static ARTOVERFIELD *Xrefp = (ARTOVERFIELD *)NULL;
+static ARTOVERFIELD *Missfields; /* header fields not listed in
+ overview.fmt, but ones that we need
+ (e.g. message-id */
+static size_t Missfieldsize = 0;
+
+static void OverAddAllNewsgroups(void);
+
+/*
+** Check and parse an date header line. Return the new value or
+** zero on error.
+*/
+static long
+GetaDate(char *p)
+{
+ time_t t;
+
+ while (ISWHITE(*p))
+ p++;
+ if ((t = parsedate(p, &Now)) == -1)
+ return 0L;
+ return (long)t;
+}
+
+/*
+** Check and parse a Message-ID header line. Return private space.
+*/
+static const char *
+GetMessageID(char *p)
+{
+ static struct buffer buffer = { 0, 0, 0, NULL };
+
+ while (ISWHITE(*p))
+ p++;
+ if (p[0] != '<' || p[strlen(p) - 1] != '>')
+ return "";
+
+ /* Copy into re-used memory space, including NUL. */
+ buffer_set(&buffer, p, strlen(p)+1);
+ return buffer.data;
+}
+
+/*
+ * The overview temp file is used to accumulate overview lines as articles are
+ * scanned. The format is
+ * (1st) newsgroup name\tToken\toverview data.
+ * When about 10000 lines of this overview data are accumulated, the data
+ * file is sorted and then read back in and the data added to overview.
+ * The sorting/batching helps improve efficiency.
+ */
+
+/*
+ * Flush the unwritten OverTempFile data to disk, sort the file, read it
+ * back in, and add it to overview.
+ */
+
+static void
+FlushOverTmpFile(void)
+{
+ char temp[SMBUF];
+ char *SortedTmpPath;
+ int i, pid, fd;
+ TOKEN token;
+ QIOSTATE *qp;
+ int count;
+ char *line, *p;
+ char *q = NULL;
+ char *r = NULL;
+ time_t arrived, expires;
+ static int first = 1;
+
+ if (OverTmpFile == NULL)
+ return;
+ if (fflush(OverTmpFile) == EOF || ferror(OverTmpFile) || fclose(OverTmpFile) == EOF)
+ sysdie("cannot close temporary overview file");
+ if(Fork) {
+ if(!first) { /* if previous one is running, wait for it */
+ int status;
+ wait(&status);
+ if((WIFEXITED(status) && WEXITSTATUS(status) != 0)
+ || WIFSIGNALED(status))
+ exit(1);
+ }
+
+ pid = fork();
+ if(pid == -1)
+ sysdie("cannot fork");
+ if(pid > 0) {
+ /* parent */
+ first = 0;
+ free(OverTmpPath);
+ OverTmpPath = NULL;
+ return;
+ }
+
+ /* child */
+ /* init the overview setup. */
+ if (!OVopen(OV_WRITE)) {
+ warn("cannot open overview");
+ _exit(1);
+ }
+ if (!OVctl(OVSORT, (void *)&sorttype)) {
+ warn("cannot obtain overview sorting information");
+ OVclose();
+ _exit(1);
+ }
+ if (!OVctl(OVCUTOFFLOW, (void *)&Cutofflow)) {
+ warn("cannot obtain overview cutoff information");
+ OVclose();
+ _exit(1);
+ }
+ }
+
+ /* This is a bit odd, but as long as other user's files can't be deleted
+ out of the temporary directory, it should work. We're using mkstemp to
+ create a file and then passing its name to sort, which will then open
+ it again and overwrite it. */
+ SortedTmpPath = concatpath(TmpDir, "hisTXXXXXX");
+ fd = mkstemp(SortedTmpPath);
+ if (fd < 0) {
+ syswarn("cannot create temporary file");
+ OVclose();
+ Fork ? _exit(1) : exit(1);
+ }
+ close(fd);
+ snprintf(temp, sizeof(temp), "exec %s -T %s -t'%c' -o %s %s", _PATH_SORT,
+ TmpDir, '\t', SortedTmpPath, OverTmpPath);
+
+ i = system(temp) >> 8;
+ if (i != 0) {
+ syswarn("cannot sort temporary overview file (%s exited %d)",
+ _PATH_SORT, i);
+ OVclose();
+ Fork ? _exit(1) : exit(1);
+ }
+
+ /* don't need old path anymore. */
+ unlink(OverTmpPath);
+ free(OverTmpPath);
+ OverTmpPath = NULL;
+
+ /* read sorted lines. */
+ if ((qp = QIOopen(SortedTmpPath)) == NULL) {
+ syswarn("cannot open sorted overview file %s", SortedTmpPath);
+ OVclose();
+ Fork ? _exit(1) : exit(1);
+ }
+
+ for (count = 1; ; ++count) {
+ line = QIOread(qp);
+ if (line == NULL) {
+ if (QIOtoolong(qp)) {
+ warn("overview line %d is too long", count);
+ continue;
+ } else
+ break;
+ }
+ if ((p = strchr(line, '\t')) == NULL
+ || (q = strchr(p+1, '\t')) == NULL
+ || (r = strchr(q+1, '\t')) == NULL) {
+ warn("sorted overview file %s has a bad line at %d",
+ SortedTmpPath, count);
+ continue;
+ }
+ /* p+1 now points to start of token, q+1 points to start of overline. */
+ if (sorttype == OVNEWSGROUP) {
+ *p++ = '\0';
+ *q++ = '\0';
+ *r++ = '\0';
+ arrived = (time_t)atol(p);
+ expires = (time_t)atol(q);
+ q = r;
+ if ((r = strchr(r, '\t')) == NULL) {
+ warn("sorted overview file %s has a bad line at %d",
+ SortedTmpPath, count);
+ continue;
+ }
+ *r++ = '\0';
+ } else {
+ *p++ = '\0';
+ *q++ = '\0';
+ *r++ = '\0';
+ arrived = (time_t)atol(line);
+ expires = (time_t)atol(p);
+ }
+ token = TextToToken(q);
+ if (OVadd(token, r, strlen(r), arrived, expires) == OVADDFAILED) {
+ if (OVctl(OVSPACE, (void *)&i) && i == OV_NOSPACE) {
+ warn("no space left for overview");
+ OVclose();
+ Fork ? _exit(1) : exit(1);
+ }
+ warn("cannot write overview data \"%.40s\"", q);
+ }
+ }
+ /* Check for errors and close. */
+ if (QIOerror(qp)) {
+ syswarn("cannot read sorted overview file %s", SortedTmpPath);
+ OVclose();
+ Fork ? _exit(1) : exit(1);
+ }
+ QIOclose(qp);
+ /* unlink sorted tmp file */
+ unlink(SortedTmpPath);
+ free(SortedTmpPath);
+ if(Fork) {
+ OVclose();
+ _exit(0);
+ }
+}
+
+
+/*
+ * Write a line to the overview temp file.
+ */
+static void
+WriteOverLine(TOKEN *token, const char *xrefs, int xrefslen,
+ char *overdata, int overlen, time_t arrived, time_t expires)
+{
+ char temp[SMBUF];
+ const char *p, *q, *r;
+ int i, fd;
+
+ if (sorttype == OVNOSORT) {
+ if (Fork) {
+ fprintf(Overchan, "%s %ld %ld ", TokenToText(*token), (long)arrived, (long)expires);
+ if (fwrite(overdata, 1, overlen, Overchan) != (size_t) overlen)
+ sysdie("writing overview failed");
+ fputc('\n', Overchan);
+ } else if (OVadd(*token, overdata, overlen, arrived, expires) == OVADDFAILED) {
+ if (OVctl(OVSPACE, (void *)&i) && i == OV_NOSPACE) {
+ warn("no space left for overview");
+ OVclose();
+ exit(1);
+ }
+ warn("cannot write overview data for article %s",
+ TokenToText(*token));
+ }
+ return;
+ }
+ if (OverTmpPath == NULL) {
+ /* need new temp file, so create it. */
+ OverTmpPath = concatpath(TmpDir, "histXXXXXX");
+ fd = mkstemp(OverTmpPath);
+ if (fd < 0)
+ sysdie("cannot create temporary file");
+ OverTmpFile = fdopen(fd, "w");
+ if (OverTmpFile == NULL)
+ sysdie("cannot open %s", OverTmpPath);
+ OverTmpSegCount = 0;
+ }
+ if (sorttype == OVNEWSGROUP) {
+ /* find first ng name in xref. */
+ for (p = xrefs, q=NULL ; p < xrefs+xrefslen ; ++p) {
+ if (*p == ' ') {
+ q = p+1; /* found space */
+ break;
+ }
+ }
+ if (!q) {
+ warn("bogus Xref data for %s", TokenToText(*token));
+ /* XXX do nuke here? */
+ return;
+ }
+
+ for (p = q, r=NULL ; p < xrefs+xrefslen ; ++p) {
+ if (*p == ':') {
+ r=p;
+ break;
+ }
+ }
+ if (!r) {
+ warn("bogus Xref data for %s", TokenToText(*token));
+ /* XXX do nuke here? */
+ return;
+ }
+ /* q points to start of ng name, r points to its end. */
+ assert(sizeof(temp) > r - q + 1);
+ memcpy(temp, q, r - q + 1);
+ temp[r - q + 1] = '\0';
+ fprintf(OverTmpFile, "%s\t%10lu\t%lu\t%s\t", temp,
+ (unsigned long) arrived, (unsigned long) expires,
+ TokenToText(*token));
+ } else
+ fprintf(OverTmpFile, "%10lu\t%lu\t%s\t", (unsigned long) arrived,
+ (unsigned long) expires,
+ TokenToText(*token));
+
+ fwrite(overdata, overlen, 1, OverTmpFile);
+ fprintf(OverTmpFile, "\n");
+ OverTmpSegCount++;
+
+ if (OverTmpSegSize != 0 && OverTmpSegCount >= OverTmpSegSize) {
+ FlushOverTmpFile();
+ }
+}
+
+
+/*
+** Read the overview schema.
+*/
+static void
+ARTreadschema(bool Overview)
+{
+ FILE *F;
+ char *p;
+ ARTOVERFIELD *fp;
+ int i;
+ char buff[SMBUF];
+ bool foundxreffull = false;
+
+ if (Overview) {
+ /* Open file, count lines. */
+ if ((F = fopen(SchemaPath, "r")) == NULL)
+ sysdie("cannot open %s", SchemaPath);
+ for (i = 0; fgets(buff, sizeof buff, F) != NULL; i++)
+ continue;
+ fseeko(F, 0, SEEK_SET);
+ ARTfields = xmalloc((i + 1) * sizeof(ARTOVERFIELD));
+
+ /* Parse each field. */
+ for (fp = ARTfields; fgets(buff, sizeof buff, F) != NULL; ) {
+ /* Ignore blank and comment lines. */
+ if ((p = strchr(buff, '\n')) != NULL)
+ *p = '\0';
+ if ((p = strchr(buff, '#')) != NULL)
+ *p = '\0';
+ if (buff[0] == '\0')
+ continue;
+ if ((p = strchr(buff, ':')) != NULL) {
+ *p++ = '\0';
+ fp->NeedHeadername = (strcmp(p, "full") == 0);
+ }
+ else
+ fp->NeedHeadername = false;
+ fp->Headername = xstrdup(buff);
+ fp->HeadernameLength = strlen(buff);
+ fp->Header = (char *)NULL;
+ fp->HasHeader = false;
+ fp->HeaderLength = 0;
+ if (strncasecmp(buff, DATE, strlen(DATE)) == 0)
+ Datep = fp;
+ if (strncasecmp(buff, MESSAGEID, strlen(MESSAGEID)) == 0)
+ Msgidp = fp;
+ if (strncasecmp(buff, EXPIRES, strlen(EXPIRES)) == 0)
+ Expp = fp;
+ if (strncasecmp(buff, XREF, strlen(XREF)) == 0) {
+ Xrefp = fp;
+ foundxreffull = fp->NeedHeadername;
+ }
+ fp++;
+ }
+ ARTfieldsize = fp - ARTfields;
+ fclose(F);
+ }
+ if (Msgidp == (ARTOVERFIELD *)NULL)
+ Missfieldsize++;
+ if (Datep == (ARTOVERFIELD *)NULL)
+ Missfieldsize++;
+ if (Expp == (ARTOVERFIELD *)NULL)
+ Missfieldsize++;
+ if (Overview && (Xrefp == (ARTOVERFIELD *)NULL || !foundxreffull))
+ die("Xref:full must be included in %s", SchemaPath);
+ if (Missfieldsize > 0) {
+ Missfields = xmalloc(Missfieldsize * sizeof(ARTOVERFIELD));
+ fp = Missfields;
+ if (Msgidp == (ARTOVERFIELD *)NULL) {
+ fp->NeedHeadername = false;
+ fp->Headername = xstrdup(MESSAGEID);
+ fp->HeadernameLength = strlen(MESSAGEID);
+ fp->Header = (char *)NULL;
+ fp->HasHeader = false;
+ fp->HeaderLength = 0;
+ Msgidp = fp++;
+ }
+ if (Datep == (ARTOVERFIELD *)NULL) {
+ fp->NeedHeadername = false;
+ fp->Headername = xstrdup(DATE);
+ fp->HeadernameLength = strlen(DATE);
+ fp->Header = (char *)NULL;
+ fp->HasHeader = false;
+ fp->HeaderLength = 0;
+ Datep = fp++;
+ }
+ if (Expp == (ARTOVERFIELD *)NULL) {
+ fp->NeedHeadername = false;
+ fp->Headername = xstrdup(EXPIRES);
+ fp->HeadernameLength = strlen(EXPIRES);
+ fp->Header = (char *)NULL;
+ fp->HasHeader = false;
+ fp->HeaderLength = 0;
+ Expp = fp++;
+ }
+ if (Overview && Xrefp == (ARTOVERFIELD *)NULL) {
+ fp->NeedHeadername = false;
+ fp->Headername = xstrdup(XREF);
+ fp->HeadernameLength = strlen(XREF);
+ fp->Header = (char *)NULL;
+ fp->HasHeader = false;
+ fp->HeaderLength = 0;
+ Xrefp = fp++;
+ }
+ }
+}
+
+/*
+ * Handle a single article. This routine's fairly complicated.
+ */
+static void
+DoArt(ARTHANDLE *art)
+{
+ ARTOVERFIELD *fp;
+ const char *p, *end;
+ char *q;
+ static struct buffer buffer = { 0, 0, 0, NULL };
+ static char SEP[] = "\t";
+ static char NUL[] = "\0";
+ static char COLONSPACE[] = ": ";
+ size_t i, j, len;
+ const char *MessageID;
+ time_t Arrived;
+ time_t Expires;
+ time_t Posted;
+ char overdata[BIG_BUFFER];
+ char bytes[BIG_BUFFER];
+ struct artngnum ann;
+
+ /* Set up place to store headers. */
+ for (fp = ARTfields, i = 0; i < ARTfieldsize; i++, fp++) {
+ if (fp->HeaderLength) {
+ fp->Header = 0;
+ }
+ fp->HeaderLength = 0;
+ fp->HasHeader = false;
+ }
+ if (Missfieldsize > 0) {
+ for (fp = Missfields, i = 0; i < Missfieldsize; i++, fp++) {
+ if (fp->HeaderLength) {
+ fp->Header = 0;
+ }
+ fp->HeaderLength = 0;
+ fp->HasHeader = false;
+ }
+ }
+ for (fp = ARTfields, i = 0; i < ARTfieldsize; i++, fp++) {
+ fp->Header = wire_findheader(art->data, art->len, fp->Headername);
+
+ /* Someone managed to break their server so that they were appending
+ multiple Xref headers, and INN had a bug where it wouldn't notice
+ this and reject the article. Just in case, see if there are
+ multiple Xref headers and use the last one. */
+ if (fp == Xrefp) {
+ const char *next = fp->Header;
+ size_t left;
+
+ while (next != NULL) {
+ next = wire_endheader(fp->Header, art->data + art->len - 1);
+ if (next == NULL)
+ break;
+ next++;
+ left = art->len - (next - art->data);
+ next = wire_findheader(next, left, fp->Headername);
+ if (next != NULL)
+ fp->Header = next;
+ }
+ }
+
+ /* Now, if we have a header, find and record its length. */
+ if (fp->Header != NULL) {
+ fp->HasHeader = true;
+ p = wire_endheader(fp->Header, art->data + art->len - 1);
+ if (p == NULL)
+ continue;
+
+ /* The true length of the header is p - fp->Header + 1, but p
+ points to the \n at the end of the header, so subtract 2 to
+ peel off the \r\n (we're guaranteed we're dealing with
+ wire-format articles. */
+ fp->HeaderLength = p - fp->Header - 1;
+ } else if (RetrMode == RETR_ALL
+ && strcmp(fp->Headername, "Bytes") == 0) {
+ snprintf(bytes, sizeof(bytes), "%lu", (unsigned long) art->len);
+ fp->HasHeader = true;
+ fp->Header = bytes;
+ fp->HeaderLength = strlen(bytes);
+ }
+ }
+ if (Missfieldsize > 0) {
+ for (fp = Missfields, i = 0; i < Missfieldsize; i++, fp++) {
+ fp->Header = wire_findheader(art->data, art->len, fp->Headername);
+ if (fp->Header != NULL) {
+ fp->HasHeader = true;
+ p = wire_endheader(fp->Header, art->data + art->len - 1);
+ if (p == NULL)
+ continue;
+ fp->HeaderLength = p - fp->Header - 1;
+ }
+ }
+ }
+ if (DoOverview && Xrefp->HeaderLength == 0) {
+ if (!SMprobe(SMARTNGNUM, art->token, (void *)&ann)) {
+ Xrefp->Header = NULL;
+ Xrefp->HeaderLength = 0;
+ } else {
+ if (ann.artnum == 0 || ann.groupname == NULL)
+ return;
+ len = strlen(innconf->pathhost) + 1 + strlen(ann.groupname) + 1
+ + 16 + 1;
+ if (len > BIG_BUFFER) {
+ Xrefp->Header = NULL;
+ Xrefp->HeaderLength = 0;
+ } else {
+ snprintf(overdata, sizeof(overdata), "%s %s:%lu",
+ innconf->pathhost, ann.groupname, ann.artnum);
+ Xrefp->Header = overdata;
+ Xrefp->HeaderLength = strlen(overdata);
+ }
+ if (ann.groupname != NULL)
+ free(ann.groupname);
+ }
+ }
+
+ MessageID = (char *)NULL;
+ Arrived = art->arrived;
+ Expires = 0;
+ Posted = 0;
+
+ if (!Msgidp->HasHeader) {
+ warn("no Message-ID header in %s", TokenToText(*art->token));
+ if (NukeBadArts)
+ SMcancel(*art->token);
+ return;
+ }
+
+ buffer_set(&buffer, Msgidp->Header, Msgidp->HeaderLength);
+ buffer_append(&buffer, NUL, 1);
+ for (i = 0, q = buffer.data; i < buffer.left; q++, i++)
+ if (*q == '\t' || *q == '\n' || *q == '\r')
+ *q = ' ';
+ MessageID = GetMessageID(buffer.data);
+ if (*MessageID == '\0') {
+ warn("no Message-ID header in %s", TokenToText(*art->token));
+ if (NukeBadArts)
+ SMcancel(*art->token);
+ return;
+ }
+
+ /*
+ * check if msgid is in history if in update mode, or if article is
+ * newer than start time of makehistory.
+ */
+
+ if (!Datep->HasHeader) {
+ Posted = Arrived;
+ } else {
+ buffer_set(&buffer, Datep->Header, Datep->HeaderLength);
+ buffer_append(&buffer, NUL, 1);
+ for (i = 0, q = buffer.data; i < buffer.left; q++, i++)
+ if (*q == '\t' || *q == '\n' || *q == '\r')
+ *q = ' ';
+ if ((Posted = GetaDate(buffer.data)) == 0)
+ Posted = Arrived;
+ }
+
+ if (Expp->HasHeader) {
+ buffer_set(&buffer, Expp->Header, Expp->HeaderLength);
+ buffer_append(&buffer, NUL, 1);
+ for (i = 0, q = buffer.data; i < buffer.left; q++, i++)
+ if (*q == '\t' || *q == '\n' || *q == '\r')
+ *q = ' ';
+ Expires = GetaDate(buffer.data);
+ }
+
+ if (DoOverview && Xrefp->HeaderLength > 0) {
+ for (fp = ARTfields, j = 0; j < ARTfieldsize; j++, fp++) {
+ if (fp == ARTfields)
+ buffer_set(&buffer, "", 0);
+ else
+ buffer_append(&buffer, SEP, strlen(SEP));
+ if (fp->HeaderLength == 0)
+ continue;
+ if (fp->NeedHeadername) {
+ buffer_append(&buffer, fp->Headername, fp->HeadernameLength);
+ buffer_append(&buffer, COLONSPACE, strlen(COLONSPACE));
+ }
+ i = buffer.left;
+ buffer_resize(&buffer, buffer.left + fp->HeaderLength);
+ end = fp->Header + fp->HeaderLength - 1;
+ for (p = fp->Header, q = &buffer.data[i]; p <= end; p++) {
+ if (*p == '\r' && p < end && p[1] == '\n') {
+ p++;
+ continue;
+ }
+ if (*p == '\0' || *p == '\t' || *p == '\n' || *p == '\r')
+ *q++ = ' ';
+ else
+ *q++ = *p;
+ buffer.left++;
+ }
+ }
+ WriteOverLine(art->token, Xrefp->Header, Xrefp->HeaderLength,
+ buffer.data, buffer.left, Arrived, Expires);
+ }
+
+ if (!NoHistory) {
+ bool r;
+
+ r = HISwrite(History, MessageID,
+ Arrived, Posted, Expires, art->token);
+ if (r == false)
+ sysdie("cannot write history line");
+ }
+}
+
+
+/*
+** Add all groups to overview group.index. --rmt
+*/
+static void
+OverAddAllNewsgroups(void)
+{
+ QIOSTATE *qp;
+ int count;
+ char *q,*p;
+ char *line;
+ ARTNUM hi, lo;
+
+ if ((qp = QIOopen(ActivePath)) == NULL)
+ sysdie("cannot open %s", ActivePath);
+ for (count = 1; (line = QIOread(qp)) != NULL; count++) {
+ if ((p = strchr(line, ' ')) == NULL) {
+ warn("bad active line %d: %.40s", count, line);
+ continue;
+ }
+ *p++ = '\0';
+ hi = (ARTNUM)atol(p);
+ if ((p = strchr(p, ' ')) == NULL) {
+ warn("bad active line %d: %.40s", count, line);
+ continue;
+ }
+ *p++ = '\0';
+ lo = (ARTNUM)atol(p);
+ if ((q = strrchr(p, ' ')) == NULL) {
+ warn("bad active line %d: %.40s", count, line);
+ continue;
+ }
+ /* q+1 points to NG flag */
+ if (!OVgroupadd(line, lo, hi, q+1))
+ die("cannot add %s to overview group index", line);
+ }
+ /* Test error conditions; QIOtoolong shouldn't happen. */
+ if (QIOtoolong(qp))
+ die("active file line %d is too long", count);
+ if (QIOerror(qp))
+ sysdie("cannot read %s around line %d", ActivePath, count);
+ QIOclose(qp);
+}
+
+
+/*
+** Change to the news user if possible, and if not, die. Used for operations
+** that may create new database files, so as not to mess up the ownership.
+*/
+static void
+setuid_news(void)
+{
+ struct passwd *pwd;
+
+ pwd = getpwnam(NEWSUSER);
+ if (pwd == NULL)
+ die("can't resolve %s to a UID (account doesn't exist?)", NEWSUSER);
+ if (getuid() == 0)
+ setuid(pwd->pw_uid);
+ if (getuid() != pwd->pw_uid)
+ die("must be run as %s", NEWSUSER);
+}
+
+
+int
+main(int argc, char **argv)
+{
+ ARTHANDLE *art = NULL;
+ bool AppendMode;
+ int i;
+ bool val;
+ char *HistoryDir;
+ char *p;
+ char *buff;
+ size_t npairs = 0;
+
+ /* First thing, set up logging and our identity. */
+ openlog("makehistory", L_OPENLOG_FLAGS | LOG_PID, LOG_INN_PROG);
+ message_program_name = "makehistory";
+
+ /* Set defaults. */
+ if (!innconf_read(NULL))
+ exit(1);
+ HistoryPath = concatpath(innconf->pathdb, _PATH_HISTORY);
+ ActivePath = concatpath(innconf->pathdb, _PATH_ACTIVE);
+ TmpDir = innconf->pathtmp;
+ SchemaPath = concatpath(innconf->pathetc, _PATH_SCHEMA);
+
+ OverTmpSegSize = DEFAULT_SEGSIZE;
+ OverTmpSegCount = 0;
+ NukeBadArts = false;
+ DoOverview = false;
+ Fork = false;
+ AppendMode = false;
+ NoHistory = false;
+ RetrMode = RETR_HEAD;
+
+ while ((i = getopt(argc, argv, "aebf:Il:OT:xFs:")) != EOF) {
+ switch(i) {
+ case 'T':
+ TmpDir = optarg;
+ break;
+ case 'x':
+ NoHistory = true;
+ break;
+ case 'a':
+ AppendMode = true;
+ break;
+ case 'b':
+ NukeBadArts = true;
+ break;
+ case 'f':
+ HistoryPath = optarg;
+ break;
+ case 'I':
+ Cutofflow = true;
+ break;
+ case 'l':
+ OverTmpSegSize = atoi(optarg);
+ break;
+ case 'O':
+ DoOverview = true;
+ break;
+ case 'F':
+ Fork = true;
+ break;
+ case 'e':
+ RetrMode = RETR_ALL;
+ break;
+ case 's':
+ npairs = atoi(optarg);
+ break;
+
+ default:
+ fprintf(stderr, "%s", usage);
+ exit(1);
+ break;
+ }
+ }
+ argc -= optind;
+ argv += optind;
+ if (argc) {
+ fprintf(stderr, "%s", usage);
+ exit(1);
+ }
+
+ if ((p = strrchr(HistoryPath, '/')) == NULL) {
+ /* find the default history file directory */
+ HistoryDir = innconf->pathdb;
+ } else {
+ *p = '\0';
+ HistoryDir = xstrdup(HistoryPath);
+ *p = '/';
+ }
+
+ if (chdir(HistoryDir) < 0)
+ sysdie("cannot chdir to %s", HistoryDir);
+
+ /* Change users if necessary. */
+ setuid_news();
+
+ /* Read in the overview schema */
+ ARTreadschema(DoOverview);
+
+ if (DoOverview) {
+ /* init the overview setup. */
+ if (!OVopen(OV_WRITE))
+ sysdie("cannot open overview");
+ if (!OVctl(OVSORT, (void *)&sorttype))
+ die("cannot obtain overview sort information");
+ if (!Fork) {
+ if (!OVctl(OVCUTOFFLOW, (void *)&Cutofflow))
+ die("cannot obtain overview cutoff information");
+ OverAddAllNewsgroups();
+ } else {
+ OverAddAllNewsgroups();
+ if (sorttype == OVNOSORT) {
+ buff = concat(innconf->pathbin, "/", "overchan", NULL);
+ if ((Overchan = popen(buff, "w")) == NULL)
+ sysdie("cannot fork overchan process");
+ free(buff);
+ }
+ OVclose();
+ }
+ }
+
+ /* Init the Storage Manager */
+ val = true;
+ if (!SMsetup(SM_RDWR, (void *)&val) || !SMsetup(SM_PREOPEN, (void *)&val))
+ sysdie("cannot set up storage manager");
+ if (!SMinit())
+ sysdie("cannot initialize storage manager: %s", SMerrorstr);
+
+ /* Initialise the history manager */
+ if (!NoHistory) {
+ int flags = HIS_RDWR | HIS_INCORE;
+
+ if (!AppendMode)
+ flags |= HIS_CREAT;
+ History = HISopen(NULL, innconf->hismethod, flags);
+ if (History == NULL)
+ sysdie("cannot create history handle");
+ HISctl(History, HISCTLS_NPAIRS, &npairs);
+ if (!HISctl(History, HISCTLS_PATH, HistoryPath))
+ sysdie("cannot open %s", HistoryPath);
+ }
+
+ /* Get the time. Only get it once, which is good enough. */
+ if (GetTimeInfo(&Now) < 0)
+ sysdie("cannot get the time");
+
+ /*
+ * Scan the entire spool, nuke any bad arts if needed, and process each
+ * article.
+ */
+
+ while ((art = SMnext(art, RetrMode)) != NULL) {
+ if (art->len == 0) {
+ if (NukeBadArts && art->data == NULL && art->token != NULL)
+ SMcancel(*art->token);
+ continue;
+ }
+ DoArt(art);
+ }
+
+ if (!NoHistory) {
+ /* close history file. */
+ if (!HISclose(History))
+ sysdie("cannot close history file");
+ }
+
+ if (DoOverview) {
+ if (sorttype == OVNOSORT && Fork)
+ if (fflush(Overchan) == EOF || ferror(Overchan) || pclose(Overchan) == EOF)
+ sysdie("cannot flush overview data");
+ if (sorttype != OVNOSORT) {
+ int status;
+ FlushOverTmpFile();
+ if(Fork)
+ wait(&status);
+ }
+ }
+ if(!Fork)
+ OVclose();
+ exit(0);
+}
+
--- /dev/null
+/* $Id: prunehistory.c 6124 2003-01-14 06:03:29Z rra $
+**
+** Prune file names from history file.
+*/
+
+#include "config.h"
+#include "clibrary.h"
+#include <errno.h>
+#include <syslog.h>
+
+#include "inn/history.h"
+#include "inn/innconf.h"
+#include "inn/messages.h"
+#include "libinn.h"
+#include "paths.h"
+
+
+/*
+** Print usage message and exit.
+*/
+static void
+Usage(void)
+{
+ fprintf(stderr, "Usage: prunehistory [-p] [-f file] [input]\n");
+ exit(1);
+}
+
+
+int
+main(int ac, char *av[])
+{
+ char *p;
+ int i;
+ char buff[BUFSIZ];
+ const char *History;
+ bool Passing;
+ struct history *history = NULL;
+ int rc = 0;
+
+ /* First thing, set up logging and our identity. */
+ openlog("prunehistory", L_OPENLOG_FLAGS | LOG_PID, LOG_INN_PROG);
+ message_program_name = "prunehistory";
+
+ /* Set defaults. */
+ if (!innconf_read(NULL))
+ exit(1);
+
+ History = concatpath(innconf->pathdb, _PATH_HISTORY);
+ Passing = false;
+
+ /* Parse JCL. */
+ while ((i = getopt(ac, av, "f:p")) != EOF)
+ switch (i) {
+ default:
+ Usage();
+ /* NOTREACHED */
+ case 'f':
+ History = optarg;
+ break;
+ case 'p':
+ Passing = true;
+ break;
+ }
+ ac -= optind;
+ av += optind;
+ if (ac) {
+ Usage();
+ rc = 1;
+ goto fail;
+ }
+
+ history = HISopen(History, innconf->hismethod, HIS_RDWR);
+ if (history == NULL) {
+ syswarn("cannot set up %s database", History);
+ rc = 1;
+ goto fail;
+ }
+
+ /* Loop over all input. */
+ while (fgets(buff, sizeof buff, stdin) != NULL) {
+ time_t arrived, posted, expires;
+
+ if ((p = strchr(buff, '\n')) == NULL) {
+ if (Passing)
+ printf("%s\n", buff);
+ else
+ warn("line too long, ignored: %.40s", buff);
+ continue;
+ }
+ *p = '\0';
+
+ /* Ignore blank and comment lines. */
+ if (buff[0] == '\0' || buff[0] == '#') {
+ if (Passing)
+ printf("%s\n", buff);
+ continue;
+ }
+
+ if (buff[0] != '<' || (p = strchr(buff, '>')) == NULL) {
+ if (Passing)
+ printf("%s\n", buff);
+ else
+ warn("line doesn't start with a message ID, ignored: %.40s",
+ buff);
+ continue;
+ }
+ *++p = '\0';
+
+ if (HISlookup(history, buff, &arrived, &posted, &expires, NULL)) {
+ if (!HISreplace(history, buff, arrived, posted, expires, NULL))
+ syswarn("cannot write new text for %s", buff);
+ } else {
+ syswarn("no entry for %s", buff);
+ }
+ }
+
+ fail:
+ /* Close files; we're done. */
+ if (history != NULL && !HISclose(history)) {
+ syswarn("cannot close %s", History);
+ rc = 1;
+ }
+
+ return rc;
+}
--- /dev/null
+control 0000000000 0000000001 n
+control.cancel 0000000000 0000000001 n
+control.checkgroups 0000000000 0000000001 n
+control.newgroup 0000000000 0000000001 n
+control.rmgroup 0000000000 0000000001 n
+junk 0000000000 0000000001 n
+local.general 0000000000 0000000001 y
+local.test 0000000000 0000000001 y
--- /dev/null
+#!/bin/bash
+. /usr/lib/news/innshellvars
+
+cd $PATHTMP
+
+KEYSURL=ftp://ftp.isc.org/pub/pgpcontrol/PGPKEYS
+KEYSFILE=PGPKEYS
+
+KEYRING=${NEWSETC}/pgp/pubring.gpg
+
+trap "rm -f $KEYSFILE" 0 1 2 15
+
+rm -f ${KEYSFILE}
+${GETFTP} ${KEYSURL}
+
+test -f ${KEYSFILE} || exit 1
+
+gpg --batch --no-permission-warning \
+ --no-default-keyring --keyring=${KEYRING} --no-options \
+ --allow-non-selfsigned-uid --fast-import ${KEYSFILE}
+
+exit $$
+# this does not work because gpg refuses to use RSA-style fingerprints
+
+KEYSERVER=keyserver.linux.it
+
+SERVERKEYS=$(grep fingerprint ${CTLFILE} \
+ | sed -e 's/ //g' -e 's/.*[:=]/0x/' \
+ | grep -v '^#')
+
+for key in $SERVERKEYS; do
+ gpg --batch --no-permission-warning --verbose \
+ --no-default-keyring --keyring=${KEYRING} --no-options \
+ --keyserver=${KEYSERVER} --recv-keys ${key}
+done
+
--- /dev/null
+#!/bin/sh
+exec /bin/bzip2 -d -c
--- /dev/null
+#!/bin/sh -e
+
+IN="$1"
+OUT="$2"
+
+for file in debian/$IN.* debian/$IN*.files; do
+ case "$file" in
+ *.log) continue ;;
+ esac
+ [ -h $file ] && continue
+ base=${file##*/}
+ newfile=$(echo $file | sed -re "s#/$IN#/$OUT#")
+ [ -e $newfile ] || ln -s $base $newfile
+done
+
--- /dev/null
+#!/bin/sh -e
+
+# Add this line in /etc/news/newsfeeds:
+# !inpaths:*:Tc,WP:/usr/lib/news/bin/ginpaths2
+
+exec /usr/lib/news/bin/ninpaths -p -d /var/log/news/path/inpaths.%d
--- /dev/null
+control Various control messages (no posting)
+control.cancel Cancel messages (no posting)
+control.checkgroups Hierarchy check control messages (no posting)
+control.newgroup Newsgroup creation control messages (no posting)
+control.rmgroup Newsgroup removal control messages (no posting)
+junk Unfiled articles (no posting)
+local.general Local general group
+local.test Local test group
--- /dev/null
+tls_cert_file: /etc/news/nnrpd-cert.pem
+tls_key_file: /etc/news/nnrpd-key.pem
+tls_ca_path: /etc/news
+tls_ca_file: /etc/news/nnrpd-ca-cert.pem
--- /dev/null
+#
+# send-uucp.cf Configuration file for send-uucp
+#
+# Format: sitename<Space>compressor<Space>maxsize<Space>batchtime
+#
+# compressor, maxsize and batchtime can be left out and will
+# then use the # default values. You can't leave out the second
+# field (compressor) and still use the third (maxsize) etc.!
+# So if you want to set a maxsize, you HAVE to add a
+# compression method.
+#
+# Compress keywords are: compress gzip none
+#
+# You can use flags with your compressor, just add them. Use
+# the `_' character instead of a space.
+# For example compress_-b13 for 13 bits compression.
+#
+# Remember that the size you set is the size *before* compression!
+#
+#zoetermeer gzip 1048576 5,18,22
+#hoofddorp gzip 1048576 5,18,22
+#pa3ebv gzip 1048576 5,18,22
+#drinkel gzip 1048576 5,6,18,20,22,0,2
+#manhole compress 1048576 5,18,22
+#owl compress 1048576 5,18,22
+#able compress 1048576 5,18,22
--- /dev/null
+## $Id: Makefile 7727 2008-04-06 07:59:46Z iulius $
+
+include ../Makefile.global
+
+top = ..
+CFLAGS = $(GCFLAGS)
+
+ALL = c7unbatch cnfsheadconf cnfsstat ctlinnd decode encode \
+ getlist gunbatch inews innconfval mailpost pullnews \
+ ovdb_init ovdb_monitor ovdb_server ovdb_stat rnews \
+ scanspool sm
+
+SOURCES = ctlinnd.c decode.c encode.c getlist.c inews.c innconfval.c \
+ ovdb_init.c ovdb_monitor.c ovdb_server.c ovdb_stat.c rnews.c \
+ sm.c
+
+all: $(ALL)
+
+warnings:
+ $(MAKE) COPT='$(WARNINGS)' all
+
+install: all
+ $(LI_INEWS) inews $D$(PATHBIN)/inews
+ $(LI_RNEWS) rnews $D$(PATHBIN)/rnews
+ $(CP_XPRI) cnfsheadconf $D$(PATHBIN)/cnfsheadconf
+ for F in cnfsstat mailpost pullnews scanspool ; do \
+ $(CP_XPUB) $$F $D$(PATHBIN)/$$F ; \
+ done
+ for F in ctlinnd ovdb_init ovdb_monitor ovdb_server ovdb_stat ; do \
+ $(LI_XPRI) $$F $D$(PATHBIN)/$$F ; \
+ done
+ for F in getlist innconfval sm ; do \
+ $(LI_XPUB) $$F $D$(PATHBIN)/$$F ; \
+ done
+ $(CP_XPUB) c7unbatch $D$(PATHBIN)/rnews.libexec/c7unbatch
+ $(LI_XPUB) decode $D$(PATHBIN)/rnews.libexec/decode
+ $(LI_XPUB) encode $D$(PATHBIN)/rnews.libexec/encode
+ $(CP_XPUB) gunbatch $D$(PATHBIN)/rnews.libexec/gunbatch
+
+clean:
+ rm -f *.o $(ALL)
+ rm -rf .libs
+
+clobber distclean: clean
+ rm -f tags
+
+tags ctags: $(SOURCES)
+ $(CTAGS) $(SOURCES)
+
+profiled:
+ $(MAKEPROFILING) all
+
+$(FIXSCRIPT):
+ @echo Run configure before running make. See INSTALL for details.
+ @exit 1
+
+
+## Compilation rules.
+
+BOTH = $(LIBSTORAGE) $(LIBHIST) $(LIBINN)
+
+LINK = $(LIBLD) $(LDFLAGS) -o $@
+INNLIBS = $(LIBINN) $(LIBS)
+STORELIBS = $(BOTH) $(EXTSTORAGELIBS) $(LIBS)
+
+FIX = $(FIXSCRIPT)
+
+ctlinnd: ctlinnd.o $(LIBINN) ; $(LINK) ctlinnd.o $(INNLIBS)
+decode: decode.o $(LIBINN) ; $(LINK) decode.o $(INNLIBS)
+encode: encode.o ; $(LINK) encode.o
+getlist: getlist.o $(LIBINN) ; $(LINK) getlist.o $(INNLIBS)
+inews: inews.o $(LIBINN) ; $(LINK) inews.o $(INNLIBS)
+innconfval: innconfval.o $(LIBINN) ; $(LINK) innconfval.o $(INNLIBS)
+ovdb_init: ovdb_init.o $(BOTH) ; $(LINK) ovdb_init.o $(STORELIBS)
+ovdb_monitor: ovdb_monitor.o $(BOTH) ; $(LINK) ovdb_monitor.o $(STORELIBS)
+ovdb_server: ovdb_server.o $(BOTH) ; $(LINK) ovdb_server.o $(STORELIBS)
+ovdb_stat: ovdb_stat.o $(BOTH) ; $(LINK) ovdb_stat.o $(STORELIBS)
+rnews: rnews.o $(LIBINN) ; $(LINK) rnews.o $(STORELIBS)
+sm: sm.o $(BOTH) ; $(LINK) sm.o $(STORELIBS)
+
+ovdb_init.o: ovdb_init.c
+ $(CC) $(CFLAGS) $(BERKELEY_DB_CFLAGS) -c $<
+
+ovdb_monitor.o: ovdb_monitor.c
+ $(CC) $(CFLAGS) $(BERKELEY_DB_CFLAGS) -c $<
+
+ovdb_server.o: ovdb_server.c
+ $(CC) $(CFLAGS) $(BERKELEY_DB_CFLAGS) -c $<
+
+ovdb_stat.o: ovdb_stat.c
+ $(CC) $(CFLAGS) $(BERKELEY_DB_CFLAGS) -c $<
+
+cnfsheadconf: cnfsheadconf.in $(FIX) ; $(FIX) cnfsheadconf.in
+cnfsstat: cnfsstat.in $(FIX) ; $(FIX) cnfsstat.in
+mailpost: mailpost.in $(FIX) ; $(FIX) mailpost.in
+pullnews: pullnews.in $(FIX) ; $(FIX) -i pullnews.in
+scanspool: scanspool.in $(FIX) ; $(FIX) scanspool.in
+
+c7unbatch: Makefile ../Makefile.global
+ ( echo '#! $(SHELL)' ; echo 'decode | $(UNCOMPRESS)' ) > $@
+ chmod 755 c7unbatch
+
+gunbatch: Makefile ../Makefile.global
+ ( echo '#! $(SHELL)' ; echo 'exec $(GZIP) -d -c' ) > $@
+ chmod 755 gunbatch
+
+## Not normally built.
+feedone: feedone.o $(LIBINN) ; $(LINK) feedone.o $(INNLIBS)
+sys2nf: sys2nf.o $(LIBINN) ; $(LINK) sys2nf.o $(INNLIBS)
+
+$(LIBINN): ; (cd ../lib ; $(MAKE))
+$(LIBSTORAGE): ; (cd ../storage ; $(MAKE))
+$(LIBHIST): ; (cd ../history ; $(MAKE))
+
+
+## Dependencies. Default list, below, is probably good enough.
+
+depend: Makefile $(SOURCES)
+ $(MAKEDEPEND) '$(CFLAGS)' $(SOURCES)
+
+# DO NOT DELETE THIS LINE -- make depend depends on it.
+ctlinnd.o: ctlinnd.c ../include/config.h ../include/inn/defines.h \
+ ../include/inn/system.h ../include/clibrary.h ../include/config.h \
+ ../include/inn/innconf.h ../include/inn/defines.h \
+ ../include/inn/messages.h ../include/inndcomm.h ../include/libinn.h \
+ ../include/paths.h
+decode.o: decode.c ../include/config.h ../include/inn/defines.h \
+ ../include/inn/system.h ../include/clibrary.h ../include/config.h \
+ ../include/inn/messages.h ../include/inn/defines.h
+encode.o: encode.c ../include/config.h ../include/inn/defines.h \
+ ../include/inn/system.h ../include/clibrary.h ../include/config.h
+getlist.o: getlist.c ../include/config.h ../include/inn/defines.h \
+ ../include/inn/system.h ../include/clibrary.h ../include/config.h \
+ ../include/inn/innconf.h ../include/inn/defines.h \
+ ../include/inn/messages.h ../include/inn/qio.h ../include/libinn.h \
+ ../include/paths.h
+inews.o: inews.c ../include/config.h ../include/inn/defines.h \
+ ../include/inn/system.h ../include/clibrary.h ../include/config.h \
+ ../include/portable/time.h ../include/config.h ../include/inn/innconf.h \
+ ../include/inn/defines.h ../include/inn/messages.h ../include/libinn.h \
+ ../include/nntp.h ../include/paths.h
+innconfval.o: innconfval.c ../include/config.h ../include/inn/defines.h \
+ ../include/inn/system.h ../include/clibrary.h ../include/config.h \
+ ../include/inn/innconf.h ../include/inn/defines.h \
+ ../include/inn/messages.h ../include/libinn.h
+ovdb_init.o: ovdb_init.c ../include/config.h ../include/inn/defines.h \
+ ../include/inn/system.h ../include/clibrary.h ../include/config.h \
+ ../include/libinn.h ../include/inn/innconf.h ../include/inn/defines.h \
+ ../include/inn/messages.h ../include/ov.h ../include/storage.h \
+ ../include/inn/history.h ../storage/ovdb/ovdb.h \
+ ../storage/ovdb/ovdb-private.h
+ovdb_monitor.o: ovdb_monitor.c ../include/config.h \
+ ../include/inn/defines.h ../include/inn/system.h ../include/clibrary.h \
+ ../include/config.h ../include/portable/setproctitle.h \
+ ../include/config.h ../include/portable/wait.h ../include/inn/innconf.h \
+ ../include/inn/defines.h ../include/inn/messages.h ../include/libinn.h \
+ ../include/ov.h ../include/storage.h ../include/inn/history.h \
+ ../storage/ovdb/ovdb.h ../storage/ovdb/ovdb-private.h
+ovdb_server.o: ovdb_server.c ../include/config.h ../include/inn/defines.h \
+ ../include/inn/system.h ../include/clibrary.h ../include/config.h \
+ ../include/portable/mmap.h ../include/config.h \
+ ../include/portable/time.h ../include/portable/setproctitle.h \
+ ../include/portable/socket.h ../include/portable/wait.h \
+ ../include/inn/innconf.h ../include/inn/defines.h \
+ ../include/inn/messages.h ../include/libinn.h ../include/paths.h \
+ ../include/storage.h ../include/ov.h ../include/storage.h \
+ ../include/inn/history.h ../storage/ovdb/ovdb.h \
+ ../storage/ovdb/ovdb-private.h
+ovdb_stat.o: ovdb_stat.c ../include/config.h ../include/inn/defines.h \
+ ../include/inn/system.h ../include/clibrary.h ../include/config.h \
+ ../include/inn/innconf.h ../include/inn/defines.h \
+ ../include/inn/messages.h ../include/libinn.h ../include/ov.h \
+ ../include/storage.h ../include/inn/history.h ../include/paths.h \
+ ../include/storage.h ../storage/ovdb/ovdb.h \
+ ../storage/ovdb/ovdb-private.h
+rnews.o: rnews.c ../include/config.h ../include/inn/defines.h \
+ ../include/inn/system.h ../include/clibrary.h ../include/config.h \
+ ../include/portable/wait.h ../include/config.h ../include/inn/innconf.h \
+ ../include/inn/defines.h ../include/inn/messages.h \
+ ../include/inn/wire.h ../include/libinn.h ../include/nntp.h \
+ ../include/paths.h ../include/storage.h
+sm.o: sm.c ../include/config.h ../include/inn/defines.h \
+ ../include/inn/system.h ../include/clibrary.h ../include/config.h \
+ ../include/inn/innconf.h ../include/inn/defines.h \
+ ../include/inn/messages.h ../include/inn/qio.h ../include/storage.h
--- /dev/null
+#! /usr/bin/perl
+# fixscript will replace this line with require innshellvars.pl
+
+# $Id: cnfsheadconf.in 6727 2004-05-16 21:21:14Z rra $
+#
+# Copyright Andreas Lamrecht 1998
+# <Andreas.Lamprect@siemens.at>
+#
+# Modified by Kjetil T. Homme 1998
+# <kjetilho@ifi.uio.no>
+#
+# Modified by Robert R. Collier 1998
+# <rob@lspace.org>
+#
+# bigint support added by Duane Currie (sandman@hub.org) 1998
+#
+# cnfsheadconf is originally from cnfsstat 1999
+# <kondou@nec.co.jp>
+
+use vars qw($opt_h $opt_w);
+use Getopt::Long;
+
+# required for >32bit ints
+require 'bigint.pl';
+
+my($conffile) = "$inn::pathetc/cycbuff.conf";
+my($storageconf) = "$inn::pathetc/storage.conf";
+
+# Hex to bigint conversion routine
+# bhex(HEXSTRING) returns BIGINT (with leading + chopped off)
+#
+# In most langauge, unlimited size integers are done using string math
+# libraries usually called bigint. (Java, Perl, etc...)
+
+# Bigint's are really just strings.
+
+# Mathematics routines for bigint's:
+
+# bneg(BINT) return BINT negation
+# babs(BINT) return BINT absolute value
+# bcmp(BINT,BINT) return CODE compare numbers (undef,<0,=0,>0)
+# badd(BINT,BINT) return BINT addition
+# bsub(BINT,BINT) return BINT subtraction
+# bmul(BINT,BINT) return BINT multiplication
+# bdiv(BINT,BINT) return (BINT,BINT) division (quo,rem) just quo if scalar
+# bmod(BINT,BINT) return BINT modulus
+# bgcd(BINT,BINT) return BINT greatest common divisor
+# bnorm(BINT) return BINT normalization
+
+sub bhex {
+ my $hexValue = shift;
+ $hexValue =~ s/^0x//;
+
+ my $integerValue = '0';
+ for (my $i = 0; $i < length($hexValue); $i+=2) {
+ # Could be more efficient going at larger increments, but byte
+ # by byte is safer for the case of 9 byte values, 11 bytes, etc..
+
+ my $byte = substr($hexValue,$i,2);
+ my $byteIntValue = hex($byte);
+
+ $integerValue = bmul($integerValue,'256');
+ $integerValue = badd($integerValue,"$byteIntValue");
+ }
+
+ $integerValue =~ s/^\+//;
+ return $integerValue;
+ }
+
+sub bint2hex {
+ my $d = shift;
+ my $o = 0;
+
+ while ($d > 0) {
+ my $h = bmod("$d",'16');
+ $d = bdiv("$d",'16');
+ $h =~ s/^\+//;
+ $h='a' if $h eq '10';
+ $h='b' if $h eq '11';
+ $h='c' if $h eq '12';
+ $h='d' if $h eq '13';
+ $h='e' if $h eq '14';
+ $h='f' if $h eq '15';
+ $h =~ s/^\+//;
+ $o="$h$o";
+ }
+
+ return "$o";
+}
+
+sub usage {
+ print <<_end_;
+Summary tool for cycbuff header manipulation
+
+Usage:
+ $0 [-c CYCBUFF] [-h] [-w]
+
+ If called without args, does a one-time status of all CNFS buffers
+ -c <cycbuff>: prints out status of cycbuff
+ -w: change header
+ -h: This information
+_end_
+ exit(1);
+}
+
+my(@line, %class, %metamode, %buff, %stor, $c, @buffers, $cycbuff);
+
+my($gr, $cl, $min, $max, @storsort, $header_printed);
+
+GetOptions("-c=s", \$cycbuff, "-w", "-h");
+
+&usage if $opt_h;
+
+unless (&read_cycbuffconf) {
+ print STDERR "Cannot open CycBuff Conffile $conffile ...\n";
+ exit (1);
+}
+
+unless (&read_storageconf) {
+ print STDERR "No valid $storageconf.\n";
+ exit (1);
+}
+
+sub read_cycbuffconf {
+ return 0 unless open (CONFFILE, $conffile);
+
+ while(<CONFFILE>) {
+ $_ =~ s/^\s*(.*?)\s*$/$1/;
+ # \x23 below is #. Emacs perl-mode gets confused by the "comment"
+ next if($_ =~ /^\s*$/ || $_ =~ /^\x23/);
+ next if($_ =~ /^cycbuffupdate:/ || $_ =~ /^refreshinterval:/);
+
+ if($_ =~ /^metacycbuff:/) {
+ @line = split(/:/, $_);
+ if($class{$line[1]}) {
+ print STDERR "Class $line[1] more than one time in CycBuff Conffile $conffile ...\n";
+ return 0;
+ }
+
+ $class{$line[1]} = $line[2];
+ if ($line[3] ne "") {
+ $metamode{$line[1]} = $line[3];
+ } else {
+ $metamode{$line[1]} = "INTERLEAVE";
+ }
+ next;
+ }
+
+ if ($_ =~ /^cycbuff/) {
+ @line = split(/:/, $_);
+ if($buff{$line[1]}) {
+ print STDERR "Buff $line[1] more than one time in CycBuff Conffile $conffile ...\n";
+ return 1;
+ }
+ $buff{$line[1]} = $line[2];
+ next;
+ }
+
+ print STDERR "Unknown config line \"$_\" in CycBuff Conffile $conffile ...\n";
+ }
+ close(CONFFILE);
+ return 1;
+}
+
+sub read_storageconf {
+ my $line = 0;
+ return 0 unless open (STOR, $storageconf);
+
+ while (<STOR>) {
+ ++$line;
+ next if /^\s*#/;
+
+ # defaults
+ %key = ("NEWSGROUPS" => "*",
+ "SIZE" => "0,0");
+
+ if (/method\s+cnfs\s+\{/) {
+ while (<STOR>) {
+ ++$line;
+ next if /^\s*#/;
+ last if /\}/;
+ if (/(\w+):\s+(\S+)/i) {
+ $key{uc($1)} = $2;
+ }
+ }
+ unless (defined $key{'CLASS'} && defined $key{'OPTIONS'}) {
+ print STDERR "storage.conf:$line: ".
+ "Missing 'class' or 'options'\n";
+ return 0;
+ }
+
+ $key{'SIZE'} .= ",0" unless $key{'SIZE'} =~ /,/;
+ $key{'SIZE'} =~ s/,/:/;
+
+ if (defined $stor{$key{'OPTIONS'}}) {
+ print STDERR "storage.conf:$line: ".
+ "Class $key{'CLASS'} has several criteria\n";
+ } else {
+ $stor{$key{'OPTIONS'}} = "$key{'NEWSGROUPS'}:$key{'CLASS'}:" .
+ "$key{'SIZE'}:$key{'OPTIONS'}";
+ push(@storsort, $key{'OPTIONS'});
+ }
+ }
+ }
+ return 1;
+}
+
+START:
+
+if (! $buff{$cycbuff} ) {
+ print STDERR "No buffer definition for buffer $cycbuff ...\n";
+ exit(1);
+}
+&print_cycbuff_head($buff{$cycbuff});
+
+sub make_time {
+ my ($t) = @_;
+ my (@ret);
+
+ my ($sec,$min,$hour,$mday,$mon,$year) =
+ (localtime($t))[0..5];
+ push (@ret, sprintf("%04d-%02d-%02d %2d:%02d:%02d",
+ $year + 1900, $mon + 1, $mday, $hour, $min, $sec));
+ $t = time - $t;
+
+ $mday = int($t/86400); $t = $t % 86400;
+ $hour = int($t/3600); $t = $t % 3600;
+ $min = int($t/60); $t = $t % 60;
+
+ push (@ret, sprintf("%4d days, %2d:%02d:%02d",
+ $mday, $hour, $min, $t));
+ return @ret;
+}
+
+sub print_cycbuff_head {
+ my($buffpath) = $_[0];
+ my($CNFSMASIZ)=8;
+ my($CNFSNASIZ)=16;
+ my($CNFSPASIZ)=64;
+ my($CNFSLASIZ)=16;
+ my($headerlength) = 2 * $CNFSMASIZ + 2 * $CNFSNASIZ + $CNFSPASIZ + (5 * $CNFSLASIZ);
+ my($buff, @entries, $e);
+ my($magic, $name, $path, $lena, $freea, $updatea, $cyclenuma, $metaname, $orderinmeta, $currentbuff);
+
+ if ($opt_w) {
+ if(! open(BUFF, "+< $buffpath") ) {
+ print STDERR "Cannot open Cycbuff $buffpath ...\n";
+ exit(1);
+ }
+ } else {
+ if(! open(BUFF, "< $buffpath") ) {
+ print STDERR "Cannot open Cycbuff $buffpath ...\n";
+ exit(1);
+ }
+ }
+
+ $buff = "";
+ if(! read(BUFF, $buff, $headerlength) ) {
+ print STDERR "Cannot read $headerlength bytes from file $buffpath...\n";
+ exit(1);
+ }
+
+ ($magic, $name, $path, $lena, $freea, $updatea, $cyclenuma, $metaname, $orderinmeta, $currentbuff) = unpack("a8 a16 a64 a16 a16 a16 a16 a16 a16 a8", $buff);
+
+ if(!$magic) {
+ print STDERR "Error while unpacking header ...\n";
+ exit(1);
+ }
+
+ my($len) = bhex($lena);
+ my($free) = bhex($freea);
+ my($update) = hex($updatea);
+ my($cyclenum) = hex($cyclenuma) - 1;
+
+ my ($nupdate_str, $nago_str) = &make_time ($update);
+
+ $name =~ s/\0//g;
+ print " Buffer $name, len: ";
+ printf("%.2f", $len / (1024 * 1024));
+ print " Mbytes, used: ";
+ printf("%.2f Mbytes", $free / (1024 * 1024));
+ printf(" (%4.1f%%) %3d cycles\n", 100 * $free/$len, $cyclenum);
+ print(" Meta $metaname, order: ");
+ printf("%d", $orderinmeta);
+ print(", current: $currentbuff");
+
+ print "\n Newest: $nupdate_str, $nago_str ago\n";
+
+ if ($opt_w) {
+ print "\nBuffer [$name] => ";
+ $in = <>;
+ chop $in;
+ if ($in ne "") {
+ $name = sprintf("%0.9s\0", $in);
+ }
+ print "Path [$path] => ";
+ $in = <>;
+ chop $in;
+ if ($in ne "") {
+ $path = sprintf("%0.65s\0", $in);
+ }
+ print "Length [$len ($lena)] => ";
+ $in = <>;
+ chop $in;
+ if ($in ne "") {
+ $in = bint2hex($in);
+ $lena = sprintf("%017.17s\0", $in);
+ }
+ print "Free [$free ($freea)] => ";
+ $in = <>;
+ chop $in;
+ if ($in ne "") {
+ $in = bint2hex($in);
+ $freea = sprintf("%017.17s\0", $in);
+ }
+ print "Meta [$metaname] => ";
+ $in = <>;
+ chop $in;
+ if ($in ne "") {
+ $metaname = sprintf("%0.17s\0", $in);
+ }
+ print "Order [$orderinmeta] => ";
+ $in = <>;
+ chop $in;
+ if ($in ne "") {
+ $orderinmeta = sprintf("%016d\0", $in);
+ }
+ print "Currentbuff [$currentbuff] => ";
+ $in = <>;
+ chop $in;
+ if ($in eq "TRUE" || $in eq "FALSE") {
+ $currentbuff = sprintf("%0.8s", $in);
+ }
+ $buff = pack("a8 a16 a64 a16 a16 a16 a16 a16 a16 a8", $magic, $name, $path, $lena, $freea, $updatea, $cyclenuma, $metaname, $orderinmeta, $currentbuff);
+ seek(BUFF, 0, 0);
+ if(! syswrite(BUFF, $buff, $headerlength) ) {
+ print STDERR "Cannot write $headerlength bytes to file $buffpath...\n";
+ exit(1);
+ }
+ }
+ close(BUFF);
+}
--- /dev/null
+#! /usr/bin/perl
+# fixscript will replace this line with require innshellvars.pl
+
+# $Id: cnfsstat.in 7060 2004-12-19 21:36:38Z rra $
+#
+# Copyright Andreas Lamrecht 1998
+# <Andreas.Lamprect@siemens.at>
+#
+# Modified by Kjetil T. Homme 1998, 2000
+# <kjetilho@ifi.uio.no>
+#
+# Modified by Robert R. Collier 1998
+# <rob@lspace.org>
+#
+# bigint support added by Duane Currie (sandman@hub.org) 1998
+
+use vars qw($opt_l $opt_h $opt_a $opt_s);
+use Getopt::Long;
+use Math::BigInt;
+use Math::BigFloat;
+use English;
+
+my($conffile) = "$inn::pathetc/cycbuff.conf";
+my($storageconf) = "$inn::pathetc/storage.conf";
+
+sub usage {
+ print <<_end_;
+Summary tool for CNFS
+
+Usage:
+ $0 [-c CLASS] [-l [seconds]]
+
+ If called without args, does a one-time status of all CNFS buffers
+ -a: print the age of the oldest article in the cycbuff
+ -c <CLASS>: prints out status of CNFS buffers in class CLASS
+ -l seconds: loops like vmstat, default seconds = 600
+ -s: logs through syslog
+ -h: This information
+ -m <BUFFER>: prints out information suitable for mrtg
+ -p: prints out an mrtg config file
+ -P: write PID into $inn::pathrun/cnfsstat.pid
+_end_
+ exit(1);
+}
+
+my(@line, %class, %buff, %stor, $c, @buffers);
+
+my($gr, $cl, $min, $max, @storsort, $oclass, $header_printed);
+
+Getopt::Long::config('no_ignore_case');
+GetOptions("-a", "-c=s", \$oclass, "-h", "-l:i", "-s", "-m=s", \$obuffer,
+ "-p", "-P");
+
+&usage if $opt_h;
+
+if ($opt_s) {
+ $use_syslog = 0;
+ ## Comment out this eval line if you don't want to try to syslog
+ eval { require Sys::Syslog; import Sys::Syslog; $use_syslog = 1 };
+ if ($use_syslog) {
+ if (defined &Sys::Syslog::setlogsock && $] >= 5.00403) {
+ # we really need a common module to work all this junk out
+ if ($OSNAME eq "dec_osf") {
+ sub Sys::Syslog::_PATH_LOG { "/dev/log" }
+ }
+ Sys::Syslog::setlogsock('unix')
+ if $OSNAME =~ /linux|freebsd|dec_osf|darwin/;
+ }
+ openlog ('cnfsstat', 'pid', $inn::syslog_facility);
+ } else {
+ print STDERR "Syslog is not available. -s option is ignored.\n";
+ }
+}
+
+if ($opt_P) {
+ open(FILE, ">$inn::pathrun/cnfsstat.pid") && do {
+ print FILE "$$\n";
+ close FILE;
+ };
+}
+
+my($sleeptime) = (defined($opt_l) && $opt_l > 0) ? $opt_l : 600;
+
+unless (&read_cycbuffconf) {
+ print STDERR "Cannot open CycBuff Conffile $conffile ...\n";
+ exit (1);
+}
+
+unless (&read_storageconf) {
+ print STDERR "No valid $storageconf.\n";
+ exit (1);
+}
+
+
+&mrtg($obuffer) if $obuffer;
+&mrtg_config if $opt_p;
+
+#foreach $c (keys(%class)) {
+# print "Class: $c, definition: $class{$c}\n";
+#}
+#foreach $c (keys(%buff)) {
+# print "Buff: $c, definition: $buff{$c}\n";
+#}
+# exit(0);
+
+START:
+
+undef($logline);
+if ($oclass) {
+ if ($class{$oclass}) {
+ if (!$header_printed) {
+ ($gr, $cl, $min, $max) = split(/:/, $stor{$oclass});
+ if ($use_syslog) {
+ if ($min || $max) {
+ $logline = sprintf("Class %s for groups matching \"%s\" article size min/max: %d/%d", $oclass, $gr, $min, $max);
+ } else {
+ $logline = sprintf("Class %s for groups matching \"%s\"", $oclass, $gr);
+ }
+ } else {
+ print STDOUT "Class $oclass";
+ print STDOUT " for groups matching \"$gr\"";
+ if ($min || $max) {
+ print STDOUT ", article size min/max: $min/$max";
+ }
+ print STDOUT "\n";
+ }
+ $header_printed = 1;
+ }
+
+ @buffers = split(/,/, $class{$oclass});
+ if (! @buffers) {
+ print STDERR "No buffers in Class $main::ARGV[0] ...\n";
+ next;
+ }
+
+ foreach $b (@buffers) {
+ if (! $buff{$b} ) {
+ print STDERR "No buffer definition for buffer $b ...\n";
+ next;
+ }
+ &print_cycbuff_head($buff{$b});
+ }
+ } else {
+ print STDERR "Class $ARGV[1] not found ...\n";
+ }
+} else { # Print all Classes
+
+ foreach $c (@storsort) {
+ ($gr, $cl, $min, $max) = split(/:/, $stor{$c});
+ if ($use_syslog) {
+ if ($min || $max) {
+ $logline = sprintf("Class %s for groups matching \"%s\" article size min/max: %d/%d", $c, $gr, $min, $max);
+ } else {
+ $logline = sprintf("Class %s for groups matching \"%s\"", $c, $gr);
+ }
+ } else {
+ print STDOUT "Class $c ";
+ print STDOUT " for groups matching \"$gr\"";
+ if($min || $max) {
+ print STDOUT ", article size min/max: $min/$max";
+ }
+ print STDOUT "\n";
+ }
+ @buffers = split(/,/, $class{$c});
+ if(! @buffers) {
+ print STDERR "No buffers in Class $c ...\n";
+ next;
+ }
+
+ foreach $b (@buffers) {
+ if(! $buff{$b} ) {
+ print STDERR "No buffer definition for buffer $b ...\n";
+ next;
+ }
+ &print_cycbuff_head($buff{$b});
+ }
+ if ($use_syslog == 0) {
+ print STDOUT "\n";
+ }
+ }
+}
+
+if(defined($opt_l)) {
+ sleep($sleeptime);
+ if ($use_syslog == 0) {
+ print STDOUT "$sleeptime seconds later:\n";
+ }
+ goto START;
+}
+
+sub read_cycbuffconf {
+ return 0 unless open (CONFFILE, $conffile);
+
+ while(<CONFFILE>) {
+ $_ =~ s/^\s*(.*?)\s*$/$1/;
+ # Here we handle continuation lines
+ while (m/\\$/) {
+ $contline = <CONFFILE>;
+ $contline =~ s/^\s*(.*?)\s*$/$1/;
+ chop;
+ $_ .= $contline;
+ }
+ # \x23 below is #. Emacs perl-mode gets confused by the "comment"
+ next if($_ =~ /^\s*$/ || $_ =~ /^\x23/);
+ next if($_ =~ /^cycbuffupdate:/ || $_ =~ /^refreshinterval:/);
+
+ if($_ =~ /^metacycbuff:/) {
+ @line = split(/:/, $_);
+ if($class{$line[1]}) {
+ print STDERR "Class $line[1] more than one time in CycBuff Conffile $conffile ...\n";
+ return 0;
+ }
+
+ $class{$line[1]} = $line[2];
+ next;
+ }
+
+ if ($_ =~ /^cycbuff/) {
+ @line = split(/:/, $_);
+ if($buff{$line[1]}) {
+ print STDERR "Buff $line[1] more than one time in CycBuff Conffile $conffile ...\n";
+ return 1;
+ }
+ $buff{$line[1]} = $line[2];
+ next;
+ }
+
+ print STDERR "Unknown config line \"$_\" in CycBuff Conffile $conffile ...\n";
+ }
+ close(CONFFILE);
+ return 1;
+}
+
+sub read_storageconf {
+ my $line = 0;
+ return 0 unless open (STOR, $storageconf);
+
+ while (<STOR>) {
+ ++$line;
+ next if /^\s*#/;
+
+ # defaults
+ %key = ("NEWSGROUPS" => "*",
+ "SIZE" => "0,0");
+
+ if (/method\s+cnfs\s+\{/) {
+ while (<STOR>) {
+ ++$line;
+ next if /^\s*#/;
+ last if /\}/;
+ if (/(\w+):\s+(\S+)/i) {
+ $key{uc($1)} = $2;
+ }
+ }
+ unless (defined $key{'CLASS'} && defined $key{'OPTIONS'}) {
+ print STDERR "storage.conf:$line: ".
+ "Missing 'class' or 'options'\n";
+ return 0;
+ }
+
+ $key{'SIZE'} .= ",0" unless $key{'SIZE'} =~ /,/;
+ $key{'SIZE'} =~ s/,/:/;
+
+ if (!defined $stor{$key{'OPTIONS'}}) {
+ $stor{$key{'OPTIONS'}} = "$key{'NEWSGROUPS'}:$key{'CLASS'}:" .
+ "$key{'SIZE'}:$key{'OPTIONS'}";
+ push(@storsort, $key{'OPTIONS'});
+ }
+ }
+ }
+ return 1;
+}
+
+sub print_cycbuff_head {
+ my ($buffpath) = $_[0];
+ my ($name, $len, $free, $update, $cyclenum, $oldart) =
+ &get_cycbuff_info($buffpath);
+
+ if ($use_syslog) {
+ ($name) = split(/\s/, $name);
+ $name =~ s/\0//g;
+ syslog ('notice', '%s Buffer %s, len: %.2f Mbytes, used: %.2f Mbytes (%4.1f%%) %3d cycles',
+ $logline, $name, $len / (1024 * 1024),
+ Math::BigFloat->new ($free) / (1024 * 1024),
+ 100 * Math::BigFloat->new ($free) / $len, $cyclenum);
+ return 0;
+ }
+
+ $name =~ s/\0//g;
+ print " Buffer $name, size: ", &human_readable($len, 4);
+ print ", position: ", &human_readable($free, 4);
+ printf(" %.2f cycles\n", $cyclenum + Math::BigFloat->new ($free) / $len);
+ my ($when, $ago) = &make_time($update);
+ print " Newest: $when, $ago ago\n";
+
+ if ($opt_a) {
+ my ($when, $ago) = &make_time($oldart);
+ print " Oldest: $when, $ago ago\n";
+ }
+}
+
+sub make_time {
+ my ($t) = @_;
+ my (@ret);
+
+ my ($sec,$min,$hour,$mday,$mon,$year) =
+ (localtime($t))[0..5];
+ push (@ret, sprintf("%04d-%02d-%02d %2d:%02d:%02d",
+ $year + 1900, $mon + 1, $mday, $hour, $min, $sec));
+ $t = time - $t;
+
+ $mday = int($t/86400); $t = $t % 86400;
+ $hour = int($t/3600); $t = $t % 3600;
+ $min = int($t/60); $t = $t % 60;
+
+ push (@ret, sprintf("%4d days, %2d:%02d:%02d",
+ $mday, $hour, $min, $t));
+ return @ret;
+}
+
+sub human_readable {
+ my ($val, $digits) = @_;
+ $val =~ s/\+//;
+
+ my @name = ("kBytes", "MBytes", "GBytes", "TBytes");
+ my $base = 1024;
+ my $factor = 1024;
+
+ my $unit = -1;
+ my $oldscaled = Math::BigFloat->new ($val) / $base;
+ my $scaled = $oldscaled;
+ while ( ( int($scaled) > 0 ) && ( $unit < $#name ) ) {
+ $oldscaled = $scaled;
+ $scaled /= $factor;
+ $unit++;
+ }
+ $scaled = $oldscaled;
+ my $predigits = length (int($scaled));
+ my $postdigits = $digits - $predigits - 1;
+ $postdigits = 0 if $postdigits < 0;
+ ++$digits;
+
+ return sprintf ("%${digits}.${postdigits}f %s", $scaled, $name[$unit]);
+}
+
+sub mrtg {
+ my $buffer = shift;
+ # print "Buffer = $buff{$buffer}\n";
+ @info = &get_cycbuff_info($buff{$buffer});
+ print "$info[1]\n";
+ print "$info[2]\n";
+ print "$info[4]\n";
+ print "$info[0]\n";
+ exit(0);
+}
+
+sub mrtg_config {
+ print "Sub MRTG-CONFIG\n";
+ foreach $class (sort(keys(%class))) {
+ print "##\n## Class : $class\n## Wildmat: $stor{$class}\n##\n\n";
+ foreach $buffer (split /\,/,$class{$class}) {
+ &mrtg_buffer($class,$buffer);
+ }
+ }
+ exit(0);
+}
+
+sub mrtg_buffer {
+ my ($class,$buffer) = @_;
+ #my ($name, $num, $buff, $size) = @_;
+ $tag = 'cnfs-' . $buffer;
+
+ print 'Target[', $tag, ']: `', "$inn::pathbin/cnfsstat -m ", $buffer, '`', "\n";
+ print 'MaxBytes[', $tag, ']: ', (&get_cycbuff_info($buff{$buffer}))[1], "\n";
+ print 'Title[', $tag, ']: ', "${buffer} Usage\n";
+ print 'Options[', $tag, ']: growright gauge', "\n";
+ print 'YLegend[', $tag, ']: ', "${buffer}\n";
+ print 'ShortLegend[', $tag, ']: MB', "\n";
+ print 'PageTop[', $tag, ']: ', "<H1>Usage of ${buffer}</H1>\n";
+ print "<BR><TT>$stor{$class}</TT>\n";
+ print "\n";
+ 1;
+}
+
+sub bigsysseek {
+ my($handle, $offset) = @_;
+
+ # $offset may be a bigint; and have a value that doesn't fit in a signed long.
+ # Even with largefiles enabled, perl will still truncate the argument to lseek64
+ # to 32 bits. So we seek multiple times, <2G at a time.
+
+ if($offset > 2147483647) {
+ # Since perl truncates the return value of lseek64 to 32 bits, it might
+ # see a successful return value as negative, and return FALSE (undef).
+ # So we must ignore the return value of sysseek and assume that it worked.
+
+ seek($handle, 0, 0);
+ while($offset > 2000000000) {
+ sysseek($handle, 2000000000, 1) || return 0;
+ $offset -= 2000000000;
+ }
+ sysseek($handle, $offset, 1) || return 0;
+ return 1;
+ } else {
+ return sysseek($handle, $offset, 0);
+ }
+}
+
+sub check_read_return {
+ my $result = shift;
+ die "read: $!\n" unless defined($result);
+ die "read reached eof\n" unless $result;
+ return $result;
+}
+
+sub get_cycbuff_info {
+ my($buffpath) = $_[0];
+
+ my($CNFSMASIZ)=8;
+ my($CNFSNASIZ)=16;
+ my($CNFSPASIZ)=64;
+ my($CNFSLASIZ)=16;
+ my($headerlength) = $CNFSMASIZ + $CNFSNASIZ + $CNFSPASIZ + (4 * $CNFSLASIZ);
+
+ my($buff, @entries, $e);
+ my($magic, $name, $path, $lena, $freea, $updatea, $cyclenuma);
+
+ if(! open(BUFF, "< $buffpath") ) {
+ print STDERR "Cannot open Cycbuff $buffpath ...\n";
+ exit(1);
+ }
+
+ $buff = "";
+ if(! read(BUFF, $buff, $headerlength) ) {
+ print STDERR "Cannot read $headerlength bytes from file $buffpath...\n";
+ exit(1);
+ }
+
+ ($magic, $name, $path, $lena, $freea, $updatea, $cyclenuma) =
+ unpack("a8 a16 a64 a16 a16 a16 a16", $buff);
+
+ if(!$magic) {
+ print STDERR "Error while unpacking header ...\n";
+ exit(1);
+ }
+
+ my($len) = bhex($lena);
+ my($free) = bhex($freea);
+ my($update) = hex($updatea);
+ my($cyclenum) = hex($cyclenuma) - 1;
+
+ if ($opt_a) {
+
+ my $pagesize = 16384;
+ my $minartoffset = int($len / (512 * 8)) + 512;
+ # Align upwards:
+ $minartoffset = ($minartoffset + $pagesize) & ~($pagesize - 1);
+
+ if ($cyclenum == 0 && $free == $minartoffset) {
+ # The cycbuff has no articles yet.
+ goto done;
+ }
+
+ # Don't loop endlessly, set rough upper bound
+ my $sentinel = $cyclenum == 0 ? $free : $len;
+ my $offset = $cyclenum == 0 ? $minartoffset : $free + $pagesize;
+
+ bigsysseek (BUFF, $offset) || die "sysseek: $!\n";
+ check_read_return (sysread (BUFF, $buff, $pagesize));
+ do {
+ check_read_return (sysread (BUFF, $chunk, $pagesize));
+
+ $buff .= $chunk;
+ while ($buff =~ /^message-id:\s+(<.*?>)/mi) {
+ $buff = $POSTMATCH;
+ $oldart = &lookup_age ($1);
+ next unless $oldart;
+
+ # Is the article newer than the last update?
+ if ($oldart >= $update) {
+ $update = $oldart;
+ } elsif ($oldart < $update - 60) {
+ goto done;
+ }
+ }
+ # Just in case we chopped Message-ID in two, use the end
+ # at the front in next iteration.
+ $buff = substr ($buff, -512);
+
+ } while ($sentinel -= $pagesize > 0);
+ }
+
+done:
+ close(BUFF);
+ return($name,$len,$free,$update,$cyclenum,$oldart);
+}
+
+sub lookup_age {
+ my ($msgid) = @_;
+
+ my $history = &safe_run("grephistory", "-l", $msgid);
+ if ($history =~ /\t(\d+)~/) {
+ return $1;
+ }
+ print " (Missing $msgid)\n";
+ return 0;
+}
+
+sub safe_run {
+ my $output = "";
+
+ my $pid = open(KID_TO_READ, "-|");
+ die "fork: $!\n" unless defined $pid;
+ if ($pid) {
+ while (<KID_TO_READ>) {
+ $output .= $_;
+ }
+ close(KID_TO_READ);
+ } else {
+ exec(@_) || die "can't exec $_[0]: $!";
+ # NOTREACHED
+ }
+ return $output;
+}
+
+# Hex to bigint conversion routine
+# bhex(HEXSTRING) returns BIGINT (with leading + chopped off)
+#
+# In most languages, unlimited size integers are done using string math
+# libraries usually called bigint. (Java, Perl, etc...)
+
+# Bigint's are really just strings.
+
+sub bhex {
+ my $hexValue = shift;
+ $hexValue =~ s/^0x//;
+
+ my $integerValue = new Math::BigInt '0';
+ for (my $i = 0; $i < length($hexValue); $i += 2) {
+ # Could be more efficient going at larger increments, but byte
+ # by byte is safer for the case of 9 byte values, 11 bytes, etc..
+
+ my $byte = substr($hexValue, $i, 2);
+ my $byteIntValue = hex($byte);
+
+ $integerValue = $integerValue * "256";
+ $integerValue = $integerValue + "$byteIntValue";
+ }
+
+ $integerValue =~ s/^\+//;
+ return $integerValue;
+}
--- /dev/null
+/* $Id: ctlinnd.c 6155 2003-01-19 19:58:25Z rra $
+**
+** Send control messages to the InterNetNews daemon.
+*/
+
+#include "config.h"
+#include "clibrary.h"
+#include <ctype.h>
+#include <errno.h>
+#include <sys/stat.h>
+
+#include "inn/innconf.h"
+#include "inn/messages.h"
+#include "inndcomm.h"
+#include "libinn.h"
+#include "paths.h"
+
+
+/*
+** Datatype for an entry in the command table.
+*/
+typedef struct _COMMAND {
+ const char *Command;
+ const char *Text;
+ int argc;
+ char Letter;
+ bool Glue;
+} COMMAND;
+
+
+static COMMAND Commands[] = {
+ { "addhist", "id arr exp post token...\tAdd history line",
+ 5, SC_ADDHIST, true },
+ { "allow", "reason...\t\t\tAllow remote connections",
+ 1, SC_ALLOW, true },
+ { "begin", "site\t\t\tStart newly-added site",
+ 1, SC_BEGIN, false },
+ { "cancel", "id\t\t\tCancel message locally",
+ 1, SC_CANCEL, false },
+ { "changegroup", "group rest\tChange mode of group",
+ 2, SC_CHANGEGROUP, false },
+ { "checkfile", "\t\t\tCheck syntax of newsfeeds file",
+ 0, SC_CHECKFILE, false },
+ { "drop", "site\t\t\tStop feeding site",
+ 1, SC_DROP, false },
+ { "feedinfo", "site\t\t\tPrint state of feed to site*",
+ 1, SC_FEEDINFO, false },
+#if defined(DO_TCL)
+ { "tcl", "flag\t\t\tEnable or disable Tcl filtering",
+ 1, SC_FILTER, false },
+#endif /* defined(DO_TCL) */
+ { "flush", "site\t\t\tFlush feed for site*",
+ 1, SC_FLUSH, false },
+ { "flushlogs", "\t\t\tFlush log files",
+ 0, SC_FLUSHLOGS, false },
+ { "go", "reason...\t\t\tRestart after pause or throttle",
+ 1, SC_GO, true },
+ { "hangup", "channel\t\tHangup specified incoming channel",
+ 1, SC_HANGUP, false },
+ { "logmode", "\t\t\t\tSend server mode to syslog",
+ 0, SC_LOGMODE, false },
+ { "mode", "\t\t\t\tPrint operating mode",
+ 0, SC_MODE, false },
+ { "name", "nnn\t\t\tPrint name of specified channel*",
+ 1, SC_NAME, false },
+ { "newgroup", "group rest creator\tCreate new group",
+ 3, SC_NEWGROUP, false },
+ { "param", "letter value\t\tChange command-line parameters",
+ 2, SC_PARAM, false },
+ { "pause", "reason...\t\tShort-term pause in accepting articles",
+ 1, SC_PAUSE, true },
+#if defined(DO_PERL)
+ { "perl", "flag\t\t\tEnable or disable Perl filtering",
+ 1, SC_PERL, false },
+#endif /* defined(DO_PERL) */
+#if defined(DO_PYTHON)
+ { "python", "flag\t\t\tEnable or disable Python filtering",
+ 1, SC_PYTHON, false },
+#endif /* (DO_PYTHON) */
+ { "readers", "flag text...\t\tEnable or disable newsreading",
+ 2, SC_READERS, true },
+ { "reject", "reason...\t\t\tReject remote connections",
+ 1, SC_REJECT, true },
+ { "reload", "what reason...\t\tRe-read config files*",
+ 2, SC_RELOAD, true },
+ { "renumber", "group\t\tRenumber the active file*",
+ 1, SC_RENUMBER, false },
+ { "reserve", "reason...\t\tReserve the next pause or throttle",
+ 1, SC_RESERVE, true },
+ { "rmgroup", "group\t\t\tRemove named group",
+ 1, SC_RMGROUP, false },
+ { "send", "feed text...\t\tSend text to exploder feed",
+ 2, SC_SEND, true },
+ { "shutdown", "reason...\t\tShut down server",
+ 1, SC_SHUTDOWN, true },
+ { "stathist", "filename|off\t\tLog into filename some history stats",
+ 1, SC_STATHIST, false },
+ { "status", "interval|off\t\tTurn innd status generation on or off",
+ 1, SC_STATUS, false },
+ { "kill", "signal site\t\tSend signal to site's process",
+ 2, SC_SIGNAL, false },
+ { "throttle", "reason...\t\tStop accepting articles",
+ 1, SC_THROTTLE, true },
+ { "timer", "interval|off\t\tTurn performance monitoring on or off",
+ 1, SC_TIMER, false },
+ { "trace", "innd|#|nnrpd flag\tTurn tracing on or off",
+ 2, SC_TRACE, false },
+ { "xabort", "text...\t\tAbort the server",
+ 1, SC_XABORT, true },
+ { "lowmark", "filename\t\tReset active file low article marks",
+ 1, SC_LOWMARK, false },
+ { "renumberlow", "filename\t\tReset active file low article marks",
+ 1, SC_LOWMARK, false },
+ { "xexec", "path\t\t\tExec new server",
+ 1, SC_XEXEC, false }
+};
+
+\f
+
+/*
+** Print a help summary.
+*/
+static void
+Help(char *p)
+{
+ COMMAND *cp;
+
+ if (p == NULL) {
+ printf("Command summary:\n");
+ for (cp = Commands; cp < ARRAY_END(Commands); cp++)
+ printf(" %s %s\n", cp->Command, cp->Text);
+ printf("* Empty string means all sites/groups/etc.\n");
+ printf("... All trailing words are glued together.\n");
+ exit(0);
+ }
+ for (cp = Commands; cp < ARRAY_END(Commands); cp++)
+ if (strcmp(p, cp->Command) == 0) {
+ printf("Command usage:\n");
+ printf(" %s %s\n", cp->Command, cp->Text);
+ exit(0);
+ }
+ printf("No such command.\n");
+ exit(0);
+}
+
+
+/*
+** Print a command-usage message and exit.
+*/
+static void
+WrongArgs(COMMAND *cp)
+{
+ printf("Wrong number of arguments -- usage:\n");
+ printf(" %s %s\n", cp->Command, cp->Text);
+ exit(1);
+}
+
+
+/*
+** Print an error message and exit.
+*/
+static void
+Failed(const char *p)
+{
+ if (ICCfailure)
+ syswarn("cannot %s (%s failure)", p, ICCfailure);
+ else
+ syswarn("cannot %s", p);
+ ICCclose();
+ exit(1);
+}
+
+
+/*
+** Print an error reporting incorrect usage.
+*/
+static void
+Usage(const char *what)
+{
+ fprintf(stderr, "Usage error (%s) -- try -h for help.\n", what);
+ exit(1);
+}
+
+
+int main(int ac, char *av[])
+{
+ static char Y[] = "y";
+ static char EMPTY[] = "";
+ COMMAND *cp;
+ char *p;
+ int i;
+ bool Silent;
+ bool NeedHelp;
+ char *reply;
+ char *new;
+ int length;
+ char *nv[4];
+ struct stat Sb;
+ char buff[SMBUF];
+
+ /* First thing, set up our identity. */
+ message_program_name = "ctlinnd";
+
+ /* Set defaults. */
+ if (!innconf_read(NULL))
+ exit(1);
+ Silent = false;
+ NeedHelp = false;
+ ICCsettimeout(CTLINND_TIMEOUT);
+
+ /* Parse JCL. */
+ while ((i = getopt(ac, av, "hst:")) != EOF)
+ switch (i) {
+ default:
+ Usage("bad flags");
+ /* NOTREACHED */
+ case 'h': /* Get help */
+ NeedHelp = true;
+ break;
+ case 's': /* Silent -- no output */
+ Silent = true;
+ break;
+ case 't': /* Time to wait for reply */
+ ICCsettimeout(atoi(optarg));
+ break;
+ }
+ ac -= optind;
+ av += optind;
+ if (NeedHelp)
+ Help(av[0]);
+ if (ac == 0)
+ Usage("missing command");
+
+ /* Look up the command word and move to the arguments. */
+ if (strcmp(av[0], "help") == 0)
+ Help(av[1]);
+ for (cp = Commands; cp < ARRAY_END(Commands); cp++)
+ if (strcmp(av[0], cp->Command) == 0)
+ break;
+ if (cp == ARRAY_END(Commands))
+ Usage("unknown command");
+ ac--;
+ av++;
+
+ /* Check argument count. */
+ if (cp->Letter == SC_NEWGROUP) {
+ /* Newgroup command has defaults. */
+ switch (ac) {
+ default:
+ WrongArgs(cp);
+ /* NOTREACHED */
+ case 1:
+ nv[0] = av[0];
+ nv[1] = Y;
+ nv[2] = EMPTY;
+ nv[3] = NULL;
+ av = nv;
+ break;
+ case 2:
+ nv[0] = av[0];
+ nv[1] = av[1];
+ nv[2] = EMPTY;
+ nv[3] = NULL;
+ av = nv;
+ break;
+ case 3:
+ break;
+ }
+ ac = 3;
+ }
+ else if (ac > cp->argc && cp->Glue) {
+ /* Glue any extra words together. */
+ for (length = 0, i = cp->argc - 1; (p = av[i++]) != NULL; )
+ length += strlen(p) + 1;
+ new = xmalloc(length);
+ *new = '\0';
+ for (i = cp->argc - 1; av[i]; i++) {
+ if (i >= cp->argc)
+ strlcat(new, " ", length);
+ strlcat(new, av[i], length);
+ }
+ av[cp->argc - 1] = new;
+ av[cp->argc] = NULL;
+ }
+ else if (ac != cp->argc)
+ /* All other commands must have the right number of arguments. */
+ WrongArgs(cp);
+
+ /* For newgroup and changegroup, make sure the mode is valid. */
+ if (cp->Letter == SC_NEWGROUP || cp->Letter == SC_CHANGEGROUP) {
+ switch (av[1][0]) {
+ default:
+ Usage("Bad group mode");
+ /* NOTREACHED */
+ case NF_FLAG_ALIAS:
+ case NF_FLAG_EXCLUDED:
+ case NF_FLAG_MODERATED:
+ case NF_FLAG_OK:
+ case NF_FLAG_NOLOCAL:
+ case NF_FLAG_IGNORE:
+ break;
+ }
+ }
+
+ /* Make sure there are no separators in the parameters. */
+ for (i = 0; (p = av[i++]) != NULL; )
+ if (strchr(p, SC_SEP) != NULL)
+ die("illegal character \\%03o in %s", SC_SEP, p);
+
+ /* Do the real work. */
+ if (ICCopen() < 0)
+ Failed("setup communication");
+ i = ICCcommand(cp->Letter, (const char **) av, &reply);
+ if (i < 0) {
+ i = errno;
+ p = concatpath(innconf->pathrun, _PATH_SERVERPID);
+ if (stat(p, &Sb) < 0)
+ warn("no innd.pid file; did server die?");
+ free(p);
+ snprintf(buff, sizeof(buff), "send \"%s\" command", cp->Command);
+ errno = i;
+ Failed(buff);
+ }
+
+ if (reply) {
+ /* Skip "<exitcode><space>" part of reply. */
+ for (p = reply; *p && CTYPE(isdigit, *p); p++)
+ continue;
+ while (*p && ISWHITE(*p))
+ p++;
+ if (i != 0)
+ warn("%s", p);
+ else if (!Silent)
+ printf("%s\n", p);
+ }
+
+ if (ICCclose() < 0)
+ Failed("end communication");
+
+ exit(i);
+ /* NOTREACHED */
+}
--- /dev/null
+/*
+** Decode seven-bit input into full binary output.
+** From @(#)decode.c 1.3 5/15/85, distributed with B2.11 News.
+**
+** Collect runs of 12 seven-bit characters. Combine them in pairs to
+** make six 13-bit characters. Extract the top bit of each pair to make
+** a 13th six-bit character, and split the remaining six 12-bit
+** characters to form 12 six-bit characters. Collect four six-bit
+** characters and convert it to three eight-bit characters.
+**
+** Got that? All the remaining work in this program is to get the
+** ending conditions right.
+*/
+
+#include "config.h"
+#include "clibrary.h"
+
+#include "inn/messages.h"
+
+
+/*
+** These characters can't appear in normal output, so we use them to
+** mark that the data that follows is the terminator. The character
+** immediately following this pair is the length of the terminator (which
+** otherwise might be indeterminable)
+*/
+#define ENDMARK1 ((90 * 91 + 90) / 91)
+#define ENDMARK2 ((90 * 91 + 90) % 91)
+
+
+static char Buffer[4];
+static int count;
+
+
+static void
+pack6(int n, int last)
+{
+ char *q;
+ int i;
+ char b3[3];
+
+ i = 3;
+ if (last && (i = Buffer[n - 1]) >= 3) {
+ /* Do the best we can. */
+ warn("badly-terminated file");
+ i = 3;
+ }
+
+ b3[0] = (Buffer[0] << 2) | ((Buffer[1] >> 4) & 0x03);
+ b3[1] = (Buffer[1] << 4) | ((Buffer[2] >> 2) & 0x0F);
+ b3[2] = (Buffer[2] << 6) | ( Buffer[3] & 0x3F);
+ for (q = b3; --i >= 0; )
+ putchar(*q++);
+}
+
+
+static void
+pack12(char *p, int n, int last)
+{
+ char *q;
+ int c13;
+ int c;
+ int i;
+ char b13[13];
+ char b3[3];
+
+ for (q = b13, c13 = 0, i = 0; i < n; i += 2) {
+ c = *p++ * 91;
+ c += *p++;
+ c13 <<= 1;
+ if (c & (1 << 12))
+ c13 |= 1;
+ *q++ = (c >> 6) & 0x3F;
+ *q++ = c & 0x3F;
+ }
+ *q++ = (char)c13;
+ if (last)
+ q = &b13[last];
+
+ for (p = b13, n = q - p, i = count, q = &Buffer[count]; --n > 0; ) {
+ *q++ = *p++;
+ if (++i == 4) {
+ /* Inline expansion of pack6. */
+ b3[0] = (Buffer[0] << 2) | ((Buffer[1] >> 4) & 0x03);
+ b3[1] = (Buffer[1] << 4) | ((Buffer[2] >> 2) & 0x0F);
+ b3[2] = (Buffer[2] << 6) | ( Buffer[3] & 0x3F);
+ putchar(b3[0]);
+ putchar(b3[1]);
+ putchar(b3[2]);
+ i = 0;
+ q = Buffer;
+ }
+ }
+
+ /* The last octet. */
+ *q++ = *p++;
+ i++;
+
+ if (last || i == 4) {
+ pack6(i, last);
+ i = 0;
+ }
+
+ count = i;
+}
+
+
+int
+main(void)
+{
+ int c;
+ char *p;
+ int i;
+ int first;
+ int cnt;
+ char *base;
+ char b12[12];
+ char c12[12];
+
+ message_program_name = "decode";
+
+ base = p = b12;
+ for (i = 12, cnt = 0, first = 1; (c = getchar()) != EOF; ) {
+ if (c < ' ' || c >= ' ' + 91)
+ die("bad data");
+ if (i == 10 && p[-1] == ENDMARK1 && p[-2] == ENDMARK2) {
+ cnt = c - ' ';
+ i = 12;
+ p -= 2;
+ continue;
+ }
+ *p++ = c - ' ';
+ if (--i == 0) {
+ if (p == &b12[12]) {
+ if (!first)
+ pack12(c12, 12, 0);
+ else
+ first = 0;
+ base = p = c12;
+ }
+ else {
+ pack12(b12, 12, 0);
+ base = p = b12;
+ }
+ i = 12;
+ }
+ }
+
+ if (base == b12) {
+ if (!first)
+ pack12(c12, 12, i == 12 ? cnt : 0);
+ }
+ else
+ pack12(b12, 12, i == 12 ? cnt : 0);
+
+ if (i != 12)
+ pack12(base, 12 - i, cnt);
+
+ exit(0);
+ /* NOTREACHED */
+}
--- /dev/null
+/* $Id: encode.c 6119 2003-01-13 07:59:39Z rra $
+**
+** Produce a seven-bit printable encoding of stdin on stdout.
+** From @(#)encode.c 1.3 5/15/85, distributed with B2.11 News.
+**
+** The encoding uses characters from 0x20 (' ') through 0x7A ('z').
+** (That fits nicely into the UUCP 'f' protocol by Piet Beertema.) First,
+** expand three eight-bit charcters into four six-bit ones. Collect
+** until we have 13, and spread the last one over the first 12, so that
+** we have 12 6.5-bit characters. Since there are very few half-bit
+** machines, collect them into pairs, making six 13-bit characters. We
+** can do this as A * 91 + B where A and B are less then 91 after we add
+** 0x20 to make it printable.
+**
+** And if you thought that was unclear, then we won't even get into the
+** terminating conditions!
+*/
+
+#include "config.h"
+#include "clibrary.h"
+
+
+/*
+** These characters can't appear in normal output, so we use them to
+** mark that the data that follows is the terminator. The character
+** immediately following this pair is the length of the terminator (which
+** otherwise might be indeterminable)
+*/
+#define ENDMARK1 ((90 * 91 + 90) / 91 + ' ')
+#define ENDMARK2 ((90 * 91 + 90) % 91 + ' ')
+
+static char Buffer[13];
+static int Count;
+
+
+static void
+dumpcode(char *p, int n)
+{
+ int last;
+ int c;
+
+ if (n == 13) {
+ n--;
+ last = p[12];
+ }
+ else if (n & 1)
+ last = 1 << (6 - 1);
+ else
+ last = 0;
+
+ for (; n > 0; n -= 2) {
+ c = *p++ << 6;
+ c |= *p++;
+ if (last & (1 << (6 - 1)))
+ c |= (1 << 12);
+ last <<= 1;
+
+ putchar((c / 91) + ' ');
+ putchar((c % 91) + ' ');
+ }
+}
+
+static void
+flushout(void)
+{
+ putchar(ENDMARK1);
+ putchar(ENDMARK2);
+ putchar(Count + ' ');
+ dumpcode(Buffer, Count);
+}
+
+
+static void
+encode(char *dest, int n)
+{
+ char *p;
+ int i;
+ int j;
+ char b4[4];
+
+ b4[0] = (dest[0] >> 2) & 0x3F;
+ b4[1] = ((dest[0] & 0x03) << 4) | ((dest[1] >> 4) & 0x0F);
+ b4[2] = ((dest[1] & 0x0F) << 2) | ((dest[2] >> 6) & 0x03);
+ b4[3] = (char)(n == 3 ? dest[2] & 0x3F : n);
+
+ for (p = b4, i = Count, dest = &Buffer[i], j = 4; --j >= 0; i++) {
+ if (i == 13) {
+ dumpcode(Buffer, 13);
+ dest = Buffer;
+ i = 0;
+ }
+ *dest++ = *p++;
+ }
+ Count = i;
+}
+
+
+int
+main(void)
+{
+ char *p;
+ int c;
+ char b3[3];
+
+ for (p = b3; (c = getchar()) != EOF; ) {
+ *p++ = (char)c;
+ if (p == &b3[3]) {
+ encode(b3, 3);
+ p = b3;
+ }
+ }
+ encode(b3, (int)(p - b3));
+ flushout();
+ exit(0);
+ /* NOTREACHED */
+}
--- /dev/null
+/* $Id: feedone.c 6135 2003-01-19 01:15:40Z rra $
+**
+** Connect to the NNTP server and feed one article.
+*/
+
+#include "config.h"
+#include "clibrary.h"
+#include <errno.h>
+
+#include "inn/messages.h"
+#include "libinn.h"
+#include "nntp.h"
+
+
+static FILE *FromServer;
+static FILE *ToServer;
+static int Tracing;
+
+
+/*
+** Read a line from the server or die trying.
+*/
+static void
+GetFromServer(buff, size, text)
+ char *buff;
+ int size;
+ char *text;
+{
+ if (fgets(buff, size, FromServer) == NULL)
+ sysdie("s", text);
+ if (Tracing)
+ printf("S: %s", buff);
+}
+
+
+/*
+** Flush a stdio FILE; exit if there are any errors.
+*/
+static void
+SafeFlush(F)
+ FILE *F;
+{
+ if (fflush(F) == EOF || ferror(F))
+ sysdie("cannot send text to server");
+}
+
+
+static void
+SendQuit(x)
+ int x;
+{
+ char buff[BUFSIZ];
+
+ /* Close up. */
+ fprintf(ToServer, "quit\r\n");
+ SafeFlush(ToServer);
+ fclose(ToServer);
+ GetFromServer(buff, sizeof buff, "cannot get reply to quit");
+ exit(x);
+}
+
+
+static void
+Usage()
+{
+ fprintf(stderr, "Usage: feedone [-r|-m msgid] [-p] [-t] articlefile\n");
+ exit(1);
+}
+
+
+int
+main(ac, av)
+ int ac;
+ char *av[];
+{
+ static char MESGIDHDR[] = "Message-ID:";
+ int i;
+ FILE *F;
+ char buff[BUFSIZ];
+ char *mesgid = NULL;
+ size_t length;
+ char *p;
+ char *q;
+ bool PostMode;
+
+ /* Set defaults. */
+ mesgid[0] = '\0';
+ PostMode = false;
+ message_program_name = "feedone";
+
+ /* Parse JCL. */
+ while ((i = getopt(ac, av, "m:prt")) != EOF)
+ switch (i) {
+ default:
+ Usage();
+ /* NOTREACHED */
+ case 'm': /* Specified Message-ID */
+ if (*optarg == '<')
+ mesgid = optarg;
+ else
+ mesgid = concat("<", optarg, ">", (char *) 0);
+ break;
+ case 'p': /* Use Post, not ihave */
+ PostMode = true;
+ break;
+ case 'r': /* Random Message-ID */
+ length = snprintf(NULL, 0, "<%ld@%ld>", (long) getpid(),
+ (long) time(NULL));
+ mesgid = xmalloc(length + 1);
+ snprintf(mesgid, length, "<%ld@%ld>", (long) getpid(),
+ (long) time(NULL));
+ break;
+ case 't':
+ Tracing = true;
+ break;
+ }
+ ac -= optind;
+ av += optind;
+
+ /* One argument; the input filename. */
+ if (ac != 1)
+ Usage();
+ if ((F = fopen(av[0], "r")) == NULL)
+ sysdie("cannot open input");
+
+ /* Scan for the message-id. */
+ if (mesgid == NULL) {
+ while (fgets(buff, sizeof buff, F) != NULL)
+ if (strncasecmp(buff, MESGIDHDR, strlen(MESGIDHDR)) == 0) {
+ if ((p = strchr(buff, '<')) == NULL
+ || (q = strchr(p, '>')) == NULL)
+ die("bad message ID line");
+ q[1] = '\0';
+ mesgid = xstrdup(p);
+ break;
+ }
+ if (mesgid == NULL)
+ die("no message ID");
+ }
+
+ /* Connect to the server. */
+ if (NNTPremoteopen(NNTP_PORT, &FromServer, &ToServer, buff) < 0
+ || FromServer == NULL
+ || ToServer == NULL) {
+ if (buff[0])
+ warn("server says: %s", buff);
+ sysdie("cannot connect to server");
+ }
+
+ /* Does the server want this article? */
+ if (PostMode) {
+ fprintf(ToServer, "post\r\n");
+ i = NNTP_START_POST_VAL;
+ }
+ else {
+ fprintf(ToServer, "ihave %s\r\n", mesgid);
+ i = NNTP_SENDIT_VAL;
+ }
+ SafeFlush(ToServer);
+ GetFromServer(buff, sizeof buff, "cannot offer article to server");
+ if (atoi(buff) != i) {
+ warn("server doesn't want the article: %s", buff);
+ SendQuit(1);
+ }
+
+ /* Send the file over. */
+ fseeko(F, 0, SEEK_SET);
+ while (fgets(buff, sizeof buff, F) != NULL) {
+ if (strncasecmp(buff, MESGIDHDR, strlen(MESGIDHDR)) == 0) {
+ fprintf(ToServer, "%s %s\r\n", MESGIDHDR, mesgid);
+ continue;
+ }
+ if ((p = strchr(buff, '\n')) != NULL)
+ *p = '\0';
+ fprintf(ToServer, buff[0] == '.' ? ".%s\r\n" : "%s\r\n",
+ buff);
+ SafeFlush(ToServer);
+ }
+ fprintf(ToServer, ".\r\n");
+ SafeFlush(ToServer);
+ fclose(F);
+
+ /* How did the server respond? */
+ GetFromServer(buff, sizeof buff,
+ "no reply from server after sending the article");
+ i = PostMode ? NNTP_POSTEDOK_VAL : NNTP_TOOKIT_VAL;
+ if (atoi(buff) != i)
+ sysdie("cannot send article to the server: %s", buff);
+
+ SendQuit(0);
+ /* NOTREACHED */
+}
--- /dev/null
+/* $Id: getlist.c 6135 2003-01-19 01:15:40Z rra $
+**
+** Get a file list from an NNTP server.
+*/
+#include "config.h"
+#include "clibrary.h"
+#include <errno.h>
+#include <syslog.h>
+
+#include "inn/innconf.h"
+#include "inn/messages.h"
+#include "inn/qio.h"
+#include "libinn.h"
+#include "paths.h"
+
+
+/*
+** Print usage message and exit.
+*/
+static void
+Usage(void)
+{
+ fprintf(stderr, "Usage: getlist [-p port] [-h host] [-A] [type [pat [groups]]\n");
+ exit(1);
+}
+
+
+int
+main(int ac, char *av[])
+{
+ FILE *active;
+ FILE *FromServer;
+ FILE *ToServer;
+ QIOSTATE *qp;
+ char *field4;
+ char *types;
+ char *host;
+ char *line;
+ const char *list;
+ char *p;
+ char *pattern;
+ char buff[512 + 1];
+ int port;
+ int authinfo;
+ int i;
+
+ /* First thing, set up our identity. */
+ message_program_name = "getlist";
+
+ if (!innconf_read(NULL))
+ exit(1);
+
+ /* Set defaults. */
+ host = NULL;
+ pattern = NULL;
+ types = NULL;
+ port = NNTP_PORT;
+ authinfo = 0;
+
+ /* Parse JCL. */
+ while ((i = getopt(ac, av, "Ah:p:")) != EOF)
+ switch (i) {
+ default:
+ Usage();
+ /* NOTREACHED */
+ case 'A':
+ authinfo = 1;
+ break;
+ case 'h':
+ host = optarg;
+ break;
+ case 'p':
+ port = atoi(optarg);
+ if (port <= 0)
+ die("illegal value for -p option");
+ break;
+ }
+ ac -= optind;
+ av += optind;
+
+ /* Parse parameters. */
+ switch (ac) {
+ default:
+ Usage();
+ /* NOTREACHED */
+ case 0:
+ case 1:
+ break;
+ case 2:
+ pattern = av[1];
+ break;
+ case 3:
+ pattern = av[1];
+ types = av[2];
+ break;
+ }
+ if (av[0] == NULL)
+ list = "active";
+ else {
+ list = av[0];
+ if (strcmp(list, "active") != 0 && types != NULL)
+ Usage();
+ if (strcmp(list, "active") != 0 && strcmp(list, "newsgroups") != 0
+ && pattern != NULL)
+ Usage();
+ }
+
+ /* Open a connection to the server. */
+ if (host == NULL && (host = innconf->server) == NULL)
+ sysdie("cannot get server name");
+ buff[0] = '\0';
+ if (NNTPconnect(host, port, &FromServer, &ToServer, buff) < 0)
+ die("cannot connect to server: %s", buff[0] ? buff : strerror(errno));
+ if (authinfo && NNTPsendpassword(host, FromServer, ToServer) < 0)
+ die("cannot authenticate to server");
+
+ /* Get the data from the server. */
+ active = CAlistopen(FromServer, ToServer,
+ (strcmp(list, "active") == 0) ? NULL : list);
+ if (active == NULL)
+ sysdie("cannot retrieve data");
+
+ /* Set up to read it quickly. */
+ if ((qp = QIOfdopen((int)fileno(active))) == NULL)
+ sysdie("cannot read temporary file");
+
+ /* Scan server's output, displaying appropriate lines. */
+ i = 1;
+ while ((line = QIOread(qp)) != NULL) {
+ i++;
+
+ /* No pattern means print all. */
+ if (pattern == NULL) {
+ printf("%s\n", line);
+ continue;
+ }
+
+ /* Get the group name, see if it's one we want. */
+ if ((p = strchr(line, ' ')) == NULL) {
+ warn("line %d is malformed", i);
+ continue;
+ }
+ *p = '\0';
+ if (!uwildmat(line, pattern))
+ continue;
+ *p = ' ';
+
+ /* If no group types, we want them all. */
+ if (types == NULL) {
+ printf("%s\n", line);
+ continue;
+ }
+
+ /* Find the fourth field. */
+ if ((p = strchr(p + 1, ' ')) == NULL) {
+ warn("line %d (field 2) is malformed", i);
+ continue;
+ }
+ if ((p = strchr(p + 1, ' ')) == NULL) {
+ warn("line %d (field 3) is malformed", i);
+ continue;
+ }
+ field4 = p + 1;
+ if ((p = strchr(field4, ' ')) != NULL) {
+ warn("line %d has more than 4 fields", i);
+ continue;
+ }
+
+ /* Is this the type of line we want? */
+ if (strchr(types, field4[0]) != NULL)
+ printf("%s\n", line);
+ }
+
+ /* Determine why we stopped */
+ if (QIOerror(qp)) {
+ syswarn("cannot read temporary file at line %d", i);
+ i = 1;
+ }
+ else if (QIOtoolong(qp)) {
+ warn("line %d is too long", i);
+ i = i;
+ }
+ else
+ i = 0;
+
+ /* All done. */
+ CAclose();
+ fprintf(ToServer, "quit\r\n");
+ fclose(ToServer);
+ fgets(buff, sizeof buff, FromServer);
+ fclose(FromServer);
+ exit(i);
+ /* NOTREACHED */
+}
--- /dev/null
+/* $Id: inews.c 7769 2008-04-13 08:11:41Z iulius $
+**
+** Send an article (prepared by someone on the local site) to the
+** master news server.
+*/
+
+#include "config.h"
+#include "clibrary.h"
+#include "portable/time.h"
+#include <ctype.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <grp.h>
+#include <pwd.h>
+#include <sys/stat.h>
+
+#include "inn/innconf.h"
+#include "inn/messages.h"
+#include "libinn.h"
+#include "nntp.h"
+#include "paths.h"
+
+/* Signature handling. The separator will be appended before the signature,
+ and at most SIG_MAXLINES will be appended. */
+#define SIG_MAXLINES 4
+#define SIG_SEPARATOR "-- \n"
+
+#define FLUSH_ERROR(F) (fflush((F)) == EOF || ferror((F)))
+#define LPAREN '(' /* For vi :-) */
+#define HEADER_DELTA 20
+#define GECOSTERM(c) \
+ ((c) == ',' || (c) == ';' || (c) == ':' || (c) == LPAREN)
+#define HEADER_STRLEN 998
+
+typedef enum _HEADERTYPE {
+ HTobs,
+ HTreq,
+ HTstd
+} HEADERTYPE;
+
+typedef struct _HEADER {
+ const char *Name;
+ bool CanSet;
+ HEADERTYPE Type;
+ int Size;
+ char *Value;
+} HEADER;
+
+static bool Dump;
+static bool Revoked;
+static bool Spooling;
+static char **OtherHeaders;
+static char SIGSEP[] = SIG_SEPARATOR;
+static FILE *FromServer;
+static FILE *ToServer;
+static int OtherCount;
+static int OtherSize;
+static const char *Exclusions = "";
+static const char * const BadDistribs[] = {
+ BAD_DISTRIBS
+};
+
+static HEADER Table[] = {
+ /* Name Canset Type */
+ { "Path", true, HTstd, 0, NULL },
+#define _path 0
+ { "From", true, HTstd, 0, NULL },
+#define _from 1
+ { "Newsgroups", true, HTreq, 0, NULL },
+#define _newsgroups 2
+ { "Subject", true, HTreq, 0, NULL },
+#define _subject 3
+ { "Control", true, HTstd, 0, NULL },
+#define _control 4
+ { "Supersedes", true, HTstd, 0, NULL },
+#define _supersedes 5
+ { "Followup-To", true, HTstd, 0, NULL },
+#define _followupto 6
+ { "Date", true, HTstd, 0, NULL },
+#define _date 7
+ { "Organization", true, HTstd, 0, NULL },
+#define _organization 8
+ { "Lines", true, HTstd, 0, NULL },
+#define _lines 9
+ { "Sender", true, HTstd, 0, NULL },
+#define _sender 10
+ { "Approved", true, HTstd, 0, NULL },
+#define _approved 11
+ { "Distribution", true, HTstd, 0, NULL },
+#define _distribution 12
+ { "Expires", true, HTstd, 0, NULL },
+#define _expires 13
+ { "Message-ID", true, HTstd, 0, NULL },
+#define _messageid 14
+ { "References", true, HTstd, 0, NULL },
+#define _references 15
+ { "Reply-To", true, HTstd, 0, NULL },
+#define _replyto 16
+ { "Also-Control", true, HTstd, 0, NULL },
+#define _alsocontrol 17
+ { "Xref", false, HTstd, 0, NULL },
+ { "Summary", true, HTstd, 0, NULL },
+ { "Keywords", true, HTstd, 0, NULL },
+ { "Date-Received", false, HTobs, 0, NULL },
+ { "Received", false, HTobs, 0, NULL },
+ { "Posted", false, HTobs, 0, NULL },
+ { "Posting-Version", false, HTobs, 0, NULL },
+ { "Relay-Version", false, HTobs, 0, NULL },
+};
+
+#define HDR(_x) (Table[(_x)].Value)
+
+\f
+
+/*
+** Send the server a quit message, wait for a reply.
+*/
+static void
+QuitServer(int x)
+{
+ char buff[HEADER_STRLEN];
+ char *p;
+
+ if (Spooling)
+ exit(x);
+ if (x)
+ warn("article not posted");
+ fprintf(ToServer, "quit\r\n");
+ if (FLUSH_ERROR(ToServer))
+ sysdie("cannot send quit to server");
+ if (fgets(buff, sizeof buff, FromServer) == NULL)
+ sysdie("warning: server did not reply to quit");
+ if ((p = strchr(buff, '\r')) != NULL)
+ *p = '\0';
+ if ((p = strchr(buff, '\n')) != NULL)
+ *p = '\0';
+ if (atoi(buff) != NNTP_GOODBYE_ACK_VAL)
+ die("server did not reply to quit properly: %s", buff);
+ fclose(FromServer);
+ fclose(ToServer);
+ exit(x);
+}
+
+
+/*
+** Failure handler, called by die. Calls QuitServer to cleanly shut down the
+** connection with the remote server before exiting.
+*/
+static int
+fatal_cleanup(void)
+{
+ /* Don't recurse. */
+ message_fatal_cleanup = NULL;
+
+ /* QuitServer does all the work. */
+ QuitServer(1);
+ return 1;
+}
+
+
+/*
+** Flush a stdio FILE; exit if there are any errors.
+*/
+static void
+SafeFlush(FILE *F)
+{
+ if (FLUSH_ERROR(F))
+ sysdie("cannot send text to server");
+}
+
+
+/*
+** Trim trailing spaces, return pointer to first non-space char.
+*/
+static char *
+TrimSpaces(char *p)
+{
+ char *start;
+
+ for (start = p; ISWHITE(*start); start++)
+ continue;
+ for (p = start + strlen(start); p > start && CTYPE(isspace, p[-1]); )
+ *--p = '\0';
+ return start;
+}
+
+
+/*
+** Mark the end of the header starting at p, and return a pointer
+** to the start of the next one. Handles continuations.
+*/
+static char *
+NextHeader(char *p)
+{
+ for ( ; ; p++) {
+ if ((p = strchr(p, '\n')) == NULL)
+ die("article is all headers");
+ if (!ISWHITE(p[1])) {
+ *p = '\0';
+ return p + 1;
+ }
+ }
+}
+
+
+/*
+** Strip any headers off the article and dump them into the table.
+*/
+static char *
+StripOffHeaders(char *article)
+{
+ char *p;
+ char *q;
+ HEADER *hp;
+ char c;
+ int i;
+
+ /* Set up the other headers list. */
+ OtherSize = HEADER_DELTA;
+ OtherHeaders = xmalloc(OtherSize * sizeof(char *));
+ OtherCount = 0;
+
+ /* Scan through buffer, a header at a time. */
+ for (i = 0, p = article; ; i++) {
+
+ if ((q = strchr(p, ':')) == NULL)
+ die("no colon in header line \"%.30s...\"", p);
+ if (q[1] == '\n' && !ISWHITE(q[2])) {
+ /* Empty header; ignore this one, get next line. */
+ p = NextHeader(p);
+ if (*p == '\n')
+ break;
+ }
+
+ if (q[1] != '\0' && !ISWHITE(q[1])) {
+ if ((q = strchr(q, '\n')) != NULL)
+ *q = '\0';
+ die("no space after colon in \"%.30s...\"", p);
+ }
+
+ /* See if it's a known header. */
+ c = CTYPE(islower, *p) ? toupper(*p) : *p;
+ for (hp = Table; hp < ARRAY_END(Table); hp++)
+ if (c == hp->Name[0]
+ && p[hp->Size] == ':'
+ && ISWHITE(p[hp->Size + 1])
+ && strncasecmp(p, hp->Name, hp->Size) == 0) {
+ if (hp->Type == HTobs)
+ die("obsolete header: %s", hp->Name);
+ if (hp->Value)
+ die("duplicate header: %s", hp->Name);
+ for (q = &p[hp->Size + 1]; ISWHITE(*q); q++)
+ continue;
+ hp->Value = q;
+ break;
+ }
+
+ /* Too many headers? */
+ if (++i > 5 * HEADER_DELTA)
+ die("more than %d lines of header", i);
+
+ /* No; add it to the set of other headers. */
+ if (hp == ARRAY_END(Table)) {
+ if (OtherCount >= OtherSize - 1) {
+ OtherSize += HEADER_DELTA;
+ OtherHeaders = xrealloc(OtherHeaders, OtherSize * sizeof(char *));
+ }
+ OtherHeaders[OtherCount++] = p;
+ }
+
+ /* Get start of next header; if it's a blank line, we hit the end. */
+ p = NextHeader(p);
+ if (*p == '\n')
+ break;
+ }
+
+ return p + 1;
+}
+
+\f
+
+/*
+** See if the user is allowed to cancel the indicated message. Assumes
+** that the Sender or From line has already been filled in.
+*/
+static void
+CheckCancel(char *msgid, bool JustReturn)
+{
+ char localfrom[SMBUF];
+ char *p;
+ char buff[BUFSIZ];
+ char remotefrom[SMBUF];
+
+ /* Ask the server for the article. */
+ fprintf(ToServer, "head %s\r\n", msgid);
+ SafeFlush(ToServer);
+ if (fgets(buff, sizeof buff, FromServer) == NULL
+ || atoi(buff) != NNTP_HEAD_FOLLOWS_VAL) {
+ if (JustReturn)
+ return;
+ die("server has no such article");
+ }
+
+ /* Read the headers, looking for the From or Sender. */
+ remotefrom[0] = '\0';
+ while (fgets(buff, sizeof buff, FromServer) != NULL) {
+ if ((p = strchr(buff, '\r')) != NULL)
+ *p = '\0';
+ if ((p = strchr(buff, '\n')) != NULL)
+ *p = '\0';
+ if (buff[0] == '.' && buff[1] == '\0')
+ break;
+ if (strncmp(buff, "Sender:", 7) == 0)
+ strlcpy(remotefrom, TrimSpaces(&buff[7]), SMBUF);
+ else if (remotefrom[0] == '\0' && strncmp(buff, "From:", 5) == 0)
+ strlcpy(remotefrom, TrimSpaces(&buff[5]), SMBUF);
+ }
+ if (remotefrom[0] == '\0') {
+ if (JustReturn)
+ return;
+ die("article is garbled");
+ }
+ HeaderCleanFrom(remotefrom);
+
+ /* Get the local user. */
+ strlcpy(localfrom, HDR(_sender) ? HDR(_sender) : HDR(_from), SMBUF);
+ HeaderCleanFrom(localfrom);
+
+ /* Is the right person cancelling? */
+ if (strcasecmp(localfrom, remotefrom) != 0)
+ die("article was posted by \"%s\" and you are \"%s\"", remotefrom,
+ localfrom);
+}
+
+
+/*
+** See if the user is the news administrator.
+*/
+static bool
+AnAdministrator(char *name, gid_t group)
+{
+ struct passwd *pwp;
+ struct group *grp;
+ char **mem;
+ char *p;
+
+ if (Revoked)
+ return false;
+
+ /* Find out who we are. */
+ if ((pwp = getpwnam(NEWSUSER)) == NULL)
+ /* Silent falure; clients might not have the group. */
+ return false;
+ if (getuid() == pwp->pw_uid)
+ return true;
+
+ /* See if the we're in the right group. */
+ if ((grp = getgrnam(NEWSGRP)) == NULL || (mem = grp->gr_mem) == NULL)
+ /* Silent falure; clients might not have the group. */
+ return false;
+ if (group == grp->gr_gid)
+ return true;
+ while ((p = *mem++) != NULL)
+ if (strcmp(name, p) == 0)
+ return true;
+ return false;
+}
+
+
+/*
+** Check the control message, and see if it's legit.
+*/
+static void
+CheckControl(char *ctrl, struct passwd *pwp)
+{
+ char *p;
+ char *q;
+ char save;
+ char name[SMBUF];
+
+ /* Snip off the first word. */
+ for (p = ctrl; ISWHITE(*p); p++)
+ continue;
+ for (ctrl = p; *p && !ISWHITE(*p); p++)
+ continue;
+ if (p == ctrl)
+ die("emtpy control message");
+ save = *p;
+ *p = '\0';
+
+ if (strcmp(ctrl, "cancel") == 0) {
+ for (q = p + 1; ISWHITE(*q); q++)
+ continue;
+ if (*q == '\0')
+ die("message ID missing in cancel");
+ if (!Spooling)
+ CheckCancel(q, false);
+ }
+ else if (strcmp(ctrl, "checkgroups") == 0
+ || strcmp(ctrl, "ihave") == 0
+ || strcmp(ctrl, "sendme") == 0
+ || strcmp(ctrl, "newgroup") == 0
+ || strcmp(ctrl, "rmgroup") == 0
+ || strcmp(ctrl, "sendsys") == 0
+ || strcmp(ctrl, "senduuname") == 0
+ || strcmp(ctrl, "version") == 0) {
+ strlcpy(name, pwp->pw_name, SMBUF);
+ if (!AnAdministrator(name, pwp->pw_gid))
+ die("ask your news administrator to do the %s for you", ctrl);
+ }
+ else {
+ die("%s is not a valid control message", ctrl);
+ }
+ *p = save;
+}
+
+\f
+
+/*
+** Parse the GECOS field to get the user's full name. This comes Sendmail's
+** buildfname routine. Ignore leading stuff like "23-" "stuff]-" or
+** "stuff -" as well as trailing whitespace, or anything that comes after
+** a comma, semicolon, or in parentheses. This seems to strip off most of
+** the UCB or ATT stuff people fill out the entries with. Also, turn &
+** into the login name, with perhaps an initial capital. (Everyone seems
+** to hate that, but everyone also supports it.)
+*/
+static char *
+FormatUserName(struct passwd *pwp, char *node)
+{
+ char outbuff[SMBUF];
+ char *buff;
+ char *out;
+ char *p;
+ int left;
+
+#if !defined(DONT_MUNGE_GETENV)
+ memset(outbuff, 0, SMBUF);
+ if ((p = getenv("NAME")) != NULL)
+ strlcpy(outbuff, p, SMBUF);
+ if (strlen(outbuff) == 0) {
+#endif /* !defined(DONT_MUNGE_GETENV) */
+
+
+#ifndef DO_MUNGE_GECOS
+ strlcpy(outbuff, pwp->pw_gecos, SMBUF);
+#else
+ /* Be very careful here. If we're not, we can potentially overflow our
+ * buffer. Remember that on some Unix systems, the content of the GECOS
+ * field is under (untrusted) user control and we could be setgid. */
+ p = pwp->pw_gecos;
+ left = SMBUF - 1;
+ if (*p == '*')
+ p++;
+ for (out = outbuff; *p && !GECOSTERM(*p) && left; p++) {
+ if (*p == '&') {
+ strncpy(out, pwp->pw_name, left);
+ if (CTYPE(islower, *out)
+ && (out == outbuff || !CTYPE(isalpha, out[-1])))
+ *out = toupper(*out);
+ while (*out) {
+ out++;
+ left--;
+ }
+ }
+ else if (*p == '-'
+ && p > pwp->pw_gecos
+ && (CTYPE(isdigit, p[-1]) || CTYPE(isspace, p[-1])
+ || p[-1] == ']')) {
+ out = outbuff;
+ left = SMBUF - 1;
+ }
+ else {
+ *out++ = *p;
+ left--;
+ }
+ }
+ *out = '\0';
+#endif /* DO_MUNGE_GECOS */
+
+#if !defined(DONT_MUNGE_GETENV)
+ }
+#endif /* !defined(DONT_MUNGE_GETENV) */
+
+ out = TrimSpaces(outbuff);
+ if (out[0])
+ buff = concat(pwp->pw_name, "@", node, " (", out, ")", (char *) 0);
+ else
+ buff = concat(pwp->pw_name, "@", node, (char *) 0);
+ return buff;
+}
+
+
+/*
+** Check the Distribution header, and exit on error.
+*/
+static void CheckDistribution(char *p)
+{
+ static char SEPS[] = " \t,";
+ const char * const *dp;
+
+ if ((p = strtok(p, SEPS)) == NULL)
+ die("cannot parse Distribution header");
+ do {
+ for (dp = BadDistribs; *dp; dp++)
+ if (uwildmat(p, *dp))
+ die("illegal distribution %s", p);
+ } while ((p = strtok((char *)NULL, SEPS)) != NULL);
+}
+
+
+/*
+** Process all the headers. FYI, they're done in RFC-order.
+*/
+static void
+ProcessHeaders(bool AddOrg, int linecount, struct passwd *pwp)
+{
+ static char PATHFLUFF[] = PATHMASTER;
+ HEADER *hp;
+ char *p;
+ TIMEINFO Now;
+ char buff[SMBUF];
+ char from[SMBUF];
+
+ /* Do some preliminary fix-ups. */
+ for (hp = Table; hp < ARRAY_END(Table); hp++) {
+ if (!hp->CanSet && hp->Value)
+ die("cannot set system header %s", hp->Name);
+ if (hp->Value) {
+ hp->Value = TrimSpaces(hp->Value);
+ if (*hp->Value == '\0')
+ hp->Value = NULL;
+ }
+ }
+
+ /* Set From or Sender. */
+ if ((p = innconf->fromhost) == NULL)
+ sysdie("cannot get hostname");
+ if (HDR(_from) == NULL)
+ HDR(_from) = FormatUserName(pwp, p);
+ else {
+ if (strlen(pwp->pw_name) + strlen(p) + 2 > sizeof(buff))
+ die("username and host are too long");
+ sprintf(buff, "%s@%s", pwp->pw_name, p);
+ strlcpy(from, HDR(_from), SMBUF);
+ HeaderCleanFrom(from);
+ if (strcmp(from, buff) != 0)
+ HDR(_sender) = xstrdup(buff);
+ }
+
+ if (HDR(_date) == NULL) {
+ /* Set Date. */
+ if (!makedate(-1, true, buff, sizeof(buff)))
+ die("cannot generate Date header");
+ HDR(_date) = xstrdup(buff);
+ }
+
+ /* Newsgroups are checked later. */
+
+ /* Set Subject; Control overrides the subject. */
+ if (HDR(_control)) {
+ CheckControl(HDR(_control), pwp);
+ }
+ else {
+ p = HDR(_subject);
+ if (p == NULL)
+ die("required Subject header is missing or empty");
+ else if (HDR(_alsocontrol))
+ CheckControl(HDR(_alsocontrol), pwp);
+#if 0
+ if (strncmp(p, "Re: ", 4) == 0 && HDR(_references) == NULL)
+ die("article subject begins with \"Re: \" but has no references");
+#endif /* 0 */
+ }
+
+ /* Set Message-ID */
+ if (HDR(_messageid) == NULL) {
+ if ((p = GenerateMessageID(innconf->domain)) == NULL)
+ die("cannot generate Message-ID header");
+ HDR(_messageid) = xstrdup(p);
+ }
+ else if ((p = strchr(HDR(_messageid), '@')) == NULL
+ || strchr(++p, '@') != NULL) {
+ die("message ID must have exactly one @");
+ }
+
+ /* Set Path */
+ if (HDR(_path) == NULL) {
+#if defined(DO_INEWS_PATH)
+ if ((p = innconf->pathhost) != NULL) {
+ if (*p)
+ HDR(_path) = concat(Exclusions, p, "!", PATHFLUFF, (char *) 0);
+ else
+ HDR(_path) = concat(Exclusions, PATHFLUFF, (char *) 0);
+ }
+ else if (innconf->server != NULL) {
+ if ((p = GetFQDN(innconf->domain)) == NULL)
+ sysdie("cannot get hostname");
+ HDR(_path) = concat(Exclusions, p, "!", PATHFLUFF, (char *) 0);
+ }
+ else {
+ HDR(_path) = concat(Exclusions, PATHFLUFF, (char *) 0);
+ }
+#else
+ HDR(_path) = concat(Exclusions, PATHFLUFF, (char *) 0);
+#endif /* defined(DO_INEWS_PATH) */
+ }
+
+ /* Reply-To; left alone. */
+ /* Sender; set above. */
+ /* Followup-To; checked with Newsgroups. */
+
+ /* Check Expires. */
+ if (GetTimeInfo(&Now) < 0)
+ sysdie("cannot get the time");
+ if (HDR(_expires) && parsedate(HDR(_expires), &Now) == -1)
+ die("cannot parse \"%s\" as an expiration date", HDR(_expires));
+
+ /* References; left alone. */
+ /* Control; checked above. */
+
+ /* Distribution. */
+ if ((p = HDR(_distribution)) != NULL) {
+ p = xstrdup(p);
+ CheckDistribution(p);
+ free(p);
+ }
+
+ /* Set Organization. */
+ if (AddOrg
+ && HDR(_organization) == NULL
+ && (p = innconf->organization) != NULL) {
+ HDR(_organization) = xstrdup(p);
+ }
+
+ /* Keywords; left alone. */
+ /* Summary; left alone. */
+ /* Approved; left alone. */
+
+ /* Set Lines */
+ sprintf(buff, "%d", linecount);
+ HDR(_lines) = xstrdup(buff);
+
+ /* Check Supersedes. */
+ if (HDR(_supersedes))
+ CheckCancel(HDR(_supersedes), true);
+
+ /* Now make sure everything is there. */
+ for (hp = Table; hp < ARRAY_END(Table); hp++)
+ if (hp->Type == HTreq && hp->Value == NULL)
+ die("required header %s is missing or empty", hp->Name);
+}
+
+
+/*
+** Try to append $HOME/.signature to the article. When in doubt, exit
+** out in order to avoid postings like "Sorry, I forgot my .signature
+** -- here's the article again."
+*/
+static char *
+AppendSignature(bool UseMalloc, char *article, char *homedir, int *linesp)
+{
+ static char NOSIG[] = "Can't add your .signature (%s), article not posted";
+ int i;
+ int length;
+ size_t artsize;
+ char *p;
+ char buff[BUFSIZ];
+ FILE *F;
+
+ /* Open the file. */
+ *linesp = 0;
+ if (strlen(homedir) > sizeof(buff) - 14)
+ die("home directory path too long");
+ sprintf(buff, "%s/.signature", homedir);
+ if ((F = fopen(buff, "r")) == NULL) {
+ if (errno == ENOENT)
+ return article;
+ fprintf(stderr, NOSIG, strerror(errno));
+ QuitServer(1);
+ }
+
+ /* Read it in. */
+ length = fread(buff, 1, sizeof buff - 2, F);
+ i = feof(F);
+ fclose(F);
+ if (length == 0)
+ die("signature file is empty");
+ if (length < 0)
+ sysdie("cannot read signature file");
+ if (length == sizeof buff - 2 && !i)
+ die("signature is too large");
+
+ /* Make sure the buffer ends with \n\0. */
+ if (buff[length - 1] != '\n')
+ buff[length++] = '\n';
+ buff[length] = '\0';
+
+ /* Count the lines. */
+ for (i = 0, p = buff; (p = strchr(p, '\n')) != NULL; p++)
+ if (++i > SIG_MAXLINES)
+ die("signature has too many lines");
+ *linesp = 1 + i;
+
+ /* Grow the article to have the signature. */
+ i = strlen(article);
+ artsize = i + sizeof(SIGSEP) - 1 + length + 1;
+ if (UseMalloc) {
+ p = xmalloc(artsize);
+ strlcpy(p, article, artsize);
+ article = p;
+ }
+ else
+ article = xrealloc(article, artsize);
+ strlcat(article, SIGSEP, artsize);
+ strlcat(article, buff, artsize);
+ return article;
+}
+
+
+/*
+** See if the user has more included text than new text. Simple-minded, but
+** reasonably effective for catching neophyte's mistakes. A line starting
+** with > is included text. Decrement the count on lines starting with <
+** so that we don't reject diff(1) output.
+*/
+static void
+CheckIncludedText(char *p, int lines)
+{
+ int i;
+
+ for (i = 0; ; p++) {
+ switch (*p) {
+ case '>':
+ i++;
+ break;
+ case '|':
+ i++;
+ break;
+ case ':':
+ i++;
+ break;
+ case '<':
+ i--;
+ break;
+ }
+ if ((p = strchr(p, '\n')) == NULL)
+ break;
+ }
+ if ((i * 2 > lines) && (lines > 40))
+ die("more included text than new text");
+}
+
+\f
+
+/*
+** Read stdin into a string and return it. Can't use ReadInDescriptor
+** since that will fail if stdin is a tty.
+*/
+static char *
+ReadStdin(void)
+{
+ int size;
+ char *p;
+ char *article;
+ char *end;
+ int i;
+
+ size = BUFSIZ;
+ article = xmalloc(size);
+ end = &article[size - 3];
+ for (p = article; (i = getchar()) != EOF; *p++ = (char)i)
+ if (p == end) {
+ article = xrealloc(article, size + BUFSIZ);
+ p = &article[size - 3];
+ size += BUFSIZ;
+ end = &article[size - 3];
+ }
+
+ /* Force a \n terminator. */
+ if (p > article && p[-1] != '\n')
+ *p++ = '\n';
+ *p = '\0';
+ return article;
+}
+
+\f
+
+/*
+** Offer the article to the server, return its reply.
+*/
+static int
+OfferArticle(char *buff, bool Authorized)
+{
+ fprintf(ToServer, "post\r\n");
+ SafeFlush(ToServer);
+ if (fgets(buff, HEADER_STRLEN, FromServer) == NULL)
+ sysdie(Authorized ? "Can't offer article to server (authorized)"
+ : "Can't offer article to server");
+ return atoi(buff);
+}
+
+
+/*
+** Spool article to temp file.
+*/
+static void
+Spoolit(char *article, size_t Length, char *deadfile)
+{
+ HEADER *hp;
+ FILE *F;
+ int i;
+
+ /* Try to write to the deadfile. */
+ if (deadfile == NULL)
+ return;
+ F = xfopena(deadfile);
+ if (F == NULL)
+ sysdie("cannot create spool file");
+
+ /* Write the headers and a blank line. */
+ for (hp = Table; hp < ARRAY_END(Table); hp++)
+ if (hp->Value)
+ fprintf(F, "%s: %s\n", hp->Name, hp->Value);
+ for (i = 0; i < OtherCount; i++)
+ fprintf(F, "%s\n", OtherHeaders[i]);
+ fprintf(F, "\n");
+ if (FLUSH_ERROR(F))
+ sysdie("cannot write headers");
+
+ /* Write the article and exit. */
+ if (fwrite(article, 1, Length, F) != Length)
+ sysdie("cannot write article");
+ if (FLUSH_ERROR(F))
+ sysdie("cannot write article");
+ if (fclose(F) == EOF)
+ sysdie("cannot close spool file");
+}
+
+
+/*
+** Print usage message and exit.
+*/
+static void
+Usage(void)
+{
+ fprintf(stderr, "Usage: inews [-D] [-h] [header_flags] [article]\n");
+ /* Don't call QuitServer here -- connection isn't open yet. */
+ exit(1);
+}
+
+
+int
+main(int ac, char *av[])
+{
+ static char NOCONNECT[] = "cannot connect to server";
+ int i;
+ char *p;
+ HEADER *hp;
+ int j;
+ int port;
+ int Mode;
+ int SigLines;
+ struct passwd *pwp;
+ char *article;
+ char *deadfile;
+ char buff[HEADER_STRLEN];
+ char SpoolMessage[HEADER_STRLEN];
+ bool DoSignature;
+ bool AddOrg;
+ size_t Length;
+ uid_t uid;
+
+ /* First thing, set up logging and our identity. */
+ message_program_name = "inews";
+
+ /* Find out who we are. */
+ uid = geteuid();
+ if (uid == (uid_t) -1)
+ sysdie("cannot get your user ID");
+ if ((pwp = getpwuid(uid)) == NULL)
+ sysdie("cannot get your passwd entry");
+
+ /* Set defaults. */
+ Mode = '\0';
+ Dump = false;
+ DoSignature = true;
+ AddOrg = true;
+ port = 0;
+
+ if (!innconf_read(NULL))
+ exit(1);
+
+ umask(NEWSUMASK);
+
+ /* Parse JCL. */
+ while ((i = getopt(ac, av, "DNAVWORShx:a:c:d:e:f:n:p:r:t:F:o:w:")) != EOF)
+ switch (i) {
+ default:
+ Usage();
+ /* NOTREACHED */
+ case 'D':
+ case 'N':
+ Dump = true;
+ break;
+ case 'A':
+ case 'V':
+ case 'W':
+ /* Ignore C News options. */
+ break;
+ case 'O':
+ AddOrg = false;
+ break;
+ case 'R':
+ Revoked = true;
+ break;
+ case 'S':
+ DoSignature = false;
+ break;
+ case 'h':
+ Mode = i;
+ break;
+ case 'x':
+ Exclusions = concat(optarg, "!", (char *) 0);
+ break;
+ case 'p':
+ port = atoi(optarg);
+ break;
+ /* Header lines that can be specified on the command line. */
+ case 'a': HDR(_approved) = optarg; break;
+ case 'c': HDR(_control) = optarg; break;
+ case 'd': HDR(_distribution) = optarg; break;
+ case 'e': HDR(_expires) = optarg; break;
+ case 'f': HDR(_from) = optarg; break;
+ case 'n': HDR(_newsgroups) = optarg; break;
+ case 'r': HDR(_replyto) = optarg; break;
+ case 't': HDR(_subject) = optarg; break;
+ case 'F': HDR(_references) = optarg; break;
+ case 'o': HDR(_organization) = optarg; break;
+ case 'w': HDR(_followupto) = optarg; break;
+ }
+ ac -= optind;
+ av += optind;
+
+ /* Parse positional arguments; at most one, the input file. */
+ switch (ac) {
+ default:
+ Usage();
+ /* NOTREACHED */
+ case 0:
+ /* Read stdin. */
+ article = ReadStdin();
+ break;
+ case 1:
+ /* Read named file. */
+ article = ReadInFile(av[0], (struct stat *)NULL);
+ if (article == NULL)
+ sysdie("cannot read input file");
+ break;
+ }
+
+ if (port == 0)
+ port = NNTP_PORT;
+
+ /* Try to open a connection to the server. */
+ if (NNTPremoteopen(port, &FromServer, &ToServer, buff) < 0) {
+ Spooling = true;
+ if ((p = strchr(buff, '\n')) != NULL)
+ *p = '\0';
+ if ((p = strchr(buff, '\r')) != NULL)
+ *p = '\0';
+ strcpy(SpoolMessage, buff[0] ? buff : NOCONNECT);
+ deadfile = concatpath(pwp->pw_dir, "dead.article");
+ }
+ else {
+ /* We now have an open server connection, so close it on failure. */
+ message_fatal_cleanup = fatal_cleanup;
+
+ /* See if we can post. */
+ i = atoi(buff);
+
+ /* Tell the server we're posting. */
+ setbuf(FromServer, xmalloc(BUFSIZ));
+ setbuf(ToServer, xmalloc(BUFSIZ));
+ fprintf(ToServer, "mode reader\r\n");
+ SafeFlush(ToServer);
+ if (fgets(buff, HEADER_STRLEN, FromServer) == NULL)
+ sysdie("cannot tell server we're reading");
+ if ((j = atoi(buff)) != NNTP_BAD_COMMAND_VAL)
+ i = j;
+
+ if (i != NNTP_POSTOK_VAL) {
+ /* We try to authenticate in case it is all the same possible
+ * to post. */
+ if (NNTPsendpassword((char *)NULL, FromServer, ToServer) < 0)
+ die("you do not have permission to post");
+ }
+ deadfile = NULL;
+ }
+
+ /* Basic processing. */
+ for (hp = Table; hp < ARRAY_END(Table); hp++)
+ hp->Size = strlen(hp->Name);
+ if (Mode == 'h')
+ article = StripOffHeaders(article);
+ for (i = 0, p = article; (p = strchr(p, '\n')) != NULL; i++, p++)
+ continue;
+ if (innconf->checkincludedtext)
+ CheckIncludedText(article, i);
+ if (DoSignature)
+ article = AppendSignature(Mode == 'h', article, pwp->pw_dir, &SigLines);
+ else
+ SigLines = 0;
+ ProcessHeaders(AddOrg, i + SigLines, pwp);
+ Length = strlen(article);
+ if ((innconf->localmaxartsize > 0)
+ && (Length > (size_t)innconf->localmaxartsize))
+ die("article is larger than local limit of %ld bytes",
+ innconf->localmaxartsize);
+
+ /* Do final checks. */
+ if (i == 0 && HDR(_control) == NULL)
+ die("article is empty");
+ for (hp = Table; hp < ARRAY_END(Table); hp++)
+ if (hp->Value && (int)strlen(hp->Value) + hp->Size > HEADER_STRLEN)
+ die("%s header is too long", hp->Name);
+ for (i = 0; i < OtherCount; i++)
+ if ((int)strlen(OtherHeaders[i]) > HEADER_STRLEN)
+ die("header too long (maximum length is %d): %.40s...",
+ HEADER_STRLEN, OtherHeaders[i]);
+
+ if (Dump) {
+ /* Write the headers and a blank line. */
+ for (hp = Table; hp < ARRAY_END(Table); hp++)
+ if (hp->Value)
+ printf("%s: %s\n", hp->Name, hp->Value);
+ for (i = 0; i < OtherCount; i++)
+ printf("%s\n", OtherHeaders[i]);
+ printf("\n");
+ if (FLUSH_ERROR(stdout))
+ sysdie("cannot write headers");
+
+ /* Write the article and exit. */
+ if (fwrite(article, 1, Length, stdout) != Length)
+ sysdie("cannot write article");
+ SafeFlush(stdout);
+ QuitServer(0);
+ }
+
+ if (Spooling) {
+ warn("warning: %s", SpoolMessage);
+ warn("article will be spooled");
+ Spoolit(article, Length, deadfile);
+ exit(0);
+ }
+
+ /* Article is prepared, offer it to the server. */
+ i = OfferArticle(buff, false);
+ if (i == NNTP_AUTH_NEEDED_VAL) {
+ /* Posting not allowed, try to authorize. */
+ if (NNTPsendpassword((char *)NULL, FromServer, ToServer) < 0)
+ sysdie("authorization error");
+ i = OfferArticle(buff, true);
+ }
+ if (i != NNTP_START_POST_VAL)
+ die("server doesn't want the article: %s", buff);
+
+ /* Write the headers, a blank line, then the article. */
+ for (hp = Table; hp < ARRAY_END(Table); hp++)
+ if (hp->Value)
+ fprintf(ToServer, "%s: %s\r\n", hp->Name, hp->Value);
+ for (i = 0; i < OtherCount; i++)
+ fprintf(ToServer, "%s\r\n", OtherHeaders[i]);
+ fprintf(ToServer, "\r\n");
+ if (NNTPsendarticle(article, ToServer, true) < 0)
+ sysdie("cannot send article to server");
+ SafeFlush(ToServer);
+
+ if (fgets(buff, sizeof buff, FromServer) == NULL)
+ sysdie("no reply from server after sending the article");
+ if ((p = strchr(buff, '\r')) != NULL)
+ *p = '\0';
+ if ((p = strchr(buff, '\n')) != NULL)
+ *p = '\0';
+ if (atoi(buff) != NNTP_POSTEDOK_VAL)
+ die("cannot send article to server: %s", buff);
+
+ /* Close up. */
+ QuitServer(0);
+ /* NOTREACHED */
+ return 1;
+}
--- /dev/null
+/* $Id: innconfval.c 5962 2002-12-08 19:52:13Z rra $
+**
+** Get a config value from INN.
+*/
+
+#include "config.h"
+#include "clibrary.h"
+
+#include "inn/innconf.h"
+#include "inn/messages.h"
+#include "libinn.h"
+
+/*
+** Print the INN version string with appropriate quoting.
+*/
+static void
+print_version(FILE *file, enum innconf_quoting quoting)
+{
+ switch (quoting) {
+ case INNCONF_QUOTE_NONE:
+ fprintf(file, "%s\n", inn_version_string);
+ break;
+ case INNCONF_QUOTE_SHELL:
+ fprintf(file, "VERSION='%s'; export VERSION\n", inn_version_string);
+ break;
+ case INNCONF_QUOTE_PERL:
+ fprintf(file, "$version = '%s';\n", inn_version_string);
+ break;
+ case INNCONF_QUOTE_TCL:
+ fprintf(file, "set inn_version \"%s\"\n", inn_version_string);
+ break;
+ }
+}
+
+
+/*
+** Main routine. Most of the real work is done by the innconf library
+** routines.
+*/
+int
+main(int argc, char *argv[])
+{
+ int option, i;
+ char *file = NULL;
+ enum innconf_quoting quoting = INNCONF_QUOTE_NONE;
+ bool okay = true;
+ bool version = false;
+ bool checking = false;
+
+ message_program_name = "innconfval";
+
+ while ((option = getopt(argc, argv, "Ci:pstv")) != EOF)
+ switch (option) {
+ default:
+ die("usage error");
+ break;
+ case 'C':
+ checking = true;
+ break;
+ case 'i':
+ file = optarg;
+ break;
+ case 'p':
+ quoting = INNCONF_QUOTE_PERL;
+ break;
+ case 's':
+ quoting = INNCONF_QUOTE_SHELL;
+ break;
+ case 't':
+ quoting = INNCONF_QUOTE_TCL;
+ break;
+ case 'v':
+ version = true;
+ break;
+ }
+ argc -= optind;
+ argv += optind;
+
+ if (version) {
+ print_version(stdout, quoting);
+ exit(0);
+ }
+ if (checking)
+ exit(innconf_check(file) ? 0 : 1);
+
+ /* Read in the inn.conf file specified. */
+ if (!innconf_read(file))
+ exit(1);
+
+ /* Perform the specified action. */
+ if (argv[0] == NULL) {
+ innconf_dump(stdout, quoting);
+ print_version(stdout, quoting);
+ } else {
+ for (i = 0; i < argc; i++)
+ if (strcmp(argv[i], "version") == 0)
+ print_version(stdout, quoting);
+ else if (!innconf_print_value(stdout, argv[i], quoting))
+ okay = false;
+ }
+ exit(okay ? 0 : 1);
+}
--- /dev/null
+#! /usr/bin/perl
+# fixscript will replace this line with require innshellvars.pl
+
+# mailpost - Yet another mail-to-news filter
+#
+# $Id: mailpost.in 7795 2008-04-26 08:28:08Z iulius $
+#
+# 21feb00 [added "lc" to duplicate header fixer stmt to make it case-insensitive]
+# doka 11may99 [fixed duplicate headers problem]
+# brister 19oct98 [cleaned up somewhat for Perl v. 5. and made a little more robust]
+# vixie 29jan95 [RCS'd]
+# vixie 15jun93 [added -m]
+# vixie 30jun92 [added -a and -d]
+# vixie 17jun92 [attempt simple-minded fixup to $path]
+# vixie 14jun92 [original]
+
+use Getopt::Std;
+use IPC::Open3;
+use IO::Select;
+use Sys::Syslog;
+use strict;
+
+my $debugging = 0 ;
+my $tmpfile ;
+my $tmpfile2 ;
+my $msg ;
+
+END {
+ unlink ($tmpfile) if $tmpfile ; # in case we die()
+ unlink ($tmpfile2) if $tmpfile2 ; # in case we die()
+}
+
+my $LOCK_SH = 1;
+my $LOCK_EX = 2;
+my $LOCK_NB = 4;
+my $LOCK_UN = 8;
+
+my $usage = $0 ;
+$usage =~ s!.*/!! ;
+my $prog = $usage ;
+
+openlog $usage, "pid", $inn::syslog_facility ;
+
+$usage .= "[ -r addr ][ -f addr ][ -a approved ][ -d distribution ]" .
+ " [ -m mailing-list ][ -b database ][ -o output-path ] [ -c wait-time ]" .
+ " [ -x header[:header...] ] [ -p port ] newsgroups" ;
+
+use vars qw($opt_r $opt_f $opt_a $opt_d $opt_m $opt_b $opt_n $opt_o $opt_h $opt_c $opt_x $opt_p) ;
+getopts("hr:f:a:d:m:b:no:c:x:p:") || die "usage: $usage\n" ;
+die "usage: $usage\n" if $opt_h ;
+
+#
+# $Submit is a program which takes no arguments and whose stdin is supposed
+# to be a news article (without the #!rnews header but with the news hdr).
+#
+
+my $Sendmail = $inn::mta ;
+my $Submit = $inn::inews . " -S -h" . ($opt_p ? " -p $opt_p" : '');
+my $Database = ($opt_b || $inn::pathtmp) . "/mailpost-msgid" ;
+my $Maintainer = $inn::newsmaster || "usenet" ;
+my $WhereTo = $opt_o || $Submit ;
+my $Mailname = $inn::fromhost ;
+
+# Can't use $inn::pathtmp as we're usually not running as news.
+my $Tmpdir = "/var/tmp" ;
+
+if ($debugging || $opt_n) {
+ $Sendmail = "cat" ;
+ $WhereTo = "cat" ;
+}
+
+chop ($Mailname = `/bin/hostname`) if ! $Mailname ;
+
+
+#
+# Our command-line argument(s) are the list of newsgroups to post to.
+#
+# There may be a "-r sender" or "-f sender" which becomes the $path
+# (which is in turn overridden below by various optional headers).
+#
+# -d (distribution) and -a (approved) are also supported to supply
+# or override the mail headers by those names.
+#
+
+my $path = 'nobody';
+my $newsgroups = undef;
+my $approved = undef;
+my $distribution = undef;
+my $mailing_list = undef;
+my $references = undef;
+my @errorText = ();
+
+if ($opt_r || $opt_f) {
+ $path = $opt_r || $opt_f ;
+ push @errorText, "((path: $path))\n" ;
+}
+
+if ($opt_a) {
+ $approved = &fix_sender_addr($opt_a);
+ push @errorText, "((approved: $approved))\n";
+}
+
+if ($opt_d) {
+ $distribution = $opt_d ;
+ push @errorText, "((distribution: $distribution))\n";
+}
+
+if ($opt_m) {
+ $mailing_list = "<" . $opt_m . "> /dev/null";
+ push @errorText, "((mailing_list: $mailing_list))\n";
+}
+
+my $exclude = 'Organization|Distribution';
+if ($opt_x) {
+ $exclude .= '|' . join('|', split(/:/, $opt_x));
+}
+
+$newsgroups = join ",", @ARGV ;
+
+die "usage: $0 newsgroup [newsgroup ...]\n" unless $newsgroups;
+
+
+#
+# Do the header. Our input is a mail message, with or without the From.
+#
+
+#$message_id = sprintf("<mailpost.%d.%d@%s>", time, $$, $Hostname);
+my $real_news_hdrs = '';
+my $weird_mail_hdrs = '';
+my $fromHdr = "MAILPOST-UNKNOWN-FROM" ;
+my $dateHdr= "MAILPOST-UNKNOWN-DATE" ;
+my $msgIdHdr = "MAILPOST-UNKNOWN-MESSAGE-ID" ;
+my $from = undef;
+my $date = undef;
+my $hdr = undef;
+my $txt = undef;
+my $message_id ;
+my $subject = "(NONE)";
+
+$_ = <STDIN>;
+if (!$_) {
+ if ( $debugging || -t STDERR ) {
+ die "empty input" ;
+ } else {
+ syslog "err", "empty input" ;
+ exit (0) ;
+ }
+}
+
+chomp $_;
+
+my $line = undef;
+if (/^From\s+([^\s]+)\s+/) {
+ $path = $1;
+ push @errorText, "((path: $path))\n";
+ $_ = $';
+ if (/ remote from /) {
+ $path = $' . '!' . $path;
+ $_ = $`;
+ }
+} else {
+ $line = $_;
+}
+
+for (;;) {
+ last if defined($line) && ($line =~ /^$/) ;
+
+ $_ = <STDIN> ;
+ last unless defined $_ ;
+ chomp ;
+
+ # Gather up a single header with possible continuation lines into $line.
+ if (/^\s+/) {
+ if (! $line) {
+ $msg = "First line with leading whitespace!" ;
+ syslog "err", $msg unless -t STDERR ;
+ die "$msg\n" ;
+ }
+
+ $line .= "\n" . $_ ;
+ next ;
+ }
+
+ # On the first header, $line will be undefined.
+ ($_, $line) = ($line, $_) ; # Swap $line and $_.
+
+ last if defined($_) && /^$/ ;
+ next if /^$/ ; # Only on first header will this happen.
+
+ push @errorText, "($_)\n";
+
+ next if /^Approved:\s/sio && defined($approved);
+ next if /^Distribution:\s/sio && defined($distribution);
+
+ if (/^($exclude):\s*/sio) {
+ $real_news_hdrs .= "$_\n";
+ next;
+ }
+
+ if (/^Subject:\s*/sio) {
+ $subject = $';
+ next;
+ }
+
+ if (/^Message-ID:\s*/sio) {
+ $message_id = $';
+ next;
+ }
+
+ if (/^Mailing-List:\s*/sio) {
+ $mailing_list = $';
+ next;
+ }
+
+ if (/^(Sender|Approved):\s*/sio) {
+ $real_news_hdrs .= "$&" . fix_sender_addr($') . "\n";
+ next;
+ }
+
+ if (/^Return-Path:\s*/sio) {
+ $path = $';
+ $path = $1 if ($path =~ /\<([^\>]*)\>/);
+ push@errorText, "((path: $path))\n";
+ next;
+ }
+
+ if (/^Date:\s*/sio) {
+ $date = $';
+ next;
+ }
+
+ if (/^From:\s*/sio) {
+ $from = &fix_sender_addr($');
+ next;
+ }
+
+ if (/^References:\s*/sio) {
+ $references = $';
+ next;
+ }
+
+ if (!defined($references) && /^In-Reply-To:[^\<]*\<([^\>]+)\>/sio) {
+ $references = "<$1>";
+ # FALLTHROUGH
+ }
+
+ if (/^(MIME|Content)-[^:]+:\s*/sio) {
+ $real_news_hdrs .= $_ . "\n" ;
+ next ;
+ }
+
+ # Strip out news X-Trace: and X-Complaints-To: headers since otherwise posting
+ # may fail. Other trace headers will be renamed to add 'X-' so we don't have
+ # to worry about them.
+ if (/^X-(Trace|Complaints-To):\s*/sio) {
+ next ;
+ }
+
+ # Random unknown header. Prepend 'X-' if it is not already there.
+ $_ = "X-$_" unless /^X-/sio ;
+ $weird_mail_hdrs .= "$_\n";
+}
+
+
+$msgIdHdr = $message_id if $message_id ;
+$fromHdr = $from if $from ;
+$dateHdr = $date if $date ;
+
+if ($path !~ /\!/) {
+ $path = "$'!$`" if ($path =~ /\@/);
+}
+
+$real_news_hdrs .= "Subject: ${subject}\n";
+$real_news_hdrs .= "Message-ID: ${msgIdHdr}\n" if defined($message_id);
+$real_news_hdrs .= "Mailing-List: ${mailing_list}\n" if defined($mailing_list);
+$real_news_hdrs .= "Distribution: ${distribution}\n" if defined($distribution);
+$real_news_hdrs .= "Approved: ${approved}\n" if defined($approved);
+$real_news_hdrs .= "References: ${references}\n" if defined($references);
+
+# Remove duplicate headers.
+my %headers = ();
+$real_news_hdrs =~ s/((.*?:) .*?($|\n)([ \t]+.*?($|\n))*)/$headers{lc$2}++?"":"$1"/ges;
+
+# inews writes error messages to stdout. We want to capture those and mail
+# them back to the newsmaster. Trying to write and read from a subprocess is
+# ugly and prone to deadlock, so we use a temp file.
+$tmpfile = sprintf "%s/mailpost.%d.%d", $Tmpdir, time, $$ ;
+
+if (!open TMPFILE,">$tmpfile") {
+ $msg = "can't open temp file ($tmpfile): $!" ;
+ $tmpfile = undef ;
+ syslog("err", "$msg\n") unless $debugging || -t STDERR ;
+ open(TMPFILE, "|" . sprintf ($Sendmail, $Maintainer)) ||
+ die "die(no tmpfile): sendmail: $!\n" ;
+ print TMPFILE <<"EOF";
+To: $Maintainer
+Subject: mailpost failure ($newsgroups): $msg
+
+-------- Article Contents
+
+EOF
+}
+
+print TMPFILE <<"EOF";
+Path: ${path}
+From: ${fromHdr}
+Newsgroups: ${newsgroups}
+${real_news_hdrs}Date: ${dateHdr}
+${weird_mail_hdrs}
+EOF
+
+my $rest;
+$rest .= $_ while (<STDIN>);
+$rest =~ s/\n*$/\n/g; # Remove trailing \n except very last.
+
+print TMPFILE $rest;
+close TMPFILE ;
+
+if ( ! $tmpfile ) {
+ # We had to bail and mail the article to the admin.
+ exit (0) ;
+}
+
+
+##
+## We've got the article in a temp file and now we validate some of the
+## data we found and update our Message-ID database.
+##
+
+mailArtAndDie ("no From: found") unless $from;
+mailArtAndDie ("no Message-ID: found") unless $message_id;
+mailArtAndDie ("Malformed Message-ID ($message_id)")
+ if ($message_id !~ /\<(\S+)\@(\S+)\>/);
+
+
+# Update (with locking) our Message-ID database. This is used to make sure we
+# don't loop our own gatewayed articles back through the mailing list.
+
+my ($lhs, $rhs) = ($1, $2); # Of message_id matched above.
+$rhs =~ tr/A-Z/a-z/;
+
+$message_id = "${lhs}\@${rhs}";
+
+push @errorText, "(TAS Message-ID database for $message_id)\n";
+
+my $lockfile = sprintf("%s.lock", $Database);
+
+open(LOCKFILE, "<$lockfile") ||
+ open(LOCKFILE, ">$lockfile") ||
+ mailArtAndDie ("can't open $lockfile: $!") ;
+
+my $i ;
+for ($i = 0 ; $i < 5 ; $i++) {
+ flock(LOCKFILE, $LOCK_EX) && last ;
+ sleep 1 ;
+}
+
+mailArtAndDie ("can't lock $lockfile: $!") if ($i == 5) ;
+
+my %DATABASE ;
+dbmopen(%DATABASE, $Database, 0666) || mailArtAndDie ("can't dbmopen $Database: $!");
+
+if (defined $DATABASE{$message_id}) {
+
+ exit 0 if (!$opt_c) ;
+
+## crosspost -c
+ $newsgroups = &append_newsgroups($DATABASE{$message_id}, $newsgroups) ;
+ syslog "err", "crosspost $newsgroups\n" if $debugging ;
+}
+
+#$DATABASE{$message_id} = sprintf "%d.%s", time, 'mailpost' ;
+$DATABASE{$message_id} = $newsgroups ;
+
+mailArtAndDie ("TAS didn't set $message_id") unless defined $DATABASE{$message_id};
+
+dbmclose(%DATABASE) || mailArtAndDie ("can't dbmclose $Database: $!") ;
+
+flock(LOCKFILE, $LOCK_UN) || mailArtAndDie ("can't unlock $lockfile: $!");
+close LOCKFILE ;
+
+## For crosspost.
+
+if ($opt_c) {
+ if (fork() != 0) {
+ undef $tmpfile; # Don't unlink $tmpfile.
+ exit 0;
+ }
+ sleep $opt_c ;
+
+ open(LOCKFILE, "<$lockfile") ||
+ open(LOCKFILE, ">$lockfile") ||
+ mailArtAndDie ("can't open $lockfile: $!") ;
+
+ my $i ;
+ for ($i = 0 ; $i < 5 ; $i++) {
+ flock(LOCKFILE, $LOCK_EX) && last ;
+ sleep 1 ;
+ }
+ mailArtAndDie ("can't lock $lockfile: $!") if ($i == 5) ;
+
+ my $umask_bak = umask();
+ umask(000);
+ dbmopen(%DATABASE, $Database, 0666) || mailArtAndDie ("can't dbmopen $Database: $!");
+ umask($umask_bak);
+
+ my $dup = undef ;
+ syslog "err", "check " . $DATABASE{$message_id} . " : $newsgroups\n" if $debugging ;
+ $dup = 1 if ($DATABASE{$message_id} ne $newsgroups) ;
+
+ dbmclose(%DATABASE) || mailArtAndDie ("can't dbmclose $Database: $!") ;
+
+ flock(LOCKFILE, $LOCK_UN) || mailArtAndDie ("can't unlock $lockfile: $!");
+ close LOCKFILE ;
+
+ if (defined($dup)) {
+ syslog "err", "mismatch $newsgroups\n" if $debugging ;
+ exit 0 ;
+ }
+
+ # Replace Newsgroups:.
+ open(TMPFILE, "$tmpfile") || mailArtAndDie ("can't open temp file ($tmpfile): $!") ;
+ $tmpfile2 = sprintf "%s/mailpost-crosspost.%d.%d", $Tmpdir, time, $$ ;
+ if ( !open TMPFILE2, ">$tmpfile2") {
+ $msg = "can't open temp file ($tmpfile2): $!" ;
+ $tmpfile2 = undef ;
+ die $msg ;
+ }
+ for (;;) {
+ $_ = <TMPFILE> ;
+ chomp ;
+ last if defined($_) && /^$/ ;
+
+ if (/^Newsgroups:\s*/sio) {
+ printf TMPFILE2 "Newsgroups: %s\n", $newsgroups ;
+ next ;
+ }
+ print TMPFILE2 "$_\n" ;
+ }
+ printf TMPFILE2 "\n" ;
+
+ my $rest;
+ $rest .= $_ while (<TMPFILE>);
+ $rest =~ s/\n*$/\n/g; # Remove trailing \n except very last.
+
+ print TMPFILE2 $rest;
+ close TMPFILE2 ;
+ close TMPFILE ;
+ rename($tmpfile2, $tmpfile) || mailArtAndDie ("can't rename $tmpfile2 $tmpfile: $!") ;
+ $tmpfile2 = undef ;
+
+}
+
+if (!open INEWS, "$WhereTo < $tmpfile 2>&1 |") {
+ mailArtAndDie ("can't start $WhereTo: $!") ;
+}
+
+my @inews = <INEWS> ;
+close INEWS ;
+my $status = $? ;
+
+if (@inews) {
+ chomp @inews ;
+ mailArtAndDie ("inews failed: @inews") ;
+}
+
+unlink $tmpfile ;
+
+exit $status;
+
+sub mailArtAndDie {
+ my ($msg) = @_ ;
+
+ print STDERR $msg,"\n" if -t STDERR ;
+
+ open(SENDMAIL, "|" . sprintf ($Sendmail,$Maintainer)) ||
+ die "die($msg): sendmail: $!\n" ;
+ print SENDMAIL <<"EOF" ;
+To: $Maintainer
+Subject: mailpost failure ($newsgroups)
+
+$msg
+
+EOF
+
+ if ($tmpfile && -f $tmpfile) {
+ print SENDMAIL "\n-------- Article Contents\n\n" ;
+ open(FILE, "<$tmpfile") || die "open($tmpfile): $!\n" ;
+ print SENDMAIL while <FILE> ;
+ close FILE ;
+ } else {
+ print "No article left to send back.\n" ;
+ }
+ close SENDMAIL ;
+
+# unlink $tmpfile ;
+
+ exit (0) ; # Using a non-zero exit may cause problems.
+}
+
+
+#
+# Take 822-format name (either "comment <addr> comment" or "addr (comment)")
+# and return in always-qualified 974-format ("addr (comment)").
+#
+sub fix_sender_addr {
+ my ($address) = @_;
+ my ($lcomment, $addr, $rcomment, $comment);
+ local ($',$`,$_) ;
+
+ if ($address =~ /\<([^\>]*)\>/) {
+ ($lcomment, $addr, $rcomment) = (&dltb($`), &dltb($1), &dltb($'));
+ } elsif ($address =~ /\(([^\)]*)\)/) {
+ ($lcomment, $addr, $rcomment) = ('', &dltb($`.$'), &dltb($1));
+ } else {
+ ($lcomment, $addr, $rcomment) = ('', &dltb($address), '');
+ }
+
+ #print STDERR "fix_sender_addr($address) == ($lcomment, $addr, $rcomment)\n";
+
+ $addr .= "\@$Mailname" unless ($addr =~ /\@/);
+
+ if ($lcomment && $rcomment) {
+ $comment = $lcomment . ' ' . $rcomment;
+ } else {
+ $comment = $lcomment . $rcomment;
+ }
+
+ $_ = $addr;
+ $_ .= " ($comment)" if $comment;
+
+ #print STDERR "\t-> $_\n";
+
+ return $_;
+}
+
+#
+# Delete leading and trailing blanks.
+#
+
+sub dltb {
+ my ($str) = @_;
+
+ $str =~ s/^\s+//o;
+ $str =~ s/\s+$//o;
+
+ return $str;
+}
+
+sub append_newsgroups ($$) {
+ my (@orig) = split(/,/,$_[0]) ;
+ my (@new) = split(/,/,$_[1]) ;
+ my $newsgroup ;
+
+ foreach $newsgroup (@new) {
+ if ( !grep($_ eq $newsgroup,@orig)) {
+ push @orig, $newsgroup ;
+ } else {
+# mailArtAndDie ("Duplicate Newsgroups: $newsgroup") ;
+ }
+ }
+ return join ",", @orig ;
+
+}
+
--- /dev/null
+/*
+ * ovdb_init
+ * Performs recovery on OV database, if needed
+ * Performs upgrade of OV database, if needed and if '-u' used
+ * Starts ovdb_monitor, if needed
+ */
+
+#include "config.h"
+#include "clibrary.h"
+#include "libinn.h"
+#include <errno.h>
+#include <syslog.h>
+
+#include "inn/innconf.h"
+#include "inn/messages.h"
+#include "ov.h"
+#include "../storage/ovdb/ovdb.h"
+#include "../storage/ovdb/ovdb-private.h"
+
+#ifndef USE_BERKELEY_DB
+
+int main(int argc UNUSED, char **argv UNUSED)
+{
+ die("BerkeleyDB support not compiled");
+}
+
+#else /* USE_BERKELEY_DB */
+
+static int open_db(DB **db, const char *name, int type)
+{
+ int ret;
+#if DB_VERSION_MAJOR == 2
+ DB_INFO dbinfo;
+ memset(&dbinfo, 0, sizeof dbinfo);
+
+ ret = db_open(name, type, DB_CREATE, 0666, OVDBenv, &dbinfo, db);
+ if (ret != 0) {
+ warn("db_open failed: %s", db_strerror(ret));
+ return ret;
+ }
+#else
+ ret = db_create(db, OVDBenv, 0);
+ if (ret != 0) {
+ warn("db_create failed: %s\n", db_strerror(ret));
+ return ret;
+ }
+#if DB_VERSION_MAJOR > 4 || (DB_VERSION_MAJOR == 4 && DB_VERSION_MINOR >= 1)
+ ret = (*db)->open(*db, NULL, name, NULL, type, DB_CREATE, 0666);
+#else
+ ret = (*db)->open(*db, name, NULL, type, DB_CREATE, 0666);
+#endif
+ if (ret != 0) {
+ (*db)->close(*db, 0);
+ warn("%s->open failed: %s", name, db_strerror(ret));
+ return ret;
+ }
+#endif
+ return 0;
+}
+
+/* Upgrade BerkeleyDB version */
+static int upgrade_database(const char *name UNUSED)
+{
+#if DB_VERSION_MAJOR == 2
+ return 0;
+#else
+ int ret;
+ DB *db;
+
+ ret = db_create(&db, OVDBenv, 0);
+ if (ret != 0)
+ return ret;
+
+ notice("upgrading %s...", name);
+ ret = db->upgrade(db, name, 0);
+ if (ret != 0)
+ warn("db->upgrade(%s) failed: %s", name, db_strerror(ret));
+
+ db->close(db, 0);
+ return ret;
+#endif
+}
+
+
+struct groupstats {
+ ARTNUM low;
+ ARTNUM high;
+ int count;
+ int flag;
+ time_t expired;
+};
+
+static int v1_which_db(char *group)
+{
+ HASH grouphash;
+ unsigned int i;
+
+ grouphash = Hash(group, strlen(group));
+ memcpy(&i, &grouphash, sizeof(i));
+ return i % ovdb_conf.numdbfiles;
+}
+
+/* Upgrade ovdb data format version 1 to 2 */
+/* groupstats and groupsbyname are replaced by groupinfo */
+static int upgrade_v1_to_v2(void)
+{
+ DB *groupstats, *groupsbyname, *groupinfo, *vdb;
+ DBT key, val, ikey, ival;
+ DBC *cursor;
+ group_id_t gid, higid = 0, higidbang = 0;
+ struct groupinfo gi;
+ struct groupstats gs;
+ char group[MAXHEADERSIZE];
+ u_int32_t v2 = 2;
+ int ret;
+ char *p;
+
+ notice("upgrading data to version 2");
+ ret = open_db(&groupstats, "groupstats", DB_BTREE);
+ if (ret != 0)
+ return ret;
+ ret = open_db(&groupsbyname, "groupsbyname", DB_HASH);
+ if (ret != 0)
+ return ret;
+ ret = open_db(&groupinfo, "groupinfo", DB_BTREE);
+ if (ret != 0)
+ return ret;
+
+ memset(&key, 0, sizeof key);
+ memset(&val, 0, sizeof val);
+ memset(&ikey, 0, sizeof ikey);
+ memset(&ival, 0, sizeof ival);
+
+ ret = groupsbyname->cursor(groupsbyname, NULL, &cursor, 0);
+ if (ret != 0)
+ return ret;
+
+ while((ret = cursor->c_get(cursor, &key, &val, DB_NEXT)) == 0) {
+ if(key.size == 1 && *((char *)(key.data)) == '!') {
+ if(val.size == sizeof(group_id_t))
+ memcpy(&higidbang, val.data, sizeof(group_id_t));
+ continue;
+ }
+ if(key.size >= MAXHEADERSIZE)
+ continue;
+ memcpy(group, key.data, key.size);
+ group[key.size] = 0;
+
+ if(val.size != sizeof(group_id_t))
+ continue;
+ memcpy(&gid, val.data, sizeof(group_id_t));
+ if(gid > higid)
+ higid = gid;
+ ikey.data = &gid;
+ ikey.size = sizeof(group_id_t);
+
+ ret = groupstats->get(groupstats, NULL, &ikey, &ival, 0);
+ if (ret != 0)
+ continue;
+ if(ival.size != sizeof(struct groupstats))
+ continue;
+ memcpy(&gs, ival.data, sizeof(struct groupstats));
+
+ gi.low = gs.low;
+ gi.high = gs.high;
+ gi.count = gs.count;
+ gi.flag = gs.flag;
+ gi.expired = gs.expired;
+ gi.current_gid = gi.new_gid = gid;
+ gi.current_db = gi.new_db = v1_which_db(group);
+ gi.expiregrouppid = gi.status = 0;
+
+ val.data = &gi;
+ val.size = sizeof(gi);
+ ret = groupinfo->put(groupinfo, NULL, &key, &val, 0);
+ if (ret != 0) {
+ warn("groupinfo->put failed: %s", db_strerror(ret));
+ cursor->c_close(cursor);
+ return ret;
+ }
+ }
+ cursor->c_close(cursor);
+ if(ret != DB_NOTFOUND) {
+ warn("cursor->get failed: %s", db_strerror(ret));
+ return ret;
+ }
+
+ higid++;
+ if(higidbang > higid)
+ higid = higidbang;
+
+ key.data = (char *) "!groupid_freelist";
+ key.size = sizeof("!groupid_freelist");
+ val.data = &higid;
+ val.size = sizeof(group_id_t);
+
+ ret = groupinfo->put(groupinfo, NULL, &key, &val, 0);
+ if (ret != 0) {
+ warn("groupinfo->put failed: %s", db_strerror(ret));
+ return ret;
+ }
+
+ ret = open_db(&vdb, "version", DB_BTREE);
+ if (ret != 0)
+ return ret;
+
+ key.data = (char *) "dataversion";
+ key.size = sizeof("dataversion");
+ val.data = &v2;
+ val.size = sizeof v2;
+
+ ret = vdb->put(vdb, NULL, &key, &val, 0);
+ if (ret != 0) {
+ warn("version->put failed: %s", db_strerror(ret));
+ return ret;
+ }
+
+ groupstats->close(groupstats, 0);
+ groupsbyname->close(groupsbyname, 0);
+ groupinfo->close(groupinfo, 0);
+ vdb->close(vdb, 0);
+
+#if DB_VERSION_MAJOR >= 3
+ ret = db_create(&groupstats, OVDBenv, 0);
+ if (ret != 0)
+ return ret;
+ groupstats->remove(groupstats, "groupstats", NULL, 0);
+ ret = db_create(&groupsbyname, OVDBenv, 0);
+ if (ret != 0)
+ return ret;
+ groupsbyname->remove(groupsbyname, "groupsbyname", NULL, 0);
+#else
+ /* This won't work if someone changed DB_DATA_DIR in DB_CONFIG */
+ p = concatpath(ovdb_conf.home, "groupstats");
+ unlink(p);
+ free(p);
+ p = concatpath(ovdb_conf.home, "groupsbyname");
+ unlink(p);
+ free(p);
+#endif
+
+ return 0;
+}
+
+static int check_upgrade(int do_upgrade)
+{
+ int ret, i;
+ DB *db;
+ DBT key, val;
+ u_int32_t dv;
+ char name[50];
+
+ if(do_upgrade && (ret = upgrade_database("version")))
+ return ret;
+
+ ret = open_db(&db, "version", DB_BTREE);
+ if (ret != 0)
+ return ret;
+
+ memset(&key, 0, sizeof key);
+ memset(&val, 0, sizeof val);
+ key.data = (char *) "dataversion";
+ key.size = sizeof("dataversion");
+ ret = db->get(db, NULL, &key, &val, 0);
+ if (ret != 0) {
+ if(ret != DB_NOTFOUND) {
+ warn("cannot retrieve version: %s", db_strerror(ret));
+ db->close(db, 0);
+ return ret;
+ }
+ }
+ if(ret == DB_NOTFOUND || val.size != sizeof dv) {
+ dv = DATA_VERSION;
+
+ val.data = &dv;
+ val.size = sizeof dv;
+ ret = db->put(db, NULL, &key, &val, 0);
+ if (ret != 0) {
+ warn("cannot store version: %s", db_strerror(ret));
+ db->close(db, 0);
+ return ret;
+ }
+ } else
+ memcpy(&dv, val.data, sizeof dv);
+
+ key.data = (char *) "numdbfiles";
+ key.size = sizeof("numdbfiles");
+ if ((ret = db->get(db, NULL, &key, &val, 0)) == 0)
+ if(val.size == sizeof(ovdb_conf.numdbfiles))
+ memcpy(&(ovdb_conf.numdbfiles), val.data, sizeof(ovdb_conf.numdbfiles));
+ db->close(db, 0);
+
+ if(do_upgrade) {
+ if(dv == 1) {
+ ret = upgrade_database("groupstats");
+ if (ret != 0)
+ return ret;
+ ret = upgrade_database("groupsbyname");
+ if (ret != 0)
+ return ret;
+ } else {
+ ret = upgrade_database("groupinfo");
+ if (ret != 0)
+ return ret;
+ }
+ ret = upgrade_database("groupaliases");
+ if (ret != 0)
+ return ret;
+ for(i = 0; i < ovdb_conf.numdbfiles; i++) {
+ snprintf(name, sizeof(name), "ov%05d", i);
+ ret = upgrade_database(name);
+ if (ret != 0)
+ return ret;
+ }
+ }
+
+ if(dv > DATA_VERSION) {
+ warn("cannot open database: unknown version %d", dv);
+ return EINVAL;
+ }
+ if(dv < DATA_VERSION) {
+ if(do_upgrade)
+ return upgrade_v1_to_v2();
+
+ warn("database needs to be upgraded");
+ return EINVAL;
+ }
+ return 0;
+}
+
+int
+upgrade_environment(void)
+{
+ int ret;
+
+ ovdb_close_berkeleydb();
+ ret = ovdb_open_berkeleydb(OV_WRITE, OVDB_UPGRADE);
+ if (ret != 0)
+ return ret;
+#if DB_VERSION_MAJOR >= 3
+#if DB_VERSION_MAJOR == 3 && DB_VERSION_MINOR == 0
+ ret = OVDBenv->remove(OVDBenv, ovdb_conf.home, NULL, 0);
+#else
+ ret = OVDBenv->remove(OVDBenv, ovdb_conf.home, 0);
+#endif
+ if (ret != 0)
+ return ret;
+ OVDBenv = NULL;
+ ret = ovdb_open_berkeleydb(OV_WRITE, 0);
+#endif
+ return ret;
+}
+
+int main(int argc, char **argv)
+{
+ int ret, c, do_upgrade = 0, recover_only = 0, err = 0;
+ bool locked;
+ int flags;
+
+ openlog("ovdb_init", L_OPENLOG_FLAGS | LOG_PID, LOG_INN_PROG);
+ message_program_name = "ovdb_init";
+
+ if (!innconf_read(NULL))
+ exit(1);
+
+ if(strcmp(innconf->ovmethod, "ovdb"))
+ die("ovmethod not set to ovdb in inn.conf");
+
+ if(!ovdb_check_user())
+ die("command must be run as user " NEWSUSER);
+
+ chdir(innconf->pathtmp);
+ ovdb_errmode = OVDB_ERR_STDERR;
+
+ while((c = getopt(argc, argv, "ru")) != -1) {
+ switch(c) {
+ case 'r':
+ recover_only = 1;
+ break;
+ case 'u':
+ do_upgrade = 1;
+ break;
+ case '?':
+ warn("unrecognized option -%c", optopt);
+ err++;
+ break;
+ }
+ }
+ if(recover_only && do_upgrade) {
+ warn("cannot use both -r and -u at the same time");
+ err++;
+ }
+ if(err) {
+ fprintf(stderr, "Usage: ovdb_init [-r|-u]\n");
+ exit(1);
+ }
+
+ locked = ovdb_getlock(OVDB_LOCK_EXCLUSIVE);
+ if(locked) {
+ if(do_upgrade) {
+ notice("database is quiescent, upgrading");
+ flags = OVDB_RECOVER | OVDB_UPGRADE;
+ }
+ else {
+ notice("database is quiescent, running normal recovery");
+ flags = OVDB_RECOVER;
+ }
+ } else {
+ warn("database is active");
+ if(do_upgrade) {
+ warn("upgrade will not be attempted");
+ do_upgrade = 0;
+ }
+ if(recover_only)
+ die("recovery will not be attempted");
+ ovdb_getlock(OVDB_LOCK_ADMIN);
+ flags = 0;
+ }
+
+ ret = ovdb_open_berkeleydb(OV_WRITE, flags);
+ if(ret == DB_RUNRECOVERY) {
+ if(locked)
+ die("database could not be recovered");
+ else {
+ warn("database needs recovery but cannot be locked");
+ die("other processes accessing the database must exit to start"
+ " recovery");
+ }
+ }
+ if(ret != 0)
+ die("cannot open BerkeleyDB: %s", db_strerror(ret));
+
+ if(recover_only)
+ exit(0);
+
+ if(do_upgrade) {
+ ret = upgrade_environment();
+ if(ret != 0)
+ die("cannot upgrade BerkeleyDB environment: %s", db_strerror(ret));
+ }
+
+ if(check_upgrade(do_upgrade)) {
+ ovdb_close_berkeleydb();
+ exit(1);
+ }
+
+ ovdb_close_berkeleydb();
+ ovdb_releaselock();
+
+ if(ovdb_check_pidfile(OVDB_MONITOR_PIDFILE) == false) {
+ notice("starting ovdb monitor");
+ switch(fork()) {
+ case -1:
+ sysdie("cannot fork");
+ case 0:
+ setsid();
+ execl(concatpath(innconf->pathbin, "ovdb_monitor"),
+ "ovdb_monitor", SPACES, NULL);
+ syswarn("cannot exec ovdb_monitor");
+ _exit(1);
+ }
+ sleep(2); /* give the monitor a chance to start */
+ } else
+ warn("ovdb_monitor already running");
+
+ if(ovdb_conf.readserver) {
+ if(ovdb_check_pidfile(OVDB_SERVER_PIDFILE) == false) {
+ notice("starting ovdb server");
+ daemonize(innconf->pathtmp);
+ execl(concatpath(innconf->pathbin, "ovdb_server"), "ovdb_server",
+ SPACES, NULL);
+ syswarn("cannot exec ovdb_server");
+ _exit(1);
+ } else
+ warn("ovdb_server already running");
+ }
+
+ exit(0);
+}
+#endif /* USE_BERKELEY_DB */
+
--- /dev/null
+/*
+ * ovdb_monitor
+ * Performs database maintenance tasks
+ * + Transaction checkpoints
+ * + Deadlock detection
+ * + Transaction log removal
+ */
+
+#include "config.h"
+#include "clibrary.h"
+#include "portable/setproctitle.h"
+#include "portable/wait.h"
+#include <fcntl.h>
+#include <signal.h>
+#include <syslog.h>
+
+#include "inn/innconf.h"
+#include "inn/messages.h"
+#include "libinn.h"
+#include "ov.h"
+
+#include "../storage/ovdb/ovdb.h"
+#include "../storage/ovdb/ovdb-private.h"
+
+#ifndef USE_BERKELEY_DB
+
+int main(int argc UNUSED, char **argv UNUSED)
+{
+ exit(0);
+}
+
+#else /* USE_BERKELEY_DB */
+
+static int signalled = 0;
+static void sigfunc(int sig UNUSED)
+{
+ signalled = 1;
+}
+
+
+static pid_t deadlockpid = 0;
+static pid_t checkpointpid = 0;
+static pid_t logremoverpid = 0;
+
+static int putpid(const char *path)
+{
+ char buf[30];
+ int fd = open(path, O_WRONLY|O_TRUNC|O_CREAT, 0664);
+ if(!fd) {
+ syswarn("cannot open %s", path);
+ return -1;
+ }
+ snprintf(buf, sizeof(buf), "%d\n", getpid());
+ if(write(fd, buf, strlen(buf)) < 0) {
+ syswarn("cannot write to %s", path);
+ close(fd);
+ return -1;
+ }
+ close(fd);
+ return 0;
+}
+
+static void deadlock(void)
+{
+ int ret, status = 0;
+ u_int32_t atype = DB_LOCK_YOUNGEST;
+
+ if(ovdb_open_berkeleydb(OV_WRITE, 0))
+ _exit(1);
+
+ setproctitle("deadlock");
+
+ while(!signalled) {
+#if DB_VERSION_MAJOR == 2
+ ret = lock_detect(OVDBenv->lk_info, 0, atype);
+#elif DB_VERSION_MAJOR == 3
+ ret = lock_detect(OVDBenv, 0, atype, NULL);
+#else
+ ret = OVDBenv->lock_detect(OVDBenv, 0, atype, NULL);
+#endif
+ if(ret != 0) {
+ warn("OVDB: lock_detect: %s", db_strerror(ret));
+ status = 1;
+ break;
+ }
+ sleep(30);
+ }
+
+ ovdb_close_berkeleydb();
+ _exit(status);
+}
+
+static void checkpoint(void)
+{
+ int ret, status = 0;
+ DB *db;
+#if DB_VERSION_MAJOR == 2
+ DB_INFO dbinfo;
+#endif
+
+ if(ovdb_open_berkeleydb(OV_WRITE, 0))
+ _exit(1);
+
+ setproctitle("checkpoint");
+
+ /* Open a database and close it. This is so a necessary initialization
+ gets performed (by the db->open function). */
+
+#if DB_VERSION_MAJOR == 2
+ memset(&dbinfo, 0, sizeof dbinfo);
+ ret = db_open("version", DB_BTREE, DB_CREATE, 0666, OVDBenv, &dbinfo, &db);
+ if (ret != 0) {
+ warn("OVDB: checkpoint: db_open failed: %s", db_strerror(ret));
+ _exit(1);
+ }
+#else
+ ret = db_create(&db, OVDBenv, 0);
+ if (ret != 0) {
+ warn("OVDB: checkpoint: db_create: %s", db_strerror(ret));
+ _exit(1);
+ }
+#if DB_VERSION_MAJOR > 4 || (DB_VERSION_MAJOR == 4 && DB_VERSION_MINOR >= 1)
+ ret = db->open(db, NULL, "version", NULL, DB_BTREE, DB_CREATE, 0666);
+#else
+ ret = db->open(db, "version", NULL, DB_BTREE, DB_CREATE, 0666);
+#endif
+ if (ret != 0) {
+ db->close(db, 0);
+ warn("OVDB: checkpoint: version open: %s", db_strerror(ret));
+ _exit(1);
+ }
+#endif
+ db->close(db, 0);
+
+
+ while(!signalled) {
+#if DB_VERSION_MAJOR == 2
+ ret = txn_checkpoint(OVDBenv->tx_info, 2048, 1);
+#elif DB_VERSION_MAJOR == 3 && DB_VERSION_MINOR == 0
+ ret = txn_checkpoint(OVDBenv, 2048, 1);
+#elif DB_VERSION_MAJOR == 3
+ ret = txn_checkpoint(OVDBenv, 2048, 1, 0);
+#elif DB_VERSION_MAJOR == 4 && DB_VERSION_MINOR < 1
+ ret = OVDBenv->txn_checkpoint(OVDBenv, 2048, 1, 0);
+#else
+ OVDBenv->txn_checkpoint(OVDBenv, 2048, 1, 0);
+#endif
+#if DB_VERSION_MAJOR > 4 || (DB_VERSION_MAJOR == 4 && DB_VERSION_MINOR >= 1)
+ sleep(30);
+#else
+ if(ret != 0 && ret != DB_INCOMPLETE) {
+ warn("OVDB: txn_checkpoint: %s", db_strerror(ret));
+ status = 1;
+ break;
+ }
+ if(ret == DB_INCOMPLETE)
+ sleep(2);
+ else
+ sleep(30);
+#endif
+ }
+
+ ovdb_close_berkeleydb();
+ _exit(status);
+}
+
+static void logremover(void)
+{
+ int ret, status = 0;
+ char **listp, **p;
+
+ if(ovdb_open_berkeleydb(OV_WRITE, 0))
+ _exit(1);
+
+ setproctitle("logremover");
+
+ while(!signalled) {
+#if DB_VERSION_MAJOR == 2
+ ret = log_archive(OVDBenv->lg_info, &listp, DB_ARCH_ABS, malloc);
+#elif DB_VERSION_MAJOR == 3 && DB_VERSION_MINOR <= 2
+ ret = log_archive(OVDBenv, &listp, DB_ARCH_ABS, malloc);
+#elif DB_VERSION_MAJOR == 3
+ ret = log_archive(OVDBenv, &listp, DB_ARCH_ABS);
+#else
+ ret = OVDBenv->log_archive(OVDBenv, &listp, DB_ARCH_ABS);
+#endif
+ if(ret != 0) {
+ warn("OVDB: log_archive: %s", db_strerror(ret));
+ status = 1;
+ break;
+ }
+ if(listp != NULL) {
+ for(p = listp; *p; p++)
+ unlink(*p);
+ free(listp);
+ }
+ sleep(45);
+ }
+
+ ovdb_close_berkeleydb();
+ _exit(status);
+}
+
+static int start_process(pid_t *pid, void (*func)(void))
+{
+ pid_t child;
+
+ switch(child = fork()) {
+ case 0:
+ (*func)();
+ _exit(0);
+ case -1:
+ syswarn("cannot fork");
+ return -1;
+ default:
+ *pid = child;
+ return 0;
+ }
+ /*NOTREACHED*/
+}
+
+static void cleanup(int status)
+{
+ int cs;
+
+ if(deadlockpid)
+ kill(deadlockpid, SIGTERM);
+ if(checkpointpid)
+ kill(checkpointpid, SIGTERM);
+ if(logremoverpid)
+ kill(logremoverpid, SIGTERM);
+
+ xsignal(SIGINT, SIG_DFL);
+ xsignal(SIGTERM, SIG_DFL);
+ xsignal(SIGHUP, SIG_DFL);
+
+ if(deadlockpid)
+ waitpid(deadlockpid, &cs, 0);
+ if(checkpointpid)
+ waitpid(checkpointpid, &cs, 0);
+ if(logremoverpid)
+ waitpid(logremoverpid, &cs, 0);
+
+ unlink(concatpath(innconf->pathrun, OVDB_MONITOR_PIDFILE));
+ exit(status);
+}
+
+static void monitorloop(void)
+{
+ int cs, restartit;
+ pid_t child;
+
+ while(!signalled) {
+ child = waitpid(-1, &cs, WNOHANG);
+ if(child > 0) {
+ if(WIFSIGNALED(cs)) {
+ restartit = 0;
+ } else {
+ if(WEXITSTATUS(cs) == 0)
+ restartit = 1;
+ else
+ restartit = 0;
+ }
+ if(child == deadlockpid) {
+ deadlockpid = 0;
+ if(restartit && start_process(&deadlockpid, deadlock))
+ cleanup(1);
+ } else if(child == checkpointpid) {
+ checkpointpid = 0;
+ if(restartit && start_process(&checkpointpid, checkpoint))
+ cleanup(1);
+ } else if(child == logremoverpid) {
+ logremoverpid = 0;
+ if(restartit && start_process(&logremoverpid, logremover))
+ cleanup(1);
+ }
+ if(!restartit)
+ cleanup(1);
+ }
+ sleep(20);
+ }
+ cleanup(0);
+}
+
+
+int main(int argc, char **argv)
+{
+ char *pidfile;
+
+ setproctitle_init(argc, argv);
+
+ openlog("ovdb_monitor", L_OPENLOG_FLAGS | LOG_PID, LOG_INN_PROG);
+ message_program_name = "ovdb_monitor";
+
+ if(argc != 2 || strcmp(argv[1], SPACES))
+ die("should be started by ovdb_init");
+ message_handlers_warn(1, message_log_syslog_err);
+ message_handlers_die(1, message_log_syslog_err);
+
+ if (!innconf_read(NULL))
+ exit(1);
+
+ if(strcmp(innconf->ovmethod, "ovdb"))
+ die("ovmethod not set to ovdb in inn.conf");
+ if(!ovdb_check_user())
+ die("command must be run as user " NEWSUSER);
+ if(!ovdb_getlock(OVDB_LOCK_ADMIN))
+ die("cannot lock database");
+
+ xsignal(SIGINT, sigfunc);
+ xsignal(SIGTERM, sigfunc);
+ xsignal(SIGHUP, sigfunc);
+
+ pidfile = concatpath(innconf->pathrun, OVDB_MONITOR_PIDFILE);
+ if(putpid(pidfile))
+ exit(1);
+ if(start_process(&deadlockpid, deadlock))
+ cleanup(1);
+ if(start_process(&checkpointpid, checkpoint))
+ cleanup(1);
+ if(start_process(&logremoverpid, logremover))
+ cleanup(1);
+
+ monitorloop();
+
+ /* Never reached. */
+ return 1;
+}
+
+#endif /* USE_BERKELEY_DB */
+
--- /dev/null
+/*
+ * ovdb_server.c
+ * ovdb read server
+ */
+
+#include "config.h"
+#include "clibrary.h"
+#include "portable/mmap.h"
+#include "portable/time.h"
+#include "portable/setproctitle.h"
+#include "portable/socket.h"
+#include "portable/wait.h"
+#include <errno.h>
+#include <fcntl.h>
+#include <signal.h>
+#ifdef HAVE_SYS_SELECT_H
+# include <sys/select.h>
+#endif
+#include <syslog.h>
+
+#ifdef HAVE_UNIX_DOMAIN_SOCKETS
+# include <sys/un.h>
+#endif
+
+#include "inn/innconf.h"
+#include "inn/messages.h"
+#include "libinn.h"
+#include "paths.h"
+#include "storage.h"
+#include "ov.h"
+
+#include "../storage/ovdb/ovdb.h"
+#include "../storage/ovdb/ovdb-private.h"
+
+#ifndef USE_BERKELEY_DB
+
+int
+main(int argc UNUSED, char **argv UNUSED)
+{
+ die("BerkeleyDB support not compiled");
+}
+
+#else /* USE_BERKELEY_DB */
+
+
+#define SELECT_TIMEOUT 15
+
+
+/* This will work unless user sets a larger clienttimeout
+ in readers.conf */
+#define CLIENT_TIMEOUT (innconf->clienttimeout + 60)
+/*#define CLIENT_TIMEOUT 3600*/
+
+
+static int listensock;
+
+#define MODE_READ 0
+#define MODE_WRITE 1
+#define MODE_CLOSED 2
+#define STATE_READCMD 0
+#define STATE_READGROUP 1
+struct reader {
+ int fd;
+ int mode;
+ int state;
+ int buflen;
+ int bufpos;
+ void *buf;
+ time_t lastactive;
+ void *currentsearch;
+};
+
+static struct reader *readertab;
+static int readertablen;
+static int numreaders;
+static time_t now;
+static pid_t parent;
+
+struct child {
+ pid_t pid;
+ int num;
+ time_t started;
+};
+static struct child *children;
+#define wholistens (children[ovdb_conf.numrsprocs].num)
+
+static int signalled = 0;
+static void
+sigfunc(int sig UNUSED)
+{
+ signalled = 1;
+}
+
+static int updated = 0;
+static void
+childsig(int sig UNUSED)
+{
+ updated = 1;
+}
+
+static void
+parentsig(int sig UNUSED)
+{
+ int i, which, smallest;
+ if(wholistens < 0) {
+ which = smallest = -1;
+ for(i = 0; i < ovdb_conf.numrsprocs; i++) {
+ if(children[i].pid == -1)
+ continue;
+ if(!ovdb_conf.maxrsconn || children[i].num <= ovdb_conf.maxrsconn) {
+ if(smallest == -1 || children[i].num < smallest) {
+ smallest = children[i].num;
+ which = i;
+ }
+ }
+ }
+ if(which != -1) {
+ wholistens = which;
+ kill(children[which].pid, SIGUSR1);
+ } else {
+ wholistens = -2;
+ }
+ updated = 1;
+ }
+}
+
+static int putpid(const char *path)
+{
+ char buf[30];
+ int fd = open(path, O_WRONLY|O_TRUNC|O_CREAT, 0664);
+ if(fd == -1) {
+ syswarn("cannot open %s", path);
+ return -1;
+ }
+ snprintf(buf, sizeof(buf), "%d\n", getpid());
+ if(write(fd, buf, strlen(buf)) < 0) {
+ syswarn("cannot write to %s", path);
+ close(fd);
+ return -1;
+ }
+ close(fd);
+ return 0;
+}
+
+static void
+do_groupstats(struct reader *r)
+{
+ struct rs_groupstats *reply;
+ char *group = (char *)(r->buf) + sizeof(struct rs_cmd);
+ reply = xmalloc(sizeof(struct rs_groupstats));
+
+ /*syslog(LOG_DEBUG, "OVDB: rs: do_groupstats '%s'", group);*/
+ if(ovdb_groupstats(group, &reply->lo, &reply->hi, &reply->count, &reply->flag)) {
+ reply->status = CMD_GROUPSTATS;
+ reply->aliaslen = 0;
+ } else {
+ reply->status = CMD_GROUPSTATS | RPLY_ERROR;
+ }
+ free(r->buf);
+ r->buf = reply;
+ r->buflen = sizeof(struct rs_groupstats);
+ r->bufpos = 0;
+ r->mode = MODE_WRITE;
+}
+
+static void
+do_opensrch(struct reader *r)
+{
+ struct rs_cmd *cmd = r->buf;
+ struct rs_opensrch *reply;
+ char *group = (char *)(r->buf) + sizeof(struct rs_cmd);
+ reply = xmalloc(sizeof(struct rs_opensrch));
+
+ /*syslog(LOG_DEBUG, "OVDB: rs: do_opensrch '%s' %d %d", group, cmd->artlo, cmd->arthi);*/
+
+ if(r->currentsearch != NULL) {
+ /* can only open one search at a time */
+ reply->status = CMD_OPENSRCH | RPLY_ERROR;
+ } else {
+ reply->handle = ovdb_opensearch(group, cmd->artlo, cmd->arthi);
+ if(reply->handle == NULL) {
+ reply->status = CMD_OPENSRCH | RPLY_ERROR;
+ } else {
+ reply->status = CMD_OPENSRCH;
+ }
+ r->currentsearch = reply->handle;
+ }
+ free(r->buf);
+ r->buf = reply;
+ r->buflen = sizeof(struct rs_opensrch);
+ r->bufpos = 0;
+ r->mode = MODE_WRITE;
+}
+
+static void
+do_srch(struct reader *r)
+{
+ struct rs_cmd *cmd = r->buf;
+ struct rs_srch *reply;
+ ARTNUM artnum;
+ TOKEN token;
+ time_t arrived;
+ int len;
+ char *data;
+
+ if(ovdb_search(cmd->handle, &artnum, &data, &len, &token, &arrived)) {
+ reply = xmalloc(sizeof(struct rs_srch) + len);
+ reply->status = CMD_SRCH;
+ reply->artnum = artnum;
+ reply->token = token;
+ reply->arrived = arrived;
+ reply->len = len;
+ memcpy((char *)reply + sizeof(struct rs_srch), data, len);
+ r->buflen = sizeof(struct rs_srch) + len;
+ } else {
+ reply = xmalloc(sizeof(struct rs_srch));
+ reply->status = CMD_SRCH | RPLY_ERROR;
+ r->buflen = sizeof(struct rs_srch);
+ }
+ free(r->buf);
+ r->buf = reply;
+ r->bufpos = 0;
+ r->mode = MODE_WRITE;
+}
+
+static void
+do_closesrch(struct reader *r)
+{
+ struct rs_cmd *cmd = r->buf;
+
+ ovdb_closesearch(cmd->handle);
+ free(r->buf);
+ r->buf = NULL;
+ r->bufpos = r->buflen = 0;
+ r->mode = MODE_READ;
+ r->currentsearch = NULL;
+}
+
+static void
+do_artinfo(struct reader *r)
+{
+ struct rs_cmd *cmd = r->buf;
+ struct rs_artinfo *reply;
+ char *group = (char *)(r->buf) + sizeof(struct rs_cmd);
+ TOKEN token;
+
+ /*syslog(LOG_DEBUG, "OVDB: rs: do_artinfo: '%s' %d", group, cmd->artlo);*/
+ if(ovdb_getartinfo(group, cmd->artlo, &token)) {
+ reply = xmalloc(sizeof(struct rs_artinfo));
+ reply->status = CMD_ARTINFO;
+ reply->token = token;
+ r->buflen = sizeof(struct rs_artinfo);
+ } else {
+ reply = xmalloc(sizeof(struct rs_artinfo));
+ reply->status = CMD_ARTINFO | RPLY_ERROR;
+ r->buflen = sizeof(struct rs_artinfo);
+ }
+ free(r->buf);
+ r->buf = reply;
+ r->bufpos = 0;
+ r->mode = MODE_WRITE;
+}
+
+
+static int
+process_cmd(struct reader *r)
+{
+ struct rs_cmd *cmd = r->buf;
+
+ if(r->state == STATE_READCMD) {
+ switch(cmd->what) {
+ case CMD_GROUPSTATS:
+ case CMD_OPENSRCH:
+ case CMD_ARTINFO:
+ r->state = STATE_READGROUP;
+ if(cmd->grouplen == 0) {
+ /* shoudn't happen... */
+ r->mode = MODE_CLOSED;
+ close(r->fd);
+ free(r->buf);
+ r->buf = NULL;
+ return 0;
+ }
+ r->buflen += cmd->grouplen;
+ r->buf = xrealloc(r->buf, r->buflen);
+ return 1;
+ }
+ }
+
+ switch(cmd->what) {
+ case CMD_GROUPSTATS:
+ ((char *)r->buf)[r->buflen - 1] = 0; /* make sure group is null-terminated */
+ do_groupstats(r);
+ break;
+ case CMD_OPENSRCH:
+ ((char *)r->buf)[r->buflen - 1] = 0;
+ do_opensrch(r);
+ break;
+ case CMD_SRCH:
+ do_srch(r);
+ break;
+ case CMD_CLOSESRCH:
+ do_closesrch(r);
+ break;
+ case CMD_ARTINFO:
+ ((char *)r->buf)[r->buflen - 1] = 0;
+ do_artinfo(r);
+ break;
+ default:
+ r->mode = MODE_CLOSED;
+ close(r->fd);
+ free(r->buf);
+ r->buf = NULL;
+ break;
+ }
+
+ return 0;
+}
+
+static void
+handle_read(struct reader *r)
+{
+ int n;
+ r->lastactive = now;
+
+ if(r->buf == NULL) {
+ r->state = STATE_READCMD;
+ r->buf = xmalloc(sizeof(struct rs_cmd));
+ r->buflen = sizeof(struct rs_cmd);
+ r->bufpos = 0;
+ }
+again:
+ n = read(r->fd, (char *)(r->buf) + r->bufpos, r->buflen - r->bufpos);
+ if(n <= 0) {
+ if(n < 0 && (errno == EAGAIN || errno == EINTR || errno == EWOULDBLOCK))
+ return;
+ r->mode = MODE_CLOSED;
+ close(r->fd);
+ free(r->buf);
+ r->buf = NULL;
+ }
+ r->bufpos += n;
+
+ if(r->bufpos >= r->buflen)
+ if(process_cmd(r))
+ goto again;
+}
+
+static void
+handle_write(struct reader *r)
+{
+ int n;
+ r->lastactive = now;
+
+ if(r->buf == NULL) /* shouldn't happen */
+ return;
+
+ n = write(r->fd, (char *)(r->buf) + r->bufpos, r->buflen - r->bufpos);
+ if(n <= 0) {
+ if(n < 0 && (errno == EAGAIN || errno == EINTR || errno == EWOULDBLOCK))
+ return;
+ r->mode = MODE_CLOSED;
+ close(r->fd);
+ free(r->buf);
+ r->buf = NULL;
+ }
+ r->bufpos += n;
+
+ if(r->bufpos >= r->buflen) {
+ free(r->buf);
+ r->buf = NULL;
+ r->bufpos = r->buflen = 0;
+ r->mode = MODE_READ;
+ }
+}
+
+static void
+newclient(int fd)
+{
+ struct reader *r;
+ int i;
+
+ nonblocking(fd, 1);
+
+ if(numreaders >= readertablen) {
+ readertablen += 50;
+ readertab = xrealloc(readertab, readertablen * sizeof(struct reader));
+ for(i = numreaders; i < readertablen; i++) {
+ readertab[i].mode = MODE_CLOSED;
+ readertab[i].buf = NULL;
+ }
+ }
+
+ r = &(readertab[numreaders]);
+ numreaders++;
+
+ r->fd = fd;
+ r->mode = MODE_WRITE;
+ r->buflen = sizeof(OVDB_SERVER_BANNER);
+ r->bufpos = 0;
+ r->buf = xstrdup(OVDB_SERVER_BANNER);
+ r->lastactive = now;
+ r->currentsearch = NULL;
+
+ handle_write(r);
+}
+
+static void
+delclient(int which)
+{
+ int i;
+ struct reader *r = &(readertab[which]);
+
+ if(r->mode != MODE_CLOSED)
+ close(r->fd);
+
+ if(r->buf != NULL) {
+ free(r->buf);
+ }
+ if(r->currentsearch != NULL) {
+ ovdb_closesearch(r->currentsearch);
+ r->currentsearch = NULL;
+ }
+
+ /* numreaders will get decremented by the calling function */
+ for(i = which; i < numreaders-1; i++)
+ readertab[i] = readertab[i+1];
+
+ readertab[i].mode = MODE_CLOSED;
+ readertab[i].buf = NULL;
+}
+
+static pid_t
+serverproc(int me)
+{
+ fd_set rdset, wrset;
+ int i, ret, count, lastfd, lastnumreaders;
+ socklen_t salen;
+ struct sockaddr_in sa;
+ struct timeval tv;
+ pid_t pid;
+
+ pid = fork();
+ if (pid != 0)
+ return pid;
+
+ if (!ovdb_open(OV_READ|OVDB_SERVER))
+ die("cannot open overview");
+ xsignal_norestart(SIGINT, sigfunc);
+ xsignal_norestart(SIGTERM, sigfunc);
+ xsignal_norestart(SIGHUP, sigfunc);
+ xsignal_norestart(SIGUSR1, childsig);
+ xsignal(SIGPIPE, SIG_IGN);
+
+ numreaders = lastnumreaders = 0;
+ if(ovdb_conf.maxrsconn) {
+ readertablen = ovdb_conf.maxrsconn;
+ } else {
+ readertablen = 50;
+ }
+ readertab = xmalloc(readertablen * sizeof(struct reader));
+ for(i = 0; i < readertablen; i++) {
+ readertab[i].mode = MODE_CLOSED;
+ readertab[i].buf = NULL;
+ }
+
+ setproctitle("0 clients");
+
+ /* main loop */
+ while(!signalled) {
+ FD_ZERO(&rdset);
+ FD_ZERO(&wrset);
+ lastfd = 0;
+ if(wholistens == me) {
+ if(!ovdb_conf.maxrsconn || numreaders < ovdb_conf.maxrsconn) {
+ FD_SET(listensock, &rdset);
+ lastfd = listensock;
+ setproctitle("%d client%s *", numreaders,
+ numreaders == 1 ? "" : "s");
+ } else {
+ wholistens = -1;
+ kill(parent, SIGUSR1);
+ }
+ }
+
+ for(i = 0; i < numreaders; i++) {
+ switch(readertab[i].mode) {
+ case MODE_READ:
+ FD_SET(readertab[i].fd, &rdset);
+ break;
+ case MODE_WRITE:
+ FD_SET(readertab[i].fd, &wrset);
+ break;
+ default:
+ continue;
+ }
+ if(readertab[i].fd > lastfd)
+ lastfd = readertab[i].fd;
+ }
+ tv.tv_usec = 0;
+ tv.tv_sec = SELECT_TIMEOUT;
+ count = select(lastfd + 1, &rdset, &wrset, NULL, &tv);
+
+ if(signalled)
+ break;
+ if(count <= 0)
+ continue;
+
+ now = time(NULL);
+
+ if(FD_ISSET(listensock, &rdset)) {
+ if(!ovdb_conf.maxrsconn || numreaders < ovdb_conf.maxrsconn) {
+ salen = sizeof(sa);
+ ret = accept(listensock, (struct sockaddr *)&sa, &salen);
+ if(ret >= 0) {
+ newclient(ret);
+ wholistens = -1;
+ children[me].num = numreaders;
+ kill(parent, SIGUSR1);
+ }
+ }
+ }
+
+ for(i = 0; i < numreaders; i++) {
+ switch(readertab[i].mode) {
+ case MODE_READ:
+ if(FD_ISSET(readertab[i].fd, &rdset))
+ handle_read(&(readertab[i]));
+ break;
+ case MODE_WRITE:
+ if(FD_ISSET(readertab[i].fd, &wrset))
+ handle_write(&(readertab[i]));
+ break;
+ }
+ }
+
+ for(i = 0; i < numreaders; i++) {
+ if(readertab[i].mode == MODE_CLOSED
+ || readertab[i].lastactive + CLIENT_TIMEOUT < now) {
+ delclient(i);
+ numreaders--;
+ i--;
+ }
+ }
+ if(children[me].num != numreaders) {
+ children[me].num = numreaders;
+ kill(parent, SIGUSR1);
+ }
+ if(numreaders != lastnumreaders) {
+ lastnumreaders = numreaders;
+ setproctitle("%d client%s", numreaders,
+ numreaders == 1 ? "" : "s");
+ }
+ }
+
+ ovdb_close();
+ exit(0);
+}
+
+static int
+reap(void)
+{
+ int i, cs;
+ pid_t c;
+
+ while((c = waitpid(-1, &cs, WNOHANG)) > 0) {
+ for(i = 0; i < ovdb_conf.numrsprocs; i++) {
+ if(c == children[i].pid) {
+ if(children[i].started + 30 > time(NULL))
+ return 1;
+
+ children[i].num = 0;
+
+ if(wholistens == i)
+ wholistens = -1;
+
+ if((children[i].pid = serverproc(i)) == -1)
+ return 1;
+
+ children[i].started = time(NULL);
+ break;
+ }
+ }
+ }
+ if(wholistens == -1)
+ parentsig(SIGUSR1);
+ return 0;
+}
+
+#ifndef MAP_ANON
+#ifdef MAP_ANONYMOUS
+#define MAP_ANON MAP_ANONYMOUS
+#endif
+#endif
+
+static void *
+sharemem(size_t len)
+{
+#ifdef MAP_ANON
+ return mmap(0, len, PROT_READ|PROT_WRITE, MAP_ANON|MAP_SHARED, -1, 0);
+#else
+ int fd = open("/dev/zero", O_RDWR, 0);
+ char *ptr = mmap(0, len, PROT_READ|PROT_WRITE, MAP_SHARED, fd, 0);
+ close(fd);
+ return ptr;
+#endif
+}
+
+int
+main(int argc, char *argv[])
+{
+ int i, ret;
+ socklen_t salen;
+ char *path, *pidfile;
+#ifdef HAVE_UNIX_DOMAIN_SOCKETS
+ struct sockaddr_un sa;
+#else
+ struct sockaddr_in sa;
+#endif
+ struct timeval tv;
+ fd_set rdset;
+
+ setproctitle_init(argc, argv);
+
+ openlog("ovdb_server", L_OPENLOG_FLAGS | LOG_PID, LOG_INN_PROG);
+ message_program_name = "ovdb_server";
+
+ if(argc != 2 || strcmp(argv[1], SPACES))
+ die("should be started by ovdb_init");
+ message_handlers_warn(1, message_log_syslog_err);
+ message_handlers_die(1, message_log_syslog_err);
+
+ if (!innconf_read(NULL))
+ exit(1);
+
+ if(strcmp(innconf->ovmethod, "ovdb"))
+ die("ovmethod not set to ovdb in inn.conf");
+
+ read_ovdb_conf();
+
+#ifdef HAVE_UNIX_DOMAIN_SOCKETS
+ listensock = socket(AF_UNIX, SOCK_STREAM, 0);
+#else
+ listensock = socket(AF_INET, SOCK_STREAM, 0);
+#endif
+ if(listensock < 0)
+ sysdie("cannot create socket");
+
+ nonblocking(listensock, 1);
+
+#ifdef HAVE_UNIX_DOMAIN_SOCKETS
+ sa.sun_family = AF_UNIX;
+ path = concatpath(innconf->pathrun, OVDB_SERVER_SOCKET);
+ strlcpy(sa.sun_path, path, sizeof(sa.sun_path));
+ unlink(sa.sun_path);
+ free(path);
+ ret = bind(listensock, (struct sockaddr *)&sa, sizeof sa);
+#else
+ sa.sin_family = AF_INET;
+ sa.sin_port = htons(OVDB_SERVER_PORT);
+ sa.sin_addr.s_addr = htonl(INADDR_LOOPBACK);
+
+ ret = bind(listensock, (struct sockaddr *)&sa, sizeof sa);
+
+ if(ret != 0 && errno == EADDRNOTAVAIL) {
+ sa.sin_family = AF_INET;
+ sa.sin_port = htons(OVDB_SERVER_PORT);
+ sa.sin_addr.s_addr = INADDR_ANY;
+ ret = bind(listensock, (struct sockaddr *)&sa, sizeof sa);
+ }
+#endif
+
+ if(ret != 0)
+ sysdie("cannot bind socket");
+ if(listen(listensock, MAXLISTEN) < 0)
+ sysdie("cannot listen on socket");
+
+ pidfile = concatpath(innconf->pathrun, OVDB_SERVER_PIDFILE);
+ if(putpid(pidfile))
+ exit(1);
+
+ xsignal_norestart(SIGINT, sigfunc);
+ xsignal_norestart(SIGTERM, sigfunc);
+ xsignal_norestart(SIGHUP, sigfunc);
+
+ xsignal_norestart(SIGUSR1, parentsig);
+ xsignal_norestart(SIGCHLD, childsig);
+ parent = getpid();
+
+ children = sharemem(sizeof(struct child) * (ovdb_conf.numrsprocs+1));
+
+ if(children == NULL)
+ sysdie("cannot mmap shared memory");
+ for(i = 0; i < ovdb_conf.numrsprocs+1; i++) {
+ children[i].pid = -1;
+ children[i].num = 0;
+ }
+
+ for(i = 0; i < ovdb_conf.numrsprocs; i++) {
+ if((children[i].pid = serverproc(i)) == -1) {
+ for(i--; i >= 0; i--)
+ kill(children[i].pid, SIGTERM);
+ exit(1);
+ }
+ children[i].started = time(NULL);
+ sleep(1);
+ }
+
+ while(!signalled) {
+ if(reap())
+ break;
+
+ if(wholistens == -2) {
+ FD_ZERO(&rdset);
+ FD_SET(listensock, &rdset);
+ tv.tv_usec = 0;
+ tv.tv_sec = SELECT_TIMEOUT;
+ ret = select(listensock+1, &rdset, NULL, NULL, &tv);
+
+ if(ret == 1 && wholistens == -2) {
+ salen = sizeof(sa);
+ ret = accept(listensock, (struct sockaddr *)&sa, &salen);
+ if(ret >= 0)
+ close(ret);
+ }
+ } else {
+ pause();
+ }
+ }
+
+ for(i = 0; i < ovdb_conf.numrsprocs; i++)
+ if(children[i].pid != -1)
+ kill(children[i].pid, SIGTERM);
+
+ while(wait(&ret) > 0)
+ ;
+
+ unlink(pidfile);
+
+ exit(0);
+}
+
+
+#endif /* USE_BERKELEY_DB */
--- /dev/null
+/*
+ * ovdb_stat.c
+ * print information about ovdb database
+ */
+
+#include "config.h"
+#include "clibrary.h"
+#include <errno.h>
+#include <signal.h>
+#include <syslog.h>
+#include <time.h>
+
+#include "inn/innconf.h"
+#include "inn/messages.h"
+#include "libinn.h"
+#include "ov.h"
+#include "paths.h"
+#include "storage.h"
+
+#include "../storage/ovdb/ovdb.h"
+#include "../storage/ovdb/ovdb-private.h"
+
+
+#ifndef USE_BERKELEY_DB
+
+int main(int argc UNUSED, char **argv UNUSED)
+{
+ die("BerkeleyDB support not compiled");
+}
+
+#else /* USE_BERKELEY_DB */
+
+static int signalled = 0;
+static void sigfunc(int signum UNUSED)
+{
+ signalled = 1;
+}
+
+static int html = 0;
+
+typedef enum {
+ END,
+ INT32, /* 'a' points to u_int32_t */
+ HEX32, /* 'a' printed in hex */
+ DIFF32, /* 'a' - 'b' - 'c' */
+ PCT32, /* 100 * 'a' / ('a' + 'b') */
+ FF, /* 'a' = freebytes, 'b' = npages, 'c' = pagesize */
+ BYTES, /* 'a' = bytes, 'b' = mbytes, 'c' = gbytes */
+ MODE, /* 'a' points to int, printed as octal mode */
+ TIME, /* 'a' points to time_t, printed as date/time */
+ LSN, /* 'a' points to DB_LSN */
+ STR, /* 'a' points to char* */
+ SIZE /* 'a' points to size_t */
+} DATATYPE;
+
+struct datatab {
+ DATATYPE type;
+ ssize_t a;
+ ssize_t b;
+ ssize_t c;
+ const char *desc;
+};
+
+static void display_heading(const char *str)
+{
+ if(html)
+ printf("<h2>%s<h2>\n", str);
+ else
+ printf("%s\n", str);
+}
+
+
+static void getval(int i, void *p, struct datatab *tab, char *val, char *sufx)
+{
+ int mode = 0;
+ u_int32_t a = 0, b = 0, c = 0, bytes = 0, mbytes = 0, gbytes = 0;
+ char *cp = p;
+ char *tmp = NULL;
+ time_t tm = 0;
+ size_t sz = 0;
+ DB_LSN *dl = NULL;
+
+ val[0] = 0;
+ sufx[0] = 0;
+
+ switch(tab[i].type) {
+ case INT32: /* 'a' points to u_int32_t */
+ memcpy(&a, cp + tab[i].a, sizeof(a));
+ sprintf(val, "%u", a);
+ break;
+ case HEX32: /* 'a' printed in hex */
+ memcpy(&a, cp + tab[i].a, sizeof(a));
+ sprintf(val, "%x", a);
+ break;
+ case DIFF32: /* 'a' - 'b' - 'c' */
+ memcpy(&a, cp + tab[i].a, sizeof(a));
+ memcpy(&b, cp + tab[i].b, sizeof(b));
+ if(tab[i].c != -1) {
+ memcpy(&c, cp + tab[i].c, sizeof(c));
+ sprintf(val, "%d", a - b - c);
+ } else {
+ sprintf(val, "%d", a - b);
+ }
+ break;
+ case PCT32: /* 100 * 'a' / ('a' + 'b') */
+ memcpy(&a, cp + tab[i].a, sizeof(a));
+ memcpy(&b, cp + tab[i].b, sizeof(b));
+ sprintf(val, "%.0f", (double) a / (a + b) * 100.0);
+ strcpy(sufx, "%");
+ break;
+ case FF: /* 'a' = freebytes, 'b' = npages, 'c' = pagesize */
+ memcpy(&a, cp + tab[i].a, sizeof(a));
+ memcpy(&b, cp + tab[i].b, sizeof(b));
+ memcpy(&c, cp + tab[i].c, sizeof(c));
+ if(b == 0) {
+ sprintf(val, "%.0f", 0.0);
+ } else {
+ sprintf(val, "%.0f", (double)((b * c) - a) / (b * c) * 100);
+ }
+ strcpy(sufx, "%");
+ break;
+ case BYTES: /* 'a' = bytes, 'b' = mbytes, 'c' = gbytes */
+ if(tab[i].a != -1)
+ memcpy(&bytes, cp + tab[i].a, sizeof(bytes));
+ else
+ bytes = 0;
+ if(tab[i].b != -1)
+ memcpy(&mbytes, cp + tab[i].b, sizeof(mbytes));
+ else
+ mbytes = 0;
+ if(tab[i].c != -1)
+ memcpy(&gbytes, cp + tab[i].c, sizeof(gbytes));
+ else
+ gbytes = 0;
+ if(gbytes > 0 || mbytes > 0) {
+ mbytes += gbytes * 1024;
+ if(bytes > (1024*1024))
+ mbytes += bytes / (1024*1024);
+ sprintf(val, "%u", mbytes);
+ strcpy(sufx, "MB");
+ } else {
+ sprintf(val, "%u", bytes);
+ }
+ break;
+ case MODE: /* 'a' points to int, printed as octal mode */
+ memcpy(&mode, cp + tab[i].a, sizeof(mode));
+ sprintf(val, "%04o", mode);
+ break;
+ case TIME: /* 'a' points to time_t, printed as date/time */
+ memcpy(&tm, cp + tab[i].a, sizeof(tm));
+ if(tm == 0) {
+ strcpy(val, "none");
+ } else {
+ strftime(val, SMBUF, "%Y-%m-%d %T %Z", localtime(&tm));
+ }
+ break;
+ case LSN: /* 'a' points to DB_LSN */
+ dl = (DB_LSN *)(cp + tab[i].a);
+ if(dl->file == 0) {
+ strcpy(val, "none");
+ } else {
+ sprintf(val, "%u/%u", dl->file, dl->offset);
+ }
+ break;
+ case STR: /* 'a' points to char* */
+ memcpy(&tmp, cp + tab[i].a, sizeof(tmp));
+ strcpy(val, tmp);
+ break;
+ case SIZE: /* 'a' points to size_t */
+ memcpy(&sz, cp + tab[i].a, sizeof(sz));
+ sprintf(val, "%lu", (unsigned long) sz);
+ break;
+ case END:
+ break;
+ }
+}
+
+static char *myctime(time_t *tm)
+{
+ static char val[SMBUF];
+ strftime(val, SMBUF, "%Y-%m-%d %T %Z", localtime(tm));
+ return val;
+}
+
+static void display_data(void *p, struct datatab *tab)
+{
+ int i;
+ char val[SMBUF], sufx[SMBUF];
+ if(html)
+ puts("<table border=0 cellpadding=1>");
+ for(i = 0; tab[i].type != END; i++) {
+ getval(i, p, tab, val, sufx);
+ if(html)
+ printf("<tr><td align=right>%s<td>%s<td>%s\n", val, sufx, tab[i].desc);
+ else
+ printf("%16s%-2s %s\n", val, sufx, tab[i].desc);
+ }
+ if(html)
+ puts("</table><p>");
+}
+
+static void start_table(const char *label, struct datatab *tab)
+{
+ int i;
+ if(html) {
+ printf("<h2>%s</h2>\n", label);
+ puts("<table border=0 cellpadding=1>\n<tr bgcolor=#3399aa>");
+ for(i = 0; tab[i].type != END; i++)
+ printf("<th colspan=2>%s\n", tab[i].desc);
+ }
+}
+
+static void display_row(void *p, struct datatab *tab)
+{
+ int i;
+ char val[SMBUF], sufx[SMBUF];
+ if(html) {
+ puts("<tr>");
+ for(i = 0; tab[i].type != END; i++) {
+ getval(i, p, tab, val, sufx);
+ printf("<td align=right>%s<td>%s\n", val, sufx);
+ }
+ } else {
+ puts("---------------------------------------------");
+ display_data(p, tab);
+ }
+}
+
+static void end_table(void)
+{
+ if(html)
+ puts("</table><p>");
+}
+
+#define OFFSETOF(type, f) ((char *)&(((type *)0)->f) - (char *)0)
+
+#define F(f) OFFSETOF(DB_LOCK_STAT, f)
+
+static struct datatab LOCK_tab[] = {
+#if DB_VERSION_MAJOR >= 3
+ { INT32,
+#if DB_VERSION_MAJOR > 4 || (DB_VERSION_MAJOR == 4 && DB_VERSION_MINOR >= 1)
+ F(st_id),
+#else
+ F(st_lastid),
+#endif
+ -1, -1, "Last allocated locker ID" },
+#endif
+ { INT32, F(st_maxlocks), -1, -1, "Maximum number of locks possible" },
+#if DB_VERSION_MAJOR >= 4 || (DB_VERSION_MAJOR == 3 && DB_VERSION_MINOR >= 2)
+ { INT32, F(st_maxlockers), -1, -1, "Maximum number of lockers possible" },
+ { INT32, F(st_maxobjects), -1, -1, "Maximum number of objects possible" },
+#endif
+ { INT32, F(st_nmodes), -1, -1, "Lock modes" },
+#if DB_VERSION_MAJOR >= 4 || (DB_VERSION_MAJOR == 3 && DB_VERSION_MINOR >= 2)
+ { INT32, F(st_nlocks), -1, -1, "Current locks" },
+ { INT32, F(st_maxnlocks), -1, -1, "Maximum locks" },
+#endif
+ { INT32, F(st_nlockers), -1, -1, "Current lockers" },
+#if DB_VERSION_MAJOR >= 3
+ { INT32, F(st_maxnlockers), -1, -1, "Maximum lockers" },
+#else
+ { INT32, F(st_numobjs), -1, -1, "Lock objects" },
+#endif
+#if DB_VERSION_MAJOR >= 4 || (DB_VERSION_MAJOR == 3 && DB_VERSION_MINOR >= 2)
+ { INT32, F(st_nobjects), -1, -1, "Current objects" },
+ { INT32, F(st_maxnobjects), -1, -1, "Maximum objects" },
+#endif
+#if DB_VERSION_MAJOR == 4 && DB_VERSION_MINOR >= 4
+ { INT32, F(st_lock_wait), -1, -1, "Lock conflicts" },
+#else
+ { INT32, F(st_nconflicts), -1, -1, "Lock conflicts" },
+#endif
+ { INT32, F(st_nrequests), -1, -1, "Lock requests" },
+ { INT32, F(st_nreleases), -1, -1, "Lock releases" },
+ { DIFF32, F(st_nrequests), F(st_nreleases), F(st_ndeadlocks), "Outstanding locks" },
+#if DB_VERSION_MAJOR == 4 && DB_VERSION_MINOR >= 4
+ { INT32, F(st_lock_nowait), -1, -1, "Lock conflicts w/o subsequent wait" },
+#elif DB_VERSION_MAJOR >= 4 || (DB_VERSION_MAJOR == 3 && DB_VERSION_MINOR > 0)
+ { INT32, F(st_nnowaits), -1, -1, "Lock conflicts w/o subsequent wait" },
+#endif
+ { INT32, F(st_ndeadlocks), -1, -1, "Deadlocks" },
+#if DB_VERSION_MAJOR >= 4
+ { INT32, F(st_nlocktimeouts), -1, -1, "Lock timeouts" },
+ { INT32, F(st_ntxntimeouts), -1, -1, "Transaction timeouts" },
+#endif
+ { INT32, F(st_region_nowait), -1, -1, "Region locks granted without waiting" },
+ { INT32, F(st_region_wait), -1, -1, "Region locks granted after waiting" },
+ { BYTES, F(st_regsize), -1, -1, "Lock region size" },
+ { END, -1, -1, -1, NULL }
+};
+
+static int display_lock(void)
+{
+ DB_LOCK_STAT *sp;
+
+#if DB_VERSION_MAJOR == 2
+ if(lock_stat(OVDBenv->lk_info, &sp, NULL) != 0)
+#elif DB_VERSION_MAJOR == 3 && DB_VERSION_MINOR <= 2
+ if(lock_stat(OVDBenv, &sp, NULL) != 0)
+#elif DB_VERSION_MAJOR == 3
+ if(lock_stat(OVDBenv, &sp) != 0)
+#else
+ if(OVDBenv->lock_stat(OVDBenv, &sp, 0) != 0)
+#endif
+ return 1;
+
+ display_heading("Lock Region Statistics");
+ display_data(sp, LOCK_tab);
+
+ free(sp);
+ return 0;
+}
+
+
+#undef F
+#define F(f) OFFSETOF(DB_LOG_STAT, f)
+
+static struct datatab LOG_tab[] = {
+ { HEX32, F(st_magic), -1, -1, "Log magic number" },
+ { INT32, F(st_version), -1, -1, "Log version number" },
+ { MODE, F(st_mode), -1, -1, "Log file mode" },
+#if DB_VERSION_MAJOR >= 3
+ { BYTES, F(st_lg_bsize), -1, -1, "Log record cache size" },
+#endif
+#if DB_VERSION_MAJOR > 4 || (DB_VERSION_MAJOR == 4 && DB_VERSION_MINOR >= 1)
+ { BYTES, F(st_lg_size), -1, -1, "The current log file size" },
+#else
+ { BYTES, F(st_lg_max), -1, -1, "Max log file size" },
+#endif
+ { BYTES, F(st_w_bytes), F(st_w_mbytes), -1, "Log bytes written" },
+ { BYTES, F(st_wc_bytes), F(st_wc_mbytes), -1, "Log bytes written since last checkpoint" },
+ { INT32, F(st_wcount), -1, -1, "Total log writes" },
+#if DB_VERSION_MAJOR >= 3
+ { INT32, F(st_wcount_fill), -1, -1, "Total log writes due to overflow" },
+#endif
+ { INT32, F(st_scount), -1, -1, "Total log flushes" },
+ { INT32, F(st_region_nowait), -1, -1, "Region locks granted without waiting" },
+ { INT32, F(st_region_wait), -1, -1, "Region locks granted after waiting" },
+ { INT32, F(st_cur_file), -1, -1, "Current log file number" },
+ { INT32, F(st_cur_offset), -1, -1, "Current log file offset" },
+#if DB_VERSION_MAJOR >= 4 || (DB_VERSION_MAJOR == 3 && DB_VERSION_MINOR >= 3)
+ { INT32, F(st_disk_file), -1, -1, "Known on disk log file number" },
+ { INT32, F(st_disk_offset), -1, -1, "Known on disk log file offset" },
+#endif
+ { BYTES, F(st_regsize), -1, -1, "Log region size" },
+#if DB_VERSION_MAJOR >= 4
+#if DB_VERSION_MINOR < 1
+ { INT32, F(st_flushcommit), -1, -1, "Flushes containing a commit"},
+#endif
+ { INT32, F(st_maxcommitperflush), -1, -1, "Max number of commits in a flush"},
+ { INT32, F(st_mincommitperflush), -1, -1, "Min number of commits in a flush"},
+#endif
+ { END, -1, -1, -1, NULL }
+};
+
+static int display_log(void)
+{
+ DB_LOG_STAT *sp;
+
+#if DB_VERSION_MAJOR == 2
+ if(log_stat(OVDBenv->lg_info, &sp, NULL) != 0)
+#elif DB_VERSION_MAJOR == 3 && DB_VERSION_MINOR <= 2
+ if(log_stat(OVDBenv, &sp, NULL) != 0)
+#elif DB_VERSION_MAJOR == 3
+ if(log_stat(OVDBenv, &sp) != 0)
+#else
+ if(OVDBenv->log_stat(OVDBenv, &sp, 0) != 0)
+#endif
+ return 1;
+
+ display_heading("Log Region Statistics");
+ display_data(sp, LOG_tab);
+
+ free(sp);
+ return 0;
+}
+
+
+#undef F
+#define F(f) OFFSETOF(DB_MPOOL_STAT, f)
+
+static struct datatab MEM_tab[] = {
+ { INT32, F(st_cache_hit), -1, -1, "Cache hits"},
+ { INT32, F(st_cache_miss), -1, -1, "Cache misses"},
+ { PCT32, F(st_cache_hit), F(st_cache_miss), -1, "Cache hit percentage"},
+#if DB_VERSION_MAJOR == 2
+ { INT32, F(st_cachesize), -1, -1, "Total cache size"},
+ { INT32, F(st_regsize), -1, -1, "Pool region size"},
+#else
+ { BYTES, F(st_bytes), -1, F(st_gbytes), "Total cache size"},
+#if DB_VERSION_MAJOR == 3 && DB_VERSION_MINOR == 0
+ { INT32, F(st_regsize), -1, -1, "Pool region size"},
+#else
+ { INT32, F(st_ncache), -1, -1, "Number of caches"},
+ { INT32, F(st_regsize), -1, -1, "Pool individual cache size"},
+#endif
+#endif
+ { INT32, F(st_map), -1, -1, "Memory mapped pages"},
+ { INT32, F(st_page_create), -1, -1, "Pages created in the cache"},
+ { INT32, F(st_page_in), -1, -1, "Pages read into the cache"},
+ { INT32, F(st_page_out), -1, -1, "Pages written from the cache to the backing file"},
+ { INT32, F(st_ro_evict), -1, -1, "Clean pages forced from the cache"},
+ { INT32, F(st_rw_evict), -1, -1, "Dirty pages forced from the cache"},
+ { INT32, F(st_hash_buckets), -1, -1, "Hash buckets used for page location"},
+ { INT32, F(st_hash_searches), -1, -1, "Total hash chain searches"},
+ { INT32, F(st_hash_longest), -1, -1, "Longest hash chain searched"},
+ { INT32, F(st_hash_examined), -1, -1, "Total hash entries searched"},
+ { INT32, F(st_page_trickle), -1, -1, "Dirty buffers written by trickle-sync thread"},
+ { INT32, F(st_page_clean), -1, -1, "Current clean buffer count"},
+ { INT32, F(st_page_dirty), -1, -1, "Current dirty buffer count"},
+ { INT32, F(st_region_nowait), -1, -1, "Region locks granted without waiting"},
+ { INT32, F(st_region_wait), -1, -1, "Region locks granted after waiting"},
+ { END, -1, -1, -1, NULL }
+};
+
+#undef F
+#define F(f) OFFSETOF(DB_MPOOL_FSTAT, f)
+
+static struct datatab MEMF_tab[] = {
+ { STR, F(file_name), -1, -1, "Database"},
+ { SIZE, F(st_pagesize), -1, -1, "Page size"},
+ { INT32, F(st_cache_hit), -1, -1, "Cache hits"},
+ { INT32, F(st_cache_miss), -1, -1, "Cache misses"},
+ { PCT32, F(st_cache_hit), F(st_cache_miss), -1, "Cache hit percentage"},
+ { INT32, F(st_map), -1, -1, "Memory mapped pages"},
+ { INT32, F(st_page_create), -1, -1, "Pages created in the cache"},
+ { INT32, F(st_page_in), -1, -1, "Pages read into the cache"},
+ { INT32, F(st_page_out), -1, -1, "Pages written from the cache to the backing file"},
+ { END, -1, -1, -1, NULL }
+};
+
+static int display_mem(int all)
+{
+ DB_MPOOL_FSTAT **fsp;
+ DB_MPOOL_STAT *gsp;
+
+#if DB_VERSION_MAJOR == 2
+ if(memp_stat(OVDBenv->mp_info, &gsp, &fsp, NULL) != 0)
+#elif DB_VERSION_MAJOR == 3 && DB_VERSION_MINOR <= 2
+ if(memp_stat(OVDBenv, &gsp, &fsp, NULL) != 0)
+#elif DB_VERSION_MAJOR == 3
+ if(memp_stat(OVDBenv, &gsp, &fsp) != 0)
+#else
+ if(OVDBenv->memp_stat(OVDBenv, &gsp, &fsp, 0) != 0)
+#endif
+ return 1;
+
+ display_heading("Memory Pool Statistics");
+ display_data(gsp, MEM_tab);
+
+ if(all) {
+ DB_MPOOL_FSTAT **p = fsp;
+
+ start_table("Per-database Memory Pool Statistics", MEMF_tab);
+ for(; p != NULL && *p != NULL; ++p) {
+ display_row(*p, MEMF_tab);
+ }
+ end_table();
+ }
+
+ free(fsp);
+ free(gsp);
+ return 0;
+}
+
+static int txn_compare(const void *a, const void *b)
+{
+ if (((const DB_TXN_ACTIVE *)a)->txnid > ((const DB_TXN_ACTIVE *)b)->txnid)
+ return 1;
+ if (((const DB_TXN_ACTIVE *)a)->txnid < ((const DB_TXN_ACTIVE *)b)->txnid)
+ return -1;
+ return 0;
+}
+
+#undef F
+#define F(f) OFFSETOF(DB_TXN_STAT, f)
+
+static struct datatab TXN_tab[] = {
+ { LSN, F(st_last_ckp), -1, -1, "File/offset for last checkpoint LSN" },
+#if DB_VERSION_MAJOR < 4 || (DB_VERSION_MAJOR == 4 && DB_VERSION_MINOR < 1)
+ { LSN, F(st_pending_ckp), -1, -1, "File/offset for last pending checkpoint LSN" },
+#endif
+ { TIME, F(st_time_ckp), -1, -1, "Checkpoint timestamp" },
+ { HEX32, F(st_last_txnid), -1, -1, "Last transaction ID allocated" },
+ { INT32, F(st_maxtxns), -1, -1, "Maximum active transactions possible" },
+ { INT32, F(st_nactive), -1, -1, "Active transactions" },
+#if DB_VERSION_MAJOR >= 4 || (DB_VERSION_MAJOR == 3 && DB_VERSION_MINOR >= 3)
+ { INT32, F(st_nrestores), -1, -1, "Restored transactions after recovery" },
+#endif
+#if DB_VERSION_MAJOR >= 3
+ { INT32, F(st_maxnactive), -1, -1, "Maximum active transactions" },
+#endif
+ { INT32, F(st_nbegins), -1, -1, "Transactions started" },
+ { INT32, F(st_ncommits), -1, -1, "Transactions committed" },
+ { INT32, F(st_naborts), -1, -1, "Transactions aborted" },
+ { INT32, F(st_region_nowait), -1, -1, "Region locks granted without waiting"},
+ { INT32, F(st_region_wait), -1, -1, "Region locks granted after waiting"},
+ { BYTES, F(st_regsize), -1, -1, "Transaction region size" },
+ { END, -1, -1, -1, NULL }
+};
+
+#undef F
+#define F(f) OFFSETOF(DB_TXN_ACTIVE, f)
+
+static struct datatab TXNA_tab[] = {
+ { INT32, F(txnid), -1, -1, "Transaction ID" },
+#if DB_VERSION_MAJOR >= 3
+ { INT32, F(parentid), -1, -1, "Parent Transaction ID" },
+#endif
+ { LSN, F(lsn), -1, -1, "Initial LSN file/offset" },
+ { END, -1, -1, -1, NULL }
+};
+
+static int display_txn(void)
+{
+ DB_TXN_STAT *sp;
+ u_int32_t i;
+
+#if DB_VERSION_MAJOR == 2
+ if(txn_stat(OVDBenv->tx_info, &sp, NULL) != 0)
+#elif DB_VERSION_MAJOR == 3 && DB_VERSION_MINOR <= 2
+ if(txn_stat(OVDBenv, &sp, NULL) != 0)
+#elif DB_VERSION_MAJOR == 3
+ if(txn_stat(OVDBenv, &sp) != 0)
+#else
+ if(OVDBenv->txn_stat(OVDBenv, &sp, 0) != 0)
+#endif
+ return 1;
+
+ display_heading("Transaction Region Statistics");
+ display_data(sp, TXN_tab);
+
+ if(sp->st_nactive) {
+ qsort(sp->st_txnarray, sp->st_nactive, sizeof(sp->st_txnarray[0]), txn_compare);
+ start_table("Active Transactions", TXNA_tab);
+ for (i = 0; i < sp->st_nactive; ++i)
+ display_row(&(sp->st_txnarray[i]), TXNA_tab);
+ end_table();
+ }
+ free(sp);
+ return 0;
+}
+
+static int display_ver(void)
+{
+ if(html) puts("<p>");
+ printf("ovdb data version: %d\n", DATA_VERSION);
+ if(html) puts("<br>");
+ printf("BerkeleyDB version: %s\n", db_version(NULL,NULL,NULL));
+ if(html) puts("<p>");
+ return 0;
+}
+
+#undef F
+#define F(f) OFFSETOF(DB_BTREE_STAT, f)
+
+static struct datatab BTREE_tab[] = {
+ { HEX32, F(bt_magic), -1, -1, "Btree magic number" },
+ { INT32, F(bt_version), -1, -1, "Btree version number" },
+ { INT32, F(bt_minkey), -1, -1, "Minimum keys per page (minkey)" },
+ { INT32, F(bt_pagesize), -1, -1, "Database page size" },
+ { INT32, F(bt_levels), -1, -1, "Levels in the tree" },
+#if DB_VERSION_MAJOR == 2 || (DB_VERSION_MAJOR == 3 && DB_VERSION_MINOR == 0)
+ { INT32, F(bt_nrecs), -1, -1, "Keys in the tree" },
+#else
+ { INT32, F(bt_nkeys), -1, -1, "Unique keys in the tree" },
+ { INT32, F(bt_ndata), -1, -1, "Data items in the tree" },
+#endif
+ { INT32, F(bt_int_pg), -1, -1, "Tree internal pages" },
+ { BYTES, F(bt_int_pgfree), -1, -1, "Bytes free in internal pages" },
+ { FF, F(bt_int_pgfree), F(bt_int_pg), F(bt_pagesize), "Internal page fill factor" },
+
+ { INT32, F(bt_leaf_pg), -1, -1, "Tree leaf pages" },
+ { BYTES, F(bt_leaf_pgfree), -1, -1, "Bytes free in leaf pages" },
+ { FF, F(bt_leaf_pgfree), F(bt_leaf_pg), F(bt_pagesize), "Leaf page fill factor" },
+
+ { INT32, F(bt_dup_pg), -1, -1, "Tree duplicate pages" },
+ { BYTES, F(bt_dup_pgfree), -1, -1, "Bytes free in duplicate pages" },
+ { FF, F(bt_dup_pgfree), F(bt_dup_pg), F(bt_pagesize), "Duplicate page fill factor" },
+
+ { INT32, F(bt_over_pg), -1, -1, "Tree overflow pages" },
+ { BYTES, F(bt_over_pgfree), -1, -1, "Bytes free overflow pages" },
+ { FF, F(bt_over_pgfree), F(bt_over_pg), F(bt_pagesize), "Overflow page fill factor" },
+
+#if DB_VERSION_MAJOR >= 3
+ { INT32, F(bt_free), -1, -1, "Pages on the free list" },
+#endif
+ { END, -1, -1, -1, NULL }
+};
+
+static int display_btree(DB *db)
+{
+ DB_BTREE_STAT *sp;
+
+#if DB_VERSION_MAJOR > 4 || (DB_VERSION_MAJOR == 4 && DB_VERSION_MINOR >= 3)
+ if(db->stat(db, NULL, &sp, 0))
+#else
+#if DB_VERSION_MAJOR == 4 || (DB_VERSION_MAJOR == 3 && DB_VERSION_MINOR >= 3)
+ if(db->stat(db, &sp, 0))
+#else
+ if(db->stat(db, &sp, NULL, 0))
+#endif
+#endif
+ return 1;
+
+ display_heading("Btree Statistics");
+ display_data(sp, BTREE_tab);
+
+ free(sp);
+ return 0;
+}
+
+
+#if DB_VERSION_MAJOR >= 3
+
+#undef F
+#define F(f) OFFSETOF(DB_HASH_STAT, f)
+
+static struct datatab HASH_tab[] = {
+ { HEX32, F(hash_magic), -1, -1, "Hash magic number" },
+ { INT32, F(hash_version), -1, -1, "Hash version number" },
+ { INT32, F(hash_pagesize), -1, -1, "Database page size" },
+#if DB_VERSION_MAJOR == 3 && DB_VERSION_MINOR == 0
+ { INT32, F(hash_nrecs), -1, -1, "Keys in the database" },
+#else
+ { INT32, F(hash_nkeys), -1, -1, "Keys in the database" },
+ { INT32, F(hash_ndata), -1, -1, "Data items in the database" },
+#endif
+ { INT32, F(hash_buckets), -1, -1, "Hash buckets" },
+ { BYTES, F(hash_bfree), -1, -1, "Bytes free on bucket pages" },
+ { FF, F(hash_buckets), F(hash_bfree), F(hash_pagesize), "Bucket page fill factor" },
+
+ { INT32, F(hash_bigpages), -1, -1, "Overflow pages" },
+ { BYTES, F(hash_big_bfree), -1, -1, "Bytes free on Overflow pages" },
+ { FF, F(hash_bigpages), F(hash_big_bfree), F(hash_pagesize), "Overflow page fill factor" },
+
+ { INT32, F(hash_overflows), -1, -1, "Bucket overflow pages" },
+ { BYTES, F(hash_ovfl_free), -1, -1, "Bytes free on bucket overflow pages" },
+ { FF, F(hash_overflows), F(hash_ovfl_free), F(hash_pagesize), "Bucket overflow page fill factor" },
+
+ { INT32, F(hash_dup), -1, -1, "Duplicate pages" },
+ { BYTES, F(hash_dup_free), -1, -1, "Bytes free in duplicate pages" },
+ { FF, F(hash_dup), F(hash_dup_free), F(hash_pagesize), "Duplicate page fill factor" },
+
+ { INT32, F(hash_free), -1, -1, "Pages on the free list"},
+ { END, -1, -1, -1, NULL }
+};
+#endif
+
+static int display_hash(DB *db UNUSED)
+{
+#if DB_VERSION_MAJOR == 2
+ printf("Hash statistics not available.\n");
+ return 0;
+#else
+ DB_HASH_STAT *sp;
+
+#if DB_VERSION_MAJOR > 4 || (DB_VERSION_MAJOR == 4 && DB_VERSION_MINOR >= 3)
+ if(db->stat(db, NULL, &sp, 0))
+#else
+#if DB_VERSION_MAJOR == 3 && DB_VERSION_MINOR <= 2
+ if(db->stat(db, &sp, NULL, 0))
+#else
+ if(db->stat(db, &sp, 0))
+#endif
+#endif
+ return 1;
+
+ display_heading("Hash Information");
+ display_data(sp, HASH_tab);
+
+ return 0;
+#endif
+}
+
+static int display_db(char *dbfile)
+{
+ int ret;
+ DB *db;
+
+#if DB_VERSION_MAJOR == 2
+ if(db_open(dbfile, DB_UNKNOWN, DB_RDONLY, 0, OVDBenv, NULL, &db))
+ return 1;
+#else
+ if(db_create(&db, OVDBenv, 0))
+ return 1;
+#if DB_VERSION_MAJOR > 4 || (DB_VERSION_MAJOR == 4 && DB_VERSION_MINOR >= 1)
+ if(db->open(db, NULL, dbfile, NULL, DB_UNKNOWN, DB_RDONLY, 0))
+#else
+ if(db->open(db, dbfile, NULL, DB_UNKNOWN, DB_RDONLY, 0))
+#endif
+ return 1;
+#endif
+
+ switch(db->type) {
+ case DB_BTREE:
+ case DB_RECNO:
+ ret = display_btree(db);
+ break;
+ case DB_HASH:
+ ret = display_hash(db);
+ break;
+ default:
+ ret = 1;
+ break;
+ }
+ db->close(db, 0);
+ return ret;
+}
+
+static int parse_artrange(char *str, ARTNUM *start, ARTNUM *stop)
+{
+ char *c;
+ int i;
+
+ c = strchr(str, '-');
+ if(c == NULL) {
+ i = atoi(str);
+ if(i == 0) {
+ return 1;
+ }
+ *start = *stop = i;
+ return 0;
+ }
+ if(c == str) {
+ *start = 0;
+ *stop = atoi(str+1);
+ return (*stop == 0);
+ }
+ if (strlen(str) == (size_t)(c - str + 1)) {
+ *start = atoi(str);
+ *stop = 0xffffffff;
+ return (*start == 0);
+ }
+ *start = atoi(str);
+ *stop = atoi(c+1);
+ if(*start == 0 || *stop == 0 || *start > *stop)
+ return 1;
+
+ return 0;
+}
+
+static void htwrite(char *data, int len)
+{
+ int i;
+ for(i = 0; i < len; i++) {
+ switch(data[i]) {
+ case '<':
+ case '>':
+ case '&':
+ printf("&#%d;", (int)data[i]);
+ break;
+ default:
+ putchar(data[i]);
+ }
+ }
+}
+
+int main(int argc, char *argv[])
+{
+ void *s;
+ ARTNUM a, start=0, stop=0, low, high;
+ char *data, *disp_db = NULL;
+ int len, c, count, flag, lowi, highi;
+ int getgs=0, getcount=0, getinfo=0, err=0, gotone=0;
+ int disp_lock=0, disp_log=0, disp_mem=0, disp_mem_all=0, disp_txn=0, disp_ver=0;
+ int needng=0, o;
+
+ openlog("ovdb_stat", L_OPENLOG_FLAGS | LOG_PID, LOG_INN_PROG);
+ message_program_name = "ovdb_stat";
+
+ if (!innconf_read(NULL))
+ exit(1);
+
+ if(!ovdb_check_user())
+ die("command must be run as user " NEWSUSER);
+ if(!ovdb_getlock(OVDB_LOCK_ADMIN))
+ sysdie("cannot lock database");
+ if(!ovdb_open(OV_READ|OVDB_SERVER))
+ sysdie("cannot open overview; check syslog for OVDB messages");
+
+ xsignal(SIGINT, sigfunc);
+ xsignal(SIGTERM, sigfunc);
+ xsignal(SIGHUP, sigfunc);
+
+ while((c = getopt(argc, argv, ":Hgcir:klmMtvd:")) != -1) {
+ switch(c) {
+ case 'H':
+ html = 1;
+ break;
+ case 'g':
+ getgs = 1;
+ needng = 1;
+ gotone++;
+ break;
+ case 'c':
+ getcount = 1;
+ needng = 1;
+ gotone++;
+ break;
+ case 'i':
+ getinfo = 1;
+ needng = 1;
+ gotone++;
+ break;
+ case 'r':
+ if(parse_artrange(optarg, &start, &stop))
+ err++;
+ needng = 1;
+ gotone++;
+ break;
+ case 'k':
+ disp_lock = 1;
+ gotone++;
+ break;
+ case 'l':
+ disp_log = 1;
+ gotone++;
+ break;
+ case 'm':
+ disp_mem = 1;
+ gotone++;
+ break;
+ case 'M':
+ disp_mem = 1;
+ disp_mem_all = 1;
+ gotone++;
+ break;
+ case 't':
+ disp_txn = 1;
+ gotone++;
+ break;
+ case 'v':
+ disp_ver = 1;
+ gotone++;
+ break;
+ case 'd':
+ disp_db = optarg;
+ gotone++;
+ break;
+ case ':':
+ warn("option -%c requires an argument", optopt);
+ err++;
+ break;
+ case '?':
+ warn("unrecognized option -%c", optopt);
+ err++;
+ break;
+ }
+ }
+ if(!gotone) {
+ err++;
+ } else if(optind == argc && needng) {
+ warn("missing newsgroup argument(s)");
+ err++;
+ }
+ if(err) {
+ fprintf(stderr, "\
+Usage:\n\
+ ovdb_stat -Hgci [-r artnum] newsgroup [newsgroup ...]\n\
+ -H : output in HTML\n\
+ -g : show groupstats info\n\
+ -c : show groupstats info by counting actual records\n\
+ -i : show additional group info\n\
+ -r artnum-range : retrieve OV records for article number range\n\
+\n\
+ ovdb_stat -Hklmtv [-d <database>]\n\
+ -H : output in HTML\n\
+ -k : Display lock region statistics\n\
+ -l : Display log region statistics\n\
+ -m : Display global memory cache statistics\n\
+ -M : Display all memory cache statistics\n\
+ -t : Display transaction statistics\n\
+ -v : Display version information\n\
+ -d database : Display statistics of specified database\n");
+
+ goto out;
+ }
+
+ if(html)
+ puts("<html><head><title>ovdb_stat</title></head><body><p>");
+ if(disp_lock)
+ display_lock();
+ if(disp_log)
+ display_log();
+ if(disp_mem)
+ display_mem(disp_mem_all);
+ if(disp_txn)
+ display_txn();
+ if(disp_ver)
+ display_ver();
+ if(disp_db)
+ display_db(disp_db);
+
+ if(getgs || getcount || getinfo) {
+ if(html) {
+ puts("<table border=0 cellpadding=1 width=90%>\n<tr bgcolor=#3399aa>");
+ puts("<th rowspan=2>Group");
+ if(getgs)
+ puts("<th colspan=4>Groupstats");
+ if(getcount)
+ puts("<th colspan=3>Counted");
+ if(getinfo)
+ puts("<th>Status<th colspan=2>Current<th colspan=2>Pending");
+ puts("<th rowspan=2>Expired<th rowspan=2>Expire PID<tr bgcolor=#3399aa>");
+ if(getgs)
+ puts("<th>Low<th>High<th>Count<th>Flag");
+ if(getcount)
+ puts("<th>Low<th>High<th>Count");
+ if(getinfo)
+ puts("<th>Flags<th>GroupID<th>DB<th>GroupID<th>DB");
+ }
+ for(o = optind ; o < argc; o++) {
+ if(html)
+ printf("<tr><td>%s", argv[o]);
+ if(getgs) {
+ if(ovdb_groupstats(argv[o], &lowi, &highi, &count, &flag)) {
+ if(html)
+ printf("<td>%d<td>%d<td>%d<td>%c", lowi, highi, count, flag);
+ else
+ printf("%s: groupstats: low: %d, high: %d, count: %d, flag: %c\n",
+ argv[o], lowi, highi, count, flag);
+ }
+ }
+ if(getcount) {
+ low = high = count = 0;
+ s = ovdb_opensearch(argv[o], 1, 0xffffffff);
+ if (s != NULL) {
+ while(ovdb_search(s, &a, NULL, NULL, NULL, NULL)) {
+ if(low == 0 || a < low)
+ low = a;
+ if(a > high)
+ high = a;
+ count++;
+ if(signalled)
+ break;
+ }
+ ovdb_closesearch(s);
+ if(signalled)
+ goto out;
+ if(html)
+ printf("<td>%ld<td>%ld<td>%d", low, high, count);
+ else
+ printf("%s: counted: low: %ld, high: %ld, count: %d\n",
+ argv[o], low, high, count);
+ }
+ }
+ if(getinfo) {
+ int ret;
+ struct groupinfo gi;
+
+ ret = ovdb_getgroupinfo(argv[o], &gi, false, NULL, 0);
+ if (ret != 0) {
+ warn("%s: ovdb_getgroupinfo error: %s", argv[o],
+ db_strerror(ret));
+ continue;
+ }
+ if(html) {
+ printf("<td>%s%s%s%s",
+ (gi.status & GROUPINFO_DELETED) ? "D ":"",
+ (gi.status & GROUPINFO_EXPIRING) ? "E ":"",
+ (gi.status & GROUPINFO_MOVING) ? "M":"",
+ (gi.status == 0) ? " ":"");
+ printf("<td>%d<td>ov%05d", gi.current_gid, gi.current_db);
+ if(gi.status & GROUPINFO_MOVING)
+ printf("<td>%d<td>ov%05d", gi.new_gid, gi.new_db);
+ else
+ printf("<td> <td> ");
+ if(gi.expired)
+ printf("<td>%s<td>%lu", myctime(&gi.expired),
+ (unsigned long) gi.expiregrouppid);
+ else
+ printf("<td> <td> ");
+ putchar('\n');
+ } else {
+ printf("%s: flags: %s%s%s%s\n", argv[o],
+ (gi.status & GROUPINFO_DELETED) ? "DELETED ":"",
+ (gi.status & GROUPINFO_EXPIRING) ? "EXPIRING ":"",
+ (gi.status & GROUPINFO_MOVING) ? "MOVING":"",
+ (gi.status == 0) ? "none":"");
+
+ printf("%s: gid: %d; Stored in: ov%05d\n", argv[o], gi.current_gid, gi.current_db);
+ if(gi.status & GROUPINFO_MOVING)
+ printf("%s: pending gid: %d; pending db: ov%05d\n", argv[o], gi.new_gid, gi.new_db);
+ if(gi.expired) {
+ printf("%s: last expired: %s\n", argv[o], myctime(&gi.expired));
+ printf("%s: by process id: %lu\n", argv[o],
+ (unsigned long) gi.expiregrouppid);
+ }
+ }
+ }
+ if(signalled)
+ goto out;
+ }
+ if(html)
+ puts("</table><p>");
+ }
+ if(start || stop) {
+ if(html)
+ puts("<pre>");
+ for(o = optind ; o < argc; o++) {
+ s = ovdb_opensearch(argv[o], start, stop);
+ if (s != NULL) {
+ while(ovdb_search(s, &a, &data, &len, NULL, NULL)) {
+ if(html)
+ htwrite(data, len);
+ else
+ fwrite(data, len, 1, stdout);
+ if(signalled)
+ break;
+ }
+ ovdb_closesearch(s);
+ if(signalled)
+ goto out;
+ }
+ if(signalled)
+ goto out;
+ }
+ if(html)
+ puts("</pre>");
+ }
+out:
+ if(html)
+ puts("<p></body></html>");
+ ovdb_close();
+ return 0;
+}
+
+#endif /* USE_BERKELEY_DB */
+
--- /dev/null
+#! /usr/bin/perl -w
+#
+# Author: James Brister <brister@vix.com> -- berkeley-unix --
+# Start Date: Sat, 10 Oct 1998 21:40:11 +0200
+# Project: INN
+# File: pullnews.pl
+# RCSId: $Id: pullnews.in 7862 2008-06-08 09:15:41Z iulius $
+#
+# History: May 2008: Geraint A. Edwards greatly improved pullnews, adding
+# -b, -C, -d, -G, -H, -k, -l, -m, -M, -n, -P, -Q, -R, -t, -T, -w and
+# improving -s as well as fixing some bugs.
+# He also integrated the backupfeed contrib script by Kai Henningsen,
+# adding -f, -F, -N, -S, -z and -Z to pullnews.
+#
+# Description: A simple pull feeder. Connects to multiple upstream
+# machines (in the guise of a reader), and pulls over articles
+# and feeds them to a downstream server (in the guise of a feeder).
+#
+# Uses a simple configuration file: $HOME/.pullnews to define
+# which machines to pull articles from and which groups at each
+# machine to pull over. There is also support for more specific
+# configurations like cross-posted newsgroups to kill, thanks to
+# the -m flag which allows articles with headers matching regexp
+# to be dropped.
+#
+# A configuration file looks like:
+#
+# data.pa.vix.com
+# news.software.nntp 0 0
+# comp.lang.c 0 0
+# news.uu.net username password
+# uunet.announce 0 0
+# uunet.help 0 0
+#
+# Hostname lines have no leading space and may have an optional
+# username and password after the hostname; all the
+# subsequent group lines for that host must have leading
+# spaces. The two integers on the group line will be updated by
+# the program when it runs. They are the Unix time the group was
+# accessed, and the highest numbered article that was pulled
+# over.
+#
+
+require 5.004;
+
+$0 =~ s!.*/!!;
+
+my $rcsID =<<'EOM';
+$Id: pullnews.in 7862 2008-06-08 09:15:41Z iulius $
+EOM
+
+$SIG{INT} = \&outtaHere;
+$SIG{QUIT} = \&bail;
+
+use Net::NNTP 2.18; # With libnet 1.0606 (10-Dec-1998) because older versions
+ # issued MODE READER with Net::NNTP::new().
+use Getopt::Std;
+use IO::Handle;
+use POSIX qw(ceil floor);
+use Fcntl;
+use Fcntl qw(:flock);
+use strict;
+
+my $usage = $0;
+my $defaultConfig = "$ENV{HOME}/.pullnews";
+my $defaultPort = 119;
+my $defaultHost = "localhost";
+my $defaultCheckPoint = 0;
+my $defaultRetries = 0;
+my $defaultDebug = 0;
+my $defaultRetryTime = 1;
+my $defaultProgressWidth = 50;
+my $defaultMaxArts;
+
+$usage =~ s!.*/!!;
+$usage .= " [ -hnqRx -b fraction -c config -C width -d level
+ -f fraction -F fakehop -g groups -G newsgroups -H headers
+ -k checkpt -l logfile -m header_pats -M num -N num
+ -p port -P hop_limit -Q level -r file -s host[:port] -S num
+ -t retries -T seconds -w num -z num -Z num ]
+ [ upstream_host ... ]
+
+ -b fraction backtrack on server numbering reset. The proportion
+ (0.0 to 1.0) of a group's articles to pull when the
+ server's article number is less than our high for that
+ group. When fraction is 1.0, pull all the articles on
+ the server. The default is to do nothing.
+
+ -c config specify the configuration file instead of the
+ default of $ENV{HOME}/.pullnews (also called ~/.pullnews).
+
+ -C width use width characters for progress (default is $defaultProgressWidth).
+
+ -d level set debugging level to this integer (default is $defaultDebug).
+
+ -f fraction proportion of articles to get in each group (0.0 to 1.0).
+
+ -F fakehop prepend fakehop as a host to the Path: header.
+
+ -g groups specify a collection of groups to get. The value must be
+ a single argument with commas between group names:
+
+ -g comp.lang.c,comp.lang.lisp,comp.lang.python
+
+ The groups must be defined in the config file somewhere.
+ Only the hosts that carry those groups will be contacted.
+
+ -G newsgroups add these groups to the configuration (see -g and -w).
+
+ -h print this message.
+
+ -H headers remove these named headers (colon-separated list).
+
+ -k checkpt checkpoint the config file every checkpt articles
+ (default is $defaultCheckPoint). A value of 0 means
+ normally (at end).
+
+ -l logfile log progress/stats to logfile (default is stdout).
+
+ -m 'Hdr1:regexp1 !Hdr2:regexp2 ...'
+ feed article only if:
+ the Hdr1: header matches regexp1
+ and the Hdr2: header does not match regexp2.
+
+ -M num maximum number of articles (per group) to process before
+ bailing out.
+
+ -n do nothing -- just fake it.
+
+ -N num timeout length when establishing NNTP connection.
+
+ -p port specify the port to connect to in order to feed articles
+ (default is $defaultPort).
+
+ -P hop_limit count hops ('!') in the Path: header, feed article only if:
+ hop_limit is '+num' and hop_count is more than num;
+ or hop_limit is '-num' and hop_count is less than num.
+
+ -q $0 will normally be verbose about what it is doing. This
+ option will make it quiet.
+
+ -Q level set the quietness level (-Q 2 is equivalent to -q).
+
+ -r file rather than feeding to a server, $0 will instead
+ create an rnews-compatible file.
+
+ -R be a reader (use MODE READER and POST)
+
+ -s host[:port]
+ specify the downstream hostname (and optional port)
+ (default is $defaultHost).
+
+ -S num specify the maximum time (in seconds) to run.
+
+ -t retries number of attempts to connect to a server
+ (default is $defaultRetries, see also -T).
+
+ -T secs time (in seconds) to pause between retries
+ (default is $defaultRetryTime, see also -t).
+
+ -w num set highwater mark to num (if num is negative, use Current+num
+ instead); a num of 0 will re-get all articles on the server;
+ but a num of -0 will get no old articles, set mark to Current.
+
+ -x insert an Xref: header in any article that lacks one.
+
+ -z num time (in seconds) to sleep between articles.
+
+ -Z num time (in seconds) to sleep between groups.
+";
+
+
+use vars qw($opt_b $opt_c $opt_C $opt_d $opt_f $opt_F $opt_g $opt_G
+ $opt_h $opt_H $opt_k $opt_l $opt_m $opt_M $opt_n
+ $opt_N $opt_p $opt_P $opt_q $opt_Q $opt_r $opt_R $opt_s
+ $opt_S $opt_t $opt_T $opt_w $opt_x $opt_z $opt_Z);
+getopts("b:c:C:d:f:F:g:G:hH:k:l:m:M:nN:p:P:qQ:r:Rs:S:t:T:w:xz:Z:") || die $usage;
+
+die $usage if $opt_h;
+
+my @groupsToGet = (); # Empty list means all groups in config file.
+my @groupsToAdd = ();
+my $rnews = $opt_r;
+my $groupFile = $opt_c || $defaultConfig;
+my $localServer = $opt_s || $defaultHost;
+my $localPort = $opt_p || $defaultPort;
+my $quiet = $opt_q;
+my $watermark = $opt_w;
+my $retries = $opt_t || $defaultRetries;
+my $retryTime = $opt_T || $defaultRetryTime;
+my $checkPoint = $opt_k || $defaultCheckPoint;
+my $debug = $opt_d || $defaultDebug;
+my $progressWidth = $opt_C || $defaultProgressWidth;
+my $maxArts = $opt_M || $defaultMaxArts;
+my $no_op = $opt_n || 0;
+my $reader = $opt_R || 0;
+my $quietness = $opt_Q || 0;
+my $skip_headers = lc($opt_H) || '';
+my $logFile = '>&STDOUT';
+$logFile = ">>$opt_l" if $opt_l;
+my @hdr_to_match = split(/\s+/, $opt_m) if defined $opt_m;
+my $pathSteps = $opt_P if defined $opt_P;
+my $path_limit;
+
+$localPort = $1 if not defined $opt_p and $localServer =~ s/:(\d+)$//;
+
+die "can\'t have both ``-s'' and ``-r''\n" if $opt_s && $opt_r;
+
+die "``-b'' value not 0.0-1.0: $opt_b\n" if defined $opt_b and $opt_b !~ /^([01](\.0*)?|0?\.\d+)$/;
+die "``-C'' value not an integer: $opt_C\n" if $progressWidth !~ m!^\d+$!;
+die "``-d'' value not an integer: $opt_d\n" if $debug !~ m!^\d+$!;
+die "``-f'' value not 0.0-1.0: $opt_f\n" if defined $opt_f and $opt_f !~ /^([01](\.0*)?|0?\.\d+)$/;
+die "``-F'' value not a hostname: $opt_F\n" if defined $opt_f and $opt_f !~ m!^[\w\-\.]+$!;
+die "``-k'' value not an integer: $opt_k\n" if $checkPoint !~ m!^\d+$!;
+die "``-M'' value not an integer: $opt_M\n" if defined $maxArts and $maxArts !~ m!^\d+$!;
+die "``-N'' value not an integer: $opt_N\n" if defined $opt_N and $opt_N !~ /^\d+$/;
+die "``-p'' value not an integer: $opt_p\n" if $localPort !~ m!^\d+$!;
+if (defined $pathSteps) {
+ die "``-P'' value not a signed integer: $opt_P\n" if $pathSteps !~ /^[-+](\d+)$/;
+ $path_limit = $1;
+}
+die "option ``-r -'' needs ``-l'' option\n" if defined $opt_r and $opt_r eq '-' and not $opt_l;
+die "``-S'' value not an integer: $opt_S\n" if defined $opt_S and $opt_S !~ /^\d+$/;
+die "``-t'' value not an integer: $opt_t\n" if $retries !~ m!^\d+$!;
+die "``-w'' value not an integer: $opt_w\n" if defined $watermark and $watermark !~ /^-?\d+$/;
+die "``-z'' value not an integer: $opt_z\n" if defined $opt_z and $opt_z !~ /^\d+$/;
+die "``-Z'' value not an integer: $opt_Z\n" if defined $opt_Z and $opt_Z !~ /^\d+$/;
+
+$quiet = 1 if $quietness > 1;
+my %NNTP_Args = ();
+$NNTP_Args{'Timeout'} = $opt_N if defined $opt_N;
+
+@groupsToGet = map { s!^\s*(\S+)\s*!$1!; $_ } split (",", $opt_g) if $opt_g;
+@groupsToAdd = map { s!^\s*(\S+)\s*!$1!; $_ } split (",", $opt_G) if $opt_G;
+
+$| = 1;
+
+my $servers = {};
+my $sname = undef;
+my %fed = ();
+my %refused = ();
+my %rejected = ();
+my $pulled = {};
+my %passwd = ();
+my %info = (
+ fed => 0,
+ refused => 0,
+ rejected => 0,
+ bytes => 0,
+);
+
+if ($rnews) {
+ if ($no_op) {
+ print "Would write to rnews file $rnews\n";
+ } else {
+ open(RNEWS, ">$rnews") ||
+ die "can't open rnews-format output: $rnews: $!\n";
+ }
+}
+open(LOG, $logFile) || die "can't open logfile ($logFile)!: $!\n";
+
+my $oldfh = select;
+$| = 1; select LOG; $| = 1; select $oldfh;
+
+my $lockfile = $ENV{HOME} . "/.pullnews.pid";
+sysopen (LOCK, "$lockfile", O_RDWR | O_CREAT, 0700) ||
+ die "can't create lock file ($lockfile): $!\n";
+$oldfh = select; select LOCK; $| = 1; select $oldfh;
+
+if (!flock (LOCK, LOCK_EX | LOCK_NB)) {
+ seek LOCK, 0, 0;
+ my $otherpid = <LOCK>;
+ chomp $otherpid;
+ die "Another pullnews (pid: $otherpid) seems to be running.\n";
+}
+
+print LOCK "$$\n";
+
+print LOG scalar(localtime(time)), " start\n\n" unless $quiet;
+
+if (@groupsToGet && ! $quiet) {
+ print LOG "Checking for specific groups:\n";
+ map { printf LOG "\t%s\n", $_ } @groupsToGet;
+ print LOG "\n";
+}
+
+open(FILE, "<$groupFile") || die "can't open group file $groupFile\n";
+while (<FILE>) {
+ next if m!^\s*\#! || m!^\s*$!;
+
+ if (m!^(\S+)(\s+(\S+)\s+(\S+))?\s*$!) {
+ $sname = $1;
+ $servers->{$sname} = {};
+ $passwd{$sname} = [ $3, $4 ] if defined $3 and $3 ne "";
+ } elsif (m!^\s+(\S+)\s+(\d+)\s+(\d+)!) {
+ my ($group,$date,$high) = ($1,$2,$3);
+ $servers->{$sname}->{$group} = [ $date, $high ];
+ } elsif (m!^\s+(\S+)\s*$!) {
+ # Assume this is a new group.
+ my ($group,$date,$high) = ($1,0,0);
+ print LOG "Looking for new group $group on $sname\n" unless $quiet;
+ $servers->{$sname}->{$group} = [ $date, $high ];
+ } else {
+ die "Fatal error in $groupFile: $.: $_\n";
+ }
+}
+close FILE;
+
+my @servers = (@ARGV || sort keys %$servers);
+
+die "No servers!\n" if ! @servers;
+
+my $localcxn;
+
+if ( not $rnews ) {
+ print LOG "Connecting to downstream host: $localServer " .
+ "port: $localPort ..."
+ unless $quiet;
+
+ my %localopts = ("Port" => "$localPort", "Reader" => $reader, %NNTP_Args);
+ $localcxn = Net::NNTP->new($localServer, %localopts) ||
+ die "Can't connect to server $localServer\n";
+}
+
+if ( not $quiet and not $quietness ) {
+ print LOG "done.\n\n";
+ print LOG "Legend: ``.'' is an article the downstream server refused\n";
+ print LOG " ``*'' is an article the downstream server rejected\n";
+ print LOG " ``+'' is an article the downstream server accepted\n";
+ print LOG " ``x'' is an article the upstream server couldn't ";
+ print LOG "give out\n";
+ print LOG " ``m'' is an article skipped due to headers (-m)\n";
+ print LOG "\n";
+ print LOG "Writing to rnews-format output: $rnews\n\n" if $rnews;
+}
+
+foreach my $server (@servers) {
+ my ($username, $passwd);
+
+ foreach my $addGroup (@groupsToAdd) {
+ next if defined $servers->{$server}->{$addGroup};
+ $servers->{$server}->{$addGroup} = [ 0, 0 ];
+ }
+
+ if (@groupsToGet > 0) {
+ my $ok;
+ foreach my $sgroup (keys %{$servers->{$server}}) {
+ $ok = 1 if grep($_ eq $sgroup, @groupsToGet);
+ }
+
+ if (! $ok) {
+ # User gave -g and the server doesn't have those groups.
+ warn "Skipping server $server. Doesn't have specified groups.\n";
+ next;
+ }
+ }
+
+ if (exists $passwd{$server}) {
+ ($username, $passwd) = @{$passwd{$server}};
+ }
+
+ if (!exists($servers->{$server})) {
+ warn "No such upstream host $server configured.\n";
+ next;
+ }
+
+ my $shash = $servers->{$server};
+
+ my $connectionAttempts = 0;
+ my $upstream;
+ {{
+ print LOG "connecting to upstream server $server..." unless $quiet;
+ $upstream = Net::NNTP->new($server, %NNTP_Args);
+ $connectionAttempts++;
+ if (!$upstream && $connectionAttempts <= $retries) {
+ sleep $retryTime;
+ next;
+ }
+ }}
+
+ if (!$upstream) {
+ print LOG "failed.\n" unless $quiet;
+ warn "can't connect to upstream server $server: $!\n";
+ next;
+ } else {
+ print LOG "done.\n" unless $quiet;
+ }
+
+ if ($username && !$upstream->authinfo($username, $passwd)) {
+ warn sprintf ("failed to authorize: %s %s\n",
+ $upstream->code(), $upstream->message());
+ next;
+ }
+
+ $info{server}->{$server}->{bytes} = 0;
+ $info{server}->{$server}->{fed} = 0;
+ $info{server}->{$server}->{refused} = 0;
+ $info{server}->{$server}->{rejected} = 0;
+
+ foreach my $group (sort keys %{$servers->{$server}}) {
+ next if (@groupsToGet && !grep ($_ eq $group, @groupsToGet));
+
+ last if !crossFeedGroup ($upstream,$localcxn,$server,$group,$shash);
+ last if defined $opt_S and time >= $^T+$opt_S;
+ sleep $opt_Z if defined $opt_Z;
+ }
+
+ $upstream->quit();
+ last if defined $opt_S and time >= $^T+$opt_S;
+}
+
+saveConfig ();
+stats() unless $quiet;
+
+if ($rnews) {
+ if (not $no_op and not close RNEWS) {
+ print LOG "\nRNEWS close failure: $!";
+ }
+ unlink $rnews if -f $rnews and not -s $rnews;
+}
+
+print LOG "\nDone ", scalar(localtime(time)), "\n" unless $quiet;
+
+cleanLock();
+exit (0);
+
+###############################################################################
+
+sub stats {
+ my $ltotal = 0;
+ my $reftotal = 0;
+ my $rejtotal = 0;
+ my $sum;
+
+ map { $reftotal += $refused{$_} } keys %refused;
+ map { $rejtotal += $rejected{$_} } keys %rejected;
+ map { $ltotal += $fed{$_} } keys %fed;
+
+ $sum = $reftotal + $rejtotal + $ltotal;
+
+ if ($quiet) {
+ printf LOG localtime() . " [$$] %d article%s to $localServer\n",
+ $sum, ($sum != 1 ? "s" : "");
+ } else {
+ printf LOG "\n%d article%s offered to server on $localServer\n",
+ $sum, ($sum != 1 ? "s were" : " was");
+ }
+
+ return if ($sum == 0);
+
+ if ($quiet) {
+ print LOG localtime() . " [$$] $ltotal ok, $reftotal ref, $rejtotal rej\n";
+ } else {
+ printf LOG "%d article%s accepted\n",
+ $ltotal, ($ltotal != 1 ? "s were" : " was")
+ if ($ltotal != 0);
+ printf LOG "%d article%s refused\n",
+ $reftotal, ($reftotal != 1 ? "s were" : " was")
+ if ($reftotal != 0);
+ printf LOG "%d article%s rejected\n",
+ $rejtotal, ($rejtotal != 1 ? "s were" : " was")
+ if ($rejtotal != 0);
+ }
+
+ map {
+ print LOG "\nUpstream server $_:\n" if not $quiet;
+ my $server = $_;
+ my $width = 0;
+
+ map {
+ $width = length if length > $width;
+ } sort keys %{$pulled->{$server}} if not $quiet;
+
+ map {
+ if ($quiet) {
+ printf LOG "%s [$$] from $server $_ %s\n", localtime(), $pulled->{$server}->{$_};
+ } else {
+ printf LOG "\t%${width}s %d\n", $_, $pulled->{$server}->{$_};
+ }
+ } sort keys %{$pulled->{$server}};
+ } sort keys %{$pulled};
+}
+
+sub saveConfig {
+ return if $no_op;
+
+ $SIG{INT} = $SIG{QUIT} = 'IGNORE';
+
+ open(FILE,">$groupFile") || die "can't open $groupFile: $!\n";
+ my $server;
+ my $group;
+
+ print LOG "\nSaving config\n" unless $quiet;
+ print FILE "# Format: (date is epoch seconds)\n";
+ print FILE "# hostname [username password]\n";
+ print FILE "# group date high\n";
+ foreach $server (sort keys %$servers) {
+ print FILE "$server";
+ if (defined $passwd{$server}) {
+ printf FILE " %s %s", $passwd{$server}->[0], $passwd{$server}->[1];
+ }
+ print FILE "\n";
+ foreach $group (sort keys %{$servers->{$server}}) {
+ my ($date,$high) = @{$servers->{$server}->{$group}};
+ printf FILE "\t%s %d %d\n",$group,$date,$high;
+ }
+ }
+ close FILE;
+}
+
+
+sub outtaHere {
+ saveConfig();
+ cleanLock();
+ exit (0);
+}
+
+sub cleanLock {
+ flock (LOCK, LOCK_UN);
+ unlink $lockfile if defined $lockfile;
+}
+
+sub bail {
+ warn "received QUIT signal. Not saving config.\n";
+ cleanLock();
+ exit (0);
+}
+
+sub crossFeedGroup {
+ my ($fromServer,$toServer,$server,$group,$shash) = @_;
+ my ($date,$high) = @{$shash->{$group}};
+ my ($prevDate,$prevHigh) = @{$shash->{$group}};
+ my ($narticles,$first,$last,$name) = $fromServer->group($group);
+ my $count = 0;
+ my $code;
+ my $startTime = time;
+ my ($prevRefused, $prevRejected) = ($info{refused}, $info{rejected});
+
+ if (!defined($narticles)) { # Group command failed.
+ warn sprintf ("Group command failed: %s %s\n",
+ $fromServer->code(), $fromServer->message());
+ return undef;
+ }
+
+ if (not $quiet) {
+ printf LOG "\n%s:\n", $name;
+ printf LOG "\tlast checked: %s\n", scalar(localtime($prevDate));
+ printf LOG "\t%d articles available. First %d Last %d\n",
+ $narticles, $first, $last;
+ }
+ if (defined $watermark) {
+ printf LOG "\tOur previous highest: %d\n", $prevHigh if not $quiet;
+ $high = $watermark;
+ $high = $last+$watermark if substr($watermark, 0, 1) eq '-';
+ $high = 0 if $high < 0;
+ $shash->{$group} = [ time, $high ];
+ }
+ printf LOG "\tOur current highest: %d", $high if not $quiet;
+
+ return 0 if ! $name;
+ if ($narticles == 0) {
+ print LOG " (nothing to get)\n" unless $quiet;
+ return 1;
+ }
+
+ my $toget = (($last - $high) < $narticles ?
+ $last - $high : $narticles);
+ $toget = ceil($toget * $opt_f) if defined $opt_f;
+ if ($last < $high and $opt_b) {
+ $high = $first+floor(($last-$first+1)*(1-$opt_b));
+ $toget = $last - $high;
+ print LOG " (reset highwater mark to $high)" unless $quiet;
+ } elsif ($prevHigh == -1 || $last <= $prevHigh) {
+ # We connected OK but there's nothing there, or we just want
+ # to reset our highwater mark.
+ $shash->{$group} = [ time, $high ];
+ print LOG " (nothing to get)\n" unless $quiet;
+ return 1;
+ }
+ print LOG " ($toget to get)\n" unless $quiet;
+
+ my $i;
+ my @warns;
+ for ($i = ($first > $high ? $first : $high + 1) ; $i <= $last ; $i++) {
+ last if defined $maxArts and $count >= $maxArts;
+ last if defined $opt_f and $count >= $toget;
+ $count++;
+ sleep $opt_z if defined $opt_z and $count > 1;
+ my $article = $fromServer->article($i);
+ if ($article) {
+ my $msgid;
+ my $xref = 0;
+ my $headers = 1;
+ my $idx;
+ my $len = 0; # Received article length (bytes) (for stats).
+ my $tx_len = 0; # Transmitted article length (bytes) (for rnews).
+ my @header_nums_to_go = ();
+ my $match_all_hdrs = 1; # Assume no headers to match.
+ my $skip_due_to_hdrs = 0;
+ my %m_found_hdrs = ();
+ my $curr_hdr = '';
+
+ for ($idx = 0 ; $idx < @{$article} ; $idx++) {
+ $len += length($article->[$idx]);
+ $tx_len += length($article->[$idx]);
+ next if not $headers;
+
+ $curr_hdr = lc($1) if $article->[$idx] =~ /^([^:[:blank:]]+):/;
+ $curr_hdr = ' ' if $article->[$idx] eq "\n";
+
+ if ($match_all_hdrs and @hdr_to_match and $article->[$idx] =~ /^[^[:blank:]]/) {
+ # Check header matches -m flag if new header.
+
+ # Unfold this header (with following lines).
+ my $unfolded_art_hdr = $article->[$idx];
+ for (my $idx_step = $idx+1; $article->[$idx_step] =~ /^[[:space:]](.+)/; $idx_step++) {
+ # While next line is continuation...
+ my $more_line = $1;
+ chomp $unfolded_art_hdr;
+ $unfolded_art_hdr .= $more_line;
+ }
+
+ my ($hdr_un, $val_un) = split(':', $unfolded_art_hdr, 2);
+ $val_un = '' if not defined $val_un;
+ $val_un =~ s/^\s*//;
+ for my $tuple_match (@hdr_to_match) {
+ my ($hdr_m, $val_m) = split(':', $tuple_match, 2);
+ my $negate_h = ($hdr_m =~ s/^!//);
+ next if lc($hdr_un) ne lc($hdr_m);
+ $m_found_hdrs{lc($hdr_m)} = 1;
+ if ($negate_h) {
+ if ($val_un =~ /$val_m/i) {
+ print LOG "\tDEBUGGING $i\t-- $hdr_un [$1]\n" if $debug >= 2;
+ $match_all_hdrs = 0;
+ }
+ } elsif (not $val_un =~ /$val_m/i) {
+ print LOG "\tDEBUGGING $i\t++ $hdr_un [$1]\n" if $debug >= 2;
+ $match_all_hdrs = 0;
+ }
+ last if not $match_all_hdrs;
+ }
+ }
+
+ if (grep { $curr_hdr eq $_ } split(':', $skip_headers)) {
+ print LOG "\tDEBUGGING $i\tskip_hdr $idx\t$curr_hdr\n" if $debug >= 2;
+ push @header_nums_to_go, $idx;
+ }
+ if ($article->[$idx] =~ m!^message-id:\s*(\S+)!i) {
+ $msgid = $1;
+ }
+ if (not $skip_due_to_hdrs and defined $pathSteps and $article->[$idx] =~ m!^Path:\s*!i) {
+ my $path_count = $article->[$idx];
+ $path_count = ($path_count =~ s@!@@g) || 0;
+ if (substr($pathSteps, 0, 1) eq '-') {
+ $skip_due_to_hdrs = 1 if $path_count >= $path_limit;
+ } elsif (substr($pathSteps, 0, 1) eq '+') {
+ $skip_due_to_hdrs = 1 if $path_count <= $path_limit;
+ }
+ if ($skip_due_to_hdrs) {
+ print LOG "\tDEBUGGING $i\tNpath_skip_art $i\n" if $debug >= 2;
+ } elsif (defined $opt_F) {
+ $tx_len += length($opt_F)+1;
+ $article->[$idx] =~ s/^Path:\s*/$&$opt_F!/i;
+ }
+ }
+
+ if ($opt_x && $article->[$idx] =~ m!^xref:!i) {
+ $xref = 1;
+ }
+
+ # Catch some of the more common problems with articles.
+ if ($article->[$idx] =~ m!^\s+\n$! and $curr_hdr ne 'subject') {
+ print STDERR "Fixing bad header line[$idx]-1: $article->[$idx-1]" if $idx > 0;
+ print STDERR "Fixing bad header line[$idx]::: $article->[$idx]";
+ print STDERR "Fixing bad header line[$idx]+1: $article->[$idx+1]";
+ $tx_len -= length($article->[$idx])-1;
+ $article->[$idx] = "\n";
+ }
+
+ $headers = 0 if $article->[$idx] eq "\n";
+ }
+ if (@hdr_to_match and (not $match_all_hdrs or @hdr_to_match != scalar(keys %m_found_hdrs))) {
+ print LOG "\tDEBUGGING $i\thdr_skip_art $i\n" if $debug >= 2;
+ $skip_due_to_hdrs = 1;
+ }
+ while (@header_nums_to_go) {
+ my $idx = pop @header_nums_to_go; # Start from last.
+ my $cut = join("\n\t", splice(@{$article}, $idx, 1));
+ $tx_len -= length($cut);
+ print LOG "\tDEBUGGING $i\tcut1 $cut" if $debug >= 2;
+ while ($article->[$idx] =~ /^[[:space:]](.+)/) {
+ # Folded lines.
+ my $cut = join("\n\t", splice(@{$article}, $idx, 1));
+ $tx_len -= length($cut);
+ print LOG "\tDEBUGGING $i\tcut_ $cut" if $debug >= 2;
+ }
+ }
+
+ if (!$msgid) {
+ warn "No Message-ID: header found in article\n";
+ next;
+ } else {
+ print LOG "\tDEBUGGING $i\tMessage-ID: $msgid\n" if $debug >= 2;
+ }
+
+ # Some old servers lack Xref:, which bothers a downstream INN if
+ # it has xrefslave set, so add one just before the blank line.
+ if ($opt_x && !$xref) {
+ warn "No Xref: header found in article, adding\n";
+ my $xref_h = "Xref: $server $group: $i\n";
+ splice(@{$article}, $idx, 0, $xref_h);
+ $tx_len += length($xref_h);
+ }
+
+ $pulled->{$server}->{$group}++;
+ $info{server}->{$server}->{bytes} += $len;
+ $info{bytes} += $len;
+
+ if ($skip_due_to_hdrs) {
+ print LOG "m" unless $quiet;
+ } elsif ($rnews) {
+ printf RNEWS "#! rnews %d\n", $tx_len;
+ map { print RNEWS $_ } @{$article};
+ print LOG "+" unless $quiet;
+ } else {
+ if ($no_op) {
+ print "Would offer $msgid\n";
+
+ } elsif ($reader and not $toServer->post($article)) {
+ # 240 article posted ok
+ # 340 send article to be posted. End with <CR-LF>.<CR-LF>
+ # 440 posting not allowed
+ # 441 posting failed
+ my $code = $toServer->code();
+ my $msg = $toServer->message();
+ print LOG "\tDEBUGGING $i\tPost $code: Msg: <" . join('//', split(/\r?\n/, $msg)) . ">\n" if $debug >= 2;
+ $msg =~ s/^340 .*?\n(?=.)//o;
+ if ($msg =~ /^240 /) {
+ print LOG "+" unless $quiet;
+ push @warns, "Post $i ok ($code): $msg";
+ $fed{$group}++;
+ $info{server}->{$server}->{fed}++;
+ $info{fed}++;
+ } elsif ($msg =~ /^435 / or $msg =~ /duplicate message-id/io) {
+ print LOG "." unless $quiet;
+ push @warns, "Post $i to server declined ($code): $msg"
+ if $msg !~ /^435 $msgid$/
+ and $msg !~ /duplicate message-id/io;
+ $refused{$group}++;
+ $info{server}->{$server}->{refused}++;
+ $info{refused}++;
+ } else {
+ warn "Post $i to server failed ($code): $msg\n";
+ $toServer->quit();
+ }
+
+ } elsif (not $reader and not $toServer->ihave($msgid,$article)) {
+ # 235 article transferred ok
+ # 335 send article to be transferred. End with <CR-LF>.<CR-LF>
+ # 435 article not wanted -- do not send it
+ # 436 transfer failed -- try again later
+ # 437 article rejected -- do not try again
+ my $code = $toServer->code();
+ my $msg = $toServer->message();
+ print LOG "\tDEBUGGING $i\tPost $code: Msg: <" . join('//', split(/\r?\n/, $msg)) . ">\n" if $debug >= 2;
+ if ($code == 435) {
+ print LOG "." unless $quiet;
+ $refused{$group}++;
+ $info{server}->{$server}->{refused}++;
+ $info{refused}++;
+ } elsif ($code == 437) {
+ print LOG "*" unless $quiet;
+ $rejected{$group}++;
+ $info{server}->{$server}->{rejected}++;
+ $info{rejected}++;
+ } else {
+ warn "Transfer to server failed ($code): $msg\n";
+ $toServer->quit();
+ saveConfig();
+ exit (1);
+ }
+
+ } else {
+ my $code = $toServer->code();
+ my $msg = $toServer->message();
+ print LOG "\tDEBUGGING $i\tPost $code: Msg: <" . join('//', split(/\r?\n/, $msg)) . ">\n" if $debug >= 2;
+ print LOG "+" unless $quiet;
+ $fed{$group}++;
+ $info{server}->{$server}->{fed}++;
+ $info{fed}++;
+ }
+ }
+
+ $shash->{$group} = [ time, $high = $i ];
+ } else {
+ $shash->{$group} = [ time, $high = $i ] if $fromServer->code() == 430; # no such article, do not retry
+ print LOG "x" unless $quiet;
+ printf LOG ("\nDEBUGGING $i %s %s\n", $fromServer->code(),
+ $fromServer->message()) if $debug >= 2;
+ }
+ saveConfig() if $checkPoint and ($count % $checkPoint) == 0;
+ print LOG "\n" if (!$quiet && (($count % $progressWidth) == 0));
+ last if defined $opt_S and time >= $^T+$opt_S;
+ }
+ print LOG "\n" unless $quiet;
+ print LOG join("\n\t", '', @warns) . "\n\n" if @warns;
+ my $elapsed_time = time - $startTime + 1;
+ if ($quiet) {
+ my $rejectedDiff = $info{rejected}-$prevRejected;
+ my $refusedDiff = $info{refused}-$prevRefused;
+ my $destServer = ($localServer ne $defaultHost ? " to $localServer" : '');
+ print LOG localtime() . "[$$] $server$destServer $name $narticles $first-$last : $count $prevHigh-" .
+ ($high == $last ? '' : $high) . " $refusedDiff $rejectedDiff\n"
+ unless $prevHigh == $high and $count == 0;
+ } else {
+ printf LOG "%s article%s retrieved in %d seconds (%d bytes, %d cps)\n",
+ $count, ($count == 1 ? "" : "s"), $elapsed_time,
+ $info{server}->{$server}->{bytes},
+ int($info{server}->{$server}->{bytes}*100/$elapsed_time)/100;
+ }
+ return 1;
+}
--- /dev/null
+/* $Id: rnews.c 7424 2005-10-09 05:04:12Z eagle $
+**
+** A front-end for InterNetNews.
+**
+** Read UUCP batches and offer them up NNTP-style. Because we may end
+** up sending our input down a pipe to uncompress, we have to be careful
+** to do unbuffered reads.
+*/
+
+#include "config.h"
+#include "clibrary.h"
+#include "portable/wait.h"
+#include <ctype.h>
+#include <dirent.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <pwd.h>
+#include <syslog.h>
+#include <sys/stat.h>
+
+#include "inn/innconf.h"
+#include "inn/messages.h"
+#include "inn/wire.h"
+#include "libinn.h"
+#include "nntp.h"
+#include "paths.h"
+#include "storage.h"
+
+
+typedef struct _HEADER {
+ const char *Name;
+ int size;
+} HEADER;
+
+
+static bool Verbose;
+static const char *InputFile = "stdin";
+static char *UUCPHost;
+static char *PathBadNews = NULL;
+static char *remoteServer;
+static FILE *FromServer;
+static FILE *ToServer;
+static char UNPACK[] = "gzip";
+static HEADER RequiredHeaders[] = {
+ { "Message-ID", 10 },
+#define _messageid 0
+ { "Newsgroups", 10 },
+#define _newsgroups 1
+ { "From", 4 },
+#define _from 2
+ { "Date", 4 },
+#define _date 3
+ { "Subject", 7 },
+#define _subject 4
+ { "Path", 4 },
+#define _path 5
+};
+#define IS_MESGID(hp) ((hp) == &RequiredHeaders[_messageid])
+#define IS_PATH(hp) ((hp) == &RequiredHeaders[_path])
+
+\f
+
+/*
+** Open up a pipe to a process with fd tied to its stdin. Return a
+** descriptor tied to its stdout or -1 on error.
+*/
+static int
+StartChild(int fd, const char *path, const char *argv[])
+{
+ int pan[2];
+ int i;
+ pid_t pid;
+
+ /* Create a pipe. */
+ if (pipe(pan) < 0)
+ sysdie("cannot pipe for %s", path);
+
+ /* Get a child. */
+ for (i = 0; (pid = fork()) < 0; i++) {
+ if (i == innconf->maxforks) {
+ syswarn("cannot fork %s, spooling", path);
+ return -1;
+ }
+ notice("cannot fork %s, waiting", path);
+ sleep(60);
+ }
+
+ /* Run the child, with redirection. */
+ if (pid == 0) {
+ close(pan[PIPE_READ]);
+
+ /* Stdin comes from our old input. */
+ if (fd != STDIN_FILENO) {
+ if ((i = dup2(fd, STDIN_FILENO)) != STDIN_FILENO) {
+ syswarn("cannot dup2 %d to 0, got %d", fd, i);
+ _exit(1);
+ }
+ close(fd);
+ }
+
+ /* Stdout goes down the pipe. */
+ if (pan[PIPE_WRITE] != STDOUT_FILENO) {
+ if ((i = dup2(pan[PIPE_WRITE], STDOUT_FILENO)) != STDOUT_FILENO) {
+ syswarn("cannot dup2 %d to 1, got %d", pan[PIPE_WRITE], i);
+ _exit(1);
+ }
+ close(pan[PIPE_WRITE]);
+ }
+
+ execv(path, (char * const *)argv);
+ syswarn("cannot execv %s", path);
+ _exit(1);
+ }
+
+ close(pan[PIPE_WRITE]);
+ close(fd);
+ return pan[PIPE_READ];
+}
+
+
+/*
+** Wait for the specified number of children.
+*/
+static void
+WaitForChildren(int n)
+{
+ pid_t pid;
+
+ while (--n >= 0) {
+ pid = waitpid(-1, NULL, WNOHANG);
+ if (pid == (pid_t) -1 && errno != EINTR) {
+ if (errno != ECHILD)
+ syswarn("cannot wait");
+ break;
+ }
+ }
+}
+
+
+\f
+
+/*
+** Clean up the NNTP escapes from a line.
+*/
+static char *REMclean(char *buff)
+{
+ char *p;
+
+ if ((p = strchr(buff, '\r')) != NULL)
+ *p = '\0';
+ if ((p = strchr(buff, '\n')) != NULL)
+ *p = '\0';
+
+ /* The dot-escape is only in text, not command responses. */
+ return buff;
+}
+
+
+/*
+** Write an article to the rejected directory.
+*/
+static void
+Reject(const char *article, size_t length UNUSED, const char *reason,
+ const char *arg)
+{
+#if defined(DO_RNEWS_SAVE_BAD)
+ char *filename;
+ FILE *F;
+ int fd;
+#endif /* defined(DO_RNEWS_SAVE_BAD) */
+
+ notice(reason, arg);
+ if (Verbose) {
+ fprintf(stderr, "%s: ", InputFile);
+ fprintf(stderr, reason, arg);
+ fprintf(stderr, " [%.40s...]\n", article);
+ }
+
+#if defined(DO_RNEWS_SAVE_BAD)
+ filename = concat(PathBadNews, "/XXXXXX", (char *) 0);
+ fd = mkstemp(filename);
+ if (fd < 0) {
+ warn("cannot create temporary file");
+ return;
+ }
+ F = fdopen(fd, "w");
+ if (F == NULL) {
+ warn("cannot fdopen %s", filename);
+ return;
+ }
+ if (fwrite(article, 1, length, F) != length)
+ warn("cannot fwrite to %s", filename);
+ if (fclose(F) == EOF)
+ warn("cannot close %s", filename);
+ free(filename);
+#endif /* defined(DO_RNEWS_SAVE_BAD) */
+}
+
+
+/*
+** Process one article. Return true if the article was okay; false if the
+** whole batch needs to be saved (such as when the server goes down or if
+** the file is corrupted).
+*/
+static bool
+Process(char *article, size_t artlen)
+{
+ HEADER *hp;
+ const char *p;
+ size_t length;
+ char *wirefmt, *q;
+ const char *id = NULL;
+ char *msgid;
+ char buff[SMBUF];
+#if defined(FILE_RNEWS_LOG_DUPS)
+ FILE *F;
+#endif /* defined(FILE_RNEWS_LOG_DUPS) */
+#if !defined(DONT_RNEWS_LOG_DUPS)
+ char path[40];
+#endif /* !defined(DONT_RNEWS_LOG_DUPS) */
+
+ /* Empty article? */
+ if (*article == '\0')
+ return true;
+
+ /* Convert the article to wire format. */
+ wirefmt = ToWireFmt(article, artlen, &length);
+
+ /* Make sure that all the headers are there, note the ID. */
+ for (hp = RequiredHeaders; hp < ARRAY_END(RequiredHeaders); hp++) {
+ p = wire_findheader(wirefmt, length, hp->Name);
+ if (p == NULL) {
+ free(wirefmt);
+ Reject(article, artlen, "bad_article missing %s", hp->Name);
+ return true;
+ }
+ if (IS_MESGID(hp)) {
+ id = p;
+ continue;
+ }
+#if !defined(DONT_RNEWS_LOG_DUPS)
+ if (IS_PATH(hp)) {
+ strlcpy(path, p, sizeof(path));
+ if ((q = strchr(path, '\r')) != NULL)
+ *q = '\0';
+ }
+#endif /* !defined(DONT_RNEWS_LOG_DUPS) */
+ }
+
+ /* Send the NNTP "ihave" message. */
+ if ((p = strchr(id, '\r')) == NULL) {
+ free(wirefmt);
+ Reject(article, artlen, "bad_article unterminated %s header",
+ "Message-ID");
+ return true;
+ }
+ msgid = xstrndup(id, p - id);
+ fprintf(ToServer, "ihave %s\r\n", msgid);
+ fflush(ToServer);
+ if (UUCPHost)
+ notice("offered %s %s", msgid, UUCPHost);
+ free(msgid);
+
+ /* Get a reply, see if they want the article. */
+ if (fgets(buff, sizeof buff, FromServer) == NULL) {
+ free(wirefmt);
+ if (ferror(FromServer))
+ syswarn("cannot fgets after ihave");
+ else
+ warn("unexpected EOF from server after ihave");
+ return false;
+ }
+ REMclean(buff);
+ if (!CTYPE(isdigit, buff[0])) {
+ free(wirefmt);
+ notice("bad_reply after ihave %s", buff);
+ return false;
+ }
+ switch (atoi(buff)) {
+ default:
+ free(wirefmt);
+ notice("unknown_reply after ihave %s", buff);
+ return false;
+ case NNTP_RESENDIT_VAL:
+ free(wirefmt);
+ return false;
+ case NNTP_SENDIT_VAL:
+ break;
+ case NNTP_HAVEIT_VAL:
+#if defined(SYSLOG_RNEWS_LOG_DUPS)
+ *p = '\0';
+ notice("duplicate %s %s", id, path);
+#endif /* defined(SYSLOG_RNEWS_LOG_DUPS) */
+#if defined(FILE_RNEWS_LOG_DUPS)
+ if ((F = fopen(_PATH_RNEWS_DUP_LOG, "a")) != NULL) {
+ *p = '\0';
+ fprintf(F, "duplicate %s %s\n", id, path);
+ fclose(F);
+ }
+#endif /* defined(FILE_RNEWS_LOG_DUPS) */
+ free(wirefmt);
+ return true;
+ }
+
+ /* Send the article to the server. */
+ if (fwrite(wirefmt, length, 1, ToServer) != 1) {
+ free(wirefmt);
+ sysnotice("cant sendarticle");
+ return false;
+ }
+ free(wirefmt);
+
+ /* Flush the server buffer. */
+ if (fflush(ToServer) == EOF) {
+ syswarn("cant fflush after article");
+ return false;
+ }
+
+ /* Process server reply code. */
+ if (fgets(buff, sizeof buff, FromServer) == NULL) {
+ if (ferror(FromServer))
+ syswarn("cannot fgets after article");
+ else
+ warn("unexpected EOF from server after article");
+ return false;
+ }
+ REMclean(buff);
+ if (!CTYPE(isdigit, buff[0])) {
+ notice("bad_reply after article %s", buff);
+ return false;
+ }
+ switch (atoi(buff)) {
+ default:
+ notice("unknown_reply after article %s", buff);
+ /* FALLTHROUGH */
+ case NNTP_RESENDIT_VAL:
+ return false;
+ case NNTP_TOOKIT_VAL:
+ break;
+ case NNTP_REJECTIT_VAL:
+ Reject(article, artlen, "rejected %s", buff);
+ break;
+ }
+ return true;
+}
+
+
+/*
+** Read the rest of the input as an article.
+*/
+static bool
+ReadRemainder(int fd, char first, char second)
+{
+ char *article;
+ char *p;
+ char buf[BUFSIZ];
+ int size;
+ int used;
+ int left;
+ int skipnl;
+ int i, n;
+ bool ok;
+
+ /* Get an initial allocation, leaving space for the \0. */
+ size = BUFSIZ + 1;
+ article = xmalloc(size + 2);
+ article[0] = first;
+ article[1] = second;
+ used = second ? 2 : 1;
+ left = size - used;
+ skipnl = 0;
+
+ /* Read the input, coverting line ends as we go if necessary. */
+ while ((n = read(fd, buf, sizeof(buf))) > 0) {
+ p = article + used;
+ for (i = 0; i < n; i++) {
+ if (skipnl) {
+ skipnl = 0;
+ if (buf[i] == '\n') continue;
+ }
+ if (buf[i] == '\r') {
+ buf[i] = '\n';
+ skipnl = 1;
+ }
+ *p++ = buf[i];
+ used++;
+ left--;
+ if (left < SMBUF) {
+ size += BUFSIZ;
+ left += BUFSIZ;
+ article = xrealloc(article, size);
+ p = article + used;
+ }
+ }
+ }
+ if (n < 0)
+ sysdie("cannot read after %d bytes", used);
+
+ if (article[used - 1] != '\n')
+ article[used++] = '\n';
+ article[used] = '\0';
+
+ ok = Process(article, used);
+ free(article);
+ return ok;
+}
+
+
+/*
+** Read an article from the input stream that is artsize bytes long.
+*/
+static bool
+ReadBytecount(int fd, int artsize)
+{
+ static char *article;
+ static int oldsize;
+ char *p;
+ int left;
+ int i;
+
+ /* If we haven't gotten any memory before, or we didn't get enough,
+ * then get some. */
+ if (article == NULL) {
+ oldsize = artsize;
+ article = xmalloc(oldsize + 1 + 1);
+ }
+ else if (artsize > oldsize) {
+ oldsize = artsize;
+ article = xrealloc(article, oldsize + 1 + 1);
+ }
+
+ /* Read in the article. */
+ for (p = article, left = artsize; left; p += i, left -= i)
+ if ((i = read(fd, p, left)) <= 0) {
+ i = errno;
+ warn("cannot read, wanted %d got %d", artsize, artsize - left);
+#if 0
+ /* Don't do this -- if the article gets re-processed we
+ * will end up accepting the truncated version. */
+ artsize = p - article;
+ article[artsize] = '\0';
+ Reject(article, "short read (%s?)", strerror(i));
+#endif /* 0 */
+ return true;
+ }
+ if (p[-1] != '\n') {
+ *p++ = '\n';
+ artsize++;
+ }
+ *p = '\0';
+
+ return Process(article, artsize);
+}
+
+\f
+
+/*
+** Read a single text line; not unlike fgets(). Just more inefficient.
+*/
+static bool
+ReadLine(char *p, int size, int fd)
+{
+ char *save;
+
+ /* Fill the buffer, a byte at a time. */
+ for (save = p; size > 0; p++, size--) {
+ if (read(fd, p, 1) != 1) {
+ *p = '\0';
+ sysdie("cannot read first line, got %s", save);
+ }
+ if (*p == '\n') {
+ *p = '\0';
+ return true;
+ }
+ }
+ *p = '\0';
+ warn("bad_line too long %s", save);
+ return false;
+}
+
+
+/*
+** Unpack a single batch.
+*/
+static bool
+UnpackOne(int *fdp, size_t *countp)
+{
+#if defined(DO_RNEWSPROGS)
+ char path[(SMBUF * 2) + 1];
+ char *p;
+#endif /* defined(DO_RNEWSPROGS) */
+ char buff[SMBUF];
+ const char *cargv[4];
+ int artsize;
+ int i;
+ int gzip = 0;
+ bool HadCount;
+ bool SawCunbatch;
+ int len;
+
+ *countp = 0;
+ for (SawCunbatch = false, HadCount = false; ; ) {
+ /* Get the first character. */
+ if ((i = read(*fdp, &buff[0], 1)) < 0) {
+ syswarn("cannot read first character");
+ return false;
+ }
+ if (i == 0)
+ break;
+
+ if (buff[0] == 0x1f)
+ gzip = 1;
+ else if (buff[0] != '#')
+ /* Not a batch file. If we already got one count, the batch
+ * is corrupted, else read rest of input as an article. */
+ return HadCount ? false : ReadRemainder(*fdp, buff[0], '\0');
+
+ /* Get the second character. */
+ if ((i = read(*fdp, &buff[1], 1)) < 0) {
+ syswarn("cannot read second character");
+ return false;
+ }
+ if (i == 0)
+ /* A one-byte batch? */
+ return false;
+
+ /* Check second magic character. */
+ /* gzipped ($1f$8b) or compressed ($1f$9d) */
+ if (gzip && ((buff[1] == (char)0x8b) || (buff[1] == (char)0x9d))) {
+ cargv[0] = "gzip";
+ cargv[1] = "-d";
+ cargv[2] = NULL;
+ lseek(*fdp, 0, 0); /* Back to the beginning */
+ *fdp = StartChild(*fdp, _PATH_GZIP, cargv);
+ if (*fdp < 0)
+ return false;
+ (*countp)++;
+ SawCunbatch = true;
+ continue;
+ }
+ if (buff[1] != '!')
+ return HadCount ? false : ReadRemainder(*fdp, buff[0], buff[1]);
+
+ /* Some kind of batch -- get the command. */
+ if (!ReadLine(&buff[2], (int)(sizeof buff - 3), *fdp))
+ return false;
+
+ if (strncmp(buff, "#! rnews ", 9) == 0) {
+ artsize = atoi(&buff[9]);
+ if (artsize <= 0) {
+ syswarn("bad_line bad count %s", buff);
+ return false;
+ }
+ HadCount = true;
+ if (ReadBytecount(*fdp, artsize))
+ continue;
+ return false;
+ }
+
+ if (HadCount)
+ /* Already saw a bytecount -- probably corrupted. */
+ return false;
+
+ if (strcmp(buff, "#! cunbatch") == 0) {
+ if (SawCunbatch) {
+ syswarn("nested_cunbatch");
+ return false;
+ }
+ cargv[0] = UNPACK;
+ cargv[1] = "-d";
+ cargv[2] = NULL;
+ *fdp = StartChild(*fdp, _PATH_GZIP, cargv);
+ if (*fdp < 0)
+ return false;
+ (*countp)++;
+ SawCunbatch = true;
+ continue;
+ }
+
+#if defined(DO_RNEWSPROGS)
+ cargv[0] = UNPACK;
+ cargv[1] = NULL;
+ /* Ignore any possible leading pathnames, to avoid trouble. */
+ if ((p = strrchr(&buff[3], '/')) != NULL)
+ p++;
+ else
+ p = &buff[3];
+ if (strchr(_PATH_RNEWSPROGS, '/') == NULL) {
+ snprintf(path, sizeof(path), "%s/%s/%s", innconf->pathbin,
+ _PATH_RNEWSPROGS, p);
+ len = strlen(innconf->pathbin) + 1 + sizeof _PATH_RNEWSPROGS;
+ } else {
+ snprintf(path, sizeof(path), "%s/%s", _PATH_RNEWSPROGS, p);
+ len = sizeof _PATH_RNEWSPROGS;
+ }
+ for (p = &path[len]; *p; p++)
+ if (ISWHITE(*p)) {
+ *p = '\0';
+ break;
+ }
+ *fdp = StartChild(*fdp, path, cargv);
+ if (*fdp < 0)
+ return false;
+ (*countp)++;
+ continue;
+#else
+ warn("bad_format unknown command %s", buff);
+ return false;
+#endif /* defined(DO_RNEWSPROGS) */
+ }
+ return true;
+}
+
+
+/*
+** Read all articles in the spool directory and unpack them. Print all
+** errors with xperror as well as syslog, since we're probably being run
+** interactively.
+*/
+static void
+Unspool(void)
+{
+ DIR *dp;
+ struct dirent *ep;
+ bool ok;
+ struct stat Sb;
+ char hostname[10];
+ int fd;
+ size_t i;
+ char *uuhost;
+
+ message_handlers_die(2, message_log_stderr, message_log_syslog_err);
+ message_handlers_warn(2, message_log_stderr, message_log_syslog_err);
+
+ /* Go to the spool directory, get ready to scan it. */
+ if (chdir(innconf->pathincoming) < 0)
+ sysdie("cannot chdir to %s", innconf->pathincoming);
+ if ((dp = opendir(".")) == NULL)
+ sysdie("cannot open spool directory");
+
+ /* Loop over all files, and parse them. */
+ while ((ep = readdir(dp)) != NULL) {
+ InputFile = ep->d_name;
+ if (InputFile[0] == '.')
+ continue;
+ if (stat(InputFile, &Sb) < 0 && errno != ENOENT) {
+ syswarn("cannot stat %s", InputFile);
+ continue;
+ }
+
+ if (!S_ISREG(Sb.st_mode))
+ continue;
+
+ if ((fd = open(InputFile, O_RDWR)) < 0) {
+ if (errno != ENOENT)
+ syswarn("cannot open %s", InputFile);
+ continue;
+ }
+
+ /* Make sure multiple Unspools don't stomp on eachother. */
+ if (!inn_lock_file(fd, INN_LOCK_WRITE, 0)) {
+ close(fd);
+ continue;
+ }
+
+ /* Get UUCP host from spool file, deleting the mktemp XXXXXX suffix. */
+ uuhost = UUCPHost;
+ hostname[0] = 0;
+ if ((i = strlen(InputFile)) > 6) {
+ i -= 6;
+ if (i > sizeof hostname - 1)
+ /* Just in case someone wrote their own spooled file. */
+ i = sizeof hostname - 1;
+ strlcpy(hostname, InputFile, i + 1);
+ UUCPHost = hostname;
+ }
+ ok = UnpackOne(&fd, &i);
+ WaitForChildren(i);
+ UUCPHost = uuhost;
+
+ /* If UnpackOne returned true, the article has been dealt with one way
+ or the other, so remove it. Otherwise, leave it in place; either
+ we got an unknown error from the server or we got a deferral, and
+ for both we want to try later. */
+ if (ok) {
+ if (unlink(InputFile) < 0)
+ syswarn("cannot remove %s", InputFile);
+ }
+
+ close(fd);
+ }
+ closedir(dp);
+
+ message_handlers_die(1, message_log_syslog_err);
+ message_handlers_warn(1, message_log_syslog_err);
+}
+
+\f
+
+/*
+** Can't connect to the server, so spool our input. There isn't much
+** we can do if this routine fails, unfortunately. Perhaps try to use
+** an alternate filesystem?
+*/
+static void
+Spool(int fd, int mode)
+{
+ int spfd;
+ int i;
+ int j;
+ char *tmpspool, *spoolfile, *p;
+ char buff[BUFSIZ];
+ int count;
+ int status;
+
+ if (mode == 'N')
+ exit(9);
+ tmpspool = concat(innconf->pathincoming, "/.",
+ UUCPHost ? UUCPHost : "", "XXXXXX", (char *)0);
+ spfd = mkstemp(tmpspool);
+ if (spfd < 0)
+ sysdie("cannot create temporary batch file %s", tmpspool);
+ if (fchmod(spfd, BATCHFILE_MODE) < 0)
+ sysdie("cannot chmod temporary batch file %s", tmpspool);
+
+ /* Read until we there is nothing left. */
+ for (status = 0, count = 0; (i = read(fd, buff, sizeof buff)) != 0; ) {
+ /* Break out on error. */
+ if (i < 0) {
+ syswarn("cannot read after %d", count);
+ status++;
+ break;
+ }
+ /* Write out what we read. */
+ for (count += i, p = buff; i; p += j, i -= j)
+ if ((j = write(spfd, p, i)) <= 0) {
+ syswarn("cannot write around %d", count);
+ status++;
+ break;
+ }
+ }
+
+ /* Close the file. */
+ if (close(spfd) < 0) {
+ syswarn("cannot close spooled article %s", tmpspool);
+ status++;
+ }
+
+ /* Move temp file into the spool area, and exit appropriately. */
+ spoolfile = concat(innconf->pathincoming, "/",
+ UUCPHost ? UUCPHost : "", "XXXXXX", (char *)0);
+ spfd = mkstemp(spoolfile);
+ if (spfd < 0) {
+ syswarn("cannot create spool file %s", spoolfile);
+ status++;
+ } else {
+ close(spfd);
+ if (rename(tmpspool, spoolfile) < 0) {
+ syswarn("cannot rename %s to %s", tmpspool, spoolfile);
+ status++;
+ }
+ }
+ free(tmpspool);
+ free(spoolfile);
+ exit(status);
+ /* NOTREACHED */
+}
+
+
+/*
+** Try to read the password file and open a connection to a remote
+** NNTP server.
+*/
+static bool OpenRemote(char *server, int port, char *buff)
+{
+ int i;
+
+ /* Open the remote connection. */
+ if (server)
+ i = NNTPconnect(server, port, &FromServer, &ToServer, buff);
+ else
+ i = NNTPremoteopen(port, &FromServer, &ToServer, buff);
+ if (i < 0)
+ return false;
+
+ *buff = '\0';
+ if (NNTPsendpassword(server, FromServer, ToServer) < 0) {
+ int oerrno = errno;
+ fclose(FromServer);
+ fclose(ToServer);
+ errno = oerrno;
+ return false;
+ }
+ return true;
+}
+
+
+/*
+** Can't connect to server; print message and spool if necessary.
+*/
+static void
+CantConnect(char *buff, int mode, int fd)
+{
+ if (buff[0])
+ notice("rejected connection %s", REMclean(buff));
+ else
+ syswarn("cant open_remote");
+ if (mode != 'U')
+ Spool(fd, mode);
+ exit(1);
+}
+
+
+int main(int ac, char *av[])
+{
+ int fd;
+ int i;
+ size_t count;
+ int mode;
+ char buff[SMBUF];
+ int port = NNTP_PORT;
+
+ /* First thing, set up logging and our identity. */
+ openlog("rnews", L_OPENLOG_FLAGS, LOG_INN_PROG);
+ message_program_name = "rnews";
+ message_handlers_notice(1, message_log_syslog_notice);
+ message_handlers_warn(1, message_log_syslog_err);
+ message_handlers_die(1, message_log_syslog_err);
+
+ /* The reason for the following is somewhat obscure and is done only
+ because rnews is sometimes installed setuid.
+
+ The stderr stream used by message_log_syslog_err is associated with
+ file descriptor 2, generally even if that file descriptor is closed.
+ Someone running rnews may close all of the standard file descriptors
+ before running it, in which case, later in its operations, one of the
+ article files or network connections it has open could be file
+ descriptor 2. If an error occurs at that point, the error message may
+ be written to that file or network connection instead of to stderr,
+ with unpredictable results.
+
+ We avoid this by burning three file descriptors if the real and
+ effective user IDs don't match, or if we're running as root. (If they
+ do match, there is no escalation of privileges and at worst the user is
+ just managing to produce a strange bug.) */
+ if (getuid() != geteuid() || geteuid() == 0) {
+ if (open("/dev/null", O_RDONLY) < 0)
+ sysdie("cannot open /dev/null");
+ if (open("/dev/null", O_RDONLY) < 0)
+ sysdie("cannot open /dev/null");
+ if (open("/dev/null", O_RDONLY) < 0)
+ sysdie("cannot open /dev/null");
+ }
+
+ /* Make sure that we switch to the news user if we're running as root,
+ since we may spool files and don't want those files owned by root.
+ Don't require that we be running as the news user, though; there are
+ other setups where rnews might be setuid news or be run by other
+ processes in the news group. */
+ if (getuid() == 0 || geteuid() == 0) {
+ struct passwd *pwd;
+
+ pwd = getpwnam(NEWSUSER);
+ if (pwd == NULL)
+ die("can't resolve %s to a UID (account doesn't exist?)",
+ NEWSUSER);
+ setuid(pwd->pw_uid);
+ }
+
+ if (!innconf_read(NULL))
+ exit(1);
+ UUCPHost = getenv(_ENV_UUCPHOST);
+ PathBadNews = concatpath(innconf->pathincoming, _PATH_BADNEWS);
+ port = innconf->nnrpdpostport;
+
+ umask(NEWSUMASK);
+
+ /* Parse JCL. */
+ fd = STDIN_FILENO;
+ mode = '\0';
+ while ((i = getopt(ac, av, "h:P:NUvr:S:")) != EOF)
+ switch (i) {
+ default:
+ die("usage error");
+ /* NOTRTEACHED */
+ case 'h':
+ UUCPHost = *optarg ? optarg : NULL;
+ break;
+ case 'N':
+ case 'U':
+ mode = i;
+ break;
+ case 'P':
+ port = atoi(optarg);
+ break;
+ case 'v':
+ Verbose = true;
+ break;
+ case 'r':
+ case 'S':
+ remoteServer = optarg;
+ break;
+ }
+ ac -= optind;
+ av += optind;
+
+ /* Parse arguments. At most one, the input file. */
+ switch (ac) {
+ default:
+ die("usage error");
+ /* NOTREACHED */
+ case 0:
+ break;
+ case 1:
+ if (mode == 'U')
+ die("usage error");
+ if (freopen(av[0], "r", stdin) == NULL)
+ sysdie("cannot freopen %s", av[0]);
+ fd = fileno(stdin);
+ InputFile = av[0];
+ break;
+ }
+
+ /* Open the link to the server. */
+ if (remoteServer != NULL) {
+ if (!OpenRemote(remoteServer,port,buff))
+ CantConnect(buff,mode,fd);
+ } else if (innconf->nnrpdposthost != NULL) {
+ if (!OpenRemote(innconf->nnrpdposthost,
+ (port != NNTP_PORT) ? port : innconf->nnrpdpostport, buff))
+ CantConnect(buff, mode, fd);
+ }
+ else {
+#if defined(DO_RNEWSLOCALCONNECT)
+ if (NNTPlocalopen(&FromServer, &ToServer, buff) < 0) {
+ /* If server rejected us, no point in continuing. */
+ if (buff[0])
+ CantConnect(buff, mode, fd);
+ if (!OpenRemote((char *)NULL,
+ (port != NNTP_PORT) ? port : innconf->port, buff))
+ CantConnect(buff, mode, fd);
+ }
+#else
+ if (!OpenRemote((char *)NULL,
+ (port != NNTP_PORT) ? port : innconf->port, buff))
+ CantConnect(buff, mode, fd);
+#endif /* defined(DO_RNEWSLOCALCONNECT) */
+ }
+ close_on_exec(fileno(FromServer), true);
+ close_on_exec(fileno(ToServer), true);
+
+ /* Execute the command. */
+ if (mode == 'U')
+ Unspool();
+ else {
+ if (!UnpackOne(&fd, &count)) {
+ lseek(fd, 0, 0);
+ Spool(fd, mode);
+ }
+ close(fd);
+ WaitForChildren(count);
+ }
+
+ /* Tell the server we're quitting, get his okay message. */
+ fprintf(ToServer, "quit\r\n");
+ fflush(ToServer);
+ fgets(buff, sizeof buff, FromServer);
+
+ /* Return the appropriate status. */
+ exit(0);
+ /* NOTREACHED */
+}
--- /dev/null
+#! /usr/bin/perl
+# fixscript will replace this line with require innshellvars.pl
+
+# @(#)scanspool.pl 1.20 4/6/92 00:47:35
+#
+# Written by: Landon Curt Noll (chongo was here /\../\)
+#
+# This code is placed in the public domain.
+#
+# scanspool - perform a big scan over all articles in /usr/spool/news
+#
+# usage:
+# scanspool [-a active_file] [-s spool_dir] [-v] [-c] [-n]
+#
+# -a active_file active file to use (default /usr/lib/news/active)
+# -s spool_dir spool tree (default /usr/spool/news)
+# -v verbose mode
+# verbose messages begin with a tab
+# show articles found in non-active directories
+# -c check article filenames, don't scan the articles
+# -n don't throttle innd
+#
+# NOTE: This take a while, -v is a good thing if you want to know
+# how far this program has progressed.
+#
+# This program will scan first the active file, noting problems such as:
+#
+# malformed line
+# group aliased to a non-existent group
+# group aliased to a group tat is also aliased
+#
+# Then it will examine all articles under your news spool directory,
+# looking for articles that:
+#
+# basename that starts with a leading 0
+# basename that is out of range with the active file
+# does not contain a Newsgroups: line
+# article that is all header and no text
+# is in a directory for which there is no active group
+# article that is in a group to which it does not belong
+#
+# Scanspool understands aliased groups. Thus, if an article is posted
+# to foo.old.name that is aliases to foo.bar, it will be expected to
+# be found under foo.bar and not foo.old.name.
+#
+# Any group that is of type 'j' or 'x' (4th field of the active file)
+# will be allowed to show up under the junk group.
+#
+# Scanspool assumes that the path of a valid newsgroup's directory
+# from the top of the spool tree will not contain any "." character.
+# Thus, directories such as out.going, tmp.dir, in.coming and
+# news.archive will not be searched. This program also assumes that
+# article basenames contain only decimal digits. Last, files under
+# the top level directory "lost+found" are not scanned.
+#
+# The output of scanspool will start with one of 4 forms:
+#
+# FATAL: fatal or internal error (to stderr)
+#
+# WARN: active or article format problem, (to stderr)
+# group alias problem, find error,
+# article open error
+#
+# path/123: basename starts with 0, (to stdout)
+# article number out of range,
+# article in the wrong directory,
+# article in directory not related to
+# an active non-aliases newsgroup
+#
+# \t ... verbose message starting with a tab (to stdout)
+
+
+# Data structures
+#
+# $gname2type{$name}
+# $name - newsgroup name in foo.dot.form
+# produces => 4th active field (y, n, x, ...)
+# alias type is "=", not "=foo.bar"
+#
+# $realgname{$name}
+# $name - newsgroup name in foo.dot.form
+# produces => newsgroup name in foo.dot.form
+# if type is =, this will be a.b, not $name
+#
+# $lowart{$name}
+# $name - newsgroup name in foo.dot.form
+# produces => lowest article allowed in the group
+# if type is =, this is not valid
+#
+# $highart{$name}
+# $name - newsgroup name in foo.dot.form
+# produces => highest article allowed in the group
+# if type is =, this is not valid
+# If $highart{$name} < $lowart{$name},
+# then the group should be empty
+
+# perl requirements
+#
+require "getopts.pl";
+
+# setup non-buffered stdout and stderr
+#
+select(STDERR);
+$|=1;
+select(STDOUT);
+$|=1;
+
+# global constants
+#
+$prog = $0; # our name
+$spool = "$inn::patharticles";
+$active = "$inn::pathdb/active";
+$ctlinnd = "$inn::pathbin/ctlinnd";
+$reason = "running scanspool"; # throttle reason
+
+# parse args
+#
+&Getopts("a:s:vcn");
+$active = $opt_a if (defined($opt_a));
+$spool = $opt_s if (defined($opt_s));
+
+# throttle innd unless -n
+#
+if (! defined($opt_n)) {
+ system("$ctlinnd throttle '$reason' >/dev/null 2>&1");
+}
+
+# process the active file
+#
+&parse_active($active);
+
+# check the spool directory
+#
+&check_spool($spool);
+
+# unthrottle innd unless -n
+#
+if (! defined($opt_n)) {
+ system("$ctlinnd go '$reason' >/dev/null 2>&1");
+}
+
+# all done
+exit(0);
+
+
+# parse_active - parse the active file
+#
+# From the active file, fill out the @gname2type (type of newsgroup)
+# and @realgname (real/non-aliased name of group), @lowart & @highart
+# (low and high article numbers). This routine will also check for
+# aliases to missing groups or groups that are also aliases.
+#
+sub parse_active
+{
+ local ($active) = $_[0]; # the name of the active file to use
+ local (*ACTIVE); # active file handle
+ local ($line); # active file line
+ local ($name); # name of newsgroup
+ local ($low); # low article number
+ local ($high); # high article number
+ local ($type); # type of newsgroup (4th active field)
+ local ($field5); # 5th active field (should not exist)
+ local ($dir); # directory path of group from $spool
+ local ($alias); # realname of an aliased group
+ local ($linenum); # active file line number
+
+ # if verbose (-v), say what we are doing
+ print "\tscanning $active\n" if defined($opt_v);
+
+ # open the active file
+ open (ACTIVE, $active) || &fatal(1, "cannot open $active");
+
+ # parse each line
+ $linenum = 0;
+ while ($line = <ACTIVE>) {
+
+ # count the line
+ ++$linenum;
+
+ # verify that we have a correct number of tokens
+ if ($line !~ /^\S+ 0*(\d+) 0*(\d+) \S+$/o) {
+ &problem("WARNING: active line is mal-formed at line $linenum");
+ next;
+ }
+ ($name, $high, $low, $type) = $line =~ /^(\S+) 0*(\d+) 0*(\d+) (\S+)$/o;
+
+ # watch for duplicate entries
+ if (defined($realgname{$name})) {
+ &problem("WARNING: ignoring dup group: $name, at line $linenum");
+ next;
+ }
+
+ # record which type it is
+ $gname2type{$name} = $type;
+
+ # record the low and high article numbers
+ $lowart{$name} = $low;
+ $highart{$name} = $high;
+
+ # determine the directory and real group name
+ if ($type eq "j" || $type eq "x") {
+ $dir = "junk";
+ $alias = $name;
+ } elsif ($type =~ /^=(.+)/o) {
+ $alias = $1;
+ ($dir = $alias) =~ s#\.#/#go;
+ $gname2type{$name} = "="; # rename type to be just =
+ } else {
+ $dir = $name;
+ $dir =~ s#\.#/#go;
+ $alias = $name;
+ }
+ $realgname{$name} = $alias;
+ }
+
+ # close the active file
+ close (ACTIVE);
+
+ # be sure that any alias type is aliased to a real group
+ foreach $name (keys %realgname) {
+
+ # skip if not an alias type
+ next if $gname2type{$name} ne "=";
+
+ # be sure that the alias exists
+ $alias = $realgname{$name};
+ if (! defined($realgname{$alias})) {
+ &problem("WARNING: alias for $name: $alias, is not a group");
+ next;
+ }
+
+ # be sure that the alias is not an alias of something else
+ if ($gname2type{$alias} eq "=") {
+ &problem("WARNING: alias for $name: $alias, is also an alias");
+ next;
+ }
+ }
+}
+
+
+# problem - report a problem to stdout
+#
+# Print a message to stdout. Parameters are space separated.
+# A final newline is appended to it.
+#
+# usage:
+# &problem(arg, arg2, ...)
+#
+sub problem
+{
+ local ($line); # the line to write
+
+ # print the line with the header and newline
+ $line = join(" ", @_);
+ print STDERR $line, "\n";
+}
+
+
+# fatal - report a fatal error to stderr and exit
+#
+# Print a message to stderr. The message has the program name prepended
+# to it. Parameters are space separated. A final newline is appended
+# to it. This function exists with the code of exitval.
+#
+# usage:
+# &fatal(exitval, arg, arg2, ...)
+#
+sub fatal
+{
+ local ($exitval) = $_[0]; # what to exit with
+
+ # firewall
+ if ($#_ < 1) {
+ print STDERR "FATAL: fatal called with only ", $#_-1, " arguments\n";
+ if ($#_ < 0) {
+ $exitval = -1;
+ }
+ }
+
+ # print the error message
+ shift(@_);
+ $line = join(" ", @_);
+ print STDERR "$prog: ", $line, "\n";
+
+ # unthrottle innd unless -n
+ #
+ if (! defined($opt_n)) {
+ system("$ctlinnd go '$reason' >/dev/null 2>&1");
+ }
+
+ # exit
+ exit($exitval);
+}
+
+
+# check_spool - check the articles found in the spool directory
+#
+# This subroutine will check all articles found under the $spool directory.
+# It will examine only file path that do not contain any "." or whitespace
+# character, and whose basename is completely numeric. Files under
+# lost+found will also be ignored.
+#
+# given:
+# $spooldir - top of /usr/spool/news article tree
+#
+sub check_spool
+{
+ local ($spooldir) = $_[0]; # top of article tree
+ local (*FILEFILE); # pipe from the find files process
+ local ($filename); # article pathname under $spool
+ local ($artgrp); # group of an article
+ local ($artnum); # article number in a group
+ local ($prevgrp); # previous different value of $artgrp
+ local ($preverrgrp); # previous non-active $artgrp
+ local (*ARTICLE); # article handle
+ local ($aline); # header line from an article
+ local (@group); # array of groups from the Newsgroup header
+ local ($j);
+
+ # if verbose, say what we are doing
+ print "\tfinding articles under $spooldir\n" if defined($opt_v);
+
+ # move to the $spool directory
+ chdir $spooldir || &fatal(2, "cannot chdir to $spool");
+
+ # start finding files
+ #
+ if (!open (FINDFILE,
+ "find . \\( -type f -o -type l \\) -name '[0-9]*' -print 2>&1 |")) {
+ &fatal(3, "cannot start find in $spool");
+ }
+
+ # process each history line
+ #
+ while ($filename = <FINDFILE>) {
+
+ # if the line contains find:, assume it is a find error and print it
+ chop($filename);
+ if ($filename =~ /find:\s/o) {
+ &problem("WARNING:", $filename);
+ next;
+ }
+
+ # remove the \n and ./ that find put in our path
+ $filename =~ s#^\./##o;
+
+ # skip is this path has a . in it (beyond a leading ./)
+ next if ($filename =~ /\./o);
+
+ # skip if lost+found
+ next if ($filename =~ m:^lost+found/:o);
+
+ # skip if not a numeric basename
+ next if ($filename !~ m:/\d+$:o);
+
+ # get the article's newsgroup name (based on its path from $spool)
+ $artgrp = $filename;
+ $artgrp =~ s#/\d+$##o;
+ $artgrp =~ s#/#.#go;
+
+ # if verbose (-v), then note if our group changed
+ if (defined($opt_v) && $artgrp ne $prevgrp) {
+ print "\t$artgrp\n";
+ $prevgrp = $artgrp;
+ }
+
+ # note if the article is not in a directory that is used by
+ # a real (non-aliased) group in the active file
+ #
+ # If we complained about this dgroup before, don't complain again.
+ # If verbose, note files that could be removed.
+ #
+ if (!defined($gname2type{$artgrp}) || $gname2type{$artgrp} =~ /[=jx]/o){
+ if ($preverrgrp ne $artgrp) {
+ &problem("$artgrp: not an active group directory");
+ $preverrgrp = $artgrp;
+ }
+ if (defined($opt_v)) {
+ &problem("$filename: article found in non-active directory");
+ }
+ next;
+ }
+
+ # check on the article number
+ $artnum = $filename;
+ $artnum =~ s#^.+/##o;
+ if ($artnum =~ m/^0/o) {
+ &problem("$filename: article basename starts with a 0");
+ }
+ if (defined($gname2type{$artgrp})) {
+ if ($lowart{$artgrp} > $highart{$artgrp}) {
+ &problem("$filename: active indicates group should be empty");
+ } elsif ($artnum < $lowart{$artgrp}) {
+ &problem("$filename: article number is too low");
+ } elsif ($artnum > $highart{$artgrp}) {
+ &problem("$filename: article number is too high");
+ }
+ }
+
+ # if check filenames only (-c), then do nothing else with the file
+ next if (defined($opt_c));
+
+ # don't open a control or junk, they can be from anywhere
+ next if ($artgrp eq "control" || $artgrp eq "junk");
+
+ # try open the file
+ if (!open(ARTICLE, $filename)) {
+
+ # the find is now gone (expired?), give up on it
+ &problem("WARNING: cannot open $filename");
+ next;
+ }
+
+ # read until the Newsgroup header line is found
+ AREADLINE:
+ while ($aline = <ARTICLE>) {
+
+ # catch the newsgroup: header
+ if ($aline =~ /^Newsgroups:\w*\W/io) {
+
+ # convert $aline into a comma separated list of groups
+ $aline =~ s/^Newsgroups://io;
+ $aline =~ tr/ \t\n//d;
+
+ # form an array of news groups
+ @group = split(",", $aline);
+
+ # see if any groups in the Newsgroup list are our group
+ for ($j=0; $j <= $#group; ++$j) {
+
+ # look at the group
+ if ($realgname{$group[$j]} eq $artgrp) {
+ # this article was posted to this group
+ last AREADLINE;
+ }
+ }
+
+ # no group or group alias was found
+ &problem("$filename: does not belong in $artgrp");
+ last;
+
+ # else watch for the end of the header
+ } elsif ($aline =~ /^\s*$/o) {
+
+ # no Newsgroup: header found
+ &problem("WARNING: $filename: no Newsgroup header");
+ last;
+ }
+ if (eof(ARTICLE)) {
+ &problem("WARNING: $filename: EOF found while reading header");
+ }
+ }
+
+ # close the article
+ close(ARTICLE);
+ }
+
+ # all done with the find
+ close(FINDFILE);
+}
--- /dev/null
+/* $Id: sm.c 6682 2004-03-06 18:31:15Z rra $
+**
+** Provide a command line interface to the storage manager
+*/
+
+#include "config.h"
+#include "clibrary.h"
+
+#include "inn/innconf.h"
+#include "inn/messages.h"
+#include "inn/qio.h"
+#include "storage.h"
+
+static const char usage[] = "\
+Usage: sm [-dHiqrRS] [token ...]\n\
+\n\
+Command-line interface to the INN storage manager. The default action is\n\
+to display the complete article associated with each token given. If no\n\
+tokens are specified on the command line, they're read from stdin, one per\n\
+line.\n\
+\n\
+ -d, -r Delete the articles associated with the given tokens\n\
+ -H Display the headers of articles only\n\
+ -i Translate tokens into newsgroup names and article numbers\n\
+ -q Suppress all error messages except usage\n\
+ -R Display the raw article rather than undoing wire format\n\
+ -S Output articles in rnews batch file format\n";
+
+/* The options that can be set on the command line, used to determine what to
+ do with each token. */
+struct options {
+ bool artinfo; /* Show newsgroup and article number. */
+ bool delete; /* Delete articles instead of showing them. */
+ bool header; /* Display article headers only. */
+ bool raw; /* Show the raw wire-format articles. */
+ bool rnews; /* Output articles as rnews batch files. */
+};
+
+
+/*
+** Process a single token, performing the operations specified in the given
+** options struct. Calls warn and die to display error messages; -q is
+** implemented by removing all the warn and die error handlers.
+*/
+static bool
+process_token(const char *id, const struct options *options)
+{
+ TOKEN token;
+ struct artngnum artinfo;
+ ARTHANDLE *article;
+ size_t length;
+ char *text;
+
+ if (!IsToken(id)) {
+ warn("%s is not a storage token", id);
+ return false;
+ }
+ token = TextToToken(id);
+
+ if (options->artinfo) {
+ if (!SMprobe(SMARTNGNUM, &token, &artinfo)) {
+ warn("could not get article information for %s", id);
+ return false;
+ } else {
+ printf("%s: %lu\n", artinfo.groupname, artinfo.artnum);
+ free(artinfo.groupname);
+ }
+ } else if (options->delete) {
+ if (!SMcancel(token)) {
+ warn("could not remove %s: %s", id, SMerrorstr);
+ return false;
+ }
+ } else {
+ article = SMretrieve(token, options->header ? RETR_HEAD : RETR_ALL);
+ if (article == NULL) {
+ warn("could not retrieve %s", id);
+ return false;
+ }
+ if (options->raw) {
+ if (fwrite(article->data, article->len, 1, stdout) != 1)
+ die("output failed");
+ } else {
+ text = FromWireFmt(article->data, article->len, &length);
+ if (options->rnews)
+ printf("#! rnews %lu\n", (unsigned long) length);
+ if (fwrite(text, length, 1, stdout) != 1)
+ die("output failed");
+ free(text);
+ }
+ SMfreearticle(article);
+ }
+ return true;
+}
+
+
+int
+main(int argc, char *argv[])
+{
+ int option;
+ bool okay, status;
+ struct options options = { false, false, false, false, false };
+
+ message_program_name = "sm";
+
+ if (!innconf_read(NULL))
+ exit(1);
+
+ while ((option = getopt(argc, argv, "iqrdRSH")) != EOF) {
+ switch (option) {
+ case 'd':
+ case 'r':
+ options.delete = true;
+ break;
+ case 'H':
+ options.header = true;
+ break;
+ case 'i':
+ options.artinfo = true;
+ break;
+ case 'q':
+ message_handlers_warn(0);
+ message_handlers_die(0);
+ break;
+ case 'R':
+ options.raw = true;
+ break;
+ case 'S':
+ options.rnews = true;
+ break;
+ default:
+ fprintf(stderr, usage);
+ exit(1);
+ }
+ }
+
+ /* Check options for consistency. */
+ if (options.artinfo && options.delete)
+ die("-i cannot be used with -r, -d");
+ if (options.artinfo && (options.header || options.raw || options.rnews))
+ die("-i cannot be used with -H, -R, or -S");
+ if (options.delete && (options.header || options.rnews))
+ die("-r or -d cannot be used with -H or -S");
+ if (options.raw && options.rnews)
+ die("-R cannot be used with -S");
+ if (options.header && options.rnews)
+ die("-H cannot be used with -S");
+
+ /* Initialize the storage manager. If we're doing article deletions, we
+ need to open it read/write. */
+ if (options.delete) {
+ bool value = true;
+
+ if (!SMsetup(SM_RDWR, &value))
+ die("cannot set up storage manager");
+ }
+ if (!SMinit())
+ die("cannot initialize storage manager: %s", SMerrorstr);
+
+ /* Process tokens. If no arguments were given on the command line,
+ process tokens from stdin. Otherwise, walk through the remaining
+ command line arguments. */
+ okay = true;
+ if (optind == argc) {
+ QIOSTATE *qp;
+ char *line;
+
+ qp = QIOfdopen(fileno(stdin));
+ for (line = QIOread(qp); line != NULL; line = QIOread(qp)) {
+ status = process_token(line, &options);
+ okay = okay && status;
+ }
+ if (QIOerror(qp)) {
+ if (QIOtoolong(qp))
+ die("input line too long");
+ sysdie("error reading stdin");
+ }
+ QIOclose(qp);
+ } else {
+ int i;
+
+ for (i = optind; i < argc; i++) {
+ status = process_token(argv[i], &options);
+ okay = okay && status;
+ }
+ }
+
+ SMshutdown();
+ exit(okay ? 0 : 1);
+}
--- /dev/null
+/* $Id: sys2nf.c 7741 2008-04-06 09:51:47Z iulius $
+**
+** Read a C news "sys" file and split it up into a set of INN
+** newsfeeds entries. Also works with B news.
+**
+** Once done, edit all files that have HELP or all in them.
+** Review all files, anyway.
+*/
+
+#include "config.h"
+#include "clibrary.h"
+#include <ctype.h>
+#include <errno.h>
+#include <sys/stat.h>
+
+#include "inn/innconf.h"
+#include "libinn.h"
+#include "nntp.h"
+
+#define TEMPFILE ":tmp"
+static char **Groups;
+
+
+/*
+** Fill in the Groups array with the names of all active newsgroups.
+*/
+static void
+ReadActive(act)
+ char *act;
+{
+ FILE *F;
+ int i;
+ char buff[BUFSIZ];
+ char *p;
+
+ /* Open file, count lines. */
+ if ((F = fopen(act, "r")) == NULL) {
+ perror(act);
+ exit(1);
+ }
+ for (i = 0; fgets(buff, sizeof buff, F) != NULL; i++)
+ continue;
+ Groups = xmalloc((i + 2) * sizeof(char *));
+
+ /* Fill in each word. */
+ rewind(F);
+ for (i = 0; fgets(buff, sizeof buff, F) != NULL; i++) {
+ if ((p = strchr(buff, ' ')) != NULL)
+ *p = '\0';
+ Groups[i] = xstrdup(buff);
+ }
+ Groups[i] = NULL;
+ fclose(F);
+}
+
+
+/*
+** Read in the sys file and turn it into an array of strings, one
+** per continued line.
+*/
+char **
+ReadSys(sys)
+ char *sys;
+{
+ char *p;
+ char *to;
+ char *site;
+ int i;
+ char *data;
+ char **strings;
+
+ /* Read in the file, get rough count. */
+ if ((data = ReadInFile(sys, (struct stat *)NULL)) == NULL) {
+ perror(sys);
+ exit(1);
+ }
+ for (p = data, i = 0; (p = strchr(p, '\n')) != NULL; p++, i++)
+ continue;
+
+ /* Scan the file, glue all multi-line entries. */
+ for (strings = xmalloc((i + 1) * sizeof(char *)), i = 0, to = p = data; *p; ) {
+ for (site = to; *p; ) {
+ if (*p == '\n') {
+ p++;
+ *to = '\0';
+ break;
+ }
+ if (*p == '\\' && p[1] == '\n')
+ while (*++p && CTYPE(isspace, *p))
+ continue;
+ else
+ *to++ = *p++;
+ }
+ *to++ = '\0';
+ if (*site == '\0')
+ continue;
+ strings[i++] = xstrdup(site);
+ }
+ strings[i] = NULL;
+ free(data);
+ return strings;
+}
+
+
+/*
+** Is this the name of a top-level group? We want a simple name, "foo",
+** and should find a "foo." in the group list.
+*/
+static bool
+Toplevel(p)
+ char *p;
+{
+ char **gp;
+ char *g;
+ int i;
+
+ if (strchr(p, '.') != NULL)
+ return false;
+ for (i = strlen(p) - 1, gp = Groups; (g = *gp++) != NULL; )
+ if (strncmp(p, g, i) == 0 && g[i + 1] == '.')
+ return true;
+ return false;
+}
+
+
+/*
+** Do we have a name that's a prefix for more then one newsgroup?
+** For "foo.bar", we must find more then one "foo.bar" or "foo.bar."
+*/
+static bool
+GroupPrefix(p)
+ char *p;
+{
+ char **gp;
+ char *g;
+ int count;
+ int i;
+
+ if (strchr(p, '.') == NULL)
+ return false;
+ for (i = strlen(p), count = 0, gp = Groups; (g = *gp++) != NULL; )
+ if (strcmp(p, g) == 0 || (strncmp(p, g, i) == 0 && g[i] == '.'))
+ count++;
+ return count > 1;
+}
+
+
+/*
+** Step through the old subscription list, try to update each one in
+** turn.
+*/
+static void
+DoSub(F, p)
+ FILE *F;
+ char *p;
+{
+ char *s;
+ int len, i;
+ bool matched;
+ bool SawBang;
+ bool SawAll;
+
+ /* Distributions, not newsgroups. */
+ static const char * const distributions[] = {
+ "world", "na", "usa", "inet", "mod", "net", "local"
+ };
+
+ /* Newsgroup hierarchies. */
+ static const char * const hierarchies[] = {
+ "comp", "misc", "news", "rec", "sci", "soc", "talk", "alt", "bionet",
+ "bit", "biz", "clari", "ddn", "gnu", "ieee", "k12", "pubnet", "trial",
+ "u3b", "vmsnet",
+
+ "ba", "ca", "dc", "ne", "ny", "tx",
+
+ "info", "mail", "opinions", "uunet"
+ }
+
+ if ((s = strtok(p, ",")) == NULL)
+ return;
+
+ fprintf(F, "!*");
+ len = 8 + 1 + 2;
+ do {
+ for (matched = false, i = 0; i < ARRAY_SIZE(distributions); i++)
+ if (strcmp(s, distributions[i]) == 0) {
+ matched = true;
+ break;
+ }
+ if (matched)
+ continue;
+
+ if (innconf->mergetogroups)
+ if (strcmp(s, "!to") == 0 || strncmp(s, "to.", 3) == 0)
+ continue;
+
+ putc(',', F);
+ len++;
+
+ if (len + strlen(s) + 3 > 72) {
+ fprintf(F,"\\\n\t ");
+ len = 12;
+ }
+
+ SawBang = *s == '!';
+ if (SawBang) {
+ putc('!', F);
+ len++;
+ s++;
+ }
+
+ SawAll = (strcmp(s, "all") == 0);
+ if (SawAll)
+ s = SawBang ? "*" : "*,!control,!control.*";
+ len += strlen(s);
+ fprintf(F, "%s", s);
+
+ if (SawAll)
+ ;
+ else {
+ for (matched = false, i = 0; i < ARRAY_SIZE(distributions); i++)
+ if (strcmp(s, hierarchies[i]) == 0) {
+ matched = true;
+ break;
+ }
+
+ if (matched) {
+ fprintf(F, ".*");
+ len += 2;
+ } else if (GroupPrefix(s)) {
+ putc('*', F);
+ len++;
+ }
+ }
+ } while ((s = strtok((char *)NULL, ",")) != NULL);
+}
+
+
+int
+main(ac, av)
+ int ac;
+ char *av[];
+{
+ FILE *F;
+ FILE *out;
+ char **sites;
+ char *f2;
+ char *f3;
+ char *f4;
+ char *p;
+ char *q;
+ char *site;
+ char buff[256];
+ char *act;
+ char *dir;
+ char *sys;
+ int i;
+
+ if (!innconf_read(NULL))
+ exit(1);
+ /* Set defaults. */
+ act = "/usr/local/lib/newslib/active";
+ sys = "sys";
+ dir = "feeds";
+ while ((i = getopt(ac, av, "a:s:d:")) != EOF)
+ switch (i) {
+ default:
+ exit(1);
+ /* NOTREACHED */
+ case 'a': act = optarg; break;
+ case 'd': dir = optarg; break;
+ case 's': sys = optarg; break;
+ }
+
+ sites = ReadSys(sys);
+ ReadActive(act);
+ if (mkdir(dir, 0777) < 0 && errno != EEXIST)
+ perror(dir), exit(1);
+ if (chdir(dir) < 0)
+ perror("chdir"), exit(1);
+ for ( ; ; ) {
+ /* Get next non-comment ilne. */
+ if ((p = *sites++) == NULL)
+ break;
+ for (F = fopen(TEMPFILE, "w"); p && *p == '#'; p = *sites++)
+ fprintf(F, "%s\n", p);
+ if (p == NULL) {
+ fclose(F);
+ break;
+ }
+ site = xstrdup(p);
+ if ((f2 = strchr(site, ':')) == NULL)
+ f2 = "HELP";
+ else
+ *f2++ = '\0';
+ if ((f3 = strchr(f2, ':')) == NULL)
+ f3 = "HELP";
+ else
+ *f3++ = '\0';
+ if ((f4 = strchr(f3, ':')) == NULL)
+ f4 = "HELP";
+ else
+ *f4++ = '\0';
+
+ /* Write the fields. */
+ fprintf(F, "%s\\\n", site);
+ fprintf(F, "\t:");
+ DoSub(F, f2);
+ fprintf(F, "\\\n");
+ if (strcmp(f3, "n") == 0)
+ fprintf(F, "\t:Tf,Wnm\\\n", f3);
+ else
+ fprintf(F, "\t:HELP%s\\\n", f3);
+ fprintf(F, "\t:%s\n", f4);
+ if (ferror(F) || fclose(F) == EOF)
+ perror(TEMPFILE), exit(1);
+
+ free(site);
+
+ /* Find the sitename. */
+ for (q = p; *q && *q != '/' && *q != ':'; q++)
+ continue;
+ *q = '\0';
+
+ /* Append temp file to site file. */
+ if ((F = fopen(TEMPFILE, "r")) == NULL)
+ perror(TEMPFILE), exit(1);
+ if ((out = xfopena(p)) == NULL)
+ perror(p), exit(1);
+ while ((i = fread(buff, 1, sizeof buff, F)) > 0)
+ if (fwrite(buff, 1, i, out) != i)
+ perror(p), exit(1);
+ fclose(F);
+ if (fclose(out) == EOF)
+ perror(p), exit(1);
+
+ if (unlink(TEMPFILE) < 0)
+ perror("can't unlink temp file");
+ }
+
+ exit(0);
+ /* NOTREACHED */
+}
--- /dev/null
+# This file is automatically generated by buildconfig
+
+METHOD_SOURCES = hisv6/hisv6.c
+EXTRA_SOURCES =
+PROGRAMS =
--- /dev/null
+## $Id: Makefile 7727 2008-04-06 07:59:46Z iulius $
+
+include ../Makefile.global
+
+top = ..
+CFLAGS = $(GCFLAGS) -I.
+
+SOURCES = his.c hismethods.c $(METHOD_SOURCES)
+OBJECTS = $(SOURCES:.c=.o)
+LOBJECTS = $(OBJECTS:.o=.lo)
+
+.SUFFIXES: .lo
+
+all: library programs
+
+# Included here after the all target, since additional rules are defined in
+# Make.methods to be sure that we recurse properly to build the methods.
+include Make.methods
+
+warnings:
+ $(MAKE) COPT='$(WARNINGS)' all
+
+install: all
+ $(LI_XPUB) libinnhist.$(EXTLIB) $(D)$(PATHLIB)/libinnhist.$(EXTLIB)
+
+library: libinnhist.$(EXTLIB)
+
+programs: $(PROGRAMS)
+
+clobber clean distclean:
+ rm -f *.o *.lo */*.o */*.lo libinnhist.la libinnhist.a
+ rm -f libinnhist_pure_*.a .pure $(PROGRAMS)
+ rm -f buildconfig hismethods.c hismethods.h
+ rm -f profiled libinnhist$(PROFSUFFIX).a
+ rm -rf .libs */.libs
+
+tags ctags: $(SOURCES)
+ $(CTAGS) $(SOURCES) ../include/*.h
+
+$(FIXSCRIPT):
+ @echo Run configure before running make. See INSTALL for details.
+ @exit 1
+
+libinnhist.la: $(OBJECTS) $(LIBSTORAGE) $(LIBINN)
+ $(LIBLD) $(LDFLAGS) -o $@ $(LOBJECTS) \
+ $(LIBSTORAGE) $(LIBINN) $(EXTSTORAGELIBS) $(LIBS) \
+ -rpath $(PATHLIB) -version-info 2:0:0
+
+libinnhist.a: $(OBJECTS)
+ ar r $@ $(OBJECTS)
+ $(RANLIB) libinnhist.a
+
+# Try to set up these rules so that buildconfig is only run once.
+# Make.methods is included in the distribution tarball since some non-GNU
+# makes can't deal with including a non-existent file, so don't depend on
+# it. The dependencies aren't entirely accurate; you really want to re-run
+# buildconfig each time a new subdirectory is added to the directory. But
+# adding a dependency on . is a bit too non-portable for my taste and causes
+# too many rebuilds.
+Make.methods hismethods.h: hismethods.c buildconfig
+hismethods.c: buildconfig
+ ./buildconfig
+
+buildconfig: buildconfig.in $(FIXSCRIPT)
+ $(FIXSCRIPT) -i buildconfig.in
+
+.c.o .c.lo:
+ $(LIBCC) $(CFLAGS) $(CCOUTPUT)
+
+$(LIBINN): ; (cd ../lib ; $(MAKE))
+$(LIBSTORAGE): ; (cd ../storage ; $(MAKE) library)
+
+
+## Profiling. The rules are a bit brute-force, but good enough.
+
+profiled: libinnhist$(PROFSUFFIX).a
+ date >$@
+
+libinnhist$(PROFSUFFIX).a: $(SOURCES)
+ rm -f $(OBJECTS)
+ $(MAKEPROFILING) libinnhist.a
+ mv libinnhist.a libinnhist$(PROFSUFFIX).a
+ $(RANLIB) libinnhist$(PROFSUFFIX).a
+ rm -f $(OBJECTS)
+
+
+## Dependencies. Default list, below, is probably good enough.
+
+depend: $(SOURCES) $(EXTRA_SOURCES)
+ $(MAKEDEPEND) '$(CFLAGS)' $(SOURCES) $(EXTRA_SOURCES)
+
+# DO NOT DELETE THIS LINE -- make depend depends on it.
+his.o: his.c ../include/config.h ../include/inn/defines.h \
+ ../include/inn/system.h ../include/clibrary.h ../include/config.h \
+ ../include/portable/time.h ../include/config.h ../include/inn/history.h \
+ ../include/inn/defines.h ../include/inn/messages.h \
+ ../include/inn/timer.h ../include/libinn.h ../include/storage.h \
+ hisinterface.h hismethods.h
+hismethods.o: hismethods.c hisinterface.h ../include/config.h \
+ ../include/inn/defines.h ../include/inn/system.h hismethods.h \
+ hisv6/hisv6.h
+hisv6/hisv6.o: hisv6/hisv6.c ../include/config.h ../include/inn/defines.h \
+ ../include/inn/system.h ../include/clibrary.h ../include/config.h \
+ hisinterface.h ../include/config.h hisv6/hisv6.h hisv6/hisv6-private.h \
+ ../include/inn/history.h ../include/inn/defines.h ../include/storage.h \
+ ../include/libinn.h ../include/dbz.h ../include/libinn.h \
+ ../include/inn/innconf.h ../include/inn/timer.h ../include/inn/qio.h \
+ ../include/inn/sequence.h ../include/inndcomm.h
--- /dev/null
+#! /usr/bin/perl
+
+## $Id: buildconfig.in 6806 2004-05-18 01:18:57Z rra $
+##
+## Generate linkage code and makefiles for storage and overview methods.
+##
+## Goes through all subdirectories of the current directory and finds
+## directories that history methods or overview methods. Builds
+## hismethods.[ch] as well as makefile stubs.
+
+require 5.003;
+
+use strict;
+use vars qw(@HISTORY);
+
+# History API functions.
+@HISTORY = qw(open close sync lookup check write replace expire walk remember
+ ctl);
+
+# Used to make heredocs more readable.
+sub unquote { my ($string) = @_; $string =~ s/^:( {0,7}|\t)//gm; $string }
+
+# Parse a hismethod.config file for a history method, putting information
+# about that history method into the given hash ref.
+sub parse_config {
+ my ($dir, $file, $config) = @_;
+ local $_;
+ $$config{sources} ||= [];
+ $$config{extra} ||= [];
+ $$config{programs} ||= [];
+ $$config{makefiles} ||= [];
+ open (CONFIG, "$dir/$file") or die "Can't open $dir/$file: $!\n";
+ while (<CONFIG>) {
+ s/^\s+//;
+ s/\s+$//;
+ if (/^name\s*=\s*(\S+)$/) {
+ my $method = $1;
+ die "$dir/$file: $method has already been defined\n"
+ if (defined $$config{method}{$method});
+ $$config{method}{$method} = $dir;
+ } elsif (/^number\s*=\s*(\d+)$/) {
+ my $number = $1;
+ if (defined $$config{number}{$number}) {
+ die "$dir/$file: method number $number was already "
+ . "allocated in $$config{number}{$number}\n";
+ }
+ $$config{number}{$dir} = $number;
+ } elsif (/^sources\s*=\s*(.*)/) {
+ my $sources = $1;
+ my @sources = split (' ', $sources);
+ push (@{ $$config{sources} }, map { "$dir/$_" } @sources);
+ } elsif (/^extra-sources\s*=\s*(.*)/) {
+ my $extra = $1;
+ my @extra = split (' ', $extra);
+ push (@{ $$config{extra} }, map { "$dir/$_" } @extra);
+ } elsif (/^programs\s*=\s*(.*)/) {
+ my $programs = $1;
+ my @programs = split (' ', $programs);
+ push (@{ $$config{programs} }, map { "$dir/$_" } @programs);
+ } else {
+ warn "$dir/$file: ignoring unknown line: $_\n";
+ }
+ }
+
+ # If there is a makefile fragment in the directory, note it.
+ if (-f "$dir/hismethod.mk") {
+ push (@{ $$config{makefiles} }, "$dir/hismethod.mk");
+ }
+}
+
+# Write out include directives for a list of files.
+sub write_includes {
+ my ($fh, $config) = @_;
+ my $method;
+ for $method (sort keys %{ $$config{method} }) {
+ my $path = $$config{method}{$method};
+ print $fh qq(\#include "$path/$method.h"\n);
+ }
+}
+
+# Write out the method struct.
+sub write_methods {
+ my ($fh, $config, $prefix, @funcs) = @_;
+ my ($notfirst, $method);
+ for $method (sort keys %{ $$config{method} }) {
+ print $fh "\n},\n" if $notfirst;
+ print $fh qq(\{\n "$method");
+ print $fh ', ', $prefix, '_', uc ($method) if $prefix;
+ for (@funcs) {
+ print $fh ",\n ${method}_$_";
+ }
+ $notfirst++;
+ }
+ print $fh "\n}\n};\n\n";
+}
+
+# Write out hismethods.c and hismethods.h for the interface to the history
+# methods.
+sub write_history {
+ my $history = shift;
+ open (DEF, '> hismethods.c.new')
+ or die "Can't create hismethods.c.new: $!\n";
+ print DEF unquote (<<'EOE');
+: /* This file is automatically generated by buildconfig. */
+:
+: #include "hisinterface.h"
+: #include "hismethods.h"
+EOE
+ my $method;
+ write_includes (\*DEF, $history);
+ print DEF "\nHIS_METHOD his_methods[",
+ scalar (keys %{ $$history{method} }), "] = {\n";
+ write_methods (\*DEF, $history, undef, @HISTORY);
+ close DEF;
+ rename ('hismethods.c.new', 'hismethods.c');
+
+ open (H, '> hismethods.h.new') or die "Can't open hismethods.h.new: $!\n";
+ print H unquote (<<'EOE');
+: /* This file is automatically generated by buildconfig */
+:
+: #ifndef HISMETHODS_H
+: #define HISMETHODS_H 1
+:
+: #include "hisinterface.h"
+:
+EOE
+ print H '#define NUM_HIS_METHODS ',
+ scalar (keys %{ $$history{method} }), "\n";
+ print H unquote (<<'EOE');
+:
+: extern HIS_METHOD his_methods[NUM_HIS_METHODS];
+:
+: #endif /* HISMETHODS_H */
+EOE
+ close H;
+ rename ('hismethods.h.new', 'hismethods.h');
+}
+
+# Return a string setting a makefile variable. Tab over the = properly and
+# wrap to fit our coding standards.
+sub makefile_var {
+ my ($variable, @values) = @_;
+ my $output;
+ $output = sprintf ("%-15s =", $variable);
+ my $column = 17;
+ for (@values) {
+ if ($column > 17 && 77 - $column < length ($_)) {
+ $output .= " \\\n" . ' ' x 17;
+ $column = 17;
+ }
+ $output .= " $_";
+ $column += 1 + length ($_);
+ }
+ $output .= "\n";
+ return $output;
+}
+
+# Write out the makefile fragment for history methods.
+sub write_makefile {
+ my ($dirs, $sources, $extra, $programs, $makefiles) = @_;
+ open (MAKE, '> Make.methods.new')
+ or die "Can't create Make.methods.new: $!\n";
+ print MAKE "# This file is automatically generated by buildconfig\n\n";
+ print MAKE makefile_var ('METHOD_SOURCES', @$sources);
+ print MAKE makefile_var ('EXTRA_SOURCES', @$extra);
+ print MAKE makefile_var ('PROGRAMS', @$programs);
+ for (@$makefiles) {
+ print MAKE "\n\n## Included from $_\n\n";
+ open (FRAG, $_) or die "Can't open $_: $!\n";
+ print MAKE <FRAG>;
+ close FRAG;
+ }
+ rename ('Make.methods.new', 'Make.methods');
+}
+
+my ($dir, %history);
+if (!-d 'hisv6') {
+ if (-d 'history/cnfs') {
+ chdir 'history' or die "Can't chdir to history: $!\n";
+ } else {
+ die "Can't find history directory (looking for history/hisv6)\n";
+ }
+}
+opendir (D, ".") or die "Can't open current directory: $!\n";
+my @dirs = sort readdir D;
+for $dir (@dirs) {
+ if (-e "$dir/hismethod.config") {
+ parse_config ($dir, 'hismethod.config', \%history);
+ }
+}
+write_history (\%history);
+@dirs = sort values %{ $history{method} };
+my @sources = sort @{ $history{sources} };
+my @extra = sort @{ $history{extra} };
+my @programs = sort @{ $history{programs} };
+my @makefiles = sort @{ $history{makefiles} };
+write_makefile (\@dirs, \@sources, \@extra, \@programs, \@makefiles);
--- /dev/null
+/* $Id: his.c 6351 2003-05-19 02:00:06Z rra $
+**
+** API to history routines
+**
+** Copyright (c) 2001, Thus plc
+**
+** Redistribution and use of the source code in source and binary
+** forms, with or without modification, are permitted provided that
+** the following 3 conditions are met:
+**
+** 1. Redistributions of the source code must retain the above
+** copyright notice, this list of conditions and the disclaimer
+** set out below.
+**
+** 2. Redistributions of the source code in binary form must
+** reproduce the above copyright notice, this list of conditions
+** and the disclaimer set out below in the documentation and/or
+** other materials provided with the distribution.
+**
+** 3. Neither the name of the Thus plc nor the names of its
+** contributors may be used to endorse or promote products
+** derived from this software without specific prior written
+** permission from Thus plc.
+**
+** Disclaimer:
+**
+** "THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS 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 THE DIRECTORS
+** 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 "config.h"
+#include "clibrary.h"
+#include "portable/time.h"
+#include <errno.h>
+#include <syslog.h>
+
+#include "inn/history.h"
+#include "inn/messages.h"
+#include "inn/timer.h"
+#include "libinn.h"
+#include "storage.h"
+
+#include "hisinterface.h"
+#include "hismethods.h"
+
+struct hiscache {
+ HASH Hash; /* Hash value of the message-id using Hash() */
+ bool Found; /* Whether this entry is in the dbz file yet */
+};
+
+struct history {
+ struct hismethod *methods;
+ void *sub;
+ struct hiscache *cache;
+ size_t cachesize;
+ const char *error;
+ struct histstats stats;
+};
+
+enum HISRESULT {HIScachehit, HIScachemiss, HIScachedne};
+
+static const struct histstats nullhist = {0};
+
+/*
+** Put an entry into the history cache
+*/
+static void
+his_cacheadd(struct history *h, HASH MessageID, bool Found)
+{
+ unsigned int i, loc;
+
+ his_logger("HIScacheadd begin", S_HIScacheadd);
+ if (h->cache != NULL) {
+ memcpy(&loc, ((char *)&MessageID) + (sizeof(HASH) - sizeof(loc)),
+ sizeof(loc));
+ i = loc % h->cachesize;
+ memcpy((char *)&h->cache[i].Hash, (char *)&MessageID, sizeof(HASH));
+ h->cache[i].Found = Found;
+ }
+ his_logger("HIScacheadd end", S_HIScacheadd);
+}
+
+/*
+** Lookup an entry in the history cache
+*/
+static enum HISRESULT
+his_cachelookup(struct history *h, HASH MessageID)
+{
+ unsigned int i, loc;
+
+ if (h->cache == NULL)
+ return HIScachedne;
+ his_logger("HIScachelookup begin", S_HIScachelookup);
+ memcpy(&loc, ((char *)&MessageID) + (sizeof(HASH) - sizeof(loc)), sizeof(loc));
+ i = loc % h->cachesize;
+ if (memcmp((char *)&h->cache[i].Hash, (char *)&MessageID, sizeof(HASH)) == 0) {
+ his_logger("HIScachelookup end", S_HIScachelookup);
+ if (h->cache[i].Found) {
+ return HIScachehit;
+ } else {
+ return HIScachemiss;
+ }
+ } else {
+ his_logger("HIScachelookup end", S_HIScachelookup);
+ return HIScachedne;
+ }
+}
+
+/*
+** set error status to that indicated by s; doesn't copy the string,
+** assumes the caller did that for us
+*/
+void
+his_seterror(struct history *h, const char *s)
+{
+ if (h != NULL) {
+ if (h->error)
+ free((void *)h->error);
+ h->error = s;
+ }
+ if (s != NULL)
+ warn("%s", s);
+}
+
+struct history *
+HISopen(const char *path, const char *method, int flags)
+{
+ struct history *h;
+ int i;
+
+ for (i = 0; i < NUM_HIS_METHODS; ++i) {
+ if (strcmp(method, his_methods[i].name) == 0)
+ break;
+ }
+ if (i == NUM_HIS_METHODS) {
+ warn("`%s' isn't a valid history method", method);
+ return NULL;
+ }
+
+ /* allocate up our structure and start subordinate history
+ * manager */
+ h = xmalloc(sizeof *h);
+ h->methods = &his_methods[i];
+ h->cache = NULL;
+ h->error = NULL;
+ h->cachesize = 0;
+ h->stats = nullhist;
+ h->sub = (*h->methods->open)(path, flags, h);
+ if (h->sub == NULL) {
+ free(h);
+ h = NULL;
+ }
+ return h;
+}
+
+static bool
+his_checknull(struct history *h)
+{
+ if (h != NULL)
+ return false;
+ errno = EBADF;
+ return true;
+
+}
+
+bool
+HISclose(struct history *h)
+{
+ bool r;
+
+ if (his_checknull(h))
+ return false;
+ r = (*h->methods->close)(h->sub);
+ if (h->cache) {
+ free(h->cache);
+ h->cache = NULL;
+ }
+ if (h->error) {
+ free((void *)h->error);
+ h->error = NULL;
+ }
+ free(h);
+ return r;
+}
+
+bool
+HISsync(struct history *h)
+{
+ bool r = false;
+
+ if (his_checknull(h))
+ return false;
+ TMRstart(TMR_HISSYNC);
+ r = (*h->methods->sync)(h->sub);
+ TMRstop(TMR_HISSYNC);
+ return r;
+}
+
+bool
+HISlookup(struct history *h, const char *key, time_t *arrived,
+ time_t *posted, time_t *expires, TOKEN *token)
+{
+ bool r;
+
+ if (his_checknull(h))
+ return false;
+ TMRstart(TMR_HISGREP);
+ r = (*h->methods->lookup)(h->sub, key, arrived, posted, expires, token);
+ TMRstop(TMR_HISGREP);
+ return r;
+}
+
+bool
+HIScheck(struct history *h, const char *key)
+{
+ bool r = false;
+ HASH hash;
+
+ if (his_checknull(h))
+ return false;
+ TMRstart(TMR_HISHAVE);
+ hash = HashMessageID(key);
+ switch (his_cachelookup(h, hash)) {
+ case HIScachehit:
+ h->stats.hitpos++;
+ r = true;
+ break;
+
+ case HIScachemiss:
+ h->stats.hitneg++;
+ r = false;
+ break;
+
+ case HIScachedne:
+ r = (*h->methods->check)(h->sub, key);
+ his_cacheadd(h, hash, r);
+ if (r)
+ h->stats.misses++;
+ else
+ h->stats.dne++;
+ break;
+ }
+ TMRstop(TMR_HISHAVE);
+ return r;
+}
+
+bool
+HISwrite(struct history *h, const char *key, time_t arrived,
+ time_t posted, time_t expires, const TOKEN *token)
+{
+ bool r;
+
+ if (his_checknull(h))
+ return false;
+ TMRstart(TMR_HISWRITE);
+ r = (*h->methods->write)(h->sub, key, arrived, posted, expires, token);
+ if (r == true) {
+ HASH hash;
+
+ /* if we successfully wrote it, add it to the cache */
+ hash = HashMessageID(key);
+ his_cacheadd(h, hash, true);
+ }
+ TMRstop(TMR_HISWRITE);
+
+ return r;
+}
+
+bool
+HISremember(struct history *h, const char *key, time_t arrived)
+{
+ bool r;
+
+ if (his_checknull(h))
+ return false;
+ TMRstart(TMR_HISWRITE);
+ r = (*h->methods->remember)(h->sub, key, arrived);
+ if (r == true) {
+ HASH hash;
+
+ /* if we successfully wrote it, add it to the cache */
+ hash = HashMessageID(key);
+ his_cacheadd(h, hash, true);
+ }
+ TMRstop(TMR_HISWRITE);
+
+ return r;
+}
+
+bool
+HISreplace(struct history *h, const char *key, time_t arrived,
+ time_t posted, time_t expires, const TOKEN *token)
+{
+ bool r;
+
+ if (his_checknull(h))
+ return false;
+ r = (*h->methods->replace)(h->sub, key, arrived, posted, expires, token);
+ if (r == true) {
+ HASH hash;
+
+ /* if we successfully wrote it, add it to the cache */
+ hash = HashMessageID(key);
+ his_cacheadd(h, hash, true);
+ }
+ return r;
+}
+
+bool
+HISwalk(struct history *h, const char *reason, void *cookie,
+ bool (*callback)(void *, time_t, time_t, time_t, const TOKEN *))
+{
+ bool r;
+
+ if (his_checknull(h))
+ return false;
+ r = (*h->methods->walk)(h->sub, reason, cookie, callback);
+ return r;
+}
+
+bool
+HISexpire(struct history *h, const char *path, const char *reason,
+ bool writing, void *cookie, time_t threshold,
+ bool (*exists)(void *, time_t, time_t, time_t, TOKEN *))
+{
+ bool r;
+
+ if (his_checknull(h))
+ return false;
+ r = (*h->methods->expire)(h->sub, path, reason, writing,
+ cookie, threshold, exists);
+ return r;
+}
+
+void
+HISsetcache(struct history *h, size_t size)
+{
+ if (h == NULL)
+ return;
+ if (h->cache) {
+ free(h->cache);
+ h->cache = NULL;
+ }
+ h->cachesize = size / sizeof(struct hiscache);
+ if (h->cachesize != 0)
+ h->cache = xcalloc(h->cachesize, sizeof(struct hiscache));
+ h->stats = nullhist;
+}
+
+
+/*
+** return current history cache stats and zero the counters
+*/
+struct histstats
+HISstats(struct history *h)
+{
+ struct histstats r;
+
+ if (h == NULL)
+ return nullhist;
+ r = h->stats;
+ h->stats = nullhist;
+ return r;
+}
+
+
+/*
+** return current error status to caller
+*/
+const char *
+HISerror(struct history *h)
+{
+ if (h == NULL)
+ return NULL;
+ return h->error;
+}
+
+
+/*
+** control interface to underlying method
+*/
+bool
+HISctl(struct history *h, int selector, void *val)
+{
+ bool r;
+
+ if (his_checknull(h))
+ return false;
+ r = (*h->methods->ctl)(h->sub, selector, val);
+ return r;
+}
+
+
+/*
+** This code doesn't fit well with the generic history API, it really
+** needs migrating to use the new nested timers
+*/
+
+FILE *HISfdlog = NULL; /* filehandle for history logging purpose */
+
+static struct timeval HISstat_start[S_HIS_MAX];
+static struct timeval HISstat_total[S_HIS_MAX];
+static unsigned long HISstat_count[S_HIS_MAX];
+
+void HISlogclose(void) {
+ if (HISfdlog != NULL)
+ Fclose(HISfdlog);
+ HISfdlog = NULL;
+}
+
+void HISlogto(const char *s) {
+ int i;
+
+ HISlogclose();
+ if ((HISfdlog = Fopen(s, "a", INND_HISLOG)) == NULL)
+ syslog(L_FATAL, "cant open %s %m", s);
+ /* initialize our counters */
+ for (i = 0; i < S_HIS_MAX; i++) {
+ HISstat_start[i].tv_sec = 0;
+ HISstat_start[i].tv_usec = 0;
+ HISstat_total[i].tv_sec = 0;
+ HISstat_total[i].tv_usec = 0;
+ HISstat_count[i] = 0;
+ }
+}
+
+void
+his_logger(char *s, int code)
+{
+ struct timeval tv;
+ struct tm *tm;
+
+ if (HISfdlog == NULL) /* do nothing unless HISlogto() has been called */
+ return;
+
+ gettimeofday(&tv, NULL);
+ tm = localtime((const time_t *)&(tv.tv_sec));
+ if (HISstat_start[code].tv_sec != 0) {
+ fprintf(HISfdlog, "%d/%d/%d %02d:%02d:%02d.%06d: [%d] %s (%.6f)\n",
+ tm->tm_year + 1900, tm->tm_mon + 1, tm->tm_mday, tm->tm_hour,
+ tm->tm_min, tm->tm_sec, (int)tv.tv_usec, code, s, (float) tv.tv_sec +
+ (float) tv.tv_usec / 1000000 - (float) HISstat_start[code].tv_sec -
+ (float) HISstat_start[code].tv_usec / 1000000);
+ if (tv.tv_usec < HISstat_start[code].tv_usec) {
+ HISstat_total[code].tv_sec++;
+ HISstat_total[code].tv_usec +=
+ tv.tv_usec - HISstat_start[code].tv_usec + 1000000;
+ }
+ else
+ HISstat_total[code].tv_usec +=
+ tv.tv_usec - HISstat_start[code].tv_usec;
+ HISstat_total[code].tv_sec += tv.tv_sec - HISstat_start[code].tv_sec;
+ HISstat_count[code]++;
+ HISstat_start[code].tv_sec = 0;
+ HISstat_start[code].tv_usec = 0;
+ }
+ else {
+ fprintf(HISfdlog, "%d/%d/%d %02d:%02d:%02d.%06d: [%d] %s\n",
+ tm->tm_year + 1900, tm->tm_mon + 1, tm->tm_mday, tm->tm_hour,
+ tm->tm_min, tm->tm_sec, (int)tv.tv_usec, code, s);
+ HISstat_start[code].tv_sec = tv.tv_sec;
+ HISstat_start[code].tv_usec = tv.tv_usec;
+ }
+}
--- /dev/null
+/* $Id: hisinterface.h 5745 2002-09-08 19:52:12Z rra $
+**
+** Interface to history API modules
+*/
+
+#ifndef HISINTERFACE_H
+#define HISINTERFACE_H
+
+#include "config.h"
+#include <sys/types.h>
+
+struct token;
+struct histopts;
+struct history;
+
+typedef struct hismethod {
+ const char *name;
+ void *(*open)(const char *path, int flags, struct history *);
+ bool (*close)(void *);
+ bool (*sync)(void *);
+ bool (*lookup)(void *, const char *, time_t *, time_t *, time_t *,
+ struct token *);
+ bool (*check)(void *, const char *);
+ bool (*write)(void *, const char *, time_t, time_t, time_t,
+ const struct token *);
+ bool (*replace)(void *, const char *, time_t, time_t, time_t,
+ const struct token *);
+ bool (*expire)(void *, const char *, const char *, bool, void *, time_t,
+ bool (*)(void *, time_t, time_t, time_t,
+ struct token *));
+ bool (*walk)(void *, const char *, void *,
+ bool (*)(void *, time_t, time_t, time_t,
+ const struct token *));
+ bool (*remember)(void *, const char *, time_t);
+ bool (*ctl)(void *, int, void *);
+} HIS_METHOD;
+
+/* subordinate history manager private methods */
+void his_seterror(struct history *, const char *);
+
+enum { S_HIScacheadd, S_HIScachelookup, S_HISsetup, S_HISsync,
+ S_HISlogstats, S_HISclose, S_HISfilesfor, S_HIShavearticle,
+ S_HISwrite, S_HISremember, S_HIS_MAX };
+
+/* fine grained history logging */
+void his_logger(char *s, int code);
+#endif
--- /dev/null
+name = hisv6
+number = 0
+sources = hisv6.c
--- /dev/null
+#ifndef HISV6_PRIVATE_H
+#define HISV6_PRIVATE_H
+
+#include "config.h"
+#include <stdio.h>
+#include <time.h>
+#include <syslog.h>
+#include <sys/stat.h>
+#include "inn/history.h"
+#include "storage.h"
+#include "libinn.h"
+
+/* Used by lots of stuff that parses history file entries. Should be moved
+ into a header specifically for history parsing. */
+#define HISV6_BADCHAR '_'
+#define HISV6_FIELDSEP '\t'
+#define HISV6_NOEXP '-'
+#define HISV6_SUBFIELDSEP '~'
+
+/* maximum length of a history line:
+ 34 - hash
+ 1 - \t
+ 20 - arrived
+ 1 - ~
+ 20 - expires
+ 1 - ~
+ 20 - posted
+ 1 - tab
+ 38 - token
+ 1 - \n */
+#define HISV6_MAXLINE 137
+
+/* minimum length of a history line:
+ 34 - hash
+ 1 - \t
+ 1 - arrived
+ 1 - \n */
+#define HISV6_MINLINE 37
+
+struct hisv6 {
+ char *histpath;
+ FILE *writefp;
+ off_t offset; /* Offset into writefp. */
+ unsigned long nextcheck;
+ struct history *history;
+ time_t statinterval;
+ size_t synccount;
+ size_t dirty;
+ ssize_t npairs;
+ int readfd;
+ int flags;
+ struct stat st;
+};
+
+/* values in the bitmap returned from hisv6_splitline */
+#define HISV6_HAVE_HASH (1<<0)
+#define HISV6_HAVE_ARRIVED (1<<1)
+#define HISV6_HAVE_POSTED (1<<2)
+#define HISV6_HAVE_EXPIRES (1<<3)
+#define HISV6_HAVE_TOKEN (1<<4)
+
+/* structure used to hold the callback and cookie so we don't try
+ * passing too many parameters into the callers callback */
+struct hisv6_walkstate {
+ union {
+ bool (*expire)(void *, time_t, time_t, time_t, TOKEN *);
+ bool (*walk)(void *, time_t, time_t, time_t, const TOKEN *);
+ } cb;
+ void *cookie;
+ bool paused;
+ bool ignore;
+ /* the next two fields are only used during expire... they should
+ * probably be linked off of cookie, but I've been lazy */
+ struct hisv6 *new;
+ time_t threshold;
+};
+
+/* maximum length of the string from hisv6_errloc */
+#define HISV6_MAX_LOCATION 22
+
+#endif
--- /dev/null
+/* $Id: hisv6.c 7339 2005-06-20 03:16:41Z eagle $
+**
+** History v6 implementation against the history API.
+**
+** Copyright (c) 2001, Thus plc
+**
+** Redistribution and use of the source code in source and binary
+** forms, with or without modification, are permitted provided that
+** the following 3 conditions are met:
+**
+** 1. Redistributions of the source code must retain the above
+** copyright notice, this list of conditions and the disclaimer
+** set out below.
+**
+** 2. Redistributions of the source code in binary form must
+** reproduce the above copyright notice, this list of conditions
+** and the disclaimer set out below in the documentation and/or
+** other materials provided with the distribution.
+**
+** 3. Neither the name of the Thus plc nor the names of its
+** contributors may be used to endorse or promote products
+** derived from this software without specific prior written
+** permission from Thus plc.
+**
+** Disclaimer:
+**
+** "THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS 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 THE DIRECTORS
+** 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 "config.h"
+#include "clibrary.h"
+#include <fcntl.h>
+#include <limits.h>
+#include <errno.h>
+#include "hisinterface.h"
+#include "hisv6.h"
+#include "hisv6-private.h"
+#include "dbz.h"
+#include "inn/innconf.h"
+#include "inn/timer.h"
+#include "inn/qio.h"
+#include "inn/sequence.h"
+#include "inndcomm.h"
+
+/*
+** because we can only have one open dbz per process, we keep a
+** pointer to which of the current history structures owns it
+*/
+static struct hisv6 *hisv6_dbzowner;
+
+
+/*
+** set error status to that indicated by s; doesn't copy the string,
+** assumes the caller did that for us
+*/
+static void
+hisv6_seterror(struct hisv6 *h, const char *s)
+{
+ his_seterror(h->history, s);
+}
+
+
+/*
+** format line or offset into a string for error reporting
+*/
+static void
+hisv6_errloc(char *s, size_t line, off_t offset)
+{
+ if (offset != -1) {
+ /* really we want an autoconf test for %ll/%L/%I64, sigh */
+ sprintf(s, "@%.0f", (double)offset);
+ } else {
+ sprintf(s, ":%lu", (unsigned long)line);
+ }
+}
+
+
+/*
+** split a history line into its constituent components; return a
+** bitmap indicating which components we're returning are valid (or
+** would be valid if a NULL pointer is passed for that component) or
+** -1 for error. *error is set to a string which describes the
+** failure.
+*/
+static int
+hisv6_splitline(const char *line, const char **error, HASH *hash,
+ time_t *arrived, time_t *posted, time_t *expires,
+ TOKEN *token)
+{
+ char *p = (char *)line;
+ unsigned long l;
+ int r = 0;
+
+ /* parse the [...] hash field */
+ if (*p != '[') {
+ *error = "`[' missing from history line";
+ return -1;
+ }
+ ++p;
+ if (hash)
+ *hash = TextToHash(p);
+ p += 32;
+ if (*p != ']') {
+ *error = "`]' missing from history line";
+ return -1;
+ }
+ ++p;
+ r |= HISV6_HAVE_HASH;
+ if (*p != HISV6_FIELDSEP) {
+ *error = "field separator missing from history line";
+ return -1;
+ }
+
+ /* parse the arrived field */
+ l = strtoul(p + 1, &p, 10);
+ if (l == ULONG_MAX) {
+ *error = "arrived timestamp out of range";
+ return -1;
+ }
+ r |= HISV6_HAVE_ARRIVED;
+ if (arrived)
+ *arrived = (time_t)l;
+ if (*p != HISV6_SUBFIELDSEP) {
+ /* no expires or posted time */
+ if (posted)
+ *posted = 0;
+ if (expires)
+ *expires = 0;
+ } else {
+ /* parse out the expires field */
+ ++p;
+ if (*p == HISV6_NOEXP) {
+ ++p;
+ if (expires)
+ *expires = 0;
+ } else {
+ l = strtoul(p, &p, 10);
+ if (l == ULONG_MAX) {
+ *error = "expires timestamp out of range";
+ return -1;
+ }
+ r |= HISV6_HAVE_EXPIRES;
+ if (expires)
+ *expires = (time_t)l;
+ }
+ /* parse out the posted field */
+ if (*p != HISV6_SUBFIELDSEP) {
+ /* no posted time */
+ if (posted)
+ *posted = 0;
+ } else {
+ ++p;
+ l = strtoul(p, &p, 10);
+ if (l == ULONG_MAX) {
+ *error = "posted timestamp out of range";
+ return -1;
+ }
+ r |= HISV6_HAVE_POSTED;
+ if (posted)
+ *posted = (time_t)l;
+ }
+ }
+
+ /* parse the token */
+ if (*p == HISV6_FIELDSEP)
+ ++p;
+ else if (*p != '\0') {
+ *error = "field separator missing from history line";
+ return -1;
+ }
+ /* IsToken false would imply a remembered line, or where someone's
+ * used prunehistory */
+ if (IsToken(p)) {
+ r |= HISV6_HAVE_TOKEN;
+ if (token)
+ *token = TextToToken(p);
+ }
+ return r;
+}
+
+
+/*
+** Given the time, now, return the time at which we should next check
+** the history file
+*/
+static unsigned long
+hisv6_nextcheck(struct hisv6 *h, unsigned long now)
+{
+ return now + h->statinterval;
+}
+
+
+/*
+** close any dbz structures associated with h; we also manage the
+** single dbz instance voodoo
+*/
+static bool
+hisv6_dbzclose(struct hisv6 *h)
+{
+ bool r = true;
+
+ if (h == hisv6_dbzowner) {
+ if (!hisv6_sync(h))
+ r = false;
+ if (!dbzclose()) {
+ hisv6_seterror(h, concat("can't dbzclose ",
+ h->histpath, " ",
+ strerror(errno), NULL));
+ r = false;
+ }
+ hisv6_dbzowner = NULL;
+ }
+ return r;
+}
+
+
+/*
+** close an existing history structure, cleaning it to the point
+** where we can reopon without leaking resources
+*/
+static bool
+hisv6_closefiles(struct hisv6 *h)
+{
+ bool r = true;
+
+ if (!hisv6_dbzclose(h))
+ r = false;
+
+ if (h->readfd != -1) {
+ if (close(h->readfd) != 0 && errno != EINTR) {
+ hisv6_seterror(h, concat("can't close history ",
+ h->histpath, " ",
+ strerror(errno),NULL));
+ r = false;
+ }
+ h->readfd = -1;
+ }
+
+ if (h->writefp != NULL) {
+ if (ferror(h->writefp) || fflush(h->writefp) == EOF) {
+ hisv6_seterror(h, concat("error on history ",
+ h->histpath, " ",
+ strerror(errno), NULL));
+ r = false;
+ }
+ if (Fclose(h->writefp) == EOF) {
+ hisv6_seterror(h, concat("can't fclose history ",
+ h->histpath, " ",
+ strerror(errno), NULL));
+ r = false;
+ }
+ h->writefp = NULL;
+ h->offset = 0;
+ }
+
+ h->nextcheck = 0;
+ h->st.st_ino = (ino_t)-1;
+ h->st.st_dev = (dev_t)-1;
+ return r;
+}
+
+
+/*
+** Reopen (or open from fresh) a history structure; assumes the flags
+** & path are all set up, ready to roll. If we don't own the dbz, we
+** suppress the dbz code; this is needed during expiry (since the dbz
+** code doesn't yet understand multiple open contexts... yes its a
+** hack)
+*/
+static bool
+hisv6_reopen(struct hisv6 *h)
+{
+ bool r = false;
+
+ if (h->flags & HIS_RDWR) {
+ const char *mode;
+
+ if (h->flags & HIS_CREAT)
+ mode = "w";
+ else
+ mode = "r+";
+ if ((h->writefp = Fopen(h->histpath, mode, INND_HISTORY)) == NULL) {
+ hisv6_seterror(h, concat("can't fopen history ",
+ h->histpath, " ",
+ strerror(errno), NULL));
+ hisv6_closefiles(h);
+ goto fail;
+ }
+ if (fseeko(h->writefp, 0, SEEK_END) == -1) {
+ hisv6_seterror(h, concat("can't fseek to end of ",
+ h->histpath, " ",
+ strerror(errno), NULL));
+ hisv6_closefiles(h);
+ goto fail;
+ }
+ h->offset = ftello(h->writefp);
+ if (h->offset == -1) {
+ hisv6_seterror(h, concat("can't ftello ", h->histpath, " ",
+ strerror(errno), NULL));
+ hisv6_closefiles(h);
+ goto fail;
+ }
+ close_on_exec(fileno(h->writefp), true);
+ }
+
+ /* Open the history file for reading. */
+ if ((h->readfd = open(h->histpath, O_RDONLY)) < 0) {
+ hisv6_seterror(h, concat("can't open ", h->histpath, " ",
+ strerror(errno), NULL));
+ hisv6_closefiles(h);
+ goto fail;
+ }
+ close_on_exec(h->readfd, true);
+
+ /* if there's no current dbz owner, claim it here */
+ if (hisv6_dbzowner == NULL) {
+ hisv6_dbzowner = h;
+ }
+
+ /* During expiry we need two history structures in place, so we
+ have to select which one gets the dbz file */
+ if (h == hisv6_dbzowner) {
+ dbzoptions opt;
+
+ /* Open the DBZ file. */
+ dbzgetoptions(&opt);
+
+ /* HIS_INCORE usually means we're rebuilding from scratch, so
+ keep the whole lot in core until we flush */
+ if (h->flags & HIS_INCORE) {
+ opt.writethrough = false;
+ opt.pag_incore = INCORE_MEM;
+#ifndef DO_TAGGED_HASH
+ opt.exists_incore = INCORE_MEM;
+#endif
+ } else {
+ opt.writethrough = true;
+#ifdef DO_TAGGED_HASH
+ opt.pag_incore = INCORE_MMAP;
+#else
+ /*opt.pag_incore = INCORE_NO;*/
+ opt.pag_incore = (h->flags & HIS_MMAP) ? INCORE_MMAP : INCORE_NO;
+ opt.exists_incore = (h->flags & HIS_MMAP) ? INCORE_MMAP : INCORE_NO;
+
+# if defined(MMAP_NEEDS_MSYNC) && INND_DBZINCORE == 1
+ /* Systems that have MMAP_NEEDS_MSYNC defined will have their
+ on-disk copies out of sync with the mmap'ed copies most of
+ the time. So if innd is using INCORE_MMAP, then we force
+ everything else to use it, too (unless we're on NFS) */
+ if(!innconf->nfsreader) {
+ opt.pag_incore = INCORE_MMAP;
+ opt.exists_incore = INCORE_MMAP;
+ }
+# endif
+#endif
+ }
+ dbzsetoptions(opt);
+ if (h->flags & HIS_CREAT) {
+ size_t npairs;
+
+ /* must only do this once! */
+ h->flags &= ~HIS_CREAT;
+ npairs = (h->npairs == -1) ? 0 : h->npairs;
+ if (!dbzfresh(h->histpath, dbzsize(npairs))) {
+ hisv6_seterror(h, concat("can't dbzfresh ", h->histpath, " ",
+ strerror(errno), NULL));
+ hisv6_closefiles(h);
+ goto fail;
+ }
+ } else if (!dbzinit(h->histpath)) {
+ hisv6_seterror(h, concat("can't dbzinit ", h->histpath, " ",
+ strerror(errno), NULL));
+ hisv6_closefiles(h);
+ goto fail;
+ }
+ }
+ h->nextcheck = hisv6_nextcheck(h, TMRnow());
+ r = true;
+ fail:
+ return r;
+}
+
+
+/*
+** check if the history file has changed, if so rotate to the new
+** history file. Returns false on failure (which is probably fatal as
+** we'll have closed the files)
+*/
+static bool
+hisv6_checkfiles(struct hisv6 *h)
+{
+ unsigned long t = TMRnow();
+
+ if (h->statinterval == 0)
+ return true;
+
+ if (h->readfd == -1) {
+ /* this can happen if a previous checkfiles() has failed to
+ * reopen the handles, but our caller hasn't realised... */
+ hisv6_closefiles(h);
+ if (!hisv6_reopen(h)) {
+ hisv6_closefiles(h);
+ return false;
+ }
+ }
+ if (seq_lcompare(t, h->nextcheck) == 1) {
+ struct stat st;
+
+ if (stat(h->histpath, &st) == 0 &&
+ (st.st_ino != h->st.st_ino ||
+ st.st_dev != h->st.st_dev)) {
+ /* there's a possible race on the history file here... */
+ hisv6_closefiles(h);
+ if (!hisv6_reopen(h)) {
+ hisv6_closefiles(h);
+ return false;
+ }
+ h->st = st;
+ }
+ h->nextcheck = hisv6_nextcheck(h, t);
+ }
+ return true;
+}
+
+
+/*
+** dispose (and clean up) an existing history structure
+*/
+static bool
+hisv6_dispose(struct hisv6 *h)
+{
+ bool r;
+
+ r = hisv6_closefiles(h);
+ if (h->histpath) {
+ free(h->histpath);
+ h->histpath = NULL;
+ }
+
+ free(h);
+ return r;
+}
+
+
+/*
+** return a newly constructed, but empty, history structure
+*/
+static struct hisv6 *
+hisv6_new(const char *path, int flags, struct history *history)
+{
+ struct hisv6 *h;
+
+ h = xmalloc(sizeof *h);
+ h->histpath = path ? xstrdup(path) : NULL;
+ h->flags = flags;
+ h->writefp = NULL;
+ h->offset = 0;
+ h->history = history;
+ h->readfd = -1;
+ h->nextcheck = 0;
+ h->statinterval = 0;
+ h->npairs = 0;
+ h->dirty = 0;
+ h->synccount = 0;
+ h->st.st_ino = (ino_t)-1;
+ h->st.st_dev = (dev_t)-1;
+ return h;
+}
+
+
+/*
+** open the history database identified by path in mode flags
+*/
+void *
+hisv6_open(const char *path, int flags, struct history *history)
+{
+ struct hisv6 *h;
+
+ his_logger("HISsetup begin", S_HISsetup);
+
+ h = hisv6_new(path, flags, history);
+ if (path) {
+ if (!hisv6_reopen(h)) {
+ hisv6_dispose(h);
+ h = NULL;
+ }
+ }
+ his_logger("HISsetup end", S_HISsetup);
+ return h;
+}
+
+
+/*
+** close and free a history handle
+*/
+bool
+hisv6_close(void *history)
+{
+ struct hisv6 *h = history;
+ bool r;
+
+ his_logger("HISclose begin", S_HISclose);
+ r = hisv6_dispose(h);
+ his_logger("HISclose end", S_HISclose);
+ return r;
+}
+
+
+/*
+** synchronise any outstanding history changes to disk
+*/
+bool
+hisv6_sync(void *history)
+{
+ struct hisv6 *h = history;
+ bool r = true;
+
+ if (h->writefp != NULL) {
+ his_logger("HISsync begin", S_HISsync);
+ if (fflush(h->writefp) == EOF) {
+ hisv6_seterror(h, concat("error on history ",
+ h->histpath, " ",
+ strerror(errno), NULL));
+ r = false;
+ }
+ if (h->dirty && h == hisv6_dbzowner) {
+ if (!dbzsync()) {
+ hisv6_seterror(h, concat("can't dbzsync ", h->histpath,
+ " ", strerror(errno), NULL));
+ r = false;
+ } else {
+ h->dirty = 0;
+ }
+ }
+ his_logger("HISsync end", S_HISsync);
+ }
+ return r;
+}
+
+
+/*
+** fetch the line associated with `hash' in the history database into
+** buf; buf must be at least HISV6_MAXLINE+1 bytes. `poff' is filled
+** with the offset of the line in the history file.
+*/
+static bool
+hisv6_fetchline(struct hisv6 *h, const HASH *hash, char *buf, off_t *poff)
+{
+ off_t offset;
+ bool r;
+
+ if (h != hisv6_dbzowner) {
+ hisv6_seterror(h, concat("dbz not open for this history file ",
+ h->histpath, NULL));
+ return false;
+ }
+ if ((h->flags & (HIS_RDWR | HIS_INCORE)) == (HIS_RDWR | HIS_INCORE)) {
+ /* need to fflush as we may be reading uncommitted data
+ written via writefp */
+ if (fflush(h->writefp) == EOF) {
+ hisv6_seterror(h, concat("error on history ",
+ h->histpath, " ",
+ strerror(errno), NULL));
+ r = false;
+ goto fail;
+ }
+ }
+
+ /* Get the seek value into the history file. */
+ errno = 0;
+ r = dbzfetch(*hash, &offset);
+#ifdef ESTALE
+ /* If your history is on NFS need to deal with stale NFS
+ * handles */
+ if (!r && errno == ESTALE) {
+ hisv6_closefiles(h);
+ if (!hisv6_reopen(h)) {
+ hisv6_closefiles(h);
+ r = false;
+ goto fail;
+ }
+ }
+#endif
+ if (r) {
+ ssize_t n;
+
+ do {
+ n = pread(h->readfd, buf, HISV6_MAXLINE, offset);
+#ifdef ESTALE
+ if (n == -1 && errno == ESTALE) {
+ hisv6_closefiles(h);
+ if (!hisv6_reopen(h)) {
+ hisv6_closefiles(h);
+ r = false;
+ goto fail;
+ }
+ }
+#endif
+ } while (n == -1 && errno == EINTR);
+ if (n >= HISV6_MINLINE) {
+ char *p;
+
+ buf[n] = '\0';
+ p = strchr(buf, '\n');
+ if (!p) {
+ char location[HISV6_MAX_LOCATION];
+
+ hisv6_errloc(location, (size_t)-1, offset);
+ hisv6_seterror(h,
+ concat("can't locate end of line in history ",
+ h->histpath, location,
+ NULL));
+ r = false;
+ } else {
+ *p = '\0';
+ *poff = offset;
+ r = true;
+ }
+ } else {
+ char location[HISV6_MAX_LOCATION];
+
+ hisv6_errloc(location, (size_t)-1, offset);
+ hisv6_seterror(h, concat("line too short in history ",
+ h->histpath, location,
+ NULL));
+ r = false;
+
+ }
+ } else {
+ /* not found */
+ r = false;
+ }
+ fail:
+ return r;
+}
+
+
+/*
+** lookup up the entry `key' in the history database, returning
+** arrived, posted and expires (for those which aren't NULL
+** pointers), and any storage token associated with the entry.
+**
+** If any of arrived, posted or expires aren't available, return zero
+** for that component.
+*/
+bool
+hisv6_lookup(void *history, const char *key, time_t *arrived,
+ time_t *posted, time_t *expires, TOKEN *token)
+{
+ struct hisv6 *h = history;
+ HASH messageid;
+ bool r;
+ off_t offset;
+ char buf[HISV6_MAXLINE + 1];
+
+ his_logger("HISfilesfor begin", S_HISfilesfor);
+ hisv6_checkfiles(h);
+
+ messageid = HashMessageID(key);
+ r = hisv6_fetchline(h, &messageid, buf, &offset);
+ if (r == true) {
+ int status;
+ const char *error;
+
+ status = hisv6_splitline(buf, &error, NULL,
+ arrived, posted, expires, token);
+ if (status < 0) {
+ char location[HISV6_MAX_LOCATION];
+
+ hisv6_errloc(location, (size_t)-1, offset);
+ hisv6_seterror(h, concat(error, " ",
+ h->histpath, location,
+ NULL));
+ r = false;
+ } else {
+ /* if we have a token then we have the article */
+ r = !!(status & HISV6_HAVE_TOKEN);
+ }
+ }
+ his_logger("HISfilesfor end", S_HISfilesfor);
+ return r;
+}
+
+
+/*
+** check `key' has been seen in this history database
+*/
+bool
+hisv6_check(void *history, const char *key)
+{
+ struct hisv6 *h = history;
+ bool r;
+ HASH hash;
+
+ if (h != hisv6_dbzowner) {
+ hisv6_seterror(h, concat("dbz not open for this history file ",
+ h->histpath, NULL));
+ return false;
+ }
+
+ his_logger("HIShavearticle begin", S_HIShavearticle);
+ hisv6_checkfiles(h);
+ hash = HashMessageID(key);
+ r = dbzexists(hash);
+ his_logger("HIShavearticle end", S_HIShavearticle);
+ return r;
+}
+
+
+/*
+** Format a history line. s should hold at least HISV6_MAXLINE + 1
+** characters (to allow for the nul). Returns the length of the data
+** written, 0 if there was some error or if the data was too long to write.
+*/
+static int
+hisv6_formatline(char *s, const HASH *hash, time_t arrived,
+ time_t posted, time_t expires, const TOKEN *token)
+{
+ int i;
+ const char *hashtext = HashToText(*hash);
+
+ if (token == NULL) {
+ i = snprintf(s, HISV6_MAXLINE, "[%s]%c%lu%c%c\n",
+ hashtext, HISV6_FIELDSEP,
+ (unsigned long)arrived, HISV6_SUBFIELDSEP, HISV6_NOEXP);
+ } else {
+ const char *texttok;
+
+ texttok = TokenToText(*token);
+ if (expires <= 0) {
+ i = snprintf(s, HISV6_MAXLINE, "[%s]%c%lu%c%c%c%lu%c%s\n",
+ hashtext, HISV6_FIELDSEP,
+ (unsigned long)arrived, HISV6_SUBFIELDSEP,
+ HISV6_NOEXP, HISV6_SUBFIELDSEP,
+ (unsigned long)posted, HISV6_FIELDSEP,
+ texttok);
+ } else {
+ i = snprintf(s, HISV6_MAXLINE, "[%s]%c%lu%c%lu%c%lu%c%s\n",
+ hashtext, HISV6_FIELDSEP,
+ (unsigned long)arrived, HISV6_SUBFIELDSEP,
+ (unsigned long)expires, HISV6_SUBFIELDSEP,
+ (unsigned long)posted, HISV6_FIELDSEP,
+ texttok);
+ }
+ }
+ if (i < 0 || i >= HISV6_MAXLINE)
+ return 0;
+ return i;
+}
+
+
+/*
+** write the hash and offset to the dbz
+*/
+static bool
+hisv6_writedbz(struct hisv6 *h, const HASH *hash, off_t offset)
+{
+ bool r;
+ char location[HISV6_MAX_LOCATION];
+ const char *error;
+
+ /* store the offset in the database */
+ switch (dbzstore(*hash, offset)) {
+ case DBZSTORE_EXISTS:
+ error = "dbzstore duplicate message-id ";
+ /* not `false' so that we duplicate the pre-existing
+ behaviour */
+ r = true;
+ break;
+
+ case DBZSTORE_ERROR:
+ error = "dbzstore error ";
+ r = false;
+ break;
+
+ default:
+ error = NULL;
+ r = true;
+ break;
+ }
+ if (error) {
+ hisv6_errloc(location, (size_t)-1, offset);
+ hisv6_seterror(h, concat(error, h->histpath,
+ ":[", HashToText(*hash), "]",
+ location, " ", strerror(errno), NULL));
+ }
+ if (r && h->synccount != 0 && ++h->dirty >= h->synccount)
+ r = hisv6_sync(h);
+
+ return r;
+}
+
+
+/*
+** write a history entry, hash, with times arrived, posted and
+** expires, and storage token.
+*/
+static bool
+hisv6_writeline(struct hisv6 *h, const HASH *hash, time_t arrived,
+ time_t posted, time_t expires, const TOKEN *token)
+{
+ bool r;
+ size_t i, length;
+ char hisline[HISV6_MAXLINE + 1];
+ char location[HISV6_MAX_LOCATION];
+
+ if (h != hisv6_dbzowner) {
+ hisv6_seterror(h, concat("dbz not open for this history file ",
+ h->histpath, NULL));
+ return false;
+ }
+
+ if (!(h->flags & HIS_RDWR)) {
+ hisv6_seterror(h, concat("history not open for writing ",
+ h->histpath, NULL));
+ return false;
+ }
+
+ length = hisv6_formatline(hisline, hash, arrived, posted, expires, token);
+ if (length == 0) {
+ hisv6_seterror(h, concat("error formatting history line ",
+ h->histpath, NULL));
+ return false;
+ }
+
+ i = fwrite(hisline, 1, length, h->writefp);
+
+ /* If the write failed, the history line is now an orphan. Attempt to
+ rewind the write pointer to our offset to avoid leaving behind a
+ partial write and desyncing the offset from our file position. */
+ if (i < length ||
+ (!(h->flags & HIS_INCORE) && fflush(h->writefp) == EOF)) {
+ hisv6_errloc(location, (size_t)-1, h->offset);
+ hisv6_seterror(h, concat("can't write history ", h->histpath,
+ location, " ", strerror(errno), NULL));
+ if (fseeko(h->writefp, h->offset, SEEK_SET) == -1)
+ h->offset += i;
+ r = false;
+ goto fail;
+ }
+
+ r = hisv6_writedbz(h, hash, h->offset);
+ h->offset += length; /* increment regardless of error from writedbz */
+ fail:
+ return r;
+}
+
+
+/*
+** write a history entry, key, with times arrived, posted and
+** expires, and storage token.
+*/
+bool
+hisv6_write(void *history, const char *key, time_t arrived,
+ time_t posted, time_t expires, const TOKEN *token)
+{
+ struct hisv6 *h = history;
+ HASH hash;
+ bool r;
+
+ his_logger("HISwrite begin", S_HISwrite);
+ hash = HashMessageID(key);
+ r = hisv6_writeline(h, &hash, arrived, posted, expires, token);
+ his_logger("HISwrite end", S_HISwrite);
+ return r;
+}
+
+
+/*
+** remember a history entry, key, with arrival time arrived.
+*/
+bool
+hisv6_remember(void *history, const char *key, time_t arrived)
+{
+ struct hisv6 *h = history;
+ HASH hash;
+ bool r;
+
+ his_logger("HISwrite begin", S_HISwrite);
+ hash = HashMessageID(key);
+ r = hisv6_writeline(h, &hash, arrived, 0, 0, NULL);
+ his_logger("HISwrite end", S_HISwrite);
+ return r;
+}
+
+
+/*
+** replace an existing history entry, `key', with times arrived,
+** posted and expires, and (optionally) storage token `token'. The
+** new history line must fit in the space allocated for the old one -
+** if it had previously just been HISremember()ed you'll almost
+** certainly lose.
+*/
+bool
+hisv6_replace(void *history, const char *key, time_t arrived,
+ time_t posted, time_t expires, const TOKEN *token)
+{
+ struct hisv6 *h = history;
+ HASH hash;
+ bool r;
+ off_t offset;
+ char old[HISV6_MAXLINE + 1];
+
+ if (!(h->flags & HIS_RDWR)) {
+ hisv6_seterror(h, concat("history not open for writing ",
+ h->histpath, NULL));
+ return false;
+ }
+
+ hash = HashMessageID(key);
+ r = hisv6_fetchline(h, &hash, old, &offset);
+ if (r == true) {
+ char new[HISV6_MAXLINE + 1];
+
+ if (hisv6_formatline(new, &hash, arrived, posted, expires,
+ token) == 0) {
+ hisv6_seterror(h, concat("error formatting history line ",
+ h->histpath, NULL));
+ r = false;
+ } else {
+ size_t oldlen, newlen;
+
+ oldlen = strlen(old);
+ newlen = strlen(new);
+ if (new[newlen - 1] == '\n')
+ newlen--;
+ if (newlen > oldlen) {
+ hisv6_seterror(h, concat("new history line too long ",
+ h->histpath, NULL));
+ r = false;
+ } else {
+ ssize_t n;
+
+ /* space fill any excess in the tail of new */
+ memset(new + newlen, ' ', oldlen - newlen);
+
+ do {
+ n = pwrite(fileno(h->writefp), new, oldlen, offset);
+ } while (n == -1 && errno == EINTR);
+ if (n != oldlen) {
+ char location[HISV6_MAX_LOCATION];
+
+ hisv6_errloc(location, (size_t)-1, offset);
+ hisv6_seterror(h, concat("can't write history ",
+ h->histpath, location, " ",
+ strerror(errno), NULL));
+ r = false;
+ }
+ }
+ }
+ }
+ return r;
+}
+
+
+/*
+** traverse a history database, passing the pieces through a
+** callback; note that we have more parameters in the callback than
+** the public interface, we add the internal history struct and the
+** message hash so we can use those if we need them. If the callback
+** returns false we abort the traversal.
+**/
+static bool
+hisv6_traverse(struct hisv6 *h, struct hisv6_walkstate *cookie,
+ const char *reason,
+ bool (*callback)(struct hisv6 *, void *, const HASH *hash,
+ time_t, time_t, time_t,
+ const TOKEN *))
+{
+ bool r = false;
+ QIOSTATE *qp;
+ void *p;
+ size_t line;
+ char location[HISV6_MAX_LOCATION];
+
+ if ((qp = QIOopen(h->histpath)) == NULL) {
+ hisv6_seterror(h, concat("can't QIOopen history file ",
+ h->histpath, strerror(errno), NULL));
+ return false;
+ }
+
+ line = 1;
+ /* we come back to again after we hit EOF for the first time, when
+ we pause the server & clean up any lines which sneak through in
+ the interim */
+ again:
+ while ((p = QIOread(qp)) != NULL) {
+ time_t arrived, posted, expires;
+ int status;
+ TOKEN token;
+ HASH hash;
+ const char *error;
+
+ status = hisv6_splitline(p, &error, &hash,
+ &arrived, &posted, &expires, &token);
+ if (status > 0) {
+ r = (*callback)(h, cookie, &hash, arrived, posted, expires,
+ (status & HISV6_HAVE_TOKEN) ? &token : NULL);
+ if (r == false)
+ hisv6_seterror(h, concat("callback failed ",
+ h->histpath, NULL));
+ } else {
+ hisv6_errloc(location, line, (off_t)-1);
+ hisv6_seterror(h, concat(error, " ", h->histpath, location,
+ NULL));
+ /* if we're not ignoring errors set the status */
+ if (!cookie->ignore)
+ r = false;
+ }
+ if (r == false)
+ goto fail;
+ ++line;
+ }
+
+ if (p == NULL) {
+ /* read or line-format error? */
+ if (QIOerror(qp) || QIOtoolong(qp)) {
+ hisv6_errloc(location, line, (off_t)-1);
+ if (QIOtoolong(qp)) {
+ hisv6_seterror(h, concat("line too long ",
+ h->histpath, location, NULL));
+ /* if we're not ignoring errors set the status */
+ if (!cookie->ignore)
+ r = false;
+ } else {
+ hisv6_seterror(h, concat("can't read line ",
+ h->histpath, location, " ",
+ strerror(errno), NULL));
+ r = false;
+ }
+ if (r == false)
+ goto fail;
+ }
+
+ /* must have been EOF, pause the server & clean up any
+ * stragglers */
+ if (reason && !cookie->paused) {
+ if (ICCpause(reason) != 0) {
+ hisv6_seterror(h, concat("can't pause server ",
+ h->histpath, strerror(errno), NULL));
+ r = false;
+ goto fail;
+ }
+ cookie->paused = true;
+ goto again;
+ }
+ }
+ fail:
+ QIOclose(qp);
+ return r;
+}
+
+
+/*
+** internal callback used during hisv6_traverse; we just pass on the
+** parameters the user callback expects
+**/
+static bool
+hisv6_traversecb(struct hisv6 *h UNUSED, void *cookie, const HASH *hash UNUSED,
+ time_t arrived, time_t posted, time_t expires,
+ const TOKEN *token)
+{
+ struct hisv6_walkstate *hiscookie = cookie;
+
+ return (*hiscookie->cb.walk)(hiscookie->cookie,
+ arrived, posted, expires,
+ token);
+}
+
+
+/*
+** history API interface to the database traversal routine
+*/
+bool
+hisv6_walk(void *history, const char *reason, void *cookie,
+ bool (*callback)(void *, time_t, time_t, time_t,
+ const TOKEN *))
+{
+ struct hisv6 *h = history;
+ struct hisv6_walkstate hiscookie;
+ bool r;
+
+ /* our internal walk routine passes too many parameters, so add a
+ wrapper */
+ hiscookie.cb.walk = callback;
+ hiscookie.cookie = cookie;
+ hiscookie.new = NULL;
+ hiscookie.paused = false;
+ hiscookie.ignore = false;
+
+ r = hisv6_traverse(h, &hiscookie, reason, hisv6_traversecb);
+
+ return r;
+}
+
+
+/*
+** internal callback used during expire
+**/
+static bool
+hisv6_expirecb(struct hisv6 *h, void *cookie, const HASH *hash,
+ time_t arrived, time_t posted, time_t expires,
+ const TOKEN *token)
+{
+ struct hisv6_walkstate *hiscookie = cookie;
+ bool r = true;
+
+ /* check if we've seen this message id already */
+ if (hiscookie->new && dbzexists(*hash)) {
+ /* continue after duplicates, it serious, but not fatal */
+ hisv6_seterror(h, concat("duplicate message-id [",
+ HashToText(*hash), "] in history ",
+ hiscookie->new->histpath, NULL));
+ } else {
+ struct hisv6_walkstate *hiscookie = cookie;
+ TOKEN ltoken, *t;
+
+ /* if we have a token pass it to the discrimination function */
+ if (token) {
+ bool keep;
+
+ /* make a local copy of the token so the callback can
+ * modify it */
+ ltoken = *token;
+ t = <oken;
+ keep = (*hiscookie->cb.expire)(hiscookie->cookie,
+ arrived, posted, expires,
+ t);
+ /* if the callback returns true, we should keep the
+ token for the time being, else we just remember
+ it */
+ if (keep == false) {
+ t = NULL;
+ posted = expires = 0;
+ }
+ } else {
+ t = NULL;
+ }
+ if (hiscookie->new &&
+ (t != NULL || arrived >= hiscookie->threshold)) {
+ r = hisv6_writeline(hiscookie->new, hash,
+ arrived, posted, expires, t);
+ }
+ }
+ return r;
+}
+
+
+/*
+** unlink files associated with the history structure h
+*/
+static bool
+hisv6_unlink(struct hisv6 *h)
+{
+ bool r = true;
+ char *p;
+
+#ifdef DO_TAGGED_HASH
+ p = concat(h->histpath, ".pag", NULL);
+ r = (unlink(p) == 0) && r;
+ free(p);
+#else
+ p = concat(h->histpath, ".index", NULL);
+ r = (unlink(p) == 0) && r;
+ free(p);
+
+ p = concat(h->histpath, ".hash", NULL);
+ r = (unlink(p) == 0) && r;
+ free(p);
+#endif
+
+ p = concat(h->histpath, ".dir", NULL);
+ r = (unlink(p) == 0) && r;
+ free(p);
+
+ r = (unlink(h->histpath) == 0) && r;
+ return r;
+}
+
+
+/*
+** rename files associated with hold to hnew
+*/
+static bool
+hisv6_rename(struct hisv6 *hold, struct hisv6 *hnew)
+{
+ bool r = true;
+ char *old, *new;
+
+#ifdef DO_TAGGED_HASH
+ old = concat(hold->histpath, ".pag", NULL);
+ new = concat(hnew->histpath, ".pag", NULL);
+ r = (rename(old, new) == 0) && r;
+ free(old);
+ free(new);
+#else
+ old = concat(hold->histpath, ".index", NULL);
+ new = concat(hnew->histpath, ".index", NULL);
+ r = (rename(old, new) == 0) && r;
+ free(old);
+ free(new);
+
+ old = concat(hold->histpath, ".hash", NULL);
+ new = concat(hnew->histpath, ".hash", NULL);
+ r = (rename(old, new) == 0) && r;
+ free(old);
+ free(new);
+#endif
+
+ old = concat(hold->histpath, ".dir", NULL);
+ new = concat(hnew->histpath, ".dir", NULL);
+ r = (rename(old, new) == 0) && r;
+ free(old);
+ free(new);
+
+ r = (rename(hold->histpath, hnew->histpath) == 0) && r;
+ return r;
+}
+
+
+/*
+** expire the history database, history.
+*/
+bool
+hisv6_expire(void *history, const char *path, const char *reason,
+ bool writing, void *cookie, time_t threshold,
+ bool (*exists)(void *, time_t, time_t, time_t, TOKEN *))
+{
+ struct hisv6 *h = history, *hnew = NULL;
+ const char *nhistory = NULL;
+ dbzoptions opt;
+ bool r;
+ struct hisv6_walkstate hiscookie;
+
+ /* this flag is always tested in the fail clause, so initialise it
+ now */
+ hiscookie.paused = false;
+
+ /* during expire we ignore errors whilst reading the history file
+ * so any errors in it get fixed automagically */
+ hiscookie.ignore = true;
+
+ if (writing && (h->flags & HIS_RDWR)) {
+ hisv6_seterror(h, concat("can't expire from read/write history ",
+ h->histpath, NULL));
+ r = false;
+ goto fail;
+ }
+
+ if (writing) {
+ /* form base name for new history file */
+ if (path != NULL) {
+ nhistory = concat(path, ".n", NULL);
+ } else {
+ nhistory = concat(h->histpath, ".n", NULL);
+ }
+
+ hnew = hisv6_new(nhistory, HIS_CREAT | HIS_RDWR | HIS_INCORE,
+ h->history);
+ if (!hisv6_reopen(hnew)) {
+ hisv6_dispose(hnew);
+ hnew = NULL;
+ r = false;
+ goto fail;
+ }
+
+ /* this is icky... we can only have one dbz open at a time; we
+ really want to make dbz take a state structure. For now we'll
+ just close the existing one and create our new one they way we
+ need it */
+ if (!hisv6_dbzclose(h)) {
+ r = false;
+ goto fail;
+ }
+
+ dbzgetoptions(&opt);
+ opt.writethrough = false;
+ opt.pag_incore = INCORE_MEM;
+#ifndef DO_TAGGED_HASH
+ opt.exists_incore = INCORE_MEM;
+#endif
+ dbzsetoptions(opt);
+
+ if (h->npairs == 0) {
+ if (!dbzagain(hnew->histpath, h->histpath)) {
+ hisv6_seterror(h, concat("can't dbzagain ",
+ hnew->histpath, ":", h->histpath,
+ strerror(errno), NULL));
+ r = false;
+ goto fail;
+ }
+ } else {
+ size_t npairs;
+
+ npairs = (h->npairs == -1) ? 0 : h->npairs;
+ if (!dbzfresh(hnew->histpath, dbzsize(npairs))) {
+ hisv6_seterror(h, concat("can't dbzfresh ",
+ hnew->histpath, ":", h->histpath,
+ strerror(errno), NULL));
+ r = false;
+ goto fail;
+ }
+ }
+ hisv6_dbzowner = hnew;
+ }
+
+ /* set up the callback handler */
+ hiscookie.cb.expire = exists;
+ hiscookie.cookie = cookie;
+ hiscookie.new = hnew;
+ hiscookie.threshold = threshold;
+ r = hisv6_traverse(h, &hiscookie, reason, hisv6_expirecb);
+
+ fail:
+ if (writing) {
+ if (hnew && !hisv6_closefiles(hnew)) {
+ /* error will already have been set */
+ r = false;
+ }
+
+ /* reopen will synchronise the dbz stuff for us */
+ if (!hisv6_closefiles(h)) {
+ /* error will already have been set */
+ r = false;
+ }
+
+ if (r) {
+ /* if the new path was explicitly specified don't move the
+ files around, our caller is planning to do it out of
+ band */
+ if (path == NULL) {
+ /* unlink the old files */
+ r = hisv6_unlink(h);
+
+ if (r) {
+ r = hisv6_rename(hnew, h);
+ }
+ }
+ } else if (hnew) {
+ /* something went pear shaped, unlink the new files */
+ hisv6_unlink(hnew);
+ }
+
+ /* re-enable dbz on the old history file */
+ if (!hisv6_reopen(h)) {
+ hisv6_closefiles(h);
+ }
+ }
+
+ if (hnew && !hisv6_dispose(hnew))
+ r = false;
+ if (nhistory && nhistory != path)
+ free((char *)nhistory);
+ if (r == false && hiscookie.paused)
+ ICCgo(reason);
+ return r;
+}
+
+
+/*
+** control interface
+*/
+bool
+hisv6_ctl(void *history, int selector, void *val)
+{
+ struct hisv6 *h = history;
+ bool r = true;
+
+ switch (selector) {
+ case HISCTLG_PATH:
+ *(char **)val = h->histpath;
+ break;
+
+ case HISCTLS_PATH:
+ if (h->histpath) {
+ hisv6_seterror(h, concat("path already set in handle", NULL));
+ r = false;
+ } else {
+ h->histpath = xstrdup((char *)val);
+ if (!hisv6_reopen(h)) {
+ free(h->histpath);
+ h->histpath = NULL;
+ r = false;
+ }
+ }
+ break;
+
+ case HISCTLS_STATINTERVAL:
+ h->statinterval = *(time_t *)val * 1000;
+ break;
+
+ case HISCTLS_SYNCCOUNT:
+ h->synccount = *(size_t *)val;
+ break;
+
+ case HISCTLS_NPAIRS:
+ h->npairs = (ssize_t)*(size_t *)val;
+ break;
+
+ case HISCTLS_IGNOREOLD:
+ if (h->npairs == 0 && *(bool *)val) {
+ h->npairs = -1;
+ } else if (h->npairs == -1 && !*(bool *)val) {
+ h->npairs = 0;
+ }
+ break;
+
+ default:
+ /* deliberately doesn't call hisv6_seterror as we don't want
+ * to spam the error log if someone's passing in stuff which
+ * would be relevant to a different history manager */
+ r = false;
+ break;
+ }
+ return r;
+}
--- /dev/null
+/* $Id: hisv6.h 4959 2001-07-25 12:23:32Z alexk $
+**
+** Internal history API interface exposed to HISxxx
+*/
+
+#ifndef HISV6_H
+#define HISV6_H
+
+struct token;
+struct histopts;
+struct history;
+
+void *hisv6_open(const char *path, int flags, struct history *);
+
+bool hisv6_close(void *);
+
+bool hisv6_sync(void *);
+
+bool hisv6_lookup(void *, const char *key, time_t *arrived,
+ time_t *posted, time_t *expires, struct token *token);
+
+bool hisv6_check(void *, const char *key);
+
+bool hisv6_write(void *, const char *key, time_t arrived,
+ time_t posted, time_t expires, const struct token *token);
+
+bool hisv6_replace(void *, const char *key, time_t arrived,
+ time_t posted, time_t expires, const struct token *token);
+
+bool hisv6_expire(void *, const char *, const char *, bool,
+ void *, time_t threshold,
+ bool (*exists)(void *, time_t, time_t, time_t,
+ struct token *));
+
+bool hisv6_walk(void *, const char *, void *,
+ bool (*)(void *, time_t, time_t, time_t,
+ const struct token *));
+
+const char *hisv6_error(void *);
+
+bool hisv6_remember(void *, const char *key, time_t arrived);
+
+bool hisv6_ctl(void *, int, void *);
+
+#endif
--- /dev/null
+## $Id: Makefile 6326 2003-05-05 21:47:43Z rra $
+##
+## Currently just handles creation of the automatically generated header
+## files. Eventually, rules for installing INN's header files will go
+## here.
+
+include ../Makefile.global
+
+top = ..
+
+ALL = inn/system.h inn/version.h
+
+EXTRA = config.h paths.h
+
+PUBLIC = config.h conffile.h dbz.h inndcomm.h libinn.h nntp.h ov.h \
+ paths.h storage.h
+
+all: $(ALL) $(EXTRA)
+
+clean:
+ rm -f $(ALL)
+
+clobber distclean: clean
+ rm -f $(EXTRA)
+
+depend tags ctags:
+
+profiled: all
+
+$(EXTRA) $(FIXSCRIPT):
+ @echo Run configure before running make. See INSTALL for details.
+ @exit 1
+
+
+## Build rules.
+
+inn/system.h: config.h $(top)/support/mksystem
+ $(top)/support/mksystem $(AWK) config.h > $@
+
+inn/version.h: $(top)/support/mkversion $(top)/Makefile.global
+ $(top)/support/mkversion '$(VERSION)' '$(VERSION_EXTRA)' > $@
+
+
+## Installation rules.
+
+install:
+ for F in $(PUBLIC) ; do \
+ $(CP_RPUB) $$F $D$(PATHINCLUDE)/$$F ; \
+ done
+ $(top)/support/install-sh $(OWNER) -m 0755 -d $D$(PATHINCLUDE)/inn
+ for F in inn/*.h ; do \
+ $(CP_RPUB) $$F $D$(PATHINCLUDE)/$$F ; \
+ done
--- /dev/null
+/* $Id: acconfig.h 6717 2004-05-16 20:48:51Z rra $
+**
+** Here be configuration data used by various InterNetNews programs. This
+** file is used as source for the autoheader script, which from it and
+** configure.in generates a config.h.in file that will be used by autoconf
+** as the repository of all wisdom.
+**
+** Stuff before TOP and after BOTTOM is copied verbatim to config.h.in;
+** the rest of this file is in the desired form for autoconfiscation.
+** Only stuff that autoconf 2.13 couldn't figure out for itself or that
+** needs a larger comment is included here.
+*/
+
+#ifndef CONFIG_H
+#define CONFIG_H 1
+
+/* Portable defines that don't rely on autoconf results come from here. */
+#include "inn/defines.h"
+
+/*
+** GENERAL SETTINGS
+**
+** Look over these settings and make sure they're correct for your site.
+** These values don't come from configure and therefore may need manual
+** editing. The defaults normally should be fine.
+**
+** For boolean #defines, uncomment and change #undef to #define to enable,
+** do the reverse to disable.
+*/
+
+/* A null-terminated list of uwildmat(3) patterns matching illegal
+ distributions. inews and nnrpd will reject posts with a distribution
+ matching one of these patterns. */
+#define BAD_DISTRIBS "*.*", NULL
+
+/* Default timeout period for ctlinnd, overridden by the -t flag. If set to
+ zero, ctlinnd will never time out, but will check every two minutes to
+ see if the server is still running so it won't hang forever on a dead
+ server. */
+#define CTLINND_TIMEOUT 0
+
+/* Reject articles posted more than this many seconds in the future. */
+#define DATE_FUZZ (24L * 60L * 60L)
+
+/* innd will flush the history and active file after this many seconds. */
+#define DEFAULT_TIMEOUT 300
+
+/* Define if inews should put hostnames into the Path header itself. */
+#define DO_INEWS_PATH
+
+/* Define if inews should munge the GECOS entry of the passwd file when
+ attempting to determine a poster's real name. Use this if your GECOS
+ entries have other stuff after trailing commas or before dashes, things
+ in parentheses that aren't part of the name, etc. See frontends/inews.c
+ for the full algorithm. */
+#define DO_MUNGE_GECOS
+
+/* Define if rnews should try to connect to the local host. */
+#define DO_RNEWSLOCALCONNECT
+
+/* Define if rnews should syslog articles rejected as duplicates. */
+/* #undef DO_RNEWS_LOG_DUPS */
+
+/* Define if rnews should look in _PATH_RNEWSPROGS for batch unpackers. */
+#define DO_RNEWSPROGS
+
+/* Define if rnews should save articles rejected by the server. */
+/* #undef DO_RNEWS_SAVE_BAD */
+
+/* Value to pass to dbzincore() inside innd. Under some bizarre low memory
+ circumstance you may want this not to be 1, but normally you always want
+ to load the full history indexes into innd's memory. Has no effect if
+ using tagged hash (which is always in core). */
+#define INND_DBZINCORE 1
+
+/* A null-terminated list of unknown commands that, when seen by innd,
+ shouldn't be logged to syslog. Normally innd logs all unknown commands,
+ but sometimes some are so frequent that it's not worth it. */
+#define INND_QUIET_BADLIST NULL
+
+/* innd will throttle itself after this many I/O errors. The count is reset
+ on a ctlinnd go. (ENOSPC is special and will always cause an immediate
+ throttle.) */
+#define IO_ERROR_COUNT 50
+
+/* Length of listen queue for innd. */
+#define MAXLISTEN 25
+
+/* The standard NNTP port. */
+#define NNTP_PORT 119
+
+/* What to use for a Path tail for local posts. */
+#define PATHMASTER "not-for-mail"
+
+
+/*
+** CONFIGURE RESULTS
+**
+** Things determined automatically by autoconf. Nothing below this point
+** should require manual editing; if anything here is wrong, see if you
+** should be passing a flag to configure to set it correctly for your
+** system.
+**
+** Be aware that success of some tests will cause other tests to be skipped
+** since their results aren't then needed. For example, if you have
+** standard C headers, INN won't bother looking for stdlib.h, and
+** HAVE_STDLIB_H will be false whether you have it or not. This is normal.
+**
+** Fodder for autoheader is provided in sort -df order (alphabetical,
+** case-insensitive, ignoring punctuation) to make it easier to check
+** whether a given entry is in the file.
+*/
+@TOP@
+
+/* Define to a suitable 32-bit type if standard headers don't define. */
+#undef int32_t
+
+/* Define to `long' if <sys/types.h> doesn't define. */
+#undef ptrdiff_t
+
+/* Define to `int' if <signal.h> doesn't define. */
+#undef sig_atomic_t
+
+/* Define to `int' if <sys/socket.h> doesn't define. */
+#undef socklen_t
+
+/* Define to `int' if <sys/types.h> doesn't define. */
+#undef ssize_t
+
+/* Define to a suitable 32-bit type if standard headers don't define. */
+#undef uint32_t
+
+@BOTTOM@
+
+
+/*
+** BUFFER SIZES AND DATA LENGTHS
+**
+** You shouldn't need to change any of the following, and changing some of
+** them may cause other things to break. Some standard buffer sizes and
+** lengths of data types for various different things.
+*/
+
+/* The data type to use for article numbers. This probably can't be
+ increased without a lot of work due to assumptions about the active file
+ format, etc. */
+typedef unsigned long ARTNUM;
+
+/* Input buffers start at START_BUFF_SIZE. While reading input, if we have
+ less than LOW_WATER bytes left free in the buffer, use the current
+ buffersize as input to GROW_AMOUNT to determine how much to realloc.
+ Growth must be at least NNTP_STRLEN bytes! The default settings provide
+ aggressive, exponential buffer growth. */
+#define START_BUFF_SIZE (4 * 1024)
+#define LOW_WATER (1 * 1024)
+#define GROW_AMOUNT(x) ((x) < 128 * 1024 ? (x) : 128 * 1024)
+
+/* The size of a large buffer. Free dynamically allocated buffers larger
+ than this when we're done with them. */
+#define BIG_BUFFER (2 * START_BUFF_SIZE)
+
+/* The maximum length of a single header, used as a good guess at a buffer
+ size for some header parsing code. This is currently also used by innd
+ to determine whether to reject a message for an excessively long header;
+ this behavior should be fixed. */
+#define MAXHEADERSIZE 1024
+
+/* Default buffer size for outgoing feeds from innd. */
+#define SITE_BUFFER_SIZE (16 * 1024)
+
+/* The size of a small buffer. */
+#define SMBUF 256
+
+/* Maximum size of a pathname in the spool directory. */
+#define SPOOLNAMEBUFF 512
+
+
+/*
+** LEGACY
+**
+** Everything below this point is here so that parts of INN that haven't
+** been tweaked to use more standard constructs don't break. Don't count
+** on any of this staying in this file. If you have a chance, consider
+** following the comments before each item and fixing it.
+*/
+
+/* Used to send commands to exploders. Should be moved into a more specific
+ header file; used by innd/site.c and backends/buffchan.c. */
+#define EXP_CONTROL '!'
+
+/* Only used by innd and cvtbatch, should be moved to a more specific header
+ file. */
+#define FEED_BYTESIZE 'b'
+#define FEED_FULLNAME 'f'
+#define FEED_HASH 'h'
+#define FEED_HDR_DISTRIB 'D'
+#define FEED_HDR_NEWSGROUP 'N'
+#define FEED_MESSAGEID 'm'
+#define FEED_FNLNAMES '*'
+#define FEED_HEADERS 'H'
+#define FEED_NAME 'n'
+#define FEED_STOREDGROUP 'G'
+#define FEED_NEWSGROUP 'g'
+#define FEED_OVERVIEW 'O'
+#define FEED_PATH 'P'
+#define FEED_REPLIC 'R'
+#define FEED_SITE 's'
+#define FEED_TIMEEXPIRED 'e'
+#define FEED_TIMERECEIVED 't'
+#define FEED_TIMEPOSTED 'p'
+
+/* Maximum number of flags for a feed in newsfeeds. Only used in innd,
+ should be moved there (or made dynamic). */
+#define FEED_MAXFLAGS 20
+
+/* Maximum length of argv vectors used in innd/site.c. This should be moved
+ out of here into that file, or even better hard-coded rather than
+ defined; this value isn't affected by user data and the right value can
+ be determined by looking at the code and seeing how big of an argv it
+ will attempt to construct. */
+#define MAX_BUILTIN_ARGV 20
+
+/* active file flags. Should be moved to a more specific header file. */
+#define NF_FLAG_ALIAS '='
+#define NF_FLAG_EXCLUDED 'j'
+#define NF_FLAG_MODERATED 'm'
+#define NF_FLAG_OK 'y'
+#define NF_FLAG_NOLOCAL 'n'
+#define NF_FLAG_IGNORE 'x'
+
+/* Used for parsing the Newsgroups header. Should be rolled into a library
+ for parsing headers, combining all the code that's currently scattered
+ all over INN for doing that. */
+#define NG_SEPARATOR ","
+#define NG_ISSEP(c) ((c) == ',')
+
+/* There's no reason to make all of these #defines except possibly for
+ L_CC_CMD and even that's a stretch. Since we're logging to our own
+ distinguished log facility, provided that we spread things out between a
+ reasonable variety of log levels, the sysadmin shouldn't have to change
+ any of this. (Some of this is arguably wrong; L_NOTICE should be
+ LOG_NOTICE, for example.) */
+
+/* Flags to use in opening the logs; some programs add LOG_PID. */
+#define L_OPENLOG_FLAGS (LOG_CONS | LOG_NDELAY)
+
+/* Fatal error, program is about to exit. */
+#define L_FATAL LOG_CRIT
+
+/* Log an error that might mean one or more articles get lost. */
+#define L_ERROR LOG_ERR
+
+/* Informational notice, usually not worth caring about. */
+#define L_NOTICE LOG_WARNING
+
+/* A protocol trace. */
+#define L_TRACE LOG_DEBUG
+
+/* All incoming control commands (ctlinnd, etc). */
+#define L_CC_CMD LOG_INFO
+
+#endif /* !CONFIG_H */
--- /dev/null
+/* $Id: clibrary.h 6704 2004-05-16 19:46:20Z rra $
+**
+** Here be declarations of routines and variables in the C library.
+** Including this file is the equivalent of including all of the following
+** headers, portably:
+**
+** #include <sys/types.h>
+** #include <stdarg.h>
+** #include <stdio.h>
+** #include <stdlib.h>
+** #include <stddef.h>
+** #include <stdint.h>
+** #include <string.h>
+** #include <unistd.h>
+**
+** Missing functions are provided via #define or prototyped if we'll be
+** adding them to INN's library. If the system doesn't define a SUN_LEN
+** macro, one will be provided. Also provides some standard #defines.
+*/
+
+#ifndef CLIBRARY_H
+#define CLIBRARY_H 1
+
+/* Make sure we have our configuration information. */
+#include "config.h"
+
+/* Assume stdarg is available; don't bother with varargs support any more.
+ We need this to be able to declare vsnprintf. */
+#include <stdarg.h>
+
+/* This is the same method used by autoconf as of 2000-07-29 for including
+ the basic system headers with the addition of handling of strchr,
+ strrchr, and memcpy. Note that we don't attempt to declare any of the
+ functions; the number of systems left without ANSI-compatible function
+ prototypes isn't high enough to be worth the trouble. */
+#include <stdio.h>
+#include <sys/types.h>
+#if STDC_HEADERS
+# include <stdlib.h>
+# include <stddef.h>
+#else
+# if HAVE_STDLIB_H
+# include <stdlib.h>
+# endif
+# if !HAVE_STRCHR
+# define strchr index
+# define strrchr rindex
+# endif
+# if !HAVE_MEMCPY
+# define memcpy(d, s, n) bcopy((s), (d), (n))
+# endif
+#endif
+#if HAVE_STRING_H
+# if !STDC_HEADERS && HAVE_MEMORY_H
+# include <memory.h>
+# endif
+# include <string.h>
+#else
+# if HAVE_STRINGS_H
+# include <strings.h>
+# endif
+#endif
+
+#if HAVE_INTTYPES_H
+# include <inttypes.h>
+#endif
+#if HAVE_STDINT_H
+# include <stdint.h>
+#endif
+#if HAVE_UNISTD_H
+# include <unistd.h>
+#endif
+
+/* SCO OpenServer gets int32_t from here. */
+#if HAVE_SYS_BITYPES_H
+# include <sys/bitypes.h>
+#endif
+
+BEGIN_DECLS
+
+/* Provide prototypes for functions not declared in system headers. Use the
+ NEED_DECLARATION macros for those functions that may be prototyped but
+ implemented incorrectly. */
+#if !HAVE_FSEEKO
+extern int fseeko(FILE *, off_t, int);
+#endif
+#if !HAVE_FTELLO
+extern off_t ftello(FILE *);
+#endif
+#if !HAVE_HSTRERROR
+extern const char * hstrerror(int);
+#endif
+#if !HAVE_MKSTEMP
+extern int mkstemp(char *);
+#endif
+#if !HAVE_PREAD
+extern ssize_t pread(int, void *, size_t, off_t);
+#endif
+#if !HAVE_PWRITE
+extern ssize_t pwrite(int, const void *, size_t, off_t);
+#endif
+#if !HAVE_SETENV
+extern int setenv(const char *, const char *, int);
+#endif
+#if !HAVE_SETEUID
+extern int seteuid(uid_t);
+#endif
+#if NEED_DECLARATION_SNPRINTF
+extern int snprintf(char *, size_t, const char *, ...)
+ __attribute__((__format__(printf, 3, 4)));
+#endif
+#if !HAVE_STRERROR
+extern const char * strerror(int);
+#endif
+#if !HAVE_STRLCAT
+extern size_t strlcat(char *, const char *, size_t);
+#endif
+#if !HAVE_STRLCPY
+extern size_t strlcpy(char *, const char *, size_t);
+#endif
+#if NEED_DECLARATION_VSNPRINTF
+extern int vsnprintf(char *, size_t, const char *, va_list);
+#endif
+
+END_DECLS
+
+/* "Good enough" replacements for standard functions. */
+#if !HAVE_ATEXIT
+# define atexit(arg) on_exit((arg), 0)
+#endif
+#if !HAVE_STRTOUL
+# define strtoul(a, b, c) (unsigned long) strtol((a), (b), (c))
+#endif
+
+/* This almost certainly isn't necessary, but it's not hurting anything.
+ gcc assumes that if SEEK_SET isn't defined none of the rest are either,
+ so we certainly can as well. */
+#ifndef SEEK_SET
+# define SEEK_SET 0
+# define SEEK_CUR 1
+# define SEEK_END 2
+#endif
+
+/* POSIX requires that these be defined in <unistd.h>. If one of them has
+ been defined, all the rest almost certainly have. */
+#ifndef STDIN_FILENO
+# define STDIN_FILENO 0
+# define STDOUT_FILENO 1
+# define STDERR_FILENO 2
+#endif
+
+/* On some systems, the macros defined by <ctype.h> are only vaild on ASCII
+ characters (those characters that isascii() says are ASCII). This comes
+ into play when applying <ctype.h> macros to eight-bit data. autoconf
+ checks for this with as part of AC_HEADER_STDC, so if autoconf doesn't
+ think our headers are standard, check isascii() first. */
+#if STDC_HEADERS
+# define CTYPE(isXXXXX, c) (isXXXXX((unsigned char)(c)))
+#else
+# define CTYPE(isXXXXX, c) \
+ (isascii((unsigned char)(c)) && isXXXXX((unsigned char)(c)))
+#endif
+
+/* POSIX.1g requires <sys/un.h> to define a SUN_LEN macro for determining
+ the real length of a struct sockaddr_un, but it's not available
+ everywhere yet. If autoconf couldn't find it, define our own. This
+ definition is from 4.4BSD by way of Stevens, Unix Network Programming
+ (2nd edition), vol. 1, pg. 917. */
+#if !HAVE_SUN_LEN
+# define SUN_LEN(sun) \
+ (sizeof(*(sun)) - sizeof((sun)->sun_path) + strlen((sun)->sun_path))
+#endif
+
+/* Used to name the elements of the array passed to pipe(). */
+#define PIPE_READ 0
+#define PIPE_WRITE 1
+
+/* Used for iterating through arrays. ARRAY_SIZE returns the number of
+ elements in the array (useful for a < upper bound in a for loop) and
+ ARRAY_END returns a pointer to the element past the end (ISO C99 makes it
+ legal to refer to such a pointer as long as it's never dereferenced). */
+#define ARRAY_SIZE(array) (sizeof(array) / sizeof((array)[0]))
+#define ARRAY_END(array) (&(array)[ARRAY_SIZE(array)])
+
+
+#endif /* !CLIBRARY_H */
--- /dev/null
+/* $Revision: 5086 $
+**
+** Data structures, functions and cetera used for config file parsing.
+*/
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+typedef struct {
+ FILE *f;
+ char *buf;
+ unsigned int sbuf;
+ int lineno;
+ int array_len;
+ char **array;
+ char *filename;
+} CONFFILE;
+
+typedef struct {
+ int type;
+#define CONFstring -1
+ char *name;
+} CONFTOKEN;
+
+extern char CONFerror[];
+
+extern CONFFILE *CONFfopen(char*);
+extern void CONFfclose(CONFFILE*);
+
+extern CONFTOKEN *CONFgettoken(CONFTOKEN*, CONFFILE*);
+
+#ifdef __cplusplus
+}
+#endif
--- /dev/null
+/* include/config.h.in. Generated automatically from configure.in by autoheader 2.13. */
+/* $Id: config.h.in 7565 2006-08-28 02:42:54Z eagle $
+**
+** Here be configuration data used by various InterNetNews programs. This
+** file is used as source for the autoheader script, which from it and
+** configure.in generates a config.h.in file that will be used by autoconf
+** as the repository of all wisdom.
+**
+** Stuff before TOP and after BOTTOM is copied verbatim to config.h.in;
+** the rest of this file is in the desired form for autoconfiscation.
+** Only stuff that autoconf 2.13 couldn't figure out for itself or that
+** needs a larger comment is included here.
+*/
+
+#ifndef CONFIG_H
+#define CONFIG_H 1
+
+/* Portable defines that don't rely on autoconf results come from here. */
+#include "inn/defines.h"
+
+/*
+** GENERAL SETTINGS
+**
+** Look over these settings and make sure they're correct for your site.
+** These values don't come from configure and therefore may need manual
+** editing. The defaults normally should be fine.
+**
+** For boolean #defines, uncomment and change #undef to #define to enable,
+** do the reverse to disable.
+*/
+
+/* A null-terminated list of uwildmat(3) patterns matching illegal
+ distributions. inews and nnrpd will reject posts with a distribution
+ matching one of these patterns. */
+#define BAD_DISTRIBS "*.*", NULL
+
+/* Default timeout period for ctlinnd, overridden by the -t flag. If set to
+ zero, ctlinnd will never time out, but will check every two minutes to
+ see if the server is still running so it won't hang forever on a dead
+ server. */
+#define CTLINND_TIMEOUT 0
+
+/* Reject articles posted more than this many seconds in the future. */
+#define DATE_FUZZ (24L * 60L * 60L)
+
+/* innd will flush the history and active file after this many seconds. */
+#define DEFAULT_TIMEOUT 300
+
+/* Define if inews should put hostnames into the Path header itself. */
+#define DO_INEWS_PATH
+
+/* Define if inews should munge the GECOS entry of the passwd file when
+ attempting to determine a poster's real name. Use this if your GECOS
+ entries have other stuff after trailing commas or before dashes, things
+ in parentheses that aren't part of the name, etc. See frontends/inews.c
+ for the full algorithm. */
+#define DO_MUNGE_GECOS
+
+/* Define if rnews should try to connect to the local host. */
+#define DO_RNEWSLOCALCONNECT
+
+/* Define if rnews should syslog articles rejected as duplicates. */
+/* #undef DO_RNEWS_LOG_DUPS */
+
+/* Define if rnews should look in _PATH_RNEWSPROGS for batch unpackers. */
+#define DO_RNEWSPROGS
+
+/* Define if rnews should save articles rejected by the server. */
+/* #undef DO_RNEWS_SAVE_BAD */
+
+/* Value to pass to dbzincore() inside innd. Under some bizarre low memory
+ circumstance you may want this not to be 1, but normally you always want
+ to load the full history indexes into innd's memory. Has no effect if
+ using tagged hash (which is always in core). */
+#define INND_DBZINCORE 1
+
+/* A null-terminated list of unknown commands that, when seen by innd,
+ shouldn't be logged to syslog. Normally innd logs all unknown commands,
+ but sometimes some are so frequent that it's not worth it. */
+#define INND_QUIET_BADLIST NULL
+
+/* innd will throttle itself after this many I/O errors. The count is reset
+ on a ctlinnd go. (ENOSPC is special and will always cause an immediate
+ throttle.) */
+#define IO_ERROR_COUNT 50
+
+/* Length of listen queue for innd. */
+#define MAXLISTEN 25
+
+/* The standard NNTP port. */
+#define NNTP_PORT 119
+
+/* What to use for a Path tail for local posts. */
+#define PATHMASTER "not-for-mail"
+
+
+/*
+** CONFIGURE RESULTS
+**
+** Things determined automatically by autoconf. Nothing below this point
+** should require manual editing; if anything here is wrong, see if you
+** should be passing a flag to configure to set it correctly for your
+** system.
+**
+** Be aware that success of some tests will cause other tests to be skipped
+** since their results aren't then needed. For example, if you have
+** standard C headers, INN won't bother looking for stdlib.h, and
+** HAVE_STDLIB_H will be false whether you have it or not. This is normal.
+**
+** Fodder for autoheader is provided in sort -df order (alphabetical,
+** case-insensitive, ignoring punctuation) to make it easier to check
+** whether a given entry is in the file.
+*/
+
+/* Define if on AIX 3.
+ System headers sometimes define this.
+ We just want to avoid a redefinition error message. */
+#ifndef _ALL_SOURCE
+#undef _ALL_SOURCE
+#endif
+
+/* Define to empty if the keyword does not work. */
+#undef const
+
+/* Define to `int' if <sys/types.h> doesn't define. */
+#undef gid_t
+
+/* Define if you have a working `mmap' system call. */
+#undef HAVE_MMAP
+
+/* Define if your struct stat has st_blksize. */
+#undef HAVE_ST_BLKSIZE
+
+/* Define if you have <sys/wait.h> that is POSIX.1 compatible. */
+#undef HAVE_SYS_WAIT_H
+
+/* Define to `long' if <sys/types.h> doesn't define. */
+#undef off_t
+
+/* Define to `int' if <sys/types.h> doesn't define. */
+#undef pid_t
+
+/* Define if you need to in order for stat and other things to work. */
+#undef _POSIX_SOURCE
+
+/* Define as the return type of signal handlers (int or void). */
+#undef RETSIGTYPE
+
+/* Define to `unsigned' if <sys/types.h> doesn't define. */
+#undef size_t
+
+/* Define if you have the ANSI C header files. */
+#undef STDC_HEADERS
+
+/* Define if you can safely include both <sys/time.h> and <time.h>. */
+#undef TIME_WITH_SYS_TIME
+
+/* Define if your <sys/time.h> declares struct tm. */
+#undef TM_IN_SYS_TIME
+
+/* Define to `int' if <sys/types.h> doesn't define. */
+#undef uid_t
+
+/* Define if your processor stores words with the most significant
+ byte first (like Motorola and SPARC, unlike Intel and VAX). */
+#undef WORDS_BIGENDIAN
+
+/* Define to a suitable 32-bit type if standard headers don't define. */
+#undef int32_t
+
+/* Define to `long' if <sys/types.h> doesn't define. */
+#undef ptrdiff_t
+
+/* Define to `int' if <signal.h> doesn't define. */
+#undef sig_atomic_t
+
+/* Define to `int' if <sys/socket.h> doesn't define. */
+#undef socklen_t
+
+/* Define to `int' if <sys/types.h> doesn't define. */
+#undef ssize_t
+
+/* Define to a suitable 32-bit type if standard headers don't define. */
+#undef uint32_t
+
+/* Define if you have the atexit function. */
+#undef HAVE_ATEXIT
+
+/* Define if you have the fseeko function. */
+#undef HAVE_FSEEKO
+
+/* Define if you have the ftello function. */
+#undef HAVE_FTELLO
+
+/* Define if you have the getdtablesize function. */
+#undef HAVE_GETDTABLESIZE
+
+/* Define if you have the getloadavg function. */
+#undef HAVE_GETLOADAVG
+
+/* Define if you have the getpagesize function. */
+#undef HAVE_GETPAGESIZE
+
+/* Define if you have the getrlimit function. */
+#undef HAVE_GETRLIMIT
+
+/* Define if you have the getrusage function. */
+#undef HAVE_GETRUSAGE
+
+/* Define if you have the getspnam function. */
+#undef HAVE_GETSPNAM
+
+/* Define if you have the hstrerror function. */
+#undef HAVE_HSTRERROR
+
+/* Define if you have the inet_aton function. */
+#undef HAVE_INET_ATON
+
+/* Define if you have the krb5_init_ets function. */
+#undef HAVE_KRB5_INIT_ETS
+
+/* Define if you have the madvise function. */
+#undef HAVE_MADVISE
+
+/* Define if you have the memcpy function. */
+#undef HAVE_MEMCPY
+
+/* Define if you have the mkstemp function. */
+#undef HAVE_MKSTEMP
+
+/* Define if you have the pread function. */
+#undef HAVE_PREAD
+
+/* Define if you have the pstat function. */
+#undef HAVE_PSTAT
+
+/* Define if you have the pwrite function. */
+#undef HAVE_PWRITE
+
+/* Define if you have the setbuffer function. */
+#undef HAVE_SETBUFFER
+
+/* Define if you have the setenv function. */
+#undef HAVE_SETENV
+
+/* Define if you have the seteuid function. */
+#undef HAVE_SETEUID
+
+/* Define if you have the setgroups function. */
+#undef HAVE_SETGROUPS
+
+/* Define if you have the setrlimit function. */
+#undef HAVE_SETRLIMIT
+
+/* Define if you have the setsid function. */
+#undef HAVE_SETSID
+
+/* Define if you have the sigaction function. */
+#undef HAVE_SIGACTION
+
+/* Define if you have the socketpair function. */
+#undef HAVE_SOCKETPAIR
+
+/* Define if you have the statfs function. */
+#undef HAVE_STATFS
+
+/* Define if you have the statvfs function. */
+#undef HAVE_STATVFS
+
+/* Define if you have the strcasecmp function. */
+#undef HAVE_STRCASECMP
+
+/* Define if you have the strchr function. */
+#undef HAVE_STRCHR
+
+/* Define if you have the strerror function. */
+#undef HAVE_STRERROR
+
+/* Define if you have the strlcat function. */
+#undef HAVE_STRLCAT
+
+/* Define if you have the strlcpy function. */
+#undef HAVE_STRLCPY
+
+/* Define if you have the strncasecmp function. */
+#undef HAVE_STRNCASECMP
+
+/* Define if you have the strspn function. */
+#undef HAVE_STRSPN
+
+/* Define if you have the strtoul function. */
+#undef HAVE_STRTOUL
+
+/* Define if you have the symlink function. */
+#undef HAVE_SYMLINK
+
+/* Define if you have the sysconf function. */
+#undef HAVE_SYSCONF
+
+/* Define if you have the ulimit function. */
+#undef HAVE_ULIMIT
+
+/* Define if you have the <crypt.h> header file. */
+#undef HAVE_CRYPT_H
+
+/* Define if you have the <db1/ndbm.h> header file. */
+#undef HAVE_DB1_NDBM_H
+
+/* Define if you have the <dirent.h> header file. */
+#undef HAVE_DIRENT_H
+
+/* Define if you have the <dlfcn.h> header file. */
+#undef HAVE_DLFCN_H
+
+/* Define if you have the <et/com_err.h> header file. */
+#undef HAVE_ET_COM_ERR_H
+
+/* Define if you have the <gdbm-ndbm.h> header file. */
+#undef HAVE_GDBM_NDBM_H
+
+/* Define if you have the <inttypes.h> header file. */
+#undef HAVE_INTTYPES_H
+
+/* Define if you have the <limits.h> header file. */
+#undef HAVE_LIMITS_H
+
+/* Define if you have the <memory.h> header file. */
+#undef HAVE_MEMORY_H
+
+/* Define if you have the <ndbm.h> header file. */
+#undef HAVE_NDBM_H
+
+/* Define if you have the <ndir.h> header file. */
+#undef HAVE_NDIR_H
+
+/* Define if you have the <pam/pam_appl.h> header file. */
+#undef HAVE_PAM_PAM_APPL_H
+
+/* Define if you have the <stdbool.h> header file. */
+#undef HAVE_STDBOOL_H
+
+/* Define if you have the <stddef.h> header file. */
+#undef HAVE_STDDEF_H
+
+/* Define if you have the <stdint.h> header file. */
+#undef HAVE_STDINT_H
+
+/* Define if you have the <stdlib.h> header file. */
+#undef HAVE_STDLIB_H
+
+/* Define if you have the <string.h> header file. */
+#undef HAVE_STRING_H
+
+/* Define if you have the <strings.h> header file. */
+#undef HAVE_STRINGS_H
+
+/* Define if you have the <sys/bitypes.h> header file. */
+#undef HAVE_SYS_BITYPES_H
+
+/* Define if you have the <sys/dir.h> header file. */
+#undef HAVE_SYS_DIR_H
+
+/* Define if you have the <sys/filio.h> header file. */
+#undef HAVE_SYS_FILIO_H
+
+/* Define if you have the <sys/loadavg.h> header file. */
+#undef HAVE_SYS_LOADAVG_H
+
+/* Define if you have the <sys/mount.h> header file. */
+#undef HAVE_SYS_MOUNT_H
+
+/* Define if you have the <sys/ndir.h> header file. */
+#undef HAVE_SYS_NDIR_H
+
+/* Define if you have the <sys/param.h> header file. */
+#undef HAVE_SYS_PARAM_H
+
+/* Define if you have the <sys/select.h> header file. */
+#undef HAVE_SYS_SELECT_H
+
+/* Define if you have the <sys/sysinfo.h> header file. */
+#undef HAVE_SYS_SYSINFO_H
+
+/* Define if you have the <sys/time.h> header file. */
+#undef HAVE_SYS_TIME_H
+
+/* Define if you have the <sys/vfs.h> header file. */
+#undef HAVE_SYS_VFS_H
+
+/* Define if you have the <unistd.h> header file. */
+#undef HAVE_UNISTD_H
+
+/* The user that INN should run as. */
+#undef NEWSUSER
+
+/* The group that INN should run as. */
+#undef NEWSGRP
+
+/* The user who gets all INN-related e-mail. */
+#undef NEWSMASTER
+
+/* Mode that incoming articles are created with. */
+#undef ARTFILE_MODE
+
+/* Mode that batch files are created with. */
+#undef BATCHFILE_MODE
+
+/* Mode that directories are created with. */
+#undef GROUPDIR_MODE
+
+/* The umask used by all INN programs. */
+#undef NEWSUMASK
+
+/* Additional valid low-numbered port for inndstart. */
+#undef INND_PORT
+
+/* Define to enable IPv6 support. */
+#undef HAVE_INET6
+
+/* Maximum number of sockets that innd can listen on. */
+#undef MAX_SOCKETS
+
+/* Define to use tagged hash for the history file. */
+#undef DO_TAGGED_HASH
+
+/* Define to 1 to compile in support for keyword generation code. */
+#undef DO_KEYWORDS
+
+/* Define to compile in Perl script support. */
+#undef DO_PERL
+
+/* Define to compile in Python module support. */
+#undef DO_PYTHON
+
+/* Define if you have the setproctitle function. */
+#undef HAVE_SETPROCTITLE
+
+/* Define if you have PAM. */
+#undef HAVE_PAM
+
+/* Define if BerkeleyDB is available. */
+#undef USE_BERKELEY_DB
+
+/* Define if the BerkeleyDB dbm compatibility layer is available. */
+#undef HAVE_BDB_DBM
+
+/* Define if you have a dbm library. */
+#undef HAVE_DBM
+
+/* Define if OpenSSL is available. */
+#undef HAVE_SSL
+
+/* Define if SASL is available. */
+#undef HAVE_SASL
+
+/* Define if SASL is available. */
+#undef HAVE_SASL
+
+/* Some versions of glibc need this defined for pread/pwrite. */
+#undef _GNU_SOURCE
+
+/* Define if <netdb.h> does not declare h_errno. */
+#undef NEED_HERRNO_DECLARATION
+
+/* Define if inet_aton isn't declared in the system headers. */
+#undef NEED_DECLARATION_INET_ATON
+
+/* Define if inet_ntoa isn't declared in the system headers. */
+#undef NEED_DECLARATION_INET_NTOA
+
+/* Define if snprintf isn't declared in the system headers. */
+#undef NEED_DECLARATION_SNPRINTF
+
+/* Define if vsnprintf isn't declared in the system headers. */
+#undef NEED_DECLARATION_VSNPRINTF
+
+/* Define if the compiler supports C99 variadic macros. */
+#undef HAVE_C99_VAMACROS
+
+/* Define if the compiler supports GNU-style variadic macros. */
+#undef HAVE_GNU_VAMACROS
+
+/* Define if the compiler supports long long int. */
+#undef HAVE_LONG_LONG
+
+/* Define to the max vectors in an iovec. */
+#undef IOV_MAX
+
+/* Define if <sys/un.h> defines the SUN_LEN macro. */
+#undef HAVE_SUN_LEN
+
+/* Define if your struct tm has a tm_gmtoff member. */
+#undef HAVE_TM_GMTOFF
+
+/* Define if your struct tm has a tm_zone member. */
+#undef HAVE_TM_ZONE
+
+/* Define if timezone is an external variable in <time.h>. */
+#undef HAVE_VAR_TIMEZONE
+
+/* Define if tzname is an external variable in <time.h>. */
+#undef HAVE_VAR_TZNAME
+
+/* Define if your system has a working inet_ntoa function. */
+#undef HAVE_INET_NTOA
+
+/* Define if your system has a sa_len field in struct sockaddr */
+#undef HAVE_SOCKADDR_LEN
+
+/* Define if your system has a SA_LEN(s) macro */
+#undef HAVE_SA_LEN_MACRO
+
+/* Define if your system has struct sockaddr_storage */
+#undef HAVE_SOCKADDR_STORAGE
+
+/* Define if your system has sockaddr_storage.__ss_family */
+#undef HAVE_2553_STYLE_SS_FAMILY
+
+/* Define if your IN6_ARE_ADDR_EQUAL macro is broken */
+#undef HAVE_BROKEN_IN6_ARE_ADDR_EQUAL
+
+/* Define if your system has a working snprintf function. */
+#undef HAVE_SNPRINTF
+
+/* Define if fpos_t is at least 64 bits and compatible with off_t. */
+#undef HAVE_LARGE_FPOS_T
+
+/* Define if you need to call msync after writes. */
+#undef MMAP_MISSES_WRITES
+
+/* Define if you need to call msync for calls to read to see changes. */
+#undef MMAP_NEEDS_MSYNC
+
+/* Define if your msync function takes three arguments. */
+#undef HAVE_MSYNC_3_ARG
+
+/* Define if you have unix domain sockets. */
+#undef HAVE_UNIX_DOMAIN_SOCKETS
+
+/* Syslog facility to use for innd logs. */
+#undef LOG_INN_SERVER
+
+/* Syslog facility to use for INN program logs. */
+#undef LOG_INN_PROG
+
+
+
+/*
+** BUFFER SIZES AND DATA LENGTHS
+**
+** You shouldn't need to change any of the following, and changing some of
+** them may cause other things to break. Some standard buffer sizes and
+** lengths of data types for various different things.
+*/
+
+/* The data type to use for article numbers. This probably can't be
+ increased without a lot of work due to assumptions about the active file
+ format, etc. */
+typedef unsigned long ARTNUM;
+
+/* Input buffers start at START_BUFF_SIZE. While reading input, if we have
+ less than LOW_WATER bytes left free in the buffer, use the current
+ buffersize as input to GROW_AMOUNT to determine how much to realloc.
+ Growth must be at least NNTP_STRLEN bytes! The default settings provide
+ aggressive, exponential buffer growth. */
+#define START_BUFF_SIZE (4 * 1024)
+#define LOW_WATER (1 * 1024)
+#define GROW_AMOUNT(x) ((x) < 128 * 1024 ? (x) : 128 * 1024)
+
+/* The size of a large buffer. Free dynamically allocated buffers larger
+ than this when we're done with them. */
+#define BIG_BUFFER (2 * START_BUFF_SIZE)
+
+/* The maximum length of a single header, used as a good guess at a buffer
+ size for some header parsing code. This is currently also used by innd
+ to determine whether to reject a message for an excessively long header;
+ this behavior should be fixed. */
+#define MAXHEADERSIZE 1024
+
+/* Default buffer size for outgoing feeds from innd. */
+#define SITE_BUFFER_SIZE (16 * 1024)
+
+/* The size of a small buffer. */
+#define SMBUF 256
+
+/* Maximum size of a pathname in the spool directory. */
+#define SPOOLNAMEBUFF 512
+
+
+/*
+** LEGACY
+**
+** Everything below this point is here so that parts of INN that haven't
+** been tweaked to use more standard constructs don't break. Don't count
+** on any of this staying in this file. If you have a chance, consider
+** following the comments before each item and fixing it.
+*/
+
+/* Used to send commands to exploders. Should be moved into a more specific
+ header file; used by innd/site.c and backends/buffchan.c. */
+#define EXP_CONTROL '!'
+
+/* Only used by innd and cvtbatch, should be moved to a more specific header
+ file. */
+#define FEED_BYTESIZE 'b'
+#define FEED_FULLNAME 'f'
+#define FEED_HASH 'h'
+#define FEED_HDR_DISTRIB 'D'
+#define FEED_HDR_NEWSGROUP 'N'
+#define FEED_MESSAGEID 'm'
+#define FEED_FNLNAMES '*'
+#define FEED_HEADERS 'H'
+#define FEED_NAME 'n'
+#define FEED_STOREDGROUP 'G'
+#define FEED_NEWSGROUP 'g'
+#define FEED_OVERVIEW 'O'
+#define FEED_PATH 'P'
+#define FEED_REPLIC 'R'
+#define FEED_SITE 's'
+#define FEED_TIMEEXPIRED 'e'
+#define FEED_TIMERECEIVED 't'
+#define FEED_TIMEPOSTED 'p'
+
+/* Maximum number of flags for a feed in newsfeeds. Only used in innd,
+ should be moved there (or made dynamic). */
+#define FEED_MAXFLAGS 20
+
+/* Maximum length of argv vectors used in innd/site.c. This should be moved
+ out of here into that file, or even better hard-coded rather than
+ defined; this value isn't affected by user data and the right value can
+ be determined by looking at the code and seeing how big of an argv it
+ will attempt to construct. */
+#define MAX_BUILTIN_ARGV 20
+
+/* active file flags. Should be moved to a more specific header file. */
+#define NF_FLAG_ALIAS '='
+#define NF_FLAG_EXCLUDED 'j'
+#define NF_FLAG_MODERATED 'm'
+#define NF_FLAG_OK 'y'
+#define NF_FLAG_NOLOCAL 'n'
+#define NF_FLAG_IGNORE 'x'
+
+/* Used for parsing the Newsgroups header. Should be rolled into a library
+ for parsing headers, combining all the code that's currently scattered
+ all over INN for doing that. */
+#define NG_SEPARATOR ","
+#define NG_ISSEP(c) ((c) == ',')
+
+/* There's no reason to make all of these #defines except possibly for
+ L_CC_CMD and even that's a stretch. Since we're logging to our own
+ distinguished log facility, provided that we spread things out between a
+ reasonable variety of log levels, the sysadmin shouldn't have to change
+ any of this. (Some of this is arguably wrong; L_NOTICE should be
+ LOG_NOTICE, for example.) */
+
+/* Flags to use in opening the logs; some programs add LOG_PID. */
+#define L_OPENLOG_FLAGS (LOG_CONS | LOG_NDELAY)
+
+/* Fatal error, program is about to exit. */
+#define L_FATAL LOG_CRIT
+
+/* Log an error that might mean one or more articles get lost. */
+#define L_ERROR LOG_ERR
+
+/* Informational notice, usually not worth caring about. */
+#define L_NOTICE LOG_WARNING
+
+/* A protocol trace. */
+#define L_TRACE LOG_DEBUG
+
+/* All incoming control commands (ctlinnd, etc). */
+#define L_CC_CMD LOG_INFO
+
+#endif /* !CONFIG_H */
--- /dev/null
+#ifndef __DBZ_H__
+#define __DBZ_H__
+
+/* Need the definition of HASH. */
+#include "libinn.h"
+
+BEGIN_DECLS
+
+/* This is the number of bytes of the md5 to actually store in
+ * the .pag file. This number directly effects the collision
+ * rate and memory usage. You can probably set this number as
+ * low as 5 w/o problems and some sites may want to set it as
+ * high as 8. Anything higher than that is probably not useful.
+ * Note at the internal hash size isn't the only factor that
+ * effects collision rate. The table index is used as an implicit
+ * part of the hash value stored also.
+ */
+#ifdef DO_TAGGED_HASH
+#define DBZMAXKEY 255
+#define DBZ_INTERNAL_HASH_SIZE 4
+#else
+#define DBZ_INTERNAL_HASH_SIZE 6
+#endif
+
+typedef enum {DBZSTORE_OK, DBZSTORE_EXISTS, DBZSTORE_ERROR} DBZSTORE_RESULT;
+typedef enum {INCORE_NO, INCORE_MEM, INCORE_MMAP} dbz_incore_val;
+
+typedef struct {
+ /* Whether to write to the filesystem in addition to updating the incore
+ copy. This will replace a single large write to disk when dbzsync is
+ called. */
+ bool writethrough;
+ /* Whether to do hash lookups from disk, memory or a mmap'ed file */
+ dbz_incore_val pag_incore;
+ dbz_incore_val exists_incore;
+ /* Whether dbzstore should update the database async or sync. This
+ is only applicable if you're not mmaping the database */
+ bool nonblock;
+} dbzoptions;
+
+#ifdef __GNUC__
+#define PACKED __attribute__ ((packed))
+#else
+#if !defined(PACKED)
+#define PACKED
+#endif
+#endif
+
+#if !defined(lint) && (defined(__SUNPRO_C) || defined(_nec_ews))
+#pragma pack(1)
+#endif /* nor lint, nor __SUNPRO_C, nor sgi, nor _nec_ews */
+typedef struct {
+ char hash[DBZ_INTERNAL_HASH_SIZE];
+} PACKED erec;
+#if !defined(lint) && (defined(__SUNPRO_C) || defined(_nec_ews))
+#pragma pack()
+#endif /* nor lint, nor__SUNPRO_C, nor _nec_ews */
+
+/* standard dbm functions */
+extern bool dbzinit(const char *name);
+extern bool dbzclose(void);
+
+/* new stuff for dbz */
+extern bool dbzfresh(const char *name, off_t size);
+extern bool dbzagain(const char *name, const char *oldname);
+extern bool dbzexists(const HASH key);
+extern bool dbzfetch(const HASH key, off_t *value);
+extern DBZSTORE_RESULT dbzstore(const HASH key, off_t data);
+extern bool dbzsync(void);
+extern long dbzsize(off_t contents);
+extern void dbzsetoptions(const dbzoptions options);
+extern void dbzgetoptions(dbzoptions *options);
+
+END_DECLS
+
+#endif /* __DBZ_H__ */
--- /dev/null
+/* $Id: buffer.h 6295 2003-04-16 05:46:38Z rra $
+**
+** Counted, reusable memory buffer.
+**
+** A buffer is an allocated bit of memory with a known size and a separate
+** data length. It's intended to store strings and can be reused repeatedly
+** to minimize the number of memory allocations. Buffers increase in
+** increments of 1K.
+**
+** A buffer contains a notion of the data that's been used and the data
+** that's been left, used when the buffer is an I/O buffer where lots of data
+** is buffered and then slowly processed out of the buffer. The total length
+** of the data is used + left. If a buffer is just used to store some data,
+** used can be set to 0 and left stores the length of the data.
+*/
+
+#ifndef INN_BUFFER_H
+#define INN_BUFFER_H 1
+
+#include <inn/defines.h>
+
+struct buffer {
+ size_t size; /* Total allocated length. */
+ size_t used; /* Data already used. */
+ size_t left; /* Remaining unused data. */
+ char *data; /* Pointer to allocated memory. */
+};
+
+BEGIN_DECLS
+
+/* Allocate a new buffer and initialize its contents. */
+struct buffer *buffer_new(void);
+
+/* Resize a buffer to be at least as large as the provided size. */
+void buffer_resize(struct buffer *, size_t);
+
+/* Set the buffer contents, ignoring anything currently there. */
+void buffer_set(struct buffer *, const char *data, size_t length);
+
+/* Append data to the buffer. */
+void buffer_append(struct buffer *, const char *data, size_t length);
+
+/* Swap the contents of two buffers. */
+void buffer_swap(struct buffer *, struct buffer *);
+
+END_DECLS
+
+#endif /* INN_BUFFER_H */
--- /dev/null
+/* $Id: confparse.h 5114 2002-02-18 01:17:24Z rra $
+**
+** Configuration file parsing interface.
+*/
+
+#ifndef INN_CONFPARSE_H
+#define INN_CONFPARSE_H 1
+
+#include <inn/defines.h>
+
+/* Avoid including <inn/vector.h> unless the client needs it. */
+struct vector;
+
+/* The opaque data type representing a configuration tree. */
+struct config_group;
+
+BEGIN_DECLS
+
+/* Parse the given file and build a configuration tree. This does purely
+ syntactic parsing; no semantic checking is done. After the file name, a
+ NULL-terminated list of const char * pointers should be given, naming the
+ top-level group types that the caller is interested in. If none are given
+ (if the second argument is NULL), the entire file is parsed. (This is
+ purely for efficiency reasons; if one doesn't care about speed, everything
+ will work the same if no types are given.)
+
+ Returns a config_group for the top-level group representing the entire
+ file. Generally one never wants to query parameters in this group;
+ instead, the client should then call config_find_group for the group type
+ of interest. Returns NULL on failure to read the file or on a parse
+ failure; errors are reported via warn. */
+struct config_group *config_parse_file(const char *filename, /* types */ ...);
+
+/* config_find_group returns the first group of the given type found in the
+ tree rooted at its argument. config_next_group returns the next group in
+ the tree of the same type as the given group (or NULL if none is found).
+ This can be used to do such things as enumerate all "peer" groups in a
+ configuration file. */
+struct config_group *config_find_group(struct config_group *,
+ const char *type);
+struct config_group *config_next_group(struct config_group *);
+
+/* Accessor functions for group information. */
+const char *config_group_type(struct config_group *);
+const char *config_group_tag(struct config_group *);
+
+/* Look up a parameter in a given config tree. The second argument is the
+ name of the parameter, and the result will be stored in the third argument
+ if the function returns true. If it returns false, the third argument is
+ unchanged and that parameter wasn't set (or was set to an invalid value for
+ the expected type). */
+bool config_param_boolean(struct config_group *, const char *, bool *);
+bool config_param_integer(struct config_group *, const char *, long *);
+bool config_param_real(struct config_group *, const char *, double *);
+bool config_param_string(struct config_group *, const char *, const char **);
+bool config_param_list(struct config_group *, const char *, struct vector *);
+
+/* Used for checking a configuration file, returns a vector of all parameters
+ set for the given config_group, including inherited ones. */
+struct vector *config_params(struct config_group *);
+
+/* Used for reporting semantic errors, config_error_param reports the given
+ error at a particular parameter in a config_group and config_error_group
+ reports an error at the definition of that group. The error is reported
+ using warn. */
+void config_error_group(struct config_group *, const char *format, ...);
+void config_error_param(struct config_group *, const char *key,
+ const char *format, ...);
+
+/* Free all space allocated by the tree rooted at config_group. One normally
+ never wants to do this. WARNING: This includes the storage allocated for
+ all strings returned by config_param_string and config_param_list for any
+ configuration groups in this tree. */
+void config_free(struct config_group *);
+
+END_DECLS
+
+#endif /* INN_CONFPARSE_H */
--- /dev/null
+/* $Id: defines.h 6124 2003-01-14 06:03:29Z rra $
+**
+** Portable defines used by other INN header files.
+**
+** In order to make the libraries built by INN usable by other software,
+** INN needs to install several header files. Installing autoconf-
+** generated header files, however, is a bad idea, since the defines will
+** conflict with other software that uses autoconf.
+**
+** This header contains common definitions, such as internal typedefs and
+** macros, common to INN's header files but not based on autoconf probes.
+** As such, it's limited in what it can do; if compiling software against
+** INN's header files on a system not supporting basic ANSI C features
+** (such as const) or standard types (like size_t), the software may need
+** to duplicate the tests that INN itself performs, generate a config.h,
+** and make sure that config.h is included before any INN header files.
+*/
+
+#ifndef INN_DEFINES_H
+#define INN_DEFINES_H 1
+
+#include <inn/system.h>
+
+/* BEGIN_DECLS is used at the beginning of declarations so that C++
+ compilers don't mangle their names. END_DECLS is used at the end. */
+#undef BEGIN_DECLS
+#undef END_DECLS
+#ifdef __cplusplus
+# define BEGIN_DECLS extern "C" {
+# define END_DECLS }
+#else
+# define BEGIN_DECLS /* empty */
+# define END_DECLS /* empty */
+#endif
+
+/* __attribute__ is available in gcc 2.5 and later, but only with gcc 2.7
+ could you use the __format__ form of the attributes, which is what we use
+ (to avoid confusion with other macros). */
+#ifndef __attribute__
+# if __GNUC__ < 2 || (__GNUC__ == 2 && __GNUC_MINOR__ < 7)
+# define __attribute__(spec) /* empty */
+# endif
+#endif
+
+/* Used for unused parameters to silence gcc warnings. */
+#define UNUSED __attribute__((__unused__))
+
+/* Make available the bool type. */
+#if INN_HAVE_STDBOOL_H
+# include <stdbool.h>
+#else
+# undef true
+# undef false
+# define true (1)
+# define false (0)
+# ifndef __cplusplus
+# define bool int
+# endif
+#endif /* INN_HAVE_STDBOOL_H */
+
+/* Tell Perl that we have a bool type. */
+#ifndef HAS_BOOL
+# define HAS_BOOL 1
+#endif
+
+#endif /* !INN_DEFINES_H */
--- /dev/null
+/* $Id: hashtab.h 5944 2002-12-08 02:33:08Z rra $
+**
+** Generic hash table interface.
+**
+** Written by Russ Allbery <rra@stanford.edu>
+** This work is hereby placed in the public domain by its author.
+**
+** A hash table takes a hash function that acts on keys, a function to
+** extract the key from a data item stored in a hash, a function that takes
+** a key and a data item and returns true if the key matches, and a
+** function to be called on any data item being deleted from the hash.
+**
+** hash_create creates a hash and hash_free frees all the space allocated
+** by one. hash_insert, hash_replace, and hash_delete modify it, and
+** hash_lookup extracts values. hash_traverse can be used to walk the
+** hash, and hash_count returns the number of elements currently stored in
+** the hash. hash_searches, hash_collisions, and hash_expansions extract
+** performance and debugging statistics.
+*/
+
+#ifndef INN_HASHTAB_H
+#define INN_HASHTAB_H 1
+
+#include <inn/defines.h>
+
+BEGIN_DECLS
+
+/* The layout of this struct is entirely internal to the implementation. */
+struct hash;
+
+/* Data types for function pointers used by the hash table interface. */
+typedef unsigned long (*hash_func)(const void *);
+typedef const void * (*hash_key_func)(const void *);
+typedef bool (*hash_equal_func)(const void *, const void *);
+typedef void (*hash_delete_func)(void *);
+typedef void (*hash_traverse_func)(void *, void *);
+
+/* Generic hash table interface. */
+struct hash * hash_create(size_t, hash_func, hash_key_func,
+ hash_equal_func, hash_delete_func);
+void hash_free(struct hash *);
+void * hash_lookup(struct hash *, const void *key);
+bool hash_insert(struct hash *, const void *key, void *datum);
+bool hash_replace(struct hash *, const void *key, void *datum);
+bool hash_delete(struct hash *, const void *key);
+void hash_traverse(struct hash *, hash_traverse_func, void *);
+unsigned long hash_count(struct hash *);
+unsigned long hash_searches(struct hash *);
+unsigned long hash_collisions(struct hash *);
+unsigned long hash_expansions(struct hash *);
+
+/* Hash functions available for callers. */
+unsigned long hash_string(const void *);
+
+/* Functions useful for constructing new hashes. */
+unsigned long hash_lookup2(const char *, size_t, unsigned long partial);
+
+END_DECLS
+
+#endif /* INN_HASHTAB_H */
--- /dev/null
+/* $Id: history.h 4916 2001-07-18 12:33:01Z alexk $
+**
+** Interface to history API
+*/
+
+#ifndef INN_HISTORY_H
+#define INN_HISTORY_H
+
+#include <inn/defines.h>
+
+BEGIN_DECLS
+
+/*
+** ensure appropriate scoping; we don't pull inn/storage.h as we
+** don't need; our caller then has the option
+*/
+struct history;
+struct token;
+
+/*
+** structure giving cache statistics returned from HISstats
+*/
+struct histstats {
+ /* number of positive hits */
+ int hitpos;
+ /* number of negative hits */
+ int hitneg;
+ /* number of misses (positive hit, but not in cache) */
+ int misses;
+ /* number of does not exists (negative hit, but not in cache) */
+ int dne;
+};
+
+
+/*
+** flags passed to HISopen
+*/
+
+/* open database read only */
+#define HIS_RDONLY (0)
+
+/* open database read/write */
+#define HIS_RDWR (1<<0)
+
+/* create on open */
+#define HIS_CREAT (1<<1)
+
+/* hint that the data should be kept on disk */
+#define HIS_ONDISK (1<<2)
+
+/* hint that the data should be kept in core */
+#define HIS_INCORE (1<<3)
+
+/* hint that the data should be kept mmap()ed */
+#define HIS_MMAP (1<<4)
+
+/*
+** values passed to HISctl
+*/
+enum {
+ /* (char **) get history path */
+ HISCTLG_PATH,
+
+ /* (char *) set history path */
+ HISCTLS_PATH,
+
+ /* (int) how many history writes may be outstanding */
+ HISCTLS_SYNCCOUNT,
+
+ /* (size_t) number of pairs for which the database should be sized */
+ HISCTLS_NPAIRS,
+
+ /* (bool) Ignore old database during expire */
+ HISCTLS_IGNOREOLD,
+
+ /* (time_t) interval, in s, between stats of the history database
+ * for * detecting a replacement, or 0 to disable (no checks);
+ * defaults {hisv6, taggedhash} */
+ HISCTLS_STATINTERVAL
+
+};
+
+struct history * HISopen(const char *, const char *, int);
+bool HISclose(struct history *);
+bool HISsync(struct history *);
+void HISsetcache(struct history *, size_t);
+bool HISlookup(struct history *, const char *, time_t *,
+ time_t *, time_t *, struct token *);
+bool HIScheck(struct history *, const char *);
+bool HISwrite(struct history *, const char *, time_t,
+ time_t, time_t, const struct token *);
+bool HISremember(struct history *, const char *, time_t);
+bool HISreplace(struct history *, const char *, time_t,
+ time_t, time_t, const struct token *);
+bool HISexpire(struct history *, const char *, const char *,
+ bool, void *, time_t,
+ bool (*)(void *, time_t, time_t, time_t,
+ struct token *));
+bool HISwalk(struct history *, const char *, void *,
+ bool (*)(void *, time_t, time_t, time_t,
+ const struct token *));
+struct histstats HISstats(struct history *);
+const char * HISerror(struct history *);
+bool HISctl(struct history *, int, void *);
+void HISlogclose(void);
+void HISlogto(const char *s);
+
+END_DECLS
+
+#endif
--- /dev/null
+/* $Id: innconf.h 7751 2008-04-06 14:35:40Z iulius $
+**
+** inn.conf parser interface.
+**
+** The interface to reading inn.conf configuration files and managing the
+** resulting innconf struct.
+*/
+
+#ifndef INN_INNCONF_H
+#define INN_INNCONF_H 1
+
+#include <inn/defines.h>
+#include <stdio.h>
+
+/*
+** This structure is organized in the same order as the variables contained
+** in it are mentioned in the inn.conf documentation, and broken down into
+** the same sections. Note that due to the implementation, only three types
+** of variables are permissible here: char *, bool, and long.
+*/
+struct innconf {
+ /* General Settings */
+ char *domain; /* Default domain of local host */
+ char *innflags; /* Flags to pass to innd on startup */
+ char *mailcmd; /* Command to send report/control type mail */
+ char *mta; /* MTA for mailing to moderators, innmail */
+ char *pathhost; /* Entry for the Path line */
+ char *server; /* Default server to connect to */
+
+ /* Feed Configuration */
+ long artcutoff; /* Max accepted article age */
+ char *bindaddress; /* Which interface IP to bind to */
+ char *bindaddress6; /* Which interface IPv6 to bind to */
+ bool dontrejectfiltered; /* Don't reject filtered article? */
+ long hiscachesize; /* Size of the history cache in kB */
+ bool ignorenewsgroups; /* Propagate cmsgs by affected group? */
+ bool immediatecancel; /* Immediately cancel timecaf messages? */
+ long linecountfuzz; /* Check linecount and reject if off by more */
+ long maxartsize; /* Reject articles bigger than this */
+ long maxconnections; /* Max number of incoming NNTP connections */
+ char *pathalias; /* Prepended Host for the Path line */
+ char *pathcluster; /* Appended Host for the Path line */
+ bool pgpverify; /* Verify control messages with pgpverify? */
+ long port; /* Which port innd should listen on */
+ bool refusecybercancels; /* Reject message IDs with "<cancel."? */
+ bool remembertrash; /* Put unwanted article IDs into history */
+ char *sourceaddress; /* Source IP for outgoing NNTP connections */
+ char *sourceaddress6; /* Source IPv6 for outgoing NNTP connections */
+ bool verifycancels; /* Verify cancels against article author */
+ bool wanttrash; /* Put unwanted articles in junk */
+ long wipcheck; /* How long to defer other copies of article */
+ long wipexpire; /* How long to keep pending article record */
+
+ /* History settings */
+ char *hismethod; /* Which history method to use */
+
+ /* Article Storage */
+ long cnfscheckfudgesize; /* Additional CNFS integrity checking */
+ bool enableoverview; /* Store overview info for articles? */
+ bool groupbaseexpiry; /* Do expiry by newsgroup? */
+ bool mergetogroups; /* Refile articles from to.* into to */
+ bool nfswriter; /* Use NFS writer functionality */
+ long overcachesize; /* fd size cache for tradindexed */
+ char *ovgrouppat; /* Newsgroups to store overview for */
+ char *ovmethod; /* Which overview method to use */
+ bool storeonxref; /* SMstore use Xref to detemine class? */
+ bool useoverchan; /* overchan write the overview, not innd? */
+ bool wireformat; /* Store tradspool artilces in wire format? */
+ bool xrefslave; /* Act as a slave of another server? */
+
+ /* Reading */
+ bool allownewnews; /* Allow use of the NEWNEWS command */
+ bool articlemmap; /* Use mmap to read articles? */
+ long clienttimeout; /* How long nnrpd can be inactive */
+ long initialtimeout; /* How long nnrpd waits for first command */
+ long msgidcachesize; /* Number of entries in the message ID cache */
+ bool nfsreader; /* Use NFS reader functionality */
+ long nfsreaderdelay; /* Delay applied to article arrival */
+ bool nnrpdcheckart; /* Check article existence before returning? */
+ char *nnrpdflags; /* Arguments to pass when spawning nnrpd */
+ long nnrpdloadlimit; /* Maximum getloadvg() we allow */
+ bool noreader; /* Refuse to fork nnrpd for readers? */
+ bool readerswhenstopped; /* Allow nnrpd when server is paused */
+ bool readertrack; /* Use the reader tracking system? */
+ bool tradindexedmmap; /* Whether to mmap for tradindexed */
+
+ /* Reading -- Keyword Support */
+ bool keywords; /* Generate keywords in overview? */
+ long keyartlimit; /* Max article size for keyword generation */
+ long keylimit; /* Max allocated space for keywords */
+ long keymaxwords; /* Max count of interesting works */
+
+ /* Posting */
+ bool addnntppostingdate; /* Add NNTP-Posting-Date: to posts */
+ bool addnntppostinghost; /* Add NNTP-Posting-Host: to posts */
+ bool checkincludedtext; /* Reject if too much included text */
+ char *complaints; /* Address for X-Complaints-To: */
+ char *fromhost; /* Host for the From: line */
+ long localmaxartsize; /* Max article size of local postings */
+ char *moderatormailer; /* Default host to mail moderated articles */
+ bool nnrpdauthsender; /* Add authenticated Sender: header? */
+ char *nnrpdposthost; /* Host postings should be forwarded to */
+ long nnrpdpostport; /* Port postings should be forwarded to */
+ char *organization; /* Data for the Organization: header */
+ bool spoolfirst; /* Spool all posted articles? */
+ bool strippostcc; /* Strip To:, Cc: and Bcc: from posts */
+
+ /* Posting -- Exponential Backoff */
+ bool backoffauth; /* Backoff by user, not IP address */
+ char *backoffdb; /* Directory for backoff databases */
+ long backoffk; /* Multiple for the sleep time */
+ long backoffpostfast; /* Upper time limit for fast posting */
+ long backoffpostslow; /* Lower time limit for slow posting */
+ long backofftrigger; /* Number of postings before triggered */
+
+ /* Monitoring */
+ bool doinnwatch; /* Start innwatch from rc.news? */
+ long innwatchbatchspace; /* Minimum free space in pathoutgoing */
+ long innwatchlibspace; /* Minimum free space in pathdb */
+ long innwatchloload; /* Load times 100 at which to restart */
+ long innwatchhiload; /* Load times 100 at which to throttle */
+ long innwatchpauseload; /* Load times 100 at which to pause */
+ long innwatchsleeptime; /* Seconds to wait between checks */
+ long innwatchspoolnodes; /* Minimum free inodes in patharticles */
+ long innwatchspoolspace; /* Minimum free space in patharticles */
+
+ /* Logging */
+ bool docnfsstat; /* Run cnfsstat in the background? */
+ bool logartsize; /* Log article sizes? */
+ bool logcancelcomm; /* Log ctlinnd cancel commands to syslog? */
+ long logcycles; /* How many old logs scanlogs should keep */
+ bool logipaddr; /* Log by host IP address? */
+ bool logsitename; /* Log outgoing site names? */
+ bool nnrpdoverstats; /* Log overview statistics? */
+ long nntpactsync; /* Checkpoint log after this many articles */
+ bool nntplinklog; /* Put storage token into the log? */
+ long status; /* Status file update interval */
+ long timer; /* Performance monitoring interval */
+ char *stathist; /* Filename for history profiler outputs */
+
+ /* System Tuning */
+ long badiocount; /* Failure count before dropping channel */
+ long blockbackoff; /* Multiplier for sleep in EAGAIN writes */
+ long chaninacttime; /* Wait before noticing inactive channels */
+ long chanretrytime; /* How long before channel restarts */
+ long icdsynccount; /* Articles between active & history updates */
+ long keepmmappedthreshold; /* Threshold for keeping mmap in buffindexed */
+ long maxforks; /* Give up after this many fork failure */
+ long nicekids; /* Child processes get niced to this */
+ long nicenewnews; /* If NEWNEWS command is used, nice to this */
+ long nicennrpd; /* nnrpd is niced to this */
+ long pauseretrytime; /* Seconds before seeing if pause is ended */
+ long peertimeout; /* How long peers can be inactive */
+ long rlimitnofile; /* File descriptor limit to set */
+ long maxcmdreadsize; /* max NNTP command read size used by innd */
+ long datamovethreshold; /* threshold no to extend buffer for ever */
+
+ /* Paths */
+ char *patharchive; /* Archived news. */
+ char *patharticles; /* Articles. */
+ char *pathbin; /* News binaries. */
+ char *pathcontrol; /* Path to control message handlers */
+ char *pathdb; /* News database files */
+ char *pathetc; /* News configuration files */
+ char *pathfilter; /* Filtering code */
+ char *pathhttp; /* HTML files */
+ char *pathincoming; /* Incoming spooled news */
+ char *pathlog; /* Log files */
+ char *pathnews; /* Home directory for news user */
+ char *pathoutgoing; /* Outgoing news batch files */
+ char *pathoverview; /* Overview infomation */
+ char *pathrun; /* Runtime state and sockets */
+ char *pathspool; /* Root of news spool hierarchy */
+ char *pathtmp; /* Temporary files for the news system */
+};
+
+/* The global innconf variable used in programs. */
+extern struct innconf *innconf;
+
+/* Used to request various types of quoting when printing out values. */
+enum innconf_quoting {
+ INNCONF_QUOTE_NONE,
+ INNCONF_QUOTE_SHELL,
+ INNCONF_QUOTE_PERL,
+ INNCONF_QUOTE_TCL
+};
+
+BEGIN_DECLS
+
+/* Parse the given file into innconf, using the default path if NULL. */
+bool innconf_read(const char *path);
+
+/* Free an innconf struct and all allocated memory for it. */
+void innconf_free(struct innconf *);
+
+/* Print a single value with appropriate quoting, return whether found. */
+bool innconf_print_value(FILE *, const char *key, enum innconf_quoting);
+
+/* Dump the entire configuration with appropriate quoting. */
+void innconf_dump(FILE *, enum innconf_quoting);
+
+/* Compare two instances of an innconf struct, for testing. */
+bool innconf_compare(struct innconf *, struct innconf *);
+
+/* Check the validity of an inn.conf file. Does innconf_read plus checking
+ for any unknown parameters that are set. */
+bool innconf_check(const char *path);
+
+END_DECLS
+
+#endif /* INN_INNCONF_H */
--- /dev/null
+/* $Id: list.h 6168 2003-01-21 06:27:32Z alexk $
+**
+*/
+
+#ifndef INN_LIST_H
+#define INN_LIST_H 1
+
+#include <inn/defines.h>
+
+struct node {
+ struct node *succ;
+ struct node *pred;
+};
+
+struct list {
+ struct node *head;
+ struct node *tail;
+ struct node *tailpred;
+};
+
+BEGIN_DECLS
+
+/* initialise a new list */
+void list_new(struct list *list);
+
+/* add a node to the head of the list */
+struct node *list_addhead(struct list *list, struct node *node);
+
+/* add a node to the tail of the list */
+struct node *list_addtail(struct list *list, struct node *node);
+
+/* return a pointer to the first node on the list */
+struct node *list_head(struct list *list);
+
+/* return a pointer to the last node on the list */
+struct node *list_tail(struct list *list);
+
+struct node *list_succ(struct node *node);
+struct node *list_pred(struct node *node);
+
+struct node *list_remhead(struct list *list);
+struct node *list_remove(struct node *node);
+struct node *list_remtail(struct list *list);
+struct node *list_insert(struct list *list, struct node *node,
+ struct node *pred);
+
+bool list_isempty(struct list *list);
+
+END_DECLS
+
+#endif /* INN_LIST_H */
--- /dev/null
+/* $Id: md5.h 4567 2001-02-24 08:10:16Z rra $
+**
+** RSA Data Security, Inc. MD5 Message-Digest Algorithm
+**
+** LANDON CURT NOLL DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE,
+** INCLUDING ALL IMPLIED WARRANTIES OF MER- CHANTABILITY AND FITNESS. IN
+** NO EVENT SHALL LANDON CURT NOLL BE LIABLE FOR ANY SPECIAL, INDIRECT OR
+** CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF
+** USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR
+** OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+** PERFORMANCE OF THIS SOFTWARE.
+**
+** Copyright (C) 1990, RSA Data Security, Inc. All rights reserved.
+**
+** License to copy and use this software is granted provided that it is
+** identified as the "RSA Data Security, Inc. MD5 Message-Digest
+** Algorithm" in all material mentioning or referencing this software or
+** this function.
+**
+** License is also granted to make and use derivative works provided that
+** such works are identified as "derived from the RSA Data Security,
+** Inc. MD5 Message-Digest Algorithm" in all material mentioning or
+** referencing the derived work.
+**
+** RSA Data Security, Inc. makes no representations concerning either the
+** merchantability of this software or the suitability of this software for
+** any particular purpose. It is provided "as is" without express or
+** implied warranty of any kind.
+**
+** These notices must be retained in any copies of any part of this
+** documentation and/or software.
+*/
+
+#ifndef INN_MD5_H
+#define INN_MD5_H 1
+
+#include <inn/defines.h>
+
+/* Make sure we have uint32_t. */
+#include <sys/types.h>
+#if INN_HAVE_INTTYPES_H
+# include <inttypes.h>
+#endif
+
+/* SCO OpenServer gets int32_t from here. */
+#if INN_HAVE_SYS_BITYPES_H
+# include <sys/bitypes.h>
+#endif
+
+/* Bytes to process at once, defined by the algorithm. */
+#define MD5_CHUNKSIZE (1 << 6)
+#define MD5_CHUNKWORDS (MD5_CHUNKSIZE / sizeof(uint32_t))
+
+/* Length of the digest, defined by the algorithm. */
+#define MD5_DIGESTSIZE 16
+#define MD5_DIGESTWORDS (MD5_DIGESTSIZE / sizeof(uint32_t))
+
+BEGIN_DECLS
+
+/* Data structure for MD5 message-digest computation. */
+struct md5_context {
+ uint32_t count[2]; /* A 64-bit byte count. */
+ uint32_t buf[MD5_DIGESTWORDS]; /* Scratch buffer. */
+ union {
+ unsigned char byte[MD5_CHUNKSIZE]; /* Byte chunk buffer. */
+ uint32_t word[MD5_CHUNKWORDS]; /* Word chunk buffer. */
+ } in;
+ unsigned int datalen; /* Length of data in in. */
+ unsigned char digest[MD5_DIGESTSIZE]; /* Final digest. */
+};
+
+extern void md5_hash(const unsigned char *, size_t, unsigned char *);
+extern void md5_init(struct md5_context *);
+extern void md5_update(struct md5_context *, const unsigned char *, size_t);
+extern void md5_final(struct md5_context *);
+
+END_DECLS
+
+#endif /* !INN_MD5_H */
--- /dev/null
+/* $Id: messages.h 5496 2002-06-07 13:59:06Z alexk $
+**
+** Logging, debugging, and error reporting functions.
+**
+** This collection of functions facilitate logging, debugging, and error
+** reporting in a flexible manner that can be used by libraries as well as by
+** programs. The functions are based around the idea of handlers, which take
+** a message and do something appropriate with it. The program can set the
+** appropriate handlers for all the message reporting functions, and then
+** library code can use them with impunity and know the right thing will
+** happen with the messages.
+*/
+
+#ifndef INN_MESSAGES_H
+#define INN_MESSAGES_H 1
+
+#include <inn/defines.h>
+#include <stdarg.h>
+
+BEGIN_DECLS
+
+/* These are the currently-supported types of traces. */
+enum message_trace {
+ TRACE_NETWORK, /* Network traffic. */
+ TRACE_PROGRAM, /* Stages of program execution. */
+ TRACE_ALL /* All traces; this must be last. */
+};
+
+/* The reporting functions. The ones prefaced by "sys" add a colon, a space,
+ and the results of strerror(errno) to the output and are intended for
+ reporting failures of system calls. */
+extern void trace(enum message_trace, const char *, ...)
+ __attribute__((__format__(printf, 2, 3)));
+extern void notice(const char *, ...)
+ __attribute__((__format__(printf, 1, 2)));
+extern void sysnotice(const char *, ...)
+ __attribute__((__format__(printf, 1, 2)));
+extern void warn(const char *, ...)
+ __attribute__((__format__(printf, 1, 2)));
+extern void syswarn(const char *, ...)
+ __attribute__((__format__(printf, 1, 2)));
+extern void die(const char *, ...)
+ __attribute__((__noreturn__, __format__(printf, 1, 2)));
+extern void sysdie(const char *, ...)
+ __attribute__((__noreturn__, __format__(printf, 1, 2)));
+
+/* Debug is handled specially, since we want to make the code disappear
+ completely unless we're built with -DDEBUG. We can only do that with
+ support for variadic macros, though; otherwise, the function just won't do
+ anything. */
+#if !defined(DEBUG) && (INN_HAVE_C99_VAMACROS || INN_HAVE_GNU_VAMACROS)
+# if INN_HAVE_C99_VAMACROS
+# define debug(format, ...) /* empty */
+# elif INN_HAVE_GNU_VAMACROS
+# define debug(format, args...) /* empty */
+# endif
+#else
+extern void debug(const char *, ...)
+ __attribute__((__format__(printf, 1, 2)));
+#endif
+
+/* Set the handlers for various message functions. All of these functions
+ take a count of the number of handlers and then function pointers for each
+ of those handlers. These functions are not thread-safe; they set global
+ variables. */
+extern void message_handlers_debug(int count, ...);
+extern void message_handlers_trace(int count, ...);
+extern void message_handlers_notice(int count, ...);
+extern void message_handlers_warn(int count, ...);
+extern void message_handlers_die(int count, ...);
+
+/* Enable or disable tracing for particular classes of messages. */
+extern void message_trace_enable(enum message_trace, bool);
+
+/* Some useful handlers, intended to be passed to message_handlers_*. All
+ handlers take the length of the formatted message, the format, a variadic
+ argument list, and the errno setting if any. */
+extern void message_log_stdout(int, const char *, va_list, int);
+extern void message_log_stderr(int, const char *, va_list, int);
+extern void message_log_syslog_debug(int, const char *, va_list, int);
+extern void message_log_syslog_info(int, const char *, va_list, int);
+extern void message_log_syslog_notice(int, const char *, va_list, int);
+extern void message_log_syslog_warning(int, const char *, va_list, int);
+extern void message_log_syslog_err(int, const char *, va_list, int);
+extern void message_log_syslog_crit(int, const char *, va_list, int);
+
+/* The type of a message handler. */
+typedef void (*message_handler_func)(int, const char *, va_list, int);
+
+/* If non-NULL, called before exit and its return value passed to exit. */
+extern int (*message_fatal_cleanup)(void);
+
+/* If non-NULL, prepended (followed by ": ") to all messages printed by either
+ message_log_stdout or message_log_stderr. */
+extern const char *message_program_name;
+
+END_DECLS
+
+#endif /* INN_MESSAGE_H */
--- /dev/null
+/* $Id: mmap.h 7598 2007-02-09 02:40:51Z eagle $
+**
+** MMap manipulation routines
+**
+** Written by Alex Kiernan (alex.kiernan@thus.net)
+**
+** These routines work with mmap()ed memory
+*/
+
+#ifndef INN_MMAP_H
+#define INN_MMAP_H 1
+
+#include <inn/defines.h>
+
+BEGIN_DECLS
+
+/* Figure out what page an address is in and flush those pages. This is the
+ internal function, which we wrap with a define below. */
+void inn__mapcntl(void *, size_t, int);
+
+/* Some platforms only support two arguments to msync. On those platforms,
+ make the third argument to mapcntl always be zero, getting rid of whatever
+ the caller tried to pass. This avoids undefined symbols for MS_ASYNC and
+ friends on platforms with two-argument msync functions. */
+#ifdef INN_HAVE_MSYNC_3_ARG
+# define inn_mapcntl inn__mapcntl
+#else
+# define inn_mapcntl(p, l, f) inn__mapcntl((p), (l), 0)
+#endif
+
+END_DECLS
+
+#endif /* INN_MMAP_H */
--- /dev/null
+/* $Id: qio.h 3653 2000-07-29 02:57:50Z rra $
+**
+** Quick I/O package.
+**
+** The interface to the Quick I/O package, optimized for reading through
+** files line by line. This package uses internal buffering like stdio,
+** but is even more aggressive about its buffering.
+*/
+
+#ifndef INN_QIO_H
+#define INN_QIO_H 1
+
+#include <inn/defines.h>
+
+BEGIN_DECLS
+
+/*
+** State for a quick open file, equivalent to FILE for stdio. All callers
+** should treat this structure as opaque and instead use the functions and
+** macros defined below.
+*/
+enum QIOflag { QIO_ok, QIO_error, QIO_long };
+
+typedef struct {
+ int _fd;
+ size_t _length; /* Length of the current string. */
+ size_t _size; /* Size of the internal buffer. */
+ char * _buffer;
+ char * _start; /* Start of the unread data. */
+ char * _end; /* End of the available data. */
+ off_t _count; /* Number of bytes read so far. */
+ enum QIOflag _flag;
+} QIOSTATE;
+
+#define QIOerror(qp) ((qp)->_flag != QIO_ok)
+#define QIOtoolong(qp) ((qp)->_flag == QIO_long)
+#define QIOfileno(qp) ((qp)->_fd)
+#define QIOlength(qp) ((qp)->_length)
+#define QIOtell(qp) ((qp)->_count - ((qp)->_end - (qp)->_start))
+
+extern QIOSTATE * QIOopen(const char *name);
+extern QIOSTATE * QIOfdopen(int fd);
+extern char * QIOread(QIOSTATE *qp);
+extern void QIOclose(QIOSTATE *qp);
+extern int QIOrewind(QIOSTATE *qp);
+
+END_DECLS
+
+#endif /* !INN_QIO_H */
--- /dev/null
+/* $Id: sequence.h 4871 2001-07-09 08:09:58Z alexk $
+**
+** Sequence space arithmetic routines.
+**
+** This is a set of routines for implementing so called sequence
+** space arithmetic (typically used for DNS serial numbers). The
+** implementation here is taken from RFC 1982.
+*/
+
+#ifndef INN_SEQUENCE_H
+#define INN_SEQUENCE_H 1
+
+#include <inn/defines.h>
+
+BEGIN_DECLS
+
+int seq_lcompare(unsigned long, unsigned long);
+
+END_DECLS
+
+#endif /* INN_SEQUENCE_H */
--- /dev/null
+/* $Id: timer.h 6129 2003-01-19 00:39:49Z rra $
+**
+** Timer library interface.
+**
+** An interface to a simple profiling library. An application can declare
+** its intent to use n timers by calling TMRinit(n), and then start and
+** stop numbered timers with TMRstart and TMRstop. TMRsummary logs the
+** results to syslog given labels for each numbered timer.
+*/
+
+#ifndef INN_TIMER_H
+#define INN_TIMER_H
+
+#include <inn/defines.h>
+
+BEGIN_DECLS
+
+enum {
+ TMR_HISHAVE, /* Looking up ID in history (yes/no). */
+ TMR_HISGREP, /* Looking up ID in history (data). */
+ TMR_HISWRITE, /* Writing to history. */
+ TMR_HISSYNC, /* Syncing history to disk. */
+ TMR_APPLICATION /* Application numbering starts here. */
+};
+
+void TMRinit(unsigned int);
+void TMRstart(unsigned int);
+void TMRstop(unsigned int);
+void TMRsummary(const char *prefix, const char *const *labels);
+unsigned long TMRnow(void);
+void TMRfree(void);
+
+/* Return the current time as a double of seconds and fractional sections. */
+double TMRnow_double(void);
+
+END_DECLS
+
+#endif /* INN_TIMER_H */
--- /dev/null
+/* $Id: tst.h 6083 2002-12-27 07:24:36Z rra $
+**
+** Ternary search trie implementation.
+**
+** This implementation is based on the implementation by Peter A. Friend
+** (version 1.3), but has been assimilated into INN and modified to use INN
+** formatting conventions.
+**
+** Copyright (c) 2002, Peter A. Friend
+** All rights reserved.
+**
+** Redistribution and use in source and binary forms, with or without
+** modification, are permitted provided that the following conditions are
+** met:
+**
+** Redistributions of source code must retain the above copyright notice,
+** this list of conditions and the following disclaimer.
+**
+** 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.
+**
+** Neither the name of Peter A. Friend nor the names of its contributors may
+** be used to endorse or promote products derived from this software without
+** specific prior written permission.
+**
+** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS 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 THE COPYRIGHT OWNER 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.
+*/
+
+#ifndef INN_TST_H
+#define INN_TST_H 1
+
+#include <inn/defines.h>
+
+BEGIN_DECLS
+
+/* Constants used for return values and options. */
+enum tst_constants {
+ TST_OK,
+ TST_NULL_KEY,
+ TST_NULL_DATA,
+ TST_DUPLICATE_KEY,
+ TST_REPLACE
+};
+
+/* Opaque data type returned by and used by ternary search trie functions. */
+struct tst;
+
+/* Allocate a new ternary search trie. width is the number of nodes allocated
+ at a time and should be chosen carefully. One node is required for every
+ character in the tree. If you choose a value that is too small, your
+ application will spend too much time calling malloc and your node space
+ will be too spread out. Too large a value is just a waste of space. */
+struct tst *tst_init(int width);
+
+/* Insert a value into the tree. If the key already exists in the tree,
+ option determiens the behavior. If set to TST_REPLACE, the data for that
+ key is replaced with the new data value and the old value is returned in
+ exist_ptr. Otherwise, TST_DUPLICATE_KEY is returned. If key is zero
+ length, TST_NULL_KEY is returned. If data is NULL, TST_NULL_DATA is
+ returned. On success, TST_OK is returned.
+
+ The data argument may not be NULL. For a simple existence tree, use the
+ struct tst pointer as the data. */
+int tst_insert(struct tst *, const unsigned char *key, void *data, int option,
+ void **exist_ptr);
+
+/* Search for a key and return the associated data, or NULL if not found. */
+void *tst_search(struct tst *, const unsigned char *key);
+
+/* Delete the given key out of the trie, returning the data that it pointed
+ to. If the key was not found, returns NULL. */
+void *tst_delete(struct tst *, const unsigned char *key);
+
+/* Free the given ternary search trie and all resources it uses. */
+void tst_cleanup(struct tst *);
+
+#endif /* !INN_TST_H */
--- /dev/null
+/* $Id: vector.h 5450 2002-04-23 06:06:10Z rra $
+**
+** Vector handling (counted lists of char *'s).
+**
+** Written by Russ Allbery <rra@stanford.edu>
+** This work is hereby placed in the public domain by its author.
+**
+** A vector is a simple array of char *'s combined with a count. It's a
+** convenient way of managing a list of strings, as well as a reasonable
+** output data structure for functions that split up a string. There are
+** two basic types of vectors, regular vectors (in which case strings are
+** copied when put into a vector and freed when the vector is freed) and
+** cvectors or const vectors (where each pointer is a const char * to some
+** external string that isn't freed when the vector is freed).
+**
+** There are two interfaces here, one for vectors and one for cvectors,
+** with the basic operations being the same between the two.
+*/
+
+#ifndef INN_VECTOR_H
+#define INN_VECTOR_H 1
+
+#include <inn/defines.h>
+
+struct vector {
+ size_t count;
+ size_t allocated;
+ char **strings;
+};
+
+struct cvector {
+ size_t count;
+ size_t allocated;
+ const char **strings;
+};
+
+BEGIN_DECLS
+
+/* Create a new, empty vector. */
+struct vector *vector_new(void);
+struct cvector *cvector_new(void);
+
+/* Add a string to a vector. Resizes the vector if necessary. */
+void vector_add(struct vector *, const char *string);
+void cvector_add(struct cvector *, const char *string);
+
+/* Resize the array of strings to hold size entries. Saves reallocation work
+ in vector_add if it's known in advance how many entries there will be. */
+void vector_resize(struct vector *, size_t size);
+void cvector_resize(struct cvector *, size_t size);
+
+/* Reset the number of elements to zero, freeing all of the strings for a
+ regular vector, but not freeing the strings array (to cut down on memory
+ allocations if the vector will be reused). */
+void vector_clear(struct vector *);
+void cvector_clear(struct cvector *);
+
+/* Free the vector and all resources allocated for it. */
+void vector_free(struct vector *);
+void cvector_free(struct cvector *);
+
+/* Split functions build a vector from a string. vector_split splits on a
+ specified character, while vector_split_space splits on any sequence of
+ spaces or tabs (not any sequence of whitespace, as just spaces or tabs is
+ more useful for INN). The cvector versions destructively modify the
+ provided string in-place to insert nul characters between the strings. If
+ the vector argument is NULL, a new vector is allocated; otherwise, the
+ provided one is reused.
+
+ Empty strings will yield zero-length vectors. Adjacent delimiters are
+ treated as a single delimiter by *_split_space, but *not* by *_split, so
+ callers of *_split should be prepared for zero-length strings in the
+ vector. */
+struct vector *vector_split(const char *string, char sep, struct vector *);
+struct vector *vector_split_space(const char *string, struct vector *);
+struct cvector *cvector_split(char *string, char sep, struct cvector *);
+struct cvector *cvector_split_space(char *string, struct cvector *);
+
+/* Build a string from a vector by joining its components together with the
+ specified string as separator. Returns a newly allocated string; caller is
+ responsible for freeing. */
+char *vector_join(const struct vector *, const char *seperator);
+char *cvector_join(const struct cvector *, const char *separator);
+
+END_DECLS
+
+#endif /* INN_VECTOR_H */
--- /dev/null
+/* $Id: wire.h 6028 2002-12-24 05:10:39Z rra $
+**
+** Wire format article utilities.
+**
+** Originally written by Alex Kiernan (alex.kiernan@thus.net)
+**
+** These routines manipulate wire format articles; in particular, they should
+** be safe in the presence of embedded NULs and UTF-8 characters.
+*/
+
+#ifndef INN_WIRE_H
+#define INN_WIRE_H 1
+
+#include <inn/defines.h>
+
+BEGIN_DECLS
+
+/* Given a pointer to the start of an article, locate the first octet
+ of the body (which may be the octet beyond the end of the buffer if
+ your article is bodyless). */
+char *wire_findbody(const char *, size_t);
+
+/* Given a pointer into an article and a pointer to the end of the article,
+ find the start of the next line or return NULL if there are no more lines
+ remaining in the article. */
+char *wire_nextline(const char *, const char *end);
+
+/* Given a pointer to the start of an article and the name of a header, find
+ the beginning of the value of the given header (the returned pointer will
+ be after the name of the header and any initial whitespace). Headers whose
+ only content is whitespace are ignored. If the header isn't found, returns
+ NULL.
+
+ WARNING: This function does not comply with RFC 2822's idea of header
+ content, particularly in its skipping of initial whitespace. */
+char *wire_findheader(const char *article, size_t, const char *header);
+
+/* Given a pointer inside a header's value and a pointer to the end of the
+ article, returns a pointer to the end of the header value (the \n at the
+ end of the terminating \r\n with folding taken into account), or NULL if no
+ such terminator was found before the end of the article. */
+char *wire_endheader(const char *header, const char *end);
+
+END_DECLS
+
+#endif /* INN_WIRE_H */
--- /dev/null
+/* $Revision: 6786 $
+**
+** Here be values used for communicating with the server once it is
+** running.
+*/
+
+#ifdef __cplusplus
+extern "C" {
+#endif /* __cplusplus */
+
+/* The header for the ICC protocol is a one-byte protocol version followed
+ by a 2 byte messages length*/
+#define HEADER_SIZE (sizeof (ICC_PROTOCOLTYPE) + sizeof (ICC_MSGLENTYPE))
+
+typedef unsigned short ICC_MSGLENTYPE; /* Length code to prefix commands to
+ ** the server. */
+typedef char ICC_PROTOCOLTYPE ;
+
+/* Values for the protocol version field of the message. 8 bits wide. */
+#define ICC_PROTOCOL_1 'a'
+
+
+
+#define SC_SEP '\001'
+#define SC_MAXFIELDS 6
+
+#define SC_ADDHIST 'a'
+#define SC_ALLOW 'D'
+#define SC_BEGIN 'b'
+#define SC_CANCEL 'c'
+#define SC_CHANGEGROUP 'u'
+#define SC_CHECKFILE 'd'
+#define SC_DROP 'e'
+#define SC_FEEDINFO 'F'
+#define SC_FILTER 'T'
+#define SC_FLUSH 'f'
+#define SC_FLUSHLOGS 'g'
+#define SC_GO 'h'
+#define SC_HANGUP 'i'
+#define SC_LOGMODE 'E'
+#define SC_LOWMARK 'L'
+#define SC_MODE 's'
+#define SC_NAME 'j'
+#define SC_NEWGROUP 'k'
+#define SC_PARAM 'l'
+#define SC_PAUSE 'm'
+#define SC_PERL 'P'
+#define SC_PYTHON 'Y'
+#define SC_READERS 'v'
+#define SC_REJECT 'C'
+#define SC_RELOAD 'o'
+#define SC_RENUMBER 'n'
+#define SC_RESERVE 'z'
+#define SC_RMGROUP 'p'
+#define SC_SEND 'A'
+#define SC_SHUTDOWN 'q'
+#define SC_STATHIST 'H'
+#define SC_STATUS 'S'
+#define SC_SIGNAL 'B'
+#define SC_THROTTLE 'r'
+#define SC_TIMER 'Z'
+#define SC_TRACE 'w'
+#define SC_XABORT 'x'
+#define SC_XEXEC 'y'
+
+ /* Yes, we don't want anyone to use this. */
+#define SC_FIRSTFREE G
+
+#define MAX_REASON_LEN 80
+
+
+extern void ICCsettimeout(int i);
+extern int ICCopen(void);
+extern int ICCclose(void);
+extern int ICCcommand(char cmd, const char *argv[], char **replyp);
+extern int ICCcancel(const char *msgid);
+extern int ICCgo(const char *why);
+extern int ICCpause(const char *why);
+extern int ICCreserve(const char *why);
+
+extern const char *ICCfailure;
+
+/* Use a read or recv call to read a descriptor. */
+#ifdef HAVE_UNIX_DOMAIN_SOCKETS
+# define RECVorREAD(fd, p, s) recv((fd), (p), (s), 0)
+#else
+# define RECVorREAD(fd, p, s) read((fd), (p), (s))
+#endif
+
+#ifdef __cplusplus
+}
+#endif /* __cplusplus */
--- /dev/null
+/* $Id: innperl.h 5302 2002-03-12 20:10:46Z rra $
+**
+** Declarations for embedded Perl.
+*/
+
+#ifndef INNPERL_H
+#define INNPERL_H 1
+
+/* Skip this entire file if DO_PERL (./configure --with-perl) isn't set. */
+#if DO_PERL
+
+#include "config.h"
+
+BEGIN_DECLS
+
+extern bool PerlFilterActive;
+
+extern bool PerlFilter(bool value);
+extern void PerlClose(void);
+extern void PERLsetup(char *startupfile, char *filterfile,
+ const char *function);
+extern int PERLreadfilter(char *filterfile, const char *function);
+
+END_DECLS
+
+#endif /* DO_PERL */
+#endif /* !INNPERL_H */
--- /dev/null
+/* $Id: libinn.h 6135 2003-01-19 01:15:40Z rra $
+**
+** Here be declarations of functions in the InterNetNews library.
+*/
+
+#ifndef LIBINN_H
+#define LIBINN_H 1
+
+#include "inn/defines.h"
+
+/* Eventually we don't want to include this, since this will be an installed
+ header and we don't want to install config.h. */
+#include "config.h"
+
+#include <stdio.h> /* FILE */
+#include <sys/types.h> /* size_t and ssize_t */
+
+/* Forward declarations to avoid unnecessary includes. */
+struct stat;
+struct iovec;
+struct sockaddr;
+struct sockaddr_in;
+struct in_addr;
+
+BEGIN_DECLS
+
+/*
+** MEMORY MANAGEMENT
+*/
+
+/* The functions are actually macros so that we can pick up the file and line
+ number information for debugging error messages without the user having to
+ pass those in every time. */
+#define xcalloc(n, size) x_calloc((n), (size), __FILE__, __LINE__)
+#define xmalloc(size) x_malloc((size), __FILE__, __LINE__)
+#define xrealloc(p, size) x_realloc((p), (size), __FILE__, __LINE__)
+#define xstrdup(p) x_strdup((p), __FILE__, __LINE__)
+#define xstrndup(p, size) x_strndup((p), (size), __FILE__, __LINE__)
+
+/* Last two arguments are always file and line number. These are internal
+ implementations that should not be called directly. ISO C99 says that
+ identifiers beginning with _ and a lowercase letter are reserved for
+ identifiers of file scope, so while the position of libraries in the
+ standard isn't clear, it's probably not entirely kosher to use _xmalloc
+ here. Use x_malloc instead. */
+extern void *x_calloc(size_t, size_t, const char *, int);
+extern void *x_malloc(size_t, const char *, int);
+extern void *x_realloc(void *, size_t, const char *, int);
+extern char *x_strdup(const char *, const char *, int);
+extern char *x_strndup(const char *, size_t, const char *, int);
+
+/* Failure handler takes the function, the size, the file, and the line. */
+typedef void (*xmalloc_handler_t)(const char *, size_t, const char *, int);
+
+/* The default error handler. */
+void xmalloc_fail(const char *, size_t, const char *, int);
+
+/* Assign to this variable to choose a handler other than the default, which
+ just calls sysdie. */
+extern xmalloc_handler_t xmalloc_error_handler;
+
+
+/*
+** TIME AND DATE PARSING, GENERATION, AND HANDLING
+*/
+typedef struct _TIMEINFO {
+ time_t time;
+ long usec;
+ long tzone;
+} TIMEINFO;
+
+extern int GetTimeInfo(TIMEINFO *Now);
+extern bool makedate(time_t, bool local, char *buff, size_t buflen);
+extern time_t parsedate(char *p, TIMEINFO *now);
+extern time_t parsedate_nntp(const char *, const char *, bool local);
+extern time_t parsedate_rfc2822(const char *);
+
+
+/*
+** VERSION INFORMATION
+*/
+extern const int inn_version[3];
+extern const char inn_version_extra[];
+extern const char inn_version_string[];
+
+/* Earlier versions of INN didn't have <inn/version.h> and some source is
+ intended to be portable to different INN versions; it can use this macro
+ to determine whether <inn/version.h> is available. */
+#define HAVE_INN_VERSION_H 1
+
+
+/*
+** WILDMAT MATCHING
+*/
+enum uwildmat {
+ UWILDMAT_FAIL = 0,
+ UWILDMAT_MATCH = 1,
+ UWILDMAT_POISON
+};
+
+extern bool uwildmat(const char *text, const char *pat);
+extern bool uwildmat_simple(const char *text, const char *pat);
+extern enum uwildmat uwildmat_poison(const char *text, const char *pat);
+
+
+/*
+** FILE LOCKING
+*/
+enum inn_locktype {
+ INN_LOCK_READ,
+ INN_LOCK_WRITE,
+ INN_LOCK_UNLOCK
+};
+
+extern bool inn_lock_file(int fd, enum inn_locktype type, bool block);
+extern bool inn_lock_range(int fd, enum inn_locktype type, bool block,
+ off_t offset, off_t size);
+
+
+/*
+** MISCELLANEOUS UTILITY FUNCTIONS
+*/
+extern void close_on_exec(int fd, bool flag);
+extern char * concat(const char *first, ...);
+extern char * concatpath(const char *base, const char *name);
+extern void daemonize(const char *path);
+extern int getfdlimit(void);
+extern int nonblocking(int fd, bool flag);
+extern int setfdlimit(unsigned int limit);
+extern ssize_t xpwrite(int fd, const void *buffer, size_t size, off_t offset);
+extern void (*xsignal(int signum, void (*sigfunc)(int)))(int);
+extern void (*xsignal_norestart(int signum, void (*sigfunc)(int)))(int);
+extern ssize_t xwrite(int fd, const void *buffer, size_t size);
+extern ssize_t xwritev(int fd, const struct iovec *iov, int iovcnt);
+
+
+/* Headers. */
+extern char * GenerateMessageID(char *domain);
+extern void HeaderCleanFrom(char *from);
+extern struct _DDHANDLE * DDstart(FILE *FromServer, FILE *ToServer);
+extern void DDcheck(struct _DDHANDLE *h, char *group);
+extern char * DDend(struct _DDHANDLE *h);
+
+/* NNTP functions. */
+extern int NNTPlocalopen(FILE **FromServerp, FILE **ToServerp,
+ char *errbuff);
+extern int NNTPremoteopen(int port, FILE **FromServerp,
+ FILE **ToServerp, char *errbuff);
+extern int NNTPconnect(char *host, int port, FILE **FromServerp,
+ FILE **ToServerp, char *errbuff);
+extern int NNTPsendarticle(char *, FILE *F, bool Terminate);
+extern int NNTPsendpassword(char *server, FILE *FromServer,
+ FILE *ToServer);
+
+/* clientlib compatibility functions. */
+extern char * getserverbyfile(char *file);
+extern int server_init(char *host, int port);
+extern int handle_server_response(int response, char *host);
+extern void put_server(const char *text);
+extern int get_server(char *buff, int buffsize);
+extern void close_server(void);
+
+/* Opening the active file on a client. */
+extern FILE * CAopen(FILE *FromServer, FILE *ToServer);
+extern FILE * CAlistopen(FILE *FromServer, FILE *ToServer,
+ const char *request);
+extern FILE * CA_listopen(char *pathname, FILE *FromServer, FILE *ToServer,
+ const char *request);
+extern void CAclose(void);
+
+extern char * GetFQDN(char *domain);
+extern char * GetModeratorAddress(FILE *FromServer, FILE *ToServer,
+ char *group, char *moderatormailer);
+
+#define TEMPORARYOPEN 0
+#define INND_HISTORY 1
+#define INND_HISLOG 2
+#define DBZ_DIR 3
+#define DBZ_BASE 4
+
+/* Hash functions */
+typedef struct {
+ char hash[16];
+} HASH;
+extern HASH Hash(const void *value, const size_t len);
+/* Return the hash of a case mapped message-id */
+extern HASH HashMessageID(const char *MessageID);
+extern bool HashEmpty(const HASH hash);
+extern void HashClear(HASH *hash);
+extern char * HashToText(const HASH hash);
+extern HASH TextToHash(const char *text);
+extern int HashCompare(const HASH *h1, const HASH *h2);
+
+/* Miscellaneous. */
+extern int dbzneedfilecount(void);
+extern bool MakeDirectory(char *Name, bool Recurse);
+extern int xread(int fd, char *p, off_t i);
+extern int GetResourceUsage(double *usertime, double *systime);
+extern void Radix32(unsigned long, char *buff);
+extern char * ReadInDescriptor(int fd, struct stat *Sbp);
+extern char * ReadInFile(const char *name, struct stat *Sbp);
+extern FILE * xfopena(const char *p);
+extern bool fdreserve(int fdnum);
+extern FILE * Fopen(const char *p, const char *type, int fd);
+extern int Fclose(FILE *fp);
+extern char * sprint_sockaddr(const struct sockaddr *sa);
+extern void make_sin(struct sockaddr_in *s, const struct in_addr *src);
+
+END_DECLS
+
+/* <ctype.h>'s isspace includes \n, which is not what we want. */
+#define ISWHITE(c) ((c) == ' ' || (c) == '\t')
+
+#endif /* LIBINN_H */
--- /dev/null
+/* $Id: nntp.h 6070 2002-12-26 07:10:10Z rra $
+**
+** Here be a set of NNTP response codes as defined in RFC977 and elsewhere.
+** The reponse codes are three digits, RFI, defined like this:
+** R, Response:
+** 1xx Informative message
+** 2xx Command ok
+** 3xx Command ok so far, send the rest of it.
+** 4xx Command was correct, but couldn't be performed for
+** some reason.
+** 5xx Command unimplemented, or incorrect, or a serious
+** program error occurred.
+** F, Function:
+** x0x Connection, setup, and miscellaneous messages
+** x1x Newsgroup selection
+** x2x Article selection
+** x3x Distribution functions
+** x4x Posting
+** x8x Nonstandard extensions (AUTHINFO, XGTITLE)
+** x9x Debugging output
+** I, Information:
+** No defined semantics
+*/
+#define NNTP_HELPOK_VAL 100
+#define NNTP_BAD_COMMAND_VAL 500
+#define NNTP_BAD_COMMAND "500 Syntax error or bad command"
+#define NNTP_TEMPERR_VAL 503
+#define NNTP_ACCESS "502 Permission denied"
+#define NNTP_ACCESS_VAL 502
+#define NNTP_GOODBYE_ACK "205 ."
+#define NNTP_GOODBYE_ACK_VAL 205
+#define NNTP_GOODBYE "400"
+#define NNTP_GOODBYE_VAL 400
+#define NNTP_HAVEIT "435 Duplicate"
+#define NNTP_HAVEIT_BADID "435 Bad Message-ID"
+#define NNTP_HAVEIT_VAL 435
+#define NNTP_LIST_FOLLOWS "215"
+#define NNTP_LIST_FOLLOWS_VAL 215
+#define NNTP_HELP_FOLLOWS "100 Legal commands"
+#define NNTP_HELP_FOLLOWS_VAL 100
+#define NNTP_NOTHING_FOLLOWS_VAL 223
+#define NNTP_ARTICLE_FOLLOWS "220"
+#define NNTP_ARTICLE_FOLLOWS_VAL 220
+#define NNTP_NEWGROUPS_FOLLOWS_VAL 231
+#define NNTP_HEAD_FOLLOWS "221"
+#define NNTP_HEAD_FOLLOWS_VAL 221
+#define NNTP_BODY_FOLLOWS_VAL 222
+#define NNTP_OVERVIEW_FOLLOWS_VAL 224
+#define NNTP_DATE_FOLLOWS_VAL 111
+#define NNTP_POSTOK "200"
+#define NNTP_POSTOK_VAL 200
+#define NNTP_START_POST_VAL 340
+#define NNTP_NOPOSTOK_VAL 201
+#define NNTP_SLAVEOK_VAL 202
+#define NNTP_REJECTIT_VAL 437
+#define NNTP_REJECTIT_EMPTY "437 Empty article"
+#define NNTP_DONTHAVEIT "430"
+#define NNTP_DONTHAVEIT_VAL 430
+#define NNTP_RESENDIT_LATER "436 Retry later"
+#define NNTP_RESENDIT_VAL 436
+#define NNTP_POSTEDOK "240 Article posted"
+#define NNTP_POSTEDOK_VAL 240
+#define NNTP_POSTFAIL_VAL 441
+#define NNTP_GROUPOK_VAL 211
+#define NNTP_SENDIT "335"
+#define NNTP_SENDIT_VAL 335
+#define NNTP_SYNTAX_USE "501 Bad command use"
+#define NNTP_SYNTAX_VAL 501
+#define NNTP_BAD_SUBCMD "501 Bad subcommand"
+#define NNTP_TOOKIT "235"
+#define NNTP_TOOKIT_VAL 235
+#define NNTP_NOTINGROUP "412 Not in a newsgroup"
+#define NNTP_NOTINGROUP_VAL 412
+#define NNTP_NOSUCHGROUP "411 No such group"
+#define NNTP_NOSUCHGROUP_VAL 411
+#define NNTP_NEWNEWSOK "230 New news follows"
+#define NNTP_NOARTINGRP "423 Bad article number"
+#define NNTP_NOARTINGRP_VAL 423
+#define NNTP_NOCURRART "420 No current article"
+#define NNTP_NOCURRART_VAL 420
+#define NNTP_NONEXT_VAL 421
+#define NNTP_NOPREV_VAL 422
+#define NNTP_CANTPOST "440 Posting not allowed"
+#define NNTP_CANTPOST_VAL 440
+
+/* new entries for the "streaming" protocol */
+/* response to "mode stream" else 500 if stream not supported */
+#define NNTP_OK_STREAM_VAL 203 /* Streaming supported */
+
+/* response to "check <id>". Must include ID of article.
+** Example: "431 <1234@host.domain>"
+*/
+#define NNTP_OK_SENDID_VAL 238 /* I want article <id> */
+#define NNTP_RESENDID_VAL 431 /* try <id> again later */
+#define NNTP_ERR_GOTID_VAL 438 /* Got <id>, don't send */
+
+/* responses to "takethis <id>. Must include ID of article */
+#define NNTP_OK_RECID_VAL 239 /* Article <id> received OK */
+#define NNTP_ERR_FAILID_VAL 439 /* Transfer of <id> failed */
+
+/* End of new entries for the "streaming" protocol */
+
+/*
+** The first character of an NNTP reply can be used as a category class.
+*/
+#define NNTP_CLASS_OK '2'
+#define NNTP_CLASS_ERROR '4'
+#define NNTP_CLASS_FATAL '5'
+
+
+/*
+** Authentication commands from the RFC update (not official).
+*/
+#define NNTP_AUTH_NEEDED "480"
+#define NNTP_AUTH_NEEDED_VAL 480
+#define NNTP_AUTH_BAD "481"
+#define NNTP_AUTH_NEXT "381"
+#define NNTP_AUTH_NEXT_VAL 381
+#define NNTP_AUTH_OK "281"
+#define NNTP_AUTH_OK_VAL 281
+#define NNTP_AUTH_REJECT_VAL 482
+
+/*
+** Starttls commands (not official).
+*/
+#define NNTP_STARTTLS_NEXT "382"
+#define NNTP_STARTTLS_NEXT_VAL 382
+#define NNTP_STARTTLS_DONE "483"
+#define NNTP_STARTTLS_DONE_VAL 483
+#define NNTP_STARTTLS_BAD "580"
+#define NNTP_STARTTLS_BAD_VAL 580
+
+/*
+** XGTITLE, from ANU news.
+*/
+#define NNTP_XGTITLE_BAD 481 /* Yes, 481. */
+#define NNTP_XGTITLE_OK 282
+
+/*
+** MODE CANCEL extension.
+*/
+#define NNTP_OK_CANCEL_VAL 284
+#define NNTP_OK_CANCELLED "289"
+#define NNTP_ERR_CANCEL_VAL 484
+
+/*
+** XBATCH feed extension.
+*/
+#define NNTP_OK_XBATCHED_VAL 239 /* Batch transferred successfully */
+#define NNTP_OK_XBATCHED "239"
+#define NNTP_CONT_XBATCH_VAL 339 /* Continue to send batch */
+#define NNTP_CONT_XBATCH "339"
+/* and one more meaning for the 436 code NNTP_RESENDIT_VAL */
+#define NNTP_RESENDIT_XBATCHERR "436 xbatch failed: "
+/* and one more meaning for the 501 code NNTP_SYNTAX_USE */
+#define NNTP_XBATCH_BADSIZE "501 Invalid or missing size for xbatch"
+
+#define NNTP_STRLEN 512
+
+/* Consensus on the USEFOR mailing list in June of 2000 indicates that the
+ next revision of the Usenet article standard will limit the length of the
+ message ID to 250 characters. This is also the limit recommended by
+ son-of-1036.
+
+ You can increase this limit if you want, but don't increase it above 497.
+ RFC 977 limits each line of the NNTP protocol to 512 octets, including
+ the terminating CRLF. For a message ID to be passed using the TAKETHIS
+ command, it can therefore be a maximum of 501 octets. The November 1999
+ draft of the replacement RFC limits it to 497 octets.
+
+ Both Cyclone and DNews are known to reject message IDs longer than 500
+ octets as of June of 2000. DNews has been reported to have problems with
+ message IDs of 494 octets. */
+
+#define NNTP_MSGID_MAXLEN 250
--- /dev/null
+#ifndef _OV_H_
+#define _OV_H_
+
+#include "storage.h"
+#include "inn/history.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif /* __cplusplus */
+
+#define OV_READ 1
+#define OV_WRITE 2
+
+typedef enum {OVSPACE, OVSORT, OVCUTOFFLOW, OVGROUPBASEDEXPIRE, OVSTATICSEARCH, OVSTATALL, OVCACHEKEEP, OVCACHEFREE} OVCTLTYPE;
+#define OV_NOSPACE 100
+typedef enum {OVNEWSGROUP, OVARRIVED, OVNOSORT} OVSORTTYPE;
+typedef enum {OVADDCOMPLETED, OVADDFAILED, OVADDGROUPNOMATCH} OVADDRESULT;
+
+typedef struct _OVGE {
+ bool delayrm; /* append tokens to filename if true */
+ bool usepost; /* posting date is used to determine expiry
+ time if true */
+ bool quiet; /* statistics will be suppressed if true */
+ bool keep; /* keep article so long as any of crossposted
+ newsgroups is not expired if true */
+ bool earliest; /* purge article any of crossposted
+ newsgroups is expired if true */
+ bool ignoreselfexpire; /* purge article even if storing method has
+ self expiry */
+ char *filename; /* used to append tokens to this file if
+ delayrm is true */
+ time_t now; /* used as current time */
+ float timewarp; /* used to bias expiry time */
+} OVGE;
+
+extern bool OVstatall;
+bool OVopen(int mode);
+bool OVgroupstats(char *group, int *lo, int *hi, int *count, int *flag);
+bool OVgroupadd(char *group, ARTNUM lo, ARTNUM hi, char *flag);
+bool OVgroupdel(char *group);
+OVADDRESULT OVadd(TOKEN token, char *data, int len, time_t arrived, time_t expires);
+bool OVcancel(TOKEN token);
+void *OVopensearch(char *group, int low, int high);
+bool OVsearch(void *handle, ARTNUM *artnum, char **data, int *len, TOKEN *token, time_t *arrived);
+void OVclosesearch(void *handle);
+bool OVgetartinfo(char *group, ARTNUM artnum, TOKEN *token);
+bool OVexpiregroup(char *group, int *lo, struct history *h);
+bool OVctl(OVCTLTYPE type, void *val);
+void OVclose(void);
+
+/* Overview data manipulation functions. */
+const struct cvector *overview_fields(void);
+struct vector *overview_extra_fields(void);
+struct buffer *overview_build(ARTNUM number, const char *article,
+ size_t length, const struct vector *extra,
+ struct buffer *);
+bool overview_check(const char *data, size_t length, ARTNUM article);
+int overview_index(const char *field, const struct vector *extra);
+struct cvector *overview_split(const char *line, size_t length,
+ ARTNUM *number, struct cvector *vector);
+char *overview_getheader(const struct cvector *vector, int element,
+ const struct vector *extra);
+
+/* offsets into vectors for standard overview headers */
+enum {
+ OVERVIEW_SUBJECT,
+ OVERVIEW_FROM,
+ OVERVIEW_DATE,
+ OVERVIEW_MESSAGE_ID,
+ OVERVIEW_REFERENCES,
+ OVERVIEW_BYTES,
+ OVERVIEW_LINES,
+ OVERVIEW_MAX
+};
+
+#ifdef __cplusplus
+}
+#endif /* __cplusplus */
+
+#endif /* _OV_H_ */
--- /dev/null
+/* $Id: paths.h.in 4844 2001-06-30 03:54:06Z rra $ -*- c -*-
+** @configure_input@
+**
+** Here be #define's for filenames, socket names, environment variables,
+** and so on.
+*/
+
+/*
+** PATHS TO FILES AND SOCKETS
+**
+** Default prefixes can be overridden by defining the constant to a full
+** path. That magic is handled by concatpath. At some point all of these
+** defines will change to start with INN_PATH_ instead of _PATH_ because
+** identifiers beginning with an underscore and an uppercase letter are
+** reserved by the C standard. New ones should use INN_PATH_ for this
+** reason, to save eventual work.
+*/
+
+/* Default prefix path is pathbin. */
+#define _PATH_NNRPD "nnrpd"
+#define _PATH_NNTPD "nnrpd"
+#define _PATH_AUTHDIR "auth"
+#define _PATH_AUTHDIR_GENERIC "generic"
+#define _PATH_AUTHDIR_NOPASS "resolv"
+#define _PATH_AUTHDIR_PASSWD "passwd"
+#define _PATH_CTLINND "ctlinnd"
+#define _PATH_RNEWSPROGS "rnews.libexec"
+
+/* Default prefix path is pathfilter. */
+#define _PATH_TCL_STARTUP "startup.tcl"
+#define _PATH_TCL_FILTER "filter.tcl"
+#define _PATH_PERL_STARTUP_INND "startup_innd.pl"
+#define _PATH_PERL_FILTER_INND "filter_innd.pl"
+#define _PATH_PERL_FILTER_NNRPD "filter_nnrpd.pl"
+#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"
+#define _PATH_NEWSCONTROL "control"
+#define _PATH_TEMPSOCK "ctlinndXXXXXX"
+#define _PATH_SERVERPID "innd.pid"
+
+/* Default prefix path is pathdb. */
+#define _PATH_HISTORY "history"
+#define _PATH_ACTIVE "active"
+#define _PATH_NEWACTIVE "active.tmp"
+#define _PATH_OLDACTIVE "active.old"
+#define _PATH_ACTIVETIMES "active.times"
+#define _PATH_NEWSGROUPS "newsgroups"
+
+/* Default prefix path is pathetc. */
+#define _PATH_NEWSFEEDS "newsfeeds"
+#define _PATH_INNDHOSTS "incoming.conf"
+#define _PATH_DISTPATS "distrib.pats"
+#define _PATH_NNRPDIST "distributions"
+#define _PATH_NNRPSUBS "subscriptions"
+#define _PATH_CONFIG "@ETCDIR@/inn.conf"
+#define _PATH_CLIENTACTIVE "active"
+#define _PATH_MODERATORS "moderators"
+#define _PATH_SERVER "server"
+#define _PATH_NNTPPASS "passwd.nntp"
+#define _PATH_NNRPACCESS "readers.conf"
+#define _PATH_EXPIRECTL "expire.ctl"
+#define _PATH_SCHEMA "overview.fmt"
+#define _PATH_MOTD "motd.news"
+#define _PATH_STORAGECTL "storage.conf"
+#define _PATH_RADIUS_CONFIG "radius.conf"
+#define _PATH_SASL_CONFIG "sasl.conf"
+#define INN_PATH_FILESYSTEMS "filesystems"
+
+/* Default prefix path is pathspool. */
+#define _PATH_SPOOL "articles"
+#define _PATH_BADNEWS "bad"
+
+/* Default prefix path is pathlog. */
+#define _PATH_LOGFILE "news"
+#define _PATH_ERRLOG "errlog"
+
+/* Paths to various programs. */
+#define _PATH_COMPRESS "@COMPRESS@"
+#define _PATH_GZIP "@GZIP@"
+#define _PATH_SH "@_PATH_SH@"
+#define _PATH_SORT "@_PATH_SORT@"
+
+/* Absolute paths. */
+#define _PATH_TMP "@tmpdir@"
+#define _PATH_RNEWS_DUP_LOG "/dev/null"
+
+/* Always relative to pathtmp. */
+#define _PATH_TEMPACTIVE "activeXXXXXX"
+#define _PATH_TEMPMODERATORS "moderatorsXXXXXX"
+
+/*
+** ENVIRONMENT VARIABLES
+*/
+
+/* The host name of the NNTP server, for client posting. */
+#define _ENV_NNTPSERVER "NNTPSERVER"
+
+/* The Organization header line, for client posting. */
+#define _ENV_ORGANIZATION "ORGANIZATION"
+
+/* What to put in the From line, for client posting. */
+#define _ENV_FROMHOST "FROMHOST"
+
+/* UUCP host, for rnews. */
+#define _ENV_UUCPHOST "UU_MACHINE"
+
+/* Interface to bind as, for sockets. */
+#define _ENV_INNBINDADDR "INND_BIND_ADDRESS"
--- /dev/null
+/* $Id: mmap.h 6012 2002-12-16 11:19:21Z alexk $
+**
+** Portability wrapper around <sys/mman.h>.
+**
+** This header file includes <sys/mman.h> and then sets up various
+** additional defines and macros to permit a uniform API across platforms
+** with varying mmap implementations.
+*/
+
+#ifndef PORTABLE_MMAP_H
+#define PORTABLE_MMAP_H 1
+
+#include "config.h"
+#include <sys/mman.h>
+
+/* Make sure that the symbolic constant for the error return from mmap is
+ defined (some platforms don't define it). */
+#ifndef MAP_FAILED
+# define MAP_FAILED ((void *) -1)
+#endif
+
+/* Solaris 8 (at least) prototypes munmap, msync, and madvise as taking char *
+ (actually a caddr_t, which is a typedef for a char *) instead of void * as
+ is required by the standard. This macro adds a cast that silences compiler
+ warnings on Solaris 8 without adversely affecting other platforms. (ISO C
+ allows macro definitions of this sort; this macro is not recursive.) */
+#define munmap(p, l) munmap((void *)(p), (l))
+
+/* On some platforms, msync only takes two arguments. (ANSI C allows macro
+ definitions of this sort; this macro is not recursive.) */
+#if HAVE_MSYNC_3_ARG
+# define msync(p, l, f) msync((void *)(p), (l), (f))
+#else
+# define msync(p, l, f) msync((void *)(p), (l))
+#endif
+
+/* Turn calls to madvise into a no-op if that call isn't available. */
+#if HAVE_MADVISE
+# define madvise(p, l, o) madvise((void *)(p), (l), (o))
+#else
+# define madvise(p, l, o) /* empty */
+#endif
+
+/* Some platforms don't flush data written to a memory mapped region until
+ msync or munmap; on those platforms, we sometimes need to force an msync
+ so that other parts of INN will see the changed data. Some other
+ platforms don't see writes to a file that's memory-mapped until the
+ memory mappings have been flushed.
+
+ These platforms can use mmap_flush to schedule a flush to disk and
+ mmap_invalidate to force re-reading from disk. Note that all platforms
+ should still periodically call msync so that data is written out in case
+ of a crash. */
+#if MMAP_NEEDS_MSYNC
+# define mmap_flush(p, l) msync((p), (l), MS_ASYNC)
+#else
+# define mmap_flush(p, l) /* empty */
+#endif
+
+#if MMAP_MISSES_WRITES
+# define mmap_invalidate(p, l) msync((p), (l), MS_INVALIDATE)
+#else
+# define mmap_invalidate(p, l) /* empty */
+#endif
+
+#endif /* PORTABLE_MMAP_H */
--- /dev/null
+/* $Id: setproctitle.h 5682 2002-08-29 05:17:56Z rra $
+**
+** Set things up for setproctitle portably.
+**
+** If the system supports setproctitle, we need to define away
+** setproctitle_init. Otherwise, we have to prototype setproctitle (which is
+** normally prototyped in stdlib.h).
+*/
+
+#ifndef PORTABLE_SETPROCTITLE_H
+#define PORTABLE_SETPROCTITLE_H 1
+
+#include "config.h"
+
+#if !HAVE_SETPROCTITLE
+void setproctitle(const char *format, ...);
+#endif
+
+#if HAVE_SETPROCTITLE || HAVE_PSTAT
+# define setproctitle_init(argc, argv) /* empty */
+#else
+void setproctitle_init(int argc, char *argv[]);
+#endif
+
+#endif /* !PORTABLE_SETPROCTITLE_H */
--- /dev/null
+/* $Id: socket.h 5388 2002-04-01 07:03:01Z rra $
+**
+** Portability wrapper around <sys/socket.h> and friends.
+**
+** This header file is the equivalent of:
+**
+** #include <arpa/inet.h>
+** #include <netinet/in.h>
+** #include <sys/socket.h>
+**
+** but also cleans up various messes, mostly related to IPv6 support. It
+** ensures that inet_aton and inet_ntoa are available and properly
+** prototyped.
+*/
+
+#ifndef PORTABLE_SOCKET_H
+#define PORTABLE_SOCKET_H 1
+
+#include "config.h"
+#include <sys/types.h>
+
+/* BSDI needs <netinet/in.h> before <arpa/inet.h>. */
+#include <netinet/in.h>
+#include <arpa/inet.h>
+#include <sys/socket.h>
+
+/* Provide prototypes for inet_aton and inet_ntoa if not prototyped in the
+ system header files since they're occasionally available without proper
+ prototypes. */
+#if NEED_DECLARATION_INET_ATON
+extern int inet_aton(const char *, struct in_addr *);
+#endif
+#if NEED_DECLARATION_INET_NTOA
+extern const char * inet_ntoa(const struct in_addr);
+#endif
+
+/* Defined by RFC 2553, used to store a generic address. Note that this
+ doesn't do the alignment mangling that RFC 2553 does; it's not clear if
+ that should be added.... */
+#if !HAVE_SOCKADDR_STORAGE
+# if HAVE_SOCKADDR_LEN
+struct sockaddr_storage {
+ unsigned char ss_len;
+ unsigned char ss_family;
+ unsigned char __padding[128 - 2];
+};
+# else
+struct sockaddr_storage {
+ unsigned short ss_family;
+ unsigned char __padding[128 - 2];
+};
+# endif
+#endif
+
+/* Use convenient, non-uglified names for the fields since we use them quite a
+ bit in code. */
+#if HAVE_2553_STYLE_SS_FAMILY
+# define ss_family __ss_family
+# define ss_len __ss_len
+#endif
+
+/* Define an SA_LEN macro that gives us the length of a sockaddr. */
+#if !HAVE_SA_LEN_MACRO
+# if HAVE_SOCKADDR_LEN
+# define SA_LEN(s) ((s)->sa_len)
+# else
+/* Hack courtesy of the USAGI project. */
+# if HAVE_INET6
+# define SA_LEN(s) \
+ ((((struct sockaddr *)(s))->sa_family == AF_INET6) \
+ ? sizeof(struct sockaddr_in6) \
+ : ((((struct sockaddr *)(s))->sa_family == AF_INET) \
+ ? sizeof(struct sockaddr_in) \
+ : sizeof(struct sockaddr)))
+# else
+# define SA_LEN(s) \
+ ((((struct sockaddr *)(s))->sa_family == AF_INET) \
+ ? sizeof(struct sockaddr_in) \
+ : sizeof(struct sockaddr))
+# endif
+# endif /* HAVE_SOCKADDR_LEN */
+#endif /* !HAVE_SA_LEN_MACRO */
+
+#endif /* PORTABLE_SOCKET_H */
--- /dev/null
+/* $Id: time.h 4020 2000-10-03 01:27:13Z rra $
+**
+** Portability wrapper around <time.h> and <sys/time.h>.
+**
+** This header includes <time.h> and <sys/time.h> as applicable, handling
+** systems where one can't include both headers (per the autoconf manual).
+*/
+
+#ifndef PORTABLE_TIME_H
+#define PORTABLE_TIME_H 1
+
+#include "config.h"
+
+#if TIME_WITH_SYS_TIME
+# include <sys/time.h>
+# include <time.h>
+#else
+# if HAVE_SYS_TIME_H
+# include <sys/time.h>
+# else
+# include <time.h>
+# endif
+#endif
+
+#endif /* PORTABLE_TIME_H */
--- /dev/null
+/* $Id: wait.h 5398 2002-04-09 07:21:58Z rra $
+**
+** Portability wrapper around <sys/wait.h>.
+**
+** This header includes <sys/wait.h> if it's available, and then makes sure
+** that the standard wait macros are defined and defines them if they
+** aren't.
+*/
+
+#ifndef PORTABLE_WAIT_H
+#define PORTABLE_WAIT_H 1
+
+#include "config.h"
+
+#ifdef HAVE_SYS_WAIT_H
+# include <sys/wait.h>
+#endif
+
+/* Per the autoconf documentation, just always check to see if the various
+ macros are defined and define them ourselves if they aren't. These
+ definitions are based on the approach taken by BSDI. */
+#ifndef WCOREDUMP
+# define WCOREDUMP(status) ((unsigned)(status) & 0x80)
+#endif
+#ifndef WEXITSTATUS
+# define WEXITSTATUS(status) (((unsigned)(status) >> 8) & 0xff)
+#endif
+#ifndef WTERMSIG
+# define WTERMSIG(status) ((unsigned)(status) & 0x7f)
+#endif
+#ifndef WIFEXITED
+# define WIFEXITED(status) (((unsigned)(status) & 0xff) == 0)
+#endif
+#ifndef WIFSTOPPED
+# define WIFSTOPPED(status) (((unsigned)(status) & 0xff) == 0x7f)
+#endif
+#ifndef WIFSIGNALED
+# define WIFSIGNALED(status) (!WIFSTOPPED(status) && !WIFEXITED(status))
+#endif
+
+#endif /* PORTABLE_WAIT_H */
--- /dev/null
+
+#ifndef PPPORT_H
+#define PPPORT_H 1
+
+/* Perl/Pollution/Portability Version 1.0003 */
+
+/* Copyright (C) 1999, Kenneth Albanowski. This code may be used and
+ distributed under the same license as any version of Perl. */
+
+/* For the latest version of this code, please contact the author at
+ <kjahds@kjahds.com>, or check with the Perl maintainers. */
+
+/* If you needed to customize this file for your project, please mention
+ your changes. */
+
+/*
+ Modified for Perl 5.6.0 by Russ Allbery (use PERL_VERSION instead of
+ PERL_PATCHLEVEL).
+*/
+
+
+/*
+ In order for a Perl extension module to be as portable as possible
+ across differing versions of Perl itself, certain steps need to be taken.
+ Including this header is the first major one, then using dTHR is all the
+ appropriate places and using a PL_ prefix to refer to global Perl
+ variables is the second.
+*/
+
+
+/* If you use one of a few functions that were not present in earlier
+ versions of Perl, please add a define before the inclusion of ppport.h
+ for a static include, or use the GLOBAL request in a single module to
+ produce a global definition that can be referenced from the other
+ modules.
+
+ Function: Static define: Extern define:
+ newCONSTSUB() NEED_newCONSTSUB NEED_newCONSTSUB_GLOBAL
+
+*/
+
+
+/* To verify whether ppport.h is needed for your module, and whether any
+ special defines should be used, ppport.h can be run through Perl to check
+ your source code. Simply say:
+
+ perl -x ppport.h *.c *.h *.xs foo/perl.c [etc]
+
+ The result will be a list of patches suggesting changes that should at
+ least be acceptable, if not necessarily the most efficient solution, or a
+ fix for all possible problems. It won't catch where dTHR is needed, and
+ doesn't attempt to account for global macro or function definitions,
+ nested includes, typemaps, etc.
+
+ In order to test for the need of dTHR, please try your module under a
+ recent version of Perl that has threading compiled-in.
+
+*/
+
+
+/*
+#!/usr/bin/perl
+@ARGV = ("*.xs") if !@ARGV;
+%badmacros = %funcs = %macros = ();
+foreach (<DATA>) {
+ $funcs{$1} = 1 if /Provide:\s+(\S+)/;
+ $macros{$1} = 1 if /^#\s*define\s+([a-zA-Z0-9_]+)/;
+ $badmacros{$2}=$1 if /^#\s*define\s+(PL_\S+)\s+(\S+)/;
+}
+foreach $filename (map(glob($_),@ARGV)) {
+ unless (open(IN, "<$filename")) {
+ warn "Unable to read from $file: $!\n";
+ next;
+ }
+ print "Scanning $filename...\n";
+ $c = ""; while (<IN>) { $c .= $_; } close(IN);
+ $need_include = 0; %add_func = (); $changes = 0;
+ $has_include = ($c =~ /#.*include.*ppport/m);
+
+ foreach $func (keys %funcs) {
+ if ($c =~ /#.*define.*\bNEED_$func(_GLOBAL)?\b/m) {
+ if ($c !~ /\b$func\b/m) {
+ print "If $func isn't needed, you don't need to request it.\n" if
+ $changes += ($c =~ s/^.*#.*define.*\bNEED_$func\b.*\n//m);
+ } else {
+ print "Uses $func\n";
+ $need_include = 1;
+ }
+ } else {
+ if ($c =~ /\b$func\b/m) {
+ $add_func{$func} =1 ;
+ print "Uses $func\n";
+ $need_include = 1;
+ }
+ }
+ }
+
+ if (not $need_include) {
+ foreach $macro (keys %macros) {
+ if ($c =~ /\b$macro\b/m) {
+ print "Uses $macro\n";
+ $need_include = 1;
+ }
+ }
+ }
+
+ foreach $badmacro (keys %badmacros) {
+ if ($c =~ /\b$badmacro\b/m) {
+ $changes += ($c =~ s/\b$badmacro\b/$badmacros{$badmacro}/gm);
+ print "Uses $badmacros{$badmacro} (instead of $badmacro)\n";
+ $need_include = 1;
+ }
+ }
+
+ if (scalar(keys %add_func) or $need_include != $has_include) {
+ if (!$has_include) {
+ $inc = join('',map("#define NEED_$_\n", sort keys %add_func)).
+ "#include \"ppport.h\"\n";
+ $c = "$inc$c" unless $c =~ s/#.*include.*XSUB.*\n/$&$inc/m;
+ } elsif (keys %add_func) {
+ $inc = join('',map("#define NEED_$_\n", sort keys %add_func));
+ $c = "$inc$c" unless $c =~ s/^.*#.*include.*ppport.*$/$inc$&/m;
+ }
+ if (!$need_include) {
+ print "Doesn't seem to need ppport.h.\n";
+ $c =~ s/^.*#.*include.*ppport.*\n//m;
+ }
+ $changes++;
+ }
+
+ if ($changes) {
+ open(OUT,">/tmp/ppport.h.$$");
+ print OUT $c;
+ close(OUT);
+ open(DIFF, "diff -u $filename /tmp/ppport.h.$$|");
+ while (<DIFF>) { s!/tmp/ppport\.h\.$$!$filename.patched!; print STDOUT; }
+ close(DIFF);
+ unlink("/tmp/ppport.h.$$");
+ } else {
+ print "Looks OK\n";
+ }
+}
+__DATA__
+*/
+
+
+#if !defined(PERL_VERSION) && !defined(PERL_PATCHLEVEL)
+# ifndef __PATCHLEVEL_H_INCLUDED__
+# include <patchlevel.h>
+# endif
+#endif
+#ifndef PERL_VERSION
+# ifdef PERL_PATCHLEVEL
+# define PERL_VERSION PERL_PATCHLEVEL
+# else
+# define PERL_VERSION PATCHLEVEL
+# define PERL_SUBVERSION SUBVERSION
+# endif
+#endif
+
+#ifndef ERRSV
+# define ERRSV perl_get_sv("@",false)
+#endif
+
+#if (PERL_VERSION < 4) || ((PERL_VERSION == 4) && (PERL_SUBVERSION <= 4))
+# define PL_sv_undef sv_undef
+# define PL_sv_yes sv_yes
+# define PL_sv_no sv_no
+# define PL_na na
+# define PL_stdingv stdingv
+# define PL_hints hints
+# define PL_curcop curcop
+# define PL_curstash curstash
+# define PL_copline copline
+#endif
+
+#if (PERL_VERSION < 5)
+# undef dTHR
+# ifdef WIN32
+# define dTHR extern int Perl___notused
+# else
+# define dTHR extern int errno
+# endif
+#endif
+
+#ifndef boolSV
+# define boolSV(b) ((b) ? &PL_sv_yes : &PL_sv_no)
+#endif
+
+/* Perl tries to export a bunch of its own functions. Mutter. */
+#undef die
+#undef list
+#undef warn
+
+#endif /* !PPPORT_H */
--- /dev/null
+/* $Id: storage.h 5933 2002-12-07 09:47:17Z rra $
+**
+** Here be declarations related to the storage subsystem.
+*/
+
+#ifndef __STORAGE_H__
+#define __STORAGE_H__
+
+/* We've probably already included this; only include it if we need it. */
+#ifndef __CONFIG_H__
+# include "config.h"
+#endif
+
+#include <stdio.h>
+#include <sys/types.h>
+
+#define STORAGE_TOKEN_LENGTH 16
+
+/* This is the type of an empty token. Tokens with this type will be
+ returned when errors occur */
+#define TOKEN_EMPTY 255
+
+typedef enum {RETR_ALL, RETR_HEAD, RETR_BODY, RETR_STAT} RETRTYPE;
+typedef enum {SM_RDWR, SM_PREOPEN} SMSETUP;
+
+#define NUM_STORAGE_CLASSES 256
+typedef unsigned char STORAGECLASS;
+typedef unsigned char STORAGETYPE;
+
+typedef struct token {
+ STORAGETYPE type;
+ STORAGECLASS class;
+ char token[STORAGE_TOKEN_LENGTH];
+} TOKEN;
+
+typedef struct {
+ unsigned char type; /* Method that retrieved the article */
+ const char *data; /* Where the requested data starts */
+ struct iovec *iov; /* writev() style vector */
+ int iovcnt; /* writev() style count */
+ size_t len; /* Length of the requested data */
+ unsigned char nextmethod; /* Next method to try when iterating over the
+ spool */
+ void *private; /* A pointer to method specific data */
+ time_t arrived; /* The time when the article arrived */
+ time_t expires; /* The time when the article will be expired */
+ char *groups; /* Where Newsgroups header starts */
+ int groupslen; /* Length of Newsgroups header */
+ TOKEN *token; /* A pointer to the article's TOKEN */
+} ARTHANDLE;
+
+#define SMERR_NOERROR 0
+#define SMERR_INTERNAL 1
+#define SMERR_UNDEFINED 2
+#define SMERR_NOENT 3
+#define SMERR_TOKENSHORT 4
+#define SMERR_NOBODY 5
+#define SMERR_UNINIT 6
+#define SMERR_CONFIG 7
+#define SMERR_BADHANDLE 8
+#define SMERR_BADTOKEN 9
+#define SMERR_NOMATCH 10
+
+extern int SMerrno;
+extern char *SMerrorstr;
+
+typedef enum {SELFEXPIRE, SMARTNGNUM, EXPENSIVESTAT} PROBETYPE;
+typedef enum {SM_ALL, SM_HEAD, SM_CANCELEDART} FLUSHTYPE;
+
+struct artngnum {
+ char *groupname;
+ ARTNUM artnum;
+};
+
+BEGIN_DECLS
+
+char * TokenToText(const TOKEN token);
+TOKEN TextToToken(const char *text);
+bool IsToken(const char *text);
+char * ToWireFmt(const char *article, size_t len, size_t *newlen);
+char * FromWireFmt(const char *article, size_t len, size_t *newlen);
+
+bool SMsetup(SMSETUP type, void *value);
+bool SMinit(void);
+TOKEN SMstore(const ARTHANDLE article);
+ARTHANDLE * SMretrieve(const TOKEN token, const RETRTYPE amount);
+ARTHANDLE * SMnext(const ARTHANDLE *article, const RETRTYPE amount);
+void SMfreearticle(ARTHANDLE *article);
+bool SMcancel(TOKEN token);
+bool SMprobe(PROBETYPE type, TOKEN *token, void *value);
+bool SMflushcacheddata(FLUSHTYPE type);
+void SMprintfiles(FILE *file, TOKEN token, char **xref, int ngroups);
+void SMshutdown(void);
+
+END_DECLS
+
+#endif
--- /dev/null
+## $Id: Makefile 7837 2008-05-19 17:14:15Z iulius $
+
+include ../Makefile.global
+
+top = ..
+CFLAGS = $(GCFLAGS) $(TCLINC)
+
+ALL = innd inndstart
+
+SOURCES = art.c cc.c chan.c icd.c innd.c inndstart.c keywords.c lc.c \
+ nc.c newsfeeds.c ng.c perl.c proc.c python.c rc.c site.c \
+ status.c tcl.c util.c wip.c
+
+# The objects that are linked into innd. All SOURCES except inndstart.
+OBJECTS = art.o cc.o chan.o icd.o innd.o keywords.o lc.o nc.o \
+ newsfeeds.o ng.o perl.o proc.o python.o rc.o site.o \
+ status.o tcl.o util.o wip.o
+
+all: $(ALL)
+
+warnings:
+ $(MAKE) COPT='$(WARNINGS)' all
+
+install: all
+ $(LI_XPRI) innd $D$(PATHBIN)/innd
+ @ME=`$(WHOAMI)` ; \
+ if [ x"$$ME" = xroot ] ; then \
+ echo $(LI_SPRI) inndstart $D$(PATHBIN)/inndstart ; \
+ $(LI_SPRI) inndstart $D$(PATHBIN)/inndstart ; \
+ else \
+ echo $(LI_XPRI) inndstart $D$(PATHBIN)/inndstart ; \
+ $(LI_XPRI) inndstart $D$(PATHBIN)/inndstart ; \
+ echo '' ; \
+ echo '========================' ; \
+ echo 'NOTE NOTE NOTE NOTE NOTE' ; \
+ ls -l $D$(PATHBIN)/inndstart ; \
+ echo '$D$(PATHBIN)/inndstart needs to be installed setuid root' ; \
+ echo '' ; echo ; \
+ fi
+
+clean:
+ rm -f *.o $(ALL) inndp profiled
+ rm -rf .libs
+
+clobber distclean: clean
+ rm -f tags
+
+tags ctags: $(SOURCES)
+ $(CTAGS) $(SOURCES) ../lib/*.c innd.h ../include/*.h
+
+
+## Compilation rules.
+
+INNDLIBS = $(LIBSTORAGE) $(LIBHIST) $(LIBINN) $(EXTSTORAGELIBS) \
+ $(PERLLIB) $(TCLLIB) $(PYTHONLIB) $(REGEXLIB) $(LIBS)
+
+perl.o: perl.c ; $(CC) $(CFLAGS) $(PERLINC) -c perl.c
+python.o: python.c ; $(CC) $(CFLAGS) $(PYTHONINC) -c python.c
+
+innd: $(OBJECTS) $(LIBSTORAGE) $(LIBHIST) $(LIBINN)
+ $(LIBLD) $(LDFLAGS) -o $@ $(OBJECTS) $(INNDLIBS)
+
+inndstart: inndstart.o $(LIBINN)
+ $(LIBLD) $(LDFLAGS) -o $@ inndstart.o $(LIBINN) $(LIBS)
+
+$(LIBINN): ; (cd ../lib ; $(MAKE))
+$(LIBSTORAGE): ; (cd ../storage ; $(MAKE))
+$(LIBHIST): ; (cd ../history ; $(MAKE))
+
+
+## Profiling. These rules have not been checked for a while and may need
+## some work.
+
+profiled: inndp
+ date >$@
+
+inndp: $(SOURCES)
+ rm -f $(OBJECTS)
+ $(MAKEPROFILING) innd
+ mv innd inndp
+ rm -f $(OBJECTS)
+
+
+## Dependencies. Default list, below, is probably good enough.
+
+depend: Makefile $(SOURCES)
+ $(MAKEDEPEND) '$(CFLAGS) $(PERLINC) $(PYTHONINC) $(TCLINC)' $(SOURCES)
+
+# DO NOT DELETE THIS LINE -- make depend depends on it.
+art.o: art.c ../include/config.h ../include/inn/defines.h \
+ ../include/inn/system.h ../include/clibrary.h ../include/config.h \
+ ../include/inn/innconf.h ../include/inn/defines.h ../include/inn/wire.h \
+ ../include/inn/md5.h innd.h ../include/portable/time.h \
+ ../include/config.h ../include/portable/socket.h \
+ ../include/inn/buffer.h ../include/inn/history.h \
+ ../include/inn/messages.h ../include/inn/timer.h ../include/libinn.h \
+ ../include/nntp.h ../include/paths.h ../include/storage.h \
+ ../include/ov.h ../include/storage.h ../include/inn/history.h
+cc.o: cc.c ../include/config.h ../include/inn/defines.h \
+ ../include/inn/system.h ../include/clibrary.h ../include/config.h \
+ ../include/inn/innconf.h ../include/inn/defines.h ../include/inn/qio.h \
+ innd.h ../include/portable/time.h ../include/config.h \
+ ../include/portable/socket.h ../include/inn/buffer.h \
+ ../include/inn/history.h ../include/inn/messages.h \
+ ../include/inn/timer.h ../include/libinn.h ../include/nntp.h \
+ ../include/paths.h ../include/storage.h ../include/inndcomm.h \
+ ../include/innperl.h
+chan.o: chan.c ../include/config.h ../include/inn/defines.h \
+ ../include/inn/system.h ../include/clibrary.h ../include/config.h \
+ ../include/inn/innconf.h ../include/inn/defines.h innd.h \
+ ../include/portable/time.h ../include/config.h \
+ ../include/portable/socket.h ../include/inn/buffer.h \
+ ../include/inn/history.h ../include/inn/messages.h \
+ ../include/inn/timer.h ../include/libinn.h ../include/nntp.h \
+ ../include/paths.h ../include/storage.h
+icd.o: icd.c ../include/config.h ../include/inn/defines.h \
+ ../include/inn/system.h ../include/clibrary.h ../include/config.h \
+ ../include/portable/mmap.h ../include/config.h ../include/inn/innconf.h \
+ ../include/inn/defines.h innd.h ../include/portable/time.h \
+ ../include/portable/socket.h ../include/inn/buffer.h \
+ ../include/inn/history.h ../include/inn/messages.h \
+ ../include/inn/timer.h ../include/libinn.h ../include/nntp.h \
+ ../include/paths.h ../include/storage.h ../include/ov.h \
+ ../include/storage.h ../include/inn/history.h
+innd.o: innd.c ../include/config.h ../include/inn/defines.h \
+ ../include/inn/system.h ../include/clibrary.h ../include/config.h \
+ ../include/inn/innconf.h ../include/inn/defines.h \
+ ../include/inn/messages.h ../include/innperl.h innd.h \
+ ../include/portable/time.h ../include/config.h \
+ ../include/portable/socket.h ../include/inn/buffer.h \
+ ../include/inn/history.h ../include/inn/timer.h ../include/libinn.h \
+ ../include/nntp.h ../include/paths.h ../include/storage.h \
+ ../include/ov.h ../include/storage.h ../include/inn/history.h
+inndstart.o: inndstart.c ../include/config.h ../include/inn/defines.h \
+ ../include/inn/system.h ../include/clibrary.h ../include/config.h \
+ ../include/portable/socket.h ../include/config.h \
+ ../include/inn/innconf.h ../include/inn/defines.h \
+ ../include/inn/messages.h ../include/libinn.h ../include/paths.h
+keywords.o: keywords.c ../include/config.h ../include/inn/defines.h \
+ ../include/inn/system.h ../include/clibrary.h ../include/config.h \
+ ../include/libinn.h ../include/inn/innconf.h ../include/inn/defines.h \
+ innd.h ../include/portable/time.h ../include/config.h \
+ ../include/portable/socket.h ../include/inn/buffer.h \
+ ../include/inn/history.h ../include/inn/messages.h \
+ ../include/inn/timer.h ../include/nntp.h ../include/paths.h \
+ ../include/storage.h
+lc.o: lc.c ../include/config.h ../include/inn/defines.h \
+ ../include/inn/system.h ../include/clibrary.h ../include/config.h \
+ ../include/inn/innconf.h ../include/inn/defines.h innd.h \
+ ../include/portable/time.h ../include/config.h \
+ ../include/portable/socket.h ../include/inn/buffer.h \
+ ../include/inn/history.h ../include/inn/messages.h \
+ ../include/inn/timer.h ../include/libinn.h ../include/nntp.h \
+ ../include/paths.h ../include/storage.h
+nc.o: nc.c ../include/config.h ../include/inn/defines.h \
+ ../include/inn/system.h ../include/clibrary.h ../include/config.h \
+ ../include/inn/innconf.h ../include/inn/defines.h innd.h \
+ ../include/portable/time.h ../include/config.h \
+ ../include/portable/socket.h ../include/inn/buffer.h \
+ ../include/inn/history.h ../include/inn/messages.h \
+ ../include/inn/timer.h ../include/libinn.h ../include/nntp.h \
+ ../include/paths.h ../include/storage.h
+newsfeeds.o: newsfeeds.c ../include/config.h ../include/inn/defines.h \
+ ../include/inn/system.h ../include/clibrary.h ../include/config.h \
+ ../include/inn/innconf.h ../include/inn/defines.h innd.h \
+ ../include/portable/time.h ../include/config.h \
+ ../include/portable/socket.h ../include/inn/buffer.h \
+ ../include/inn/history.h ../include/inn/messages.h \
+ ../include/inn/timer.h ../include/libinn.h ../include/nntp.h \
+ ../include/paths.h ../include/storage.h
+ng.o: ng.c ../include/config.h ../include/inn/defines.h \
+ ../include/inn/system.h ../include/clibrary.h ../include/config.h \
+ ../include/inn/innconf.h ../include/inn/defines.h innd.h \
+ ../include/portable/time.h ../include/config.h \
+ ../include/portable/socket.h ../include/inn/buffer.h \
+ ../include/inn/history.h ../include/inn/messages.h \
+ ../include/inn/timer.h ../include/libinn.h ../include/nntp.h \
+ ../include/paths.h ../include/storage.h ../include/ov.h \
+ ../include/storage.h ../include/inn/history.h
+perl.o: perl.c ../include/config.h ../include/inn/defines.h \
+ ../include/inn/system.h
+proc.o: proc.c ../include/config.h ../include/inn/defines.h \
+ ../include/inn/system.h ../include/clibrary.h ../include/config.h \
+ ../include/portable/wait.h ../include/config.h innd.h \
+ ../include/portable/time.h ../include/portable/socket.h \
+ ../include/inn/buffer.h ../include/inn/defines.h \
+ ../include/inn/history.h ../include/inn/messages.h \
+ ../include/inn/timer.h ../include/libinn.h ../include/nntp.h \
+ ../include/paths.h ../include/storage.h
+python.o: python.c ../include/config.h ../include/inn/defines.h \
+ ../include/inn/system.h ../include/clibrary.h ../include/config.h \
+ ../include/inn/innconf.h ../include/inn/defines.h innd.h \
+ ../include/portable/time.h ../include/config.h \
+ ../include/portable/socket.h ../include/inn/buffer.h \
+ ../include/inn/history.h ../include/inn/messages.h \
+ ../include/inn/timer.h ../include/libinn.h ../include/nntp.h \
+ ../include/paths.h ../include/storage.h
+rc.o: rc.c ../include/config.h ../include/inn/defines.h \
+ ../include/inn/system.h ../include/clibrary.h ../include/config.h \
+ ../include/portable/socket.h ../include/config.h \
+ ../include/inn/innconf.h ../include/inn/defines.h \
+ ../include/inn/vector.h innd.h ../include/portable/time.h \
+ ../include/inn/buffer.h ../include/inn/history.h \
+ ../include/inn/messages.h ../include/inn/timer.h ../include/libinn.h \
+ ../include/nntp.h ../include/paths.h ../include/storage.h
+site.o: site.c ../include/config.h ../include/inn/defines.h \
+ ../include/inn/system.h ../include/clibrary.h ../include/config.h \
+ ../include/inn/innconf.h ../include/inn/defines.h innd.h \
+ ../include/portable/time.h ../include/config.h \
+ ../include/portable/socket.h ../include/inn/buffer.h \
+ ../include/inn/history.h ../include/inn/messages.h \
+ ../include/inn/timer.h ../include/libinn.h ../include/nntp.h \
+ ../include/paths.h ../include/storage.h
+status.o: status.c ../include/config.h ../include/inn/defines.h \
+ ../include/inn/system.h ../include/clibrary.h ../include/config.h \
+ ../include/portable/socket.h ../include/config.h \
+ ../include/inn/innconf.h ../include/inn/defines.h innd.h \
+ ../include/portable/time.h ../include/inn/buffer.h \
+ ../include/inn/history.h ../include/inn/messages.h \
+ ../include/inn/timer.h ../include/libinn.h ../include/nntp.h \
+ ../include/paths.h ../include/storage.h ../include/innperl.h
+tcl.o: tcl.c ../include/config.h ../include/inn/defines.h \
+ ../include/inn/system.h ../include/clibrary.h ../include/config.h \
+ ../include/inn/innconf.h ../include/inn/defines.h innd.h \
+ ../include/portable/time.h ../include/config.h \
+ ../include/portable/socket.h ../include/inn/buffer.h \
+ ../include/inn/history.h ../include/inn/messages.h \
+ ../include/inn/timer.h ../include/libinn.h ../include/nntp.h \
+ ../include/paths.h ../include/storage.h
+util.o: util.c ../include/config.h ../include/inn/defines.h \
+ ../include/inn/system.h ../include/clibrary.h ../include/config.h \
+ ../include/inn/innconf.h ../include/inn/defines.h ../include/libinn.h \
+ innd.h ../include/portable/time.h ../include/config.h \
+ ../include/portable/socket.h ../include/inn/buffer.h \
+ ../include/inn/history.h ../include/inn/messages.h \
+ ../include/inn/timer.h ../include/nntp.h ../include/paths.h \
+ ../include/storage.h
+wip.o: wip.c ../include/config.h ../include/inn/defines.h \
+ ../include/inn/system.h ../include/clibrary.h ../include/config.h \
+ ../include/inn/innconf.h ../include/inn/defines.h innd.h \
+ ../include/portable/time.h ../include/config.h \
+ ../include/portable/socket.h ../include/inn/buffer.h \
+ ../include/inn/history.h ../include/inn/messages.h \
+ ../include/inn/timer.h ../include/libinn.h ../include/nntp.h \
+ ../include/paths.h ../include/storage.h
--- /dev/null
+/* $Id: art.c 7748 2008-04-06 13:49:56Z iulius $
+**
+** Article-processing.
+*/
+
+#include "config.h"
+#include "clibrary.h"
+#include <sys/uio.h>
+
+#include "inn/innconf.h"
+#include "inn/wire.h"
+#include "inn/md5.h"
+#include "innd.h"
+#include "ov.h"
+#include "storage.h"
+
+typedef struct iovec IOVEC;
+
+#define ARTIOVCNT 16
+
+extern bool DoCancels;
+
+#if defined(S_IXUSR)
+#define EXECUTE_BITS (S_IXUSR | S_IXGRP | S_IXOTH)
+#else
+#define EXECUTE_BITS 0111
+#endif /* defined(S_IXUSR) */
+
+/* Characters used in log messages indicating the disposition of messages. */
+#define ART_ACCEPT '+'
+#define ART_CANC 'c'
+#define ART_STRSTR '?'
+#define ART_JUNK 'j'
+#define ART_REJECT '-'
+
+/*
+** used to sort Xref, Bytes and Path pointers
+*/
+typedef struct _HEADERP {
+ int index;
+ char *p;
+} HEADERP;
+
+#define HPCOUNT 4
+
+/*
+** For speed we build a binary tree of the headers, sorted by their
+** name. We also store the header's Name fields in the tree to avoid
+** doing an extra indirection.
+*/
+typedef struct _TREE {
+ const char *Name;
+ const ARTHEADER *Header;
+ struct _TREE *Before;
+ struct _TREE *After;
+} TREE;
+
+static TREE *ARTheadertree;
+
+/*
+** For doing the overview database, we keep a list of the headers and
+** a flag saying if they're written in brief or full format.
+*/
+typedef struct _ARTOVERFIELD {
+ const ARTHEADER *Header;
+ bool NeedHeader;
+} ARTOVERFIELD;
+
+static ARTOVERFIELD *ARTfields;
+
+/*
+** General newsgroup we care about, and what we put in the Path line.
+*/
+static char ARTctl[] = "control";
+static char ARTjnk[] = "junk";
+static char *ARTpathme;
+
+/*
+** Different types of rejected articles.
+*/
+typedef enum {REJECT_DUPLICATE, REJECT_SITE, REJECT_FILTER, REJECT_DISTRIB,
+ REJECT_GROUP, REJECT_UNAPP, REJECT_OTHER} Reject_type;
+
+/*
+** Flag array, indexed by character. Character classes for Message-ID's.
+*/
+static char ARTcclass[256];
+#define CC_MSGID_ATOM 01
+#define CC_MSGID_NORM 02
+#define CC_HOSTNAME 04
+#define ARTnormchar(c) ((ARTcclass[(unsigned char)(c)] & CC_MSGID_NORM) != 0)
+#define ARTatomchar(c) ((ARTcclass[(unsigned char)(c)] & CC_MSGID_ATOM) != 0)
+#define ARThostchar(c) ((ARTcclass[(unsigned char)(c)] & CC_HOSTNAME) != 0)
+
+#if defined(DO_PERL) || defined(DO_PYTHON)
+const char *filterPath;
+#endif /* DO_PERL || DO_PYTHON */
+
+
+\f
+/*
+** Trim '\r' from buffer.
+*/
+static void
+buffer_trimcr(struct buffer *bp)
+{
+ char *p, *q;
+ int trimmed = 0;
+
+ for (p = q = bp->data ; p < bp->data + bp->left ; p++) {
+ if (*p == '\r' && p+1 < bp->data + bp->left && p[1] == '\n') {
+ trimmed++;
+ continue;
+ }
+ *q++ = *p;
+ }
+ bp->left -= trimmed;
+}
+
+/*
+** Mark that the site gets this article.
+*/
+static void
+SITEmark(SITE *sp, NEWSGROUP *ngp)
+{
+ SITE *funnel;
+
+ sp->Sendit = true;
+ if (sp->ng == NULL)
+ sp->ng = ngp;
+ if (sp->Funnel != NOSITE) {
+ funnel = &Sites[sp->Funnel];
+ if (funnel->ng == NULL)
+ funnel->ng = ngp;
+ }
+}
+
+/*
+**
+*/
+bool
+ARTreadschema(void)
+{
+ static char *SCHEMA = NULL;
+ FILE *F;
+ int i;
+ char *p;
+ ARTOVERFIELD *fp;
+ const ARTHEADER *hp;
+ bool ok;
+ char buff[SMBUF];
+ bool foundxref = false;
+ bool foundxreffull = false;
+
+ if (ARTfields != NULL) {
+ free(ARTfields);
+ ARTfields = NULL;
+ }
+
+ /* Open file, count lines. */
+ if (SCHEMA == NULL)
+ SCHEMA = concatpath(innconf->pathetc, _PATH_SCHEMA);
+ if ((F = Fopen(SCHEMA, "r", TEMPORARYOPEN)) == NULL)
+ return false;
+ for (i = 0; fgets(buff, sizeof buff, F) != NULL; i++)
+ continue;
+ fseeko(F, 0, SEEK_SET);
+ ARTfields = xmalloc((i + 1) * sizeof(ARTOVERFIELD));
+
+ /* Parse each field. */
+ for (ok = true, fp = ARTfields ; fgets(buff, sizeof buff, F) != NULL ;) {
+ /* Ignore blank and comment lines. */
+ if ((p = strchr(buff, '\n')) != NULL)
+ *p = '\0';
+ if ((p = strchr(buff, '#')) != NULL)
+ *p = '\0';
+ if (buff[0] == '\0')
+ continue;
+ if ((p = strchr(buff, ':')) != NULL) {
+ *p++ = '\0';
+ fp->NeedHeader = (strcmp(p, "full") == 0);
+ } else
+ fp->NeedHeader = false;
+ if (strcasecmp(buff, "Xref") == 0) {
+ foundxref = true;
+ foundxreffull = fp->NeedHeader;
+ }
+ for (hp = ARTheaders; hp < ARRAY_END(ARTheaders); hp++) {
+ if (strcasecmp(buff, hp->Name) == 0) {
+ fp->Header = hp;
+ break;
+ }
+ }
+ if (hp == ARRAY_END(ARTheaders)) {
+ syslog(L_ERROR, "%s bad_schema unknown header \"%s\"",
+ LogName, buff);
+ ok = false;
+ continue;
+ }
+ fp++;
+ }
+ fp->Header = NULL;
+
+ Fclose(F);
+ if (!foundxref || !foundxreffull) {
+ syslog(L_FATAL, "%s 'Xref:full' must be included in %s", LogName, SCHEMA);
+ exit(1);
+ }
+ return ok;
+}
+
+
+/*
+** Build a balanced tree for the headers in subscript range [lo..hi).
+** This only gets called once, and the tree only has about 37 entries,
+** so we don't bother to unroll the recursion.
+*/
+static TREE *
+ARTbuildtree(const ARTHEADER **Table, int lo, int hi)
+{
+ int mid;
+ TREE *tp;
+
+ mid = lo + (hi - lo) / 2;
+ tp = xmalloc(sizeof(TREE));
+ tp->Header = Table[mid];
+ tp->Name = tp->Header->Name;
+ if (mid == lo)
+ tp->Before = NULL;
+ else
+ tp->Before = ARTbuildtree(Table, lo, mid);
+ if (mid == hi - 1)
+ tp->After = NULL;
+ else
+ tp->After = ARTbuildtree(Table, mid + 1, hi);
+ return tp;
+}
+
+
+/*
+** Sorting predicate for qsort call in ARTsetup.
+*/
+static int
+ARTcompare(const void *p1, const void *p2)
+{
+ return strcasecmp(((const ARTHEADER **)p1)[0]->Name,
+ ((const ARTHEADER **)p2)[0]->Name);
+}
+
+
+/*
+** Setup the article processing.
+*/
+void
+ARTsetup(void)
+{
+ const char * p;
+ const ARTHEADER ** table;
+ unsigned int i;
+
+ /* Set up the character class tables. These are written a
+ * little strangely to work around a GCC2.0 bug. */
+ memset(ARTcclass, 0, sizeof ARTcclass);
+ p = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789";
+ while ((i = *p++) != 0) {
+ ARTcclass[i] = CC_HOSTNAME | CC_MSGID_ATOM | CC_MSGID_NORM;
+ }
+ p = "!#$%&'*+-/=?^_`{|}~";
+ while ((i = *p++) != 0) {
+ ARTcclass[i] = CC_MSGID_ATOM | CC_MSGID_NORM;
+ }
+ p = "\"(),.:;<@[\\]";
+ while ((i = *p++) != 0) {
+ ARTcclass[i] = CC_MSGID_NORM;
+ }
+
+ /* The RFC's don't require it, but we add underscore to the list of valid
+ * hostname characters. */
+ ARTcclass['.'] |= CC_HOSTNAME;
+ ARTcclass['-'] |= CC_HOSTNAME;
+ ARTcclass['_'] |= CC_HOSTNAME;
+
+ /* Build the header tree. */
+ table = xmalloc(ARRAY_SIZE(ARTheaders) * sizeof(ARTHEADER *));
+ for (i = 0; i < ARRAY_SIZE(ARTheaders); i++)
+ table[i] = &ARTheaders[i];
+ qsort(table, ARRAY_SIZE(ARTheaders), sizeof *table, ARTcompare);
+ ARTheadertree = ARTbuildtree(table, 0, ARRAY_SIZE(ARTheaders));
+ free(table);
+
+ /* Get our Path name, kill trailing !. */
+ ARTpathme = xstrdup(Path.data);
+ ARTpathme[Path.used - 1] = '\0';
+
+ /* Set up database; ignore errors. */
+ ARTreadschema();
+}
+
+
+static void
+ARTfreetree(TREE *tp)
+{
+ TREE *next;
+
+ for ( ; tp != NULL; tp = next) {
+ if (tp->Before)
+ ARTfreetree(tp->Before);
+ next = tp->After;
+ free(tp);
+ }
+}
+
+
+void
+ARTclose(void)
+{
+ if (ARTfields != NULL) {
+ free(ARTfields);
+ ARTfields = NULL;
+ }
+ ARTfreetree(ARTheadertree);
+}
+
+/*
+** Start a log message about an article.
+*/
+static void
+ARTlog(const ARTDATA *data, char code, const char *text)
+{
+ const HDRCONTENT *hc = data->HdrContent;
+ int i;
+ bool Done;
+
+ TMRstart(TMR_ARTLOG);
+ /* We could be a bit faster by not dividing Now.usec by 1000,
+ * but who really wants to log at the Microsec level? */
+ Done = code == ART_ACCEPT || code == ART_JUNK;
+ if (text)
+ i = fprintf(Log, "%.15s.%03d %c %s %s %s%s",
+ ctime(&Now.time) + 4, (int)(Now.usec / 1000), code, data->Feedsite,
+ HDR_FOUND(HDR__MESSAGE_ID) ? HDR(HDR__MESSAGE_ID) : "(null)",
+ text, Done ? "" : "\n");
+ else
+ i = fprintf(Log, "%.15s.%03d %c %s %s%s",
+ ctime(&Now.time) + 4, (int)(Now.usec / 1000), code, data->Feedsite,
+ HDR_FOUND(HDR__MESSAGE_ID) ? HDR(HDR__MESSAGE_ID) : "(null)",
+ Done ? "" : "\n");
+ if (i == EOF || (Done && !BufferedLogs && fflush(Log)) || ferror(Log)) {
+ i = errno;
+ syslog(L_ERROR, "%s cant write log_start %m", LogName);
+ IOError("logging article", i);
+ clearerr(Log);
+ }
+ TMRstop(TMR_ARTLOG);
+}
+
+/*
+** Parse a Path line, splitting it up into NULL-terminated array of strings.
+*/
+static int
+ARTparsepath(const char *p, int size, LISTBUFFER *list)
+{
+ int i;
+ char *q, **hp;
+
+ /* setup buffer */
+ SetupListBuffer(size, list);
+
+ /* loop over text and copy */
+ for (i = 0, q = list->Data, hp = list->List ; *p ; p++, *q++ = '\0') {
+ /* skip leading separators. */
+ for (; *p && !ARThostchar(*p) && ISWHITE(*p) ; p++)
+ continue;
+ if (*p == '\0')
+ break;
+
+ if (list->ListLength <= i) {
+ list->ListLength += DEFAULTNGBOXSIZE;
+ list->List = xrealloc(list->List, list->ListLength * sizeof(char *));
+ hp = &list->List[i];
+ }
+ /* mark the start of the host, move to the end of it while copying */
+ for (*hp++ = q, i++ ; *p && ARThostchar(*p) && !ISWHITE(*p) ;)
+ *q++ = *p++;
+ if (*p == '\0')
+ break;
+ }
+ *q = '\0';
+ if (i == list->ListLength) {
+ list->ListLength += DEFAULTNGBOXSIZE;
+ list->List = xrealloc(list->List, list->ListLength * sizeof(char *));
+ hp = &list->List[i];
+ }
+ *hp = NULL;
+ return i;
+}
+
+/*
+** Sorting pointer where header starts
+*/
+static int
+ARTheaderpcmp(const void *p1, const void *p2)
+{
+ return (((const HEADERP *)p1)->p - ((const HEADERP *)p2)->p);
+}
+
+/* Write an article using the storage api. Put it together in memory and
+ call out to the api. */
+static TOKEN
+ARTstore(CHANNEL *cp)
+{
+ struct buffer *Article = &cp->In;
+ ARTDATA *data = &cp->Data;
+ HDRCONTENT *hc = data->HdrContent;
+ const char *p;
+ ARTHANDLE arth;
+ int i, j, iovcnt = 0;
+ long headersize = 0;
+ TOKEN result;
+ struct buffer *headers = &data->Headers;
+ struct iovec iov[ARTIOVCNT];
+ HEADERP hp[HPCOUNT];
+
+ /* find Path, Bytes and Xref to be prepended/dropped/replaced */
+ arth.len = i = 0;
+ /* assumes Path header is required header */
+ hp[i].p = HDR(HDR__PATH);
+ hp[i++].index = HDR__PATH;
+ if (HDR_FOUND(HDR__XREF)) {
+ hp[i].p = HDR(HDR__XREF);
+ hp[i++].index = HDR__XREF;
+ }
+ if (HDR_FOUND(HDR__BYTES)) {
+ hp[i].p = HDR(HDR__BYTES);
+ hp[i++].index = HDR__BYTES;
+ }
+ /* get the order of header appearance */
+ qsort(hp, i, sizeof(HEADERP), ARTheaderpcmp);
+ /* p always points where the next data should be written from */
+ for (p = Article->data + cp->Start, j = 0 ; j < i ; j++) {
+ switch (hp[j].index) {
+ case HDR__PATH:
+ if (!data->Hassamepath || data->AddAlias || Pathcluster.used) {
+ /* write heading data */
+ iov[iovcnt].iov_base = (char *) p;
+ iov[iovcnt++].iov_len = HDR(HDR__PATH) - p;
+ arth.len += HDR(HDR__PATH) - p;
+ /* append clusterpath */
+ if (Pathcluster.used) {
+ iov[iovcnt].iov_base = Pathcluster.data;
+ iov[iovcnt++].iov_len = Pathcluster.used;
+ arth.len += Pathcluster.used;
+ }
+ /* now append new one */
+ iov[iovcnt].iov_base = Path.data;
+ iov[iovcnt++].iov_len = Path.used;
+ arth.len += Path.used;
+ if (data->AddAlias) {
+ iov[iovcnt].iov_base = Pathalias.data;
+ iov[iovcnt++].iov_len = Pathalias.used;
+ arth.len += Pathalias.used;
+ }
+ /* next to write */
+ p = HDR(HDR__PATH);
+ if (data->Hassamecluster)
+ p += Pathcluster.used;
+ }
+ break;
+ case HDR__XREF:
+ if (!innconf->xrefslave) {
+ /* write heading data */
+ iov[iovcnt].iov_base = (char *) p;
+ iov[iovcnt++].iov_len = HDR(HDR__XREF) - p;
+ arth.len += HDR(HDR__XREF) - p;
+ /* replace with new one */
+ iov[iovcnt].iov_base = data->Xref;
+ iov[iovcnt++].iov_len = data->XrefLength - 2;
+ arth.len += data->XrefLength - 2;
+ /* next to write */
+ /* this points where trailing "\r\n" of orginal Xref header exists */
+ p = HDR(HDR__XREF) + HDR_LEN(HDR__XREF);
+ }
+ break;
+ case HDR__BYTES:
+ /* ditch whole Byte header */
+ /* write heading data */
+ iov[iovcnt].iov_base = (char *) p;
+ iov[iovcnt++].iov_len = data->BytesHeader - p;
+ arth.len += data->BytesHeader - p;
+ /* next to write */
+ /* need to skip trailing "\r\n" of Bytes header */
+ p = HDR(HDR__BYTES) + HDR_LEN(HDR__BYTES) + 2;
+ break;
+ default:
+ result.type = TOKEN_EMPTY;
+ return result;
+ }
+ }
+ /* in case Xref is not included in orignal article */
+ if (!HDR_FOUND(HDR__XREF)) {
+ /* write heading data */
+ iov[iovcnt].iov_base = (char *) p;
+ iov[iovcnt++].iov_len = Article->data + (data->Body - 2) - p;
+ arth.len += Article->data + (data->Body - 2) - p;
+ /* Xref needs to be inserted */
+ iov[iovcnt].iov_base = (char *) "Xref: ";
+ iov[iovcnt++].iov_len = sizeof("Xref: ") - 1;
+ arth.len += sizeof("Xref: ") - 1;
+ iov[iovcnt].iov_base = data->Xref;
+ iov[iovcnt++].iov_len = data->XrefLength;
+ arth.len += data->XrefLength;
+ p = Article->data + (data->Body - 2);
+ }
+ /* write rest of data */
+ iov[iovcnt].iov_base = (char *) p;
+ iov[iovcnt++].iov_len = Article->data + cp->Next - p;
+ arth.len += Article->data + cp->Next - p;
+
+ /* revert trailing '\0\n' to '\r\n' of all system header */
+ for (i = 0 ; i < MAX_ARTHEADER ; i++) {
+ if (HDR_FOUND(i))
+ HDR_PARSE_END(i);
+ }
+
+ arth.iov = iov;
+ arth.iovcnt = iovcnt;
+ arth.arrived = (time_t)0;
+ arth.token = (TOKEN *)NULL;
+ arth.expires = data->Expires;
+ if (innconf->storeonxref) {
+ arth.groups = data->Replic;
+ arth.groupslen = data->ReplicLength;
+ } else {
+ arth.groups = HDR(HDR__NEWSGROUPS);
+ arth.groupslen = HDR_LEN(HDR__NEWSGROUPS);
+ }
+
+ SMerrno = SMERR_NOERROR;
+ result = SMstore(arth);
+ if (result.type == TOKEN_EMPTY) {
+ if (SMerrno == SMERR_NOMATCH)
+ ThrottleNoMatchError();
+ else if (SMerrno != SMERR_NOERROR)
+ IOError("SMstore", SMerrno);
+ return result;
+ }
+
+ /* calculate stored size */
+ for (data->BytesValue = i = 0 ; i < iovcnt ; i++) {
+ if (NeedHeaders && (i + 1 == iovcnt)) {
+ /* body begins at last iov */
+ headersize = data->BytesValue +
+ Article->data + data->Body - (char *) iov[i].iov_base;
+ }
+ data->BytesValue += iov[i].iov_len;
+ }
+ /* "\r\n" is counted as 1 byte. trailing ".\r\n" and body delimitor are also
+ substituted */
+ data->BytesValue -= (data->HeaderLines + data->Lines + 4);
+ /* Figure out how much space we'll need and get it. */
+ snprintf(data->Bytes, sizeof(data->Bytes), "Bytes: %ld\r\n",
+ data->BytesValue);
+ /* does not include strlen("Bytes: \r\n") */
+ data->BytesLength = strlen(data->Bytes) - 9;
+
+ if (!NeedHeaders)
+ return result;
+
+ /* Add the data. */
+ buffer_resize(headers, headersize);
+ buffer_set(headers, data->Bytes, strlen(data->Bytes));
+ for (i = 0 ; i < iovcnt ; i++) {
+ if (i + 1 == iovcnt)
+ buffer_append(headers, iov[i].iov_base,
+ Article->data + data->Body - (char *) iov[i].iov_base);
+ else
+ buffer_append(headers, iov[i].iov_base, iov[i].iov_len);
+ }
+ buffer_trimcr(headers);
+
+ return result;
+}
+
+/*
+** Parse a header that starts at header. size includes trailing "\r\n"
+*/
+static void
+ARTparseheader(CHANNEL *cp, int size)
+{
+ ARTDATA *data = &cp->Data;
+ char *header = cp->In.data + data->CurHeader;
+ HDRCONTENT *hc = cp->Data.HdrContent;
+ TREE *tp;
+ const ARTHEADER *hp;
+ char c, *p, *colon;
+ int i;
+
+ /* Find first colon */
+ if ((colon = memchr(header, ':', size)) == NULL || !ISWHITE(colon[1])) {
+ if ((p = memchr(header, '\r', size)) != NULL)
+ *p = '\0';
+ snprintf(cp->Error, sizeof(cp->Error),
+ "%d No colon-space in \"%s\" header",
+ NNTP_REJECTIT_VAL, MaxLength(header, header));
+ if (p != NULL)
+ *p = '\r';
+ return;
+ }
+
+ /* See if this is a system header. A fairly tightly-coded binary search. */
+ c = CTYPE(islower, *header) ? toupper(*header) : *header;
+ for (*colon = '\0', tp = ARTheadertree; tp; ) {
+ if ((i = c - tp->Name[0]) == 0 && (i = strcasecmp(header, tp->Name)) == 0)
+ break;
+ if (i < 0)
+ tp = tp->Before;
+ else
+ tp = tp->After;
+ }
+ *colon = ':';
+
+ if (tp == NULL) {
+ /* Not a system header, make sure we have <word><colon><space>. */
+ for (p = colon; --p > header; ) {
+ if (ISWHITE(*p)) {
+ c = *p;
+ *p = '\0';
+ snprintf(cp->Error, sizeof(cp->Error),
+ "%d Space before colon in \"%s\" header",
+ NNTP_REJECTIT_VAL, MaxLength(header, header));
+ *p = c;
+ return;
+ }
+ }
+ return;
+ }
+ hp = tp->Header;
+ i = hp - ARTheaders;
+ /* remember to ditch if it's Bytes: */
+ if (i == HDR__BYTES)
+ cp->Data.BytesHeader = header;
+ hc = &hc[i];
+ if (hc->Length != 0) {
+ /* duplicated */
+ hc->Length = -1;
+ } else {
+ for (p = colon + 1 ; (p < header + size - 2) &&
+ (ISWHITE(*p) || *p == '\r' || *p == '\n'); p++);
+ if (p < header + size - 2) {
+ hc->Value = p;
+ /* HDR_LEN() does not include trailing "\r\n" */
+ hc->Length = header + size - 2 - p;
+ } else {
+ snprintf(cp->Error, sizeof(cp->Error),
+ "%d Body of header is all blanks in \"%s\" header",
+ NNTP_REJECTIT_VAL, MaxLength(hp->Name, hp->Name));
+ }
+ }
+ return;
+}
+
+/*
+** Check Message-ID format based on RFC 822 grammar, except that (as per
+** RFC 1036) whitespace, non-printing, and '>' characters are excluded.
+** Based on code by Paul Eggert posted to news.software.b on 22-Nov-90
+** in <#*tyo2'~n@twinsun.com>, with additional email discussion.
+** Thanks, Paul.
+*/
+bool
+ARTidok(const char *MessageID)
+{
+ int c;
+ const char *p;
+
+ /* Check the length of the message ID. */
+ if (MessageID == NULL || strlen(MessageID) > NNTP_MSGID_MAXLEN)
+ return false;
+
+ /* Scan local-part: "< atom|quoted [ . atom|quoted]" */
+ p = MessageID;
+ if (*p++ != '<')
+ return false;
+ for (; ; p++) {
+ if (ARTatomchar(*p))
+ while (ARTatomchar(*++p))
+ continue;
+ else {
+ if (*p++ != '"')
+ return false;
+ for ( ; ; ) {
+ switch (c = *p++) {
+ case '\\':
+ c = *p++;
+ /* FALLTHROUGH */
+ default:
+ if (ARTnormchar(c))
+ continue;
+ return false;
+ case '"':
+ break;
+ }
+ break;
+ }
+ }
+ if (*p != '.')
+ break;
+ }
+
+ /* Scan domain part: "@ atom|domain [ . atom|domain] > \0" */
+ if (*p++ != '@')
+ return false;
+ for ( ; ; p++) {
+ if (ARTatomchar(*p))
+ while (ARTatomchar(*++p))
+ continue;
+ else {
+ if (*p++ != '[')
+ return false;
+ for ( ; ; ) {
+ switch (c = *p++) {
+ case '\\':
+ c = *p++;
+ /* FALLTHROUGH */
+ default:
+ if (ARTnormchar(c))
+ continue;
+ /* FALLTHROUGH */
+ case '[':
+ return false;
+ case ']':
+ break;
+ }
+ break;
+ }
+ }
+ if (*p != '.')
+ break;
+ }
+
+ return *p == '>' && *++p == '\0';
+}
+
+/*
+** Clean up data field where article informations are stored.
+** This must be called before article processing.
+*/
+void
+ARTprepare(CHANNEL *cp)
+{
+ ARTDATA *data = &cp->Data;
+ HDRCONTENT *hc = data->HdrContent;
+ int i;
+
+ for (i = 0 ; i < MAX_ARTHEADER ; i++, hc++) {
+ hc->Value = NULL;
+ hc->Length = 0;
+ }
+ data->Lines = data->HeaderLines = data->CRwithoutLF = data->LFwithoutCR = 0;
+ data->CurHeader = data->LastTerminator = data->LastCR = cp->Start - 1;
+ data->LastCRLF = data->Body = cp->Start - 1;
+ data->BytesHeader = NULL;
+ data->Feedsite = "?";
+ *cp->Error = '\0';
+}
+
+/*
+** Clean up an article. This is mainly copying in-place, stripping bad
+** headers. Also fill in the article data block with what we can find.
+** Return NULL if the article is okay, or a string describing the error.
+** Parse headers and end of article
+** This is called by NCproc().
+*/
+void
+ARTparse(CHANNEL *cp)
+{
+ struct buffer *bp = &cp->In;
+ ARTDATA *data = &cp->Data;
+ long i, limit, fudge, size;
+ int hopcount;
+ char **hops;
+ HDRCONTENT *hc = data->HdrContent;
+
+ /* Read through the buffer to find header, body and end of article */
+ /* this routine is designed not to refer data so long as possible for
+ performance reason, so the code may look redundant at a glance */
+ limit = bp->used;
+ i = cp->Next;
+ if (cp->State == CSgetheader) {
+ /* header processing */
+ for (; i < limit ;) {
+ if (data->LastCRLF + 1 == i) {
+ /* begining of the line */
+ switch (bp->data[i]) {
+ case '.':
+ data->LastTerminator = i;
+ data->NullHeader = false;
+ break;
+ case '\r':
+ data->LastCR = i;
+ data->NullHeader = false;
+ break;
+ case '\n':
+ data->LFwithoutCR++;
+ data->NullHeader = false;
+ break;
+ case '\t':
+ case ' ':
+ /* header is folded. NullHeader is untouched */
+ break;
+ case '\0':
+ snprintf(cp->Error, sizeof(cp->Error), "%d Null Header",
+ NNTP_REJECTIT_VAL);
+ data->NullHeader = true;
+ break;
+ default:
+ if (data->CurHeader >= cp->Start) {
+ /* parse previous header */
+ if (!data->NullHeader && (*cp->Error == '\0'))
+ /* skip if already got an error */
+ ARTparseheader(cp, i - data->CurHeader);
+ }
+ data->CurHeader = i;
+ data->NullHeader = false;
+ break;
+ }
+ i++;
+ }
+ for (; i < limit ;) {
+ /* rest of the line */
+ switch (bp->data[i]) {
+ case '\0':
+ snprintf(cp->Error, sizeof(cp->Error), "%d Null Header",
+ NNTP_REJECTIT_VAL);
+ data->NullHeader = true;
+ break;
+ case '\r':
+ if (data->LastCR >= cp->Start)
+ data->CRwithoutLF++;
+ data->LastCR = i;
+ break;
+ case '\n':
+ if (data->LastCR + 1 == i) {
+ /* found CRLF */
+ data->LastCR = cp->Start - 1;
+ if (data->LastTerminator + 2 == i) {
+ /* terminated still in header */
+ if (cp->Start + 3 == i) {
+ snprintf(cp->Error, sizeof(cp->Error), "%d Empty article",
+ NNTP_REJECTIT_VAL);
+ cp->State = CSnoarticle;
+ } else {
+ snprintf(cp->Error, sizeof(cp->Error), "%d No body",
+ NNTP_REJECTIT_VAL);
+ cp->State = CSgotarticle;
+ }
+ cp->Next = ++i;
+ goto sizecheck;
+ }
+ if (data->LastCRLF + MAXHEADERSIZE < i)
+ snprintf(cp->Error, sizeof(cp->Error),
+ "%d Too long line in header %ld bytes",
+ NNTP_REJECTIT_VAL, i - data->LastCRLF);
+ else if (data->LastCRLF + 2 == i) {
+ /* header ends */
+ /* parse previous header */
+ if (data->CurHeader >= cp->Start) {
+ if (!data->NullHeader && (*cp->Error == '\0'))
+ /* skip if already got an error */
+ ARTparseheader(cp, i - 1 - data->CurHeader);
+ } else {
+ snprintf(cp->Error, sizeof(cp->Error), "%d No header",
+ NNTP_REJECTIT_VAL);
+ }
+ data->LastCRLF = i++;
+ data->Body = i;
+ cp->State = CSgetbody;
+ goto bodyprocessing;
+ }
+ data->HeaderLines++;
+ data->LastCRLF = i++;
+ goto endofheaderline;
+ } else {
+ data->LFwithoutCR++;
+ }
+ break;
+ default:
+ break;
+ }
+ i++;
+ }
+endofheaderline:
+ ;
+ }
+ } else {
+bodyprocessing:
+ /* body processing, or eating huge article */
+ for (; i < limit ;) {
+ if (data->LastCRLF + 1 == i) {
+ /* begining of the line */
+ switch (bp->data[i]) {
+ case '.':
+ data->LastTerminator = i;
+ break;
+ case '\r':
+ data->LastCR = i;
+ break;
+ case '\n':
+ data->LFwithoutCR++;
+ break;
+ default:
+ break;
+ }
+ i++;
+ }
+ for (; i < limit ;) {
+ /* rest of the line */
+ switch (bp->data[i]) {
+ case '\r':
+ if (data->LastCR >= cp->Start)
+ data->CRwithoutLF++;
+ data->LastCR = i;
+ break;
+ case '\n':
+ if (data->LastCR + 1 == i) {
+ /* found CRLF */
+ data->LastCR = cp->Start - 1;
+ if (data->LastTerminator + 2 == i) {
+ /* found end of article */
+ if (cp->State == CSeatarticle) {
+ cp->State = CSgotlargearticle;
+ cp->Next = ++i;
+ snprintf(cp->Error, sizeof(cp->Error),
+ "%d Article of %ld bytes exceeds local limit of %ld bytes",
+ NNTP_REJECTIT_VAL, (unsigned long) i - cp->Start,
+ innconf->maxartsize);
+ } else {
+ cp->State = CSgotarticle;
+ i++;
+ }
+ if (*cp->Error != '\0' && HDR_FOUND(HDR__MESSAGE_ID)) {
+ HDR_PARSE_START(HDR__MESSAGE_ID);
+ if (HDR_FOUND(HDR__PATH)) {
+ /* to record path into news log */
+ HDR_PARSE_START(HDR__PATH);
+ hopcount = ARTparsepath(HDR(HDR__PATH), HDR_LEN(HDR__PATH),
+ &data->Path);
+ HDR_PARSE_END(HDR__PATH);
+ if (hopcount > 0) {
+ hops = data->Path.List;
+ if (innconf->logipaddr) {
+ data->Feedsite = RChostname(cp);
+ if (data->Feedsite == NULL)
+ data->Feedsite = CHANname(cp);
+ if (strcmp("0.0.0.0", data->Feedsite) == 0 ||
+ data->Feedsite[0] == '\0')
+ data->Feedsite =
+ hops && hops[0] ? hops[0] : CHANname(cp);
+ } else {
+ data->Feedsite =
+ hops && hops[0] ? hops[0] : CHANname(cp);
+ }
+ }
+ }
+ ARTlog(data, ART_REJECT, cp->Error);
+ HDR_PARSE_END(HDR__MESSAGE_ID);
+ }
+ if (cp->State == CSgotlargearticle)
+ return;
+ goto sizecheck;
+ }
+#if 0 /* this may be examined in the future */
+ if (data->LastCRLF + MAXHEADERSIZE < i)
+ snprintf(cp->Error, sizeof(cp->Error),
+ "%d Too long line in body %d bytes",
+ NNTP_REJECTIT_VAL, i);
+#endif
+ data->Lines++;
+ data->LastCRLF = i++;
+ goto endofline;
+ } else {
+ data->LFwithoutCR++;
+ }
+ break;
+ default:
+ break;
+ }
+ i++;
+ }
+endofline:
+ ;
+ }
+ }
+sizecheck:
+ size = i - cp->Start;
+ fudge = data->HeaderLines + data->Lines + 4;
+ if (innconf->maxartsize > 0)
+ if (size > fudge && size - fudge > innconf->maxartsize)
+ cp->State = CSeatarticle;
+ cp->Next = i;
+ return;
+}
+
+/*
+** Clean up an article. This is mainly copying in-place, stripping bad
+** headers. Also fill in the article data block with what we can find.
+** Return true if the article has no error, or false which means the error.
+*/
+static bool
+ARTclean(ARTDATA *data, char *buff)
+{
+ HDRCONTENT *hc = data->HdrContent;
+ const ARTHEADER *hp = ARTheaders;
+ int i;
+ char *p;
+ int delta;
+
+ TMRstart(TMR_ARTCLEAN);
+ data->Arrived = Now.time;
+ data->Expires = 0;
+
+ /* replace trailing '\r\n' with '\0\n' of all system header to be handled
+ easily by str*() functions */
+ for (i = 0 ; i < MAX_ARTHEADER ; i++) {
+ if (HDR_FOUND(i))
+ HDR_PARSE_START(i);
+ }
+
+ /* Make sure all the headers we need are there */
+ for (i = 0; i < MAX_ARTHEADER ; i++) {
+ if (hp[i].Type == HTreq) {
+ if (HDR_FOUND(i))
+ continue;
+ if (hc[i].Length < 0) {
+ sprintf(buff, "%d Duplicate \"%s\" header", NNTP_REJECTIT_VAL,
+ hp[1].Name);
+ } else {
+ sprintf(buff, "%d Missing \"%s\" header", NNTP_REJECTIT_VAL,
+ hp[i].Name);
+ }
+ TMRstop(TMR_ARTCLEAN);
+ return false;
+ }
+ }
+
+ /* assumes Message-ID header is required header */
+ if (!ARTidok(HDR(HDR__MESSAGE_ID))) {
+ HDR_LEN(HDR__MESSAGE_ID) = 0;
+ sprintf(buff, "%d Bad \"Message-ID\" header", NNTP_REJECTIT_VAL);
+ TMRstop(TMR_ARTCLEAN);
+ return false;
+ }
+
+ if (innconf->linecountfuzz && HDR_FOUND(HDR__LINES)) {
+ p = HDR(HDR__LINES);
+ i = data->Lines;
+ if ((delta = i - atoi(p)) != 0 && abs(delta) > innconf->linecountfuzz) {
+ sprintf(buff, "%d Linecount %s != %d +- %ld", NNTP_REJECTIT_VAL,
+ MaxLength(p, p), i, innconf->linecountfuzz);
+ TMRstop(TMR_ARTCLEAN);
+ return false;
+ }
+ }
+
+ /* Is article too old? */
+ /* assumes Date header is required header */
+ p = HDR(HDR__DATE);
+ if ((data->Posted = parsedate(p, &Now)) == -1) {
+ sprintf(buff, "%d Bad \"Date\" header -- \"%s\"", NNTP_REJECTIT_VAL,
+ MaxLength(p, p));
+ TMRstop(TMR_ARTCLEAN);
+ return false;
+ }
+ if (innconf->artcutoff) {
+ long cutoff = innconf->artcutoff * 24 * 60 * 60;
+
+ if (data->Posted < Now.time - cutoff) {
+ sprintf(buff, "%d Too old -- \"%s\"", NNTP_REJECTIT_VAL,
+ MaxLength(p, p));
+ TMRstop(TMR_ARTCLEAN);
+ return false;
+ }
+ }
+ if (data->Posted > Now.time + DATE_FUZZ) {
+ sprintf(buff, "%d Article posted in the future -- \"%s\"",
+ NNTP_REJECTIT_VAL, MaxLength(p, p));
+ TMRstop(TMR_ARTCLEAN);
+ return false;
+ }
+ if (HDR_FOUND(HDR__EXPIRES)) {
+ p = HDR(HDR__EXPIRES);
+ data->Expires = parsedate(p, &Now);
+ }
+
+ /* Colon or whitespace in the Newsgroups header? */
+ /* assumes Newsgroups header is required header */
+ if ((data->Groupcount =
+ NGsplit(HDR(HDR__NEWSGROUPS), HDR_LEN(HDR__NEWSGROUPS),
+ &data->Newsgroups)) == 0) {
+ TMRstop(TMR_ARTCLEAN);
+ sprintf(buff, "%d Unwanted character in \"Newsgroups\" header",
+ NNTP_REJECTIT_VAL);
+ return false;
+ }
+
+ /* Fill in other Data fields. */
+ if (HDR_FOUND(HDR__SENDER))
+ data->Poster = HDR(HDR__SENDER);
+ else
+ data->Poster = HDR(HDR__FROM);
+ if (HDR_FOUND(HDR__REPLY_TO))
+ data->Replyto = HDR(HDR__REPLY_TO);
+ else
+ data->Replyto = HDR(HDR__FROM);
+
+ TMRstop(TMR_ARTCLEAN);
+ return true;
+}
+
+/*
+** We are going to reject an article, record the reason and
+** and the article.
+*/
+static void
+ARTreject(Reject_type code, CHANNEL *cp, struct buffer *article UNUSED)
+{
+ /* Remember why the article was rejected (for the status file) */
+
+ switch (code) {
+ case REJECT_DUPLICATE:
+ cp->Duplicate++;
+ cp->DuplicateSize += cp->Next - cp->Start;
+ break;
+ case REJECT_SITE:
+ cp->Unwanted_s++;
+ break;
+ case REJECT_FILTER:
+ cp->Unwanted_f++;
+ break;
+ case REJECT_DISTRIB:
+ cp->Unwanted_d++;
+ break;
+ case REJECT_GROUP:
+ cp->Unwanted_g++;
+ break;
+ case REJECT_UNAPP:
+ cp->Unwanted_u++;
+ break;
+ case REJECT_OTHER:
+ cp->Unwanted_o++;
+ break;
+ default:
+ /* should never be here */
+ syslog(L_NOTICE, "%s unknown reject type received by ARTreject()",
+ LogName);
+ break;
+ }
+ /* error */
+}
+
+/*
+** Verify if a cancel message is valid. If the user posting the cancel
+** matches the user who posted the article, return the list of filenames
+** otherwise return NULL.
+*/
+static bool
+ARTcancelverify(const ARTDATA *data, const char *MessageID, TOKEN *token)
+{
+ const char *p;
+ char *q, *q1;
+ const char *local;
+ char buff[SMBUF];
+ ARTHANDLE *art;
+ bool r;
+
+ if (!HISlookup(History, MessageID, NULL, NULL, NULL, token))
+ return false;
+ if ((art = SMretrieve(*token, RETR_HEAD)) == NULL)
+ return false;
+ local = wire_findheader(art->data, art->len, "Sender");
+ if (local == NULL) {
+ local = wire_findheader(art->data, art->len, "From");
+ if (local == NULL) {
+ SMfreearticle(art);
+ return false;
+ }
+ }
+ for (p = local; p < art->data + art->len; p++) {
+ if (*p == '\r' || *p == '\n')
+ break;
+ }
+ if (p == art->data + art->len) {
+ SMfreearticle(art);
+ return false;
+ }
+ q = xmalloc(p - local + 1);
+ memcpy(q, local, p - local);
+ SMfreearticle(art);
+ q[p - local] = '\0';
+ HeaderCleanFrom(q);
+
+ /* Compare canonical forms. */
+ q1 = xstrdup(data->Poster);
+ HeaderCleanFrom(q1);
+ if (strcmp(q, q1) != 0) {
+ r = false;
+ sprintf(buff, "\"%.50s\" wants to cancel %s by \"%.50s\"",
+ q1, MaxLength(MessageID, MessageID), q);
+ ARTlog(data, ART_REJECT, buff);
+ }
+ else {
+ r = true;
+ }
+ free(q1);
+ free(q);
+ return r;
+}
+
+/*
+** Process a cancel message.
+*/
+void
+ARTcancel(const ARTDATA *data, const char *MessageID, const bool Trusted)
+{
+ char buff[SMBUF+16];
+ TOKEN token;
+ bool r;
+
+ TMRstart(TMR_ARTCNCL);
+ if (!DoCancels && !Trusted) {
+ TMRstop(TMR_ARTCNCL);
+ return;
+ }
+
+ if (!ARTidok(MessageID)) {
+ syslog(L_NOTICE, "%s bad cancel Message-ID %s", data->Feedsite,
+ MaxLength(MessageID, MessageID));
+ TMRstop(TMR_ARTCNCL);
+ return;
+ }
+
+ if (!HIScheck(History, MessageID)) {
+ /* Article hasn't arrived here, so write a fake entry using
+ * most of the information from the cancel message. */
+ if (innconf->verifycancels && !Trusted) {
+ TMRstop(TMR_ARTCNCL);
+ return;
+ }
+ InndHisRemember(MessageID);
+ snprintf(buff, sizeof(buff), "Cancelling %s",
+ MaxLength(MessageID, MessageID));
+ ARTlog(data, ART_CANC, buff);
+ TMRstop(TMR_ARTCNCL);
+ return;
+ }
+ if (Trusted || !innconf->verifycancels)
+ r = HISlookup(History, MessageID, NULL, NULL, NULL, &token);
+ else
+ r = ARTcancelverify(data, MessageID, &token);
+ if (r == false) {
+ TMRstop(TMR_ARTCNCL);
+ return;
+ }
+
+ /* Get stored message and zap them. */
+ if (!SMcancel(token) && SMerrno != SMERR_NOENT && SMerrno != SMERR_UNINIT)
+ syslog(L_ERROR, "%s cant cancel %s (SMerrno %d)", LogName,
+ TokenToText(token), SMerrno);
+ if (innconf->immediatecancel && !SMflushcacheddata(SM_CANCELEDART))
+ syslog(L_ERROR, "%s cant cancel cached %s", LogName, TokenToText(token));
+ snprintf(buff, sizeof(buff), "Cancelling %s",
+ MaxLength(MessageID, MessageID));
+ ARTlog(data, ART_CANC, buff);
+ TMRstop(TMR_ARTCNCL);
+}
+
+/*
+** Process a control message. Cancels are handled here, but any others
+** are passed out to an external program in a specific directory that
+** has the same name as the first word of the control message.
+*/
+static void
+ARTcontrol(ARTDATA *data, char *Control, CHANNEL *cp UNUSED)
+{
+ char *p, c;
+
+ /* See if it's a cancel message. */
+ c = *Control;
+ if (c == 'c' && strncmp(Control, "cancel", 6) == 0) {
+ for (p = &Control[6]; ISWHITE(*p); p++)
+ continue;
+ if (*p && ARTidok(p))
+ ARTcancel(data, p, false);
+ return;
+ }
+}
+
+/*
+** Parse a Distribution line, splitting it up into NULL-terminated array of
+** strings.
+*/
+static void
+ARTparsedist(const char *p, int size, LISTBUFFER *list)
+{
+ int i;
+ char *q, **dp;
+
+ /* setup buffer */
+ SetupListBuffer(size, list);
+
+ /* loop over text and copy */
+ for (i = 0, q = list->Data, dp = list->List ; *p ; p++, *q++ = '\0') {
+ /* skip leading separators. */
+ for (; *p && ((*p == ',') || ISWHITE(*p)) ; p++)
+ continue;
+ if (*p == '\0')
+ break;
+
+ if (list->ListLength <= i) {
+ list->ListLength += DEFAULTNGBOXSIZE;
+ list->List = xrealloc(list->List, list->ListLength * sizeof(char *));
+ dp = &list->List[i];
+ }
+ /* mark the start of the host, move to the end of it while copying */
+ for (*dp++ = q, i++ ; *p && (*p != ',') && !ISWHITE(*p) ;)
+ *q++ = *p++;
+ if (*p == '\0')
+ break;
+ }
+ *q = '\0';
+ if (i == list->ListLength) {
+ list->ListLength += DEFAULTNGBOXSIZE;
+ list->List = xrealloc(list->List, list->ListLength * sizeof(char *));
+ dp = &list->List[i];
+ }
+ *dp = NULL;
+ return;
+}
+
+/*
+** A somewhat similar routine, except that this handles negated entries
+** in the list and is used to check the distribution sub-field.
+*/
+static bool
+DISTwanted(char **list, char *p)
+{
+ char *q;
+ char c;
+ bool sawbang;
+
+ for (sawbang = false, c = *p; (q = *list) != NULL; list++) {
+ if (*q == '!') {
+ sawbang = true;
+ if (c == *++q && strcmp(p, q) == 0)
+ return false;
+ } else if (c == *q && strcmp(p, q) == 0)
+ return true;
+ }
+
+ /* If we saw any !foo's and didn't match, then assume they are all negated
+ distributions and return true, else return false. */
+ return sawbang;
+}
+
+/*
+** See if any of the distributions in the article are wanted by the site.
+*/
+static bool
+DISTwantany(char **site, char **article)
+{
+ for ( ; *article; article++)
+ if (DISTwanted(site, *article))
+ return true;
+ return false;
+}
+
+/*
+** Send the current article to all sites that would get it if the
+** group were created.
+*/
+static void
+ARTsendthegroup(char *name)
+{
+ SITE *sp;
+ int i;
+ NEWSGROUP *ngp;
+
+ for (ngp = NGfind(ARTctl), sp = Sites, i = nSites; --i >= 0; sp++) {
+ if (sp->Name != NULL && SITEwantsgroup(sp, name)) {
+ SITEmark(sp, ngp);
+ }
+ }
+}
+
+/*
+** Check if site doesn't want this group even if it's crossposted
+** to a wanted group.
+*/
+static void
+ARTpoisongroup(char *name)
+{
+ SITE *sp;
+ int i;
+
+ for (sp = Sites, i = nSites; --i >= 0; sp++) {
+ if (sp->Name != NULL && (sp->PoisonEntry || ME.PoisonEntry) &&
+ SITEpoisongroup(sp, name))
+ sp->Poison = true;
+ }
+}
+
+/*
+** Assign article numbers to the article and create the Xref line.
+** If we end up not being able to write the article, we'll get "holes"
+** in the directory and active file.
+*/
+static void
+ARTassignnumbers(ARTDATA *data)
+{
+ char *p, *q;
+ int i, len, linelen, buflen;
+ NEWSGROUP *ngp;
+
+ if (data->XrefBufLength == 0) {
+ data->XrefBufLength = MAXHEADERSIZE * 2 + 1;
+ data->Xref = xmalloc(data->XrefBufLength);
+ strncpy(data->Xref, Path.data, Path.used - 1);
+ }
+ len = Path.used - 1;
+ p = q = data->Xref + len;
+ for (linelen = i = 0; (ngp = GroupPointers[i]) != NULL; i++) {
+ /* If already went to this group (i.e., multiple groups are aliased
+ * into it), then skip it. */
+ if (ngp->PostCount > 0)
+ continue;
+
+ /* Bump the number. */
+ ngp->PostCount++;
+ ngp->Last++;
+ if (!FormatLong(ngp->LastString, (long)ngp->Last, ngp->Lastwidth)) {
+ syslog(L_ERROR, "%s cant update_active %s", LogName, ngp->Name);
+ continue;
+ }
+ ngp->Filenum = ngp->Last;
+ /* len ' ' "news_groupname" ':' "#" "\r\n" */
+ if (len + 1 + ngp->NameLength + 1 + 10 + 2 > data->XrefBufLength) {
+ data->XrefBufLength += MAXHEADERSIZE;
+ data->Xref = xrealloc(data->Xref, data->XrefBufLength);
+ p = data->Xref + len;
+ }
+ if (linelen + 1 + ngp->NameLength + 1 + 10 > MAXHEADERSIZE) {
+ /* line exceeded */
+ sprintf(p, "\r\n %s:%lu", ngp->Name, ngp->Filenum);
+ buflen = strlen(p);
+ linelen = buflen - 2;
+ } else {
+ sprintf(p, " %s:%lu", ngp->Name, ngp->Filenum);
+ buflen = strlen(p);
+ linelen += buflen;
+ }
+ len += buflen;
+ p += buflen;
+ }
+ /* p[0] is replaced with '\r' to be wireformatted when stored. p[1] needs to
+ be '\n' */
+ p[0] = '\r';
+ p[1] = '\n';
+ /* data->XrefLength includes trailing "\r\n" */
+ data->XrefLength = len + 2;
+ data->Replic = q + 1;
+ data->ReplicLength = len - (q + 1 - data->Xref);
+}
+
+/*
+** Parse the data from the xref header and assign the numbers.
+** This involves replacing the GroupPointers entries.
+*/
+static bool
+ARTxrefslave(ARTDATA *data)
+{
+ char *p, *q, *name, *next, c = 0;
+ NEWSGROUP *ngp;
+ int i;
+ bool nogroup = true;
+ HDRCONTENT *hc = data->HdrContent;
+
+ if (!HDR_FOUND(HDR__XREF))
+ return false;
+ /* skip server name */
+ if ((p = strpbrk(HDR(HDR__XREF), " \t\r\n")) == NULL)
+ return false;
+ /* in case Xref is folded */
+ while (*++p == ' ' || *p == '\t' || *p == '\r' || *p == '\n');
+ if (*p == '\0')
+ return false;
+ data->Replic = p;
+ data->ReplicLength = HDR_LEN(HDR__XREF) - (p - HDR(HDR__XREF));
+ for (i = 0; (*p != '\0') && (p < HDR(HDR__XREF) + HDR_LEN(HDR__XREF)) ; p = next) {
+ /* Mark end of this entry and where next one starts. */
+ name = p;
+ if ((q = next = strpbrk(p, " \t\r\n")) != NULL) {
+ c = *q;
+ *q = '\0';
+ while (*++next == ' ' || *next == '\t' || *next == '\r' || *next == '\n');
+ } else {
+ q = NULL;
+ next = "";
+ }
+
+ /* Split into news.group:# */
+ if ((p = strchr(p, ':')) == NULL) {
+ syslog(L_ERROR, "%s bad_format %s", LogName, name);
+ if (q != NULL)
+ *q = c;
+ continue;
+ }
+ *p = '\0';
+ if ((ngp = NGfind(name)) == NULL) {
+ syslog(L_ERROR, "%s bad_newsgroup %s", LogName, name);
+ *p = ':';
+ if (q != NULL)
+ *q = c;
+ continue;
+ }
+ *p = ':';
+ ngp->Filenum = atol(p + 1);
+ if (q != NULL)
+ *q = c;
+
+ /* Update active file if we got a new high-water mark. */
+ if (ngp->Last < ngp->Filenum) {
+ ngp->Last = ngp->Filenum;
+ if (!FormatLong(ngp->LastString, (long)ngp->Last, ngp->Lastwidth)) {
+ syslog(L_ERROR, "%s cant update_active %s", LogName, ngp->Name);
+ continue;
+ }
+ }
+ /* Mark that this group gets the article. */
+ ngp->PostCount++;
+ GroupPointers[i++] = ngp;
+ nogroup = false;
+ }
+ GroupPointers[i] = NULL;
+ if (nogroup)
+ return false;
+ return true;
+}
+
+/*
+** Return true if a list of strings has a specific one. This is a
+** generic routine, but is used for seeing if a host is in the Path line.
+*/
+static bool
+ListHas(const char **list, const char *p)
+{
+ const char *q;
+ char c;
+
+ for (c = *p; (q = *list) != NULL; list++)
+ if (strcasecmp(p, q) == 0)
+ return true;
+ return false;
+}
+
+/*
+** Even though we have already calculated the Message-ID MD5sum,
+** we have to do it again since unfortunately HashMessageID()
+** lowercases the Message-ID first. We also need to remain
+** compatible with Diablo's hashfeed.
+*/
+
+static unsigned int
+HashFeedMD5(char *MessageID, unsigned int offset)
+{
+ static char LastMessageID[128];
+ static char *LastMessageIDPtr;
+ static struct md5_context context;
+ unsigned int ret;
+
+ if (offset > 12)
+ return 0;
+
+ /* Some light caching. */
+ if (MessageID != LastMessageIDPtr ||
+ strcmp(MessageID, LastMessageID) != 0) {
+ md5_init(&context);
+ md5_update(&context, (unsigned char *)MessageID, strlen(MessageID));
+ md5_final(&context);
+ LastMessageIDPtr = MessageID;
+ strncpy(LastMessageID, MessageID, sizeof(LastMessageID) - 1);
+ LastMessageID[sizeof(LastMessageID) - 1] = 0;
+ }
+
+ memcpy(&ret, &context.digest[12 - offset], 4);
+
+ return ntohl(ret);
+}
+
+/*
+** Old-style Diablo (< 5.1) quickhash.
+**
+*/
+static unsigned int
+HashFeedQH(char *MessageID, unsigned int *tmp)
+{
+ unsigned char *p;
+ int n;
+
+ if (*tmp != (unsigned int)-1)
+ return *tmp;
+
+ p = (unsigned char *)MessageID;
+ n = 0;
+ while (*p)
+ n += *p++;
+ *tmp = (unsigned int)n;
+
+ return *tmp;
+}
+
+/*
+** Return true if an element of the HASHFEEDLIST matches
+** the hash of the Message-ID.
+*/
+static bool
+HashFeedMatch(HASHFEEDLIST *hf, char *MessageID)
+{
+ unsigned int qh = (unsigned int)-1;
+ unsigned int h;
+
+ while (hf) {
+ if (hf->type == HASHFEED_MD5)
+ h = HashFeedMD5(MessageID, hf->offset);
+ else if (hf->type == HASHFEED_QH)
+ h = HashFeedQH(MessageID, &qh);
+ else
+ continue;
+ if ((h % hf->mod + 1) >= hf->begin &&
+ (h % hf->mod + 1) <= hf->end)
+ return true;
+ hf = hf->next;
+ }
+
+ return false;
+}
+
+/*
+** Propagate an article to the sites have "expressed an interest."
+*/
+static void
+ARTpropagate(ARTDATA *data, const char **hops, int hopcount, char **list,
+ bool ControlStore, bool OverviewCreated)
+{
+ HDRCONTENT *hc = data->HdrContent;
+ SITE *sp, *funnel;
+ int i, j, Groupcount, Followcount, Crosscount;
+ char *p, *q;
+ struct buffer *bp;
+ bool sendit;
+
+ /* Work out which sites should really get it. */
+ Groupcount = data->Groupcount;
+ Followcount = data->Followcount;
+ Crosscount = Groupcount + Followcount * Followcount;
+ for (sp = Sites, i = nSites; --i >= 0; sp++) {
+ if ((sp->IgnoreControl && ControlStore) ||
+ (sp->NeedOverviewCreation && !OverviewCreated))
+ sp->Sendit = false;
+ if (sp->Seenit || !sp->Sendit)
+ continue;
+ sp->Sendit = false;
+
+ if (sp->Originator) {
+ if (!HDR_FOUND(HDR__XTRACE)) {
+ if (!sp->FeedwithoutOriginator)
+ continue;
+ } else {
+ if ((p = strchr(HDR(HDR__XTRACE), ' ')) != NULL) {
+ *p = '\0';
+ for (j = 0, sendit = false; (q = sp->Originator[j]) != NULL; j++) {
+ if (*q == '@') {
+ if (uwildmat(HDR(HDR__XTRACE), &q[1])) {
+ *p = ' ';
+ sendit = false;
+ break;
+ }
+ } else {
+ if (uwildmat(HDR(HDR__XTRACE), q))
+ sendit = true;
+ }
+ }
+ *p = ' ';
+ if (!sendit)
+ continue;
+ } else
+ continue;
+ }
+ }
+
+ if (sp->Master != NOSITE && Sites[sp->Master].Seenit)
+ continue;
+
+ if (sp->MaxSize && data->BytesValue > sp->MaxSize)
+ /* Too big for the site. */
+ continue;
+
+ if (sp->MinSize && data->BytesValue < sp->MinSize)
+ /* Too small for the site. */
+ continue;
+
+ if ((sp->Hops && hopcount > sp->Hops)
+ || (!sp->IgnorePath && ListHas(hops, sp->Name))
+ || (sp->Groupcount && Groupcount > sp->Groupcount)
+ || (sp->Followcount && Followcount > sp->Followcount)
+ || (sp->Crosscount && Crosscount > sp->Crosscount))
+ /* Site already saw the article; path too long; or too much
+ * cross-posting. */
+ continue;
+
+ if (sp->HashFeedList &&
+ !HashFeedMatch(sp->HashFeedList, HDR(HDR__MESSAGE_ID)))
+ /* hashfeed doesn't match */
+ continue;
+
+ if (list && *list != NULL && sp->Distributions &&
+ !DISTwantany(sp->Distributions, list))
+ /* Not in the site's desired list of distributions. */
+ continue;
+ if (sp->DistRequired && list == NULL)
+ /* Site requires Distribution header and there isn't one. */
+ continue;
+
+ if (sp->Exclusions) {
+ for (j = 0; (p = sp->Exclusions[j]) != NULL; j++)
+ if (ListHas(hops, p))
+ break;
+ if (p != NULL)
+ /* A host in the site's exclusion list was in the Path. */
+ continue;
+ }
+
+ /* Write that the site is getting it, and flag to send it. */
+ if (innconf->logsitename) {
+ if (fprintf(Log, " %s", sp->Name) == EOF || ferror(Log)) {
+ j = errno;
+ syslog(L_ERROR, "%s cant write log_site %m", LogName);
+ IOError("logging site", j);
+ clearerr(Log);
+ }
+ }
+ sp->Sendit = true;
+ sp->Seenit = true;
+ if (sp->Master != NOSITE)
+ Sites[sp->Master].Seenit = true;
+ }
+ if (putc('\n', Log) == EOF
+ || (!BufferedLogs && fflush(Log))
+ || ferror(Log)) {
+ syslog(L_ERROR, "%s cant write log_end %m", LogName);
+ clearerr(Log);
+ }
+
+ /* Handle funnel sites. */
+ for (sp = Sites, i = nSites; --i >= 0; sp++) {
+ if (sp->Sendit && sp->Funnel != NOSITE) {
+ sp->Sendit = false;
+ funnel = &Sites[sp->Funnel];
+ funnel->Sendit = true;
+ if (funnel->FNLwantsnames) {
+ bp = &funnel->FNLnames;
+ p = &bp->data[bp->used];
+ if (bp->used) {
+ *p++ = ' ';
+ bp->used++;
+ }
+ bp->used += strlcpy(p, sp->Name, bp->size - bp->used);
+ }
+ }
+ }
+}
+
+/*
+** Build up the overview data.
+*/
+static void
+ARTmakeoverview(CHANNEL *cp)
+{
+ ARTDATA *data = &cp->Data;
+ HDRCONTENT *hc = data->HdrContent;
+ static char SEP[] = "\t";
+ static char COLONSPACE[] = ": ";
+ struct buffer *overview = &data->Overview;
+ ARTOVERFIELD *fp;
+ const ARTHEADER *hp;
+ char *p, *q;
+ int i, j, len;
+ char *key_old_value = NULL;
+ int key_old_length = 0;
+
+ if (ARTfields == NULL) {
+ /* User error. */
+ return;
+ }
+
+ /* Setup. */
+ buffer_resize(overview, MAXHEADERSIZE);
+ buffer_set(overview, "", 0);
+
+ /* Write the data, a field at a time. */
+ for (fp = ARTfields; fp->Header; fp++) {
+ if (fp != ARTfields)
+ buffer_append(overview, SEP, strlen(SEP));
+ hp = fp->Header;
+ j = hp - ARTheaders;
+
+ /* If requested, generate keywords from the body of the article and patch
+ them into the apparent value of the Keywords header so that they make
+ it into overview. */
+ if (DO_KEYWORDS && innconf->keywords) {
+ /* Ensure that there are Keywords: to shovel. */
+ if (hp == &ARTheaders[HDR__KEYWORDS]) {
+ key_old_value = HDR(HDR__KEYWORDS);
+ key_old_length = HDR_LEN(HDR__KEYWORDS);
+ KEYgenerate(&hc[HDR__KEYWORDS], cp->In.data + data->Body,
+ key_old_value, key_old_length);
+ }
+ }
+
+ switch (j) {
+ case HDR__BYTES:
+ p = data->Bytes + 7; /* skip "Bytes: " */
+ len = data->BytesLength;
+ break;
+ case HDR__XREF:
+ if (innconf->xrefslave) {
+ p = HDR(j);
+ len = HDR_LEN(j);
+ } else {
+ p = data->Xref;
+ len = data->XrefLength - 2;
+ }
+ break;
+ default:
+ p = HDR(j);
+ len = HDR_LEN(j);
+ break;
+ }
+ if (len == 0)
+ continue;
+ if (fp->NeedHeader) {
+ buffer_append(overview, hp->Name, hp->Size);
+ buffer_append(overview, COLONSPACE, strlen(COLONSPACE));
+ }
+ if (overview->used + overview->left + len > overview->size)
+ buffer_resize(overview, overview->size + len);
+ for (i = 0, q = overview->data + overview->left; i < len; p++, i++) {
+ if (*p == '\r' && i < len - 1 && p[1] == '\n') {
+ p++;
+ i++;
+ continue;
+ }
+ if (*p == '\0' || *p == '\t' || *p == '\n' || *p == '\r')
+ *q++ = ' ';
+ else
+ *q++ = *p;
+ overview->left++;
+ }
+
+ /* Patch the old keywords back in. */
+ if (DO_KEYWORDS && innconf->keywords) {
+ if (key_old_value) {
+ if (hc->Value)
+ free(hc->Value); /* malloc'd within */
+ hc->Value = key_old_value;
+ hc->Length = key_old_length;
+ key_old_value = NULL;
+ }
+ }
+ }
+}
+
+/*
+** This routine is the heart of it all. Take a full article, parse it,
+** file or reject it, feed it to the other sites. Return the NNTP
+** message to send back.
+*/
+bool
+ARTpost(CHANNEL *cp)
+{
+ char *p, **groups, ControlWord[SMBUF], **hops, *controlgroup;
+ int i, j, *isp, hopcount, oerrno, canpost;
+ NEWSGROUP *ngp, **ngptr;
+ SITE *sp;
+ ARTDATA *data = &cp->Data;
+ HDRCONTENT *hc = data->HdrContent;
+ bool Approved, Accepted, LikeNewgroup, ToGroup, GroupMissing;
+ bool NoHistoryUpdate, artclean;
+ bool ControlStore = false;
+ bool NonExist = false;
+ bool OverviewCreated = false;
+ bool IsControl = false;
+ bool Filtered = false;
+ struct buffer *article;
+ HASH hash;
+ TOKEN token;
+ char *groupbuff[2];
+#if defined(DO_PERL) || defined(DO_PYTHON)
+ char *filterrc;
+#endif /* defined(DO_PERL) || defined(DO_PYTHON) */
+ OVADDRESULT result;
+
+ /* Preliminary clean-ups. */
+ article = &cp->In;
+ artclean = ARTclean(data, cp->Error);
+
+ /* If we don't have Path or Message-ID, we can't continue. */
+ if (!artclean && (!HDR_FOUND(HDR__PATH) || !HDR_FOUND(HDR__MESSAGE_ID)))
+ return false;
+ hopcount = ARTparsepath(HDR(HDR__PATH), HDR_LEN(HDR__PATH), &data->Path);
+ if (hopcount == 0) {
+ snprintf(cp->Error, sizeof(cp->Error), "%d illegal path element",
+ NNTP_REJECTIT_VAL);
+ return false;
+ }
+ hops = data->Path.List;
+
+ if (innconf->logipaddr) {
+ data->Feedsite = RChostname(cp);
+ if (data->Feedsite == NULL)
+ data->Feedsite = CHANname(cp);
+ if (strcmp("0.0.0.0", data->Feedsite) == 0 || data->Feedsite[0] == '\0')
+ data->Feedsite = hops && hops[0] ? hops[0] : CHANname(cp);
+ } else {
+ data->Feedsite = hops && hops[0] ? hops[0] : CHANname(cp);
+ }
+ data->FeedsiteLength = strlen(data->Feedsite);
+
+ hash = HashMessageID(HDR(HDR__MESSAGE_ID));
+ data->Hash = &hash;
+ if (HIScheck(History, HDR(HDR__MESSAGE_ID))) {
+ snprintf(cp->Error, sizeof(cp->Error), "%d Duplicate", NNTP_REJECTIT_VAL);
+ ARTlog(data, ART_REJECT, cp->Error);
+ ARTreject(REJECT_DUPLICATE, cp, article);
+ return false;
+ }
+ if (!artclean) {
+ ARTlog(data, ART_REJECT, cp->Error);
+ if (innconf->remembertrash && (Mode == OMrunning) &&
+ !InndHisRemember(HDR(HDR__MESSAGE_ID)))
+ syslog(L_ERROR, "%s cant write history %s %m", LogName,
+ HDR(HDR__MESSAGE_ID));
+ ARTreject(REJECT_OTHER, cp, article);
+ return false;
+ }
+
+ i = strlen(hops[0]);
+ if (i == Path.used - 1 &&
+ strncmp(Path.data, hops[0], Path.used - 1) == 0)
+ data->Hassamepath = true;
+ else
+ data->Hassamepath = false;
+ if (Pathcluster.data != NULL &&
+ i == Pathcluster.used - 1 &&
+ strncmp(Pathcluster.data, hops[0], Pathcluster.used - 1) == 0)
+ data->Hassamecluster = true;
+ else
+ data->Hassamecluster = false;
+ if (Pathalias.data != NULL &&
+ !ListHas((const char **)hops, (const char *)innconf->pathalias))
+ data->AddAlias = true;
+ else
+ data->AddAlias = false;
+
+ /* And now check the path for unwanted sites -- Andy */
+ for(j = 0 ; ME.Exclusions && ME.Exclusions[j] ; j++) {
+ if (ListHas((const char **)hops, (const char *)ME.Exclusions[j])) {
+ snprintf(cp->Error, sizeof(cp->Error), "%d Unwanted site %s in path",
+ NNTP_REJECTIT_VAL, MaxLength(ME.Exclusions[j], ME.Exclusions[j]));
+ ARTlog(data, ART_REJECT, cp->Error);
+ if (innconf->remembertrash && (Mode == OMrunning) &&
+ !InndHisRemember(HDR(HDR__MESSAGE_ID)))
+ syslog(L_ERROR, "%s cant write history %s %m", LogName,
+ HDR(HDR__MESSAGE_ID));
+ ARTreject(REJECT_SITE, cp, article);
+ return false;
+ }
+ }
+
+#if defined(DO_PERL) || defined(DO_PYTHON)
+ filterPath = HDR(HDR__PATH);
+#endif /* DO_PERL || DO_PYHTON */
+
+#if defined(DO_PYTHON)
+ TMRstart(TMR_PYTHON);
+ filterrc = PYartfilter(data, article->data + data->Body,
+ cp->Next - data->Body, data->Lines);
+ TMRstop(TMR_PYTHON);
+ if (filterrc != NULL) {
+ if (innconf->dontrejectfiltered) {
+ Filtered = true;
+ } else {
+ snprintf(cp->Error, sizeof(cp->Error), "%d %.200s", NNTP_REJECTIT_VAL,
+ filterrc);
+ syslog(L_NOTICE, "rejecting[python] %s %s", HDR(HDR__MESSAGE_ID),
+ cp->Error);
+ ARTlog(data, ART_REJECT, cp->Error);
+ if (innconf->remembertrash && (Mode == OMrunning) &&
+ !InndHisRemember(HDR(HDR__MESSAGE_ID)))
+ syslog(L_ERROR, "%s cant write history %s %m", LogName,
+ HDR(HDR__MESSAGE_ID));
+ ARTreject(REJECT_FILTER, cp, article);
+ return false;
+ }
+ }
+#endif /* DO_PYTHON */
+
+ /* I suppose some masochist will run with Python and Perl in together */
+
+#if defined(DO_PERL)
+ TMRstart(TMR_PERL);
+ filterrc = PLartfilter(data, article->data + data->Body,
+ cp->Next - data->Body, data->Lines);
+ TMRstop(TMR_PERL);
+ if (filterrc) {
+ if (innconf->dontrejectfiltered) {
+ Filtered = true;
+ } else {
+ snprintf(cp->Error, sizeof(cp->Error), "%d %.200s", NNTP_REJECTIT_VAL,
+ filterrc);
+ syslog(L_NOTICE, "rejecting[perl] %s %s", HDR(HDR__MESSAGE_ID),
+ cp->Error);
+ ARTlog(data, ART_REJECT, cp->Error);
+ if (innconf->remembertrash && (Mode == OMrunning) &&
+ !InndHisRemember(HDR(HDR__MESSAGE_ID)))
+ syslog(L_ERROR, "%s cant write history %s %m", LogName,
+ HDR(HDR__MESSAGE_ID));
+ ARTreject(REJECT_FILTER, cp, article);
+ return false;
+ }
+ }
+#endif /* DO_PERL */
+
+ /* I suppose some masochist will run with both TCL and Perl in together */
+
+#if defined(DO_TCL)
+ if (TCLFilterActive) {
+ int code;
+ const ARTHEADER *hp;
+
+ /* make info available to Tcl */
+
+ TCLCurrArticle = article;
+ TCLCurrData = data;
+ Tcl_UnsetVar(TCLInterpreter, "Body", TCL_GLOBAL_ONLY);
+ Tcl_UnsetVar(TCLInterpreter, "Headers", TCL_GLOBAL_ONLY);
+ for (i = 0 ; i < MAX_ARTHEADER ; i++, hc++) {
+ if (HDR_FOUND(i)) {
+ hp = &ARTheaders[i];
+ Tcl_SetVar2(TCLInterpreter, "Headers", (char *) hp->Name, HDR(i),
+ TCL_GLOBAL_ONLY);
+ }
+ }
+ Tcl_SetVar(TCLInterpreter, "Body", article->data + data->Body,
+ TCL_GLOBAL_ONLY);
+ /* call filter */
+
+ code = Tcl_Eval(TCLInterpreter, "filter_news");
+ Tcl_UnsetVar(TCLInterpreter, "Body", TCL_GLOBAL_ONLY);
+ Tcl_UnsetVar(TCLInterpreter, "Headers", TCL_GLOBAL_ONLY);
+ if (code == TCL_OK) {
+ if (strcmp(TCLInterpreter->result, "accept") != 0) {
+ if (innconf->dontrejectfiltered) {
+ Filtered = true;
+ } else {
+ snprintf(cp->Error, sizeof(cp->Error), "%d %.200s",
+ NNTP_REJECTIT_VAL, TCLInterpreter->result);
+ syslog(L_NOTICE, "rejecting[tcl] %s %s", HDR(HDR__MESSAGE_ID),
+ cp->Error);
+ ARTlog(data, ART_REJECT, cp->Error);
+ if (innconf->remembertrash && (Mode == OMrunning) &&
+ !InndHisRemember(HDR(HDR__MESSAGE_ID)))
+ syslog(L_ERROR, "%s cant write history %s %m",
+ LogName, HDR(HDR__MESSAGE_ID));
+ ARTreject(REJECT_FILTER, cp, article);
+ return false;
+ }
+ }
+ } else {
+ /* the filter failed: complain and then turn off filtering */
+ syslog(L_ERROR, "TCL proc filter_news failed: %s",
+ TCLInterpreter->result);
+ TCLfilter(false);
+ }
+ }
+#endif /* defined(DO_TCL) */
+
+ /* If we limit what distributions we get, see if we want this one. */
+ if (HDR_FOUND(HDR__DISTRIBUTION)) {
+ if (HDR(HDR__DISTRIBUTION)[0] == ',') {
+ snprintf(cp->Error, sizeof(cp->Error), "%d bogus distribution \"%s\"",
+ NNTP_REJECTIT_VAL,
+ MaxLength(HDR(HDR__DISTRIBUTION), HDR(HDR__DISTRIBUTION)));
+ ARTlog(data, ART_REJECT, cp->Error);
+ if (innconf->remembertrash && Mode == OMrunning &&
+ !InndHisRemember(HDR(HDR__MESSAGE_ID)))
+ syslog(L_ERROR, "%s cant write history %s %m", LogName,
+ HDR(HDR__MESSAGE_ID));
+ ARTreject(REJECT_DISTRIB, cp, article);
+ return false;
+ } else {
+ ARTparsedist(HDR(HDR__DISTRIBUTION), HDR_LEN(HDR__DISTRIBUTION),
+ &data->Distribution);
+ if (ME.Distributions &&
+ !DISTwantany(ME.Distributions, data->Distribution.List)) {
+ snprintf(cp->Error, sizeof(cp->Error),
+ "%d Unwanted distribution \"%s\"", NNTP_REJECTIT_VAL,
+ MaxLength(data->Distribution.List[0],
+ data->Distribution.List[0]));
+ ARTlog(data, ART_REJECT, cp->Error);
+ if (innconf->remembertrash && (Mode == OMrunning) &&
+ !InndHisRemember(HDR(HDR__MESSAGE_ID)))
+ syslog(L_ERROR, "%s cant write history %s %m",
+ LogName, HDR(HDR__MESSAGE_ID));
+ ARTreject(REJECT_DISTRIB, cp, article);
+ return false;
+ }
+ }
+ } else {
+ ARTparsedist("", 0, &data->Distribution);
+ }
+
+ for (i = nSites, sp = Sites; --i >= 0; sp++) {
+ sp->Poison = false;
+ sp->Sendit = false;
+ sp->Seenit = false;
+ sp->FNLnames.used = 0;
+ sp->ng = NULL;
+ }
+
+ if (HDR_FOUND(HDR__FOLLOWUPTO)) {
+ for (i = 0, p = HDR(HDR__FOLLOWUPTO) ; (p = strchr(p, ',')) != NULL ;
+ i++, p++)
+ continue;
+ data->Followcount = i;
+ }
+ if (data->Followcount == 0)
+ data->Followcount = data->Groupcount;
+
+ groups = data->Newsgroups.List;
+ /* Parse the Control header. */
+ LikeNewgroup = false;
+ if (HDR_FOUND(HDR__CONTROL)) {
+ IsControl = true;
+
+ /* Nip off the first word into lowercase. */
+ strlcpy(ControlWord, HDR(HDR__CONTROL), sizeof(ControlWord));
+ for (p = ControlWord; *p && !ISWHITE(*p); p++)
+ if (CTYPE(isupper, *p))
+ *p = tolower(*p);
+ *p = '\0';
+ LikeNewgroup = (strcmp(ControlWord, "newgroup") == 0
+ || strcmp(ControlWord, "rmgroup") == 0);
+
+ if (innconf->ignorenewsgroups && LikeNewgroup) {
+ for (p++; *p && ISWHITE(*p); p++);
+ groupbuff[0] = p;
+ for (p++; *p; p++) {
+ if (NG_ISSEP(*p)) {
+ *p = '\0';
+ break;
+ }
+ }
+ p = groupbuff[0];
+ for (p++; *p; p++) {
+ if (ISWHITE(*p)) {
+ *p = '\0';
+ break;
+ }
+ }
+ groupbuff[1] = NULL;
+ groups = groupbuff;
+ data->Groupcount = 2;
+ if (data->Followcount == 0)
+ data->Followcount = data->Groupcount;
+ }
+
+ LikeNewgroup = (LikeNewgroup || strcmp(ControlWord, "checkgroups") == 0);
+
+ /* Control messages to "foo.ctl" are treated as if they were
+ * posted to "foo". I should probably apologize for all the
+ * side-effects in the if. */
+ for (i = 0; (p = groups[i++]) != NULL; )
+ if ((j = strlen(p) - 4) > 0 && *(p += j) == '.'
+ && p[1] == 'c' && p[2] == 't' && p[3] == 'l')
+ *p = '\0';
+ }
+
+ /* Loop over the newsgroups, see which ones we want, and get the
+ * total space needed for the Xref line. At the end of this section
+ * of code, j will have the needed length, the appropriate site
+ * entries will have their Sendit and ng fields set, and GroupPointers
+ * will have pointers to the relevant newsgroups. */
+ ToGroup = NoHistoryUpdate = false;
+ Approved = HDR_FOUND(HDR__APPROVED);
+ ngptr = GroupPointers;
+ for (GroupMissing = Accepted = false; (p = *groups) != NULL; groups++) {
+ if ((ngp = NGfind(p)) == NULL) {
+ GroupMissing = true;
+ if (LikeNewgroup && Approved) {
+ /* Checkgroups/newgroup/rmgroup being sent to a group that doesn't
+ * exist. Assume it is being sent to the group being created or
+ * removed (or to the admin group to which the checkgroups is posted),
+ * and send it to all sites that would or would have had the group
+ * if it were created. */
+ ARTsendthegroup(*groups);
+ Accepted = true;
+ } else
+ NonExist = true;
+ ARTpoisongroup(*groups);
+
+ if (innconf->mergetogroups) {
+ /* Try to collapse all "to" newsgroups. */
+ if (*p != 't' || *++p != 'o' || *++p != '.' || *++p == '\0')
+ continue;
+ ngp = NGfind("to");
+ ToGroup = true;
+ if ((sp = SITEfind(p)) != NULL) {
+ SITEmark(sp, ngp);
+ }
+ } else {
+ continue;
+ }
+ }
+
+ ngp->PostCount = 0;
+ /* Ignore this group? */
+ if (ngp->Rest[0] == NF_FLAG_IGNORE) {
+ /* See if any of this group's sites considers this group poison. */
+ for (isp = ngp->Poison, i = ngp->nPoison; --i >= 0; isp++)
+ if (*isp >= 0)
+ Sites[*isp].Poison = true;
+ continue;
+ }
+
+ /* Basic validity check. */
+ if (ngp->Rest[0] == NF_FLAG_MODERATED && !Approved) {
+ snprintf(cp->Error, sizeof(cp->Error), "%d Unapproved for \"%s\"",
+ NNTP_REJECTIT_VAL, MaxLength(ngp->Name, ngp->Name));
+ ARTlog(data, ART_REJECT, cp->Error);
+ if (innconf->remembertrash && (Mode == OMrunning) &&
+ !InndHisRemember(HDR(HDR__MESSAGE_ID)))
+ syslog(L_ERROR, "%s cant write history %s %m", LogName,
+ HDR(HDR__MESSAGE_ID));
+ ARTreject(REJECT_UNAPP, cp, article);
+ return false;
+ }
+
+ /* See if any of this group's sites considers this group poison. */
+ for (isp = ngp->Poison, i = ngp->nPoison; --i >= 0; isp++)
+ if (*isp >= 0)
+ Sites[*isp].Poison = true;
+
+ /* Check if we accept articles in this group from this peer, after
+ poisoning. This means that articles that we accept from them will
+ be handled correctly if they're crossposted. */
+ canpost = RCcanpost(cp, p);
+ if (!canpost) { /* At least one group cannot be fed by this peer.
+ If we later reject the post as unwanted group,
+ don't remember it. If we accept, do remember */
+ NoHistoryUpdate = true;
+ continue;
+ } else if (canpost < 0) {
+ snprintf(cp->Error, sizeof(cp->Error),
+ "%d Won't accept posts in \"%s\"", NNTP_REJECTIT_VAL,
+ MaxLength(p, p));
+ ARTlog(data, ART_REJECT, cp->Error);
+ ARTreject(REJECT_GROUP, cp, article);
+ return false;
+ }
+
+ /* Valid group, feed it to that group's sites. */
+ Accepted = true;
+ for (isp = ngp->Sites, i = ngp->nSites; --i >= 0; isp++) {
+ if (*isp >= 0) {
+ sp = &Sites[*isp];
+ if (!sp->Poison)
+ SITEmark(sp, ngp);
+ }
+ }
+
+ /* If it's excluded, don't file it. */
+ if (ngp->Rest[0] == NF_FLAG_EXCLUDED)
+ continue;
+
+ /* Expand aliases, mark the article as getting filed in the group. */
+ if (ngp->Alias != NULL)
+ ngp = ngp->Alias;
+ *ngptr++ = ngp;
+ ngp->PostCount = 0;
+ }
+
+ /* Loop over sites to find Poisons/ControlOnly and undo Sendit flags. */
+ for (i = nSites, sp = Sites; --i >= 0; sp++) {
+ if (sp->Poison || (sp->ControlOnly && !IsControl)
+ || (sp->DontWantNonExist && NonExist))
+ sp->Sendit = false;
+ }
+
+ /* Control messages not filed in "to" get filed only in control.name
+ * or control. */
+ if (IsControl && Accepted && !ToGroup) {
+ ControlStore = true;
+ controlgroup = concat("control.", ControlWord, (char *) 0);
+ if ((ngp = NGfind(controlgroup)) == NULL)
+ ngp = NGfind(ARTctl);
+ free(controlgroup);
+ ngp->PostCount = 0;
+ ngptr = GroupPointers;
+ *ngptr++ = ngp;
+ for (isp = ngp->Sites, i = ngp->nSites; --i >= 0; isp++) {
+ if (*isp >= 0) {
+ /* Checkgroups/newgroup/rmgroup posted to local.example
+ * will still be sent with the newsfeeds patterns
+ * "*,!local.*" and "*,@local.*". So as not to propagate
+ * them, "!control,!control.*" should be added. */
+ sp = &Sites[*isp];
+ SITEmark(sp, ngp);
+ }
+ }
+ }
+
+ /* If !Accepted, then none of the article's newgroups exist in our
+ * active file. Proper action is to drop the article on the floor.
+ * If ngp == GroupPointers, then all the new articles newsgroups are
+ * "j" entries in the active file. In that case, we have to file it
+ * under junk so that downstream feeds can get it. */
+ if (!Accepted || ngptr == GroupPointers) {
+ if (!Accepted) {
+ if (NoHistoryUpdate) {
+ snprintf(cp->Error, sizeof(cp->Error), "%d Can't post to \"%s\"",
+ NNTP_REJECTIT_VAL, MaxLength(data->Newsgroups.List[0],
+ data->Newsgroups.List[0]));
+ } else {
+ snprintf(cp->Error, sizeof(cp->Error),
+ "%d Unwanted newsgroup \"%s\"", NNTP_REJECTIT_VAL,
+ MaxLength(data->Newsgroups.List[0],
+ data->Newsgroups.List[0]));
+ }
+ ARTlog(data, ART_REJECT, cp->Error);
+ if (!innconf->wanttrash) {
+ if (innconf->remembertrash && (Mode == OMrunning) &&
+ !NoHistoryUpdate && !InndHisRemember(HDR(HDR__MESSAGE_ID)))
+ syslog(L_ERROR, "%s cant write history %s %m",
+ LogName, HDR(HDR__MESSAGE_ID));
+ ARTreject(REJECT_GROUP, cp, article);
+ return false;
+ } else {
+ /* if !GroupMissing, then all the groups the article was posted
+ * to have a flag of "x" in our active file, and therefore
+ * we should throw the article away: if you have set
+ * innconf->remembertrash true, then you want all trash except that
+ * which you explicitly excluded in your active file. */
+ if (!GroupMissing) {
+ if (innconf->remembertrash && (Mode == OMrunning) &&
+ !NoHistoryUpdate && !InndHisRemember(HDR(HDR__MESSAGE_ID)))
+ syslog(L_ERROR, "%s cant write history %s %m",
+ LogName, HDR(HDR__MESSAGE_ID));
+ ARTreject(REJECT_GROUP, cp, article);
+ return false;
+ }
+ }
+ }
+ ngp = NGfind(ARTjnk);
+ *ngptr++ = ngp;
+ ngp->PostCount = 0;
+
+ /* Junk can be fed to other sites. */
+ for (isp = ngp->Sites, i = ngp->nSites; --i >= 0; isp++) {
+ if (*isp >= 0) {
+ sp = &Sites[*isp];
+ if (!sp->Poison && !(sp->ControlOnly && !IsControl))
+ SITEmark(sp, ngp);
+ }
+ }
+ }
+ *ngptr = NULL;
+
+ if (innconf->xrefslave) {
+ if (ARTxrefslave(data) == false) {
+ if (HDR_FOUND(HDR__XREF)) {
+ snprintf(cp->Error, sizeof(cp->Error),
+ "%d Xref header \"%s\" invalid in xrefslave mode",
+ NNTP_REJECTIT_VAL,
+ MaxLength(HDR(HDR__XREF), HDR(HDR__XREF)));
+ } else {
+ snprintf(cp->Error, sizeof(cp->Error),
+ "%d Xref header required in xrefslave mode",
+ NNTP_REJECTIT_VAL);
+ }
+ ARTlog(data, ART_REJECT, cp->Error);
+ ARTreject(REJECT_OTHER, cp, article);
+ return false;
+ }
+ } else {
+ ARTassignnumbers(data);
+ }
+
+ /* Now we can file it. */
+ if (++ICDactivedirty >= innconf->icdsynccount) {
+ ICDwriteactive();
+ ICDactivedirty = 0;
+ }
+ TMRstart(TMR_ARTWRITE);
+ for (i = 0; (ngp = GroupPointers[i]) != NULL; i++)
+ ngp->PostCount = 0;
+
+ token = ARTstore(cp);
+ /* change trailing '\r\n' to '\0\n' of all system header */
+ for (i = 0 ; i < MAX_ARTHEADER ; i++) {
+ if (HDR_FOUND(i))
+ HDR_PARSE_START(i);
+ }
+ if (token.type == TOKEN_EMPTY) {
+ syslog(L_ERROR, "%s cant store article: %s", LogName, SMerrorstr);
+ snprintf(cp->Error, sizeof(cp->Error), "%d cant store article",
+ NNTP_RESENDIT_VAL);
+ ARTlog(data, ART_REJECT, cp->Error);
+ if ((Mode == OMrunning) && !InndHisRemember(HDR(HDR__MESSAGE_ID)))
+ syslog(L_ERROR, "%s cant write history %s %m", LogName,
+ HDR(HDR__MESSAGE_ID));
+ ARTreject(REJECT_OTHER, cp, article);
+ TMRstop(TMR_ARTWRITE);
+ return false;
+ }
+ TMRstop(TMR_ARTWRITE);
+ if ((innconf->enableoverview && !innconf->useoverchan) || NeedOverview) {
+ TMRstart(TMR_OVERV);
+ ARTmakeoverview(cp);
+ if (innconf->enableoverview && !innconf->useoverchan) {
+ if ((result = OVadd(token, data->Overview.data, data->Overview.left,
+ data->Arrived, data->Expires)) == OVADDFAILED) {
+ if (OVctl(OVSPACE, (void *)&i) && i == OV_NOSPACE)
+ IOError("creating overview", ENOSPC);
+ else
+ IOError("creating overview", 0);
+ syslog(L_ERROR, "%s cant store overview for %s", LogName,
+ TokenToText(token));
+ OverviewCreated = false;
+ } else {
+ if (result == OVADDCOMPLETED)
+ OverviewCreated = true;
+ else
+ OverviewCreated = false;
+ }
+ }
+ TMRstop(TMR_OVERV);
+ }
+ strlcpy(data->TokenText, TokenToText(token), sizeof(data->TokenText));
+
+ /* Update history if we didn't get too many I/O errors above. */
+ if ((Mode != OMrunning) ||
+ !InndHisWrite(HDR(HDR__MESSAGE_ID), data->Arrived, data->Posted,
+ data->Expires, &token)) {
+ i = errno;
+ syslog(L_ERROR, "%s cant write history %s %m", LogName,
+ HDR(HDR__MESSAGE_ID));
+ snprintf(cp->Error, sizeof(cp->Error), "%d cant write history, %s",
+ NNTP_RESENDIT_VAL, strerror(errno));
+ ARTlog(data, ART_REJECT, cp->Error);
+ ARTreject(REJECT_OTHER, cp, article);
+ return false;
+ }
+
+ if (NeedStoredGroup)
+ data->StoredGroupLength = strlen(data->Newsgroups.List[0]);
+
+ /* Start logging, then propagate the article. */
+ if (data->CRwithoutLF > 0 || data->LFwithoutCR > 0) {
+ if (data->CRwithoutLF > 0 && data->LFwithoutCR == 0)
+ snprintf(cp->Error, sizeof(cp->Error),
+ "%d article includes CR without LF(%d)",
+ NNTP_REJECTIT_VAL, data->CRwithoutLF);
+ else if (data->CRwithoutLF == 0 && data->LFwithoutCR > 0)
+ snprintf(cp->Error, sizeof(cp->Error),
+ "%d article includes LF without CR(%d)",
+ NNTP_REJECTIT_VAL, data->LFwithoutCR);
+ else
+ snprintf(cp->Error, sizeof(cp->Error),
+ "%d article includes CR without LF(%d) and LF withtout CR(%d)",
+ NNTP_REJECTIT_VAL, data->CRwithoutLF, data->LFwithoutCR);
+ ARTlog(data, ART_STRSTR, cp->Error);
+ }
+ ARTlog(data, Accepted ? ART_ACCEPT : ART_JUNK, (char *)NULL);
+ if ((innconf->nntplinklog) &&
+ (fprintf(Log, " (%s)", data->TokenText) == EOF || ferror(Log))) {
+ oerrno = errno;
+ syslog(L_ERROR, "%s cant write log_nntplink %m", LogName);
+ IOError("logging nntplink", oerrno);
+ clearerr(Log);
+ }
+ /* Calculate Max Article Time */
+ i = Now.time - cp->ArtBeg;
+ if(i > cp->ArtMax)
+ cp->ArtMax = i;
+ cp->ArtBeg = 0;
+
+ cp->Size += data->BytesValue;
+ if (innconf->logartsize) {
+ if (fprintf(Log, " %ld", data->BytesValue) == EOF || ferror (Log)) {
+ oerrno = errno;
+ syslog(L_ERROR, "%s cant write artsize %m", LogName);
+ IOError("logging artsize", oerrno);
+ clearerr(Log);
+ }
+ }
+
+ ARTpropagate(data, (const char **)hops, hopcount, data->Distribution.List,
+ ControlStore, OverviewCreated);
+
+ /* Now that it's been written, process the control message. This has
+ * a small window, if we get a new article before the newgroup message
+ * has been processed. We could pause ourselves here, but it doesn't
+ * seem to be worth it. */
+ if (Accepted) {
+ if (IsControl) {
+ ARTcontrol(data, HDR(HDR__CONTROL), cp);
+ }
+ if (DoCancels && HDR_FOUND(HDR__SUPERSEDES)) {
+ if (ARTidok(HDR(HDR__SUPERSEDES)))
+ ARTcancel(data, HDR(HDR__SUPERSEDES), false);
+ }
+ }
+
+ /* And finally, send to everyone who should get it */
+ for (sp = Sites, i = nSites; --i >= 0; sp++) {
+ if (sp->Sendit) {
+ if (!Filtered || !sp->DropFiltered) {
+ TMRstart(TMR_SITESEND);
+ SITEsend(sp, data);
+ TMRstop(TMR_SITESEND);
+ }
+ }
+ }
+
+ return true;
+}
--- /dev/null
+/* $Id: cc.c 7748 2008-04-06 13:49:56Z iulius $
+**
+** Routines for the control channel.
+**
+** Create a Unix-domain datagram socket that processes on the local server
+** send messages to. The control channel is used only by ctlinnd to tell
+** the server to perform special functions. We use datagrams so that we
+** don't need to do an accept() and tie up another descriptor. recvfrom
+** seems to be broken on several systems, so the client passes in the
+** socket name.
+**
+** This module completely rips away all pretense of software layering.
+*/
+
+#include "config.h"
+#include "clibrary.h"
+
+#ifdef HAVE_UNIX_DOMAIN_SOCKETS
+# include <sys/un.h>
+#endif
+
+#include "inn/innconf.h"
+#include "inn/qio.h"
+#include "innd.h"
+#include "inndcomm.h"
+#include "innperl.h"
+
+/*
+** An entry in the dispatch table. The name, and implementing function,
+** of every command we support.
+*/
+typedef struct _CCDISPATCH {
+ char Name;
+ int argc;
+ const char * (*Function)(char *av[]);
+} CCDISPATCH;
+
+
+static const char * CCallow(char *av[]);
+static const char * CCbegin(char *av[]);
+static const char * CCchgroup(char *av[]);
+static const char * CCdrop(char *av[]);
+static const char * CCfeedinfo(char *av[]);
+static const char * CCflush(char *av[]);
+static const char * CCflushlogs(char *unused[]);
+static const char * CCgo(char *av[]);
+static const char * CChangup(char *av[]);
+static const char * CCreserve(char *av[]);
+static const char * CClogmode(char *unused[]);
+static const char * CCmode(char *unused[]);
+static const char * CCname(char *av[]);
+static const char * CCnewgroup(char *av[]);
+static const char * CCparam(char *av[]);
+static const char * CCpause(char *av[]);
+static const char * CCreaders(char *av[]);
+static const char * CCreject(char *av[]);
+static const char * CCreload(char *av[]);
+static const char * CCrenumber(char *av[]);
+static const char * CCrmgroup(char *av[]);
+static const char * CCsend(char *av[]);
+static const char * CCshutdown(char *av[]);
+static const char * CCsignal(char *av[]);
+static const char * CCstathist(char *av[]);
+static const char * CCstatus(char *av[]);
+static const char * CCthrottle(char *av[]);
+static const char * CCtimer(char *av[]);
+static const char * CCtrace(char *av[]);
+static const char * CCxabort(char *av[]);
+static const char * CCxexec(char *av[]);
+static const char * CCfilter(char *av[]);
+static const char * CCperl(char *av[]);
+static const char * CCpython(char *av[]);
+static const char * CClowmark(char *av[]);
+
+
+static char *CCpath = NULL;
+static char **CCargv;
+static char CCnosite[] = "1 No such site";
+static char CCwrongtype[] = "1 Wrong site type";
+static char CCnogroup[] = "1 No such group";
+static char CCnochannel[] = "1 No such channel";
+static char CCnoreason[] = "1 Empty reason";
+static char CCbigreason[] = "1 Reason too long";
+static char CCnotrunning[] = "1 Must be running";
+static struct buffer CCreply;
+static CHANNEL *CCchan;
+static int CCwriter;
+static CCDISPATCH CCcommands[] = {
+ { SC_ADDHIST, 5, CCaddhist },
+ { SC_ALLOW, 1, CCallow },
+ { SC_BEGIN, 1, CCbegin },
+ { SC_CANCEL, 1, CCcancel },
+ { SC_CHANGEGROUP, 2, CCchgroup },
+ { SC_CHECKFILE, 0, CCcheckfile },
+ { SC_DROP, 1, CCdrop },
+ { SC_FEEDINFO, 1, CCfeedinfo },
+ { SC_FILTER, 1, CCfilter },
+ { SC_PERL, 1, CCperl },
+ { SC_PYTHON, 1, CCpython },
+ { SC_FLUSH, 1, CCflush },
+ { SC_FLUSHLOGS, 0, CCflushlogs },
+ { SC_GO, 1, CCgo },
+ { SC_HANGUP, 1, CChangup },
+ { SC_LOGMODE, 0, CClogmode },
+ { SC_MODE, 0, CCmode },
+ { SC_NAME, 1, CCname },
+ { SC_NEWGROUP, 3, CCnewgroup },
+ { SC_PARAM, 2, CCparam },
+ { SC_PAUSE, 1, CCpause },
+ { SC_READERS, 2, CCreaders },
+ { SC_REJECT, 1, CCreject },
+ { SC_RENUMBER, 1, CCrenumber },
+ { SC_RELOAD, 2, CCreload },
+ { SC_RESERVE, 1, CCreserve },
+ { SC_RMGROUP, 1, CCrmgroup },
+ { SC_SEND, 2, CCsend },
+ { SC_SHUTDOWN, 1, CCshutdown },
+ { SC_SIGNAL, 2, CCsignal },
+ { SC_STATHIST, 1, CCstathist },
+ { SC_STATUS, 1, CCstatus },
+ { SC_THROTTLE, 1, CCthrottle },
+ { SC_TIMER, 1, CCtimer },
+ { SC_TRACE, 2, CCtrace },
+ { SC_XABORT, 1, CCxabort },
+ { SC_LOWMARK, 1, CClowmark },
+ { SC_XEXEC, 1, CCxexec }
+};
+
+static RETSIGTYPE CCresetup(int unused);
+\f
+
+void
+CCcopyargv(char *av[])
+{
+ char **v;
+ int i;
+
+ /* Get the vector size. */
+ for (i = 0; av[i]; i++)
+ continue;
+
+ /* Get the vector, copy each element. */
+ for (v = CCargv = xmalloc((i + 1) * sizeof(char *)); *av; av++) {
+ /* not to renumber */
+ if (strncmp(*av, "-r", 2) == 0)
+ continue;
+ *v++ = xstrdup(*av);
+ }
+ *v = NULL;
+}
+
+
+/*
+** Return a string representing our operating mode.
+*/
+static const char *
+CCcurrmode(void)
+{
+ static char buff[32];
+
+ /* Server's mode. */
+ switch (Mode) {
+ default:
+ snprintf(buff, sizeof(buff), "Unknown %d", Mode);
+ return buff;
+ case OMrunning:
+ return "running";
+ case OMpaused:
+ return "paused";
+ case OMthrottled:
+ return "throttled";
+ }
+}
+
+
+/*
+** Add <> around Message-ID if needed.
+*/
+static const char *
+CCgetid(char *p, const char **store)
+{
+ static char NULLMESGID[] = "1 Empty Message-ID";
+ static struct buffer Save = { 0, 0, 0, NULL };
+ int i;
+
+ if (*p == '\0')
+ return NULLMESGID;
+ if (*p == '<') {
+ if (p[1] == '\0' || p[1] == '>')
+ return NULLMESGID;
+ *store = p;
+ return NULL;
+ }
+
+ /* Make sure the Message-ID buffer has room. */
+ i = 1 + strlen(p) + 1 + 1;
+ buffer_resize(&Save, i);
+ *store = Save.data;
+ snprintf(Save.data, Save.size, "<%s>", p);
+ return NULL;
+}
+
+
+/*
+** Abort and dump core.
+*/
+static const char *
+CCxabort(char *av[])
+{
+ syslog(L_FATAL, "%s abort %s", LogName, av[0]);
+ abort();
+ syslog(L_FATAL, "%s cant abort %m", LogName);
+ CleanupAndExit(1, av[0]);
+ /* NOTREACHED */
+}
+
+
+/*
+** Do the work needed to add a history entry.
+*/
+const char *
+CCaddhist(char *av[])
+{
+ static char DIGITS[] = "0123456789";
+ ARTDATA Data;
+ const char * p, *msgid;
+ bool ok;
+ TOKEN token;
+
+ /* You must pass a <message-id> ID, the history API will hash it as it
+ * wants */
+ if ((p = CCgetid(av[0], &msgid)) != NULL)
+ return p;
+
+ /* If paused, don't try to use the history database since expire may be
+ running */
+ if (Mode == OMpaused)
+ return "1 Server paused";
+
+ /* If throttled by admin, briefly open the history database. */
+ if (Mode != OMrunning) {
+ if (ThrottledbyIOError)
+ return "1 Server throttled";
+ InndHisOpen();
+ }
+
+ if (HIScheck(History, msgid)) {
+ if (Mode != OMrunning) InndHisClose();
+ return "1 Duplicate";
+ }
+ if (Mode != OMrunning) InndHisClose();
+ if (strspn(av[1], DIGITS) != strlen(av[1]))
+ return "1 Bad arrival date";
+ Data.Arrived = atol(av[1]);
+ if (strspn(av[2], DIGITS) != strlen(av[2]))
+ return "1 Bad expiration date";
+ Data.Expires = atol(av[2]);
+ if (strspn(av[3], DIGITS) != strlen(av[3]))
+ return "1 Bad posted date";
+ Data.Posted = atol(av[3]);
+
+ token = TextToToken(av[4]);
+ if (Mode == OMrunning)
+ ok = InndHisWrite(msgid, Data.Arrived, Data.Posted,
+ Data.Expires, &token);
+ else {
+ /* Possible race condition, but documented in ctlinnd manpage. */
+ InndHisOpen();
+ ok = InndHisWrite(msgid, Data.Arrived, Data.Posted,
+ Data.Expires, &token);
+ InndHisClose();
+ }
+ return ok ? NULL : "1 Write failed";
+}
+
+
+/*
+** Do the work to allow foreign connectiosn.
+*/
+static const char *
+CCallow(char *av[])
+{
+ char *p;
+
+ if (RejectReason == NULL)
+ return "1 Already allowed";
+ p = av[0];
+ if (*p && strcmp(p, RejectReason) != 0)
+ return "1 Wrong reason";
+ free(RejectReason);
+ RejectReason = NULL;
+ return NULL;
+}
+
+
+/*
+** Do the work needed to start feeding a (new) site.
+*/
+static const char *
+CCbegin(char *av[])
+{
+ SITE *sp;
+ int i;
+ int length;
+ char *p;
+ const char *p1;
+ char **strings;
+ NEWSGROUP *ngp;
+ const char *error;
+ char *subbed;
+ char *poison;
+
+ /* If site already exists, drop it. */
+ if (SITEfind(av[0]) != NULL && (p1 = CCdrop(av)) != NULL)
+ return p1;
+
+ /* Find the named site. */
+ length = strlen(av[0]);
+ for (strings = SITEreadfile(true), i = 0; (p = strings[i]) != NULL; i++)
+ if ((p[length] == NF_FIELD_SEP || p[length] == NF_SUBFIELD_SEP)
+ && strncasecmp(p, av[0], length) == 0) {
+ p = xstrdup(p);
+ break;
+ }
+ if (p == NULL)
+ return CCnosite;
+
+ if (p[0] == 'M' && p[1] == 'E' && p[2] == NF_FIELD_SEP)
+ sp = &ME;
+ else {
+ /* Get space for the new site entry, and space for it in all
+ * the groups. */
+ for (i = nSites, sp = Sites; --i >= 0; sp++)
+ if (sp->Name == NULL)
+ break;
+ if (i < 0) {
+ nSites++;
+ Sites = xrealloc(Sites, nSites * sizeof(SITE));
+ sp = &Sites[nSites - 1];
+ sp->Next = sp->Prev = NOSITE;
+ for (i = nGroups, ngp = Groups; --i >= 0; ngp++) {
+ ngp->Sites = xrealloc(ngp->Sites, nSites * sizeof(int));
+ ngp->Poison = xrealloc(ngp->Poison, nSites * sizeof(int));
+ }
+ }
+ }
+
+ /* Parse. */
+ subbed = xmalloc(nGroups);
+ poison = xmalloc(nGroups);
+ error = SITEparseone(p, sp, subbed, poison);
+ free(subbed);
+ free(poison);
+ if (error != NULL) {
+ free((void *)p);
+ syslog(L_ERROR, "%s bad_newsfeeds %s", av[0], error);
+ return "1 Parse error";
+ }
+
+ if (sp != &ME && (!SITEsetup(sp) || !SITEfunnelpatch()))
+ return "1 Startup error";
+ SITEforward(sp, "begin");
+ return NULL;
+}
+
+
+/*
+** Common code to change a group's flags.
+*/
+static const char *
+CCdochange(NEWSGROUP *ngp, char *Rest)
+{
+ int length;
+ char *p;
+
+ if (ngp->Rest[0] == Rest[0]) {
+ length = strlen(Rest);
+ if (ngp->Rest[length] == '\n' && strncmp(ngp->Rest, Rest, length) == 0)
+ return "0 Group status unchanged";
+ }
+ if (Mode != OMrunning)
+ return CCnotrunning;
+
+ p = xstrdup(ngp->Name);
+ if (!ICDchangegroup(ngp, Rest)) {
+ syslog(L_NOTICE, "%s cant change_group %s to %s", LogName, p, Rest);
+ free(p);
+ return "1 Change failed (probably can't write active?)";
+ }
+ syslog(L_NOTICE, "%s change_group %s to %s", LogName, p, Rest);
+ free(p);
+ return NULL;
+}
+
+
+/*
+** Change the mode of a newsgroup.
+*/
+static const char *
+CCchgroup(char *av[])
+{
+ NEWSGROUP *ngp;
+ char *Rest;
+
+ if ((ngp = NGfind(av[0])) == NULL)
+ return CCnogroup;
+ Rest = av[1];
+ if (Rest[0] != NF_FLAG_ALIAS) {
+ Rest[1] = '\0';
+ if (CTYPE(isupper, Rest[0]))
+ Rest[0] = tolower(Rest[0]);
+ }
+ return CCdochange(ngp, Rest);
+}
+
+
+/*
+** Cancel a message.
+*/
+const char *
+CCcancel(char *av[])
+{
+ ARTDATA Data;
+ const char * p, *msgid;
+
+ Data.Posted = Data.Arrived = Now.time;
+ Data.Expires = 0;
+ Data.Feedsite = "?";
+ if ((p = CCgetid(av[0], &msgid)) != NULL)
+ return p;
+
+ Data.HdrContent[HDR__MESSAGE_ID].Value = (char *)msgid;
+ Data.HdrContent[HDR__MESSAGE_ID].Length = strlen(msgid);
+ if (Mode == OMrunning)
+ ARTcancel(&Data, msgid, true);
+ else {
+ /* If paused, don't try to use the history database since expire may be
+ running */
+ if (Mode == OMpaused)
+ return "1 Server paused";
+ if (ThrottledbyIOError)
+ return "1 Server throttled";
+ /* Possible race condition, but documented in ctlinnd manpage. */
+ InndHisOpen();
+ ARTcancel(&Data, msgid, true);
+ InndHisClose();
+ }
+ if (innconf->logcancelcomm)
+ syslog(L_NOTICE, "%s cancelled %s", LogName, msgid);
+ return NULL;
+}
+
+
+/*
+** Syntax-check the newsfeeds file.
+*/
+const char *
+CCcheckfile(char *unused[])
+{
+ char **strings;
+ char *p;
+ int i;
+ int errors;
+ const char * error;
+ SITE fake;
+ bool needheaders, needoverview, needpath, needstoredgroup;
+ bool needreplicdata;
+
+ unused = unused; /* ARGSUSED */
+ /* Parse all site entries. */
+ strings = SITEreadfile(false);
+ fake.Buffer.size = 0;
+ fake.Buffer.data = NULL;
+ /* save global variables not to be changed */
+ needheaders = NeedHeaders;
+ needoverview = NeedOverview;
+ needpath = NeedPath;
+ needstoredgroup = NeedStoredGroup;
+ needreplicdata = NeedReplicdata;
+ for (errors = 0, i = 0; (p = strings[i]) != NULL; i++) {
+ if ((error = SITEparseone(p, &fake, (char *)NULL, (char *)NULL)) != NULL) {
+ syslog(L_ERROR, "%s bad_newsfeeds %s", MaxLength(p, p), error);
+ errors++;
+ }
+ SITEfree(&fake);
+ }
+ free(strings);
+ /* restore global variables not to be changed */
+ NeedHeaders = needheaders;
+ NeedOverview = needoverview;
+ NeedPath = needpath;
+ NeedStoredGroup = needstoredgroup;
+ NeedReplicdata = needreplicdata;
+
+ if (errors == 0)
+ return NULL;
+
+ buffer_resize(&CCreply, SMBUF);
+ snprintf(CCreply.data, CCreply.size, "1 Found %d errors -- see syslog",
+ errors);
+ return CCreply.data;
+}
+
+
+/*
+** Drop a site.
+*/
+static const char *
+CCdrop(char *av[])
+{
+ SITE *sp;
+ NEWSGROUP *ngp;
+ int *ip;
+ int idx;
+ int i;
+ int j;
+
+ if ((sp = SITEfind(av[0])) == NULL)
+ return CCnosite;
+
+ SITEdrop(sp);
+
+ /* Loop over all groups, and if the site is in a group, clobber it. */
+ for (idx = sp - Sites, i = nGroups, ngp = Groups; --i >= 0; ngp++) {
+ for (j = ngp->nSites, ip = ngp->Sites; --j >= 0; ip++)
+ if (*ip == idx)
+ *ip = NOSITE;
+ for (j = ngp->nPoison, ip = ngp->Poison; --j >= 0; ip++)
+ if (*ip == idx)
+ *ip = NOSITE;
+ }
+
+ return NULL;
+}
+
+/*
+** Return info on the feeds for one, or all, sites
+*/
+static const char *
+CCfeedinfo(char *av[])
+{
+ SITE *sp;
+ char *p;
+ int i;
+
+ buffer_set(&CCreply, "0 ", 2);
+ p = av[0];
+ if (*p != '\0') {
+ if ((sp = SITEfind(p)) == NULL)
+ return "1 No such site";
+
+ SITEinfo(&CCreply, sp, true);
+ while ((sp = SITEfindnext(p, sp)) != NULL)
+ SITEinfo(&CCreply, sp, true);
+ }
+ else
+ for (i = nSites, sp = Sites; --i >= 0; sp++)
+ if (sp->Name)
+ SITEinfo(&CCreply, sp, false);
+
+ buffer_append(&CCreply, "", 1);
+ return CCreply.data;
+}
+
+
+static const char *
+CCfilter(char *av[] UNUSED)
+{
+#if defined(DO_TCL)
+ char *p;
+
+ switch (av[0][0]) {
+ default:
+ return "1 Bad flag";
+ case 'y':
+ if (TCLFilterActive)
+ return "1 tcl filter already enabled";
+ TCLfilter(true);
+ break;
+ case 'n':
+ if (!TCLFilterActive)
+ return "1 tcl filter already disabled";
+ TCLfilter(false);
+ break;
+ }
+ return NULL;
+#else /* defined(DO_TCL) */
+ return "1 TCL filtering support not compiled in";
+#endif /* defined(DO_TCL) */
+}
+
+
+static const char *
+CCperl(char *av[])
+{
+#if defined(DO_PERL)
+ switch (av[0][0]) {
+ default:
+ return "1 Bad flag";
+ case 'y':
+ if (PerlFilterActive)
+ return "1 Perl filter already enabled";
+ else if (!PerlFilter(true))
+ return "1 Perl filter not defined";
+ break;
+ case 'n':
+ if (!PerlFilterActive)
+ return "1 Perl filter already disabled";
+ PerlFilter(false);
+ break;
+ }
+ return NULL;
+#else /* defined(DO_PERL) */
+ return "1 Perl filtering support not compiled in";
+#endif /* defined(DO_PERL) */
+}
+
+
+static const char *
+CCpython(char *av[] UNUSED)
+{
+#if defined(DO_PYTHON)
+ return PYcontrol(av);
+#else /* defined(DO_PYTHON) */
+ return "1 Python filtering support not compiled in";
+#endif /* defined(DO_PYTHON) */
+}
+
+
+/*
+** Flush all sites or one site.
+*/
+static const char *
+CCflush(char *av[])
+{
+ SITE *sp;
+ int i;
+ char *p;
+
+ p = av[0];
+ if (*p == '\0') {
+ ICDwrite();
+ for (sp = Sites, i = nSites; --i >= 0; sp++)
+ SITEflush(sp, true);
+ syslog(L_NOTICE, "%s flush_all", LogName);
+ }
+ else {
+ if ((sp = SITEfind(p)) == NULL)
+ return CCnosite;
+ syslog(L_NOTICE, "%s flush", sp->Name);
+ SITEflush(sp, true);
+ }
+ return NULL;
+}
+
+
+/*
+** Flush the log files.
+*/
+static const char *
+CCflushlogs(char *unused[])
+{
+ unused = unused; /* ARGSUSED */
+
+ if (Debug)
+ return "1 In debug mode";
+
+ ICDwrite();
+ syslog(L_NOTICE, "%s flushlogs %s", LogName, CCcurrmode());
+ ReopenLog(Log);
+ ReopenLog(Errlog);
+ return NULL;
+}
+
+
+/*
+** Leave paused or throttled mode.
+*/
+static const char *
+CCgo(char *av[])
+{
+ static char YES[] = "y";
+ char *p;
+
+ p = av[0];
+ if (Reservation && strcmp(p, Reservation) == 0) {
+ free(Reservation);
+ Reservation = NULL;
+ }
+ if (RejectReason && strcmp(p, RejectReason) == 0) {
+ free(RejectReason);
+ RejectReason = NULL;
+ }
+
+ if (Mode == OMrunning)
+ return "1 Already running";
+ if (*p && strcmp(p, ModeReason) != 0)
+ return "1 Wrong reason";
+
+#if defined(DO_PERL)
+ PLmode(Mode, OMrunning, p);
+#endif /* defined(DO_PERL) */
+#if defined(DO_PYTHON)
+ PYmode(Mode, OMrunning, p);
+#endif /* defined(DO_PYTHON) */
+
+ free(ModeReason);
+ ModeReason = NULL;
+ Mode = OMrunning;
+ ThrottledbyIOError = false;
+
+ if (NNRPReason && !innconf->readerswhenstopped) {
+ av[0] = YES;
+ av[1] = p;
+ CCreaders(av);
+ }
+ if (ErrorCount < 0)
+ ErrorCount = IO_ERROR_COUNT;
+ InndHisOpen();
+ syslog(L_NOTICE, "%s running", LogName);
+ if (ICDneedsetup)
+ ICDsetup(true);
+ SCHANwakeup(&Mode);
+ return NULL;
+}
+
+
+/*
+** Hangup a channel.
+*/
+static const char *
+CChangup(char *av[])
+{
+ CHANNEL *cp;
+ int fd;
+ char *p;
+ int i;
+
+ /* Parse the argument, a channel number. */
+ for (p = av[0], fd = 0; *p; p++) {
+ if (!CTYPE(isdigit, *p))
+ return "1 Bad channel number";
+ fd = fd * 10 + *p - '0';
+ }
+
+ /* Loop over all channels for the desired one. */
+ for (i = 0; (cp = CHANiter(&i, CTany)) != NULL; )
+ if (cp->fd == fd) {
+ p = CHANname(cp);
+ switch (cp->Type) {
+ default:
+ snprintf(CCreply.data, CCreply.size, "1 Can't close %s", p);
+ return CCreply.data;
+ case CTexploder:
+ case CTprocess:
+ case CTfile:
+ case CTnntp:
+ case CTreject:
+ syslog(L_NOTICE, "%s hangup", p);
+ CHANclose(cp, p);
+ return NULL;
+ }
+ }
+ return "1 Not active";
+}
+
+
+/*
+** Return our operating mode.
+*/
+static const char *
+CCmode(char *unused[] UNUSED)
+{
+ char *p;
+ int i;
+ int h;
+ char buff[BUFSIZ];
+#if defined(DO_PERL)
+ char *stats;
+#endif /* defined(DO_PERL) */
+
+ /* FIXME: We assume here that BUFSIZ is >= 512, and that none of
+ * ModeReason, RejectReason, Reservation, NNRPReason, or the Perl filter
+ * statistics are longer than MAX_REASON_LEN bytes (or actually, the
+ * average of their lengths is <= MAX_REASON_LEN). If this is not true,
+ * the sprintf's/strcpy's below are likely to overflow buff with somewhat
+ * nasty consequences...
+ */
+
+ p = buff;
+ p += strlen(strcpy(buff, "0 Server "));
+
+ /* Server's mode. */
+ switch (Mode) {
+ default:
+ sprintf(p, "Unknown %d", Mode);
+ p += strlen(p);
+ break;
+ case OMrunning:
+ p += strlen(strcpy(p, "running"));
+ break;
+ case OMpaused:
+ p += strlen(strcpy(p, "paused "));
+ p += strlen(strcpy(p, ModeReason));
+ break;
+ case OMthrottled:
+ p += strlen(strcpy(p, "throttled "));
+ p += strlen(strcpy(p, ModeReason));
+ break;
+ }
+ *p++ = '\n';
+ if (RejectReason) {
+ p += strlen(strcpy(p, "Rejecting "));
+ p += strlen(strcpy(p, RejectReason));
+ }
+ else
+ p += strlen(strcpy(p, "Allowing remote connections"));
+
+ /* Server parameters. */
+ for (i = 0, h = 0; CHANiter(&h, CTnntp) != NULL; )
+ i++;
+ *p++ = '\n';
+ sprintf(p, "Parameters c %ld i %ld (%d) l %ld o %d t %ld H %d T %d X %d %s %s",
+ innconf->artcutoff, innconf->maxconnections, i,
+ innconf->maxartsize, MaxOutgoing, (long)TimeOut.tv_sec,
+ RemoteLimit, RemoteTotal, (int) RemoteTimer,
+ innconf->xrefslave ? "slave" : "normal",
+ AnyIncoming ? "any" : "specified");
+ p += strlen(p);
+
+ /* Reservation. */
+ *p++ = '\n';
+ if (Reservation) {
+ sprintf(p, "Reserved %s", Reservation);
+ p += strlen(p);
+ }
+ else
+ p += strlen(strcpy(p, "Not reserved"));
+
+ /* Newsreaders. */
+ *p++ = '\n';
+ p += strlen(strcpy(p, "Readers "));
+ if (innconf->readerswhenstopped)
+ p += strlen(strcpy(p, "independent "));
+ else
+ p += strlen(strcpy(p, "follow "));
+ if (NNRPReason == NULL)
+ p += strlen(strcpy(p, "enabled"));
+ else {
+ sprintf(p, "disabled %s", NNRPReason);
+ p += strlen(p);
+ }
+
+#if defined(DO_TCL)
+ *p++ = '\n';
+ p += strlen(strcpy(p, "Tcl filtering "));
+ if (TCLFilterActive)
+ p += strlen(strcpy(p, "enabled"));
+ else
+ p += strlen(strcpy(p, "disabled"));
+#endif /* defined(DO_TCL) */
+
+#if defined(DO_PERL)
+ *p++ = '\n';
+ p += strlen(strcpy(p, "Perl filtering "));
+ if (PerlFilterActive)
+ p += strlen(strcpy(p, "enabled"));
+ else
+ p += strlen(strcpy(p, "disabled"));
+
+ /* Perl filter status. */
+ stats = PLstats();
+ if (stats != NULL) {
+ *p++ = '\n';
+ p += strlen(strcpy(p, "Perl filter stats: "));
+ p += strlen(strcpy(p, stats));
+ free(stats);
+ }
+#endif /* defined(DO_PERL) */
+
+#if defined(DO_PYTHON)
+ *p++ = '\n';
+ p += strlen(strcpy(p, "Python filtering "));
+ if (PythonFilterActive)
+ p += strlen(strcpy(p, "enabled"));
+ else
+ p += strlen(strcpy(p, "disabled"));
+#endif /* defined(DO_PYTHON) */
+
+ buffer_set(&CCreply, buff, strlen(buff) + 1);
+ return CCreply.data;
+}
+
+
+/*
+** Log our operating mode (via syslog).
+*/
+static const char *
+CClogmode(char *unused[])
+{
+ unused = unused; /* ARGSUSED */
+ syslog(L_NOTICE, "%s servermode %s", LogName, CCcurrmode());
+ return NULL;
+}
+
+
+/*
+** Name the channels. ("Name the bats -- simple names.")
+*/
+static const char *
+CCname(char *av[])
+{
+ static char NL[] = "\n";
+ static char NIL[] = "\0";
+ char buff[SMBUF];
+ CHANNEL *cp;
+ char *p;
+ int count;
+ int i;
+
+ p = av[0];
+ if (*p != '\0') {
+ if ((cp = CHANfromdescriptor(atoi(p))) == NULL)
+ return xstrdup(CCnochannel);
+ snprintf(CCreply.data, CCreply.size, "0 %s", CHANname(cp));
+ return CCreply.data;
+ }
+ buffer_set(&CCreply, "0 ", 2);
+ for (count = 0, i = 0; (cp = CHANiter(&i, CTany)) != NULL; ) {
+ if (cp->Type == CTfree)
+ continue;
+ if (++count > 1)
+ buffer_append(&CCreply, NL, 1);
+ p = CHANname(cp);
+ buffer_append(&CCreply, p, strlen(p));
+ switch (cp->Type) {
+ case CTremconn:
+ sprintf(buff, ":remconn::");
+ break;
+ case CTreject:
+ sprintf(buff, ":reject::");
+ break;
+ case CTnntp:
+ sprintf(buff, ":%s:%ld:%s",
+ cp->State == CScancel ? "cancel" : "nntp",
+ (long) Now.time - cp->LastActive,
+ (cp->MaxCnx > 0 && cp->ActiveCnx == 0) ? "paused" : "");
+ break;
+ case CTlocalconn:
+ sprintf(buff, ":localconn::");
+ break;
+ case CTcontrol:
+ sprintf(buff, ":control::");
+ break;
+ case CTfile:
+ sprintf(buff, "::");
+ break;
+ case CTexploder:
+ sprintf(buff, ":exploder::");
+ break;
+ case CTprocess:
+ sprintf(buff, ":");
+ break;
+ default:
+ sprintf(buff, ":unknown::");
+ break;
+ }
+ p = buff;
+ buffer_append(&CCreply, p, strlen(p));
+ }
+ buffer_append(&CCreply, NIL, 1);
+ return CCreply.data;
+}
+
+
+/*
+** Create a newsgroup.
+*/
+static const char *
+CCnewgroup(char *av[])
+{
+ static char *TIMES = NULL;
+ static char WHEN[] = "updating active.times";
+ int fd;
+ char *p;
+ NEWSGROUP *ngp;
+ char *Name;
+ char *Rest;
+ const char * who;
+ char *buff;
+ int oerrno;
+ size_t length;
+
+ if (TIMES == NULL)
+ TIMES = concatpath(innconf->pathdb, _PATH_ACTIVETIMES);
+
+ Name = av[0];
+ if (Name[0] == '.' || strspn(Name, "0123456789") == strlen(Name))
+ return "1 Illegal newsgroup name";
+ for (p = Name; *p; p++)
+ if (*p == '.') {
+ if (p[1] == '.' || p[1] == '\0')
+ return "1 Double or trailing period in newsgroup name";
+ }
+ else if (ISWHITE(*p) || *p == ':' || *p == '!' || *p == '/')
+ return "1 Illegal character in newsgroup name";
+
+ Rest = av[1];
+ if (Rest[0] != NF_FLAG_ALIAS) {
+ Rest[1] = '\0';
+ if (CTYPE(isupper, Rest[0]))
+ Rest[0] = tolower(Rest[0]);
+ }
+ if (strlen(Name) + strlen(Rest) > SMBUF - 24)
+ return "1 Name too long";
+
+ if ((ngp = NGfind(Name)) != NULL)
+ return CCdochange(ngp, Rest);
+
+ if (Mode == OMthrottled && ThrottledbyIOError)
+ return "1 server throttled";
+
+ /* Update the log of groups created. Don't use stdio because SunOS
+ * 4.1 has broken libc which can't handle fd's greater than 127. */
+ if ((fd = open(TIMES, O_WRONLY | O_APPEND | O_CREAT, 0664)) < 0) {
+ oerrno = errno;
+ syslog(L_ERROR, "%s cant open %s %m", LogName, TIMES);
+ IOError(WHEN, oerrno);
+ }
+ else {
+ who = av[2];
+ if (*who == '\0')
+ who = NEWSMASTER;
+
+ length = snprintf(NULL, 0, "%s %ld %s\n", Name, (long) Now.time, who) + 1;
+ buff = xmalloc(length);
+ snprintf(buff, length, "%s %ld %s\n", Name, (long) Now.time, who);
+ if (xwrite(fd, buff, strlen(buff)) < 0) {
+ oerrno = errno;
+ syslog(L_ERROR, "%s cant write %s %m", LogName, TIMES);
+ IOError(WHEN, oerrno);
+ }
+
+ free(buff);
+
+ if (close(fd) < 0) {
+ oerrno = errno;
+ syslog(L_ERROR, "%s cant close %s %m", LogName, TIMES);
+ IOError(WHEN, oerrno);
+ }
+ }
+
+ /* Update the in-core data. */
+ if (!ICDnewgroup(Name, Rest))
+ return "1 Failed";
+ syslog(L_NOTICE, "%s newgroup %s as %s", LogName, Name, Rest);
+
+ return NULL;
+}
+
+
+/*
+** Parse and set a boolean flag.
+*/
+static bool
+CCparsebool(char name, bool *bp, char value)
+{
+ switch (value) {
+ default:
+ return false;
+ case 'y':
+ *bp = false;
+ break;
+ case 'n':
+ *bp = true;
+ break;
+ }
+ syslog(L_NOTICE, "%s changed -%c %c", LogName, name, value);
+ return true;
+}
+
+
+/*
+** Change a running parameter.
+*/
+static const char *
+CCparam(char *av[])
+{
+ static char BADVAL[] = "1 Bad value";
+ char *p;
+ int temp;
+
+ p = av[1];
+ switch (av[0][0]) {
+ default:
+ return "1 Unknown parameter";
+ case 'a':
+ if (!CCparsebool('a', (bool *)&AnyIncoming, *p))
+ return BADVAL;
+ break;
+ case 'c':
+ innconf->artcutoff = atoi(p);
+ syslog(L_NOTICE, "%s changed -c %ld", LogName, innconf->artcutoff);
+ break;
+ case 'i':
+ innconf->maxconnections = atoi(p);
+ syslog(L_NOTICE, "%s changed -i %ld", LogName, innconf->maxconnections);
+ break;
+ case 'l':
+ innconf->maxartsize = atol(p);
+ syslog(L_NOTICE, "%s changed -l %ld", LogName, innconf->maxartsize);
+ break;
+ case 'n':
+ if (!CCparsebool('n', (bool *)&innconf->readerswhenstopped, *p))
+ return BADVAL;
+ break;
+ case 'o':
+ MaxOutgoing = atoi(p);
+ syslog(L_NOTICE, "%s changed -o %d", LogName, MaxOutgoing);
+ break;
+ case 't':
+ TimeOut.tv_sec = atol(p);
+ syslog(L_NOTICE, "%s changed -t %ld", LogName, (long)TimeOut.tv_sec);
+ break;
+ case 'H':
+ RemoteLimit = atoi(p);
+ syslog(L_NOTICE, "%s changed -H %d", LogName, RemoteLimit);
+ break;
+ case 'T':
+ temp = atoi(p);
+ if (temp > REMOTETABLESIZE) {
+ syslog(L_NOTICE, "%s -T must be lower than %d",
+ LogName, REMOTETABLESIZE+1);
+ temp = REMOTETABLESIZE;
+ }
+ syslog(L_NOTICE, "%s changed -T from %d to %d",
+ LogName, RemoteTotal, temp);
+ RemoteTotal = temp;
+ break;
+ case 'X':
+ RemoteTimer = (time_t) atoi(p);
+ syslog(L_NOTICE, "%s changed -X %d", LogName, (int) RemoteTimer);
+ break;
+ }
+ return NULL;
+}
+
+
+/*
+** Common code to implement a pause or throttle.
+*/
+const char *
+CCblock(OPERATINGMODE NewMode, char *reason)
+{
+ static char NO[] = "n";
+ char * av[2];
+
+ if (*reason == '\0')
+ return CCnoreason;
+
+ if (strlen(reason) > MAX_REASON_LEN) /* MAX_REASON_LEN is as big as is safe */
+ return CCbigreason;
+
+ if (Reservation) {
+ if (strcmp(reason, Reservation) != 0) {
+ snprintf(CCreply.data, CCreply.size, "1 Reserved \"%s\"",
+ Reservation);
+ return CCreply.data;
+ }
+ free(Reservation);
+ Reservation = NULL;
+ }
+
+#if defined(DO_PERL)
+ PLmode(Mode, NewMode, reason);
+#endif /* defined(DO_PERL) */
+#if defined(DO_PYTHON)
+ PYmode(Mode, NewMode, reason);
+#endif /* defined(DO_PYTHON) */
+
+ ICDwrite();
+ InndHisClose();
+ Mode = NewMode;
+ if (ModeReason)
+ free(ModeReason);
+ ModeReason = xstrdup(reason);
+ if (NNRPReason == NULL && !innconf->readerswhenstopped) {
+ av[0] = NO;
+ av[1] = ModeReason;
+ CCreaders(av);
+ }
+ syslog(L_NOTICE, "%s %s %s",
+ LogName, NewMode == OMpaused ? "paused" : "throttled", reason);
+ return NULL;
+}
+
+
+/*
+** Enter paused mode.
+*/
+static const char *
+CCpause(char *av[])
+{
+ switch (Mode) {
+ case OMrunning:
+ return CCblock(OMpaused, av[0]);
+ case OMpaused:
+ return "1 Already paused";
+ case OMthrottled:
+ return "1 Already throttled";
+ }
+ return "1 Unknown mode";
+}
+
+
+/*
+** Allow or disallow newsreaders.
+*/
+static const char *
+CCreaders(char *av[])
+{
+ const char *p;
+
+ switch (av[0][0]) {
+ default:
+ return "1 Bad flag";
+ case 'y':
+ if (NNRPReason == NULL)
+ return "1 Already allowing readers";
+ p = av[1];
+ if (*p && strcmp(p, NNRPReason) != 0)
+ return "1 Wrong reason";
+ free(NNRPReason);
+ NNRPReason = NULL;
+ break;
+ case 'n':
+ if (NNRPReason)
+ return "1 Already not allowing readers";
+ p = av[1];
+ if (*p == '\0')
+ return CCnoreason;
+ if (strlen(p) > MAX_REASON_LEN) /* MAX_REASON_LEN is as big as is safe */
+ return CCbigreason;
+ NNRPReason = xstrdup(p);
+ break;
+ }
+ return NULL;
+}
+
+
+/*
+** Re-exec ourselves.
+*/
+static const char *
+CCxexec(char *av[])
+{
+ char *inndstart;
+ char *p;
+ int i;
+
+ if (CCargv == NULL)
+ return "1 no argv!";
+
+ inndstart = concatpath(innconf->pathbin, "inndstart");
+ /* Get the pathname. */
+ p = av[0];
+ if (*p == '\0' || strcmp(p, "innd") == 0 || strcmp(p, "inndstart") == 0)
+ CCargv[0] = inndstart;
+ else
+ return "1 Bad value";
+
+ JustCleanup();
+ syslog(L_NOTICE, "%s execv %s", LogName, CCargv[0]);
+
+ /* Close all fds to protect possible fd leaking accross successive innds. */
+ for (i=3; i<30; i++)
+ close(i);
+
+ execv(CCargv[0], CCargv);
+ syslog(L_FATAL, "%s cant execv %s %m", LogName, CCargv[0]);
+ _exit(1);
+ /* NOTREACHED */
+ return "1 Exit failed";
+}
+
+/*
+** Reject remote readers.
+*/
+static const char *
+CCreject(char *av[])
+{
+ if (RejectReason)
+ return "1 Already rejecting";
+ if (strlen(av[0]) > MAX_REASON_LEN) /* MAX_REASON_LEN is as big as is safe */
+ return CCbigreason;
+ RejectReason = xstrdup(av[0]);
+ return NULL;
+}
+
+
+/*
+** Re-read all in-core data.
+*/
+static const char *
+CCreload(char *av[])
+{
+ static char BADSCHEMA[] = "1 Can't read schema";
+#if defined(DO_PERL)
+ static char BADPERLRELOAD[] = "1 Failed to define filter_art" ;
+#endif /* defined(DO_PERL) */
+#if defined(DO_PYTHON)
+ static char BADPYRELOAD[] = "1 Failed to reload filter_innd.py" ;
+#endif /* defined(DO_PYTHON) */
+ const char *p;
+ char *path;
+
+ p = av[0];
+ if (*p == '\0' || strcmp(p, "all") == 0) {
+ SITEflushall(false);
+ if (Mode == OMrunning)
+ InndHisClose();
+ RCreadlist();
+ if (Mode == OMrunning)
+ InndHisOpen();
+ ICDwrite();
+ ICDsetup(true);
+ if (!ARTreadschema())
+ return BADSCHEMA;
+#if defined(DO_TCL)
+ TCLreadfilter();
+#endif /* defined(DO_TCL) */
+#if defined(DO_PERL)
+ path = concatpath(innconf->pathfilter, _PATH_PERL_FILTER_INND);
+ PERLreadfilter(path, "filter_art") ;
+ free(path);
+#endif /* defined(DO_PERL) */
+#if defined(DO_PYTHON)
+ syslog(L_NOTICE, "reloading pyfilter");
+ PYreadfilter();
+ syslog(L_NOTICE, "reloaded pyfilter OK");
+#endif /* DO_PYTHON */
+ p = "all";
+ }
+ else if (strcmp(p, "active") == 0 || strcmp(p, "newsfeeds") == 0) {
+ SITEflushall(false);
+ ICDwrite();
+ ICDsetup(true);
+ }
+ else if (strcmp(p, "history") == 0) {
+ if (Mode != OMrunning)
+ return CCnotrunning;
+ InndHisClose();
+ InndHisOpen();
+ }
+ else if (strcmp(p, "incoming.conf") == 0)
+ RCreadlist();
+ else if (strcmp(p, "overview.fmt") == 0) {
+ if (!ARTreadschema())
+ return BADSCHEMA;
+ }
+#if 0 /* we should check almost all innconf parameter, but the code
+ is still incomplete for innd, so just commented out */
+ else if (strcmp(p, "inn.conf") == 0) {
+ struct innconf *saved;
+
+ saved = innconf;
+ innconf = NULL;
+ if (innconf_read(NULL))
+ innconf_free(saved);
+ else {
+ innconf = saved;
+ return "1 Reload of inn.conf failed";
+ }
+ if (innconf->pathhost == NULL) {
+ syslog(L_FATAL, "%s No pathhost set", LogName);
+ exit(1);
+ }
+ free(Path.Data);
+ Path.Used = strlen(innconf->pathhost) + 1;
+ Path.Data = xmalloc(Path.Used + 1);
+ sprintf(Path.Data, "%s!", innconf->pathhost);
+ if (Pathalias.Used > 0)
+ free(Pathalias.Data);
+ if (innconf->pathalias == NULL) {
+ Pathalias.Used = 0;
+ Pathalias.Data = NULL;
+ } else {
+ Pathalias.Used = strlen(innconf->pathalias) + 1;
+ Pathalias.Data = xmalloc(Pathalias.Used + 1);
+ sprintf(Pathalias.Data, "%s!", innconf->pathalias);
+ }
+ if (Pathcluster.Used > 0)
+ free(Pathcluster.Data);
+ if (innconf->pathcluster == NULL) {
+ Pathcluster.Used = 0;
+ Pathcluster.Data = NULL;
+ } else {
+ Pathcluster.Used = strlen(innconf->pathcluster) + 1;
+ Pathcluster.Data = xmalloc(Pathcluster.Used + 1);
+ sprintf(Pathcluster.Data, "%s!", innconf->pathcluster);
+ }
+ }
+#endif
+#if defined(DO_TCL)
+ else if (strcmp(p, "filter.tcl") == 0) {
+ TCLreadfilter();
+ }
+#endif /* defined(DO_TCL) */
+#if defined(DO_PERL)
+ else if (strcmp(p, "filter.perl") == 0) {
+ path = concatpath(innconf->pathfilter, _PATH_PERL_FILTER_INND);
+ if (!PERLreadfilter(path, "filter_art"))
+ return BADPERLRELOAD;
+ }
+#endif /* defined(DO_PERL) */
+#if defined(DO_PYTHON)
+ else if (strcmp(p, "filter.python") == 0) {
+ if (!PYreadfilter())
+ return BADPYRELOAD;
+ }
+#endif /* defined(DO_PYTHON) */
+ else
+ return "1 Unknown reload type";
+
+ syslog(L_NOTICE, "%s reload %s %s", LogName, p, av[1]);
+ return NULL;
+}
+
+
+/*
+** Renumber the active file.
+*/
+static const char *
+CCrenumber(char *av[])
+{
+ static char CANTRENUMBER[] = "1 Failed (see syslog)";
+ char *p;
+ NEWSGROUP *ngp;
+
+ if (Mode != OMrunning)
+ return CCnotrunning;
+ if (ICDneedsetup)
+ return "1 Must first reload newsfeeds";
+ p = av[0];
+ if (*p) {
+ if ((ngp = NGfind(p)) == NULL)
+ return CCnogroup;
+ if (!NGrenumber(ngp))
+ return CANTRENUMBER;
+ }
+ else if (!ICDrenumberactive())
+ return CANTRENUMBER;
+ return NULL;
+}
+
+
+/*
+** Reserve a lock.
+*/
+static const char *
+CCreserve(char *av[])
+{
+ char *p;
+
+ if (Mode != OMrunning)
+ return CCnotrunning;
+ p = av[0];
+ if (*p) {
+ /* Trying to make a reservation. */
+ if (Reservation)
+ return "1 Already reserved";
+ if (strlen(p) > MAX_REASON_LEN) /* MAX_REASON_LEN is as big as is safe */
+ return CCbigreason;
+ Reservation = xstrdup(p);
+ }
+ else {
+ /* Trying to remove a reservation. */
+ if (Reservation == NULL)
+ return "1 Not reserved";
+ free(Reservation);
+ Reservation = NULL;
+ }
+ return NULL;
+}
+
+
+/*
+** Remove a newsgroup.
+*/
+static const char *
+CCrmgroup(char *av[])
+{
+ NEWSGROUP *ngp;
+
+ if ((ngp = NGfind(av[0])) == NULL)
+ return CCnogroup;
+
+ if (Mode == OMthrottled && ThrottledbyIOError)
+ return "1 server throttled";
+
+ /* Update the in-core data. */
+ if (!ICDrmgroup(ngp))
+ return "1 Failed";
+ syslog(L_NOTICE, "%s rmgroup %s", LogName, av[0]);
+ return NULL;
+}
+
+
+/*
+** Send a command line to an exploder.
+*/
+static const char *
+CCsend(char *av[])
+{
+ SITE *sp;
+
+ if ((sp = SITEfind(av[0])) == NULL)
+ return CCnosite;
+ if (sp->Type != FTexploder)
+ return CCwrongtype;
+ SITEwrite(sp, av[1]);
+ return NULL;
+}
+
+
+/*
+** Shut down the system.
+*/
+static const char *
+CCshutdown(char *av[])
+{
+ syslog(L_NOTICE, "%s shutdown %s", LogName, av[0]);
+ CleanupAndExit(0, av[0]);
+ /* NOTREACHED */
+ return "1 Exit failed";
+}
+
+
+/*
+** Send a signal to a site's feed.
+*/
+static const char *
+CCsignal(char *av[])
+{
+ SITE *sp;
+ char *p;
+ int s;
+ int oerrno;
+
+ /* Parse the signal. */
+ p = av[0];
+ if (*p == '-')
+ p++;
+ if (strcasecmp(p, "HUP") == 0)
+ s = SIGHUP;
+ else if (strcasecmp(p, "INT") == 0)
+ s = SIGINT;
+ else if (strcasecmp(p, "TERM") == 0)
+ s = SIGTERM;
+ else if ((s = atoi(p)) <= 0)
+ return "1 Invalid signal";
+
+ /* Parse the site. */
+ p = av[1];
+ if ((sp = SITEfind(p)) == NULL)
+ return CCnosite;
+ if (sp->Type != FTchannel && sp->Type != FTexploder)
+ return CCwrongtype;
+ if (sp->Process < 0)
+ return "1 Site has no process";
+
+ /* Do it. */
+ if (kill(sp->pid, s) < 0) {
+ oerrno = errno;
+ syslog(L_ERROR, "%s cant kill %ld %d site %s, %m", LogName,
+ (long) sp->pid, s, p);
+ snprintf(CCreply.data, CCreply.size, "1 Can't signal process %ld, %s",
+ (long) sp->pid, strerror(oerrno));
+ return CCreply.data;
+ }
+
+ return NULL;
+}
+
+
+/*
+** Enter throttled mode.
+*/
+static const char *
+CCthrottle(char *av[])
+{
+ char *p;
+
+ p = av[0];
+ switch (Mode) {
+ case OMpaused:
+ if (*p && strcmp(p, ModeReason) != 0)
+ return "1 Already paused";
+ /* FALLTHROUGH */
+ case OMrunning:
+ return CCblock(OMthrottled, p);
+ case OMthrottled:
+ return "1 Already throttled";
+ }
+ return "1 unknown mode";
+}
+
+/*
+** Turn on or off performance monitoring
+*/
+static const char *
+CCtimer(char *av[])
+{
+ int value;
+ char *p;
+
+ if (strcmp(av[0], "off") == 0)
+ value = 0;
+ else {
+ for (p = av[0]; *p; p++) {
+ if (!CTYPE(isdigit, *p))
+ return "1 parameter should be a number or 'off'";
+ }
+ value = atoi(av[0]);
+ }
+ innconf->timer = value;
+ if (innconf->timer)
+ TMRinit(TMR_MAX);
+ else
+ TMRinit(0);
+ return NULL;
+}
+
+/*
+** Log into filename some history stats
+*/
+static const char *
+CCstathist(char *av[])
+{
+ if (strcmp(av[0], "off") == 0)
+ HISlogclose();
+ else
+ HISlogto(av[0]);
+ return NULL;
+}
+
+/*
+** Turn innd status creation on or off
+*/
+static const char *
+CCstatus(char *av[])
+{
+ int value;
+ char *p;
+
+ if (strcmp(av[0], "off") == 0)
+ value = 0;
+ else {
+ for (p = av[0]; *p; p++) {
+ if (!CTYPE(isdigit, *p))
+ return "1 parameter should be a number or 'off'";
+ }
+ value = atoi(av[0]);
+ }
+ innconf->status = value;
+ return NULL;
+}
+
+/*
+** Add or remove tracing.
+*/
+static const char *
+CCtrace(char *av[])
+{
+ char *p;
+ bool Flag;
+ const char * word;
+ CHANNEL *cp;
+
+ /* Parse the flag. */
+ p = av[1];
+ switch (p[0]) {
+ default: return "1 Bad trace flag";
+ case 'y': case 'Y': Flag = true; word = "on"; break;
+ case 'n': case 'N': Flag = false; word = "off"; break;
+ }
+
+ /* Parse what's being traced. */
+ p = av[0];
+ switch (*p) {
+ default:
+ return "1 Bad trace item";
+ case 'i': case 'I':
+ Tracing = Flag;
+ syslog(L_NOTICE, "%s trace innd %s", LogName, word);
+ break;
+ case 'n': case 'N':
+ NNRPTracing = Flag;
+ syslog(L_NOTICE, "%s trace nnrpd %s", LogName, word);
+ break;
+ case '0': case '1': case '2': case '3': case '4':
+ case '5': case '6': case '7': case '8': case '9':
+ if ((cp = CHANfromdescriptor(atoi(p))) == NULL)
+ return CCnochannel;
+ CHANtracing(cp, Flag);
+ break;
+ }
+ return NULL;
+}
+
+\f
+
+/*
+** Split up the text into fields and stuff them in argv. Return the
+** number of elements or -1 on error.
+*/
+static int
+CCargsplit(char *p, char *end, char **argv, int size)
+{
+ char **save;
+
+ for (save = argv, *argv++ = p, size--; p < end; p++)
+ if (*p == SC_SEP) {
+ if (--size <= 0)
+ return -1;
+ *p = '\0';
+ *argv++ = p + 1;
+ }
+ *argv = NULL;
+ return argv - save;
+}
+
+
+/*
+** Read function. Read and process the message.
+*/
+static void
+CCreader(CHANNEL *cp)
+{
+ static char TOOLONG[] = "0 Reply too long for server to send";
+ CCDISPATCH *dp;
+ const char * p;
+ char *q;
+ ICC_MSGLENTYPE bufflen;
+ ICC_PROTOCOLTYPE protocol ;
+#if defined(HAVE_UNIX_DOMAIN_SOCKETS)
+ struct sockaddr_un client;
+#else
+ int written;
+#endif /* defined(HAVE_UNIX_DOMAIN_SOCKETS) */
+ int i;
+ char buff[BIG_BUFFER + 2];
+ char copy[BIG_BUFFER + 2];
+ char *argv[SC_MAXFIELDS + 2];
+ int argc;
+ int len;
+ char *tbuff ;
+
+ if (cp != CCchan) {
+ syslog(L_ERROR, "%s internal CCreader wrong channel 0x%p not 0x%p",
+ LogName, (void *)cp, (void *)CCchan);
+ return;
+ }
+
+#if defined (HAVE_UNIX_DOMAIN_SOCKETS)
+
+ i = RECVorREAD(CCchan->fd, buff, BIG_BUFFER) ;
+ if (i < 0) {
+ syslog(L_ERROR, "%s cant recv CCreader %m", LogName);
+ return;
+ } else if (i == 0) {
+ syslog(L_ERROR, "%s cant recv CCreader empty", LogName);
+ return;
+ } else if (i < (int)HEADER_SIZE) {
+ syslog(L_ERROR, "%s cant recv CCreader header-length %m", LogName);
+ return;
+ }
+
+ memcpy (&protocol,buff,sizeof (protocol)) ;
+ memcpy (&bufflen,buff + sizeof (protocol),sizeof (bufflen)) ;
+ bufflen = ntohs (bufflen) ;
+
+ if (i != bufflen) {
+ syslog(L_ERROR, "%s cant recv CCreader short-read %m", LogName);
+ return;
+ }
+
+ i -= HEADER_SIZE ;
+ memmove (buff,buff + HEADER_SIZE,i) ;
+ buff[i] = '\0';
+
+ if (protocol != ICC_PROTOCOL_1) {
+ syslog(L_ERROR, "%s CCreader protocol mismatch", LogName) ;
+ return ;
+ }
+
+#else /* defined (HAVE_UNIX_DOMAIN_SOCKETS) */
+
+ i = RECVorREAD(CCchan->fd, buff, HEADER_SIZE) ;
+ if (i < 0) {
+ syslog(L_ERROR, "%s cant read CCreader header %m", LogName);
+ return;
+ } else if (i == 0) {
+ syslog(L_ERROR, "%s cant read CCreader header empty", LogName);
+ return;
+ } else if (i != HEADER_SIZE) {
+ syslog(L_ERROR, "%s cant read CCreader header-length %m", LogName);
+ return;
+ }
+
+ memcpy (&protocol,buff,sizeof (protocol)) ;
+ memcpy (&bufflen,buff + sizeof (protocol),sizeof (bufflen)) ;
+ bufflen = ntohs (bufflen);
+ if (bufflen < HEADER_SIZE || bufflen > BIG_BUFFER) {
+ syslog(L_ERROR, "%s cant read CCreader bad length", LogName);
+ return;
+ }
+ bufflen -= HEADER_SIZE ;
+
+ i = RECVorREAD(CCchan->fd, buff, bufflen) ;
+
+ if (i < 0) {
+ syslog(L_ERROR, "%s cant read CCreader buffer %m", LogName);
+ return;
+ } else if (i == 0) {
+ syslog(L_ERROR, "%s cant read CCreader buffer empty", LogName);
+ return;
+ } else if (i != bufflen) {
+ syslog(L_ERROR, "%s cant read CCreader buffer-length %m", LogName);
+ return;
+ }
+
+ buff[i] = '\0';
+
+ if (protocol != ICC_PROTOCOL_1) {
+ syslog(L_ERROR, "%s CCreader protocol mismatch", LogName) ;
+ return ;
+ }
+
+#endif /* defined (HAVE_UNIX_DOMAIN_SOCKETS) */
+
+ /* Copy to a printable buffer, and log. */
+ strcpy(copy, buff);
+ for (p = NULL, q = copy; *q; q++)
+ if (*q == SC_SEP) {
+ *q = ':';
+ if (p == NULL)
+ p = q + 1;
+ }
+ syslog(L_CC_CMD, "%s", p ? p : copy);
+
+ /* Split up the fields, get the command letter. */
+ if ((argc = CCargsplit(buff, &buff[i], argv, ARRAY_SIZE(argv))) < 2
+ || argc == ARRAY_SIZE(argv)) {
+ syslog(L_ERROR, "%s bad_fields CCreader", LogName);
+ return;
+ }
+ p = argv[1];
+ i = *p;
+
+ /* Dispatch to the command function. */
+ for (argc -= 2, dp = CCcommands; dp < ARRAY_END(CCcommands); dp++)
+ if (i == dp->Name) {
+ if (argc != dp->argc)
+ p = "1 Wrong number of parameters";
+ else
+ p = (*dp->Function)(&argv[2]);
+ break;
+ }
+ if (dp == ARRAY_END(CCcommands)) {
+ syslog(L_NOTICE, "%s bad_message %c", LogName, i);
+ p = "1 Bad command";
+ }
+ else if (p == NULL)
+ p = "0 Ok";
+
+ /* Build the reply address and send the reply. */
+ len = strlen(p) + HEADER_SIZE ;
+ tbuff = xmalloc(len + 1);
+
+ protocol = ICC_PROTOCOL_1 ;
+ memcpy (tbuff,&protocol,sizeof (protocol)) ;
+ tbuff += sizeof (protocol) ;
+
+ bufflen = htons (len) ;
+ memcpy (tbuff,&bufflen,sizeof (bufflen)) ;
+ tbuff += sizeof (bufflen) ;
+
+ strcpy (tbuff,p) ;
+ tbuff -= HEADER_SIZE ;
+
+#if defined(HAVE_UNIX_DOMAIN_SOCKETS)
+ memset(&client, 0, sizeof client);
+ client.sun_family = AF_UNIX;
+ strcpy(client.sun_path, argv[0]);
+ if (sendto(CCwriter, tbuff, len, 0,
+ (struct sockaddr *) &client, SUN_LEN(&client)) < 0) {
+ i = errno;
+ syslog(i == ENOENT ? L_NOTICE : L_ERROR,
+ "%s cant sendto CCreader bytes %d %m", LogName, len);
+ if (i == EMSGSIZE)
+ sendto(CCwriter, TOOLONG, strlen(TOOLONG), 0,
+ (struct sockaddr *) &client, SUN_LEN(&client));
+ }
+#else
+ if ((i = open(argv[0], O_WRONLY | O_NDELAY)) < 0)
+ syslog(L_ERROR, "%s cant open %s %m", LogName, argv[0]);
+ else {
+ if ((written = write(i, tbuff, len)) != len)
+ if (written < 0)
+ syslog(L_ERROR, "%s cant write %s %m", LogName, argv[0]);
+ else
+ syslog(L_ERROR, "%s cant write %s", LogName, argv[0]);
+ if (close(i) < 0)
+ syslog(L_ERROR, "%s cant close %s %m", LogName, argv[0]);
+ }
+#endif /* defined(HAVE_UNIX_DOMAIN_SOCKETS) */
+ free (tbuff) ;
+}
+
+
+/*
+** Called when a write-in-progress is done on the channel. Shouldn't happen.
+*/
+static void
+CCwritedone(CHANNEL *unused)
+{
+ unused = unused; /* ARGSUSED */
+ syslog(L_ERROR, "%s internal CCwritedone", LogName);
+}
+
+
+/*
+** Create the channel.
+*/
+void
+CCsetup(void)
+{
+ int i;
+#if defined(HAVE_UNIX_DOMAIN_SOCKETS)
+ struct sockaddr_un server;
+ int size = 65535;
+#endif /* defined(HAVE_UNIX_DOMAIN_SOCKETS) */
+
+ if (CCpath == NULL)
+ CCpath = concatpath(innconf->pathrun, _PATH_NEWSCONTROL);
+ /* Remove old detritus. */
+ if (unlink(CCpath) < 0 && errno != ENOENT) {
+ syslog(L_FATAL, "%s cant unlink %s %m", LogName, CCpath);
+ exit(1);
+ }
+
+#if defined(HAVE_UNIX_DOMAIN_SOCKETS)
+ /* Create a socket and name it. */
+ if ((i = socket(AF_UNIX, SOCK_DGRAM, 0)) < 0) {
+ syslog(L_FATAL, "%s cant socket %s %m", LogName, CCpath);
+ exit(1);
+ }
+ memset(&server, 0, sizeof server);
+ server.sun_family = AF_UNIX;
+ strcpy(server.sun_path, CCpath);
+ if (bind(i, (struct sockaddr *) &server, SUN_LEN(&server)) < 0) {
+ syslog(L_FATAL, "%s cant bind %s %m", LogName, CCpath);
+ exit(1);
+ }
+
+ /* Create an unbound socket to reply on. */
+ if ((CCwriter = socket(AF_UNIX, SOCK_DGRAM, 0)) < 0) {
+ syslog(L_FATAL, "%s cant socket unbound %m", LogName);
+ exit(1);
+ }
+
+ /* Increase the buffer size for the Unix domain socket */
+ if (setsockopt(CCwriter, SOL_SOCKET, SO_SNDBUF, &size, sizeof(size)) < 0)
+ syslog(L_ERROR, "%s cant setsockopt %m", LogName);
+#else
+ /* Create a named pipe and open it. */
+ if (mkfifo(CCpath, 0666) < 0) {
+ syslog(L_FATAL, "%s cant mkfifo %s %m", LogName, CCpath);
+ exit(1);
+ }
+ if ((i = open(CCpath, O_RDWR)) < 0) {
+ syslog(L_FATAL, "%s cant open %s %m", LogName, CCpath);
+ exit(1);
+ }
+#endif /* defined(HAVE_UNIX_DOMAIN_SOCKETS) */
+
+ CCchan = CHANcreate(i, CTcontrol, CSwaiting, CCreader, CCwritedone);
+ syslog(L_NOTICE, "%s ccsetup %s", LogName, CHANname(CCchan));
+ RCHANadd(CCchan);
+
+ buffer_resize(&CCreply, SMBUF);
+
+ /*
+ * Catch SIGUSR1 so that we can recreate the control channel when
+ * needed (i.e. something has deleted our named socket.
+ */
+#if defined(SIGUSR1)
+ xsignal(SIGUSR1, CCresetup);
+#endif /* defined(SIGUSR1) */
+}
+
+
+/*
+** Cleanly shut down the channel.
+*/
+void
+CCclose(void)
+{
+ CHANclose(CCchan, CHANname(CCchan));
+ CCchan = NULL;
+ if (unlink(CCpath) < 0)
+ syslog(L_ERROR, "%s cant unlink %s %m", LogName, CCpath);
+ free(CCpath);
+ CCpath = NULL;
+ free(CCreply.data);
+ CCreply.data = NULL;
+ CCreply.size = 0;
+ CCreply.used = 0;
+ CCreply.left = 0;
+#if defined(HAVE_UNIX_DOMAIN_SOCKETS)
+ if (close(CCwriter) < 0)
+ syslog(L_ERROR, "%s cant close unbound %m", LogName);
+#endif /* defined(HAVE_UNIX_DOMAIN_SOCKETS) */
+}
+
+
+/*
+** Restablish the control channel.
+*/
+static RETSIGTYPE
+CCresetup(int unused)
+{
+#ifndef HAVE_SIGACTION
+ xsignal(s, CCresetup);
+#else
+ unused = unused; /* ARGSUSED */
+#endif
+ CCclose();
+ CCsetup();
+}
+
+
+/*
+ * Read a file containing lines of the form "newsgroup lowmark",
+ * and reset the low article number for the specified groups.
+ */
+static const char *
+CClowmark(char *av[])
+{
+ long lo;
+ char *line, *cp;
+ const char *ret = NULL;
+ QIOSTATE *qp;
+ NEWSGROUP *ngp;
+
+ if (Mode != OMrunning)
+ return CCnotrunning;
+ if (ICDneedsetup)
+ return "1 Must first reload newsfeeds";
+ if ((qp = QIOopen(av[0])) == NULL) {
+ syslog(L_ERROR, "%s cant open %s %m", LogName, av[0]);
+ return "1 Cannot read input file";
+ }
+ while ((line = QIOread(qp)) != NULL) {
+ if (QIOerror(qp))
+ break;
+ if (QIOtoolong(qp)) {
+ ret = "1 Malformed input line (too long)";
+ break;
+ }
+ while (ISWHITE(*line))
+ line++;
+ for (cp = line; *cp && !ISWHITE(*cp); cp++)
+ ;
+ if (*cp == '\0') {
+ ret = "1 Malformed input line (only one field)";
+ break;
+ }
+ *cp++ = '\0';
+ while (ISWHITE(*cp))
+ cp++;
+ if (strspn(cp, "0123456789") != strlen(cp)) {
+ ret = "1 Malformed input line (non-digit in low mark)";
+ break;
+ }
+ if ((lo = atol(cp)) == 0 && (cp[0] != '0' || cp[1] != '\0')) {
+ ret = "1 Malformed input line (bad low mark)";
+ break;
+ }
+ if ((ngp = NGfind(line)) == NULL) {
+ /* ret = CCnogroup; break; */
+ continue;
+ }
+ if (!NGlowmark(ngp, lo)) {
+ ret = "1 Cannot set low mark - see syslog";
+ break;
+ }
+ }
+ if (ret == NULL && QIOerror(qp)) {
+ syslog(L_ERROR, "%s cant read %s %m", LogName, av[0]);
+ ret = "1 Error reading input file";
+ }
+ QIOclose(qp);
+ ICDwrite();
+ return ret;
+}
--- /dev/null
+/* $Id: chan.c 6720 2004-05-16 20:54:25Z rra $
+**
+** I/O channel (and buffer) processing.
+*/
+
+#include "config.h"
+#include "clibrary.h"
+
+/* Needed on AIX 4.1 to get fd_set and friends. */
+#ifdef HAVE_SYS_SELECT_H
+# include <sys/select.h>
+#endif
+
+#include "inn/innconf.h"
+#include "innd.h"
+
+/* These errno values don't exist on all systems, but may be returned as an
+ (ignorable) error to setting the accept socket nonblocking. Define them
+ to 0 if they don't exist so that we can unconditionally compare errno to
+ them in the code. */
+#ifndef ENOTSOCK
+# define ENOTSOCK 0
+#endif
+#ifndef ENOTTY
+# define ENOTTY 0
+#endif
+
+static const char * const timer_name[] = {
+ "idle", "artclean", "artwrite", "artcncl", "sitesend", "overv",
+ "perl", "python", "nntpread", "artparse", "artlog", "datamove"
+};
+
+/* Minutes - basically, keep the connection open but idle */
+#define PAUSE_BEFORE_DROP 5
+
+/* Divisor of the BUFFER size. If the amount free at the beginning of the
+ buffer is bigger than the quotient, then it is compacted in the
+ readloop */
+#define COMP_THRESHOLD 10
+
+static fd_set RCHANmask;
+static fd_set SCHANmask;
+static fd_set WCHANmask;
+static int SCHANcount;
+static int CHANlastfd;
+static int CHANlastsleepfd;
+static int CHANccfd;
+static int CHANtablesize;
+static CHANNEL *CHANtable;
+static CHANNEL *CHANcc;
+static CHANNEL CHANnull = { CTfree, CSerror, -1 };
+
+#define PRIORITISE_REMCONN
+#ifdef PRIORITISE_REMCONN
+static int *CHANrcfd;
+static CHANNEL **CHANrc;
+static int chanlimit;
+#endif /* PRIORITISE_REMCONN */
+
+/*
+** Tear down our world
+*/
+void
+CHANshutdown(void)
+{
+ CHANNEL *cp;
+ int i;
+
+ if (CHANtable) {
+ for (i = CHANtablesize, cp = &CHANtable[0]; --i >= 0; cp++) {
+ if (cp->In.data) {
+ free(cp->In.data);
+ }
+ if (cp->Out.data) {
+ free(cp->Out.data);
+ }
+ }
+ free(CHANtable);
+ CHANtable = NULL;
+ }
+}
+
+/*
+** Initialize all the I/O channels.
+*/
+void
+CHANsetup(int i)
+{
+ CHANNEL *cp;
+
+ FD_ZERO(&RCHANmask);
+ FD_ZERO(&SCHANmask);
+ FD_ZERO(&WCHANmask);
+ CHANshutdown();
+ CHANtablesize = i;
+ CHANtable = xcalloc(CHANtablesize, sizeof(CHANNEL));
+ CHANnull.NextLog = innconf->chaninacttime;
+ memset(&CHANnull.Address, 0, sizeof(CHANnull.Address));
+ for (cp = CHANtable; --i >= 0; cp++)
+ *cp = CHANnull;
+}
+
+
+/*
+** Create a channel from a descriptor.
+*/
+CHANNEL *
+CHANcreate(int fd, CHANNELTYPE Type, CHANNELSTATE State,
+ innd_callback_t Reader, innd_callback_t WriteDone)
+{
+ CHANNEL *cp;
+ struct buffer in = { 0, 0, 0, NULL };
+ struct buffer out = { 0, 0, 0, NULL };
+
+ cp = &CHANtable[fd];
+
+ /* Don't overwrite the buffers with CHANnull. */
+ in = cp->In;
+ buffer_resize(&in, START_BUFF_SIZE);
+ in.used = 0;
+ in.left = in.size;
+ out = cp->Out;
+ buffer_resize(&out, SMBUF);
+ buffer_set(&out, "", 0);
+
+ /* Set up the channel's info. */
+ *cp = CHANnull;
+ cp->fd = fd;
+ cp->Type = Type;
+ cp->State = State;
+ cp->Streaming = false;
+ cp->Skip = false;
+ cp->NoResendId = false;
+ cp->privileged = false;
+ cp->Ihave = cp->Ihave_Duplicate = cp->Ihave_Deferred = cp->Ihave_SendIt = cp->Ihave_Cybercan = 0;
+ cp->Check = cp->Check_send = cp->Check_deferred = cp->Check_got = cp->Check_cybercan = 0;
+ cp->Takethis = cp->Takethis_Ok = cp->Takethis_Err = 0;
+ cp->Size = cp->Duplicate = 0;
+ cp->Unwanted_s = cp->Unwanted_f = cp->Unwanted_d = 0;
+ cp->Unwanted_g = cp->Unwanted_u = cp->Unwanted_o = 0;
+ cp->Reader = Reader;
+ cp->WriteDone = WriteDone;
+ cp->Started = cp->LastActive = Now.time;
+ cp->In = in;
+ cp->Out = out;
+ cp->Tracing = Tracing;
+ cp->Sendid.size = 0;
+ cp->Next=0;
+ cp->MaxCnx=0;
+ cp->ActiveCnx=0;
+ cp->ArtBeg = 0;
+ cp->ArtMax = 0;
+ cp->Start = 0;
+ HashClear(&cp->CurrentMessageIDHash);
+ memset(cp->PrecommitWIP, '\0', sizeof(cp->PrecommitWIP));
+ cp->PrecommitiCachenext=0;
+ ARTprepare(cp);
+
+ close_on_exec(fd, true);
+
+#ifndef _HPUX_SOURCE
+ /* Stupid HPUX 11.00 has a broken listen/accept where setting the listen
+ socket to nonblocking prevents you from successfully setting the
+ socket returned by accept(2) back to blocking mode, no matter what,
+ resulting in all kinds of funny behaviour, data loss, etc. etc. */
+ if (nonblocking(fd, true) < 0 && errno != ENOTSOCK && errno != ENOTTY)
+ syslog(L_ERROR, "%s cant nonblock %d %m", LogName, fd);
+#endif
+
+ /* Note control channel, for efficiency. */
+ if (Type == CTcontrol) {
+ CHANcc = cp;
+ CHANccfd = fd;
+ }
+#ifdef PRIORITISE_REMCONN
+ /* Note remconn channel, for efficiency */
+ if (Type == CTremconn) {
+ int j;
+ for (j = 0 ; j < chanlimit ; j++ ) {
+ if (CHANrcfd[j] == -1) {
+ break;
+ }
+ }
+ if (j < chanlimit) {
+ CHANrc[j] = cp;
+ CHANrcfd[j] = fd;
+ } else if (chanlimit == 0) {
+ /* assuming two file descriptors(AF_INET and AF_INET6) */
+ chanlimit = 2;
+ CHANrc = xmalloc(chanlimit * sizeof(CHANNEL **));
+ CHANrcfd = xmalloc(chanlimit * sizeof(int *));
+ for (j = 0 ; j < chanlimit ; j++ ) {
+ CHANrc[j] = NULL;
+ CHANrcfd[j] = -1;
+ }
+ CHANrc[0] = cp;
+ CHANrcfd[0] = fd;
+ } else {
+ /* extend to double size */
+ CHANrc = xrealloc(CHANrc, chanlimit * 2 * sizeof(CHANNEL **));
+ CHANrcfd = xrealloc(CHANrcfd, chanlimit * 2 * sizeof(int *));
+ for (j = chanlimit ; j < chanlimit * 2 ; j++ ) {
+ CHANrc[j] = NULL;
+ CHANrcfd[j] = -1;
+ }
+ CHANrc[chanlimit] = cp;
+ CHANrcfd[chanlimit] = fd;
+ chanlimit *= 2;
+ }
+ }
+#endif /* PRIORITISE_REMCONN */
+ return cp;
+}
+
+
+/*
+** Start tracing a channel.
+*/
+void
+CHANtracing(CHANNEL *cp, bool Flag)
+{
+ char *p;
+
+ p = CHANname(cp);
+ syslog(L_NOTICE, "%s trace %s", p, Flag ? "on" : "off");
+ cp->Tracing = Flag;
+ if (Flag) {
+ syslog(L_NOTICE, "%s trace badwrites %d blockwrites %d badreads %d",
+ p, cp->BadWrites, cp->BlockedWrites, cp->BadReads);
+ syslog(L_NOTICE, "%s trace address %s lastactive %ld nextlog %ld",
+ p, sprint_sockaddr((struct sockaddr *)&cp->Address),
+ (long) cp->LastActive, (long) cp->NextLog);
+ if (FD_ISSET(cp->fd, &SCHANmask))
+ syslog(L_NOTICE, "%s trace sleeping %ld 0x%p",
+ p, (long)cp->Waketime, (void *)cp->Waker);
+ if (FD_ISSET(cp->fd, &RCHANmask))
+ syslog(L_NOTICE, "%s trace reading %lu %s",
+ p, (unsigned long) cp->In.used,
+ MaxLength(cp->In.data, cp->In.data));
+ if (FD_ISSET(cp->fd, &WCHANmask))
+ syslog(L_NOTICE, "%s trace writing %lu %s",
+ p, (unsigned long) cp->Out.left,
+ MaxLength(cp->Out.data, cp->Out.data));
+ }
+}
+
+
+/*
+** Close a channel.
+*/
+void
+CHANclose(CHANNEL *cp, const char *name)
+{
+ char *label, *tmplabel, buff[SMBUF];
+
+ if (cp->Type == CTfree)
+ syslog(L_ERROR, "%s internal closing free channel %d", name, cp->fd);
+ else {
+ if (cp->Type == CTnntp) {
+ WIPprecomfree(cp);
+ NCclearwip(cp);
+ if (cp->State == CScancel)
+ syslog(L_NOTICE,
+ "%s closed seconds %ld cancels %ld",
+ name, (long)(Now.time - cp->Started),
+ cp->Received);
+ else {
+ snprintf(buff, sizeof(buff),
+ "accepted size %.0f duplicate size %.0f", cp->Size,
+ cp->DuplicateSize);
+ syslog(L_NOTICE,
+ "%s closed seconds %ld accepted %ld refused %ld rejected %ld duplicate %ld %s",
+ name, (long)(Now.time - cp->Started),
+ cp->Received, cp->Refused, cp->Rejected,
+ cp->Duplicate, buff);
+ }
+ if (cp->Data.Newsgroups.Data != NULL) {
+ free(cp->Data.Newsgroups.Data);
+ cp->Data.Newsgroups.Data = NULL;
+ }
+ if (cp->Data.Newsgroups.List != NULL) {
+ free(cp->Data.Newsgroups.List);
+ cp->Data.Newsgroups.List = NULL;
+ }
+ if (cp->Data.Distribution.Data != NULL) {
+ free(cp->Data.Distribution.Data);
+ cp->Data.Distribution.Data = NULL;
+ }
+ if (cp->Data.Distribution.List != NULL) {
+ free(cp->Data.Distribution.List);
+ cp->Data.Distribution.List = NULL;
+ }
+ if (cp->Data.Path.Data != NULL) {
+ free(cp->Data.Path.Data);
+ cp->Data.Path.Data = NULL;
+ }
+ if (cp->Data.Path.List != NULL) {
+ free(cp->Data.Path.List);
+ cp->Data.Path.List = NULL;
+ }
+ if (cp->Data.Overview.size != 0) {
+ free(cp->Data.Overview.data);
+ cp->Data.Overview.data = NULL;
+ cp->Data.Overview.size = 0;
+ cp->Data.Overview.left = 0;
+ cp->Data.Overview.used = 0;
+ }
+ if (cp->Data.XrefBufLength != 0) {
+ free(cp->Data.Xref);
+ cp->Data.Xref = NULL;
+ cp->Data.XrefBufLength = 0;
+ }
+ } else if (cp->Type == CTreject)
+ syslog(L_NOTICE, "%s %ld", name, cp->Rejected);
+ else if (cp->Out.left)
+ syslog(L_NOTICE, "%s closed lost %lu", name,
+ (unsigned long) cp->Out.left);
+ else
+ syslog(L_NOTICE, "%s closed", name);
+ WCHANremove(cp);
+ RCHANremove(cp);
+ SCHANremove(cp);
+ if (cp->Argument != NULL)
+ /* Set to NULL below. */
+ free(cp->Argument);
+ if (cp->fd >= 0 && close(cp->fd) < 0)
+ syslog(L_ERROR, "%s cant close %s %m", LogName, name);
+
+ if (cp->MaxCnx > 0 && cp->Type == CTnntp) {
+ int tfd;
+ CHANNEL *tempchan;
+
+ cp->fd = -1;
+ if ((label = RClabelname(cp)) != NULL) {
+ for(tfd = 0; tfd <= CHANlastfd; tfd++) {
+ tempchan = &CHANtable[tfd];
+ if(tempchan->fd > 0 && tempchan->Type == CTnntp &&
+ ((tmplabel = RClabelname(tempchan)) != NULL) &&
+ strcmp(label, tmplabel) == 0 &&
+ tempchan->ActiveCnx == 0) {
+ tempchan->ActiveCnx = cp->ActiveCnx;
+ RCHANadd(tempchan);
+ break;
+ }
+ }
+ }
+ }
+ }
+
+ /* Mark it unused. */
+ cp->Type = CTfree;
+ cp->State = CSerror;
+ cp->fd = -1;
+ cp->Argument = NULL;
+ cp->ActiveCnx = 0;
+
+ /* Free the buffers if they got big. */
+ if (cp->In.size > BIG_BUFFER) {
+ cp->In.size = 0;
+ cp->In.used = 0;
+ cp->In.left = 0;
+ free(cp->In.data);
+ cp->In.data = NULL;
+ }
+ if (cp->Out.size > BIG_BUFFER) {
+ cp->Out.size = 0;
+ cp->Out.used = 0;
+ cp->Out.left = 0;
+ free(cp->Out.data);
+ cp->Out.data = NULL;
+ }
+ if (cp->Sendid.size > 0) {
+ cp->Sendid.size = 0;
+ cp->Sendid.used = 0;
+ cp->Sendid.left = 0;
+ free(cp->Sendid.data);
+ cp->Sendid.data = NULL;
+ }
+}
+
+
+/*
+** Return a printable name for the channel.
+*/
+char *
+CHANname(const CHANNEL *cp)
+{
+ static char buff[SMBUF];
+ int i;
+ SITE * sp;
+ const char * p;
+ pid_t pid;
+
+ switch (cp->Type) {
+ default:
+ snprintf(buff, sizeof(buff), "?%d(#%d@%ld)?", cp->Type, cp->fd,
+ (long) (cp - CHANtable));
+ break;
+ case CTany:
+ snprintf(buff, sizeof(buff), "any:%d", cp->fd);
+ break;
+ case CTfree:
+ snprintf(buff, sizeof(buff), "free:%d", cp->fd);
+ break;
+ case CTremconn:
+ snprintf(buff, sizeof(buff), "remconn:%d", cp->fd);
+ break;
+ case CTreject:
+ snprintf(buff, sizeof(buff), "%s rejected", RChostname(cp));
+ break;
+ case CTnntp:
+ snprintf(buff, sizeof(buff), "%s:%d",
+ cp->Address.ss_family == 0 ? "localhost" : RChostname(cp),
+ cp->fd);
+ break;
+ case CTlocalconn:
+ snprintf(buff, sizeof(buff), "localconn:%d", cp->fd);
+ break;
+ case CTcontrol:
+ snprintf(buff, sizeof(buff), "control:%d", cp->fd);
+ break;
+ case CTexploder:
+ case CTfile:
+ case CTprocess:
+ /* Find the site that has this channel. */
+ for (p = "?", i = nSites, sp = Sites, pid = 0; --i >= 0; sp++)
+ if (sp->Channel == cp) {
+ p = sp->Name;
+ if (cp->Type != CTfile)
+ pid = sp->pid;
+ break;
+ }
+ if (pid == 0)
+ snprintf(buff, sizeof(buff), "%s:%d:%s",
+ MaxLength(p, p), cp->fd,
+ cp->Type == CTfile ? "file" : "proc");
+ else
+ snprintf(buff, sizeof(buff), "%s:%d:%s:%ld",
+ MaxLength(p, p), cp->fd,
+ cp->Type == CTfile ? "file" : "proc", (long)pid);
+ break;
+ }
+ return buff;
+}
+
+
+/*
+** Return the channel for a specified descriptor.
+*/
+CHANNEL *
+CHANfromdescriptor(int fd)
+{
+ if (fd <0 || fd > CHANtablesize)
+ return NULL;
+ return &CHANtable[fd];
+}
+
+
+/*
+** Iterate over all channels of a specified type.
+*/
+CHANNEL *
+CHANiter(int *ip, CHANNELTYPE Type)
+{
+ CHANNEL *cp;
+ int i;
+
+ if ((i = *ip) >= 0 && i < CHANtablesize) {
+ do {
+ cp = &CHANtable[i];
+ if (cp->Type == CTfree && cp->fd == -1)
+ continue;
+ if (Type == CTany || cp->Type == Type) {
+ *ip = ++i;
+ return cp;
+ }
+ } while (++i < CHANtablesize);
+ }
+ return NULL;
+}
+
+
+/*
+** Mark a channel as an active reader.
+*/
+void
+RCHANadd(CHANNEL *cp)
+{
+ FD_SET(cp->fd, &RCHANmask);
+ if (cp->fd > CHANlastfd)
+ CHANlastfd = cp->fd;
+
+ if (cp->Type != CTnntp)
+ /* Start reading at the beginning of the buffer. */
+ cp->In.used = 0;
+}
+
+
+/*
+** Remove a channel from the set of readers.
+*/
+void
+RCHANremove(CHANNEL *cp)
+{
+ if (FD_ISSET(cp->fd, &RCHANmask)) {
+ FD_CLR(cp->fd, &RCHANmask);
+ if (cp->fd == CHANlastfd) {
+ /* This was the highest descriptor, get a new highest. */
+ while (!FD_ISSET(CHANlastfd, &RCHANmask)
+ && !FD_ISSET(CHANlastfd, &WCHANmask)
+ && CHANlastfd > 1)
+ CHANlastfd--;
+ }
+ }
+}
+
+
+/*
+** Put a channel to sleep, call a function when it wakes.
+** Note that the Argument must be NULL or allocated memory!
+*/
+void
+SCHANadd(CHANNEL *cp, time_t Waketime, void *Event, innd_callback_t Waker,
+ void *Argument)
+{
+ if (!FD_ISSET(cp->fd, &SCHANmask)) {
+ SCHANcount++;
+ FD_SET(cp->fd, &SCHANmask);
+ }
+ if (cp->fd > CHANlastsleepfd)
+ CHANlastsleepfd = cp->fd;
+ cp->Waketime = Waketime;
+ cp->Waker = Waker;
+ if (cp->Argument != Argument) {
+ free(cp->Argument);
+ cp->Argument = Argument;
+ }
+ cp->Event = Event;
+}
+
+
+/*
+** Take a channel off the sleep list.
+*/
+void
+SCHANremove(CHANNEL *cp)
+{
+ if (FD_ISSET(cp->fd, &SCHANmask)) {
+ FD_CLR(cp->fd, &SCHANmask);
+ SCHANcount--;
+ cp->Waketime = 0;
+ if (cp->fd == CHANlastsleepfd) {
+ /* This was the highest descriptor, get a new highest. */
+ while (!FD_ISSET(CHANlastsleepfd, &SCHANmask)
+ && CHANlastsleepfd > 1)
+ CHANlastsleepfd--;
+ }
+ }
+}
+
+
+/*
+** Is a channel on the sleep list?
+*/
+bool
+CHANsleeping(CHANNEL *cp)
+{
+ return FD_ISSET(cp->fd, &SCHANmask);
+}
+
+
+/*
+** Wake up channels waiting for a specific event.
+*/
+void
+SCHANwakeup(void *Event)
+{
+ CHANNEL *cp;
+ int i;
+
+ for (cp = CHANtable, i = CHANtablesize; --i >= 0; cp++)
+ if (cp->Type != CTfree && cp->Event == Event && CHANsleeping(cp))
+ cp->Waketime = 0;
+}
+
+
+/*
+** Mark a channel as an active writer. Don't reset the Out->left field
+** since we could have buffered I/O already in there.
+*/
+void
+WCHANadd(CHANNEL *cp)
+{
+ if (cp->Out.left > 0) {
+ FD_SET(cp->fd, &WCHANmask);
+ if (cp->fd > CHANlastfd)
+ CHANlastfd = cp->fd;
+ }
+}
+
+
+/*
+** Remove a channel from the set of writers.
+*/
+void
+WCHANremove(CHANNEL *cp)
+{
+ if (FD_ISSET(cp->fd, &WCHANmask)) {
+ FD_CLR(cp->fd, &WCHANmask);
+ if (cp->Out.left <= 0) {
+ /* No data left -- reset used so we don't grow the buffer. */
+ cp->Out.used = 0;
+ cp->Out.left = 0;
+ }
+ if (cp->fd == CHANlastfd) {
+ /* This was the highest descriptor, get a new highest. */
+ while (!FD_ISSET(CHANlastfd, &RCHANmask)
+ && !FD_ISSET(CHANlastfd, &WCHANmask)
+ && CHANlastfd > 1)
+ CHANlastfd--;
+ }
+ }
+}
+
+
+/*
+** Set a channel to start off with the contents of an existing channel.
+*/
+void
+WCHANsetfrombuffer(CHANNEL *cp, struct buffer *bp)
+{
+ WCHANset(cp, &bp->data[bp->used], bp->left);
+}
+
+\f
+
+/*
+** Read in text data, return the amount we read.
+*/
+int
+CHANreadtext(CHANNEL *cp)
+{
+ ptrdiff_t i, j;
+ struct buffer *bp;
+ char *p;
+ int oerrno;
+ int maxbyte;
+ HDRCONTENT *hc = cp->Data.HdrContent;
+
+ /* Grow buffer if we're getting close to current limit. FIXME: The In
+ buffer doesn't use the normal meanings of .used and .left. */
+ bp = &cp->In;
+ bp->left = bp->size - bp->used;
+ if (bp->left <= LOW_WATER) {
+ i = GROW_AMOUNT(bp->size);
+ bp->size += i;
+ bp->left += i;
+ p = bp->data;
+ TMRstart(TMR_DATAMOVE);
+ bp->data = xrealloc(bp->data, bp->size);
+
+ /* Adjust offets of realloc moved the location of the memory region.
+ FIXME: This is invalid C, although it will work on most (all?)
+ common systems. The pointers need to be reduced to offets and then
+ turned back into relative pointers rather than adjusting the
+ pointers directly, since as soon as realloc is called, pointers
+ into the old space become invalid and may not be used further. */
+ if ((i = p - bp->data) != 0) {
+ if (cp->State == CSgetheader || cp->State == CSgetbody ||
+ cp->State == CSeatarticle) {
+ /* adjust offset only in CSgetheader, CSgetbody or
+ CSeatarticle */
+ if (cp->Data.BytesHeader != NULL)
+ cp->Data.BytesHeader -= i;
+ for (j = 0 ; j < MAX_ARTHEADER ; j++, hc++) {
+ if (hc->Value != NULL)
+ hc->Value -= i;
+ }
+ }
+ }
+ TMRstop(TMR_DATAMOVE);
+ }
+
+ /* Read in whatever is there, up to some reasonable limit.
+
+ We want to limit the amount of time devoted to processing the incoming
+ data for any given channel. There's no easy way of doing that, though,
+ so we restrict the data size instead.
+
+ If the data is part of a single large article, then reading and
+ processing many kilobytes at a time costs very little. If the data is
+ a long list of CHECK commands from a streaming feed, then every line of
+ data will require a history lookup, and we probably don't want to do
+ more than about 10 of those per channel on each cycle of the main
+ select() loop (otherwise we might take too long before giving other
+ channels a turn). 10 lines of CHECK commands suggests a limit of about
+ 1KB of data, or less. innconf->maxcmdreadsize (BUFSIZ by default) is
+ often about 1KB, and is attractive for other reasons, so let's use that
+ as our size limit. If innconf->maxcmdreadsize is 0, there is no limit.
+
+ Reduce the read size only if we're reading commands.
+
+ FIXME: A better approach would be to limit the number of commands we
+ process for each channel. */
+ if (innconf->maxcmdreadsize <= 0 || cp->State != CSgetcmd
+ || bp->left < innconf->maxcmdreadsize)
+ maxbyte = bp->left;
+ else
+ maxbyte = innconf->maxcmdreadsize;
+ TMRstart(TMR_NNTPREAD);
+ i = read(cp->fd, &bp->data[bp->used], maxbyte);
+ TMRstop(TMR_NNTPREAD);
+ if (i < 0) {
+ /* Solaris (at least 2.4 through 2.6) will occasionally return
+ EAGAIN in response to a read even if the file descriptor already
+ selected true for reading, apparently due to some internal
+ resource exhaustion. In that case, return -2, which will drop
+ back out to the main loop and go on to the next file descriptor,
+ as if the descriptor never selected true. This check will
+ probably never trigger on platforms other than Solaris. */
+ if (errno == EAGAIN)
+ return -2;
+ oerrno = errno;
+ p = CHANname(cp);
+ errno = oerrno;
+ sysnotice("%s cant read", p);
+ return -1;
+ }
+ if (i == 0) {
+ p = CHANname(cp);
+ notice("%s readclose", p);
+ return 0;
+ }
+
+ bp->used += i;
+ bp->left -= i;
+ return i;
+}
+
+
+/*
+** If I/O backs up a lot, we can get EMSGSIZE on some systems. If that
+** happens we want to do the I/O in chunks. We assume stdio's BUFSIZ is
+** a good chunk value.
+*/
+static int
+CHANwrite(int fd, char *p, long length)
+{
+ int i;
+ char *save;
+
+ do {
+ /* Try the standard case -- write it all. */
+ i = write(fd, p, length);
+ if (i > 0 || (i < 0 && errno != EMSGSIZE && errno != EINTR))
+ return i;
+ } while (i < 0 && errno == EINTR);
+
+ /* Write it in pieces. */
+ for (save = p, i = 0; length; p += i, length -= i) {
+ i = write(fd, p, (length > BUFSIZ ? BUFSIZ : length));
+ if (i <= 0)
+ break;
+ }
+
+ /* Return error, or partial results if we got something. */
+ return p == save ? i : p - save;
+}
+
+
+/*
+** Try to flush out the buffer. Use this only on file channels!
+*/
+bool
+WCHANflush(CHANNEL *cp)
+{
+ struct buffer *bp;
+ int i;
+
+ /* Write it. */
+ for (bp = &cp->Out; bp->left > 0; bp->left -= i, bp->used += i) {
+ i = CHANwrite(cp->fd, &bp->data[bp->used], bp->left);
+ if (i < 0) {
+ syslog(L_ERROR, "%s cant flush count %lu %m",
+ CHANname(cp), (unsigned long) bp->left);
+ return false;
+ }
+ if (i == 0) {
+ syslog(L_ERROR, "%s cant flush count %lu",
+ CHANname(cp), (unsigned long) bp->left);
+ return false;
+ }
+ }
+ WCHANremove(cp);
+ return true;
+}
+
+\f
+
+/*
+** Wakeup routine called after a write channel was put to sleep.
+*/
+static void
+CHANwakeup(CHANNEL *cp)
+{
+ syslog(L_NOTICE, "%s wakeup", CHANname(cp));
+ WCHANadd(cp);
+}
+
+
+/*
+** Attempting to write would block; stop output or give up.
+*/
+static void
+CHANwritesleep(CHANNEL *cp, char *p)
+{
+ int i;
+
+ if ((i = ++(cp->BlockedWrites)) > innconf->badiocount)
+ switch (cp->Type) {
+ default:
+ break;
+ case CTreject:
+ case CTnntp:
+ case CTfile:
+ case CTexploder:
+ case CTprocess:
+ syslog(L_ERROR, "%s blocked closing", p);
+ SITEchanclose(cp);
+ CHANclose(cp, p);
+ return;
+ }
+ i *= innconf->blockbackoff;
+ syslog(L_ERROR, "%s blocked sleeping %d", p, i);
+ SCHANadd(cp, Now.time + i, NULL, CHANwakeup, NULL);
+}
+
+
+#if defined(INND_FIND_BAD_FDS)
+/*
+** We got an unknown error in select. Find out the culprit.
+** Not really ready for production use yet, and it's expensive, too.
+*/
+static void
+CHANdiagnose(void)
+{
+ fd_set Test;
+ int i;
+ struct timeval t;
+
+ FD_ZERO(&Test);
+ for (i = CHANlastfd; i >= 0; i--) {
+ if (FD_ISSET(i, &RCHANmask)) {
+ FD_SET(i, &Test);
+ t.tv_sec = 0;
+ t.tv_usec = 0;
+ if (select(i + 1, &Test, NULL, NULL, &t) < 0
+ && errno != EINTR) {
+ syslog(L_ERROR, "%s Bad Read File %d", LogName, i);
+ FD_CLR(i, &RCHANmask);
+ /* Probably do something about the file descriptor here; call
+ * CHANclose on it? */
+ }
+ FD_CLR(i, &Test);
+ }
+ if (FD_ISSET(i, &WCHANmask)) {
+ FD_SET(i, &Test);
+ t.tv_sec = 0;
+ t.tv_usec = 0;
+ if (select(i + 1, NULL, &Test, NULL, &t) < 0
+ && errno != EINTR) {
+ syslog(L_ERROR, "%s Bad Write File %d", LogName, i);
+ FD_CLR(i, &WCHANmask);
+ /* Probably do something about the file descriptor here; call
+ * CHANclose on it? */
+ }
+ FD_CLR(i, &Test);
+ }
+ }
+}
+#endif /* defined(INND_FIND_BAD_FDS) */
+
+void
+CHANsetActiveCnx(CHANNEL *cp) {
+ int found;
+ CHANNEL *tempchan;
+ char *label, *tmplabel;
+ int tfd;
+
+ if((cp->fd > 0) && (cp->Type == CTnntp) && (cp->ActiveCnx == 0)) {
+ found = 1;
+ if ((label = RClabelname(cp)) != NULL) {
+ for(tfd = 0; tfd <= CHANlastfd; tfd++) {
+ tempchan = &CHANtable[tfd];
+ if ((tmplabel = RClabelname(tempchan)) == NULL) {
+ continue;
+ }
+ if(strcmp(label, tmplabel) == 0) {
+ if(tempchan->ActiveCnx != 0)
+ found++;
+ }
+ }
+ }
+ cp->ActiveCnx = found;
+ }
+}
+
+/*
+** Main I/O loop. Wait for data, call the channel's handler when there is
+** something to read or when the queued write is finished. In order to
+** be fair (i.e., don't always give descriptor n priority over n+1), we
+** remember where we last had something and pick up from there.
+**
+** Yes, the main code has really wandered over to the side a lot.
+*/
+void
+CHANreadloop(void)
+{
+ static char EXITING[] = "INND exiting because of signal\n";
+ static int fd;
+ ptrdiff_t i, j;
+ int startpoint;
+ int count;
+ int lastfd;
+ int oerrno;
+ CHANNEL *cp;
+ struct buffer *bp;
+ fd_set MyRead;
+ fd_set MyWrite;
+ struct timeval MyTime;
+ long silence;
+ char *p;
+ time_t LastUpdate;
+ HDRCONTENT *hc;
+
+ STATUSinit();
+
+ LastUpdate = GetTimeInfo(&Now) < 0 ? 0 : Now.time;
+ for ( ; ; ) {
+ /* See if any processes died. */
+ PROCscan();
+
+ /* Wait for data, note the time. */
+ MyRead = RCHANmask;
+ MyWrite = WCHANmask;
+ MyTime = TimeOut;
+ if (innconf->timer) {
+ unsigned long now = TMRnow();
+
+ if (now >= 1000 * (unsigned long)(innconf->timer)) {
+ TMRsummary("ME", timer_name);
+ InndHisLogStats();
+ MyTime.tv_sec = innconf->timer;
+ }
+ else {
+ MyTime.tv_sec = innconf->timer - now / 1000;
+ }
+ }
+ TMRstart(TMR_IDLE);
+ count = select(CHANlastfd + 1, &MyRead, &MyWrite, NULL, &MyTime);
+ TMRstop(TMR_IDLE);
+
+ STATUSmainloophook();
+ if (GotTerminate) {
+ write(2, EXITING, strlen(EXITING));
+ CleanupAndExit(0, (char *)NULL);
+ }
+ if (count < 0) {
+ if (errno != EINTR) {
+ syslog(L_ERROR, "%s cant select %m", LogName);
+#if defined(INND_FIND_BAD_FDS)
+ CHANdiagnose();
+#endif /* defined(INND_FIND_BAD_FDS) */
+ }
+ continue;
+ }
+
+ /* Update the "reasonably accurate" time. */
+ if (GetTimeInfo(&Now) < 0)
+ syslog(L_ERROR, "%s cant gettimeinfo %m", LogName);
+ if (Now.time > LastUpdate + TimeOut.tv_sec) {
+ HISsync(History);
+ if (ICDactivedirty) {
+ ICDwriteactive();
+ ICDactivedirty = 0;
+ }
+ LastUpdate = Now.time;
+ }
+
+ if (count == 0) {
+ /* No channels active, so flush and skip if nobody's
+ * sleeping. */
+ if (Mode == OMrunning)
+ ICDwrite();
+ if (SCHANcount == 0)
+ continue;
+ }
+
+ /* Try the control channel first. */
+ if (FD_ISSET(CHANccfd, &RCHANmask) && FD_ISSET(CHANccfd, &MyRead)) {
+ count--;
+ if (count > 3)
+ count = 3; /* might be more requests */
+ (*CHANcc->Reader)(CHANcc);
+ FD_CLR(CHANccfd, &MyRead);
+ }
+
+#ifdef PRIORITISE_REMCONN
+ /* Try the remconn channel next. */
+ for (j = 0 ; (j < chanlimit) && (CHANrcfd[j] >= 0) ; j++) {
+ if (FD_ISSET(CHANrcfd[j], &RCHANmask) && FD_ISSET(CHANrcfd[j], &MyRead)) {
+ count--;
+ if (count > 3)
+ count = 3; /* might be more requests */
+ (*CHANrc[j]->Reader)(CHANrc[j]);
+ FD_CLR(CHANrcfd[j], &MyRead);
+ }
+ }
+#endif /* PRIORITISE_REMCONN */
+
+ /* Loop through all active channels. Somebody could have closed
+ * closed a channel so we double-check the global mask before
+ * looking at what select returned. The code here is written so
+ * that a channel could be reading and writing and sleeping at the
+ * same time, even though that's not possible. (Just as well,
+ * since in SysVr4 the count would be wrong.) */
+ lastfd = CHANlastfd;
+ if (lastfd < CHANlastsleepfd)
+ lastfd = CHANlastsleepfd;
+ if (fd > lastfd)
+ fd = 0;
+ startpoint = fd;
+ do {
+ cp = &CHANtable[fd];
+
+ if (cp->MaxCnx > 0 && cp->HoldTime > 0) {
+ CHANsetActiveCnx(cp);
+ if((cp->ActiveCnx > cp->MaxCnx) && (cp->fd > 0)) {
+ if(cp->Started + cp->HoldTime < Now.time) {
+ CHANclose(cp, CHANname(cp));
+ } else {
+ if (fd >= lastfd)
+ fd = 0;
+ else
+ fd++;
+ cp->ActiveCnx = 0;
+ RCHANremove(cp);
+ }
+ continue;
+ }
+ }
+
+ /* Anything to read? */
+ if (FD_ISSET(fd, &RCHANmask) && FD_ISSET(fd, &MyRead)) {
+ count--;
+ if (cp->Type == CTfree) {
+ syslog(L_ERROR, "%s %d free but was in RMASK",
+ CHANname(cp), fd);
+ /* Don't call RCHANremove since cp->fd will be -1. */
+ FD_CLR(fd, &RCHANmask);
+ close(fd);
+ }
+ else {
+ cp->LastActive = Now.time;
+ (*cp->Reader)(cp);
+ }
+ }
+
+ /* Check and see if the buffer is grossly overallocated and shrink
+ if needed */
+ if (cp->In.size > (BIG_BUFFER)) {
+ if (cp->In.used != 0) {
+ if ((cp->In.size / cp->In.used) > 10) {
+ cp->In.size = (cp->In.used * 2) > START_BUFF_SIZE ? (cp->In.used * 2) : START_BUFF_SIZE;
+ p = cp->In.data;
+ TMRstart(TMR_DATAMOVE);
+ cp->In.data = xrealloc(cp->In.data, cp->In.size);
+ cp->In.left = cp->In.size - cp->In.used;
+ /* do not move data, since xrealloc did it already */
+ if ((i = p - cp->In.data) != 0) {
+ if (cp->State == CSgetheader ||
+ cp->State == CSgetbody ||
+ cp->State == CSeatarticle) {
+ /* adjust offset only in CSgetheader, CSgetbody
+ or CSeatarticle */
+ if (cp->Data.BytesHeader != NULL)
+ cp->Data.BytesHeader -= i;
+ hc = cp->Data.HdrContent;
+ for (j = 0 ; j < MAX_ARTHEADER ; j++, hc++) {
+ if (hc->Value != NULL)
+ hc->Value -= i;
+ }
+ }
+ }
+ TMRstop(TMR_DATAMOVE);
+ }
+ } else {
+ p = cp->In.data;
+ TMRstart(TMR_DATAMOVE);
+ cp->In.data = xrealloc(cp->In.data, START_BUFF_SIZE);
+ cp->In.size = cp->In.left = START_BUFF_SIZE;
+ if ((i = p - cp->In.data) != 0) {
+ if (cp->State == CSgetheader ||
+ cp->State == CSgetbody ||
+ cp->State == CSeatarticle) {
+ /* adjust offset only in CSgetheader, CSgetbody
+ or CSeatarticle */
+ if (cp->Data.BytesHeader != NULL)
+ cp->Data.BytesHeader -= i;
+ hc = cp->Data.HdrContent;
+ for (j = 0 ; j < MAX_ARTHEADER ; j++, hc++) {
+ if (hc->Value != NULL)
+ hc->Value -= i;
+ }
+ }
+ }
+ TMRstop(TMR_DATAMOVE);
+ }
+ }
+ /* Possibly recheck for dead children so we don't get SIGPIPE
+ * on readerless channels. */
+ if (PROCneedscan)
+ PROCscan();
+
+ /* Ready to write? */
+ if (FD_ISSET(fd, &WCHANmask) && FD_ISSET(fd, &MyWrite)) {
+ count--;
+ if (cp->Type == CTfree) {
+ syslog(L_ERROR, "%s %d free but was in WMASK",
+ CHANname(cp), fd);
+ /* Don't call WCHANremove since cp->fd will be -1. */
+ FD_CLR(fd, &WCHANmask);
+ close(fd);
+ }
+ else {
+ bp = &cp->Out;
+ if (bp->left) {
+ cp->LastActive = Now.time;
+ i = CHANwrite(fd, &bp->data[bp->used], bp->left);
+ if (i <= 0) {
+ oerrno = errno;
+ p = CHANname(cp);
+ errno = oerrno;
+ if (i < 0)
+ sysnotice("%s cant write", p);
+ else
+ notice("%s cant write", p);
+ cp->BadWrites++;
+ if (i < 0 && oerrno == EPIPE) {
+ SITEchanclose(cp);
+ CHANclose(cp, p);
+ }
+ else if (i < 0 &&
+ (oerrno == EWOULDBLOCK
+ || oerrno == EAGAIN)) {
+ WCHANremove(cp);
+ CHANwritesleep(cp, p);
+ }
+ else if (cp->BadWrites >= innconf->badiocount) {
+ syslog(L_ERROR, "%s sleeping", p);
+ WCHANremove(cp);
+ SCHANadd(cp,
+ Now.time + innconf->pauseretrytime,
+ NULL, CHANwakeup, NULL);
+ }
+ }
+ else {
+ cp->BadWrites = 0;
+ cp->BlockedWrites = 0;
+ bp->left -= i;
+ bp->used += i;
+ if (bp->left <= 0) {
+ WCHANremove(cp);
+ (*cp->WriteDone)(cp);
+ } else if (bp->used > (bp->size/COMP_THRESHOLD)) {
+ /* compact the buffer, shoving the
+ data back to the beginning.
+ <rmtodd@mailhost.ecn.ou.edu> */
+ buffer_set(bp, &bp->data[bp->used], bp->left);
+ }
+ }
+ }
+ else
+ /* Should not be possible. */
+ WCHANremove(cp);
+ }
+ }
+
+ /* Coming off a sleep? */
+ if (FD_ISSET(fd, &SCHANmask) && cp->Waketime <= Now.time) {
+ if (cp->Type == CTfree) {
+ syslog(L_ERROR,"%s ERROR s-select free %d",CHANname(cp),fd);
+ FD_CLR(fd, &SCHANmask);
+ close(fd);
+ } else {
+ cp->LastActive = Now.time;
+ SCHANremove(cp);
+ (*cp->Waker)(cp);
+ }
+ }
+
+ /* Toss CTreject channel early if it's inactive. */
+ if (cp->Type == CTreject
+ && cp->LastActive + REJECT_TIMEOUT < Now.time) {
+ p = CHANname(cp);
+ syslog(L_NOTICE, "%s timeout reject", p);
+ CHANclose(cp, p);
+ }
+
+ /* Has this channel been inactive very long? */
+ if (cp->Type == CTnntp
+ && cp->LastActive + cp->NextLog < Now.time) {
+ p = CHANname(cp);
+ silence = Now.time - cp->LastActive;
+ cp->NextLog += innconf->chaninacttime;
+ syslog(L_NOTICE, "%s inactive %ld", p, silence / 60L);
+ if (silence > innconf->peertimeout) {
+ syslog(L_NOTICE, "%s timeout", p);
+ CHANclose(cp, p);
+ }
+ }
+
+ /* Bump pointer, modulo the table size. */
+ if (fd >= lastfd)
+ fd = 0;
+ else
+ fd++;
+
+ /* If there is nothing to do, break out. */
+ if (count == 0 && SCHANcount == 0)
+ break;
+
+ } while (fd != startpoint);
+ }
+}
--- /dev/null
+/* $Id: icd.c 6156 2003-01-19 20:58:05Z rra $
+**
+** Routines to read and write the active file.
+*/
+
+#include "config.h"
+#include "clibrary.h"
+#include "portable/mmap.h"
+#include <sys/uio.h>
+
+#include "inn/innconf.h"
+#include "innd.h"
+#include "ov.h"
+
+/* If we fork and exec under Cygwin, children hold onto the mmap */
+/* of active, and Windows won't let us resize or replace it. */
+#ifdef __CYGWIN__
+# undef HAVE_MMAP
+#endif
+
+static char *ICDactpath = NULL;
+static char *ICDactpointer;
+static int ICDactfd;
+static int ICDactsize;
+
+
+/*
+** Set and unset (or copy) IOVEC elements. We make copies to
+** avoid problems with mmap.
+*/
+#ifdef HAVE_MMAP
+static void
+ICDiovset(struct iovec *iovp, char *base, int len)
+{
+ iovp->iov_len = len;
+ iovp->iov_base = xmalloc(iovp->iov_len);
+ memcpy(iovp->iov_base, base, iovp->iov_len);
+}
+#define ICDiovrelease(iovp) free((iovp)->iov_base)
+
+#else /* !HAVE_MMAP */
+
+#define ICDiovset(iovp, base, len) \
+ (iovp)->iov_base = base, (iovp)->iov_len = len
+#define ICDiovrelease(iovp) /* NULL */
+
+#endif /* HAVE_MMAP */
+
+
+/*
+** Close the active file, releasing its resources.
+*/
+static void
+ICDcloseactive(void)
+{
+ if (ICDactpointer) {
+#ifdef HAVE_MMAP
+ if (munmap(ICDactpointer, ICDactsize) < 0)
+ syslog(L_ERROR, "%s cant munmap %s %m", LogName, ICDactpath);
+#else
+ free(ICDactpointer);
+#endif
+ ICDactpointer = NULL;
+ if (close(ICDactfd) < 0) {
+ syslog(L_FATAL, "%s cant close %s %m", LogName, ICDactpath);
+ exit(1);
+ }
+ }
+}
+
+
+/*
+** Set up the hash and in-core tables.
+*/
+void
+ICDsetup(StartSites)
+ bool StartSites;
+{
+ if (ICDneedsetup == true) {
+ ICDneedsetup = false;
+ }
+ else {
+ ICDcloseactive();
+ NGparsefile();
+ }
+ if (NGfind("control") == NULL || NGfind("junk") == NULL) {
+ syslog(L_FATAL, "%s internal no control and/or junk group", LogName);
+ exit(1);
+ }
+ if (NGfind("control.cancel") == NULL) {
+ syslog(L_FATAL, "%s internal no control.cancel group", LogName);
+ exit(1);
+ }
+ if (innconf->mergetogroups && NGfind("to") == NULL) {
+ syslog(L_FATAL, "%s internal no to group", LogName);
+ exit(1);
+ }
+ SITEparsefile(StartSites);
+}
+
+
+/*
+** Write out all in-core data.
+*/
+void
+ICDwrite(void)
+{
+ HISsync(History);
+ SMflushcacheddata(SM_ALL);
+
+ if (ICDactivedirty) {
+ ICDwriteactive();
+ ICDactivedirty = 0;
+ }
+
+ /* Flush log and error log. */
+ if (fflush(Log) == EOF)
+ syslog(L_ERROR, "%s cant fflush log %m", LogName);
+ if (fflush(Errlog) == EOF)
+ syslog(L_ERROR, "%s cant fflush errlog %m", LogName);
+}
+
+
+/*
+** Close things down.
+*/
+void
+ICDclose(void)
+{
+ ICDwrite();
+ ICDcloseactive();
+}
+
+
+/*
+** Scan the active file, and renumber the min/max counts.
+*/
+bool
+ICDrenumberactive(void)
+{
+ int i;
+ NEWSGROUP *ngp;
+
+ for (i = nGroups, ngp = Groups; --i >= 0; ngp++)
+ if (!NGrenumber(ngp))
+ return false;
+ if (i < 0)
+ ICDwrite();
+ return true;
+}
+
+
+/*
+** Use writev() to replace the active file.
+*/
+static bool
+ICDwritevactive(struct iovec *vp, int vpcount)
+{
+ static char *BACKUP = NULL;
+ static char *NEWACT = NULL;
+ static char WHEN[] = "backup active";
+ int fd;
+ int oerrno;
+#ifdef __CYGWIN__
+ size_t newactsize, padactsize, wrote;
+ struct iovec *newvp;
+ char *filler;
+ int i;
+#endif
+
+ if (BACKUP == NULL)
+ BACKUP = concatpath(innconf->pathdb, _PATH_OLDACTIVE);
+ if (NEWACT == NULL)
+ NEWACT = concatpath(innconf->pathdb, _PATH_NEWACTIVE);
+ /* Write the current file to a backup. */
+ if (unlink(BACKUP) < 0 && errno != ENOENT) {
+ oerrno = errno;
+ syslog(L_ERROR, "%s cant unlink %s %m", LogName, BACKUP);
+ IOError(WHEN, oerrno);
+ }
+ if ((fd = open(BACKUP, O_WRONLY | O_TRUNC | O_CREAT, 0664)) < 0) {
+ oerrno = errno;
+ syslog(L_ERROR, "%s cant open %s %m", LogName, BACKUP);
+ IOError(WHEN, oerrno);
+ }
+ else if (xwrite(fd, ICDactpointer, ICDactsize) < 0) {
+ oerrno = errno;
+ syslog(L_ERROR, "%s cant write %s %m", LogName, BACKUP);
+ IOError(WHEN, oerrno);
+ close(fd);
+ }
+ else if (close(fd) < 0) {
+ oerrno = errno;
+ syslog(L_ERROR, "%s cant close %s %m", LogName, BACKUP);
+ IOError(WHEN, oerrno);
+ }
+
+#ifdef __CYGWIN__
+ /* If we are shrinking active, junk will be at the end between the */
+ /* writev and ftruncate. Clobber it with values that overview and */
+ /* nnrpd can ignore. */
+ for (newactsize = 0, i = 0; i < vpcount; i++)
+ newactsize += vp[i].iov_len;
+ if (newactsize < ICDactsize) {
+ padactsize = ICDactsize - newactsize;
+ newvp = xmalloc((vpcount + 1) * sizeof(struct iovec));
+ for (i = 0; i < vpcount; i++)
+ newvp[i] = vp[i];
+ filler = xcalloc(padactsize, 1);
+ *filler = '.';
+ filler[padactsize - 1] = '\n';
+ newvp[vpcount].iov_base = filler;
+ newvp[vpcount].iov_len = padactsize;
+ vpcount++;
+ }
+ else {
+ padactsize = 0;
+ newvp = vp;
+ }
+ oerrno = 0;
+ if (lseek(ICDactfd, 0, SEEK_SET) == -1) {
+ oerrno = errno;
+ syslog(L_ERROR, "%s cant rewind %s %m", LogName, ICDactpath);
+ IOError(WHEN, oerrno);
+ goto bailout;
+ }
+ if (xwritev(ICDactfd, newvp, vpcount) < 0) {
+ oerrno = errno;
+ syslog(L_ERROR, "%s cant write %s %m", LogName, ICDactpath);
+ IOError(WHEN, oerrno);
+ goto bailout;
+ }
+ if (newactsize < ICDactsize && ftruncate(ICDactfd, newactsize) != 0) {
+ oerrno = errno;
+ syslog(L_ERROR, "%s cant truncate %s", LogName, ICDactpath);
+ }
+
+bailout:
+ if (padactsize != 0) {
+ free(filler);
+ free(newvp);
+ }
+ if (oerrno != 0)
+ return false;
+
+#else /* !__CYGWIN__, do it the Unix way. */
+
+ /* Open the active file. */
+ fd = open(NEWACT, O_WRONLY | O_TRUNC | O_CREAT, ARTFILE_MODE);
+ if (fd < 0) {
+ oerrno = errno;
+ syslog(L_ERROR, "%s cant open %s %m", LogName, NEWACT);
+ IOError(WHEN, oerrno);
+ return false;
+ }
+
+ /* Write it. */
+ if (xwritev(fd, vp, vpcount) < 0) {
+ oerrno = errno;
+ syslog(L_ERROR, "%s cant write %s %m", LogName, NEWACT);
+ IOError(WHEN, oerrno);
+ close(fd);
+ return false;
+ }
+
+ /* Close it. */
+ close(fd);
+
+ /* Rename it to be the canonical active file */
+ if (rename(NEWACT, ICDactpath) < 0) {
+ oerrno = errno;
+ syslog(L_ERROR, "%s cant rename %s to %s %m",
+ LogName, NEWACT, ICDactpath);
+ IOError(WHEN, oerrno);
+ return false;
+ }
+
+#endif /* __CYGWIN__ */
+
+ /* Invalidate in-core pointers. */
+ ICDcloseactive();
+
+ /* Restore in-core pointers. */
+ if (Mode != OMrunning) {
+ ICDneedsetup = true;
+ /* Force the active file into memory. */
+ NGparsefile();
+ }
+ else
+ ICDsetup(true);
+ return true;
+}
+
+
+/*
+** Change the flag on a newsgroup. Fairly easy.
+*/
+bool
+ICDchangegroup(NEWSGROUP *ngp, char *Rest)
+{
+ static char NEWLINE[] = "\n";
+ int i;
+ struct iovec iov[3];
+ bool ret;
+ char *Name;
+ long Last;
+
+ /* Set up the scatter/gather vectors. */
+ ICDiovset(&iov[0], ICDactpointer, ngp->Rest - ICDactpointer);
+ ICDiovset(&iov[1], Rest, strlen(Rest));
+ Name = xstrdup(ngp->Name);
+ Last = ngp->Last;
+ if (++ngp < &Groups[nGroups]) {
+ /* Not the last group, keep the \n from the next line. */
+ i = ngp->Start;
+ ICDiovset(&iov[2], &ICDactpointer[i - 1], ICDactsize - i + 1);
+ }
+ else {
+ /* Last group -- append a newline. */
+ ICDiovset(&iov[2], NEWLINE, strlen(NEWLINE));
+ }
+ ret = ICDwritevactive(iov, 3);
+ ICDiovrelease(&iov[0]);
+ ICDiovrelease(&iov[1]);
+ ICDiovrelease(&iov[2]);
+
+ if (ret) {
+ if (innconf->enableoverview && !OVgroupadd(Name, 0, Last, Rest)) {
+ free(Name);
+ return false;
+ }
+ }
+ free(Name);
+ return ret;
+}
+
+
+/*
+** Add a newsgroup. Append a line to the end of the active file and reload.
+*/
+bool
+ICDnewgroup(char *Name, char *Rest)
+{
+ char buff[SMBUF];
+ struct iovec iov[2];
+ bool ret;
+
+ /* Set up the scatter/gather vectors. */
+ if (strlen(Name) + strlen(Rest) > SMBUF - 24) {
+ syslog(L_ERROR, "%s too_long %s", LogName, MaxLength(Name, Name));
+ return false;
+ }
+ snprintf(buff, sizeof(buff), "%s 0000000000 0000000001 %s\n", Name, Rest);
+ ICDiovset(&iov[0], ICDactpointer, ICDactsize);
+ ICDiovset(&iov[1], buff, strlen(buff));
+
+ ret = ICDwritevactive(iov, 2);
+ ICDiovrelease(&iov[0]);
+ ICDiovrelease(&iov[1]);
+ if (ret) {
+ if (innconf->enableoverview && !OVgroupadd(Name, 1, 0, Rest))
+ return false;
+ }
+ return ret;
+}
+
+
+/*
+** Remove a newsgroup. Splice the line out of the active file and reload.
+*/
+bool
+ICDrmgroup(NEWSGROUP *ngp)
+{
+ struct iovec iov[2];
+ int i;
+ bool ret;
+ char *Name;
+
+ /* Don't let anyone remove newsgroups that INN requires exist. */
+ if (strcmp(ngp->Name, "junk") == 0 || strcmp(ngp->Name, "control") == 0)
+ return false;
+ if (innconf->mergetogroups && strcmp(ngp->Name, "to") == 0)
+ return false;
+
+ Name = xstrdup(ngp->Name);
+ /* If this is the first group in the file, write everything after. */
+ if (ngp == &Groups[0]) {
+ i = ngp[1].Start;
+ ICDiovset(&iov[0], &ICDactpointer[i], ICDactsize - i);
+ ret = ICDwritevactive(iov, 1);
+ ICDiovrelease(&iov[0]);
+ if (ret) {
+ if (innconf->enableoverview && !OVgroupdel(Name)) {
+ free(Name);
+ return false;
+ }
+ }
+ free(Name);
+ return ret;
+ }
+
+ /* Write everything up to this group. */
+ ICDiovset(&iov[0], ICDactpointer, ngp->Start);
+
+ /* If this is the last group, that's all we have to write. */
+ if (ngp == &Groups[nGroups - 1]) {
+ ret = ICDwritevactive(iov, 1);
+ ICDiovrelease(&iov[0]);
+ if (ret) {
+ if (innconf->enableoverview && !OVgroupdel(Name)) {
+ free(Name);
+ return false;
+ }
+ }
+ free(Name);
+ return ret;
+ }
+
+ /* Write everything after this group. */
+ i = ngp[1].Start;
+ ICDiovset(&iov[1], &ICDactpointer[i], ICDactsize - i);
+ ret = ICDwritevactive(iov, 2);
+ ICDiovrelease(&iov[0]);
+ ICDiovrelease(&iov[1]);
+ if (ret) {
+ if (innconf->enableoverview && !OVgroupdel(Name)) {
+ free(Name);
+ return false;
+ }
+ }
+ free(Name);
+ return ret;
+}
+
+\f
+
+/*
+** Open the active file and "map" it into memory.
+*/
+char *
+ICDreadactive(endp)
+ char **endp;
+{
+ struct stat Sb;
+
+ if (ICDactpointer) {
+ *endp = ICDactpointer + ICDactsize;
+ return ICDactpointer;
+ }
+ if (ICDactpath == NULL)
+ ICDactpath = concatpath(innconf->pathdb, _PATH_ACTIVE);
+ if ((ICDactfd = open(ICDactpath, O_RDWR)) < 0) {
+ syslog(L_FATAL, "%s cant open %s %m", LogName, ICDactpath);
+ exit(1);
+ }
+ close_on_exec(ICDactfd, true);
+
+#ifdef HAVE_MMAP
+
+ if (fstat(ICDactfd, &Sb) < 0) {
+ syslog(L_FATAL, "%s cant fstat %d %s %m",
+ LogName, ICDactfd, ICDactpath);
+ exit(1);
+ }
+ ICDactsize = Sb.st_size;
+ ICDactpointer = mmap(NULL, ICDactsize, PROT_READ|PROT_WRITE,
+ MAP_SHARED, ICDactfd, 0);
+ if (ICDactpointer == (char *)-1) {
+ syslog(L_FATAL, "%s cant mmap %d %s %m",
+ LogName, ICDactfd, ICDactpath);
+ exit(1);
+ }
+
+#else /* !HAVE_MMAP */
+
+ if ((ICDactpointer = ReadInDescriptor(ICDactfd, &Sb)) == NULL) {
+ syslog(L_FATAL, "%s cant read %s %m", LogName, ICDactpath);
+ exit(1);
+ }
+ ICDactsize = Sb.st_size;
+
+#endif /* HAVE_MMAP */
+
+ *endp = ICDactpointer + ICDactsize;
+ return ICDactpointer;
+}
+
+
+/*
+** Write the active file out.
+*/
+void
+ICDwriteactive(void)
+{
+#ifdef HAVE_MMAP
+ if (msync(ICDactpointer, ICDactsize, MS_ASYNC) < 0) {
+ syslog(L_FATAL, "%s msync failed %s %m", LogName, ICDactpath);
+ exit(1);
+ }
+#else /* !HAVE_MMAP */
+ if (lseek(ICDactfd, 0, SEEK_SET) == -1) {
+ syslog(L_FATAL, "%s cant rewind %s %m", LogName, ICDactpath);
+ exit(1);
+ }
+ if (xwrite(ICDactfd, ICDactpointer, ICDactsize) < 0) {
+ syslog(L_FATAL, "%s cant write %s %m", LogName, ICDactpath);
+ exit(1);
+ }
+#endif /* HAVE_MMAP */
+}
--- /dev/null
+/* $Id: innd.c 7858 2008-06-05 18:51:20Z iulius $
+**
+** Variable definitions, miscellany, and main().
+*/
+
+#include "config.h"
+#include "clibrary.h"
+
+#include "inn/innconf.h"
+#include "inn/messages.h"
+#include "innperl.h"
+
+#define DEFINE_DATA
+#include "innd.h"
+#include "ov.h"
+
+
+bool Debug = false;
+bool NNRPTracing = false;
+bool StreamingOff = false ; /* default is we can stream */
+bool Tracing = false;
+bool DoCancels = true;
+char LogName[] = "SERVER";
+int ErrorCount = IO_ERROR_COUNT;
+OPERATINGMODE Mode = OMrunning;
+int RemoteLimit = REMOTELIMIT;
+time_t RemoteTimer = REMOTETIMER;
+int RemoteTotal = REMOTETOTAL;
+bool ThrottledbyIOError = false;
+
+static char *PID = NULL;
+
+/* Signal handling. If we receive a signal that should kill the server,
+ killer_signal is set to the signal number that we received. This isn't
+ what indicates that we should terminate; that's the separate global
+ variable GotTerminate, used in CHANreadloop. */
+static volatile sig_atomic_t killer_signal = 0;
+
+/* Whether our self-maintained logs (stdout and stderr) are buffered, used
+ to determine whether fflush is needed. Should be static. */
+bool BufferedLogs = true;
+
+/* FILEs for logs and error logs. Everything should just use stdout and
+ stderr. */
+FILE *Log = NULL;
+FILE *Errlog = NULL;
+
+/* Some very old systems have a completely inadequate BUFSIZ buffer size, at
+ least for our logging purposes. */
+#if BUFSIZ < 4096
+# define LOG_BUFSIZ 4096
+#else
+# define LOG_BUFSIZ BUFSIZ
+#endif
+
+/* Internal prototypes. */
+static RETSIGTYPE catch_terminate(int sig);
+static void xmalloc_abort(const char *what, size_t size,
+ const char *file, int line);
+
+/* header table initialization */
+#define ARTHEADERINIT(name, type) {name, type, sizeof(name) - 1}
+const ARTHEADER ARTheaders[] = {
+ /* Name Type */
+ ARTHEADERINIT("Approved", HTstd),
+/* #define HDR__APPROVED 0 */
+ ARTHEADERINIT("Control", HTstd),
+/* #define HDR__CONTROL 1 */
+ ARTHEADERINIT("Date", HTreq),
+/* #define HDR__DATE 2 */
+ ARTHEADERINIT("Distribution", HTstd),
+/* #define HDR__DISTRIBUTION 3 */
+ ARTHEADERINIT("Expires", HTstd),
+/* #define HDR__EXPIRES 4 */
+ ARTHEADERINIT("From", HTreq),
+/* #define HDR__FROM 5 */
+ ARTHEADERINIT("Lines", HTstd),
+/* #define HDR__LINES 6 */
+ ARTHEADERINIT("Message-ID", HTreq),
+/* #define HDR__MESSAGE_ID 7 */
+ ARTHEADERINIT("Newsgroups", HTreq),
+/* #define HDR__NEWSGROUPS 8 */
+ ARTHEADERINIT("Path", HTreq),
+/* #define HDR__PATH 9 */
+ ARTHEADERINIT("Reply-To", HTstd),
+/* #define HDR__REPLY_TO 10 */
+ ARTHEADERINIT("Sender", HTstd),
+/* #define HDR__SENDER 11 */
+ ARTHEADERINIT("Subject", HTreq),
+/* #define HDR__SUBJECT 12 */
+ ARTHEADERINIT("Supersedes", HTstd),
+/* #define HDR__SUPERSEDES 13 */
+ ARTHEADERINIT("Bytes", HTstd),
+/* #define HDR__BYTES 14 */
+ ARTHEADERINIT("Also-Control", HTobs),
+/* #define HDR__ALSOCONTROL 15 */
+ ARTHEADERINIT("References", HTstd),
+/* #define HDR__REFERENCES 16 */
+ ARTHEADERINIT("Xref", HTsav),
+/* #define HDR__XREF 17 */
+ ARTHEADERINIT("Keywords", HTstd),
+/* #define HDR__KEYWORDS 18 */
+ ARTHEADERINIT("X-Trace", HTstd),
+/* #define HDR__XTRACE 19 */
+ ARTHEADERINIT("Date-Received", HTobs),
+/* #define HDR__DATERECEIVED 20 */
+ ARTHEADERINIT("Posted", HTobs),
+/* #define HDR__POSTED 21 */
+ ARTHEADERINIT("Posting-Version", HTobs),
+/* #define HDR__POSTINGVERSION 22 */
+ ARTHEADERINIT("Received", HTobs),
+/* #define HDR__RECEIVED 23 */
+ ARTHEADERINIT("Relay-Version", HTobs),
+/* #define HDR__RELAYVERSION 24 */
+ ARTHEADERINIT("NNTP-Posting-Host", HTstd),
+/* #define HDR__NNTPPOSTINGHOST 25 */
+ ARTHEADERINIT("Followup-To", HTstd),
+/* #define HDR__FOLLOWUPTO 26 */
+ ARTHEADERINIT("Organization", HTstd),
+/* #define HDR__ORGANIZATION 27 */
+ ARTHEADERINIT("Content-Type", HTstd),
+/* #define HDR__CONTENTTYPE 28 */
+ ARTHEADERINIT("Content-Base", HTstd),
+/* #define HDR__CONTENTBASE 29 */
+ ARTHEADERINIT("Content-Disposition", HTstd),
+/* #define HDR__CONTENTDISPOSITION 30 */
+ ARTHEADERINIT("X-Newsreader", HTstd),
+/* #define HDR__XNEWSREADER 31 */
+ ARTHEADERINIT("X-Mailer", HTstd),
+/* #define HDR__XMAILER 32 */
+ ARTHEADERINIT("X-Newsposter", HTstd),
+/* #define HDR__XNEWSPOSTER 33 */
+ ARTHEADERINIT("X-Cancelled-By", HTstd),
+/* #define HDR__XCANCELLEDBY 34 */
+ ARTHEADERINIT("X-Canceled-By", HTstd),
+/* #define HDR__XCANCELEDBY 35 */
+ ARTHEADERINIT("Cancel-Key", HTstd),
+/* #define HDR__CANCELKEY 36 */
+ ARTHEADERINIT("User-Agent", HTstd),
+/* #define HDR__USER_AGENT 37 */
+ ARTHEADERINIT("X-Original-Message-ID", HTstd),
+/* #define HDR__X_ORIGINAL_MESSAGE_ID 38 */
+ ARTHEADERINIT("Cancel-Lock", HTstd),
+/* #define HDR__CANCEL_LOCK 39 */
+ ARTHEADERINIT("Content-Transfer-Encoding", HTstd),
+/* #define HDR__CONTENT_TRANSFER_ENCODING 40 */
+ ARTHEADERINIT("Face", HTstd),
+/* #define HDR__FACE 41 */
+ ARTHEADERINIT("Injection-Info", HTstd),
+/* #define HDR__INJECTION_INFO 42 */
+ ARTHEADERINIT("List-ID", HTstd),
+/* #define HDR__LIST_ID 43 */
+ ARTHEADERINIT("MIME-Version", HTstd),
+/* #define HDR__MIME_VERSION 44 */
+ ARTHEADERINIT("Originator", HTstd),
+/* #define HDR__ORIGINATOR 45 */
+ ARTHEADERINIT("X-Auth", HTstd),
+/* #define HDR__X_AUTH 46 */
+ ARTHEADERINIT("X-Complaints-To", HTstd),
+/* #define HDR__X_COMPLAINTS_TO 47 */
+ ARTHEADERINIT("X-Face", HTstd),
+/* #define HDR__X_FACE 48 */
+ ARTHEADERINIT("X-HTTP-UserAgent", HTstd),
+/* #define HDR__X_HTTP_USERAGENT 49 */
+ ARTHEADERINIT("X-HTTP-Via", HTstd),
+/* #define HDR__X_HTTP_VIA 50 */
+ ARTHEADERINIT("X-Modbot", HTstd),
+/* #define HDR__X_MODBOT 51 */
+ ARTHEADERINIT("X-Modtrace", HTstd),
+/* #define HDR__X_MODTRACE 52 */
+ ARTHEADERINIT("X-No-Archive", HTstd),
+/* #define HDR__X_NO_ARCHIVE 53 */
+ ARTHEADERINIT("X-Original-Trace", HTstd),
+/* #define HDR__X_ORIGINAL_TRACE 54 */
+ ARTHEADERINIT("X-Originating-IP", HTstd),
+/* #define HDR__X_ORIGINATING_IP 55 */
+ ARTHEADERINIT("X-PGP-Key", HTstd),
+/* #define HDR__X_PGP_KEY 56 */
+ ARTHEADERINIT("X-PGP-Sig", HTstd),
+/* #define HDR__X_PGP_SIG 57 */
+ ARTHEADERINIT("X-Poster-Trace", HTstd),
+/* #define HDR__X_POSTER_TRACE 58 */
+ ARTHEADERINIT("X-Postfilter", HTstd),
+/* #define HDR__X_POSTFILTER 59 */
+ ARTHEADERINIT("X-Proxy-User", HTstd),
+/* #define HDR__X_PROXY_USER 60 */
+ ARTHEADERINIT("X-Submissions-To", HTstd),
+/* #define HDR__X_SUBMISSIONS_TO 61 */
+ ARTHEADERINIT("X-Usenet-Provider", HTstd),
+/* #define HDR__X_USENET_PROVIDER 62 */
+ ARTHEADERINIT("In-Reply-To", HTstd),
+/* #define HDR__IN_REPLY_TO 63 */
+ ARTHEADERINIT("Injection-Date", HTstd),
+/* #define HDR__INJECTION_DATE 64 */
+ ARTHEADERINIT("NNTP-Posting-Date", HTstd)
+/* #define HDR__NNTP_POSTING_DATE 65 */
+};
+/* #define MAX_ARTHEADER 66 */
+\f
+
+/*
+** Signal handler to catch SIGTERM and similar signals and queue a clean
+** shutdown.
+*/
+static RETSIGTYPE
+catch_terminate(int sig)
+{
+ GotTerminate = true;
+ killer_signal = sig;
+
+#ifndef HAVE_SIGACTION
+ xsignal(sig, catch_terminate);
+#endif
+}
+
+
+/*
+** Memory allocation failure handler. Instead of the default behavior of
+** just exiting, call abort to generate a core dump.
+*/
+static void
+xmalloc_abort(const char *what, size_t size, const char *file, int line)
+{
+ fprintf(stderr, "SERVER cant %s %lu bytes at %s line %d: %s", what,
+ (unsigned long) size, file, line, strerror(errno));
+ syslog(LOG_CRIT, "SERVER cant %s %lu bytes at %s line %d: %m", what,
+ (unsigned long) size, file, line);
+ abort();
+}
+
+
+/*
+** The name is self-explanatory.
+*/
+void
+CleanupAndExit(int status, const char *why)
+{
+ JustCleanup();
+ if (why)
+ syslog(LOG_WARNING, "SERVER shutdown %s", why);
+ else
+ syslog(LOG_WARNING, "SERVER shutdown received signal %d",
+ killer_signal);
+ exit(status);
+}
+
+
+/*
+** Close down all parts of the system (e.g., before calling exit or exec).
+*/
+void
+JustCleanup(void)
+{
+ SITEflushall(false);
+ CCclose();
+ LCclose();
+ NCclose();
+ RCclose();
+ ICDclose();
+ InndHisClose();
+ ARTclose();
+ if (innconf->enableoverview)
+ OVclose();
+ NGclose();
+ SMshutdown();
+
+#if DO_TCL
+ TCLclose();
+#endif
+
+#if DO_PERL
+ PerlFilter(false);
+ PerlClose();
+#endif
+
+#if DO_PYTHON
+ PYclose();
+#endif
+
+ CHANshutdown();
+ innconf_free(innconf);
+ innconf = NULL;
+
+ sleep(1);
+
+ if (unlink(PID) < 0 && errno != ENOENT)
+ syslog(LOG_ERR, "SERVER cant unlink %s: %m", PID);
+}
+
+
+/*
+** Flush one log file, re-establishing buffering if necessary. stdout is
+** block-buffered, stderr is line-buffered.
+*/
+void
+ReopenLog(FILE *F)
+{
+ char *path, *oldpath;
+ int mask;
+
+ if (Debug)
+ return;
+
+ path = concatpath(innconf->pathlog,
+ (F == stdout) ? _PATH_LOGFILE : _PATH_ERRLOG);
+ oldpath = concat(path, ".old", (char *) 0);
+ if (rename(path, oldpath) < 0)
+ syswarn("SERVER cant rename %s to %s", path, oldpath);
+ free(oldpath);
+ mask = umask(033);
+ if (freopen(path, "a", F) != F)
+ sysdie("SERVER cant freopen %s", path);
+ free(path);
+ umask(mask);
+ if (BufferedLogs)
+ setvbuf(F, NULL, (F == stdout) ? _IOFBF : _IOLBF, LOG_BUFSIZ);
+}
+
+
+/*
+** Print a usage message and exit.
+*/
+static void
+Usage(void)
+{
+ fprintf(stderr, "Usage error.\n");
+ exit(1);
+}
+
+
+int
+main(int ac, char *av[])
+{
+ const char *name, *p;
+ char *path;
+ char *t;
+ bool flag;
+ static char WHEN[] = "PID file";
+ int i, j, fd[MAX_SOCKETS + 1];
+ char buff[SMBUF], *path1, *path2;
+ FILE *F;
+ bool ShouldFork;
+ bool ShouldRenumber;
+ bool ShouldSyntaxCheck;
+ bool filter = true;
+ pid_t pid;
+#if defined(_DEBUG_MALLOC_INC)
+ union malloptarg m;
+#endif /* defined(_DEBUG_MALLOC_INC) */
+
+ /* Set up the pathname, first thing, and teach our error handlers about
+ the name of the program. */
+ name = av[0];
+ if (name == NULL || *name == '\0')
+ name = "innd";
+ else {
+ p = strrchr(name, '/');
+ if (p != NULL)
+ name = p + 1;
+ }
+ message_program_name = name;
+ openlog(name, LOG_CONS | LOG_NDELAY, LOG_INN_SERVER);
+ message_handlers_die(2, message_log_stderr, message_log_syslog_crit);
+ message_handlers_warn(2, message_log_stderr, message_log_syslog_err);
+ message_handlers_notice(1, message_log_syslog_notice);
+
+ /* Make sure innd is not running as root. innd must be either started
+ via inndstart or use a non-privileged port. */
+ if (getuid() == 0 || geteuid() == 0)
+ die("SERVER must be run as user news, not root (use inndstart)");
+
+ /* Handle malloc debugging. */
+#if defined(_DEBUG_MALLOC_INC)
+ m.i = M_HANDLE_ABORT;
+ dbmallopt(MALLOC_WARN, &m);
+ dbmallopt(MALLOC_FATAL, &m);
+ m.i = 3;
+ dbmallopt(MALLOC_FILLAREA, &m);
+ m.i = 0;
+ dbmallopt(MALLOC_CKCHAIN, &m);
+ dbmallopt(MALLOC_CKDATA, &m);
+#endif /* defined(_DEBUG_MALLOC_INC) */
+
+ /* Set defaults. */
+ TimeOut.tv_sec = DEFAULT_TIMEOUT;
+ TimeOut.tv_usec = 0;
+ ShouldFork = true;
+ ShouldRenumber = false;
+ ShouldSyntaxCheck = false;
+ fd[0] = fd[1] = -1;
+
+ /* Set some options from inn.conf that can be overridden with
+ command-line options if they exist, so read inn.conf first. */
+ if (!innconf_read(NULL))
+ exit(1);
+
+ /* Parse JCL. */
+ CCcopyargv(av);
+ while ((i = getopt(ac, av, "ac:Cdfi:l:m:o:Nn:p:P:rst:uH:T:X:")) != EOF)
+ switch (i) {
+ default:
+ Usage();
+ /* NOTREACHED */
+ case 'a':
+ AnyIncoming = true;
+ break;
+ case 'c':
+ innconf->artcutoff = atoi(optarg);
+ break;
+ case 'C':
+ DoCancels = false;
+ break;
+ case 'd':
+ Debug = true;
+ break;
+ case 'f':
+ ShouldFork = false;
+ break;
+ case 'H':
+ RemoteLimit = atoi(optarg);
+ break;
+ case 'i':
+ innconf->maxconnections = atoi(optarg);
+ break;
+ case 'I':
+ if (innconf->bindaddress) free(innconf->bindaddress);
+ innconf->bindaddress = xstrdup(optarg);
+ break;
+ case 'l':
+ innconf->maxartsize = atol(optarg);
+ break;
+ case 'm':
+ if (ModeReason)
+ free(ModeReason);
+ switch (*optarg) {
+ default:
+ Usage();
+ /* NOTREACHED */
+ case 'g': Mode = OMrunning; break;
+ case 'p': Mode = OMpaused; break;
+ case 't': Mode = OMthrottled; break;
+ }
+ if (Mode != OMrunning)
+ ModeReason = concat(OMpaused ? "Paus" : "Throttl",
+ "ed from the command line", (char *) 0);
+ break;
+ case 'N':
+ filter = false;
+ break;
+ case 'n':
+ switch (*optarg) {
+ default:
+ Usage();
+ /* NOTREACHED */
+ case 'n': innconf->readerswhenstopped = false; break;
+ case 'y': innconf->readerswhenstopped = true; break;
+ }
+ break;
+ case 'o':
+ MaxOutgoing = atoi(optarg);
+ break;
+ case 'p':
+ /* Silently ignore multiple -p flags, in case ctlinnd xexec
+ called inndstart. */
+ if (fd[0] != -1)
+ break;
+ t = xstrdup(optarg);
+ p = strtok(t, ",");
+ j = 0;
+ do {
+ fd[j++] = atoi(p);
+ if (j == MAX_SOCKETS)
+ break;
+ } while ((p = strtok(NULL, ",")) != NULL);
+ fd[j] = -1;
+ free(t);
+ break;
+ case 'P':
+ innconf->port = atoi(optarg);
+ break;
+ case 'r':
+ ShouldRenumber = true;
+ break;
+ case 's':
+ ShouldSyntaxCheck = true;
+ break;
+ case 't':
+ TimeOut.tv_sec = atol(optarg);
+ break;
+ case 'T':
+ RemoteTotal = atoi(optarg);
+ break;
+ case 'u':
+ BufferedLogs = false;
+ break;
+ case 'X':
+ RemoteTimer = atoi(optarg);
+ break;
+ case 'Z':
+ StreamingOff = true;
+ break;
+ }
+ ac -= optind;
+ if (ac != 0)
+ Usage();
+ if (ModeReason && !innconf->readerswhenstopped)
+ NNRPReason = xstrdup(ModeReason);
+
+ if (ShouldSyntaxCheck) {
+ if ((p = CCcheckfile((char **)NULL)) == NULL)
+ exit(0);
+ fprintf(stderr, "%s\n", p + 2);
+ exit(1);
+ }
+
+ /* Get the Path entry. */
+ if (innconf->pathhost == NULL) {
+ syslog(L_FATAL, "%s No pathhost set", LogName);
+ exit(1);
+ }
+ Path.used = strlen(innconf->pathhost) + 1;
+ Path.size = Path.used + 1;
+ Path.data = xmalloc(Path.size);
+ snprintf(Path.data, Path.size, "%s!", innconf->pathhost);
+ if (innconf->pathalias == NULL) {
+ Pathalias.used = 0;
+ Pathalias.data = NULL;
+ } else {
+ Pathalias.used = strlen(innconf->pathalias) + 1;
+ Pathalias.size = Pathalias.used + 1;
+ Pathalias.data = xmalloc(Pathalias.size);
+ snprintf(Pathalias.data, Pathalias.size, "%s!", innconf->pathalias);
+ }
+ if (innconf->pathcluster == NULL) {
+ Pathcluster.used = 0;
+ Pathcluster.data = NULL;
+ } else {
+ Pathcluster.used = strlen(innconf->pathcluster) + 1;
+ Pathcluster.size = Pathcluster.used + 1;
+ Pathcluster.data = xmalloc(Pathcluster.size);
+ snprintf(Pathcluster.data, Pathcluster.size, "%s!", innconf->pathcluster);
+ }
+ /* Trace history ? */
+ if (innconf->stathist != NULL) {
+ syslog(L_NOTICE, "logging hist stats to %s", innconf->stathist);
+ HISlogto(innconf->stathist);
+ }
+
+ i = dbzneedfilecount();
+ if (!fdreserve(3 + i)) { /* TEMPORARYOPEN, history stats, INND_HISTORY and i */
+ syslog(L_FATAL, "%s cant reserve file descriptors %m", LogName);
+ exit(1);
+ }
+
+ /* Set up our permissions. */
+ umask(NEWSUMASK);
+
+ /* Become a daemon and initialize our log files. */
+ if (Debug) {
+ xsignal(SIGINT, catch_terminate);
+ if (chdir(innconf->patharticles) < 0)
+ sysdie("SERVER cant chdir to %s", innconf->patharticles);
+ } else {
+ if (ShouldFork)
+ daemonize(innconf->patharticles);
+
+ /* Open the logs. stdout is used to log information about incoming
+ articles and stderr is used to log serious error conditions (as
+ well as to capture stderr from embedded filters). Both are
+ normally fully buffered. */
+ path = concatpath(innconf->pathlog, _PATH_LOGFILE);
+ if (freopen(path, "a", stdout) == NULL)
+ sysdie("SERVER cant freopen stdout to %s", path);
+ setvbuf(stdout, NULL, _IOFBF, LOG_BUFSIZ);
+ free(path);
+ path = concatpath(innconf->pathlog, _PATH_ERRLOG);
+ if (freopen(path, "a", stderr) == NULL)
+ sysdie("SERVER cant freopen stderr to %s", path);
+ setvbuf(stderr, NULL, _IOLBF, BUFSIZ);
+ free(path);
+ }
+ Log = stdout;
+ Errlog = stderr;
+
+ /* Initialize overview if necessary. */
+ if (innconf->enableoverview && !OVopen(OV_WRITE))
+ die("SERVER cant open overview method");
+
+ /* Always attempt to increase the number of open file descriptors. If
+ we're not root, this may just fail quietly. */
+ if (innconf->rlimitnofile > 0)
+ setfdlimit(innconf->rlimitnofile);
+
+ /* Get number of open channels. */
+ i = getfdlimit();
+ if (i < 0) {
+ syslog(L_FATAL, "%s cant get file descriptor limit: %m", LogName);
+ exit(1);
+ }
+
+ /* There is no file descriptor limit on some hosts; for those, cap at
+ MaxOutgoing plus maxconnections plus 20, or 5000, whichever is larger.
+ Otherwise, we use insane amounts of memory for the channel table.
+ FIXME: Get rid of this hard-coded constant. */
+ if (i > 5000) {
+ int max;
+
+ max = innconf->maxconnections + MaxOutgoing + 20;
+ if (max < 5000)
+ max = 5000;
+ i = max;
+ }
+ syslog(L_NOTICE, "%s descriptors %d", LogName, i);
+ if (MaxOutgoing == 0) {
+ /* getfdlimit() - (stdio + dbz + cc + lc + rc + art + fudge) */
+ MaxOutgoing = i - ( 3 + 3 + 2 + 1 + 1 + 1 + 2 );
+ syslog(L_NOTICE, "%s outgoing %d", LogName, MaxOutgoing);
+ }
+
+ /* See if another instance is alive. */
+ if (PID == NULL)
+ PID = concatpath(innconf->pathrun, _PATH_SERVERPID);
+ if ((F = fopen(PID, "r")) != NULL) {
+ if (fgets(buff, sizeof buff, F) != NULL
+ && ((pid = (pid_t) atol(buff)) > 0)
+ && (kill(pid, 0) > 0 || errno != ESRCH)) {
+ syslog(L_FATAL, "%s already_running pid %ld", LogName,
+ (long) pid);
+ exit(1);
+ }
+ fclose(F);
+ }
+
+ if (GetTimeInfo(&Now) < 0)
+ syslog(L_ERROR, "%s cant gettimeinfo %m", LogName);
+
+ /* Set up signal and error handlers. */
+ xmalloc_error_handler = xmalloc_abort;
+ xsignal(SIGHUP, catch_terminate);
+ xsignal(SIGTERM, catch_terminate);
+
+ /* Set up the various parts of the system. Channel feeds start
+ processes so call PROCsetup before ICDsetup. NNTP needs to know if
+ it's a slave, so call RCsetup before NCsetup. */
+ CHANsetup(i);
+ PROCsetup(10);
+ if (Mode == OMrunning)
+ InndHisOpen();
+ CCsetup();
+ LCsetup();
+ RCsetup(fd[0]);
+ for (i = 1; fd[i] != -1; i++)
+ RCsetup(fd[i]);
+ WIPsetup();
+ NCsetup();
+ ARTsetup();
+ ICDsetup(true);
+ if (innconf->timer)
+ TMRinit(TMR_MAX);
+
+ /* Initialize the storage subsystem. */
+ flag = true;
+ if (!SMsetup(SM_RDWR, &flag) || !SMsetup(SM_PREOPEN, &flag))
+ die("SERVER cant set up storage manager");
+ if (!SMinit())
+ die("SERVER cant initalize storage manager: %s", SMerrorstr);
+
+#if defined(_DEBUG_MALLOC_INC)
+ m.i = 1;
+ dbmallopt(MALLOC_CKCHAIN, &m);
+ dbmallopt(MALLOC_CKDATA, &m);
+#endif /* defined(_DEBUG_MALLOC_INC) */
+
+ /* Record our PID. */
+ pid = getpid();
+ if ((F = fopen(PID, "w")) == NULL) {
+ i = errno;
+ syslog(L_ERROR, "%s cant fopen %s %m", LogName, PID);
+ IOError(WHEN, i);
+ }
+ else {
+ if (fprintf(F, "%ld\n", (long)pid) == EOF || ferror(F)) {
+ i = errno;
+ syslog(L_ERROR, "%s cant fprintf %s %m", LogName, PID);
+ IOError(WHEN, i);
+ }
+ if (fclose(F) == EOF) {
+ i = errno;
+ syslog(L_ERROR, "%s cant fclose %s %m", LogName, PID);
+ IOError(WHEN, i);
+ }
+ if (chmod(PID, 0664) < 0) {
+ i = errno;
+ syslog(L_ERROR, "%s cant chmod %s %m", LogName, PID);
+ IOError(WHEN, i);
+ }
+ }
+
+#if DO_TCL
+ TCLsetup();
+ if (!filter)
+ TCLfilter(false);
+#endif /* DO_TCL */
+
+#if DO_PERL
+ /* Load the Perl code */
+ path1 = concatpath(innconf->pathfilter, _PATH_PERL_STARTUP_INND);
+ path2 = concatpath(innconf->pathfilter, _PATH_PERL_FILTER_INND);
+ PERLsetup(path1, path2, "filter_art");
+ free(path1);
+ free(path2);
+ PLxsinit();
+ if (filter)
+ PerlFilter(true);
+#endif /* DO_PERL */
+
+#if DO_PYTHON
+ PYsetup();
+ if (!filter)
+ PYfilter(false);
+#endif /* DO_PYTHON */
+
+ /* And away we go... */
+ if (ShouldRenumber) {
+ syslog(LOG_NOTICE, "SERVER renumbering");
+ if (!ICDrenumberactive())
+ die("SERVER cant renumber");
+ }
+ syslog(LOG_NOTICE, "SERVER starting");
+ CHANreadloop();
+
+ /* CHANreadloop should never return. */
+ CleanupAndExit(1, "CHANreadloop returned");
+ return 1;
+}
--- /dev/null
+/* $Id: innd.h 7858 2008-06-05 18:51:20Z iulius $
+**
+** Many of the data types used here have abbreviations, such as CT
+** for CHANNELTYPE. Here are a list of the conventions and meanings:
+**
+** ART A news article
+** CHAN An I/O channel
+** CS Channel state
+** CT Channel type
+** FNL Funnel, into which other feeds pour
+** FT Feed type -- how a site gets told about new articles
+** ICD In-core data (primarily the active and sys files)
+** LC Local NNTP connection-receiving channel
+** CC Control channel (used by ctlinnd)
+** NC NNTP client channel
+** NG Newsgroup
+** NGH Newgroup hashtable
+** PROC A process (used to feed a site)
+** PS Process state
+** RC Remote NNTP connection-receiving channel
+** RCHAN A channel in "read" state
+** SITE Something that gets told when we get an article
+** WCHAN A channel in "write" state
+** WIP Work-In-Progress, keeps track of articles before committed.
+*/
+
+#ifndef INND_H
+#define INND_H 1
+
+#include "config.h"
+#include "portable/time.h"
+#include "portable/socket.h"
+#include <ctype.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <signal.h>
+#include <syslog.h>
+#include <sys/stat.h>
+
+#include "inn/buffer.h"
+#include "inn/history.h"
+#include "inn/messages.h"
+#include "inn/timer.h"
+#include "libinn.h"
+#include "nntp.h"
+#include "paths.h"
+#include "storage.h"
+
+/* TCL defines EXTERN, so undef it after inclusion since we use it. */
+#if DO_TCL
+# include <tcl.h>
+# undef EXTERN
+#endif
+
+BEGIN_DECLS
+
+typedef short SITEIDX;
+#define NOSITE ((SITEIDX) -1)
+
+/*
+** Various constants.
+*/
+
+/* Used for storing group subscriptions for feeds. */
+#define SUB_DEFAULT false
+#define SUB_NEGATE '!'
+#define SUB_POISON '@'
+
+/* Special characters for newsfeeds entries. */
+#define NF_FIELD_SEP ':'
+#define NF_SUBFIELD_SEP '/'
+
+
+/*
+** Server's operating mode.
+*/
+typedef enum _OPERATINGMODE {
+ OMrunning,
+ OMpaused,
+ OMthrottled
+} OPERATINGMODE;
+
+
+typedef struct _LISTBUFFER {
+ char * Data;
+ int DataLength;
+ char ** List;
+ int ListLength;
+} LISTBUFFER;
+
+/*
+** What program to handoff a connection to.
+*/
+typedef enum _HANDOFF {
+ HOnnrpd,
+ HOnntpd
+} HANDOFF;
+
+
+/*
+** Header types.
+*/
+typedef enum _ARTHEADERTYPE {
+ HTreq, /* Drop article if this is missing */
+ HTobs, /* obsolete header but keep untouched */
+ HTstd, /* Standard optional header */
+ HTsav /* Save header, but may be deleted from article */
+} ARTHEADERTYPE;
+
+
+/*
+** Entry in the header table.
+*/
+typedef struct _ARTHEADER {
+ const char * Name;
+ ARTHEADERTYPE Type;
+ int Size; /* Length of Name. */
+} ARTHEADER;
+
+
+/*
+** Header content
+*/
+typedef struct _HDRCONTENT {
+ char * Value; /* don't copy, shows where it begins */
+ int Length; /* Length of Value(tailing CRLF is not
+ included. -1 if duplicated */
+} HDRCONTENT;
+
+
+/*
+** A way to index into the header table.
+*/
+#define HDR_FOUND(_x) (hc[(_x)].Length > 0)
+#define HDR_PARSE_START(_x) hc[(_x)].Value[hc[_x].Length] = '\0'
+#define HDR(_x) (hc[(_x)].Value)
+/* HDR_LEN does not includes trailing "\r\n" */
+#define HDR_LEN(_x) (hc[(_x)].Length)
+#define HDR_PARSE_END(_x) hc[(_x)].Value[hc[_x].Length] = '\r'
+
+
+#define HDR__APPROVED 0
+#define HDR__CONTROL 1
+#define HDR__DATE 2
+#define HDR__DISTRIBUTION 3
+#define HDR__EXPIRES 4
+#define HDR__FROM 5
+#define HDR__LINES 6
+#define HDR__MESSAGE_ID 7
+#define HDR__NEWSGROUPS 8
+#define HDR__PATH 9
+#define HDR__REPLY_TO 10
+#define HDR__SENDER 11
+#define HDR__SUBJECT 12
+#define HDR__SUPERSEDES 13
+#define HDR__BYTES 14
+#define HDR__ALSOCONTROL 15
+#define HDR__REFERENCES 16
+#define HDR__XREF 17
+#define HDR__KEYWORDS 18
+#define HDR__XTRACE 19
+#define HDR__DATERECEIVED 20
+#define HDR__POSTED 21
+#define HDR__POSTINGVERSION 22
+#define HDR__RECEIVED 23
+#define HDR__RELAYVERSION 24
+#define HDR__NNTPPOSTINGHOST 25
+#define HDR__FOLLOWUPTO 26
+#define HDR__ORGANIZATION 27
+#define HDR__CONTENTTYPE 28
+#define HDR__CONTENTBASE 29
+#define HDR__CONTENTDISPOSITION 30
+#define HDR__XNEWSREADER 31
+#define HDR__XMAILER 32
+#define HDR__XNEWSPOSTER 33
+#define HDR__XCANCELLEDBY 34
+#define HDR__XCANCELEDBY 35
+#define HDR__CANCELKEY 36
+#define HDR__USER_AGENT 37
+#define HDR__X_ORIGINAL_MESSAGE_ID 38
+#define HDR__CANCEL_LOCK 39
+#define HDR__CONTENT_TRANSFER_ENCODING 40
+#define HDR__FACE 41
+#define HDR__INJECTION_INFO 42
+#define HDR__LIST_ID 43
+#define HDR__MIME_VERSION 44
+#define HDR__ORIGINATOR 45
+#define HDR__X_AUTH 46
+#define HDR__X_COMPLAINTS_TO 47
+#define HDR__X_FACE 48
+#define HDR__X_HTTP_USERAGENT 49
+#define HDR__X_HTTP_VIA 50
+#define HDR__X_MODBOT 51
+#define HDR__X_MODTRACE 52
+#define HDR__X_NO_ARCHIVE 53
+#define HDR__X_ORIGINAL_TRACE 54
+#define HDR__X_ORIGINATING_IP 55
+#define HDR__X_PGP_KEY 56
+#define HDR__X_PGP_SIG 57
+#define HDR__X_POSTER_TRACE 58
+#define HDR__X_POSTFILTER 59
+#define HDR__X_PROXY_USER 60
+#define HDR__X_SUBMISSIONS_TO 61
+#define HDR__X_USENET_PROVIDER 62
+#define HDR__IN_REPLY_TO 63
+#define HDR__INJECTION_DATE 64
+#define HDR__NNTP_POSTING_DATE 65
+
+#define MAX_ARTHEADER 66
+
+/*
+** Miscellaneous data we want to keep on an article. All the fields
+** are not always valid.
+*/
+typedef struct _ARTDATA {
+ int Body; /* where body begins in article
+ it indicates offset from bp->Data */
+ char * Poster; /* Sender otherwise From in article */
+ char * Replyto; /* Reply-To otherwise From in article */
+ time_t Posted; /* when article posted */
+ time_t Arrived; /* when article arrived */
+ time_t Expires; /* when article should be expired */
+ int Lines; /* number of body lines */
+ int HeaderLines; /* number of header lines */
+ long BytesValue; /* size of stored article, "\r\n" is
+ counted as 1 byte */
+ char Bytes[16]; /* generated Bytes header */
+ int BytesLength; /* generated Bytes header length */
+ char * BytesHeader; /* where Bytes header begins in
+ received article */
+ char TokenText[(sizeof(TOKEN) * 2) + 3];
+ /* token of stored article */
+ LISTBUFFER Newsgroups; /* newsgroup list */
+ int Groupcount; /* number of newsgroups */
+ int Followcount; /* number of folloup to newsgroups */
+ char * Xref; /* generated Xref header */
+ int XrefLength; /* generated Xref header length */
+ int XrefBufLength; /* buffer length of generated Xref
+ header */
+ LISTBUFFER Distribution; /* distribution list */
+ const char * Feedsite; /* who gives me this article */
+ int FeedsiteLength; /* length of Feedsite */
+ LISTBUFFER Path; /* path name list */
+ int StoredGroupLength; /* 1st newsgroup name in Xref */
+ char * Replic; /* replication data */
+ int ReplicLength; /* length of Replic */
+ HASH * Hash; /* Message-ID hash */
+ struct buffer Headers; /* buffer for headers which will be sent
+ to site */
+ struct buffer Overview; /* buffer for overview data */
+ int CRwithoutLF; /* counter for '\r' without '\n' */
+ int LFwithoutCR; /* counter for '\n' without '\r' */
+ long CurHeader; /* where current header starts.
+ this is used for folded header
+ it indicates offset from bp->Data */
+ bool NullHeader; /* contains NULL in current header */
+ long LastTerminator; /* where last '.' exists. only set if
+ it exists at the begining of line
+ it indicates offset from bp->Data */
+ long LastCR; /* where last CR exists
+ it indicates offset from bp->Data */
+ long LastCRLF; /* where last CRLF exists.
+ indicates where last LF exists
+ it indicates offset from bp->Data */
+ HDRCONTENT HdrContent[MAX_ARTHEADER];
+ /* includes system headers info */
+ bool AddAlias; /* Whether Pathalias should be added
+ to this article */
+ bool Hassamepath; /* Whether this article matches Path */
+ bool Hassamecluster; /* Whether this article matches
+ Pathcluster */
+} ARTDATA;
+
+/*
+** Set of channel types.
+*/
+typedef enum _CHANNELTYPE {
+ CTany,
+ CTfree,
+ CTremconn,
+ CTreject,
+ CTnntp,
+ CTlocalconn,
+ CTcontrol,
+ CTfile,
+ CTexploder,
+ CTprocess
+} CHANNELTYPE;
+
+
+/*
+** The state a channel is in. Interpretation of this depends on the
+** channel's type. Used mostly by CTnntp channels.
+*/
+typedef enum _CHANNELSTATE {
+ CSerror,
+ CSwaiting,
+ CSgetcmd,
+ CSgetauth,
+ CSwritegoodbye,
+ CSwriting,
+ CSpaused,
+ CSgetheader,
+ CSgetbody,
+ CSgotarticle,
+ CSgotlargearticle,
+ CSnoarticle,
+ CSeatarticle,
+ CSeatcommand,
+ CSgetxbatch,
+ CScancel
+} CHANNELSTATE;
+
+#define SAVE_AMT 10 /* used for eating article/command */
+
+/*
+** I/O channel, the heart of the program. A channel has input and output
+** buffers, and functions to call when there is input to be read, or when
+** all the output was been written. Many callback functions take a
+** pointer to a channel, so set up a typedef for that.
+*/
+#define PRECOMMITCACHESIZE 128
+struct _CHANNEL;
+typedef void (*innd_callback_t)(struct _CHANNEL *);
+
+typedef struct _CHANNEL {
+ CHANNELTYPE Type;
+ CHANNELSTATE State;
+ int fd;
+ bool Skip;
+ bool Streaming;
+ bool NoResendId;
+ bool privileged;
+ bool Nolist;
+ unsigned long Duplicate;
+ unsigned long Unwanted_s;
+ unsigned long Unwanted_f;
+ unsigned long Unwanted_d;
+ unsigned long Unwanted_g;
+ unsigned long Unwanted_u;
+ unsigned long Unwanted_o;
+ float Size;
+ float DuplicateSize;
+ unsigned long Check;
+ unsigned long Check_send;
+ unsigned long Check_deferred;
+ unsigned long Check_got;
+ unsigned long Check_cybercan;
+ unsigned long Takethis;
+ unsigned long Takethis_Ok;
+ unsigned long Takethis_Err;
+ unsigned long Ihave;
+ unsigned long Ihave_Duplicate;
+ unsigned long Ihave_Deferred;
+ unsigned long Ihave_SendIt;
+ unsigned long Ihave_Cybercan;
+ int Reported;
+ long Received;
+ long Refused;
+ long Rejected;
+ int BadWrites;
+ int BadReads;
+ int BlockedWrites;
+ int BadCommands;
+ time_t LastActive;
+ time_t NextLog;
+ struct sockaddr_storage Address;
+ innd_callback_t Reader;
+ innd_callback_t WriteDone;
+ time_t Waketime;
+ time_t Started;
+ innd_callback_t Waker;
+ void * Argument;
+ void * Event;
+ struct buffer In;
+ struct buffer Out;
+ bool Tracing;
+ struct buffer Sendid;
+ HASH CurrentMessageIDHash;
+ struct _WIP * PrecommitWIP[PRECOMMITCACHESIZE];
+ int PrecommitiCachenext;
+ int XBatchSize;
+ int LargeArtSize;
+ int LargeCmdSize;
+ int ActiveCnx;
+ int MaxCnx;
+ int HoldTime;
+ time_t ArtBeg;
+ int ArtMax;
+ long Start; /* where current cmd/article starts
+ it indicates offset from bp->Data */
+ long Next; /* next pointer to read
+ it indicates offset from bp->Data */
+ char Error[SMBUF]; /* error buffer */
+ ARTDATA Data; /* used for processing article */
+ struct _CHANNEL *nextcp; /* linked list for each incoming site */
+} CHANNEL;
+
+#define DEFAULTNGBOXSIZE 64
+
+/*
+** A newsgroup has a name in different formats, and a high-water count,
+** also kept in different formats. It also has a list of sites that
+** get this group.
+*/
+typedef struct _NEWSGROUP {
+ long Start; /* Offset into the active file */
+ char * Name;
+ int NameLength;
+ ARTNUM Last;
+ ARTNUM Filenum; /* File name to use */
+ int Lastwidth;
+ int PostCount; /* Have we already put it here? */
+ char * LastString;
+ char * Rest; /* Flags, NOT NULL TERMINATED */
+ SITEIDX nSites;
+ int * Sites;
+ SITEIDX nPoison;
+ int * Poison;
+ struct _NEWSGROUP * Alias;
+} NEWSGROUP;
+
+
+/*
+** How a site is fed.
+*/
+typedef enum _FEEDTYPE {
+ FTerror,
+ FTfile,
+ FTchannel,
+ FTexploder,
+ FTfunnel,
+ FTlogonly,
+ FTprogram
+} FEEDTYPE;
+
+
+/*
+** Diablo-style hashed feeds or hashfeeds.
+*/
+#define HASHFEED_QH 1
+#define HASHFEED_MD5 2
+
+typedef struct _HASHFEEDLIST {
+ int type;
+ unsigned int begin;
+ unsigned int end;
+ unsigned int mod;
+ unsigned int offset;
+ struct _HASHFEEDLIST *next;
+} HASHFEEDLIST;
+
+
+/*
+** A site may reject something in its subscription list if it has
+** too many hops, or a bad distribution.
+*/
+typedef struct _SITE {
+ const char * Name;
+ char * Entry;
+ int NameLength;
+ char ** Exclusions;
+ char ** Distributions;
+ char ** Patterns;
+ bool Poison;
+ bool PoisonEntry;
+ bool Sendit;
+ bool Seenit;
+ bool IgnoreControl;
+ bool DistRequired;
+ bool IgnorePath;
+ bool ControlOnly;
+ bool DontWantNonExist;
+ bool NeedOverviewCreation;
+ bool FeedwithoutOriginator;
+ bool DropFiltered;
+ int Hops;
+ int Groupcount;
+ int Followcount;
+ int Crosscount;
+ FEEDTYPE Type;
+ NEWSGROUP * ng;
+ bool Spooling;
+ char * SpoolName;
+ bool Working;
+ long StartWriting;
+ long StopWriting;
+ long StartSpooling;
+ char * Param;
+ char FileFlags[FEED_MAXFLAGS + 1];
+ long MaxSize;
+ long MinSize;
+ int Nice;
+ CHANNEL * Channel;
+ bool IsMaster;
+ int Master;
+ int Funnel;
+ bool FNLwantsnames;
+ struct buffer FNLnames;
+ int Process;
+ pid_t pid;
+ long Flushpoint;
+ struct buffer Buffer;
+ bool Buffered;
+ char ** Originator;
+ HASHFEEDLIST * HashFeedList;
+ int Next;
+ int Prev;
+} SITE;
+
+
+/*
+** A process is something we start up to send articles.
+*/
+typedef enum _PROCSTATE {
+ PSfree,
+ PSrunning,
+ PSdead
+} PROCSTATE;
+
+
+/*
+** We track our children and collect them synchronously.
+*/
+typedef struct _PROCESS {
+ PROCSTATE State;
+ pid_t Pid;
+ int Status;
+ time_t Started;
+ time_t Collected;
+ int Site;
+} PROCESS;
+
+/*
+** A work in progress entry, an article that we've been offered but haven't
+** received yet.
+*/
+typedef struct _WIP {
+ HASH MessageID; /* Hash of the messageid. Doing it like
+ this saves us from haveing to allocate
+ and deallocate memory a lot, and also
+ means lookups are faster. */
+ time_t Timestamp; /* Time we last looked at this MessageID */
+ CHANNEL *Chan; /* Channel that this message is associated
+ with */
+ struct _WIP *Next; /* Next item in this bucket */
+} WIP;
+
+/*
+** Supported timers. If you add new timers to this list, also add them to
+** the list of tags in chan.c.
+*/
+enum timer {
+ TMR_IDLE = TMR_APPLICATION, /* Server is completely idle. */
+ TMR_ARTCLEAN, /* Analyzing an incoming article. */
+ TMR_ARTWRITE, /* Writing an article. */
+ TMR_ARTCNCL, /* Processing a cancel message. */
+ TMR_SITESEND, /* Sending an article to feeds. */
+ TMR_OVERV, /* Generating overview information. */
+ TMR_PERL, /* Perl filter. */
+ TMR_PYTHON, /* Python filter. */
+ TMR_NNTPREAD, /* Reading NNTP data from the network. */
+ TMR_ARTPARSE, /* Parsing an article. */
+ TMR_ARTLOG, /* Logging article disposition. */
+ TMR_DATAMOVE, /* Moving data. */
+ TMR_MAX
+};
+
+\f
+
+/*
+** In-line macros for efficiency.
+**
+** Set or append data to a channel's output buffer.
+*/
+#define WCHANset(cp, p, l) buffer_set(&(cp)->Out, (p), (l))
+#define WCHANappend(cp, p, l) buffer_append(&(cp)->Out, (p), (l))
+
+/*
+** Mark that an I/O error occurred, and block if we got too many.
+*/
+#define IOError(WHEN, e) \
+ do { \
+ if (--ErrorCount <= 0 || (e) == ENOSPC) \
+ ThrottleIOError(WHEN); \
+ } while (0)
+\f
+
+/*
+** Global data.
+**
+** Do not change "extern" to "EXTERN" in the Global data. The ones
+** marked with "extern" are initialized in innd.c. The ones marked
+** with "EXTERN" are not explicitly initialized in innd.c.
+*/
+#if defined(DEFINE_DATA)
+# define EXTERN /* NULL */
+#else
+# define EXTERN extern
+#endif
+extern const ARTHEADER ARTheaders[MAX_ARTHEADER];
+extern bool BufferedLogs;
+EXTERN bool AnyIncoming;
+extern bool Debug;
+EXTERN bool ICDneedsetup;
+EXTERN bool NeedHeaders;
+EXTERN bool NeedOverview;
+EXTERN bool NeedPath;
+EXTERN bool NeedStoredGroup;
+EXTERN bool NeedReplicdata;
+extern bool NNRPTracing;
+extern bool StreamingOff;
+extern bool Tracing;
+EXTERN struct buffer Path;
+EXTERN struct buffer Pathalias;
+EXTERN struct buffer Pathcluster;
+EXTERN char * ModeReason; /* NNTP reject message */
+EXTERN char * NNRPReason; /* NNRP reject message */
+EXTERN char * Reservation; /* Reserved lock message */
+EXTERN char * RejectReason; /* NNTP reject message */
+EXTERN FILE * Errlog;
+EXTERN FILE * Log;
+extern char LogName[];
+extern int ErrorCount;
+EXTERN int ICDactivedirty;
+EXTERN int MaxOutgoing;
+EXTERN int nGroups;
+EXTERN SITEIDX nSites;
+EXTERN int PROCneedscan;
+EXTERN NEWSGROUP ** GroupPointers;
+EXTERN NEWSGROUP * Groups;
+extern OPERATINGMODE Mode;
+EXTERN sig_atomic_t GotTerminate;
+EXTERN SITE * Sites;
+EXTERN SITE ME;
+EXTERN struct timeval TimeOut;
+EXTERN TIMEINFO Now; /* Reasonably accurate time */
+EXTERN bool ThrottledbyIOError;
+EXTERN char * NCgreeting;
+EXTERN struct history *History;
+
+/*
+** Table size for limiting incoming connects. Do not change the table
+** size unless you look at the code manipulating it in rc.c.
+*/
+#define REMOTETABLESIZE 128
+
+/*
+** Setup the default values. The REMOTETIMER being zero turns off the
+** code to limit incoming connects.
+*/
+#define REMOTELIMIT 2
+#define REMOTETIMER 0
+#define REMOTETOTAL 60
+#define REJECT_TIMEOUT 10
+extern int RemoteLimit; /* Per host limit. */
+extern time_t RemoteTimer; /* How long to remember connects. */
+extern int RemoteTotal; /* Total limit. */
+
+
+/*
+** Function declarations.
+*/
+extern void InndHisOpen(void);
+extern void InndHisClose(void);
+extern bool InndHisWrite(const char *key, time_t arrived,
+ time_t posted, time_t expires,
+ TOKEN *token);
+extern bool InndHisRemember(const char *key);
+extern void InndHisLogStats(void);
+extern bool FormatLong(char *p, unsigned long value, int width);
+extern bool NeedShell(char *p, const char **av, const char **end);
+extern char ** CommaSplit(char *text);
+extern void SetupListBuffer(int size, LISTBUFFER *list);
+extern char * MaxLength(const char *p, const char *q);
+extern pid_t Spawn(int niceval, int fd0, int fd1, int fd2,
+ char * const av[]);
+extern void CleanupAndExit(int x, const char *why);
+extern void FileGlue(char *p, const char *n1, char c, const char *n2);
+extern void JustCleanup(void);
+extern void ThrottleIOError(const char *when);
+extern void ThrottleNoMatchError(void);
+extern void ReopenLog(FILE *F);
+extern void xchown(char *p);
+
+extern bool ARTidok(const char *MessageID);
+extern bool ARTreadschema(void);
+extern const char * ARTreadarticle(char *files);
+extern char * ARTreadheader(char *files);
+extern bool ARTpost(CHANNEL *cp);
+extern void ARTcancel(const ARTDATA *Data,
+ const char *MessageID, bool Trusted);
+extern void ARTclose(void);
+extern void ARTsetup(void);
+extern void ARTprepare(CHANNEL *cp);
+extern void ARTparse(CHANNEL *cp);
+
+extern bool CHANsleeping(CHANNEL *cp);
+extern CHANNEL * CHANcreate(int fd, CHANNELTYPE Type,
+ CHANNELSTATE State,
+ innd_callback_t Reader,
+ innd_callback_t WriteDone);
+extern CHANNEL * CHANiter(int *cp, CHANNELTYPE Type);
+extern CHANNEL * CHANfromdescriptor(int fd);
+extern char * CHANname(const CHANNEL *cp);
+extern int CHANreadtext(CHANNEL *cp);
+extern void CHANclose(CHANNEL *cp, const char *name);
+extern void CHANreadloop(void);
+extern void CHANsetup(int i);
+extern void CHANshutdown(void);
+extern void CHANtracing(CHANNEL *cp, bool Flag);
+extern void CHANsetActiveCnx(CHANNEL *cp);
+
+extern void RCHANadd(CHANNEL *cp);
+extern void RCHANremove(CHANNEL *cp);
+
+extern void SCHANadd(CHANNEL *cp, time_t Waketime, void *Event,
+ innd_callback_t Waker, void *Argument);
+extern void SCHANremove(CHANNEL *cp);
+extern void SCHANwakeup(void *Event);
+
+extern bool WCHANflush(CHANNEL *cp);
+extern void WCHANadd(CHANNEL *cp);
+extern void WCHANremove(CHANNEL *cp);
+extern void WCHANsetfrombuffer(CHANNEL *cp, struct buffer *bp);
+
+extern void CCcopyargv(char *av[]);
+extern const char * CCaddhist(char *av[]);
+extern const char * CCblock(OPERATINGMODE NewMode, char *reason);
+extern const char * CCcancel(char *av[]);
+extern const char * CCcheckfile(char *av[]);
+
+extern bool ICDnewgroup(char *Name, char *Rest);
+extern char * ICDreadactive(char **endp);
+extern bool ICDchangegroup(NEWSGROUP *ngp, char *Rest);
+extern void ICDclose(void);
+extern bool ICDrenumberactive(void);
+extern bool ICDrmgroup(NEWSGROUP *ngp);
+extern void ICDsetup(bool StartSites);
+extern void ICDwrite(void);
+extern void ICDwriteactive(void);
+
+extern void CCclose(void);
+extern void CCsetup(void);
+
+extern void KEYgenerate(HDRCONTENT *, const char *body,
+ const char *orig, size_t length);
+
+extern void LCclose(void);
+extern void LCsetup(void);
+
+extern int NGsplit(char *p, int size, LISTBUFFER *List);
+extern NEWSGROUP * NGfind(const char *Name);
+extern void NGclose(void);
+extern CHANNEL * NCcreate(int fd, bool MustAuthorize, bool IsLocal);
+extern void NGparsefile(void);
+extern bool NGrenumber(NEWSGROUP *ngp);
+extern bool NGlowmark(NEWSGROUP *ngp, long lomark);
+
+extern void NCclearwip(CHANNEL *cp);
+extern void NCclose(void);
+extern void NCsetup(void);
+extern void NCwritereply(CHANNEL *cp, const char *text);
+extern void NCwriteshutdown(CHANNEL *cp, const char *text);
+
+/* perl.c */
+extern char * PLartfilter(const ARTDATA *Data, char *artBody, long artLen, int lines);
+extern char * PLmidfilter(char *messageID);
+extern void PLmode(OPERATINGMODE mode, OPERATINGMODE NewMode,
+ char *reason);
+extern char * PLstats(void);
+extern void PLxsinit(void);
+
+extern int PROCwatch(pid_t pid, int site);
+extern void PROCunwatch(int process);
+/* extern void PROCclose(bool Quickly); */
+extern void PROCscan(void);
+extern void PROCsetup(int i);
+
+extern int RClimit(CHANNEL *cp);
+extern bool RCnolimit(CHANNEL *cp);
+extern bool RCauthorized(CHANNEL *cp, char *pass);
+extern int RCcanpost(CHANNEL *cp, char *group);
+extern char * RChostname(const CHANNEL *cp);
+extern char * RClabelname(CHANNEL *cp);
+extern void RCclose(void);
+extern void RChandoff(int fd, HANDOFF h);
+extern void RCreadlist(void);
+extern void RCsetup(int i);
+
+extern bool SITEfunnelpatch(void);
+extern bool SITEsetup(SITE *sp);
+extern bool SITEwantsgroup(SITE *sp, char *name);
+extern bool SITEpoisongroup(SITE *sp, char *name);
+extern char ** SITEreadfile(const bool ReadOnly);
+extern SITE * SITEfind(const char *p);
+extern SITE * SITEfindnext(const char *p, SITE *sp);
+extern const char * SITEparseone(char *Entry, SITE *sp,
+ char *subbed, char *poison);
+extern void SITEchanclose(CHANNEL *cp);
+extern void SITEdrop(SITE *sp);
+extern void SITEflush(SITE *sp, const bool Restart);
+extern void SITEflushall(const bool Restart);
+extern void SITEforward(SITE *sp, const char *text);
+extern void SITEfree(SITE *sp);
+extern void SITEinfo(struct buffer *bp, SITE *sp, bool Verbose);
+extern void SITEparsefile(bool StartSite);
+extern void SITEprocdied(SITE *sp, int process, PROCESS *pp);
+extern void SITEsend(SITE *sp, ARTDATA *Data);
+extern void SITEwrite(SITE *sp, const char *text);
+
+extern void STATUSinit(void);
+extern void STATUSmainloophook(void);
+
+extern void WIPsetup(void);
+extern WIP * WIPnew(const char *messageid, CHANNEL *cp);
+extern void WIPprecomfree(CHANNEL *cp);
+extern void WIPfree(WIP *wp);
+extern bool WIPinprogress(const char *msgid, CHANNEL *cp,
+ bool Add);
+extern WIP * WIPbyid(const char *mesageid);
+extern WIP * WIPbyhash(const HASH hash);
+
+/*
+** TCL globals and functions
+*/
+#if DO_TCL
+extern Tcl_Interp * TCLInterpreter;
+extern bool TCLFilterActive;
+extern struct buffer * TCLCurrArticle;
+extern ARTDATA * TCLCurrData;
+
+extern void TCLfilter(bool value);
+extern void TCLreadfilter(void);
+extern void TCLsetup(void);
+extern void TCLclose(void);
+#endif /* DO_TCL */
+
+/*
+** Python globals and functions
+*/
+#if DO_PYTHON
+extern bool PythonFilterActive;
+
+void PYfilter(bool value);
+extern const char * PYcontrol(char **av);
+extern int PYreadfilter(void);
+extern char * PYartfilter(const ARTDATA *Data, char *artBody, long artLen, int lines);
+extern char * PYmidfilter(char *messageID, int msglen);
+extern void PYmode(OPERATINGMODE mode, OPERATINGMODE newmode,
+ char *reason);
+extern void PYsetup(void);
+extern void PYclose(void);
+#endif /* DO_PYTHON */
+
+END_DECLS
+
+#endif /* INND_H */
--- /dev/null
+/* $Id: inndstart.c 7749 2008-04-06 14:15:04Z iulius $
+**
+** Open the privileged port, then exec innd.
+**
+** inndstart, in a normal INN installation, is installed setuid root and
+** executable only by users in the news group. Because it is setuid root,
+** it's very important to ensure that it be as simple and secure as
+** possible so that news access can't be leveraged into root access.
+**
+** Fighting against this desire, as much of INN's operation as possible
+** should be configurable at run-time using inn.conf, and the news system
+** should be able to an alternate inn.conf by setting INNCONF to the path
+** to that file before starting any programs. The configuration data
+** therefore can't be trusted.
+**
+** Our security model is therefore:
+**
+** - The only three operations performed while privileged are determining
+** the UID and GID of NEWSUSER and NEWSGRP, setting system limits, and
+** opening the privileged port we're binding to.
+**
+** - We can only be executed by the NEWSUSER and NEWSGRP, both compile-
+** time constants; otherwise, we exit. Similarly, we will only setuid()
+** to the NEWSUSER. This is to prevent someone other than the NEWSUSER
+** but still able to execute inndstart for whatever reason from using it
+** to run innd as the news user with bogus configuration information,
+** thereby possibly compromising the news account.
+**
+** - The only ports < 1024 that we'll bind to are 119 and 433, or a port
+** given at configure time with --with-innd-port. This is to prevent
+** the news user from taking over a service such as telnet or POP and
+** potentially gaining access to user passwords.
+**
+** This program therefore gives the news user the ability to revoke system
+** file descriptor limits and bind to the news port, and nothing else.
+**
+** Note that we do use getpwnam() to determine the UID of NEWSUSER, which
+** potentially opens an exploitable hole on those systems that don't
+** correctly prevent a user running a setuid program from interfering with
+** the running process (replacing system calls, for example, or using
+** things like LD_PRELOAD).
+*/
+
+#include "config.h"
+#include "clibrary.h"
+#include "portable/socket.h"
+#include <errno.h>
+#include <fcntl.h>
+#include <grp.h>
+#include <pwd.h>
+#include <syslog.h>
+
+#ifdef HAVE_INET6
+# include <netdb.h>
+#endif
+
+#include "inn/innconf.h"
+#include "inn/messages.h"
+#include "libinn.h"
+#include "paths.h"
+
+/* Fake up a do-nothing setgroups for Cygwin. */
+#if !HAVE_SETGROUPS
+# define setgroups(n, list) 0
+#endif
+
+/* To run innd under the debugger, uncomment this and fix the path. */
+/* #define DEBUGGER "/usr/ucb/dbx" */
+
+
+int
+main(int argc, char *argv[])
+{
+ struct passwd *pwd;
+ struct group *grp;
+ uid_t news_uid, real_uid;
+ gid_t news_gid;
+ int snum = 0;
+ int port, s[MAX_SOCKETS + 1], i, j;
+#ifdef HAVE_INET6
+ struct in6_addr address6;
+ bool addr6_specified = false;
+#endif
+ struct in_addr address;
+ bool addr_specified = false;
+ char *p;
+ char **innd_argv;
+ char pflag[SMBUF];
+#ifdef PURIFY
+ char *innd_env[11];
+#else
+ char *innd_env[9];
+#endif
+
+ /* Set up the error handlers. Always print to stderr, and for warnings
+ also syslog with a priority of LOG_ERR. For fatal errors, also
+ syslog with a priority of LOG_CRIT. These priority levels are a
+ little high, but they're chosen to match innd. */
+ openlog("inndstart", LOG_CONS, LOG_INN_PROG);
+ message_handlers_warn(2, message_log_stderr, message_log_syslog_err);
+ message_handlers_die(2, message_log_stderr, message_log_syslog_crit);
+ message_program_name = "inndstart";
+
+ /* Convert NEWSUSER and NEWSGRP to a UID and GID. getpwnam() and
+ getgrnam() don't set errno normally, so don't print strerror() on
+ failure; it probably contains garbage.*/
+ pwd = getpwnam(NEWSUSER);
+ if (!pwd)
+ die("can't getpwnam(%s)", NEWSUSER);
+ news_uid = pwd->pw_uid;
+ grp = getgrnam(NEWSGRP);
+ if (!grp)
+ die("can't getgrnam(%s)", NEWSGRP);
+ news_gid = grp->gr_gid;
+
+ /* Exit if run by any other user or group. */
+ real_uid = getuid();
+ if (real_uid != news_uid)
+ die("must be run by user %s (%lu), not %lu", NEWSUSER,
+ (unsigned long)news_uid, (unsigned long)real_uid);
+
+ /* Drop all supplemental groups and drop privileges to read inn.conf.
+ setgroups() can only be invoked by root, so if inndstart isn't setuid
+ root this is where we fail. */
+ if (setgroups(1, &news_gid) < 0)
+ syswarn("can't setgroups (is inndstart setuid root?)");
+ if (seteuid(news_uid) < 0)
+ sysdie("can't seteuid to %lu", (unsigned long)news_uid);
+ if (!innconf_read(NULL))
+ exit(1);
+
+ /* Check for a bind address specified in inn.conf. "any" or "all" will
+ cause inndstart to bind to INADDR_ANY. */
+ address.s_addr = htonl(INADDR_ANY);
+ p = innconf->bindaddress;
+ if (p && strcmp(p, "all") != 0 && strcmp(p, "any") != 0) {
+ if (!inet_aton(p, &address))
+ die("invalid bindaddress in inn.conf (%s)", p);
+ addr_specified = true;
+ }
+#ifdef HAVE_INET6
+ address6 = in6addr_any;
+ p = innconf->bindaddress6;
+ if (p && strcmp(p, "all") != 0 && strcmp(p, "any") != 0) {
+ if (inet_pton(AF_INET6, p, &address6) < 1)
+ die("invalid bindaddress6 in inn.conf (%s)", p);
+ addr6_specified = true;
+ }
+#endif
+
+ /* Parse our command-line options. The only options we take are -P,
+ which specifies what port number to bind to, and -I, which specifies
+ what IP address to bind to. Both override inn.conf. Support both
+ "-P <port>" and "-P<port>". All other options are passed through to
+ innd. */
+ port = innconf->port;
+ for (i = 1; i < argc; i++) {
+ if (strncmp("-P", argv[i], 2) == 0) {
+ if (strlen(argv[i]) > 2) {
+ port = atoi(&argv[i][2]);
+ } else {
+ i++;
+ if (argv[i] == NULL)
+ die("missing port after -P");
+ port = atoi(argv[i]);
+ }
+ if (port == 0)
+ die("invalid port %s (must be a number)", argv[i]);
+#ifdef HAVE_INET6
+ } else if (strncmp("-6", argv[i], 2) == 0) {
+ if (strlen(argv[i]) > 2) {
+ p = &argv[i][2];
+ } else {
+ i++;
+ if (argv[i] == NULL)
+ die("missing address after -6");
+ p = argv[i];
+ }
+ if (inet_pton(AF_INET6, p, &address6) < 1)
+ die("invalid address %s", p);
+ addr6_specified = true;
+#endif
+ } else if (strncmp("-I", argv[i], 2) == 0) {
+ if (strlen(argv[i]) > 2) {
+ p = &argv[i][2];
+ } else {
+ i++;
+ if (argv[i] == NULL)
+ die("missing address after -I");
+ p = argv[i];
+ }
+ if (!inet_aton(p, &address))
+ die("invalid address %s", p);
+ addr_specified = true;
+ }
+ }
+
+ /* Make sure that the requested port is legitimate. */
+ if (port < 1024 && port != 119
+#ifdef INND_PORT
+ && port != INND_PORT
+#endif
+ && port != 433)
+ die("can't bind to restricted port %d", port);
+
+ /* Now, regain privileges so that we can change system limits and bind
+ to our desired port. */
+ if (seteuid(0) < 0)
+ sysdie("can't seteuid to 0");
+
+ /* innconf->rlimitnofile <= 0 says to leave it alone. */
+ if (innconf->rlimitnofile > 0 && setfdlimit(innconf->rlimitnofile) < 0)
+ syswarn("can't set file descriptor limit to %ld",
+ innconf->rlimitnofile);
+
+#if defined(HAVE_INET6) && defined(IPV6_V6ONLY)
+ /* If we have the IPV6_V6ONLY socket option, and it works,
+ always open separate IPv4 and IPv6 sockets. */
+ if (addr_specified == 0 && addr6_specified == 0) {
+ j = socket(PF_INET6, SOCK_STREAM, 0);
+ if (j >= 0) {
+ i = 1;
+ if (setsockopt (j, IPPROTO_IPV6, IPV6_V6ONLY, (char *)&i,
+ sizeof i) == 0) {
+ addr_specified = 1;
+ addr6_specified = 1;
+ }
+ close (j);
+ }
+ }
+#endif
+
+ /* Create a socket and name it. */
+#ifdef HAVE_INET6
+ if( ! (addr_specified || addr6_specified) ) {
+ struct addrinfo hints, *addr, *ressave;
+ char service[16];
+ int error;
+
+ memset(&hints, 0, sizeof hints);
+ hints.ai_family = PF_UNSPEC;
+ hints.ai_flags = AI_PASSIVE;
+ hints.ai_socktype = SOCK_STREAM;
+ snprintf(service, sizeof(service), "%d", port);
+ error = getaddrinfo(NULL, service, &hints, &addr);
+ if (error < 0)
+ die("getaddrinfo: %s", gai_strerror(error));
+
+ for (ressave = addr; addr; addr = addr->ai_next) {
+ if ((i = socket(addr->ai_family, addr->ai_socktype,
+ addr->ai_protocol)) < 0)
+ continue; /* ignore */
+#ifdef SO_REUSEADDR
+ j = 1;
+ if (setsockopt(i, SOL_SOCKET, SO_REUSEADDR, (char *)&j,
+ sizeof j) < 0)
+ syswarn("can't set SO_REUSEADDR");
+#endif
+ if (bind(i, addr->ai_addr, addr->ai_addrlen) < 0) {
+ j = errno;
+ close(i);
+ errno = j;
+ continue; /* ignore */
+ }
+ s[snum++] = i;
+ if (snum == MAX_SOCKETS)
+ break;
+ }
+ freeaddrinfo(ressave);
+
+ if (snum == 0)
+ sysdie("can't bind socket");
+ } else {
+ if ( addr6_specified ) {
+ struct sockaddr_in6 server6;
+
+ s[snum] = socket(PF_INET6, SOCK_STREAM, 0);
+ if (s[snum] < 0)
+ sysdie("can't open inet6 socket");
+#ifdef SO_REUSEADDR
+ i = 1;
+ if (setsockopt(s[snum], SOL_SOCKET, SO_REUSEADDR, (char *)&i,
+ sizeof i) < 0)
+ syswarn("can't set SO_REUSEADDR");
+#endif
+#ifdef IPV6_V6ONLY
+ i = 1;
+ if (setsockopt(s[snum], IPPROTO_IPV6, IPV6_V6ONLY, (char *)&i,
+ sizeof i) < 0)
+ syswarn("can't set IPV6_V6ONLY");
+#endif
+ memset(&server6, 0, sizeof server6);
+ server6.sin6_port = htons(port);
+ server6.sin6_family = AF_INET6;
+ server6.sin6_addr = address6;
+#ifdef HAVE_SOCKADDR_LEN
+ server6.sin6_len = sizeof server6;
+#endif
+ if (bind(s[snum], (struct sockaddr *)&server6, sizeof server6) < 0)
+ sysdie("can't bind inet6 socket");
+ snum++;
+ }
+ if ( addr_specified )
+#endif /* HAVE_INET6 */
+ {
+ struct sockaddr_in server;
+
+ s[snum] = socket(PF_INET, SOCK_STREAM, 0);
+ if (s[snum] < 0)
+ sysdie("can't open inet socket");
+#ifdef SO_REUSEADDR
+ i = 1;
+ if (setsockopt(s[snum], SOL_SOCKET, SO_REUSEADDR, (char *) &i,
+ sizeof i) < 0)
+ syswarn("can't set SO_REUSEADDR");
+#endif
+ memset(&server, 0, sizeof server);
+ server.sin_port = htons(port);
+ server.sin_family = AF_INET;
+#ifdef HAVE_SOCKADDR_LEN
+ server.sin_len = sizeof server;
+#endif
+ server.sin_addr = address;
+ if (bind(s[snum], (struct sockaddr *)&server, sizeof server) < 0)
+ sysdie("can't bind inet socket");
+ snum++;
+ }
+#ifdef HAVE_INET6
+ }
+#endif
+ s[snum] = -1;
+
+ /* Now, permanently drop privileges. */
+ if (setgid(news_gid) < 0 || getgid() != news_gid)
+ sysdie("can't setgid to %lu", (unsigned long)news_gid);
+ if (setuid(news_uid) < 0 || getuid() != news_uid)
+ sysdie("can't setuid to %lu", (unsigned long)news_uid);
+
+ /* Build the argument vector for innd. Pass -p<port> to innd to tell it
+ what port we just created and bound to for it. */
+ innd_argv = xmalloc((1 + argc + 1) * sizeof(char *));
+ i = 0;
+ strlcpy(pflag, "-p ", sizeof(pflag));
+ for (j = 0; s[j] > 0; j++) {
+ char temp[16];
+
+ snprintf(temp, sizeof(temp), "%d,", s[j]);
+ strlcat(pflag, temp, sizeof(pflag));
+ }
+ /* chop off the trailing , */
+ j = strlen(pflag) - 1;
+ pflag[j] = '\0';
+#ifdef DEBUGGER
+ innd_argv[i++] = DEBUGGER;
+ innd_argv[i++] = concatpath(innconf->pathbin, "innd");
+ innd_argv[i] = 0;
+ printf("When starting innd, use -d %s\n", s, pflag);
+#else /* DEBUGGER */
+ innd_argv[i++] = concatpath(innconf->pathbin, "innd");
+ innd_argv[i++] = pflag;
+
+ /* Don't pass along -p, -P, or -I. Check the length of the argument
+ string, and if it == 2 (meaning there's nothing after the -p or -P or
+ -I), skip the next argument too, to support leaving a space between
+ the argument and the value. */
+ for (j = 1; j < argc; j++) {
+ if (argv[j][0] == '-' && strchr("pP6I", argv[j][1])) {
+ if (strlen(argv[j]) == 2)
+ j++;
+ continue;
+ } else {
+ innd_argv[i++] = argv[j];
+ }
+ }
+ innd_argv[i] = 0;
+#endif /* !DEBUGGER */
+
+ /* Set up the environment. Note that we're trusting BIND_INADDR and TZ;
+ everything else is either from inn.conf or from configure. These
+ should be sanity-checked before being propagated, but that requires
+ knowledge of the range of possible values. Just limiting their
+ length doesn't necessarily do anything to prevent exploits and may
+ stop things from working that should. We have to pass BIND_INADDR so
+ that it's set for programs, such as innfeed, that innd may spawn. */
+ innd_env[0] = concat("PATH=", innconf->pathbin, ":", innconf->pathetc,
+ ":/bin:/usr/bin:/usr/ucb", (char *) 0);
+ innd_env[1] = concat( "TMPDIR=", innconf->pathtmp, (char *) 0);
+ innd_env[2] = concat( "SHELL=", _PATH_SH, (char *) 0);
+ innd_env[3] = concat("LOGNAME=", NEWSMASTER, (char *) 0);
+ innd_env[4] = concat( "USER=", NEWSMASTER, (char *) 0);
+ innd_env[5] = concat( "HOME=", innconf->pathnews, (char *) 0);
+ i = 6;
+ p = getenv("BIND_INADDR");
+ if (p != NULL)
+ innd_env[i++] = concat("BIND_INADDR=", p, (char *) 0);
+ p = getenv("TZ");
+ if (p != NULL)
+ innd_env[i++] = concat("TZ=", p, (char *) 0);
+#ifdef PURIFY
+ /* you have to compile with `purify cc -DPURIFY' to get this */
+ p = getenv("DISPLAY");
+ if (p != NULL)
+ innd_env[i++] = concat("DISPLAY=", p, (char *) 0);
+ p = getenv("PURIFYOPTIONS");
+ if (p != NULL)
+ innd_env[i++] = concat("PURIFYOPTIONS=", p, (char *) 0);
+#endif
+ innd_env[i] = 0;
+
+ /* Go exec innd. */
+ execve(innd_argv[0], innd_argv, innd_env);
+ sysdie("can't exec %s", innd_argv[0]);
+
+ /* Not reached. */
+ return 1;
+}
--- /dev/null
+/* $Id: keywords.c 6269 2003-03-28 01:52:36Z rra $
+**
+** Optional keyword generation code.
+**
+** Additional code for sake of manufacturing Keywords: headers out of air in
+** order to provide better (scorable) XOVER data, containing bits of article
+** body content which have a reasonable expectation of utility.
+**
+** Basic idea: Simple word-counting. We find words in the article body,
+** separated by whitespace. Remove punctuation. Sort words, count unique
+** words, sort those counts. Write the resulting Keywords: header containing
+** the poster's original Keywords: (if any) followed by a magic cookie
+** separator and then the sorted list of words.
+*/
+
+#include "config.h"
+#include "clibrary.h"
+
+#include "libinn.h"
+
+#include "inn/innconf.h"
+#include "innd.h"
+
+/* If keyword support wasn't requested, stub out the main function provided by
+ this file. */
+#if !DO_KEYWORDS
+void
+KEYgenerate(HDRCONTENT *header UNUSED, const char *body UNUSED,
+ const char *orig UNUSED, size_t length UNUSED)
+{
+}
+
+#else
+
+/* For regex-based common word elimination. */
+#include <regex.h>
+
+#define MIN_WORD_LENGTH 3 /* 1- and 2-char words don't count. */
+#define MAX_WORD_LENGTH 28 /* fits "antidisestablishmentarianism". */
+
+/*
+** A trivial structure for keeping track of words via both
+** index to the overall word list and their counts.
+*/
+struct word_entry {
+ int index;
+ int length;
+ int count;
+};
+
+/*
+** Wrapper for qsort(3) comparison of word_entry (frequency).
+*/
+
+static int
+wvec_freq_cmp(const void *p1, const void *p2)
+{
+ return ((const struct word_entry *)p2)->count - /* decreasing sort */
+ ((const struct word_entry *)p1)->count;
+}
+
+/*
+** Wrapper for qsort(3) comparison of word_entry (word length).
+*/
+
+static int
+wvec_length_cmp(const void *p1, const void *p2)
+{
+ return ((const struct word_entry *)p2)->length - /* decreasing sort */
+ ((const struct word_entry *)p1)->length;
+}
+
+/*
+** Wrapper for qsort(3), for pointer-to-pointer strings.
+*/
+
+static int
+ptr_strcmp(const void *p1, const void *p2)
+{
+ int cdiff;
+
+ cdiff = (**(const char **)p1) - (**(const char **)p2);
+ if (cdiff)
+ return cdiff;
+ return strcmp((*(const char **)p1)+1, (*(const char **)p2)+1);
+}
+
+/*
+** Build new Keywords.
+*/
+
+void
+KEYgenerate(
+ HDRCONTENT *hc, /* header data */
+ const char *body, /* article body */
+ const char *v, /* old kw value */
+ size_t l) /* old kw length */
+{
+
+ int word_count, word_length, bodylen, word_index, distinct_words;
+ int last;
+ char *text, *orig_text, *text_end, *this_word, *chase, *punc;
+ static struct word_entry *word_vec;
+ static char **word;
+ static const char *whitespace = " \t\r\n";
+
+ /* ---------------------------------------------------------------- */
+ /* Prototype setup: Regex match preparation. */
+ static int regex_lib_init = 0;
+ static regex_t preg;
+ static const char *elim_regexp = "^\\([-+/0-9][-+/0-9]*\\|.*1st\\|.*2nd\\|.*3rd\\|.*[04-9]th\\|about\\|after\\|ago\\|all\\|already\\|also\\|among\\|and\\|any\\|anybody\\|anyhow\\|anyone\\|anywhere\\|are\\|bad\\|because\\|been\\|before\\|being\\|between\\|but\\|can\\|could\\|did\\|does\\|doing\\|done\\|dont\\|during\\|eight\\|eighth\\|eleven\\|else\\|elsewhere\\|every\\|everywhere\\|few\\|five\\|fifth\\|first\\|for\\|four\\|fourth\\|from\\|get\\|going\\|gone\\|good\\|got\\|had\\|has\\|have\\|having\\|he\\|her\\|here\\|hers\\|herself\\|him\\|himself\\|his\\|how\\|ill\\|into\\|its\\|ive\\|just\\|kn[eo]w\\|least\\|less\\|let\\|like\\|look\\|many\\|may\\|more\\|m[ou]st\\|myself\\|next\\|nine\\|ninth\\|not\\|now\\|off\\|one\\|only\\|onto\\|our\\|out\\|over\\|really\\|said\\|saw\\|says\\|second\\|see\\|set\\|seven\\|seventh\\|several\\|shall\\|she\\|should\\|since\\|six\\|sixth\\|some\\|somehow\\|someone\\|something\\|somewhere\\|such\\|take\\|ten\\|tenth\\|than\\|that\\|the\\|their\\!|them\\|then\\|there\\|therell\\|theres\\|these\\|they\\|thing\\|things\\|third\\|this\\|those\\|three\\|thus\\|together\\|told\\|too\\|twelve\\|two\\|under\\|upon\\|very\\|via\\|want\\|wants\\|was\\|wasnt\\|way\\|were\\|weve\\|what\\|whatever\\|when\\|where\\|wherell\\|wheres\\|whether\\|which\\|while\\|who\\|why\\|will\\|will\\|with\\|would\\|write\\|writes\\|wrote\\|yes\\|yet\\|you\\|your\\|youre\\|yourself\\)$";
+
+ if (word_vec == 0) {
+ word_vec = xmalloc(innconf->keymaxwords * sizeof(struct word_entry));
+ if (word_vec == 0)
+ return;
+ word = xmalloc(innconf->keymaxwords * sizeof(char *));
+ if (word == NULL) {
+ free(word_vec);
+ return;
+ }
+ }
+
+ if (regex_lib_init == 0) {
+ regex_lib_init++;
+
+ if (regcomp(&preg, elim_regexp, REG_ICASE|REG_NOSUB) != 0) {
+ syslog(L_FATAL, "%s regcomp failure", LogName);
+ abort();
+ }
+ }
+ /* ---------------------------------------------------------------- */
+
+ /* first re-init kw from original value. */
+ if (l > innconf->keylimit - (MAX_WORD_LENGTH+5)) /* mostly arbitrary cutoff: */
+ l = innconf->keylimit - (MAX_WORD_LENGTH+5); /* room for minimal word vec */
+ hc->Value = xmalloc(innconf->keylimit+1);
+ if ((v != NULL) && (*v != '\0')) {
+ memcpy(hc->Value, v, l);
+ hc->Value[l] = '\0';
+ } else
+ *hc->Value = '\0';
+ l = hc->Length = strlen(hc->Value);
+
+ /*
+ * now figure acceptable extents, and copy body to working string.
+ * (Memory-intensive for hefty articles: limit to non-ABSURD articles.)
+ */
+ bodylen = strlen(body);
+ if ((bodylen < 100) || (bodylen > innconf->keyartlimit)) /* too small/big to bother */
+ return;
+
+ orig_text = text = xstrdup(body); /* orig_text is for free() later on */
+
+ text_end = text + bodylen;
+
+ /* abusive punctuation stripping: turn it all into SPCs. */
+ for (punc = text; *punc; punc++)
+ if (!CTYPE(isalpha, *punc))
+ *punc = ' ';
+
+ /* move to first word. */
+ text += strspn(text, whitespace);
+ word_count = 0;
+
+ /* hunt down words */
+ while ((text < text_end) && /* while there might be words... */
+ (*text != '\0') &&
+ (word_count < innconf->keymaxwords)) {
+
+ /* find a word. */
+ word_length = strcspn(text, whitespace);
+ if (word_length == 0)
+ break; /* no words left */
+
+ /* bookkeep to save word location, then move through text. */
+ word[word_count++] = this_word = text;
+ text += word_length;
+ *(text++) = '\0';
+ text += strspn(text, whitespace); /* move to next word. */
+
+ /* 1- and 2-char words don't count, nor do excessively long ones. */
+ if ((word_length < MIN_WORD_LENGTH) ||
+ (word_length > MAX_WORD_LENGTH)) {
+ word_count--;
+ continue;
+ }
+
+ /* squash to lowercase. */
+ for (chase = this_word; *chase; chase++)
+ if (CTYPE(isupper, *chase))
+ *chase = tolower(*chase);
+ }
+
+ /* If there were no words, we're done. */
+ if (word_count < 1)
+ goto out;
+
+ /* Sort the words. */
+ qsort(word, word_count, sizeof(word[0]), ptr_strcmp);
+
+ /* Count unique words. */
+ distinct_words = 0; /* the 1st word is "pre-figured". */
+ word_vec[0].index = 0;
+ word_vec[0].length = strlen(word[0]);
+ word_vec[0].count = 1;
+
+ for (word_index = 1; /* we compare (N-1)th and Nth words. */
+ word_index < word_count;
+ word_index++) {
+ if (strcmp(word[word_index-1], word[word_index]) == 0)
+ word_vec[distinct_words].count++;
+ else {
+ distinct_words++;
+ word_vec[distinct_words].index = word_index;
+ word_vec[distinct_words].length = strlen(word[word_index]);
+ word_vec[distinct_words].count = 1;
+ }
+ }
+
+ /* Sort the counts. */
+ distinct_words++; /* we were off-by-1 until this. */
+ qsort(word_vec, distinct_words, sizeof(struct word_entry), wvec_freq_cmp);
+
+ /* Sub-sort same-frequency words on word length. */
+ for (last = 0, word_index = 1; /* again, (N-1)th and Nth entries. */
+ word_index < distinct_words;
+ word_index++) {
+ if (word_vec[last].count != word_vec[word_index].count) {
+ if ((word_index - last) != 1) /* 2+ entries to sub-sort. */
+ qsort(&word_vec[last], word_index - last,
+ sizeof(struct word_entry), wvec_length_cmp);
+ last = word_index;
+ }
+ }
+ /* do it one last time for the only-one-appearance words. */
+ if ((word_index - last) != 1)
+ qsort(&word_vec[last], word_index - last,
+ sizeof(struct word_entry), wvec_length_cmp);
+
+ /* Scribble onto end of Keywords:. */
+ strcpy(hc->Value + l, ",\377"); /* magic separator, 'ÿ' */
+ for (chase = hc->Value + l + 2, word_index = 0;
+ word_index < distinct_words;
+ word_index++) {
+ /* ---------------------------------------------------------------- */
+ /* "noise" words don't count */
+ if (regexec(&preg, word[word_vec[word_index].index], 0, NULL, 0) == 0)
+ continue;
+ /* ---------------------------------------------------------------- */
+
+ /* add to list. */
+ *chase++ = ',';
+ strcpy(chase, word[word_vec[word_index].index]);
+ chase += word_vec[word_index].length;
+
+ if (chase - hc->Value > (innconf->keylimit - (MAX_WORD_LENGTH + 4)))
+ break;
+ }
+ /* note #words we didn't get to add. */
+ /* This code can potentially lead to a buffer overflow if the number of
+ ignored words is greater than 100, under some circumstances. It's
+ temporarily disabled until fixed. */
+ hc->Length = strlen(hc->Value);
+
+out:
+ /* We must dispose of the original strdup'd text area. */
+ free(orig_text);
+}
+
+#endif /* DO_KEYWORDS */
--- /dev/null
+/* $Id: lc.c 6155 2003-01-19 19:58:25Z rra $
+**
+** Routines for the local connect channel. Create a Unix-domain stream
+** socket that processes on the local server connect to. Once the
+** connection is set up, we speak NNTP. The connect channel is used only
+** by rnews to feed in articles from the UUCP sites.
+*/
+
+#include "config.h"
+#include "clibrary.h"
+
+#include "inn/innconf.h"
+#include "innd.h"
+
+
+#if HAVE_UNIX_DOMAIN_SOCKETS
+# include <sys/un.h>
+
+static char *LCpath = NULL;
+static CHANNEL *LCchan;
+
+
+/*
+** Read function. Accept the connection and create an NNTP channel.
+*/
+static void
+LCreader(CHANNEL *cp)
+{
+ int fd;
+ CHANNEL *new;
+
+ if (cp != LCchan) {
+ syslog(L_ERROR, "%s internal LCreader wrong channel 0x%p not 0x%p",
+ LogName, (void *)cp, (void *)LCchan);
+ return;
+ }
+
+ if ((fd = accept(cp->fd, NULL, NULL)) < 0) {
+ syslog(L_ERROR, "%s cant accept CCreader %m", LogName);
+ return;
+ }
+ if ((new = NCcreate(fd, false, true)) != NULL) {
+ memset( &new->Address, 0, sizeof( new->Address ) );
+ syslog(L_NOTICE, "%s connected %d", "localhost", new->fd);
+ NCwritereply(new, (char *)NCgreeting);
+ }
+}
+
+
+/*
+** Write-done function. Shouldn't happen.
+*/
+static void
+LCwritedone(CHANNEL *unused)
+{
+ unused = unused; /* ARGSUSED */
+ syslog(L_ERROR, "%s internal LCwritedone", LogName);
+}
+
+#endif /* HAVE_UNIX_DOMAIN_SOCKETS */
+
+
+/*
+** Create the channel.
+*/
+void
+LCsetup(void)
+{
+#if defined(HAVE_UNIX_DOMAIN_SOCKETS)
+ int i;
+ struct sockaddr_un server;
+
+ if (LCpath == NULL)
+ LCpath = concatpath(innconf->pathrun, _PATH_NNTPCONNECT);
+ /* Remove old detritus. */
+ if (unlink(LCpath) < 0 && errno != ENOENT) {
+ syslog(L_FATAL, "%s cant unlink %s %m", LogName, LCpath);
+ exit(1);
+ }
+
+ /* Create a socket and name it. */
+ if ((i = socket(AF_UNIX, SOCK_STREAM, 0)) < 0) {
+ syslog(L_FATAL, "%s cant socket %s %m", LogName, LCpath);
+ exit(1);
+ }
+ memset(&server, 0, sizeof server);
+ server.sun_family = AF_UNIX;
+ strlcpy(server.sun_path, LCpath, sizeof(server.sun_path));
+ if (bind(i, (struct sockaddr *) &server, SUN_LEN(&server)) < 0) {
+ syslog(L_FATAL, "%s cant bind %s %m", LogName, LCpath);
+ exit(1);
+ }
+
+ /* Set it up to wait for connections. */
+ if (listen(i, MAXLISTEN) < 0) {
+ syslog(L_FATAL, "%s cant listen %s %m", LogName, LCpath);
+ exit(1);
+ }
+ LCchan = CHANcreate(i, CTlocalconn, CSwaiting, LCreader, LCwritedone);
+ syslog(L_NOTICE, "%s lcsetup %s", LogName, CHANname(LCchan));
+ RCHANadd(LCchan);
+#endif /* defined(HAVE_UNIX_DOMAIN_SOCKETS) */
+}
+
+
+/*
+** Cleanly shut down the channel.
+*/
+void
+LCclose(void)
+{
+#if defined(HAVE_UNIX_DOMAIN_SOCKETS)
+ CHANclose(LCchan, CHANname(LCchan));
+ LCchan = NULL;
+ if (unlink(LCpath) < 0)
+ syslog(L_ERROR, "%s cant unlink %s %m", LogName, LCpath);
+ free(LCpath);
+#endif /* defined(HAVE_UNIX_DOMAIN_SOCKETS) */
+}
--- /dev/null
+/* $Id: nc.c 7418 2005-10-09 04:37:05Z eagle $
+**
+** Routines for the NNTP channel. Other channels get the descriptors which
+** we turn into NNTP channels, and over which we speak NNTP.
+*/
+
+#include "config.h"
+#include "clibrary.h"
+
+#include "inn/innconf.h"
+#include "innd.h"
+
+#define BAD_COMMAND_COUNT 10
+
+
+/*
+** An entry in the dispatch table. The name, and implementing function,
+** of every command we support.
+*/
+typedef struct _NCDISPATCH {
+ const char * Name;
+ innd_callback_t Function;
+ int Size;
+} NCDISPATCH;
+
+/* The functions that implement the various commands. */
+static void NCauthinfo(CHANNEL *cp);
+static void NCcancel(CHANNEL *cp);
+static void NCcheck(CHANNEL *cp);
+static void NChead(CHANNEL *cp);
+static void NChelp(CHANNEL *cp);
+static void NCihave(CHANNEL *cp);
+static void NClist(CHANNEL *cp);
+static void NCmode(CHANNEL *cp);
+static void NCquit(CHANNEL *cp);
+static void NCstat(CHANNEL *cp);
+static void NCtakethis(CHANNEL *cp);
+static void NCxbatch(CHANNEL *cp);
+
+/* Handlers for unimplemented commands. We need two handlers so that we can
+ return the right status code; reader commands that are required by the
+ standard must return a 502 error rather than a 500 error. */
+static void NC_reader(CHANNEL *cp);
+static void NC_unimp(CHANNEL *cp);
+
+/* Supporting functions. */
+static void NCwritedone(CHANNEL *cp);
+
+/* Set up the dispatch table for all of the commands. */
+#define COMMAND(name, func) { name, func, sizeof(name) - 1 }
+static NCDISPATCH NCcommands[] = {
+ COMMAND("authinfo", NCauthinfo),
+ COMMAND("check", NCcheck),
+ COMMAND("head", NChead),
+ COMMAND("help", NChelp),
+ COMMAND("ihave", NCihave),
+ COMMAND("list", NClist),
+ COMMAND("mode", NCmode),
+ COMMAND("quit", NCquit),
+ COMMAND("stat", NCstat),
+ COMMAND("takethis", NCtakethis),
+ COMMAND("xbatch", NCxbatch),
+
+ /* Unimplemented reader commands which may become available after a MODE
+ READER command. */
+ COMMAND("article", NC_reader),
+ COMMAND("body", NC_reader),
+ COMMAND("group", NC_reader),
+ COMMAND("last", NC_reader),
+ COMMAND("newgroups", NC_reader),
+ COMMAND("newnews", NC_reader),
+ COMMAND("next", NC_reader),
+ COMMAND("post", NC_reader),
+
+ /* Other unimplemented standard commands. */
+ COMMAND("date", NC_unimp),
+ COMMAND("slave", NC_unimp)
+};
+#undef COMMAND
+
+/* Number of open connections. */
+static int NCcount;
+
+static char *NCquietlist[] = { INND_QUIET_BADLIST };
+static const char NCterm[] = "\r\n";
+static const char NCdot[] = "." ;
+static const char NCbadcommand[] = NNTP_BAD_COMMAND;
+static const char NCbadsubcommand[] = NNTP_BAD_SUBCMD;
+
+/*
+** Clear the WIP entry for the given channel
+*/
+void
+NCclearwip(CHANNEL *cp)
+{
+ WIPfree(WIPbyhash(cp->CurrentMessageIDHash));
+ HashClear(&cp->CurrentMessageIDHash);
+ cp->ArtBeg = 0;
+}
+
+/*
+** Write an NNTP reply message.
+**
+** Tries to do the actual write immediately if it will not block and if there
+** is not already other buffered output. Then, if the write is successful,
+** calls NCwritedone (which does whatever is necessary to accommodate state
+** changes). Else, NCwritedone will be called from the main select loop
+** later.
+**
+** If the reply that we are writing now is associated with a state change,
+** then cp->State must be set to its new value *before* NCwritereply is
+** called.
+*/
+void
+NCwritereply(CHANNEL *cp, const char *text)
+{
+ struct buffer *bp;
+ int i;
+
+ /* XXX could do RCHANremove(cp) here, as the old NCwritetext() used to
+ * do, but that would be wrong if the channel is sreaming (because it
+ * would zap the channell's input buffer). There's no harm in
+ * never calling RCHANremove here. */
+
+ bp = &cp->Out;
+ i = bp->left;
+ WCHANappend(cp, text, strlen(text)); /* text in buffer */
+ WCHANappend(cp, NCterm, strlen(NCterm)); /* add CR NL to text */
+
+ if (i == 0) { /* if only data then try to write directly */
+ i = write(cp->fd, &bp->data[bp->used], bp->left);
+ if (Tracing || cp->Tracing)
+ syslog(L_TRACE, "%s NCwritereply %d=write(%d, \"%.15s\", %lu)",
+ CHANname(cp), i, cp->fd, &bp->data[bp->used],
+ (unsigned long) bp->left);
+ if (i > 0)
+ bp->used += i;
+ if (bp->used == bp->left) {
+ /* all the data was written */
+ bp->used = bp->left = 0;
+ NCwritedone(cp);
+ } else {
+ bp->left -= i;
+ i = 0;
+ }
+ } else i = 0;
+ if (i <= 0) { /* write failed, queue it for later */
+ WCHANadd(cp);
+ }
+ if (Tracing || cp->Tracing)
+ syslog(L_TRACE, "%s > %s", CHANname(cp), text);
+}
+
+/*
+** Tell the NNTP channel to go away.
+*/
+void
+NCwriteshutdown(CHANNEL *cp, const char *text)
+{
+ cp->State = CSwritegoodbye;
+ RCHANremove(cp); /* we're not going to read anything more */
+ WCHANappend(cp, NNTP_GOODBYE, strlen(NNTP_GOODBYE));
+ WCHANappend(cp, " ", 1);
+ WCHANappend(cp, text, (int)strlen(text));
+ WCHANappend(cp, NCterm, strlen(NCterm));
+ WCHANadd(cp);
+}
+
+
+/*
+** If a Message-ID is bad, write a reject message and return true.
+*/
+static bool
+NCbadid(CHANNEL *cp, char *p)
+{
+ if (ARTidok(p))
+ return false;
+
+ NCwritereply(cp, NNTP_HAVEIT_BADID);
+ syslog(L_NOTICE, "%s bad_messageid %s", CHANname(cp), MaxLength(p, p));
+ return true;
+}
+
+
+/*
+** We have an entire article collected; try to post it. If we're
+** not running, drop the article or just pause and reschedule.
+*/
+static void
+NCpostit(CHANNEL *cp)
+{
+ bool postok;
+ const char *response;
+ char buff[SMBUF];
+
+ /* Note that some use break, some use return here. */
+ if ((postok = ARTpost(cp)) != 0) {
+ cp->Received++;
+ if (cp->Sendid.size > 3) { /* We be streaming */
+ cp->Takethis_Ok++;
+ snprintf(buff, sizeof(buff), "%d", NNTP_OK_RECID_VAL);
+ cp->Sendid.data[0] = buff[0];
+ cp->Sendid.data[1] = buff[1];
+ cp->Sendid.data[2] = buff[2];
+ response = cp->Sendid.data;
+ } else
+ response = NNTP_TOOKIT;
+ } else {
+ cp->Rejected++;
+ if (cp->Sendid.size)
+ response = cp->Sendid.data;
+ else
+ response = cp->Error;
+ }
+ cp->Reported++;
+ if (cp->Reported >= innconf->nntpactsync) {
+ snprintf(buff, sizeof(buff), "accepted size %.0f duplicate size %.0f",
+ cp->Size, cp->DuplicateSize);
+ syslog(L_NOTICE,
+ "%s checkpoint seconds %ld accepted %ld refused %ld rejected %ld duplicate %ld %s",
+ CHANname(cp), (long)(Now.time - cp->Started),
+ cp->Received, cp->Refused, cp->Rejected,
+ cp->Duplicate, buff);
+ cp->Reported = 0;
+ }
+ if (Mode == OMthrottled) {
+ NCwriteshutdown(cp, ModeReason);
+ return;
+ }
+ cp->State = CSgetcmd;
+ NCwritereply(cp, response);
+}
+
+
+/*
+** Write-done function. Close down or set state for what we expect to
+** read next.
+*/
+static void
+NCwritedone(CHANNEL *cp)
+{
+ switch (cp->State) {
+ default:
+ syslog(L_ERROR, "%s internal NCwritedone state %d",
+ CHANname(cp), cp->State);
+ break;
+
+ case CSwritegoodbye:
+ if (NCcount > 0)
+ NCcount--;
+ CHANclose(cp, CHANname(cp));
+ break;
+
+ case CSgetcmd:
+ case CSgetauth:
+ case CSgetheader:
+ case CSgetbody:
+ case CSgetxbatch:
+ case CSgotlargearticle:
+ case CScancel:
+ RCHANadd(cp);
+ break;
+ }
+}
+
+\f
+
+/*
+** The "head" command.
+*/
+static void
+NChead(CHANNEL *cp)
+{
+ char *p;
+ TOKEN token;
+ ARTHANDLE *art;
+
+ /* Snip off the Message-ID. */
+ for (p = cp->In.data + cp->Start + strlen("head"); ISWHITE(*p); p++)
+ continue;
+ cp->Start = cp->Next;
+ if (NCbadid(cp, p))
+ return;
+
+ /* Get the article token and retrieve it. */
+ if (!HISlookup(History, p, NULL, NULL, NULL, &token)) {
+ NCwritereply(cp, NNTP_DONTHAVEIT);
+ return;
+ }
+ if ((art = SMretrieve(token, RETR_HEAD)) == NULL) {
+ NCwritereply(cp, NNTP_DONTHAVEIT);
+ return;
+ }
+
+ /* Write it. */
+ WCHANappend(cp, NNTP_HEAD_FOLLOWS, strlen(NNTP_HEAD_FOLLOWS));
+ WCHANappend(cp, " 0 ", 3);
+ WCHANappend(cp, p, strlen(p));
+ WCHANappend(cp, NCterm, strlen(NCterm));
+ WCHANappend(cp, art->data, art->len);
+
+ /* Write the terminator. */
+ NCwritereply(cp, NCdot);
+ SMfreearticle(art);
+}
+
+
+/*
+** The "stat" command.
+*/
+static void
+NCstat(CHANNEL *cp)
+{
+ char *p;
+ TOKEN token;
+ ARTHANDLE *art;
+ char *buff;
+ size_t length;
+
+ /* Snip off the Message-ID. */
+ for (p = cp->In.data + cp->Start + strlen("stat"); ISWHITE(*p); p++)
+ continue;
+ cp->Start = cp->Next;
+ if (NCbadid(cp, p))
+ return;
+
+ /* Get the article filenames; open the first file (to make sure
+ * the article is still here). */
+ if (!HISlookup(History, p, NULL, NULL, NULL, &token)) {
+ NCwritereply(cp, NNTP_DONTHAVEIT);
+ return;
+ }
+ if ((art = SMretrieve(token, RETR_STAT)) == NULL) {
+ NCwritereply(cp, NNTP_DONTHAVEIT);
+ return;
+ }
+ SMfreearticle(art);
+
+ /* Write the message. */
+ length = snprintf(NULL, 0, "%d 0 %s", NNTP_NOTHING_FOLLOWS_VAL, p) + 1;
+ buff = xmalloc(length);
+ snprintf(buff, length, "%d 0 %s", NNTP_NOTHING_FOLLOWS_VAL, p);
+ NCwritereply(cp, buff);
+ free(buff);
+}
+
+
+/*
+** The "authinfo" command. Actually, we come in here whenever the
+** channel is in CSgetauth state and we just got a command.
+*/
+static void
+NCauthinfo(CHANNEL *cp)
+{
+ static char AUTHINFO[] = "authinfo ";
+ static char PASS[] = "pass ";
+ static char USER[] = "user ";
+ char *p;
+
+ p = cp->In.data + cp->Start;
+ cp->Start = cp->Next;
+
+ /* Allow the poor sucker to quit. */
+ if (strcasecmp(p, "quit") == 0) {
+ NCquit(cp);
+ return;
+ }
+
+ /* Otherwise, make sure we're only getting "authinfo" commands. */
+ if (strncasecmp(p, AUTHINFO, strlen(AUTHINFO)) != 0) {
+ NCwritereply(cp, NNTP_AUTH_NEEDED);
+ return;
+ }
+ for (p += strlen(AUTHINFO); ISWHITE(*p); p++)
+ continue;
+
+ /* Ignore "authinfo user" commands, since we only care about the
+ * password. */
+ if (strncasecmp(p, USER, strlen(USER)) == 0) {
+ NCwritereply(cp, NNTP_AUTH_NEXT);
+ return;
+ }
+
+ /* Now make sure we're getting only "authinfo pass" commands. */
+ if (strncasecmp(p, PASS, strlen(PASS)) != 0) {
+ NCwritereply(cp, NNTP_AUTH_NEEDED);
+ return;
+ }
+ for (p += strlen(PASS); ISWHITE(*p); p++)
+ continue;
+
+ /* Got the password -- is it okay? */
+ if (!RCauthorized(cp, p)) {
+ cp->State = CSwritegoodbye;
+ NCwritereply(cp, NNTP_AUTH_BAD);
+ } else {
+ cp->State = CSgetcmd;
+ NCwritereply(cp, NNTP_AUTH_OK);
+ }
+}
+
+/*
+** The "help" command.
+*/
+static void
+NChelp(CHANNEL *cp)
+{
+ static char LINE1[] = "For more information, contact \"";
+ static char LINE2[] = "\" at this machine.";
+ NCDISPATCH *dp;
+
+ WCHANappend(cp, NNTP_HELP_FOLLOWS,strlen(NNTP_HELP_FOLLOWS));
+ WCHANappend(cp, NCterm,strlen(NCterm));
+ for (dp = NCcommands; dp < ARRAY_END(NCcommands); dp++)
+ if (dp->Function != NC_unimp) {
+ if ((!StreamingOff && cp->Streaming) ||
+ (dp->Function != NCcheck && dp->Function != NCtakethis)) {
+ WCHANappend(cp, "\t", 1);
+ WCHANappend(cp, dp->Name, dp->Size);
+ WCHANappend(cp, NCterm, strlen(NCterm));
+ }
+ }
+ WCHANappend(cp, LINE1, strlen(LINE1));
+ WCHANappend(cp, NEWSMASTER, strlen(NEWSMASTER));
+ WCHANappend(cp, LINE2, strlen(LINE2));
+ WCHANappend(cp, NCterm, strlen(NCterm));
+ NCwritereply(cp, NCdot) ;
+ cp->Start = cp->Next;
+}
+
+/*
+** The "ihave" command. Check the Message-ID, and see if we want the
+** article or not. Set the state appropriately.
+*/
+static void
+NCihave(CHANNEL *cp)
+{
+ char *p;
+#if defined(DO_PERL) || defined(DO_PYTHON)
+ char *filterrc;
+ int msglen;
+#endif /*defined(DO_PERL) || defined(DO_PYTHON) */
+
+ cp->Ihave++;
+ /* Snip off the Message-ID. */
+ for (p = cp->In.data + cp->Start + strlen("ihave"); ISWHITE(*p); p++)
+ continue;
+ cp->Start = cp->Next;
+ if (NCbadid(cp, p))
+ return;
+
+ if ((innconf->refusecybercancels) && (strncmp(p, "<cancel.", 8) == 0)) {
+ cp->Refused++;
+ cp->Ihave_Cybercan++;
+ NCwritereply(cp, NNTP_HAVEIT);
+ return;
+ }
+
+#if defined(DO_PERL)
+ /* Invoke a perl message filter on the message ID. */
+ filterrc = PLmidfilter(p);
+ if (filterrc) {
+ cp->Refused++;
+ msglen = strlen(p) + 5; /* 3 digits + space + id + null */
+ if (cp->Sendid.size < msglen) {
+ if (cp->Sendid.size > 0) free(cp->Sendid.data);
+ if (msglen > MAXHEADERSIZE)
+ cp->Sendid.size = msglen;
+ else
+ cp->Sendid.size = MAXHEADERSIZE;
+ cp->Sendid.data = xmalloc(cp->Sendid.size);
+ }
+ snprintf(cp->Sendid.data, cp->Sendid.size, "%d %.200s",
+ NNTP_HAVEIT_VAL, filterrc);
+ NCwritereply(cp, cp->Sendid.data);
+ free(cp->Sendid.data);
+ cp->Sendid.size = 0;
+ return;
+ }
+#endif
+
+#if defined(DO_PYTHON)
+ /* invoke a Python message filter on the message id */
+ msglen = strlen(p);
+ TMRstart(TMR_PYTHON);
+ filterrc = PYmidfilter(p, msglen);
+ TMRstop(TMR_PYTHON);
+ if (filterrc) {
+ cp->Refused++;
+ msglen += 5; /* 3 digits + space + id + null */
+ if (cp->Sendid.size < msglen) {
+ if (cp->Sendid.size > 0)
+ free(cp->Sendid.data);
+ if (msglen > MAXHEADERSIZE)
+ cp->Sendid.size = msglen;
+ else
+ cp->Sendid.size = MAXHEADERSIZE;
+ cp->Sendid.data = xmalloc(cp->Sendid.size);
+ }
+ snprintf(cp->Sendid.data, cp->Sendid.size, "%d %.200s",
+ NNTP_HAVEIT_VAL, filterrc);
+ NCwritereply(cp, cp->Sendid.data);
+ free(cp->Sendid.data);
+ cp->Sendid.size = 0;
+ return;
+ }
+#endif
+
+ if (HIScheck(History, p)) {
+ cp->Refused++;
+ cp->Ihave_Duplicate++;
+ NCwritereply(cp, NNTP_HAVEIT);
+ }
+ else if (WIPinprogress(p, cp, false)) {
+ cp->Ihave_Deferred++;
+ if (cp->NoResendId) {
+ cp->Refused++;
+ NCwritereply(cp, NNTP_HAVEIT);
+ } else {
+ NCwritereply(cp, NNTP_RESENDIT_LATER);
+ }
+ }
+ else {
+ if (cp->Sendid.size > 0) {
+ free(cp->Sendid.data);
+ cp->Sendid.size = 0;
+ }
+ cp->Ihave_SendIt++;
+ NCwritereply(cp, NNTP_SENDIT);
+ cp->ArtBeg = Now.time;
+ cp->State = CSgetheader;
+ ARTprepare(cp);
+ }
+}
+
+/*
+** The "xbatch" command. Set the state appropriately.
+*/
+
+static void
+NCxbatch(CHANNEL *cp)
+{
+ char *p;
+
+ /* Snip off the batch size */
+ for (p = cp->In.data + cp->Start + strlen("xbatch"); ISWHITE(*p); p++)
+ continue;
+ cp->Start = cp->Next;
+
+ if (cp->XBatchSize) {
+ syslog(L_FATAL, "NCxbatch(): oops, cp->XBatchSize already set to %d",
+ cp->XBatchSize);
+ }
+
+ cp->XBatchSize = atoi(p);
+ if (Tracing || cp->Tracing)
+ syslog(L_TRACE, "%s will read batch of size %d",
+ CHANname(cp), cp->XBatchSize);
+
+ if (cp->XBatchSize <= 0 || ((innconf->maxartsize != 0) && (innconf->maxartsize < cp->XBatchSize))) {
+ syslog(L_NOTICE, "%s got bad xbatch size %d",
+ CHANname(cp), cp->XBatchSize);
+ NCwritereply(cp, NNTP_XBATCH_BADSIZE);
+ return;
+ }
+
+ /* we prefer not to touch the buffer, NCreader() does enough magic
+ * with it
+ */
+ cp->State = CSgetxbatch;
+ NCwritereply(cp, NNTP_CONT_XBATCH);
+}
+
+/*
+** The "list" command. Send the active file.
+*/
+static void
+NClist(CHANNEL *cp)
+{
+ char *p, *q, *trash, *end, *path;
+
+ for (p = cp->In.data + cp->Start + strlen("list"); ISWHITE(*p); p++)
+ continue;
+ cp->Start = cp->Next;
+ if (cp->Nolist) {
+ NCwritereply(cp, NCbadcommand);
+ return;
+ }
+ if (strcasecmp(p, "newsgroups") == 0) {
+ path = concatpath(innconf->pathdb, _PATH_NEWSGROUPS);
+ trash = p = ReadInFile(path, NULL);
+ free(path);
+ if (p == NULL) {
+ NCwritereply(cp, NCdot);
+ return;
+ }
+ end = p + strlen(p);
+ }
+ else if (strcasecmp(p, "active.times") == 0) {
+ path = concatpath(innconf->pathdb, _PATH_ACTIVETIMES);
+ trash = p = ReadInFile(path, NULL);
+ free(path);
+ if (p == NULL) {
+ NCwritereply(cp, NCdot);
+ return;
+ }
+ end = p + strlen(p);
+ }
+ else if (*p == '\0' || (strcasecmp(p, "active") == 0)) {
+ p = ICDreadactive(&end);
+ trash = NULL;
+ }
+ else {
+ NCwritereply(cp, NCbadsubcommand);
+ return;
+ }
+
+ /* Loop over all lines, sending the text and \r\n. */
+ WCHANappend(cp, NNTP_LIST_FOLLOWS,strlen(NNTP_LIST_FOLLOWS));
+ WCHANappend(cp, NCterm, strlen(NCterm)) ;
+ for (; p < end && (q = strchr(p, '\n')) != NULL; p = q + 1) {
+ WCHANappend(cp, p, q - p);
+ WCHANappend(cp, NCterm, strlen(NCterm));
+ }
+ NCwritereply(cp, NCdot);
+ if (trash)
+ free(trash);
+}
+
+
+/*
+** The "mode" command. Hand off the channel.
+*/
+static void
+NCmode(CHANNEL *cp)
+{
+ char *p;
+ HANDOFF h;
+
+ /* Skip the first word, get the argument. */
+ for (p = cp->In.data + cp->Start + strlen("mode"); ISWHITE(*p); p++)
+ continue;
+ cp->Start = cp->Next;
+
+ if (strcasecmp(p, "reader") == 0 && !innconf->noreader)
+ h = HOnnrpd;
+ else if (strcasecmp(p, "stream") == 0 &&
+ (!StreamingOff && cp->Streaming)) {
+ char buff[16];
+
+ snprintf(buff, sizeof(buff), "%d StreamOK.", NNTP_OK_STREAM_VAL);
+ NCwritereply(cp, buff);
+ syslog(L_NOTICE, "%s NCmode \"mode stream\" received",
+ CHANname(cp));
+ return;
+ } else if (strcasecmp(p, "cancel") == 0 && cp->privileged) {
+ char buff[16];
+
+ cp->State = CScancel;
+ snprintf(buff, sizeof(buff), "%d CancelOK.", NNTP_OK_CANCEL_VAL);
+ NCwritereply(cp, buff);
+ syslog(L_NOTICE, "%s NCmode \"mode cancel\" received",
+ CHANname(cp));
+ return;
+ } else {
+ NCwritereply(cp, NCbadsubcommand);
+ return;
+ }
+ RChandoff(cp->fd, h);
+ if (NCcount > 0)
+ NCcount--;
+ CHANclose(cp, CHANname(cp));
+}
+
+
+/*
+** The "quit" command. Acknowledge, and set the state to closing down.
+*/
+static void
+NCquit(CHANNEL *cp)
+{
+ cp->State = CSwritegoodbye;
+ NCwritereply(cp, NNTP_GOODBYE_ACK);
+}
+
+
+/*
+** The catch-all for reader commands, which should return a different status
+** than just "unrecognized command" since a change of state may make them
+** available.
+*/
+static void
+NC_reader(CHANNEL *cp)
+{
+ cp->Start = cp->Next;
+ NCwritereply(cp, NNTP_ACCESS);
+}
+
+
+/*
+** The catch-all for inimplemented commands.
+*/
+static void
+NC_unimp(CHANNEL *cp)
+{
+ char *p, *q;
+ char buff[SMBUF];
+
+ /* Nip off the first word. */
+ for (p = q = cp->In.data + cp->Start; *p && !ISWHITE(*p); p++)
+ continue;
+ cp->Start = cp->Next;
+ *p = '\0';
+ snprintf(buff, sizeof(buff), "%d \"%s\" not implemented; try \"help\".",
+ NNTP_BAD_COMMAND_VAL, MaxLength(q, q));
+ NCwritereply(cp, buff);
+}
+
+\f
+
+/*
+** Check whatever data is available on the channel. If we got the
+** full amount (i.e., the command or the whole article) process it.
+*/
+static void
+NCproc(CHANNEL *cp)
+{
+ char *p, *q;
+ NCDISPATCH *dp;
+ struct buffer *bp;
+ char buff[SMBUF];
+ int i, j;
+ bool readmore, movedata;
+ ARTDATA *data = &cp->Data;
+ HDRCONTENT *hc = data->HdrContent;
+
+ readmore = movedata = false;
+ if (Tracing || cp->Tracing)
+ syslog(L_TRACE, "%s NCproc Used=%lu", CHANname(cp),
+ (unsigned long) cp->In.used);
+
+ bp = &cp->In;
+ if (bp->used == 0)
+ return;
+
+ for ( ; ; ) {
+ if (Tracing || cp->Tracing) {
+ syslog(L_TRACE, "%s cp->Start=%lu cp->Next=%lu bp->Used=%lu",
+ CHANname(cp), (unsigned long) cp->Start, (unsigned long) cp->Next,
+ (unsigned long) bp->used);
+ if (bp->used > 15)
+ syslog(L_TRACE, "%s NCproc state=%d next \"%.15s\"", CHANname(cp),
+ cp->State, &bp->data[cp->Next]);
+ }
+ switch (cp->State) {
+ default:
+ syslog(L_ERROR, "%s internal NCproc state %d", CHANname(cp), cp->State);
+ movedata = false;
+ readmore = true;
+ break;
+
+ case CSwritegoodbye:
+ movedata = false;
+ readmore = true;
+ break;
+
+ case CSgetcmd:
+ case CSgetauth:
+ case CScancel:
+ /* Did we get the whole command, terminated with "\r\n"? */
+ for (i = cp->Next; (i < bp->used) && (bp->data[i] != '\n'); i++) ;
+ if (i == bp->used) {
+ /* Check for too long command. */
+ if ((j = bp->used - cp->Start) > NNTP_STRLEN) {
+ /* Make some room, saving only the last few bytes. */
+ for (p = bp->data, i = 0; i < SAVE_AMT; i++)
+ p[i] = p[bp->used - SAVE_AMT + i];
+ cp->LargeCmdSize += j - SAVE_AMT;
+ bp->used = cp->Next = SAVE_AMT;
+ bp->left = bp->size - SAVE_AMT;
+ cp->Start = 0;
+ cp->State = CSeatcommand;
+ /* above means moving data already */
+ movedata = false;
+ } else {
+ cp->Next = bp->used;
+ /* move data to the begining anyway */
+ movedata = true;
+ }
+ readmore = true;
+ break;
+ }
+ /* i points where '\n" and go forward */
+ cp->Next = ++i;
+ /* never move data so long as "\r\n" is found, since subsequent
+ data may also include command line */
+ movedata = false;
+ readmore = false;
+ if (i - cp->Start < 3) {
+ break;
+ }
+ p = &bp->data[i];
+ if (p[-2] != '\r') { /* probably in an article */
+ char *tmpstr;
+
+ tmpstr = xmalloc(i - cp->Start + 1);
+ memcpy(tmpstr, bp->data + cp->Start, i - cp->Start);
+ tmpstr[i - cp->Start] = '\0';
+
+ syslog(L_NOTICE, "%s bad_command %s", CHANname(cp),
+ MaxLength(tmpstr, tmpstr));
+ free(tmpstr);
+
+ if (++(cp->BadCommands) >= BAD_COMMAND_COUNT) {
+ cp->State = CSwritegoodbye;
+ NCwritereply(cp, NCbadcommand);
+ break;
+ }
+ NCwritereply(cp, NCbadcommand);
+ /* still some data left, go for it */
+ cp->Start = cp->Next;
+ break;
+ }
+
+ q = &bp->data[cp->Start];
+ /* Ignore blank lines. */
+ if (*q == '\0' || i - cp->Start == 2) {
+ cp->Start = cp->Next;
+ break;
+ }
+ p[-2] = '\0';
+ if (Tracing || cp->Tracing)
+ syslog(L_TRACE, "%s < %s", CHANname(cp), q);
+
+ /* We got something -- stop sleeping (in case we were). */
+ SCHANremove(cp);
+ if (cp->Argument != NULL) {
+ free(cp->Argument);
+ cp->Argument = NULL;
+ }
+
+ if (cp->State == CSgetauth) {
+ if (strncasecmp(q, "mode", 4) == 0)
+ NCmode(cp);
+ else
+ NCauthinfo(cp);
+ break;
+ } else if (cp->State == CScancel) {
+ NCcancel(cp);
+ break;
+ }
+
+ /* Loop through the command table. */
+ for (p = q, dp = NCcommands; dp < ARRAY_END(NCcommands); dp++) {
+ if (strncasecmp(p, dp->Name, dp->Size) == 0) {
+ /* ignore the streaming commands if necessary. */
+ if (!StreamingOff || cp->Streaming ||
+ (dp->Function != NCcheck && dp->Function != NCtakethis)) {
+ (*dp->Function)(cp);
+ cp->BadCommands = 0;
+ break;
+ }
+ }
+ }
+ if (dp == ARRAY_END(NCcommands)) {
+ if (++(cp->BadCommands) >= BAD_COMMAND_COUNT)
+ cp->State = CSwritegoodbye;
+ NCwritereply(cp, NCbadcommand);
+ cp->Start = cp->Next;
+
+ /* Channel could have been freed by above NCwritereply if
+ we're writing-goodbye */
+ if (cp->Type == CTfree)
+ return;
+ for (i = 0; (p = NCquietlist[i]) != NULL; i++)
+ if (strcasecmp(p, q) == 0)
+ break;
+ if (p == NULL)
+ syslog(L_NOTICE, "%s bad_command %s", CHANname(cp),
+ MaxLength(q, q));
+ }
+ break;
+
+ case CSgetheader:
+ case CSgetbody:
+ case CSeatarticle:
+ TMRstart(TMR_ARTPARSE);
+ ARTparse(cp);
+ TMRstop(TMR_ARTPARSE);
+ if (cp->State == CSgetbody || cp->State == CSgetheader ||
+ cp->State == CSeatarticle) {
+ if (cp->Next - cp->Start > innconf->datamovethreshold ||
+ (innconf->maxartsize > 0 && cp->Size > innconf->maxartsize)) {
+ /* avoid buffer extention for ever */
+ movedata = true;
+ } else {
+ movedata = false;
+ }
+ readmore = true;
+ break;
+ }
+
+ if (cp->State == CSgotlargearticle) {
+ syslog(L_NOTICE, "%s internal rejecting huge article (%d > %ld)",
+ CHANname(cp), cp->Next - cp->Start, innconf->maxartsize);
+ if (cp->Sendid.size)
+ NCwritereply(cp, cp->Sendid.data);
+ else {
+ snprintf(buff, sizeof(buff),
+ "%d Article exceeds local limit of %ld bytes",
+ NNTP_REJECTIT_VAL, innconf->maxartsize);
+ NCwritereply(cp, buff);
+ }
+ cp->State = CSgetcmd;
+ cp->Rejected++;
+ cp->Start = cp->Next;
+
+ /* Write a local cancel entry so nobody else gives it to us. */
+ if (HDR_FOUND(HDR__MESSAGE_ID)) {
+ HDR_PARSE_START(HDR__MESSAGE_ID);
+ if (!HIScheck(History, HDR(HDR__MESSAGE_ID)) &&
+ !InndHisRemember(HDR(HDR__MESSAGE_ID)))
+ syslog(L_ERROR, "%s cant write %s", LogName, HDR(HDR__MESSAGE_ID));
+ }
+ /* Clear the work-in-progress entry. */
+ NCclearwip(cp);
+ readmore = false;
+ movedata = false;
+ break;
+ }
+
+ if (*cp->Error != '\0') {
+ cp->Rejected++;
+ cp->State = CSgetcmd;
+ cp->Start = cp->Next;
+ NCclearwip(cp);
+ if (cp->Sendid.size > 3)
+ NCwritereply(cp, cp->Sendid.data);
+ else
+ NCwritereply(cp, cp->Error);
+ readmore = false;
+ movedata = false;
+ break;
+ }
+
+ if (cp->State == CSnoarticle) {
+ /* this should happen when parsing header */
+ cp->Rejected++;
+ cp->State = CSgetcmd;
+ cp->Start = cp->Next;
+ /* Clear the work-in-progress entry. */
+ NCclearwip(cp);
+ if (cp->Sendid.size > 3) { /* We be streaming */
+ cp->Takethis_Err++;
+ snprintf(buff, sizeof(buff), "%d", NNTP_ERR_FAILID_VAL);
+ cp->Sendid.data[0] = buff[0];
+ cp->Sendid.data[1] = buff[1];
+ cp->Sendid.data[2] = buff[2];
+ NCwritereply(cp, cp->Sendid.data);
+ } else
+ NCwritereply(cp, NNTP_REJECTIT_EMPTY);
+ readmore = false;
+ movedata = false;
+ break;
+ }
+ case CSgotarticle: /* in case caming back from pause */
+ /* never move data so long as "\r\n.\r\n" is found, since subsequent data
+ may also include command line */
+ readmore = false;
+ movedata = false;
+ if (Mode == OMpaused) { /* defer processing while paused */
+ RCHANremove(cp); /* don't bother trying to read more for now */
+ SCHANadd(cp, Now.time + innconf->pauseretrytime, &Mode, NCproc, NULL);
+ return;
+ } else if (Mode == OMthrottled) {
+ /* Clear the work-in-progress entry. */
+ NCclearwip(cp);
+ NCwriteshutdown(cp, ModeReason);
+ cp->Rejected++;
+ return;
+ }
+
+ SCHANremove(cp);
+ if (cp->Argument != NULL) {
+ free(cp->Argument);
+ cp->Argument = NULL;
+ }
+ NCpostit(cp);
+ /* Clear the work-in-progress entry. */
+ NCclearwip(cp);
+ if (cp->State == CSwritegoodbye)
+ break;
+ cp->State = CSgetcmd;
+ cp->Start = cp->Next;
+ break;
+
+ case CSeatcommand:
+ /* Eat the command line and then complain that it was too large */
+ /* Reading a line; look for "\r\n" terminator. */
+ /* cp->Next should be SAVE_AMT(10) */
+ for (i = cp->Next ; i < bp->used; i++) {
+ if ((bp->data[i - 1] == '\r') && (bp->data[i] == '\n')) {
+ cp->Next = i + 1;
+ break;
+ }
+ }
+ if (i < bp->used) { /* did find terminator */
+ /* Reached the end of the command line. */
+ SCHANremove(cp);
+ if (cp->Argument != NULL) {
+ free(cp->Argument);
+ cp->Argument = NULL;
+ }
+ i += cp->LargeCmdSize;
+ syslog(L_NOTICE, "%s internal rejecting too long command line (%d > %d)",
+ CHANname(cp), i, NNTP_STRLEN);
+ cp->LargeCmdSize = 0;
+ snprintf(buff, sizeof(buff), "%d command exceeds limit of %d bytes",
+ NNTP_BAD_COMMAND_VAL, NNTP_STRLEN);
+ cp->State = CSgetcmd;
+ cp->Start = cp->Next;
+ NCwritereply(cp, buff);
+ readmore = false;
+ movedata = false;
+ } else {
+ cp->LargeCmdSize += bp->used - cp->Next;
+ bp->used = cp->Next = SAVE_AMT;
+ bp->left = bp->size - SAVE_AMT;
+ cp->Start = 0;
+ readmore = true;
+ movedata = false;
+ }
+ break;
+
+ case CSgetxbatch:
+ /* if the batch is complete, write it out into the in.coming
+ * directory with an unique timestamp, and start rnews on it.
+ */
+ if (Tracing || cp->Tracing)
+ syslog(L_TRACE, "%s CSgetxbatch: now %lu of %d bytes", CHANname(cp),
+ (unsigned long) bp->used, cp->XBatchSize);
+
+ if (cp->Next != 0) {
+ /* data must start from the begining of the buffer */
+ movedata = true;
+ readmore = false;
+ break;
+ }
+ if (bp->used < cp->XBatchSize) {
+ movedata = false;
+ readmore = true;
+ break; /* give us more data */
+ }
+ movedata = false;
+ readmore = false;
+
+ /* now do something with the batch */
+ {
+ char buff2[SMBUF];
+ int fd, oerrno, failed;
+ long now;
+
+ now = time(NULL);
+ failed = 0;
+ /* time+channel file descriptor should make an unique file name */
+ snprintf(buff, sizeof(buff), "%s/%ld%d.tmp", innconf->pathincoming,
+ now, cp->fd);
+ fd = open(buff, O_WRONLY|O_CREAT|O_EXCL, ARTFILE_MODE);
+ if (fd < 0) {
+ oerrno = errno;
+ failed = 1;
+ syslog(L_ERROR, "%s cannot open outfile %s for xbatch: %m",
+ CHANname(cp), buff);
+ snprintf(buff, sizeof(buff), "%s cant create file: %s",
+ NNTP_RESENDIT_XBATCHERR, strerror(oerrno));
+ NCwritereply(cp, buff);
+ } else {
+ if (write(fd, cp->In.data, cp->XBatchSize) != cp->XBatchSize) {
+ oerrno = errno;
+ syslog(L_ERROR, "%s cant write batch to file %s: %m", CHANname(cp),
+ buff);
+ snprintf(buff, sizeof(buff), "%s cant write batch to file: %s",
+ NNTP_RESENDIT_XBATCHERR, strerror(oerrno));
+ NCwritereply(cp, buff);
+ failed = 1;
+ }
+ }
+ if (fd >= 0 && close(fd) != 0) {
+ oerrno = errno;
+ syslog(L_ERROR, "%s error closing batch file %s: %m", CHANname(cp),
+ failed ? "" : buff);
+ snprintf(buff, sizeof(buff), "%s error closing batch file: %s",
+ NNTP_RESENDIT_XBATCHERR, strerror(oerrno));
+ NCwritereply(cp, buff);
+ failed = 1;
+ }
+ snprintf(buff2, sizeof(buff2), "%s/%ld%d.x", innconf->pathincoming,
+ now, cp->fd);
+ if (rename(buff, buff2)) {
+ oerrno = errno;
+ syslog(L_ERROR, "%s cant rename %s to %s: %m", CHANname(cp),
+ failed ? "" : buff, buff2);
+ snprintf(buff, sizeof(buff), "%s cant rename batch to %s: %s",
+ NNTP_RESENDIT_XBATCHERR, buff2, strerror(oerrno));
+ NCwritereply(cp, buff);
+ failed = 1;
+ }
+ cp->Reported++;
+ if (!failed) {
+ NCwritereply(cp, NNTP_OK_XBATCHED);
+ cp->Received++;
+ } else
+ cp->Rejected++;
+ }
+ syslog(L_NOTICE, "%s accepted batch size %d", CHANname(cp),
+ cp->XBatchSize);
+ cp->State = CSgetcmd;
+ cp->Start = cp->Next = cp->XBatchSize;
+ break;
+ }
+ if (cp->State == CSwritegoodbye || cp->Type == CTfree)
+ break;
+ if (Tracing || cp->Tracing)
+ syslog(L_TRACE, "%s NCproc state=%d Start=%lu Next=%lu Used=%lu",
+ CHANname(cp), cp->State, (unsigned long) cp->Start,
+ (unsigned long) cp->Next, (unsigned long) bp->used);
+
+ if (movedata) { /* move data rather than extend buffer */
+ TMRstart(TMR_DATAMOVE);
+ movedata = false;
+ if (cp->Start > 0)
+ memmove(bp->data, &bp->data[cp->Start], bp->used - cp->Start);
+ bp->used -= cp->Start;
+ bp->left += cp->Start;
+ cp->Next -= cp->Start;
+ if (cp->State == CSgetheader || cp->State == CSgetbody ||
+ cp->State == CSeatarticle) {
+ /* adjust offset only in CSgetheader, CSgetbody or CSeatarticle */
+ data->CurHeader -= cp->Start;
+ data->LastTerminator -= cp->Start;
+ data->LastCR -= cp->Start;
+ data->LastCRLF -= cp->Start;
+ data->Body -= cp->Start;
+ if (data->BytesHeader != NULL)
+ data->BytesHeader -= cp->Start;
+ for (i = 0 ; i < MAX_ARTHEADER ; i++, hc++) {
+ if (hc->Value != NULL)
+ hc->Value -= cp->Start;
+ }
+ }
+ cp->Start = 0;
+ TMRstop(TMR_DATAMOVE);
+ }
+ if (readmore)
+ /* need to read more */
+ break;
+ }
+}
+
+
+/*
+** Read whatever data is available on the channel. If we got the
+** full amount (i.e., the command or the whole article) process it.
+*/
+static void
+NCreader(CHANNEL *cp)
+{
+ int i;
+
+ if (Tracing || cp->Tracing)
+ syslog(L_TRACE, "%s NCreader Used=%lu",
+ CHANname(cp), (unsigned long) cp->In.used);
+
+ /* Read any data that's there; ignore errors (retry next time it's our
+ * turn) and if we got nothing, then it's EOF so mark it closed. */
+ if ((i = CHANreadtext(cp)) <= 0) {
+ /* Return of -2 indicates we got EAGAIN even though the descriptor
+ selected true for reading, probably due to the Solaris select
+ bug. Drop back out to the main loop as if the descriptor never
+ selected true. */
+ if (i == -2) {
+ return;
+ }
+ if (i == 0 || cp->BadReads++ >= innconf->badiocount) {
+ if (NCcount > 0)
+ NCcount--;
+ CHANclose(cp, CHANname(cp));
+ }
+ return;
+ }
+
+ NCproc(cp); /* check and process data */
+}
+
+
+
+/*
+** Set up the NNTP channel state.
+*/
+void
+NCsetup(void)
+{
+ char *p;
+ char buff[SMBUF];
+
+ /* Set the greeting message. */
+ p = innconf->pathhost;
+ if (p == NULL)
+ /* Worked in main, now it fails? Curious. */
+ p = Path.data;
+ snprintf(buff, sizeof(buff), "%d %s InterNetNews server %s ready",
+ NNTP_POSTOK_VAL, p, inn_version_string);
+ NCgreeting = xstrdup(buff);
+}
+
+
+/*
+** Tear down our state.
+*/
+void
+NCclose(void)
+{
+ CHANNEL *cp;
+ int j;
+
+ /* Close all incoming channels. */
+ for (j = 0; (cp = CHANiter(&j, CTnntp)) != NULL; ) {
+ if (NCcount > 0)
+ NCcount--;
+ CHANclose(cp, CHANname(cp));
+ }
+}
+
+
+/*
+** Create an NNTP channel and print the greeting message.
+*/
+CHANNEL *
+NCcreate(int fd, bool MustAuthorize, bool IsLocal)
+{
+ CHANNEL *cp;
+ int i;
+
+ /* Create the channel. */
+ cp = CHANcreate(fd, CTnntp, MustAuthorize ? CSgetauth : CSgetcmd,
+ NCreader, NCwritedone);
+
+ NCclearwip(cp);
+ cp->privileged = IsLocal;
+#if defined(SOL_SOCKET) && defined(SO_SNDBUF) && defined(SO_RCVBUF)
+ if (!IsLocal) {
+ i = 24 * 1024;
+ if (setsockopt(fd, SOL_SOCKET, SO_SNDBUF, (char *)&i, sizeof i) < 0)
+ syslog(L_ERROR, "%s cant setsockopt(SNDBUF) %m", CHANname(cp));
+ if (setsockopt(fd, SOL_SOCKET, SO_RCVBUF, (char *)&i, sizeof i) < 0)
+ syslog(L_ERROR, "%s cant setsockopt(RCVBUF) %m", CHANname(cp));
+ }
+#endif /* defined(SOL_SOCKET) && defined(SO_SNDBUF) && defined(SO_RCVBUF) */
+
+#if defined(SOL_SOCKET) && defined(SO_KEEPALIVE)
+ if (!IsLocal) {
+ /* Set KEEPALIVE to catch broken socket connections. */
+ i = 1;
+ if (setsockopt(fd, SOL_SOCKET, SO_KEEPALIVE, (char *)&i, sizeof i) < 0)
+ syslog(L_ERROR, "%s cant setsockopt(KEEPALIVE) %m", CHANname(cp));
+ }
+#endif /* defined(SOL_SOCKET) && defined(SO_KEEPALIVE) */
+
+ /* Now check our operating mode. */
+ NCcount++;
+ if (Mode == OMthrottled) {
+ NCwriteshutdown(cp, ModeReason);
+ return NULL;
+ }
+ if (RejectReason) {
+ NCwriteshutdown(cp, RejectReason);
+ return NULL;
+ }
+
+ /* See if we have too many channels. */
+ if (!IsLocal && innconf->maxconnections &&
+ NCcount >= innconf->maxconnections && !RCnolimit(cp)) {
+ /* Recount, just in case we got out of sync. */
+ for (NCcount = 0, i = 0; CHANiter(&i, CTnntp) != NULL; )
+ NCcount++;
+ if (NCcount >= innconf->maxconnections) {
+ NCwriteshutdown(cp, "Too many connections");
+ return NULL;
+ }
+ }
+ cp->BadReads = 0;
+ cp->BadCommands = 0;
+ return cp;
+}
+
+
+
+/* These modules support the streaming option to tranfer articles
+** faster.
+*/
+
+/*
+** The "check" command. Check the Message-ID, and see if we want the
+** article or not. Stay in command state.
+*/
+static void
+NCcheck(CHANNEL *cp)
+{
+ char *p;
+ int idlen, msglen;
+#if defined(DO_PERL) || defined(DO_PYTHON)
+ char *filterrc;
+#endif /* DO_PERL || DO_PYTHON */
+
+ cp->Check++;
+ /* Snip off the Message-ID. */
+ for (p = cp->In.data + cp->Start; *p && !ISWHITE(*p); p++)
+ continue;
+ cp->Start = cp->Next;
+ for ( ; ISWHITE(*p); p++)
+ continue;
+ idlen = strlen(p);
+ msglen = idlen + 5; /* 3 digits + space + id + null */
+ if (cp->Sendid.size < msglen) {
+ if (cp->Sendid.size > 0) free(cp->Sendid.data);
+ if (msglen > MAXHEADERSIZE) cp->Sendid.size = msglen;
+ else cp->Sendid.size = MAXHEADERSIZE;
+ cp->Sendid.data = xmalloc(cp->Sendid.size);
+ }
+ if (!ARTidok(p)) {
+ snprintf(cp->Sendid.data, cp->Sendid.size, "%d %s",
+ NNTP_ERR_GOTID_VAL, p);
+ NCwritereply(cp, cp->Sendid.data);
+ syslog(L_NOTICE, "%s bad_messageid %s", CHANname(cp), MaxLength(p, p));
+ return;
+ }
+
+ if ((innconf->refusecybercancels) && (strncmp(p, "<cancel.", 8) == 0)) {
+ cp->Refused++;
+ cp->Check_cybercan++;
+ snprintf(cp->Sendid.data, cp->Sendid.size, "%d %s",
+ NNTP_ERR_GOTID_VAL, p);
+ NCwritereply(cp, cp->Sendid.data);
+ return;
+ }
+
+#if defined(DO_PERL)
+ /* Invoke a perl message filter on the message ID. */
+ filterrc = PLmidfilter(p);
+ if (filterrc) {
+ cp->Refused++;
+ snprintf(cp->Sendid.data, cp->Sendid.size, "%d %s",
+ NNTP_ERR_GOTID_VAL, p);
+ NCwritereply(cp, cp->Sendid.data);
+ return;
+ }
+#endif /* defined(DO_PERL) */
+
+#if defined(DO_PYTHON)
+ /* invoke a python message filter on the message id */
+ filterrc = PYmidfilter(p, idlen);
+ if (filterrc) {
+ cp->Refused++;
+ snprintf(cp->Sendid.data, cp->Sendid.size, "%d %s",
+ NNTP_ERR_GOTID_VAL, p);
+ NCwritereply(cp, cp->Sendid.data);
+ return;
+ }
+#endif /* defined(DO_PYTHON) */
+
+ if (HIScheck(History, p)) {
+ cp->Refused++;
+ cp->Check_got++;
+ snprintf(cp->Sendid.data, cp->Sendid.size, "%d %s",
+ NNTP_ERR_GOTID_VAL, p);
+ NCwritereply(cp, cp->Sendid.data);
+ } else if (WIPinprogress(p, cp, true)) {
+ cp->Check_deferred++;
+ if (cp->NoResendId) {
+ cp->Refused++;
+ snprintf(cp->Sendid.data, cp->Sendid.size, "%d %s",
+ NNTP_ERR_GOTID_VAL, p);
+ } else {
+ snprintf(cp->Sendid.data, cp->Sendid.size, "%d %s",
+ NNTP_RESENDID_VAL, p);
+ }
+ NCwritereply(cp, cp->Sendid.data);
+ } else {
+ cp->Check_send++;
+ snprintf(cp->Sendid.data, cp->Sendid.size, "%d %s",
+ NNTP_OK_SENDID_VAL, p);
+ NCwritereply(cp, cp->Sendid.data);
+ }
+ /* stay in command mode */
+}
+
+/*
+** The "takethis" command. Article follows.
+** Remember <id> for later ack.
+*/
+static void
+NCtakethis(CHANNEL *cp)
+{
+ char *p;
+ int msglen;
+ WIP *wp;
+
+ cp->Takethis++;
+ /* Snip off the Message-ID. */
+ for (p = cp->In.data + cp->Start + strlen("takethis"); ISWHITE(*p); p++)
+ continue;
+ cp->Start = cp->Next;
+ for ( ; ISWHITE(*p); p++)
+ continue;
+ if (!ARTidok(p)) {
+ syslog(L_NOTICE, "%s bad_messageid %s", CHANname(cp), MaxLength(p, p));
+ }
+ msglen = strlen(p) + 5; /* 3 digits + space + id + null */
+ if (cp->Sendid.size < msglen) {
+ if (cp->Sendid.size > 0) free(cp->Sendid.data);
+ if (msglen > MAXHEADERSIZE) cp->Sendid.size = msglen;
+ else cp->Sendid.size = MAXHEADERSIZE;
+ cp->Sendid.data = xmalloc(cp->Sendid.size);
+ }
+ /* save ID for later NACK or ACK */
+ snprintf(cp->Sendid.data, cp->Sendid.size, "%d %s", NNTP_ERR_FAILID_VAL,
+ p);
+
+ cp->ArtBeg = Now.time;
+ cp->State = CSgetheader;
+ ARTprepare(cp);
+ /* set WIP for benefit of later code in NCreader */
+ if ((wp = WIPbyid(p)) == (WIP *)NULL)
+ wp = WIPnew(p, cp);
+ cp->CurrentMessageIDHash = wp->MessageID;
+}
+
+/*
+** Process a cancel ID from a "mode cancel" channel.
+*/
+static void
+NCcancel(CHANNEL *cp)
+{
+ char *av[2] = { NULL, NULL };
+ const char *res;
+
+ ++cp->Received;
+ av[0] = cp->In.data + cp->Start;
+ cp->Start = cp->Next;
+ res = CCcancel(av);
+ if (res) {
+ char buff[SMBUF];
+
+ snprintf(buff, sizeof(buff), "%d %s", NNTP_ERR_CANCEL_VAL,
+ MaxLength(res, res));
+ syslog(L_NOTICE, "%s cant_cancel %s", CHANname(cp),
+ MaxLength(res, res));
+ NCwritereply(cp, buff);
+ } else {
+ NCwritereply(cp, NNTP_OK_CANCELLED);
+ }
+}
--- /dev/null
+/* $Id: newsfeeds.c 7730 2008-04-06 08:27:16Z iulius $
+**
+** Routines for the in-core data structures for the newsfeeds file.
+*/
+
+#include "config.h"
+#include "clibrary.h"
+
+#include "inn/innconf.h"
+#include "innd.h"
+
+/*
+** List of variables assigned in the configuration file.
+*/
+typedef struct _SITEVARIABLES {
+ char *Name;
+ char *Value;
+ int Elements;
+ struct _SITEVARIABLES *Next;
+} SITEVARIABLES;
+
+/* The character which introduces a variable assignment or reference. */
+#define VARIABLE_CHAR '$'
+
+static SITE SITEnull;
+static char *SITEfeedspath = NULL;
+static SITEVARIABLES *SITEvariables = NULL;
+
+
+/*
+** Return a copy of an array of strings.
+*/
+static char **
+SITEcopystrings(char **av)
+{
+ char **new;
+ char **pp;
+ char **save;
+
+ for (pp = av; *pp; pp++)
+ continue;
+ for (new = save = xmalloc((pp - av + 1) * sizeof(char *)), pp = av; *pp; pp++)
+ *new++ = xstrdup(*pp);
+ *new = NULL;
+ return save;
+}
+
+/*
+** Adds a variable from a line.
+*/
+static bool
+SITEaddvariable(char *line)
+{
+ char *p, *q;
+ SITEVARIABLES *v, *w;
+
+ if (*line != VARIABLE_CHAR)
+ return false;
+
+ for (p = line + 1; *p != '\0' && CTYPE(isalnum, *p); p++)
+ ;
+ if (*p != '=')
+ return false;
+ if (p - line > 32) {
+ syslog(L_FATAL, "%s bad_newsfeed variable name '%s' too long",
+ LogName, line+1);
+ return false;
+ }
+
+ /* Chop off trailing spaces. */
+ q = p + strlen(p) - 1;
+ while (q > p && (*q == ' ' || *q == '\t'))
+ *q-- = '\0';
+
+ /* Seperate variable name from contents. */
+ *p++ = '\0';
+ if (*p == '\0')
+ return false;
+
+ /* Is variable already defined? Free and reassign. */
+ w = NULL;
+ v = SITEvariables;
+ while (v && strcmp(line + 1, v->Name)) {
+ w = v;
+ v = v->Next;
+ }
+ if (v)
+ free(v->Value);
+ else {
+ v = xmalloc(sizeof(SITEVARIABLES));
+ if (!SITEvariables)
+ SITEvariables = v;
+ if (w)
+ w->Next = v;
+ v->Name = xstrdup(line + 1);
+ v->Next = NULL;
+ }
+
+ /* Add variable's contents. */
+ v->Elements = 1;
+ for (q = v->Value = xmalloc(strlen(p) + 1); *p != '\0'; p++) {
+ if (*p != ' ' && *p != '\t')
+ *q++ = *p;
+ if (*p == ',')
+ v->Elements++;
+ }
+ *q = '\0';
+ return true;
+}
+
+static void
+SITEclearvariables(void)
+{
+ SITEVARIABLES *v, *w;
+
+ v = SITEvariables;
+ while (v) {
+ free(v->Name);
+ free(v->Value);
+ w = v;
+ v = v->Next;
+ free(w);
+ }
+ SITEvariables = NULL;
+}
+
+static SITEVARIABLES *
+SITEfindvariable(char *name)
+{
+ SITEVARIABLES *v;
+
+ v = SITEvariables;
+ while (v && strcmp(v->Name, name) != 0)
+ v = v->Next;
+ return v;
+}
+
+static char *
+SITEexpandvariables(char *site)
+{
+ char *p, *r, *s;
+ char *q = NULL;
+ int c = 0;
+ char modifier;
+ char varname[64];
+ SITEVARIABLES *v;
+
+ /* Count characters. */
+ *varname = '\0';
+ modifier = '\0';
+ for (p = site; p <= site + strlen(site); p++) {
+ /* In variable name. */
+ if (*varname) {
+ if (CTYPE(isalnum, *p)) {
+ if (q - varname > 32) {
+ /* Add ignored modifier. */
+ if (modifier)
+ c++;
+ /* Add ignored $ and characters. */
+ c += strlen(varname);
+ /* Add this character. */
+ c++;
+ *varname = '\0';
+ modifier = '\0';
+ continue;
+ }
+ /* Append to variable name. */
+ *q++ = *p;
+ continue;
+ } else {
+ v = SITEfindvariable(varname + 1);
+ if (v != NULL) {
+ /* Add length of contents. */
+ c += strlen(v->Value);
+ /* If modified add number of mods. */
+ if (modifier)
+ c += v->Elements;
+ } else {
+ /* Add ignored modifier. */
+ if (modifier)
+ c++;
+ c += strlen(varname); /* add ignored $ and characters */
+ }
+ *varname = '\0';
+ modifier = '\0';
+ }
+ }
+ /* New variable starts */
+ if (*p == VARIABLE_CHAR) {
+ q = varname;
+ memset(varname, 0, sizeof(varname));
+ *q++ = VARIABLE_CHAR;
+ continue;
+ }
+ if (modifier) {
+ /* Add last modifier */
+ c++;
+ modifier = '\0';
+ }
+ if (*p == SUB_NEGATE || *p == SUB_POISON) {
+ modifier = *p;
+ } else {
+ /* Add this character. */
+ c++;
+ }
+ }
+
+ /* Copy contents. */
+ s = r = xmalloc(c + 1);
+ *varname = '\0';
+ modifier = '\0';
+ for (p = site; p <= site + strlen(site); p++) {
+ /* In variable name. */
+ if (*varname) {
+ if (CTYPE(isalnum, *p)) {
+ if (q - varname > 32) {
+ if (modifier)
+ *s++ = modifier;
+ for (q = varname; *q; q++)
+ *s++ = *q;
+ *s++ = *p;
+ *varname = '\0';
+ modifier = '\0';
+ continue;
+ }
+ *q++ = *p;
+ continue;
+ } else {
+ v = SITEfindvariable(varname + 1);
+ if (v != NULL) {
+ if (modifier)
+ *s++ = modifier;
+ for (q = v->Value; *q; q++) {
+ *s++ = *q;
+ if (*q == ',' && modifier)
+ *s++ = modifier;
+ }
+ } else {
+ if (modifier)
+ *s++ = modifier;
+ for (q = varname; *q; q++)
+ *s++ = *q;
+ }
+ *varname = '\0';
+ modifier = '\0';
+ }
+ }
+ /* New variable starts. */
+ if (*p == VARIABLE_CHAR) {
+ q = varname;
+ memset(varname, 0, sizeof(varname));
+ *q++ = VARIABLE_CHAR;
+ continue;
+ }
+ if (modifier) {
+ *s++ = modifier;
+ modifier = '\0';
+ }
+ if (*p == SUB_NEGATE || *p == SUB_POISON)
+ modifier = *p;
+ else
+ *s++ = *p;
+ }
+ *s++ = '\0';
+
+ return r;
+}
+
+/*
+** Read the newsfeeds file, return a string array of entries.
+*/
+char **
+SITEreadfile(const bool ReadOnly)
+{
+ static char **old_strings;
+ static time_t old_mtime;
+ static ino_t old_ino;
+ static off_t old_size;
+ char *p;
+ char *to;
+ char *site;
+ int i;
+ struct stat Sb;
+ char *data;
+
+ if (SITEfeedspath == NULL)
+ SITEfeedspath = concatpath(innconf->pathetc, _PATH_NEWSFEEDS);
+ if (old_strings != NULL) {
+ /* If the file hasn't changed, return a copy of the old data. */
+ if (stat(SITEfeedspath, &Sb) >= 0
+ && Sb.st_ino == old_ino
+ && Sb.st_size == old_size
+ && Sb.st_mtime == old_mtime)
+ return ReadOnly ? old_strings : SITEcopystrings(old_strings);
+
+ /* Data's bad, toss it. */
+ for (i = 0; old_strings[i] != NULL; i++)
+ free(old_strings[i]);
+ free(old_strings);
+ }
+
+ /* Read in the file, note its statistics. */
+ if ((data = ReadInFile(SITEfeedspath, &Sb)) == NULL) {
+ syslog(L_FATAL, "%s cant read %s %m", LogName, SITEfeedspath);
+ exit(1);
+ }
+ old_mtime = Sb.st_mtime;
+ old_ino = Sb.st_ino;
+ old_size = Sb.st_size;
+
+ /* Get a gross count of the number of sites. */
+ for (p = data, i = 0; (p = strchr(p, '\n')) != NULL; p++, i++)
+ continue;
+
+ /* Scan the file, parse all multi-line entries. */
+ for (old_strings = xmalloc((i + 1) * sizeof(char *)), i = 0, to = p = data; *p; ) {
+ for (site = to; *p; ) {
+ if (*p == '\n') {
+ p++;
+ *to = '\0';
+ break;
+ }
+ if (*p == '\\' && p[1] == '\n')
+ while (*++p && CTYPE(isspace, *p))
+ continue;
+ else
+ *to++ = *p++;
+ }
+ *to++ = '\0';
+ if (*site == '#' || *site == '\0')
+ continue ;
+ if (*site == VARIABLE_CHAR && SITEaddvariable(site))
+ continue ;
+ if (strspn(site," \t") == strlen (site))
+ continue;
+ if (SITEvariables)
+ old_strings[i++] = SITEexpandvariables(site);
+ else
+ old_strings[i++] = xstrdup(site);
+ }
+ old_strings[i] = NULL;
+
+ SITEclearvariables();
+ free(data);
+ return ReadOnly ? old_strings : SITEcopystrings(old_strings);
+}
+
+
+/*
+** Modify "subbed" according to the patterns in "patlist."
+*/
+static void
+SITEsetlist(char **patlist, char *subbed, char *poison, bool *poisonEntry)
+{
+ char *pat;
+ char *p;
+ char *u;
+ char subvalue;
+ char poisonvalue;
+ NEWSGROUP *ngp;
+ int i;
+
+ while ((pat = *patlist++) != NULL) {
+ subvalue = *pat != SUB_NEGATE && *pat != SUB_POISON;
+ poisonvalue = *pat == SUB_POISON;
+ if (!subvalue)
+ pat++;
+ if (!*pat)
+ continue;
+ if (!*poisonEntry && poisonvalue)
+ *poisonEntry = true;
+
+ /* See if pattern is a simple newsgroup name. If so, set the
+ * right subbed element for that one group (if found); if not,
+ * pattern-match against all the groups. */
+ for (p = pat; *p; p++)
+ if (*p == '?' || *p == '*' || *p == '[')
+ break;
+ if (*p == '\0') {
+ /* Simple string; look it up, set it. */
+ if ((ngp = NGfind(pat)) != NULL) {
+ subbed[ngp - Groups] = subvalue;
+ poison[ngp - Groups] = poisonvalue;
+ }
+ }
+ else
+ for (p = subbed, u = poison, ngp = Groups, i = nGroups;
+ --i >= 0; ngp++, p++, u++)
+ if (uwildmat(ngp->Name, pat)) {
+ *p = subvalue;
+ *u = poisonvalue;
+ }
+ }
+}
+
+/*
+** Split text into slash-separated fields. Return an allocated
+** NULL-terminated array of the fields within the modified argument that
+** the caller is expected to save or free. We don't use strchr() since
+** the text is expected to be either relatively short or "slash-dense."
+*/
+static char **
+SlashSplit(char *text)
+{
+ int i;
+ char *p;
+ char **av;
+ char **save;
+
+ /* How much space do we need? */
+ for (i = 2, p = text; *p; p++)
+ if (*p == '/')
+ i++;
+
+ for (av = save = xmalloc(i * sizeof(char *)), *av++ = p = text; *p; )
+ if (*p == '/') {
+ *p++ = '\0';
+ *av++ = p;
+ }
+ else
+ p++;
+ *av = NULL;
+ return save;
+}
+
+/*
+** Parse an individual site entry. Subbed is used to build the subscription
+** list. Since this routine is called once for each site, the caller
+** allocates subbed once, and frees it after the last site has been parsed.
+** If subbed is NULL, we don't update the SITE array, since we're just
+** doing syntax checking.
+*/
+const char *
+SITEparseone(char *Entry, SITE *sp, char *subbed, char *poison)
+{
+ int i;
+ int j;
+ NEWSGROUP *ngp;
+ char *p;
+ char *u;
+ char *f2;
+ char *f3;
+ char *f4;
+ char **save;
+ char **argv;
+ bool JustModerated;
+ bool JustUnmoderated;
+ int isp;
+ SITE *nsp;
+ struct buffer b;
+ HASHFEEDLIST *hf;
+
+ b = sp->Buffer;
+ *sp = SITEnull;
+ sp->Buffer = b;
+ sp->Master = NOSITE;
+ sp->Funnel = NOSITE;
+ sp->PoisonEntry = false;
+ sp->Process = -1;
+ sp->Next = sp->Prev = NOSITE;
+ sp->Entry = Entry;
+ sp->Originator = NULL;
+ sp->FileFlags[0] = FEED_NAME;
+ sp->FileFlags[1] = '\0';
+ sp->Nice = innconf->nicekids;
+ sp->ControlOnly = false;
+ sp->DontWantNonExist = false;
+ sp->NeedOverviewCreation = false;
+ sp->FeedwithoutOriginator = false;
+ sp->DropFiltered = false;
+ sp->HashFeedList = NULL;
+
+ /* Nip off the first field, the site name. */
+ if ((f2 = strchr(Entry, NF_FIELD_SEP)) == NULL)
+ return "missing field 2";
+ *f2++ = '\0';
+ sp->Name = Entry;
+ if ((p = strchr(sp->Name, NF_SUBFIELD_SEP)) != NULL) {
+ /* Exclusions within the site field. */
+ *p++ = '\0';
+ if (*p)
+ sp->Exclusions = CommaSplit(p);
+ }
+ sp->NameLength = strlen(sp->Name);
+
+ /* Parse the second field, the subscriptions. */
+ if ((f3 = strchr(f2, NF_FIELD_SEP)) == NULL)
+ return "missing field 3";
+ *f3++ = '\0';
+ if ((p = strchr(f2, NF_SUBFIELD_SEP)) != NULL) {
+ /* Distributions within the subscription field. */
+ *p++ = '\0';
+ if (*p)
+ sp->Distributions = CommaSplit(p);
+ }
+ if (f2)
+ sp->Patterns = CommaSplit(f2);
+
+ if (subbed) {
+ /* Read the subscription patterns and set the bits. */
+ memset(subbed, SUB_DEFAULT, nGroups);
+ memset(poison, SUB_DEFAULT, nGroups);
+ if (ME.Patterns)
+ SITEsetlist(ME.Patterns, subbed, poison, &ME.PoisonEntry);
+ SITEsetlist(sp->Patterns, subbed, poison, &sp->PoisonEntry);
+ }
+
+ /* Get the third field, the flags. */
+ if ((f4 = strchr(f3, NF_FIELD_SEP)) == NULL)
+ return "missing field 4";
+ *f4++ = '\0';
+ JustModerated = false;
+ JustUnmoderated = false;
+ sp->Type = FTfile;
+ for (save = argv = CommaSplit(f3); (p = *argv++) != NULL; )
+ switch (*p) {
+ default:
+ return "unknown field 3 flag";
+ case '\0':
+ break;
+ case '<':
+ if (*++p && CTYPE(isdigit, *p))
+ sp->MaxSize = atol(p);
+ break;
+ case '>':
+ if (*++p && CTYPE(isdigit, *p))
+ sp->MinSize = atol(p);
+ break;
+ case 'A':
+ while (*++p)
+ switch (*p) {
+ default:
+ return "unknown A param in field 3";
+ case 'c': sp->IgnoreControl = true;
+ sp->ControlOnly = false;
+ break;
+ case 'C': sp->ControlOnly = true;
+ sp->IgnoreControl = false;
+ break;
+ case 'd': sp->DistRequired = true; break;
+ case 'e': sp->DontWantNonExist = true; break;
+ case 'f': sp->DropFiltered = true; break;
+ case 'o': sp->NeedOverviewCreation = true; break;
+ case 'O': sp->FeedwithoutOriginator = true; break;
+ case 'p': sp->IgnorePath = true; break;
+ }
+ break;
+ case 'B':
+ if (*++p && CTYPE(isdigit, *p)) {
+ sp->StartWriting = atoi(p);
+ if ((p = strchr(p, NF_SUBFIELD_SEP)) != NULL
+ && *++p
+ && CTYPE(isdigit, *p))
+ sp->StopWriting = atoi(p);
+ }
+ break;
+ case 'C':
+ if (*++p && CTYPE(isdigit, *p))
+ sp->Crosscount = atoi(p);
+ else
+ sp->Crosscount = 1;
+ break;
+ case 'F':
+ if (*++p == '\0')
+ return "missing file name for F param in field 3";
+ else if (*p == '/')
+ sp->SpoolName = xstrdup(p);
+ else {
+ sp->SpoolName = xmalloc(strlen(innconf->pathoutgoing) + 1 +
+ strlen(p) + 1);
+ FileGlue(sp->SpoolName, innconf->pathoutgoing, '/', p);
+ }
+ break;
+ case 'G':
+ if (*++p && CTYPE(isdigit, *p))
+ sp->Groupcount = atoi(p);
+ else
+ sp->Groupcount = 1;
+ break;
+ case 'H':
+ if (*++p && CTYPE(isdigit, *p))
+ sp->Hops = atoi(p);
+ else
+ sp->Hops = 1;
+ break;
+ case 'I':
+ if (*++p && CTYPE(isdigit, *p))
+ sp->Flushpoint = atol(p);
+ break;
+ case 'N':
+ while (*++p)
+ switch (*p) {
+ default:
+ return "unknown N param in field 3";
+ case 'm': JustModerated = true; break;
+ case 'u': JustUnmoderated = true; break;
+ }
+ break;
+ case 'O':
+ if (*++p == '\0')
+ return "missing originator name for O param in field 3";
+ sp->Originator = SlashSplit(p);
+ break;
+ case 'P':
+ if (*++p && CTYPE(isdigit, *p))
+ sp->Nice = atoi(p);
+ break;
+ case 'Q':
+ hf = xmalloc(sizeof(HASHFEEDLIST));
+ p++;
+ /* Check whether it is a quickhash or a MD5 hashfeed. */
+ if (*p == '@') {
+ p++;
+ hf->type = HASHFEED_QH;
+ } else
+ hf->type = HASHFEED_MD5;
+ /* Check the presence of a starting byte-offset for hashfeed. */
+ if ((u = strchr(p, '_')) != NULL) {
+ if (sscanf(u + 1, "%u", &hf->offset) != 1 || hf->offset > 12) {
+ free(hf);
+ return "invalid hash offset for Q param in field 3";
+ }
+ } else
+ hf->offset = 0;
+ if (sscanf(p, "%u/%u", &hf->begin, &hf->mod) == 2) {
+ hf->end = hf->begin;
+ } else if (sscanf(p, "%u-%u/%u", &hf->begin, &hf->end,
+ &hf->mod) != 3) {
+ free(hf);
+ return "hash not in x/z or x-y/z format for Q param in field 3";
+ }
+ if (hf->begin > hf->end || hf->end > hf->mod) {
+ free(hf);
+ return "incorrect hash values for Q param in field 3";
+ }
+ hf->next = sp->HashFeedList;
+ sp->HashFeedList = hf;
+ break;
+ case 'S':
+ if (*++p && CTYPE(isdigit, *p))
+ sp->StartSpooling = atol(p);
+ break;
+ case 'T':
+ switch (*++p) {
+ default:
+ return "unknown T param in field 3";
+ case 'c': sp->Type = FTchannel; break;
+ case 'l': sp->Type = FTlogonly; break;
+ case 'f': sp->Type = FTfile; break;
+ case 'm': sp->Type = FTfunnel; break;
+ case 'p': sp->Type = FTprogram; break;
+ case 'x': sp->Type = FTexploder; break;
+ }
+ break;
+ case 'U':
+ if (*++p && CTYPE(isdigit, *p))
+ sp->Followcount = atoi(p);
+ else
+ sp->Followcount = 1;
+ break;
+ case 'W':
+ for (i = 0; *++p && i < FEED_MAXFLAGS; ) {
+ switch (*p) {
+ default:
+ return "unknown W param in field 3";
+ case FEED_FNLNAMES: /* Funnel feed names */
+ sp->FNLwantsnames = true;
+ break;
+ case FEED_HEADERS: /* Article headers */
+ NeedHeaders = true;
+ break;
+ case FEED_OVERVIEW:
+ NeedOverview = true; /* Overview data */
+ break;
+ case FEED_PATH: /* Path */
+ NeedPath = true;
+ break;
+ case FEED_BYTESIZE: /* Size in bytes */
+ case FEED_FULLNAME: /* Full filename */
+ case FEED_HASH: /* Hash */
+ case FEED_HDR_DISTRIB: /* Distribution header */
+ case FEED_STOREDGROUP: /* stored newsgroup */
+ NeedStoredGroup = true;
+ break;
+ case FEED_HDR_NEWSGROUP: /* Newsgroup header */
+ case FEED_MESSAGEID: /* Message-ID */
+ case FEED_NAME: /* Filename */
+ case FEED_NEWSGROUP: /* Newsgroup */
+ case FEED_REPLIC: /* Replication data */
+ NeedReplicdata = true;
+ break;
+ case FEED_SITE: /* Site that gave it */
+ case FEED_TIMERECEIVED: /* When received */
+ case FEED_TIMEPOSTED: /* When posted */
+ case FEED_TIMEEXPIRED: /* When will be expired */
+ break;
+ }
+ sp->FileFlags[i++] = *p;
+ }
+ if (*p)
+ return "too many W param values";
+ sp->FileFlags[i] = '\0';
+ break;
+ }
+ free(save);
+ if (sp->Flushpoint && sp->Type != FTfile)
+ return "I param with non-file feed";
+ if (sp->Flushpoint == 0 && sp->Type == FTfile)
+ sp->Flushpoint = SITE_BUFFER_SIZE;
+
+ if (subbed) {
+ /* Modify the subscription list based on the flags. */
+ if (JustModerated)
+ for (p = subbed, ngp = Groups, i = nGroups; --i >= 0; ngp++, p++)
+ if (ngp->Rest[0] != NF_FLAG_MODERATED)
+ *p = false;
+ if (JustUnmoderated)
+ for (p = subbed, ngp = Groups, i = nGroups; --i >= 0; ngp++, p++)
+ if (ngp->Rest[0] == NF_FLAG_MODERATED)
+ *p = false;
+ }
+
+ /* Get the fourth field, the param. */
+ if (*f4 == '\0' && sp != &ME) {
+ if (sp->Type != FTfile && sp->Type != FTlogonly)
+ return "empty field 4";
+ sp->Param = xmalloc(strlen(innconf->pathoutgoing) + 1 +
+ sp->NameLength + 1);
+ FileGlue(sp->Param, innconf->pathoutgoing, '/', sp->Name);
+ }
+ else if (sp->Type == FTfile && *f4 != '/') {
+ sp->Param = xmalloc(strlen(innconf->pathoutgoing) + 1 +
+ strlen(f4) + 1);
+ FileGlue(sp->Param, innconf->pathoutgoing, '/', f4);
+ }
+ else
+ sp->Param = xstrdup(f4);
+
+ if (sp->SpoolName == NULL) {
+ sp->SpoolName = xmalloc(strlen(innconf->pathoutgoing) + 1 +
+ strlen(sp->Name) + 1);
+ FileGlue(sp->SpoolName, innconf->pathoutgoing, '/', sp->Name);
+ }
+
+ /* Make sure there is only one %s, and only one *. */
+ if (sp->Type == FTprogram) {
+ f3 = NULL;
+ for (f2 = sp->Param; *f2; f2 = p + 1) {
+ p = strchr(f2, '%');
+ if (p == NULL)
+ break;
+ if (p[1] == '%') {
+ p++;
+ continue;
+ }
+ if (f3 != NULL)
+ return "bad (extra) sprintf format for field 4";
+ f3 = p;
+ while (*++p && *p != '*' && !CTYPE(isalpha, *p))
+ continue;
+ if (*p != 's')
+ return "bad sprintf format for field 4";
+ }
+ if (sp->FNLwantsnames
+ && ((p = strchr(sp->Param, '*')) == NULL
+ || strchr(++p, '*') != NULL))
+ return "multiple or no *'s in field 4";
+ }
+
+ /* Now tell the groups this site gets that they should feed this site. */
+ if (sp != &ME && subbed) {
+ isp = sp - Sites;
+ for (p = subbed, u = poison, ngp = Groups, i = nGroups;
+ --i >= 0; ngp++) {
+ if (*p++) {
+ for (j = 0; j < ngp->nSites; j++)
+ if (ngp->Sites[j] == NOSITE) {
+ ngp->Sites[j] = isp;
+ break;
+ }
+ if (j == ngp->nSites)
+ ngp->Sites[ngp->nSites++] = isp;
+ }
+ if (*u++) {
+ for (j = 0; j < ngp->nPoison; j++)
+ if (ngp->Poison[j] == NOSITE) {
+ ngp->Poison[j] = isp;
+ break;
+ }
+ if (j == ngp->nPoison)
+ ngp->Poison[ngp->nPoison++] = isp;
+ }
+ }
+ }
+
+ /* If this is a duplicate name, find the master. */
+ nsp = SITEfind(sp->Name);
+ if (nsp == sp)
+ nsp = SITEfindnext(sp->Name, nsp);
+ if (nsp != NULL) {
+ if (nsp->Master != NOSITE)
+ nsp = &Sites[nsp->Master];
+ if (nsp != sp) {
+ sp->Master = nsp - Sites;
+ nsp->IsMaster = true;
+ }
+ }
+
+ return NULL;
+}
+
+
+/*
+** Patch up the funnel references.
+*/
+bool
+SITEfunnelpatch(void)
+{
+ int i;
+ int length;
+ SITE *sp;
+ SITE *funnel;
+ bool result;
+
+ /* Get worst-case length of all sitenames. */
+ for (length = 0, i = nSites, sp = Sites; --i >= 0; sp++)
+ if (sp->Name != NULL)
+ length += 1 + strlen(sp->Name);
+
+ /* Loop over all funnel feeds. */
+ for (result = true, i = nSites, sp = Sites; --i >= 0; sp++) {
+ if (sp->Name == NULL || sp->Type != FTfunnel)
+ continue;
+
+ /* Find the entry they feed in to, give that entry a buffer. */
+ if (sp->Param == NULL) {
+ syslog(L_FATAL, "%s funnel NULL", sp->Name);
+ SITEfree(sp);
+ result = false;
+ continue;
+ }
+ if ((funnel = SITEfind(sp->Param)) == NULL) {
+ syslog(L_FATAL, "%s funnel_bad", sp->Name);
+ SITEfree(sp);
+ result = false;
+ continue;
+ }
+ if (funnel->Type == FTfunnel) {
+ syslog(L_FATAL, "%s funnels to funnel %s", sp->Name, funnel->Name);
+ SITEfree(sp);
+ result = false;
+ continue;
+ }
+ if (funnel->FNLnames.data == NULL) {
+ funnel->FNLnames.size = length;
+ funnel->FNLnames.data = xmalloc(length);
+ }
+ else if (funnel->FNLnames.size != length) {
+ funnel->FNLnames.size = length;
+ funnel->FNLnames.data = xrealloc(funnel->FNLnames.data, length);
+ }
+ sp->Funnel = funnel - Sites;
+ }
+
+ return result;
+}
+
+
+/*
+** Read the entries in the newsfeeds file, and parse them one at a time.
+*/
+void
+SITEparsefile(bool StartSite)
+{
+ int i;
+ char * p;
+ char ** strings;
+ SITE * sp;
+ char * subbed;
+ char * poison;
+ const char * error;
+ int errors;
+ int setuperrors;
+
+ /* Free old sites info. */
+ if (Sites) {
+ for (i = nSites, sp = Sites; --i >= 0; sp++) {
+ SITEflush(sp, false);
+ SITEfree(sp);
+ }
+ free(Sites);
+ SITEfree(&ME);
+ }
+
+ /* Count the number of sites. */
+ for (strings = SITEreadfile(false), nSites = 0; strings[nSites]; nSites++)
+ continue;
+ Sites = xcalloc(nSites, sizeof(SITE));
+
+ /* Set up scratch subscription list. */
+ subbed = xmalloc(nGroups);
+ poison = xmalloc(nGroups);
+ /* reset global variables */
+ NeedHeaders = NeedOverview = NeedPath = NeedStoredGroup = NeedReplicdata
+ = false;
+
+ ME.Prev = 0; /* Used as a flag to ensure exactly one ME entry */
+ for (sp = Sites, errors = 0, setuperrors = 0, i = 0; i < nSites; i++) {
+ p = strings[i];
+ if (p[0] == 'M' && p[1] == 'E' &&
+ ((p[2] == NF_FIELD_SEP) || (p[2] == NF_SUBFIELD_SEP))) {
+ if (ME.Prev == NOSITE) {
+ syslog(L_FATAL, "bad_newsfeeds. Must have exactly one ME entry");
+ errors++;
+ } else if ((error = SITEparseone(p, &ME, subbed, poison)) != NULL) {
+ syslog(L_FATAL, "%s bad_newsfeeds %s", MaxLength(p, p), error);
+ errors++;
+ }
+ continue;
+ }
+ if ((error = SITEparseone(p, sp, subbed, poison)) != NULL) {
+ syslog(L_FATAL, "%s bad_newsfeeds %s", MaxLength(p, p), error);
+ errors++;
+ continue;
+ }
+ if (StartSite && !SITEsetup(sp)) {
+ syslog(L_FATAL, "%s cant setup %m", sp->Name);
+ setuperrors++;
+ continue;
+ }
+ sp->Working = true;
+ sp++;
+ }
+ if (ME.Prev != NOSITE) {
+ syslog(L_FATAL, "bad_newsfeeds. Must have exactly one ME entry");
+ errors++;
+ }
+
+ if (errors || setuperrors) {
+ if (errors)
+ syslog(L_FATAL, "%s syntax_error %s", LogName, SITEfeedspath);
+ if (setuperrors)
+ syslog(L_FATAL, "%s setup_error %s", LogName, SITEfeedspath);
+ JustCleanup();
+ exit(1);
+ }
+
+ /* Free our scratch array, set up the funnel links. */
+ nSites = sp - Sites;
+ free(subbed);
+ free(poison);
+ free(strings);
+ if (!SITEfunnelpatch()) {
+ JustCleanup();
+ exit(1);
+ }
+}
--- /dev/null
+/* $Id: ng.c 6494 2003-10-20 01:12:50Z rra $
+**
+** Routine for the in-core data structures for the active and newsfeeds
+** files.
+*/
+
+#include "config.h"
+#include "clibrary.h"
+#include <dirent.h>
+
+#include "inn/innconf.h"
+#include "innd.h"
+#include "ov.h"
+
+
+/*
+** Hash function taken from Chris Torek's hash package posted to
+** comp.lang.c on 18-Oct-90 in <27038@mimsy.umd.edu>. Thanks, Chris.
+*/
+#define NGH_HASH(Name, p, j) \
+ for (p = Name, j = 0; *p; ) j = (j << 5) + j + *p++
+
+
+/*
+** Size of hash table. Change NGH_BUCKET if not a power of two.
+*/
+#define NGH_SIZE 2048
+#define NGH_BUCKET(j) &NGHtable[j & (NGH_SIZE - 1)]
+
+
+/*
+** Newsgroup hash entry, which is really a hash bucket -- pointers
+** to all the groups with this hash code.
+*/
+typedef struct _NGHASH {
+ int Size;
+ int Used;
+ NEWSGROUP **Groups;
+} NGHASH;
+
+
+static struct buffer NGnames;
+static NGHASH NGHtable[NGH_SIZE];
+static int NGHbuckets;
+static int NGHcount;
+
+\f
+
+/*
+** Sorting predicate for qsort call in NGparsefile. Put newsgroups in
+** rough order of their activity. Will be better if we write a "counts"
+** file sometime.
+*/
+static int
+NGcompare(const void *p1, const void *p2)
+{
+ return ((const NEWSGROUP **)p1)[0]->Last -
+ ((const NEWSGROUP **)p2)[0]->Last;
+}
+
+
+/*
+** Parse a single line from the active file, filling in ngp. Be careful
+** not to write NUL's into the in-core copy, since we're either mmap(2)'d,
+** or we want to just blat it out to disk later.
+*/
+static bool
+NGparseentry(NEWSGROUP *ngp, const char *p, char *end)
+{
+ char *q;
+ unsigned int j;
+ NGHASH *htp;
+ NEWSGROUP **ngpp;
+ int i;
+ ARTNUM lo;
+
+ if ((q = strchr(p, ' ')) == NULL)
+ return false;
+ i = q - p;
+
+ ngp->NameLength = i;
+ ngp->Name = &NGnames.data[NGnames.used];
+ strncpy(ngp->Name, p, i);
+ ngp->Name[i] = '\0';
+ NGnames.used += i + 1;
+
+ ngp->LastString = ++q;
+ if ((q = strchr(q, ' ')) == NULL || q > end)
+ return false;
+ ngp->Lastwidth = q - ngp->LastString;
+ if ((q = strchr(q, ' ')) == NULL || q > end)
+ return false;
+ lo = (ARTNUM)atol(q + 1);
+ if ((q = strchr(q + 1, ' ')) == NULL || q > end)
+ return false;
+ ngp->Rest = ++q;
+ /* We count on atoi() to stop at the space after the digits! */
+ ngp->Last = atol(ngp->LastString);
+ ngp->nSites = 0;
+ ngp->Sites = xmalloc(NGHcount * sizeof(int));
+ ngp->nPoison = 0;
+ ngp->Poison = xmalloc(NGHcount * sizeof(int));
+ ngp->Alias = NULL;
+
+ /* Find the right bucket for the group, make sure there is room. */
+ NGH_HASH(ngp->Name, p, j);
+ htp = NGH_BUCKET(j);
+ for (p = ngp->Name, ngpp = htp->Groups, i = htp->Used; --i >= 0; ngpp++)
+ if (*p == ngpp[0]->Name[0] && strcmp(p, ngpp[0]->Name) == 0) {
+ syslog(L_ERROR, "%s duplicate_group %s", LogName, p);
+ return false;
+ }
+ if (htp->Used >= htp->Size) {
+ htp->Size += NGHbuckets;
+ htp->Groups = xrealloc(htp->Groups, htp->Size * sizeof(NEWSGROUP *));
+ }
+ htp->Groups[htp->Used++] = ngp;
+
+ if (innconf->enableoverview && !OVgroupadd(ngp->Name, lo, ngp->Last, ngp->Rest))
+ return false;
+
+ return true;
+}
+
+
+/*
+** Parse the active file, building the initial Groups global.
+*/
+void
+NGparsefile(void)
+{
+ char *p;
+ char *q;
+ int i;
+ bool SawMe;
+ NEWSGROUP *ngp;
+ NGHASH *htp;
+ char **strings;
+ char *active;
+ char *end;
+
+ /* If re-reading, remove anything we might have had. */
+ NGclose();
+
+ /* Get active file and space for group entries. */
+ active = ICDreadactive(&end);
+ for (p = active, i = 0; p < end; p++)
+ if (*p == '\n') i++;
+ if ((nGroups = i) == 0) {
+ syslog(L_FATAL, "%s empty active file", LogName);
+ exit(1);
+ }
+ Groups = xmalloc(nGroups * sizeof(NEWSGROUP));
+ GroupPointers = xmalloc(nGroups * sizeof(NEWSGROUP *));
+
+ /* Get space to hold copies of the names. This might take more space
+ * than individually allocating each element, but it is definitely easier
+ * on the system. */
+ i = end - active;
+ NGnames.size = i;
+ NGnames.data = xmalloc(NGnames.size + 1);
+ NGnames.used = 0;
+
+ /* Set up the default hash buckets. */
+ NGHbuckets = nGroups / NGH_SIZE;
+ if (NGHbuckets == 0)
+ NGHbuckets = 1;
+ if (NGHtable[0].Groups)
+ for (i = NGH_SIZE, htp = NGHtable; --i >= 0; htp++)
+ htp->Used = 0;
+ else
+ for (i = NGH_SIZE, htp = NGHtable; --i >= 0; htp++) {
+ htp->Size = NGHbuckets;
+ htp->Groups = xmalloc(htp->Size * sizeof(NEWSGROUP *));
+ htp->Used = 0;
+ }
+
+ /* Count the number of sites. */
+ SawMe = false;
+ for (strings = SITEreadfile(true), i = 0; (p = strings[i]) != NULL; i++)
+ if (*p == 'M' && *++p == 'E' && *++p == ':')
+ SawMe = true;
+ if (i == 0 || (i == 1 && SawMe)) {
+ syslog(L_ERROR, "%s bad_newsfeeds no feeding sites", LogName);
+ NGHcount = 1;
+ }
+ else
+ NGHcount = i;
+
+ /* Loop over all lines in the active file, filling in the fields of
+ * the Groups array. */
+ for (p = active, ngp = Groups, i = nGroups; --i >= 0; ngp++, p = q + 1) {
+ ngp->Start = p - active;
+ if ((q = strchr(p, '\n')) == NULL || !NGparseentry(ngp, p, q)) {
+ syslog(L_FATAL, "%s bad_active %s...", LogName, MaxLength(p, q));
+ exit(1);
+ }
+ }
+
+ /* Sort each bucket. */
+ for (i = NGH_SIZE, htp = NGHtable; --i >= 0; htp++)
+ if (htp->Used > 1)
+ qsort(htp->Groups, htp->Used, sizeof htp->Groups[0], NGcompare);
+
+ /* Chase down any alias flags. */
+ for (ngp = Groups, i = nGroups; --i >= 0; ngp++)
+ if (ngp->Rest[0] == NF_FLAG_ALIAS) {
+ ngp->Alias = ngp;
+ if ((p = strchr(ngp->Alias->Rest, '\n')) != NULL)
+ *p = '\0';
+ ngp->Alias = NGfind(&ngp->Alias->Rest[1]);
+ if (p)
+ *p = '\n';
+ if (ngp->Alias != NULL && ngp->Alias->Rest[0] == NF_FLAG_ALIAS)
+ syslog(L_NOTICE, "%s alias_error %s too many levels",
+ LogName, ngp->Name);
+ }
+}
+
+/*
+** Free allocated memory
+*/
+void
+NGclose(void)
+{
+ int i;
+ NEWSGROUP *ngp;
+ NGHASH *htp;
+
+ if (Groups) {
+ for (i = nGroups, ngp = Groups; --i >= 0; ngp++) {
+ free(ngp->Sites);
+ free(ngp->Poison);
+ }
+ free(Groups);
+ Groups = NULL;
+ free(GroupPointers);
+ free(NGnames.data);
+ }
+
+ for (i = NGH_SIZE, htp = NGHtable; --i >= 0; htp++) {
+ htp->Size = NGHbuckets;
+ if (htp->Groups) {
+ free(htp->Groups);
+ htp->Used = 0;
+ htp->Groups = NULL;
+ }
+ }
+}
+
+/*
+** Hash a newsgroup and see if we get it.
+*/
+NEWSGROUP *
+NGfind(const char *Name)
+{
+ const char *p;
+ int i;
+ unsigned int j;
+ NEWSGROUP **ngp;
+ char c;
+ NGHASH *htp;
+
+ NGH_HASH(Name, p, j);
+ htp = NGH_BUCKET(j);
+ for (c = *Name, ngp = htp->Groups, i = htp->Used; --i >= 0; ngp++)
+ if (c == ngp[0]->Name[0] && strcmp(Name, ngp[0]->Name) == 0)
+ return ngp[0];
+ return NULL;
+}
+
+
+/*
+** Split a newsgroups header line into the groups we get. Return the
+** number of newsgroups. ' ' and '\t' are dropped when copying.
+*/
+int
+NGsplit(char *p, int size, LISTBUFFER *list)
+{
+ char **gp, *q;
+ int i;
+
+ /* setup buffer */
+ SetupListBuffer(size, list);
+
+ /* loop over and copy */
+ for (i = 0, q = list->Data, gp = list->List ; *p ; p++, *q++ = '\0') {
+ /* skip leading separators and white spaces. */
+ for (; *p && (NG_ISSEP(*p) || ISWHITE(*p)) ; p++)
+ continue;
+ if (*p == '\0')
+ break;
+
+ if (i == list->ListLength) {
+ list->ListLength += DEFAULTNGBOXSIZE;
+ list->List = xrealloc(list->List, list->ListLength * sizeof(char *));
+ gp = &list->List[i];
+ }
+ /* mark the start of the newsgroup, move to the end of it while copying */
+ for (*gp++ = q, i++ ; *p && !NG_ISSEP(*p) && !ISWHITE(*p) ;) {
+ if (*p == ':')
+ /* reject if ':' is included */
+ return 0;
+ *q++ = *p++;
+ continue;
+ }
+ if (*p == '\0')
+ break;
+ }
+ *q = '\0';
+ if (i == list->ListLength) {
+ list->ListLength += DEFAULTNGBOXSIZE;
+ list->List = xrealloc(list->List, list->ListLength * sizeof(char *));
+ gp = &list->List[i];
+ }
+ *gp = NULL;
+ return i;
+}
+
+
+/*
+** Renumber a group.
+*/
+static char NORENUMBER[] = "%s cant renumber %s %s too wide";
+static char RENUMBER[] = "%s renumber %s %s from %ld to %ld";
+
+bool
+NGrenumber(NEWSGROUP *ngp)
+{
+ int low, high, count,flag;
+ char *f2;
+ char *f3;
+ char *f4;
+ char *start;
+ long lomark, himark;
+ long l;
+ char *dummy;
+
+ if (!innconf->enableoverview) return true; /* can't do anything w/o overview */
+
+ /* Get a valid offset into the active file. */
+ if (ICDneedsetup) {
+ syslog(L_ERROR, "%s unsynched must reload before renumber", LogName);
+ return false;
+ }
+ start = ICDreadactive(&dummy) + ngp->Start;
+
+ /* Check the file format. */
+ if ((f2 = strchr(start, ' ')) == NULL
+ || (f3 = strchr(++f2, ' ')) == NULL
+ || (f4 = strchr(++f3, ' ')) == NULL) {
+ syslog(L_ERROR, "%s bad_format active %s",
+ LogName, MaxLength(start, start));
+ return false;
+ }
+ himark = atol(f2);
+ lomark = himark + 1;
+ /* note these will be the low and himarks if the group turns out to be empty. */
+
+ /* Check overview data for the group. */
+ if (!OVgroupstats(ngp->Name, &low, &high, &count, &flag)) return false;
+ if (count != 0) {
+ /* non-empty group, so set low/himarks from overview. */
+ lomark = low;
+ himark = high;
+ }
+ l = atol(f2);
+ if (himark > l) {
+ syslog(L_NOTICE, RENUMBER, LogName, ngp->Name, "hi", l, himark);
+ if (!FormatLong(f2, himark, f3 - f2 - 1)) {
+ syslog(L_ERROR, NORENUMBER, LogName, ngp->Name, "hi");
+ return false;
+ }
+ ngp->Last = himark;
+ ICDactivedirty++;
+ } else if (himark < l) {
+ syslog(L_NOTICE, "%s renumber %s hi not decreasing %ld to %ld",
+ LogName, ngp->Name, l, himark);
+ }
+ l = atol(f3);
+ if (lomark != l) {
+ if (lomark < l)
+ syslog(L_NOTICE, RENUMBER, LogName, ngp->Name, "lo", l, lomark);
+ if (!FormatLong(f3, lomark, f4 - f3)) {
+ syslog(L_ERROR, NORENUMBER, LogName, ngp->Name, "lo");
+ return false;
+ }
+ ICDactivedirty++;
+ }
+ return true;
+}
+
+/*
+ * Set the low article count for the given group.
+ * Like NGrenumber(), but we don't scan the spool,
+ * and the himark is ignored.
+ */
+bool
+NGlowmark(NEWSGROUP *ngp, long lomark)
+{
+ long l;
+ char *f2, *f3, *f4;
+ char *start;
+
+ start = ICDreadactive(&f2) + ngp->Start;
+ /* Check the file format. */
+ if ((f2 = strchr(start, ' ')) == NULL
+ || (f3 = strchr(++f2, ' ')) == NULL
+ || (f4 = strchr(++f3, ' ')) == NULL) {
+ syslog(L_ERROR, "%s bad_format active %s",
+ LogName, MaxLength(start, start));
+ return false;
+ }
+ l = atol(f3);
+ if (lomark != l) {
+ if (lomark < l)
+ syslog(L_NOTICE, RENUMBER, LogName, ngp->Name, "lo", l, lomark);
+ if (!FormatLong(f3, lomark, f4 - f3)) {
+ syslog(L_ERROR, NORENUMBER, LogName, ngp->Name, "lo");
+ return false;
+ }
+ ICDactivedirty++;
+ }
+ return true;
+}
--- /dev/null
+/* $Id: perl.c 7815 2008-05-05 08:43:58Z iulius $
+**
+** Perl filtering support for innd.
+**
+** Originally written by Christophe Wolfhugel <wolf@pasteur.fr> (although
+** he wouldn't recognise it anymore so don't blame him) and modified,
+** expanded and tweaked since by James Brister, Jeremy Nixon, Ed Mooring,
+** Russell Vincent, and Russ Allbery.
+**
+** This file should contain all innd-specific Perl linkage. Linkage
+** applicable to both innd and nnrpd should go into lib/perl.c instead.
+**
+** We are assuming Perl 5.004 or later.
+**
+** Future work:
+**
+** - What we're doing with Path headers right now doesn't work for folded
+** headers. It's also kind of gross. There has to be a better way of
+** handling this.
+**
+** - The breakdown between this file, lib/perl.c, and nnrpd/perl.c should
+** be rethought, ideally in the light of supporting multiple filters in
+** different languages.
+**
+** - We're still calling strlen() on artBody, which should be avoidable
+** since we've already walked it several times. We should just cache
+** the length somewhere for speed.
+**
+** - Variable and key names should be standardized between this and nnrpd.
+**
+** - The XS code is still calling CC* functions. The common code between
+** the two control interfaces should be factored out into the rest of
+** innd instead.
+**
+** - There's a needless perl_get_cv() call for *every message ID* offered
+** to the server right now. We need to stash whether that filter is
+** active.
+*/
+
+#include "config.h"
+
+/* Skip this entire file if DO_PERL (./configure --with-perl) isn't set. */
+#if DO_PERL
+
+#include "clibrary.h"
+#include "innd.h"
+
+#include <EXTERN.h>
+#include <perl.h>
+#include <XSUB.h>
+#include "ppport.h"
+
+#include "innperl.h"
+
+/* From art.c. Ew. Need header parsing that doesn't use globals. */
+extern char *filterPath;
+
+/*
+** Run an incoming article through the Perl article filter. Returns NULL
+** accept the article or a rejection message to reject it.
+*/
+char *
+PLartfilter(const ARTDATA *data, char *artBody, long artLen, int lines)
+{
+ dSP;
+ const ARTHEADER * hp;
+ const HDRCONTENT *hc = data->HdrContent;
+ HV * hdr;
+ CV * filter;
+ int i, rc;
+ char * p;
+ static SV * body = NULL;
+ static char buf[256];
+
+ if (!PerlFilterActive) return NULL;
+ filter = perl_get_cv("filter_art", 0);
+ if (!filter) return NULL;
+
+ /* Create %hdr and stash a copy of every known header. Path has to be
+ handled separately since it's been munged by article processing. */
+ hdr = perl_get_hv("hdr", 1);
+ for (i = 0 ; i < MAX_ARTHEADER ; i++) {
+ if (HDR_FOUND(i)) {
+ hp = &ARTheaders[i];
+ hv_store(hdr, (char *) hp->Name, hp->Size, newSVpv(HDR(i), 0), 0);
+ }
+ }
+
+ /* Store the article body. We don't want to make another copy of it,
+ since it could potentially be quite large. Instead, stash the
+ pointer in the static SV * body. We set LEN to 0 and inc the
+ refcount to tell Perl not to free it (either one should be enough).
+ Requires 5.004. In testing, this produced a 17% speed improvement
+ over making a copy of the article body for a fairly heavy filter. */
+ if (artBody) {
+ if (!body) {
+ body = newSV(0);
+ (void) SvUPGRADE(body, SVt_PV);
+ }
+ SvPVX(body) = artBody;
+ SvCUR_set(body, artLen);
+ SvLEN_set(body, 0);
+ SvPOK_on(body);
+ (void) SvREADONLY_on(body);
+ (void) SvREFCNT_inc(body);
+ hv_store(hdr, "__BODY__", 8, body, 0);
+ }
+
+ hv_store(hdr, "__LINES__", 9, newSViv(lines), 0);
+
+ ENTER;
+ SAVETMPS;
+ PUSHMARK(SP);
+ rc = perl_call_sv((SV *) filter, G_EVAL|G_SCALAR|G_NOARGS);
+ SPAGAIN;
+
+ hv_undef(hdr);
+
+ /* Check $@, which will be set if the sub died. */
+ buf[0] = '\0';
+ if (SvTRUE(ERRSV)) {
+ syslog(L_ERROR, "Perl function filter_art died on article %s: %s",
+ HDR_FOUND(HDR__MESSAGE_ID) ? HDR(HDR__MESSAGE_ID) : "?",
+ SvPV(ERRSV, PL_na));
+ (void) POPs;
+ PerlFilter(false);
+ } else if (rc == 1) {
+ p = POPp;
+ if (p && *p)
+ strlcpy(buf, p, sizeof(buf));
+ }
+
+ PUTBACK;
+ FREETMPS;
+ LEAVE;
+ return (buf[0] != '\0') ? buf : NULL;
+}
+
+
+/*
+** Run an incoming message ID from CHECK or IHAVE through the Perl filter.
+** Returns NULL to accept the article or a rejection message to reject it.
+*/
+char *
+PLmidfilter(char *messageID)
+{
+ dSP;
+ CV *filter;
+ int rc;
+ char *p;
+ static char buf[256];
+
+ if (!PerlFilterActive) return NULL;
+ filter = perl_get_cv("filter_messageid", 0);
+ if (!filter) return NULL;
+
+ /* Pass filter_messageid() the message ID on the Perl stack. */
+ ENTER;
+ SAVETMPS;
+ PUSHMARK(SP);
+ XPUSHs(sv_2mortal(newSVpv(messageID, 0)));
+ PUTBACK;
+ rc = perl_call_sv((SV *) filter, G_EVAL|G_SCALAR);
+ SPAGAIN;
+
+ /* Check $@, which will be set if the sub died. */
+ buf[0] = '\0';
+ if (SvTRUE(ERRSV)) {
+ syslog(L_ERROR, "Perl function filter_messageid died on id %s: %s",
+ messageID, SvPV(ERRSV, PL_na));
+ (void) POPs;
+ PerlFilter(false);
+ } else if (rc == 1) {
+ p = POPp;
+ if (p && *p)
+ strlcpy(buf, p, sizeof(buf));
+ }
+
+ PUTBACK;
+ FREETMPS;
+ LEAVE;
+ return (buf[0] != '\0') ? buf : NULL;
+}
+
+
+/*
+** Call a Perl sub on any change in INN's mode, passing in the old and new
+** mode and the reason.
+*/
+void
+PLmode(OPERATINGMODE Mode, OPERATINGMODE NewMode, char *reason)
+{
+ dSP;
+ HV *mode;
+ CV *filter;
+
+ filter = perl_get_cv("filter_mode", 0);
+ if (!filter) return;
+
+ /* Current mode goes into $mode{Mode}, new mode in $mode{NewMode}, and
+ the reason in $mode{reason}. */
+ mode = perl_get_hv("mode", 1);
+
+ if (Mode == OMrunning)
+ hv_store(mode, "Mode", 4, newSVpv("running", 0), 0);
+ if (Mode == OMpaused)
+ hv_store(mode, "Mode", 4, newSVpv("paused", 0), 0);
+ if (Mode == OMthrottled)
+ hv_store(mode, "Mode", 4, newSVpv("throttled", 0), 0);
+
+ if (NewMode == OMrunning)
+ hv_store(mode, "NewMode", 7, newSVpv("running", 0), 0);
+ if (NewMode == OMpaused)
+ hv_store(mode, "NewMode", 7, newSVpv("paused", 0), 0);
+ if (NewMode == OMthrottled)
+ hv_store(mode, "NewMode", 7, newSVpv("throttled", 0), 0);
+
+ hv_store(mode, "reason", 6, newSVpv(reason, 0), 0);
+
+ PUSHMARK(SP);
+ perl_call_sv((SV *) filter, G_EVAL|G_DISCARD|G_NOARGS);
+
+ /* Check $@, which will be set if the sub died. */
+ if (SvTRUE(ERRSV)) {
+ syslog(L_ERROR, "Perl function filter_mode died: %s",
+ SvPV(ERRSV, PL_na));
+ (void) POPs;
+ PerlFilter(false);
+ }
+}
+
+
+/*
+** Called by CCmode, this returns the Perl filter statistics if a Perl
+** function to generate such statistics has been defined, or NULL otherwise.
+** If a string is returned, it's in newly allocated memory that must be freed
+** by the caller.
+*/
+char *
+PLstats(void)
+{
+ dSP;
+ char *argv[] = { NULL };
+
+ if (perl_get_cv("filter_stats", false) == NULL)
+ return NULL;
+ else {
+ char *stats = NULL;
+ char *result;
+
+ ENTER;
+ SAVETMPS;
+ perl_call_argv("filter_stats", G_EVAL | G_NOARGS, argv);
+ SPAGAIN;
+ result = POPp;
+ if (result != NULL && *result)
+ stats = xstrdup(result);
+ PUTBACK;
+ FREETMPS;
+ LEAVE;
+
+ return stats;
+ }
+}
+
+
+/*
+** The remainder of this file are XS callbacks visible to embedded Perl
+** code to perform various innd functions. They were originally written by
+** Ed Mooring (mooring@acm.org) on May 14, 1998, and have since been split
+** between this file and lib/perl.c (which has the ones that can also be
+** used in nnrpd). The function that registers them at startup is at the
+** end.
+*/
+
+/*
+** Add an entry to history. Takes message ID and optionally arrival,
+** article, and expire times and storage API token. If the times aren't
+** given, they default to now. If the token isn't given, that field will
+** be left empty. Returns boolean success.
+*/
+XS(XS_INN_addhist)
+{
+ dXSARGS;
+ int i;
+ char tbuff[32];
+ char* parambuf[6];
+
+ if (items < 1 || items > 5)
+ croak("Usage INN::addhist(msgid,[arrival,articletime,expire,token])");
+
+ for (i = 0; i < items; i++)
+ parambuf[i] = (char *) SvPV(ST(0), PL_na);
+
+ /* If any of the times are missing, they should default to now. */
+ if (i < 4) {
+ snprintf(tbuff, sizeof(tbuff), "%ld", (long) time(NULL));
+ for (; i < 4; i++)
+ parambuf[i] = tbuff;
+ }
+
+ /* The token defaults to an empty string. */
+ if (i == 4)
+ parambuf[4] = "";
+
+ parambuf[5] = NULL;
+
+ /* CCaddhist returns NULL on success. */
+ if (CCaddhist(parambuf))
+ XSRETURN_NO;
+ else
+ XSRETURN_YES;
+}
+
+
+/*
+** Takes the message ID of an article and returns the full article as a
+** string or undef if the article wasn't found. It will be converted from
+** wire format to native format. Note that this call isn't particularly
+** optimized or cheap.
+*/
+XS(XS_INN_article)
+{
+ dXSARGS;
+ char * msgid;
+ TOKEN token;
+ ARTHANDLE * art;
+ char * p;
+ size_t len;
+
+ if (items != 1)
+ croak("Usage: INN::article(msgid)");
+
+ /* Get the article token from the message ID and the history file. */
+ msgid = (char *) SvPV(ST(0), PL_na);
+ if (!HISlookup(History, msgid, NULL, NULL, NULL, &token)) XSRETURN_UNDEF;
+
+ /* Retrieve the article and convert it from wire format. */
+ art = SMretrieve(token, RETR_ALL);
+ if (art == NULL) XSRETURN_UNDEF;
+ p = FromWireFmt(art->data, art->len, &len);
+ SMfreearticle(art);
+
+ /* Push a copy of the article onto the Perl stack, free our temporary
+ memory allocation, and return the article to Perl. */
+ ST(0) = sv_2mortal(newSVpv(p, len));
+ free(p);
+ XSRETURN(1);
+}
+
+
+/*
+** Cancel a message by message ID; returns boolean success. Equivalent to
+** ctlinnd cancel <message>.
+*/
+XS(XS_INN_cancel)
+{
+ dXSARGS;
+ char *msgid;
+ char *parambuf[2];
+
+ if (items != 1)
+ croak("Usage: INN::cancel(msgid)");
+
+ msgid = (char *) SvPV(ST(0), PL_na);
+ parambuf[0] = msgid;
+ parambuf[1] = NULL;
+
+ /* CCcancel returns NULL on success. */
+ if (CCcancel(parambuf))
+ XSRETURN_NO;
+ else
+ XSRETURN_YES;
+}
+
+
+/*
+** Return the files for a given message ID, taken from the history file.
+** This function should really be named INN::token() and probably will be
+** some day.
+*/
+XS(XS_INN_filesfor)
+{
+ dXSARGS;
+ char *msgid;
+ TOKEN token;
+
+ if (items != 1)
+ croak("Usage: INN::filesfor(msgid)");
+
+ msgid = (char *) SvPV(ST(0), PL_na);
+ if (HISlookup(History, msgid, NULL, NULL, NULL, &token)) {
+ XSRETURN_PV(TokenToText(token));
+ } else {
+ XSRETURN_UNDEF;
+ }
+}
+
+
+/*
+** Whether message ID is in the history file; returns boolean.
+*/
+XS(XS_INN_havehist)
+{
+ dXSARGS;
+ char *msgid;
+
+ if (items != 1)
+ croak("Usage: INN::havehist(msgid)");
+
+ msgid = (char *) SvPV(ST(0), PL_na);
+ if (HIScheck(History, msgid))
+ XSRETURN_YES;
+ else
+ XSRETURN_NO;
+}
+
+
+/*
+** Takes the message ID of an article and returns the article headers as
+** a string or undef if the article wasn't found. Each line of the header
+** will end with \n.
+*/
+XS(XS_INN_head)
+{
+ dXSARGS;
+ char * msgid;
+ TOKEN token;
+ ARTHANDLE * art;
+ char * p;
+ size_t len;
+
+ if (items != 1)
+ croak("Usage: INN::head(msgid)");
+
+ /* Get the article token from the message ID and the history file. */
+ msgid = (char *) SvPV(ST(0), PL_na);
+ if (!HISlookup(History, msgid, NULL, NULL, NULL, &token)) XSRETURN_UNDEF;
+
+ /* Retrieve the article header and convert it from wire format. */
+ art = SMretrieve(token, RETR_HEAD);
+ if (art == NULL) XSRETURN_UNDEF;
+ p = FromWireFmt(art->data, art->len, &len);
+ SMfreearticle(art);
+
+ /* Push a copy of the article header onto the Perl stack, free our
+ temporary memory allocation, and return the header to Perl. */
+ ST(0) = sv_2mortal(newSVpv(p, len));
+ free(p);
+ XSRETURN(1);
+}
+
+
+/*
+** Returns the active file flag for a newsgroup or undef if it isn't in the
+** active file.
+*/
+XS(XS_INN_newsgroup)
+{
+ dXSARGS;
+ char * newsgroup;
+ NEWSGROUP * ngp;
+ char * end;
+ int size;
+
+ if (items != 1)
+ croak("Usage: INN::newsgroup(group)");
+ newsgroup = (char *) SvPV(ST(0), PL_na);
+
+ ngp = NGfind(newsgroup);
+ if (!ngp) {
+ XSRETURN_UNDEF;
+ } else {
+ /* ngp->Rest is newline-terminated; find the end. */
+ end = strchr(ngp->Rest, '\n');
+ if (end == NULL) {
+ size = strlen(ngp->Rest);
+ } else {
+ size = end - ngp->Rest;
+ }
+ ST(0) = sv_2mortal(newSVpv(ngp->Rest, size));
+ XSRETURN(1);
+ }
+}
+
+
+/*
+** Initialize the XS callbacks defined in this file.
+*/
+void
+PLxsinit(void)
+{
+ newXS("INN::addhist", XS_INN_addhist, "perl.c");
+ newXS("INN::article", XS_INN_article, "perl.c");
+ newXS("INN::cancel", XS_INN_cancel, "perl.c");
+ newXS("INN::havehist", XS_INN_havehist, "perl.c");
+ newXS("INN::head", XS_INN_head, "perl.c");
+ newXS("INN::newsgroup", XS_INN_newsgroup, "perl.c");
+ newXS("INN::filesfor", XS_INN_filesfor, "perl.c");
+}
+
+#endif /* defined(DO_PERL) */
--- /dev/null
+/* $Id: proc.c 6124 2003-01-14 06:03:29Z rra $
+**
+** Process control routines.
+*/
+
+#include "config.h"
+#include "clibrary.h"
+#include "portable/wait.h"
+
+#include "innd.h"
+
+
+static PROCESS *PROCtable;
+static int PROCtablesize;
+static PROCESS PROCnull = { PSfree, 0, 0, 0, 0, 0 };
+
+
+/*
+** Collect dead processes.
+*/
+static void
+PROCreap(void)
+{
+ int status;
+ PROCESS *pp;
+ int i;
+ pid_t pid;
+
+ for ( ; ; ) {
+ pid = waitpid(-1, &status, WNOHANG);
+ if (pid == 0)
+ break;
+ if (pid < 0) {
+ if (errno != ECHILD)
+ syslog(L_ERROR, "%s cant wait %m", LogName);
+ break;
+ }
+ for (pp = PROCtable, i = PROCtablesize; --i >= 0; pp++)
+ if (pp->Pid == pid) {
+ PROCneedscan = true;
+ pp->Status = WEXITSTATUS(status);
+ pp->State = PSdead;
+ pp->Collected = Now.time;
+ break;
+ }
+ }
+}
+
+
+/*
+** Signal handler that collects the processes, then resets the signal.
+*/
+static RETSIGTYPE
+PROCcatchsignal(int s)
+{
+ PROCreap();
+
+#ifndef HAVE_SIGACTION
+ xsignal(s, PROCcatchsignal);
+#else
+ s = s; /* ARGSUSED */
+#endif
+}
+
+
+/*
+** Synchronous version that notifies a site when its process went away.
+*/
+void
+PROCscan(void)
+{
+ PROCESS *pp;
+ int i;
+
+ for (pp = PROCtable, i = PROCtablesize; --i >= 0; pp++)
+ if (pp->State == PSdead) {
+ if (pp->Site >= 0)
+ SITEprocdied(&Sites[pp->Site], pp - PROCtable, pp);
+ pp->State = PSfree;
+ }
+ PROCneedscan = false;
+}
+
+
+#if 0
+/*
+** Close down all processes.
+*/
+void
+PROCclose(Quickly)
+ bool Quickly;
+{
+ int sig;
+ PROCESS *pp;
+ int i;
+
+ /* What signal are we sending? */
+ sig = Quickly ? SIGKILL : SIGTERM;
+
+ /* Send the signal to all living processes. */
+ for (pp = PROCtable, i = PROCtablesize; --i >= 0; pp++) {
+ if (pp->State != PSrunning)
+ continue;
+ if (kill(pp->Pid, sig) < 0 && errno != ESRCH)
+ syslog(L_ERROR, "%s cant kill %s %ld %m",
+ LogName, Quickly ? "KILL" : "TERM", (long) pp->Pid);
+ }
+
+ /* Collect any who might have died. */
+ PROCreap();
+ for (pp = PROCtable, i = PROCtablesize; --i >= 0; pp++)
+ if (pp->State == PSdead)
+ *pp = PROCnull;
+}
+#endif /* 0 */
+
+
+/*
+** Stop watching a process -- we don't care about it any more.
+*/
+void
+PROCunwatch(int process)
+{
+ if (process < 0 || process >= PROCtablesize) {
+ syslog(L_ERROR, "%s internal PROCunwatch %d", LogName, process);
+ return;
+ }
+ PROCtable[process].Site = -1;
+}
+
+
+/*
+** Add a pid to the list of processes we watch.
+*/
+int
+PROCwatch(pid_t pid, int site)
+{
+ PROCESS *pp;
+ int i;
+
+ /* Find a free slot for this process. */
+ for (pp = PROCtable, i = PROCtablesize; --i >= 0; pp++)
+ if (pp->State == PSfree)
+ break;
+ if (i < 0) {
+ /* Ran out of room -- grow the table. */
+ PROCtable = xrealloc(PROCtable, (PROCtablesize + 20) * sizeof(PROCESS));
+ for (pp = &PROCtable[PROCtablesize], i=20; --i >= 0; pp++)
+ *pp = PROCnull;
+ pp = &PROCtable[PROCtablesize];
+ PROCtablesize += 20;
+ }
+
+ pp->State = PSrunning;
+ pp->Pid = pid;
+ pp->Started = Now.time;
+ pp->Site = site;
+ return pp - PROCtable;
+}
+
+
+/*
+** Setup.
+*/
+void
+PROCsetup(int i)
+{
+ PROCESS *pp;
+
+ if (PROCtable)
+ free(PROCtable);
+ PROCtablesize = i;
+ PROCtable = xmalloc(PROCtablesize * sizeof(PROCESS));
+ for (pp = PROCtable, i = PROCtablesize; --i >= 0; pp++)
+ *pp = PROCnull;
+
+#if defined(SIGCHLD)
+ xsignal(SIGCHLD, PROCcatchsignal);
+#endif /* defined(SIGCHLD) */
+ xsignal(SIGPIPE, PROCcatchsignal);
+}
--- /dev/null
+/* $Id: python.c 7891 2008-06-22 09:59:11Z iulius $
+**
+** python.c: Embed Python in the style of innd's TCL and Perl stuff.
+**
+** Written by G.J. Andruk <meowing@banet.net> patterned after
+** TCL/Perl work by Bob Heiney and Christophe Wolfhugel and a whole
+** bunch of other people mentioned in the docs and sources for the
+** other filters.
+**
+** The astute reader may notice the commission of blatant atrocities
+** against Python's OO model here. Don't tell Guido.
+**
+** A quick note regarding Python exceptions: functions like
+** PyObject_GetAttrString(PyObject *o, const char *attr_name)
+** raise an exception when they fail, even though they return NULL.
+** And as exceptions accumulate from caller to caller and so on,
+** it generates weird issues with Python scripts afterwards. So such
+** uses should be checked before. For instance with:
+** PyObject_HasAttrString(PyObject *o, const char *attr_name).
+*/
+
+#include "config.h"
+#include "clibrary.h"
+
+#include "inn/innconf.h"
+#include "innd.h"
+
+
+#if defined(DO_PYTHON)
+
+#include "Python.h"
+
+
+bool PythonFilterActive;
+char *filterPath; /* this gets set in art.c */
+PyObject *PYFilterObject = NULL;
+PyObject *PYFilterModule = NULL;
+
+/* article filter bits and pieces */
+PyObject *PYheaders = NULL;
+PyObject **PYheaditem;
+PyObject **PYheadkey;
+PyObject *PYpathkey, *PYlineskey, *PYbodykey;
+
+/* external functions */
+PyObject *msgid_method = NULL;
+PyObject *art_method = NULL;
+PyObject *mode_method = NULL;
+PyObject *pre_reload_method = NULL;
+PyObject *close_method = NULL;
+
+
+
+/*
+** Turn filtering on or off.
+*/
+void
+PYfilter(value)
+ bool value;
+{
+ PythonFilterActive = value;
+ syslog(L_NOTICE, "%s Python filtering %s", LogName,
+ PythonFilterActive ? "enabled" : "disabled");
+}
+
+
+
+/*
+** Front end for PYfilter()
+*/
+const char *
+PYcontrol(char **av)
+{
+ char *p;
+ extern bool PythonFilterActive;
+
+ switch (av[0][0]) {
+ default:
+ return "1 Bad flag";
+ case 'y':
+ if (PythonFilterActive)
+ return "1 Python filter already enabled";
+ else if (PYFilterObject == NULL)
+ return "1 Python filter not defined" ;
+ PYfilter(true);
+ break;
+ case 'n':
+ if (!PythonFilterActive)
+ return "1 Python filter already disabled";
+ PYfilter(false);
+ break;
+ }
+ return NULL;
+}
+
+
+
+
+/*
+** Reject articles we don't like.
+*/
+char *
+PYartfilter(const ARTDATA *data, char *artBody, long artLen, int lines)
+{
+ const ARTHEADER *hp;
+ const HDRCONTENT *hc = data->HdrContent;
+ int hdrnum;
+ int i;
+ char *p, save;
+ static char buf[256];
+ PyObject *result;
+
+ if (!PythonFilterActive || PYFilterObject == NULL || art_method == NULL)
+ return NULL;
+
+ /* Add headers to the dictionary... */
+ hdrnum = 0;
+ for (i = 0 ; i < MAX_ARTHEADER ; i++) {
+ if (HDR_FOUND(i)) {
+ hp = &ARTheaders[i];
+ PYheaditem[hdrnum] = PyBuffer_FromMemory(HDR(i), HDR_LEN(i));
+ } else
+ PYheaditem[hdrnum] = Py_None;
+ PyDict_SetItem(PYheaders, PYheadkey[hdrnum], PYheaditem[hdrnum]);
+ hdrnum++;
+ }
+
+ /* ...then the body... */
+ if (artLen && artBody != NULL)
+ PYheaditem[hdrnum] = PyBuffer_FromMemory(artBody, --artLen);
+ else
+ PYheaditem[hdrnum] = Py_None;
+ PyDict_SetItem(PYheaders, PYbodykey, PYheaditem[hdrnum++]);
+
+ /* ...and finally, the line count. */
+ PYheaditem[hdrnum] = PyInt_FromLong((long) lines);
+ PyDict_SetItem(PYheaders, PYlineskey, PYheaditem[hdrnum++]);
+
+ /* Now see if the filter likes it. */
+ result = PyObject_CallFunction(art_method, "O", PYheaders);
+ if ((result != NULL) && PyObject_IsTrue(result))
+ strlcpy(buf, PyString_AS_STRING(result), sizeof(buf));
+ else
+ *buf = '\0';
+ Py_XDECREF(result);
+
+ /* Clean up after ourselves */
+ PyDict_Clear(PYheaders);
+ for (i = 0; i < hdrnum; i++)
+ if (PYheaditem[i] != Py_None)
+ Py_DECREF(PYheaditem[i]);
+
+ if (*buf != '\0')
+ return buf;
+ return NULL;
+}
+
+
+
+/*
+** Refuse message IDs offered thru CHECK or IHAVE that we don't like.
+*/
+char *
+PYmidfilter(messageID, msglen)
+ char *messageID;
+ int msglen;
+{
+ static char buf[256];
+ PyObject *result;
+
+ if (!PythonFilterActive || PYFilterObject == NULL || msgid_method == NULL)
+ return NULL;
+
+ result = PyObject_CallFunction(msgid_method, "s#", messageID, msglen);
+ if ((result != NULL) && PyObject_IsTrue(result))
+ strlcpy(buf, PyString_AS_STRING(result), sizeof(buf));
+ else
+ *buf = '\0';
+ Py_XDECREF(result);
+
+ if (*buf != '\0')
+ return buf;
+ return NULL;
+}
+
+
+
+/*
+** Tell the external module about innd's state.
+*/
+void
+PYmode(Mode, NewMode, reason)
+ OPERATINGMODE Mode, NewMode;
+ char *reason;
+{
+ PyObject *result;
+ char oldmode[10], newmode[10];
+
+ if (!PythonFilterActive || PYFilterObject == NULL || mode_method == NULL)
+ return;
+
+ switch (Mode) {
+ default: strlcpy(oldmode, "unknown", 10); break;
+ case OMrunning: strlcpy(oldmode, "running", 10); break;
+ case OMpaused: strlcpy(oldmode, "paused", 10); break;
+ case OMthrottled: strlcpy(oldmode, "throttled", 10); break;
+ }
+
+ switch (NewMode) {
+ default: strlcpy(newmode, "unknown", 10); break;
+ case OMrunning: strlcpy(newmode, "running", 10); break;
+ case OMpaused: strlcpy(newmode, "paused", 10); break;
+ case OMthrottled: strlcpy(newmode, "throttled", 10); break;
+ }
+
+ result = PyObject_CallFunction(mode_method, "sss",
+ oldmode, newmode, reason);
+ Py_XDECREF(result);
+}
+
+
+
+/*
+** Called by the external module so it can register itself with innd.
+*/
+static PyObject *
+PY_set_filter_hook(dummy, args)
+ PyObject *dummy, *args;
+{
+ PyObject *result = NULL;
+ PyObject *temp;
+
+ if (PyArg_ParseTuple(args, "O:set_filter_hook", &temp)) {
+ Py_XINCREF(temp);
+ Py_XDECREF(PYFilterObject);
+ PYFilterObject = temp;
+ Py_INCREF(Py_None);
+ result = Py_None;
+ }
+ return result;
+}
+
+
+
+/*
+** Allow external module to ask innd if an ID is in history.
+*/
+static PyObject *
+PY_havehist(self, args)
+ PyObject *self, *args;
+{
+ char *msgid;
+ int msgidlen;
+
+ if (!PyArg_ParseTuple(args, "s#", &msgid, &msgidlen))
+ return NULL;
+
+ if (HIScheck(History, msgid))
+ return PyInt_FromLong(1);
+ return PyInt_FromLong(0);
+}
+
+
+
+/*
+** Allow external module to locally delete an article.
+*/
+static PyObject *
+PY_cancel(self, args)
+ PyObject *self, *args;
+{
+ char *msgid;
+ int msgidlen;
+ char *parambuf[2];
+
+ if (!PyArg_ParseTuple(args, "s#", &msgid, &msgidlen))
+ return NULL;
+
+ parambuf[0]= msgid;
+ parambuf[1]= 0;
+
+ if (!CCcancel(parambuf))
+ return PyInt_FromLong(1);
+ return PyInt_FromLong(0);
+}
+
+
+
+/*
+** Stuff an ID into history so that it will be refused later.
+*/
+static PyObject *
+PY_addhist(self, args)
+ PyObject *self, *args;
+{
+ char *msgid;
+ int msgidlen;
+ char *articlepaths = "";
+ char tbuff[12];
+ char *parambuf[6];
+
+ if (!PyArg_ParseTuple(args, "s#", &msgid, &msgidlen))
+ return NULL;
+
+ snprintf(tbuff, sizeof(tbuff), "%d", time(NULL));
+
+ parambuf[0] = msgid;
+ parambuf[1] = parambuf[2] = parambuf[3] = tbuff;
+ parambuf[4] = articlepaths;
+ parambuf[5] = 0;
+
+ if (!CCaddhist(parambuf))
+ return PyInt_FromLong(1);
+ return PyInt_FromLong(0);
+}
+
+
+
+/*
+** Get a newsgroup's status flag (j, m, n, x, y, =other.group)
+*/
+static PyObject *
+PY_newsgroup(self, args)
+ PyObject *self, *args;
+{
+ char *newsgroup;
+ int nglen;
+ NEWSGROUP *ngp;
+ char *end;
+ char *rest;
+ int size;
+
+ if (!PyArg_ParseTuple(args, "s#", &newsgroup, &nglen))
+ return NULL;
+
+ ngp = NGfind(newsgroup);
+ if (ngp == NULL)
+ return PyString_FromStringAndSize(NULL, 0);
+
+ /* ngp->Rest is newline-terminated; find the end. */
+ end = strchr(ngp->Rest, '\n');
+ if (end == NULL)
+ size = strlen(ngp->Rest);
+ else
+ size = end - ngp->Rest;
+
+ /* If an alias is longer than this, active is probably broken. */
+ if (size > MAXHEADERSIZE) {
+ syslog(L_ERROR, "too-long flag field in active for %s", newsgroup);
+ size = MAXHEADERSIZE;
+ }
+
+ return PyString_FromStringAndSize(ngp->Rest, size);
+}
+
+
+
+/*
+** Return an article header to the external module as a string. We
+** don't use a buffer object here because that would make it harder,
+** for example, to compare two on-spool articles.
+*/
+static PyObject *
+PY_head(self, args)
+ PyObject *self, *args;
+{
+ char *msgid;
+ int msgidlen;
+ char *p;
+ TOKEN token;
+ ARTHANDLE *art;
+ PyObject *header;
+ int headerlen;
+
+ if (!PyArg_ParseTuple(args, "s#", &msgid, &msgidlen))
+ return NULL;
+
+ if (! HISlookup(History, msgid, NULL, NULL, NULL, &token))
+ return Py_BuildValue("s", "");
+ if ((art = SMretrieve(token, RETR_HEAD)) == NULL)
+ return Py_BuildValue("s", "");
+ p = FromWireFmt(art->data, art->len, &headerlen);
+ SMfreearticle(art);
+ header = PyString_FromStringAndSize(p, headerlen);
+ free(p);
+
+ return header;
+}
+
+
+
+/*
+** Return a whole article to the external module as a string.
+*/
+static PyObject *
+PY_article(self, args)
+ PyObject *self, *args;
+{
+ char *msgid;
+ int msgidlen;
+ char *p;
+ TOKEN token;
+ ARTHANDLE *arth;
+ PyObject *art;
+ int artlen;
+
+ if (!PyArg_ParseTuple(args, "s#", &msgid, &msgidlen))
+ return NULL;
+
+ if (! HISlookup(History, msgid, NULL, NULL, NULL, &token))
+ return Py_BuildValue("s", "");
+ if ((arth = SMretrieve(token, RETR_ALL)) == NULL)
+ return Py_BuildValue("s", "");
+ p = FromWireFmt(arth->data, arth->len, &artlen);
+ SMfreearticle(arth);
+ art = PyString_FromStringAndSize(p, artlen);
+ free(p);
+
+ return art;
+}
+
+
+
+/*
+** 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;
+
+ if (!PyArg_ParseTuple(args, "s#s#",
+ &loglevel, &levellen, &logmsg, &msglen))
+ return NULL;
+
+ 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;
+ }
+
+ syslog(priority, "python: %s", logmsg);
+
+ Py_INCREF(Py_None);
+ return Py_None;
+}
+
+
+
+/*
+** Compute a hash digest for a string.
+*/
+static PyObject *
+PY_hashstring(self, args)
+ PyObject *self, *args;
+{
+ char *instring, *wpos, *p, *q;
+ char *workstring = NULL;
+ int insize, worksize, newsize, i, wasspace;
+ int lines = 0;
+ HASH myhash;
+
+ if (!PyArg_ParseTuple(args, "s#|i", &instring, &insize, &lines))
+ return NULL;
+
+ /* If a linecount is provided, munge before hashing. */
+ if (lines > 0) {
+ worksize = insize;
+
+ /* chop leading whitespace */
+ for (p=instring ; worksize>0 && isspace(*p) ; p++) {
+ if (*p == '\n')
+ lines--;
+ worksize--;
+ }
+ wpos = p;
+
+ /* and trailing */
+ for (p=&wpos[worksize] ; worksize>0 && isspace(*p) ; p--) {
+ if (*p == '\n')
+ lines--;
+ worksize--;
+ }
+
+ /* chop last 3 lines if we have >= 5. From above chop the
+ * last line has no CR so we use 1 less here. */
+ if (lines >= 4) {
+ for (i=0, p=wpos+worksize ; i<2 ; p--)
+ if (*p == '\n')
+ i++;
+ worksize = p - wpos;
+ }
+
+ /* Compress out multiple whitespace in the trimmed string. We
+ * do a copy because this is probably an original art
+ * buffer. */
+ workstring = memcpy(xmalloc(worksize), wpos, worksize);
+ newsize = wasspace = 0;
+ p = wpos;
+ q = workstring;
+ for (i=0 ; i<worksize ; i++) {
+ if (isspace(*p)) {
+ if (!wasspace)
+ *q++ = ' ';
+ wasspace = 1;
+ }
+ else {
+ *q++ = tolower(*p);
+ wasspace = 0;
+ }
+ p++;
+ }
+ worksize = q - workstring;
+ myhash = Hash(workstring, worksize);
+ free(workstring);
+ }
+ else
+ myhash = Hash(instring, insize);
+
+ return PyString_FromStringAndSize((const char *)&myhash, sizeof(myhash));
+}
+
+
+
+/*
+** Make the internal INN module's functions visible to Python.
+*/
+static PyMethodDef INNPyMethods[] = {
+ {"set_filter_hook", PY_set_filter_hook, METH_VARARGS},
+ {"havehist", PY_havehist, METH_VARARGS},
+ {"addhist", PY_addhist, METH_VARARGS},
+ {"cancel", PY_cancel, METH_VARARGS},
+ {"newsgroup", PY_newsgroup, METH_VARARGS},
+ {"head", PY_head, METH_VARARGS},
+ {"article", PY_article, METH_VARARGS},
+ {"syslog", PY_syslog, METH_VARARGS},
+ {"hashstring", PY_hashstring, METH_VARARGS},
+ {NULL, NULL}
+};
+
+
+
+/*
+** This runs when innd shuts down.
+*/
+void
+PYclose(void)
+{
+ PyObject *result;
+
+ if (close_method != NULL) {
+ result = PyObject_CallFunction(close_method, NULL);
+ Py_XDECREF(result);
+ }
+}
+
+
+
+/*
+** Check that a method exists and is callable. Set a pointer to
+** the corresponding PyObject, or NULL if not found.
+*/
+void
+PYdefonemethod(methptr, methname)
+ PyObject **methptr;
+ char *methname;
+{
+ Py_XDECREF(*methptr);
+
+ /* We check with HasAttrString() the existence of the method because
+ * otherwise, in case it does not exist, an exception is raised by Python,
+ * although the result of the function is NULL. */
+ if (PyObject_HasAttrString(PYFilterObject, (char *) methname) == 1) {
+ /* Get a pointer to given method. */
+ *methptr = PyObject_GetAttrString(PYFilterObject, (char *) methname);
+ } else {
+ *methptr = NULL;
+ }
+
+ if (*methptr == NULL)
+ syslog(L_NOTICE, "python method %s not found", methname);
+ else if (PyCallable_Check(*methptr) == 0) {
+ syslog(L_ERROR, "python object %s found but not a function", methname);
+ Py_DECREF(*methptr);
+ *methptr = NULL;
+ }
+}
+
+
+
+/*
+** Look up the filter methods, so we will know what's available when
+** innd wants to call them.
+*/
+void
+PYdefmethods(void)
+{
+ PYdefonemethod(&msgid_method, "filter_messageid");
+ PYdefonemethod(&art_method, "filter_art");
+ PYdefonemethod(&mode_method, "filter_mode");
+ PYdefonemethod(&pre_reload_method, "filter_before_reload");
+ PYdefonemethod(&close_method, "filter_close");
+}
+
+
+
+/*
+** Used by "ctlinnd reload filter.python 'reason'".
+*/
+int
+PYreadfilter(void)
+{
+ PyObject *newmodule = NULL;
+ PyObject *result;
+
+ if (!Py_IsInitialized()) {
+ syslog(L_ERROR, "python is not initialized");
+ return 0;
+ }
+
+ /* If there is a filter running, let it clean up first. */
+ if (pre_reload_method != NULL) {
+ result = PyObject_CallFunction(pre_reload_method, NULL);
+ Py_XDECREF(result);
+ }
+
+ /* We need to reimport the module before reloading it because otherwise,
+ * it might not be taken into account by Python.
+ * See Python API documentation:
+ * If a module is syntactically correct but its initialization fails,
+ * the first import statement for it does not bind its name locally,
+ * but does store a (partially initialized) module object in
+ * sys.modules. To reload the module, you must first import it again
+ * (this will bind the name to the partially initialized module object)
+ * before you can reload() it.
+ */
+ PYFilterModule = PyImport_ImportModule((char *) _PATH_PYTHON_STARTUP_M);
+ if (PYFilterModule == NULL) {
+ syslog(L_ERROR, "failed to reimport external python module");
+ }
+
+ if ((newmodule = PyImport_ReloadModule(PYFilterModule)) == NULL) {
+ syslog(L_ERROR, "cant reload python filter module");
+ PYfilter(false);
+ return 0;
+ }
+
+ Py_XDECREF(PYFilterModule);
+ PYFilterModule = newmodule;
+
+ if (PYFilterObject == NULL) {
+ syslog(L_ERROR, "python reload error, filter object not defined");
+ PYfilter(false);
+ return 0;
+ }
+
+ PYfilter(true);
+ PYdefmethods();
+
+ return 1;
+}
+
+
+
+/*
+** Called when innd first starts -- this gets the filters hooked in.
+*/
+void
+PYsetup(void)
+{
+ const ARTHEADER *hp;
+ int hdrindex;
+ size_t hdrcount;
+
+ setenv("PYTHONPATH", innconf->pathfilter, 1);
+ Py_Initialize();
+
+ /* It makes Python sad when its stdout and stderr are closed. */
+ if ((fileno(stdout) == -1) || (fileno(stderr) == -1))
+ PyRun_SimpleString
+ ("import sys; sys.stdout=sys.stderr=open('/dev/null', 'a')");
+
+ if (!Py_IsInitialized ()) {
+ syslog(L_ERROR, "python interpreter NOT initialized");
+ return;
+ }
+ syslog(L_NOTICE, "python interpreter initialized OK");
+
+ Py_InitModule("INN", INNPyMethods);
+
+ PYFilterModule = PyImport_ImportModule(_PATH_PYTHON_STARTUP_M);
+ if (PYFilterModule == NULL)
+ syslog(L_ERROR, "failed to import external python module");
+
+ if (PYFilterObject == NULL) {
+ syslog(L_ERROR, "python filter object is not defined");
+ PYfilter(false);
+ } else {
+ PYfilter(true);
+ PYdefmethods();
+ syslog(L_NOTICE, "defined python methods");
+ }
+
+ /* Grab space for these so we aren't forever recreating them. We also
+ put the body and the line count into PYheaditem, so it needs to be
+ two elements longer than the total number of headers. */
+ PYheaders = PyDict_New();
+ hdrcount = ARRAY_END(ARTheaders) - ARTheaders;
+ PYheaditem = xmalloc((hdrcount + 2) * sizeof(PyObject *));
+ PYheadkey = xmalloc(hdrcount * sizeof(PyObject *));
+
+ /* Preallocate keys for the article dictionary */
+ for (hp = ARTheaders; hp < ARRAY_END(ARTheaders); hp++)
+ PYheadkey[hp - ARTheaders] = PyString_InternFromString(hp->Name);
+ PYpathkey = PyString_InternFromString("Path");
+ PYlineskey = PyString_InternFromString("__LINES__");
+ PYbodykey = PyString_InternFromString("__BODY__");
+}
+
+#endif /* defined(DO_PYTHON) */
--- /dev/null
+/* $Id: rc.c 7751 2008-04-06 14:35:40Z iulius $
+**
+** Routines for the remote connect channel. Create an Internet stream
+** socket that processes connect to. If the incoming site is not one of
+** our feeds, then we optionally pass the connection off to the standard
+** NNTP daemon.
+*/
+#include "config.h"
+#include "clibrary.h"
+#include "portable/socket.h"
+#include <errno.h>
+#include <netdb.h>
+
+#include "inn/innconf.h"
+#include "inn/vector.h"
+#include "innd.h"
+
+#define TEST_CONFIG(a, b) \
+ { \
+ b = ((peer_params.Keysetbit & (1 << a)) != 0) ? true : false; \
+ }
+
+#define SET_CONFIG(a) \
+ { \
+ peer_params.Keysetbit |= (1 << a); \
+ }
+
+/*
+** A remote host has an address and a password.
+*/
+typedef struct _REMOTEHOST {
+ char *Label; /* Peer label */
+ char *Name; /* Hostname */
+ struct sockaddr_storage Address; /* List of ip adresses */
+ char *Password; /* Optional password */
+ char *Identd; /* Optional identd */
+ bool Streaming; /* Streaming allowed ? */
+ bool Skip; /* Skip this peer ? */
+ bool NoResendId; /* Don't send RESEND responses ? */
+ bool Nolist; /* no list command allowed */
+ int MaxCnx; /* Max connections (per peer) */
+ char **Patterns; /* List of groups allowed */
+ char *Pattern; /* List of groups allowed (string) */
+ char *Email; /* Email(s) of contact */
+ char *Comment; /* Commentary [max size = MAXBUFF] */
+ int HoldTime; /* Hold time before disconnect over MaxCnx */
+ int Keysetbit; /* Bit to check duplicated key */
+} REMOTEHOST;
+
+typedef struct _REMOTEHOST_DATA {
+ int key; /* Key (as defined in the _Keywords enum) */
+ int type; /* Type of the value (see _Type enum) */
+ char *value; /* Value */
+} REMOTEHOST_DATA;
+
+typedef struct _REMOTETABLE {
+ struct sockaddr_storage Address;
+ time_t Expires;
+} REMOTETABLE;
+
+static char *RCslaveflag;
+static char *RCnnrpd = NULL;
+static char *RCnntpd = NULL;
+static CHANNEL **RCchan;
+static int chanlimit;
+static REMOTEHOST_DATA *RCpeerlistfile;
+static REMOTEHOST *RCpeerlist;
+static int RCnpeerlist;
+static char RCbuff[BIG_BUFFER];
+
+#define PEER "peer"
+#define GROUP "group"
+#define HOSTNAME "hostname:"
+#define STREAMING "streaming:"
+#define MAX_CONN "max-connections:"
+#define PASSWORD "password:"
+#define IDENTD "identd:"
+#define PATTERNS "patterns:"
+#define EMAIL "email:"
+#define COMMENT "comment:"
+#define SKIP "skip:"
+#define NORESENDID "noresendid:"
+#define HOLD_TIME "hold-time:"
+#define NOLIST "nolist:"
+
+typedef enum {K_END, K_BEGIN_PEER, K_BEGIN_GROUP, K_END_PEER, K_END_GROUP,
+ K_STREAM, K_HOSTNAME, K_MAX_CONN, K_PASSWORD, K_IDENTD,
+ K_EMAIL, K_PATTERNS, K_COMMENT, K_SKIP, K_NORESENDID,
+ K_HOLD_TIME, K_NOLIST
+ } _Keywords;
+
+typedef enum {T_STRING, T_BOOLEAN, T_INTEGER} _Types;
+
+#define GROUP_NAME "%s can't get group name in %s line %d"
+#define PEER_IN_PEER "%s peer can't contain peer in %s line %d"
+#define PEER_NAME "%s can't get peer name in %s line %d"
+#define LEFT_BRACE "%s '{' expected in %s line %d"
+#define RIGHT_BRACE "%s '}' unexpected line %d in %s"
+#define INCOMPLETE_PEER "%s incomplete peer (%s) in %s line %d"
+#define INCOMPLETE_GROUP "%s incomplete group (%s) in %s line %d"
+#define MUST_BE_BOOL "%s Must be 'true' or 'false' in %s line %d"
+#define MUST_BE_INT "%s Must be an integer value in %s line %d"
+#define HOST_NEEDED "%s 'hostname' needed in %s line %d"
+#define DUPLICATE_KEY "%s duplicate key in %s line %d"
+
+/*
+** Stuff needed for limiting incoming connects.
+*/
+static char RCterm[] = "\r\n";
+static REMOTETABLE remotetable[REMOTETABLESIZE];
+static int remotecount;
+static int remotefirst;
+
+/*
+ * Check that the client has the right identd. Return true if is the
+ * case, false, if not.
+ */
+static bool
+GoodIdent(int fd, char *identd)
+{
+#define PORT_IDENTD 113
+ char IDENTuser[80];
+ struct sockaddr_storage ss_local;
+ struct sockaddr_storage ss_distant;
+ struct sockaddr *s_local = (struct sockaddr *)&ss_local;
+ struct sockaddr *s_distant = (struct sockaddr *)&ss_distant;
+ int ident_fd;
+ socklen_t len;
+ int port1,port2;
+ ssize_t lu;
+ char buf[80], *buf2;
+
+ if(identd[0] == '\0') {
+ return true;
+ }
+
+ len = sizeof( ss_local );
+ if ((getsockname(fd,s_local,&len)) < 0) {
+ syslog(L_ERROR, "can't do getsockname for identd");
+ return false;
+ }
+ len = sizeof( ss_distant );
+ if ((getpeername(fd,s_distant,&len)) < 0) {
+ syslog(L_ERROR, "can't do getsockname for identd");
+ return false;
+ }
+#ifdef HAVE_INET6
+ if( s_local->sa_family == AF_INET6 )
+ {
+ struct sockaddr_in6 *s_l6 = (struct sockaddr_in6 *)s_local;
+ struct sockaddr_in6 *s_d6 = (struct sockaddr_in6 *)s_distant;
+
+ port1=ntohs(s_l6->sin6_port);
+ port2=ntohs(s_d6->sin6_port);
+ s_l6->sin6_port = 0;
+ s_d6->sin6_port = htons( PORT_IDENTD );
+ ident_fd=socket(PF_INET6, SOCK_STREAM, 0);
+ } else
+#endif
+ if( s_local->sa_family == AF_INET )
+ {
+ struct sockaddr_in *s_l = (struct sockaddr_in *)s_local;
+ struct sockaddr_in *s_d = (struct sockaddr_in *)s_distant;
+
+ port1=ntohs(s_l->sin_port);
+ port2=ntohs(s_d->sin_port);
+ s_l->sin_port = 0;
+ s_d->sin_port = htons( PORT_IDENTD );
+ ident_fd=socket(PF_INET, SOCK_STREAM, 0);
+ } else
+ {
+ syslog(L_ERROR, "Bad address family: %d\n", s_local->sa_family );
+ return false;
+ }
+ if (ident_fd < 0) {
+ syslog(L_ERROR, "can't open socket for identd (%m)");
+ return false;
+ }
+ if (bind(ident_fd,s_local,SA_LEN(s_local)) < 0) {
+ syslog(L_ERROR, "can't bind socket for identd (%m)");
+ close(ident_fd);
+ return false;
+ }
+ if (connect(ident_fd,s_distant,SA_LEN(s_distant)) < 0) {
+ syslog(L_ERROR, "can't connect to identd (%m)");
+ close(ident_fd);
+ return false;
+ }
+
+ snprintf(buf,sizeof(buf),"%d,%d\r\n",port2, port1);
+ write(ident_fd,buf, strlen(buf));
+ memset( buf, 0, 80 );
+ lu=read(ident_fd, buf, 79); /* pas encore parfait ("not yet perfect"?) */
+ if (lu<0)
+ {
+ syslog(L_ERROR, "error reading from ident server: %m" );
+ close( ident_fd );
+ return false;
+ }
+ buf[lu]='\0';
+ if ((lu>0) && (strstr(buf,"ERROR")==NULL)
+ && ((buf2=strrchr(buf,':'))!=NULL))
+ {
+ buf2++;
+ while(*buf2 == ' ') buf2++;
+ strlcpy(IDENTuser, buf2, sizeof(IDENTuser));
+ buf2=strchr(IDENTuser,'\r');
+ if (!buf2) buf2=strchr(IDENTuser,'\n');
+ if (buf2) *buf2='\0';
+ } else
+ strlcpy(IDENTuser, "UNKNOWN", sizeof(IDENTuser));
+ close(ident_fd);
+
+ return strcmp(identd, IDENTuser) == 0;
+}
+
+/*
+ * Split text into comma-separated fields. Return an allocated
+ * NULL-terminated array of the fields within the modified argument that
+ * the caller is expected to save or free. We don't use strchr() since
+ * the text is expected to be either relatively short or "comma-dense."
+ * (This function is different from CommaSplit because spaces are allowed
+ * and removed here)
+ */
+
+static char **
+RCCommaSplit(char *text)
+{
+ int i;
+ char *p;
+ char *q;
+ char *r;
+ char **av;
+ char **save;
+
+ /* How much space do we need? */
+ for (i = 2, p = text, q = r = xstrdup(text); *p; p++) {
+ if (*p != ' ' && *p != '\t' && *p != '\n')
+ *q++ = *p;
+ if (*p == ',')
+ i++;
+ }
+ *q = '\0';
+ free (text);
+ for (text = r, av = save = xmalloc(i * sizeof(char *)), *av++ = p = text; *p; )
+ if (*p == ',') {
+ *p++ = '\0';
+ *av++ = p;
+ }
+ else
+ p++;
+ *av = NULL;
+ return save;
+}
+
+ /*
+ * Routine to disable IP-level socket options. This code was taken from 4.4BSD
+ * rlogind source, but all mistakes in it are my fault.
+ *
+ * Author: Wietse Venema, Eindhoven University of Technology, The Netherlands.
+ *
+ * 21-Jan-1997 smd
+ * Code copied again, and modified for INN, all new mistakes are mine.
+ *
+ */
+
+/* fix_options - get rid of IP-level socket options */
+#ifndef IP_OPTIONS
+#define IP_OPTIONS 1
+#endif
+
+static int
+RCfix_options(int fd, struct sockaddr_storage *remote)
+{
+#if IP_OPTIONS
+ unsigned char optbuf[BUFSIZ / 3], *cp;
+ char lbuf[BUFSIZ], *lp;
+ socklen_t optsize = sizeof(optbuf);
+ int ipproto;
+ struct protoent *ip;
+
+ switch (remote->ss_family) {
+ case AF_INET:
+ if ((ip = getprotobyname("ip")) != 0)
+ ipproto = ip->p_proto;
+ else
+ ipproto = IPPROTO_IP;
+ break;
+#ifdef HAVE_INET6
+ case AF_INET6:
+ if ((ip = getprotobyname("ipv6")) != 0)
+ ipproto = ip->p_proto;
+ else
+ ipproto = IPPROTO_IPV6;
+ break;
+#endif
+ default:
+ syslog(LOG_ERR, "unknown address family: %d", remote->ss_family);
+ return -1;
+ }
+
+ if (getsockopt(fd, ipproto, IP_OPTIONS, (char *) optbuf, &optsize) == 0
+ && optsize != 0) {
+ lp = lbuf;
+ for (cp = optbuf; optsize > 0; cp++, optsize--, lp += 3)
+ sprintf(lp, " %2.2x", *cp);
+ syslog(LOG_NOTICE,
+ "connect from %s with IP options (ignored):%s",
+ sprint_sockaddr((struct sockaddr *)remote), lbuf);
+ if (setsockopt(fd, ipproto, IP_OPTIONS, (char *) 0, optsize) != 0) {
+ syslog(LOG_ERR, "setsockopt IP_OPTIONS NULL: %m");
+ return -1;
+ }
+ }
+#endif
+ return 0;
+}
+
+static bool
+RCaddressmatch(const struct sockaddr_storage *cp, const struct sockaddr_storage *rp)
+{
+#ifdef HAVE_INET6
+ struct sockaddr_in *sin_cp, *sin_rp;
+ struct sockaddr_in6 *sin6_cp, *sin6_rp;
+
+ if (cp->ss_family == AF_INET6 && rp->ss_family == AF_INET) {
+ sin6_cp = (struct sockaddr_in6 *)cp;
+ sin_rp = (struct sockaddr_in *)rp;
+ if (IN6_IS_ADDR_V4MAPPED(&sin6_cp->sin6_addr) &&
+ memcmp(&sin6_cp->sin6_addr.s6_addr[12],
+ &sin_rp->sin_addr.s_addr, sizeof(struct in_addr)) == 0)
+ return true;
+ } else if (cp->ss_family == AF_INET && rp->ss_family == AF_INET6) {
+ sin_cp = (struct sockaddr_in *)cp;
+ sin6_rp = (struct sockaddr_in6 *)rp;
+ if (IN6_IS_ADDR_V4MAPPED(&sin6_rp->sin6_addr) &&
+ memcmp(&sin6_rp->sin6_addr.s6_addr[12],
+ &sin_cp->sin_addr.s_addr, sizeof(struct in_addr)) == 0)
+ return true;
+ } else if (cp->ss_family == AF_INET6 && rp->ss_family == AF_INET6) {
+#ifdef HAVE_BROKEN_IN6_ARE_ADDR_EQUAL
+ if (!memcmp(&((struct sockaddr_in6 *)cp)->sin6_addr,
+ &((struct sockaddr_in6 *)rp)->sin6_addr,
+ sizeof(struct in6_addr)))
+#else
+ if (IN6_ARE_ADDR_EQUAL( &((struct sockaddr_in6 *)cp)->sin6_addr,
+ &((struct sockaddr_in6 *)rp)->sin6_addr))
+#endif
+ return true;
+ } else
+#endif /* INET6 */
+ if (((struct sockaddr_in *)cp)->sin_addr.s_addr ==
+ ((struct sockaddr_in *)rp)->sin_addr.s_addr)
+ return true;
+
+ return false;
+}
+
+/*
+** See if the site properly entered the password.
+*/
+bool
+RCauthorized(CHANNEL *cp, char *pass)
+{
+ REMOTEHOST *rp;
+ int i;
+
+ for (rp = RCpeerlist, i = RCnpeerlist; --i >= 0; rp++)
+ if (RCaddressmatch(&cp->Address, &rp->Address)) {
+ if (rp->Password[0] == '\0' || strcmp(pass, rp->Password) == 0)
+ return true;
+ syslog(L_ERROR, "%s (%s) bad_auth", rp->Label,
+ sprint_sockaddr((struct sockaddr *)&cp->Address));
+ return false;
+ }
+
+ if (!AnyIncoming)
+ /* Not found in our table; this can't happen. */
+ syslog(L_ERROR, "%s not_found", sprint_sockaddr((struct sockaddr *)&cp->Address));
+
+ /* Anonymous hosts should not authenticate. */
+ return false;
+}
+
+/*
+** See if a host is limited or not.
+*/
+bool
+RCnolimit(CHANNEL *cp)
+{
+ REMOTEHOST *rp;
+ int i;
+
+ for (rp = RCpeerlist, i = RCnpeerlist; --i >= 0; rp++)
+ if (RCaddressmatch(&cp->Address, &rp->Address))
+ return !rp->MaxCnx;
+
+ /* Not found in our table; this can't happen. */
+ return false;
+}
+
+/*
+** Return the limit (max number of connections) for a host.
+*/
+int
+RClimit(CHANNEL *cp)
+{
+ REMOTEHOST *rp;
+ int i;
+
+ for (rp = RCpeerlist, i = RCnpeerlist; --i >= 0; rp++)
+ if (RCaddressmatch(&cp->Address, &rp->Address))
+ return rp->MaxCnx;
+ /* Not found in our table; this can't happen. */
+ return RemoteLimit;
+}
+
+
+/*
+** Called when input is ready to read. Shouldn't happen.
+*/
+static void
+RCrejectreader(CHANNEL *cp)
+{
+ syslog(L_ERROR, "%s internal RCrejectreader (%s)", LogName,
+ sprint_sockaddr((struct sockaddr *)&cp->Address));
+}
+
+
+/*
+** Write-done function for rejects.
+*/
+static void
+RCrejectwritedone(CHANNEL *cp)
+{
+ switch (cp->State) {
+ default:
+ syslog(L_ERROR, "%s internal RCrejectwritedone state %d",
+ CHANname(cp), cp->State);
+ break;
+ case CSwritegoodbye:
+ CHANclose(cp, CHANname(cp));
+ break;
+ }
+}
+
+
+/*
+** Hand off a descriptor to NNRPD.
+*/
+void
+RChandoff(int fd, HANDOFF h)
+{
+ const char **argv;
+ char buff[SMBUF];
+ int i;
+ unsigned int j;
+ struct vector *flags;
+
+ flags = vector_split_space(innconf->nnrpdflags, NULL);
+ argv = xmalloc( (flags->count + 6) * sizeof(char*) );
+
+ if (RCnnrpd == NULL)
+ RCnnrpd = concatpath(innconf->pathbin, "nnrpd");
+ if (RCnntpd == NULL)
+ RCnntpd = concatpath(innconf->pathbin, "nnrpd");
+#if defined(SOL_SOCKET) && defined(SO_KEEPALIVE)
+ /* Set KEEPALIVE to catch broken socket connections. */
+ i = 1;
+ if (setsockopt(fd, SOL_SOCKET, SO_KEEPALIVE, (char *)&i, sizeof i) < 0)
+ syslog(L_ERROR, "fd %d cant setsockopt(KEEPALIVE) %m", fd);
+#endif /* defined(SOL_SOCKET) && defined(SO_KEEPALIVE) */
+
+ if (nonblocking(fd, false) < 0)
+ syslog(L_ERROR, "%s cant nonblock %d in RChandoff %m", LogName, fd);
+ switch (h) {
+ default:
+ syslog(L_ERROR, "%s internal RChandoff %d type %d", LogName, fd, h);
+ /* FALLTHROUGH */
+ case HOnnrpd: argv[0] = RCnnrpd; break;
+ case HOnntpd: argv[0] = RCnntpd; break;
+ }
+ argv[1] = "-s ";
+ i = 2;
+ if (NNRPReason) {
+ snprintf(buff, sizeof(buff), "-r%s", NNRPReason);
+ argv[i++] = buff;
+ }
+ if (NNRPTracing)
+ argv[i++] = "-t";
+ if (RCslaveflag)
+ argv[i++] = RCslaveflag;
+
+ for(j = 0; j < flags->count; j++) {
+ argv[i++] = flags->strings[j];
+ }
+ argv[i] = NULL;
+
+ /* Call NNRP; don't send back a QUIT message if Spawn fails since
+ * that's a major error we want to find out about quickly. */
+ (void)Spawn(innconf->nicekids, fd, fd, fd, (char * const *)argv);
+ vector_free(flags);
+ free(argv);
+}
+
+
+/*
+** Read function. Accept the connection and either create an NNTP channel
+** or spawn an nnrpd to handle it.
+*/
+static void
+RCreader(CHANNEL *cp)
+{
+ int fd;
+ struct sockaddr_storage remote;
+ socklen_t size;
+ int i;
+ REMOTEHOST *rp;
+ CHANNEL *new;
+ char *name;
+ long reject_val = 0;
+ const char *reject_message;
+ int count;
+ int found;
+ time_t now;
+ CHANNEL tempchan;
+ char buff[SMBUF];
+
+ for (i = 0 ; i < chanlimit ; i++) {
+ if (RCchan[i] == cp) {
+ break;
+ }
+ }
+ if (i == chanlimit) {
+ syslog(L_ERROR, "%s internal RCreader wrong channel 0x%p",
+ LogName, (void *)cp);
+ return;
+ }
+
+ /* Get the connection. */
+ size = sizeof remote;
+ if ((fd = accept(cp->fd, (struct sockaddr *)&remote, &size)) < 0) {
+ if (errno != EWOULDBLOCK && errno != EAGAIN)
+ syslog(L_ERROR, "%s cant accept RCreader %m", LogName);
+ return;
+ }
+
+ /*
+ ** Clear any IP_OPTIONS, including source routing, on the socket
+ */
+ /* FIXME RCfix_options breaks IPv6 sockets, at least on Linux -lutchann */
+#ifndef HAVE_INET6
+ if (RCfix_options(fd, &remote) != 0) {
+ /* shouldn't happen, but we're bit paranoid at this point */
+ if (close(fd) < 0)
+ syslog(L_ERROR, "%s cant close %d %m", LogName, fd);
+ return;
+ }
+#endif
+
+ /* If RemoteTimer is not zero, then check the limits on incoming
+ connections on a total and per host basis.
+
+ The incoming connection table is fixed at 128 entries to make
+ calculating the index easy (i + 1) & 7, and to be pretty sure that you
+ won't run out of space. The table is used as a ring with new entries
+ being added to the end (wrapping around) and expired entries being
+ deleted from the front (again wrapping around). It is doubtful that
+ you will ever use even half of the table.
+
+ There are three parameters controlling the use of the table not
+ counting the starting index and count:
+
+ H = per host incoming connects per X seconds allowed
+ T = total incoming connects per X seconds allowed
+ X = number of seconds to remember a successful connect
+
+ First, one pass is made over the live entries deleting any that are
+ over X seconds old. If the entry hasn't expired, compare the incoming
+ connection's host address with the entry's host address. If equal,
+ increment the "found" counter.
+
+ Second, if the number of entries now in the table is equal to the T
+ parameter, reject the connection with a message indicating that the
+ server is overloaded.
+
+ Third, if the number of entries now in the table which match the
+ incoming connection's host address is equal to the H parameter, reject
+ the connection.
+
+ Finally, if neither rejection happened, add the entry to the table, and
+ continue on as a normal connect. */
+ memcpy(&tempchan.Address, &remote, SA_LEN((struct sockaddr *)&remote));
+ reject_message = NULL;
+ if (RemoteTimer != 0) {
+ now = time(NULL);
+ i = remotefirst;
+ count = remotecount;
+ found = 0;
+ while (count--) {
+ if (remotetable[i].Expires < now) {
+ remotecount--;
+ remotefirst = (remotefirst + 1) & (REMOTETABLESIZE - 1);
+ i = (i + 1) & (REMOTETABLESIZE - 1);
+ continue;
+ }
+ if (RCaddressmatch(&remotetable[i].Address, &remote))
+ found++;
+ i = (i + 1) & (REMOTETABLESIZE - 1);
+ }
+ if (remotecount == RemoteTotal) {
+ reject_val = NNTP_GOODBYE_VAL;
+ reject_message = "400 Server overloaded, try later";
+ }
+ else if (found >= RemoteLimit && !RCnolimit(&tempchan)) {
+ reject_val = NNTP_GOODBYE_VAL;
+ reject_message = "400 Connection rejected, you're making too"
+ " many connects per minute";
+ }
+ else {
+ i = (remotefirst + remotecount) & (REMOTETABLESIZE - 1);
+ memcpy(&remotetable[i].Address, &remote, SA_LEN((struct sockaddr *)&remote));
+ remotetable[i].Expires = now + RemoteTimer;
+ remotecount++;
+ }
+ }
+
+ /*
+ ** Create a reject channel to reject the connection. This is done
+ ** to avoid a call to fork.
+ */
+ if (reject_message) {
+ new = CHANcreate(fd, CTreject, CSwritegoodbye, RCrejectreader,
+ RCrejectwritedone);
+ memcpy(&remotetable[i].Address, &remote, SA_LEN((struct sockaddr *)&remote));
+ new->Rejected = reject_val;
+ RCHANremove(new);
+ WCHANset(new, reject_message, (int)strlen(reject_message));
+ WCHANappend(new, RCterm, strlen(RCterm));
+ WCHANadd(new);
+ return;
+ }
+
+ /* See if it's one of our servers. */
+ for (name = NULL, rp = RCpeerlist, i = RCnpeerlist; --i >= 0; rp++)
+ if (RCaddressmatch(&rp->Address, &remote)) {
+ name = rp->Name;
+ break;
+ }
+
+ /* If not a server, and not allowing anyone, hand him off unless
+ not spawning nnrpd in which case we return an error. */
+ if ((i >= 0) && !rp->Skip) {
+
+ /* We check now the identd if we have to */
+ if(! GoodIdent(fd, rp->Identd))
+ {
+ if (!innconf->noreader) {
+ RChandoff(fd, HOnntpd);
+ if (close(fd) < 0)
+ syslog(L_ERROR, "%s cant close %d %m", LogName, fd);
+ return;
+ }
+ }
+
+ if ((new = NCcreate(fd, rp->Password[0] != '\0', false)) != NULL) {
+ new->Streaming = rp->Streaming;
+ new->Skip = rp->Skip;
+ new->NoResendId = rp->NoResendId;
+ new->Nolist = rp->Nolist;
+ new->MaxCnx = rp->MaxCnx;
+ new->HoldTime = rp->HoldTime;
+ memcpy(&new->Address, &remote, SA_LEN((struct sockaddr *)&remote));
+ if (new->MaxCnx > 0 && new->HoldTime == 0) {
+ CHANsetActiveCnx(new);
+ if((new->ActiveCnx > new->MaxCnx) && (new->fd > 0)) {
+ snprintf(buff, sizeof(buff),
+ "You are limited to %d connection%s",
+ new->MaxCnx, (new->MaxCnx != 1) ? "s" : "");
+ NCwriteshutdown(new, buff);
+ syslog(L_NOTICE, "too many connections from %s", rp->Label);
+ } else {
+ NCwritereply(new, (char *)NCgreeting);
+ }
+ } else {
+ NCwritereply(new, (char *)NCgreeting);
+ }
+ }
+ } else if (AnyIncoming && !rp->Skip) {
+ if ((new = NCcreate(fd, false, false)) != NULL) {
+ NCwritereply(new, (char *)NCgreeting);
+ }
+ } else if (!innconf->noreader) {
+ RChandoff(fd, HOnntpd);
+ if (close(fd) < 0)
+ syslog(L_ERROR, "%s cant close %d %m", LogName, fd);
+ return;
+ } else {
+ reject_val = NNTP_ACCESS_VAL;
+ reject_message = NNTP_ACCESS;
+ new = CHANcreate(fd, CTreject, CSwritegoodbye, RCrejectreader,
+ RCrejectwritedone);
+ memcpy(&new->Address, &remote, SA_LEN((struct sockaddr *)&remote));
+ new->Rejected = reject_val;
+ RCHANremove(new);
+ WCHANset(new, reject_message, (int)strlen(reject_message));
+ WCHANappend(new, RCterm, strlen(RCterm));
+ WCHANadd(new);
+ return;
+ }
+
+ if (new != NULL) {
+ memcpy(&new->Address, &remote, SA_LEN((struct sockaddr *)&remote));
+ syslog(L_NOTICE, "%s connected %d streaming %s",
+ name ? name : sprint_sockaddr((struct sockaddr *)&new->Address),
+ new->fd, (!StreamingOff && new->Streaming) ? "allowed" : "not allowed");
+ }
+}
+
+
+/*
+** Write-done function. Shouldn't happen.
+*/
+static void
+RCwritedone(CHANNEL *unused)
+{
+ unused = unused; /* ARGSUSED */
+ syslog(L_ERROR, "%s internal RCwritedone", LogName);
+}
+
+/*
+ * New config file style. Old hosts.nntp and hosts.nntp.nolimit are merged
+ * into one file called incoming.conf (to avoid confusion).
+ * See ../samples/incoming.conf for the new syntax.
+ *
+ * Fabien Tassin <fta@sofaraway.org>, 21-Dec-1997.
+ */
+
+
+/*
+ * Read something (a word or a double quoted string) from a file.
+ */
+static char *
+RCreaddata(int *num, FILE *F, bool *toolong)
+{
+ char *p;
+ char *s;
+ char *t;
+ char *word;
+ bool flag;
+
+ *toolong = false;
+ if (*RCbuff == '\0') {
+ if (feof (F)) return (NULL);
+ fgets(RCbuff, sizeof RCbuff, F);
+ (*num)++;
+ if (strlen (RCbuff) == sizeof RCbuff) {
+ *toolong = true;
+ return (NULL); /* Line too long */
+ }
+ }
+ p = RCbuff;
+ do {
+ /* Ignore blank and comment lines. */
+ if ((p = strchr(RCbuff, '\n')) != NULL)
+ *p = '\0';
+ if ((p = strchr(RCbuff, '#')) != NULL) {
+ if (p == RCbuff || (p > RCbuff && *(p - 1) != '\\'))
+ *p = '\0';
+ }
+ for (p = RCbuff; *p == ' ' || *p == '\t' ; p++);
+ flag = true;
+ if (*p == '\0' && !feof (F)) {
+ flag = false;
+ fgets(RCbuff, sizeof RCbuff, F);
+ (*num)++;
+ if (strlen (RCbuff) == sizeof RCbuff) {
+ *toolong = true;
+ return (NULL); /* Line too long */
+ }
+ continue;
+ }
+ break;
+ } while (!feof (F) || !flag);
+
+ if (*p == '"') { /* double quoted string ? */
+ p++;
+ do {
+ for (t = p; (*t != '"' || (*t == '"' && *(t - 1) == '\\')) &&
+ *t != '\0'; t++);
+ if (*t == '\0') {
+ *t++ = '\n';
+ fgets(t, sizeof RCbuff - strlen (RCbuff), F);
+ (*num)++;
+ if (strlen (RCbuff) == sizeof RCbuff) {
+ *toolong = true;
+ return (NULL); /* Line too long */
+ }
+ if ((s = strchr(t, '\n')) != NULL)
+ *s = '\0';
+ }
+ else
+ break;
+ } while (!feof (F));
+ *t++ = '\0';
+ }
+ else {
+ for (t = p; *t != ' ' && *t != '\t' && *t != '\0'; t++);
+ if (*t != '\0')
+ *t++ = '\0';
+ }
+ if (*p == '\0' && feof (F)) return (NULL);
+ word = xstrdup (p);
+ for (p = RCbuff; *t != '\0'; t++)
+ *p++ = *t;
+ *p = '\0';
+
+ return (word);
+}
+
+/*
+ * Add all data into RCpeerlistfile.
+ */
+static void
+RCadddata(REMOTEHOST_DATA **d, int *count, int Key, int Type, char* Value)
+{
+ (*d)[*count].key = Key;
+ (*d)[*count].type = Type;
+ (*d)[*count].value = Value;
+ (*count)++;
+ *d = xrealloc(*d, (*count + 1) * sizeof(REMOTEHOST_DATA));
+}
+
+/*
+** Read in the file listing the hosts we take news from, and fill in the
+** global list of their Internet addresses. A host can have multiple
+** addresses, so we take care to add all of them to the list.
+*/
+static void
+RCreadfile (REMOTEHOST_DATA **data, REMOTEHOST **list, int *count,
+ char *filename)
+{
+ static char NOPASS[] = "";
+ static char NOIDENTD[] = "";
+ static char NOEMAIL[] = "";
+ static char NOCOMMENT[] = "";
+ FILE *F;
+ char *p;
+ char **q;
+ char **r;
+#if !defined( HAVE_INET6)
+ struct hostent *hp;
+#endif
+#if !defined(HAVE_UNIX_DOMAIN_SOCKETS) || !defined(HAVE_INET6)
+ struct in_addr addr;
+#endif
+ int i;
+ int j;
+ int linecount;
+ int infocount;
+ int groupcount;
+ int maxgroup;
+ REMOTEHOST_DATA *dt;
+ REMOTEHOST *rp;
+ char *word;
+ REMOTEHOST *groups;
+ REMOTEHOST *group_params = NULL;
+ REMOTEHOST peer_params;
+ REMOTEHOST default_params;
+ bool flag, bit, toolong;
+
+ *RCbuff = '\0';
+ if (*list) {
+ for (rp = *list, i = *count; --i >= 0; rp++) {
+ free(rp->Name);
+ free(rp->Label);
+ free(rp->Email);
+ free(rp->Comment);
+ free(rp->Password);
+ free(rp->Identd);
+ if (rp->Patterns) {
+ free(rp->Patterns[0]);
+ free(rp->Patterns);
+ }
+ }
+ free(*list);
+ *list = NULL;
+ *count = 0;
+ }
+ if (*data) {
+ for (i = 0; (*data)[i].key != K_END; i++)
+ if ((*data)[i].value != NULL)
+ free((*data)[i].value);
+ free(*data);
+ *data = NULL;
+ }
+
+ *count = 0;
+ maxgroup = 0;
+ /* Open the server file. */
+ if ((F = Fopen(filename, "r", TEMPORARYOPEN)) == NULL) {
+ syslog(L_FATAL, "%s cant read %s: %m", LogName, filename);
+ exit(1);
+ }
+ dt = *data = xmalloc(sizeof(REMOTEHOST_DATA));
+ rp = *list = xmalloc(sizeof(REMOTEHOST));
+
+#if !defined(HAVE_UNIX_DOMAIN_SOCKETS)
+ addr.s_addr = INADDR_LOOPBACK;
+ make_sin( (struct sockaddr_in *)&rp->Address, &addr );
+ rp->Name = xstrdup("localhost");
+ rp->Label = xstrdup("localhost");
+ rp->Email = xstrdup(NOEMAIL);
+ rp->Comment = xstrdup(NOCOMMENT);
+ rp->Password = xstrdup(NOPASS);
+ rp->Identd = xstrdup(NOIDENTD);
+ rp->Patterns = NULL;
+ rp->MaxCnx = 0;
+ rp->Streaming = true;
+ rp->Skip = false;
+ rp->NoResendId = false;
+ rp->Nolist = false;
+ rp->HoldTime = 0;
+ rp++;
+ (*count)++;
+#endif /* !defined(HAVE_UNIX_DOMAIN_SOCKETS) */
+
+ linecount = 0;
+ infocount = 0;
+ groupcount = 0; /* no group defined yet */
+ groups = 0;
+ peer_params.Label = NULL;
+ default_params.Streaming = true;
+ default_params.Skip = false;
+ default_params.NoResendId = false;
+ default_params.Nolist = false;
+ default_params.MaxCnx = 0;
+ default_params.HoldTime = 0;
+ default_params.Password = xstrdup(NOPASS);
+ default_params.Identd = xstrdup(NOIDENTD);
+ default_params.Email = xstrdup(NOEMAIL);
+ default_params.Comment = xstrdup(NOCOMMENT);
+ default_params.Pattern = NULL;
+ peer_params.Keysetbit = 0;
+
+ /* Read the file to add all the hosts. */
+ while ((word = RCreaddata (&linecount, F, &toolong)) != NULL) {
+
+ /* group */
+ if (!strncmp (word, GROUP, sizeof GROUP)) {
+ free(word);
+ /* name of the group */
+ if ((word = RCreaddata (&linecount, F, &toolong)) == NULL) {
+ syslog(L_ERROR, GROUP_NAME, LogName, filename, linecount);
+ break;
+ }
+ RCadddata(data, &infocount, K_BEGIN_GROUP, T_STRING, word);
+ groupcount++;
+ if (groupcount == 1) {
+ /* First group block. */
+ group_params = groups = xmalloc(sizeof(REMOTEHOST));
+ }
+ else if (groupcount >= maxgroup) {
+ /* Alloc 5 groups for extra nested group blocks. */
+ groups = xrealloc(groups, (groupcount + 4) * sizeof(REMOTEHOST));
+ maxgroup += 5;
+ group_params = groups + groupcount - 1;
+ }
+ else {
+ /* Nested group block (no need to extend groups). */
+ group_params++;
+ }
+ group_params->Label = word;
+ group_params->Skip = groupcount > 1 ?
+ groups[groupcount - 2].Skip : default_params.Skip;
+ group_params->Streaming = groupcount > 1 ?
+ groups[groupcount - 2].Streaming : default_params.Streaming;
+ group_params->NoResendId = groupcount > 1 ?
+ groups[groupcount - 2].NoResendId : default_params.NoResendId;
+ group_params->Nolist = groupcount > 1 ?
+ groups[groupcount - 2].Nolist : default_params.Nolist;
+ group_params->Email = groupcount > 1 ?
+ groups[groupcount - 2].Email : default_params.Email;
+ group_params->Comment = groupcount > 1 ?
+ groups[groupcount - 2].Comment : default_params.Comment;
+ group_params->Pattern = groupcount > 1 ?
+ groups[groupcount - 2].Pattern : default_params.Pattern;
+ group_params->Password = groupcount > 1 ?
+ groups[groupcount - 2].Password : default_params.Password;
+ group_params->Identd = groupcount > 1 ?
+ groups[groupcount - 2].Identd : default_params.Identd;
+ group_params->MaxCnx = groupcount > 1 ?
+ groups[groupcount - 2].MaxCnx : default_params.MaxCnx;
+ group_params->HoldTime = groupcount > 1 ?
+ groups[groupcount - 2].HoldTime : default_params.HoldTime;
+
+ if ((word = RCreaddata (&linecount, F, &toolong)) == NULL) {
+ syslog(L_ERROR, LEFT_BRACE, LogName, filename, linecount);
+ break;
+ }
+ /* left brace */
+ if (strncmp (word, "{", 1)) {
+ free(word);
+ syslog(L_ERROR, LEFT_BRACE, LogName, filename, linecount);
+ break;
+ }
+ else
+ free(word);
+ peer_params.Keysetbit = 0;
+ continue;
+ }
+
+ /* peer */
+ if (!strncmp (word, PEER, sizeof PEER)) {
+ free(word);
+ if (peer_params.Label != NULL) {
+ /* peer can't contain peer */
+ syslog(L_ERROR, PEER_IN_PEER, LogName,
+ filename, linecount);
+ break;
+ }
+ if ((word = RCreaddata (&linecount, F, &toolong)) == NULL)
+ {
+ syslog(L_ERROR, PEER_NAME, LogName, filename, linecount);
+ break;
+ }
+ RCadddata(data, &infocount, K_BEGIN_PEER, T_STRING, word);
+ /* name of the peer */
+ peer_params.Label = word;
+ peer_params.Name = NULL;
+ peer_params.Skip = groupcount > 0 ?
+ group_params->Skip : default_params.Skip;
+ peer_params.Streaming = groupcount > 0 ?
+ group_params->Streaming : default_params.Streaming;
+ peer_params.NoResendId = groupcount > 0 ?
+ group_params->NoResendId : default_params.NoResendId;
+ peer_params.Nolist = groupcount > 0 ?
+ group_params->Nolist : default_params.Nolist;
+ peer_params.Email = groupcount > 0 ?
+ group_params->Email : default_params.Email;
+ peer_params.Comment = groupcount > 0 ?
+ group_params->Comment : default_params.Comment;
+ peer_params.Pattern = groupcount > 0 ?
+ group_params->Pattern : default_params.Pattern;
+ peer_params.Password = groupcount > 0 ?
+ group_params->Password : default_params.Password;
+ peer_params.Identd = groupcount > 0 ?
+ group_params->Identd : default_params.Identd;
+ peer_params.MaxCnx = groupcount > 0 ?
+ group_params->MaxCnx : default_params.MaxCnx;
+ peer_params.HoldTime = groupcount > 0 ?
+ group_params->HoldTime : default_params.HoldTime;
+
+ peer_params.Keysetbit = 0;
+
+ if ((word = RCreaddata (&linecount, F, &toolong)) == NULL)
+ {
+ syslog(L_ERROR, LEFT_BRACE, LogName, filename, linecount);
+ break;
+ }
+ /* left brace */
+ if (strncmp (word, "{", 1)) {
+ syslog(L_ERROR, LEFT_BRACE, LogName, filename, linecount);
+ free(word);
+ break;
+ }
+ else
+ free(word);
+ continue;
+ }
+
+ /* right brace */
+ if (!strncmp (word, "}", 1)) {
+ free(word);
+ if (peer_params.Label != NULL) {
+ RCadddata(data, &infocount, K_END_PEER, T_STRING, NULL);
+
+ /* Hostname defaults to label if not given */
+ if (peer_params.Name == NULL)
+ peer_params.Name = xstrdup(peer_params.Label);
+
+ for(r = q = RCCommaSplit(xstrdup(peer_params.Name)); *q != NULL; q++) {
+#ifdef HAVE_INET6
+ struct addrinfo *res, *res0, hints;
+ int gai_ret;
+#endif
+ (*count)++;
+
+ /* Grow the array */
+ j = rp - *list;
+ *list = xrealloc(*list, *count * sizeof(REMOTEHOST));
+ rp = *list + j;
+
+#ifdef HAVE_INET6
+ memset( &hints, 0, sizeof( hints ) );
+ hints.ai_socktype = SOCK_STREAM;
+ hints.ai_family = PF_UNSPEC;
+ if ((gai_ret = getaddrinfo(*q, NULL, &hints, &res0)) != 0) {
+ syslog(L_ERROR, "%s cant getaddrinfo %s %s", LogName, *q,
+ gai_strerror( gai_ret ) );
+ /* decrement *count, since we never got to add this record. */
+ (*count)--;
+ continue;
+ }
+ /* Count the addresses and see if we have to grow the list */
+ i = 0;
+ for (res = res0; res != NULL; res = res->ai_next)
+ i++;
+ /* Grow the array */
+ j = rp - *list;
+ *count += i - 1;
+ *list = xrealloc(*list, *count * sizeof(REMOTEHOST));
+ rp = *list + j;
+
+ /* Add all hosts */
+ for (res = res0; res != NULL; res = res->ai_next) {
+ (void)memcpy(&rp->Address, res->ai_addr, res->ai_addrlen);
+ rp->Name = xstrdup (*q);
+ rp->Label = xstrdup (peer_params.Label);
+ rp->Email = xstrdup(peer_params.Email);
+ rp->Comment = xstrdup(peer_params.Comment);
+ rp->Streaming = peer_params.Streaming;
+ rp->Skip = peer_params.Skip;
+ rp->NoResendId = peer_params.NoResendId;
+ rp->Nolist = peer_params.Nolist;
+ rp->Password = xstrdup(peer_params.Password);
+ rp->Identd = xstrdup(peer_params.Identd);
+ rp->Patterns = peer_params.Pattern != NULL ?
+ RCCommaSplit(xstrdup(peer_params.Pattern)) : NULL;
+ rp->MaxCnx = peer_params.MaxCnx;
+ rp->HoldTime = peer_params.HoldTime;
+ rp++;
+ }
+ freeaddrinfo(res0);
+#else /* HAVE_INET6 */
+ /* Was host specified as a dotted quad ? */
+ if (inet_aton(*q, &addr)) {
+ make_sin( (struct sockaddr_in *)&rp->Address, &addr );
+ rp->Name = xstrdup (*q);
+ rp->Label = xstrdup (peer_params.Label);
+ rp->Password = xstrdup(peer_params.Password);
+ rp->Identd = xstrdup(peer_params.Identd);
+ rp->Skip = peer_params.Skip;
+ rp->Streaming = peer_params.Streaming;
+ rp->NoResendId = peer_params.NoResendId;
+ rp->Nolist = peer_params.Nolist;
+ rp->Email = xstrdup(peer_params.Email);
+ rp->Comment = xstrdup(peer_params.Comment);
+ rp->Patterns = peer_params.Pattern != NULL ?
+ RCCommaSplit(xstrdup(peer_params.Pattern)) : NULL;
+ rp->MaxCnx = peer_params.MaxCnx;
+ rp->HoldTime = peer_params.HoldTime;
+ rp++;
+ continue;
+ }
+
+ /* Host specified as a text name ? */
+ if ((hp = gethostbyname(*q)) == NULL) {
+ syslog(L_ERROR, "%s cant gethostbyname %s %m", LogName, *q);
+ /* decrement *count, since we never got to add this record. */
+ (*count)--;
+ continue;
+ }
+
+ /* Count the adresses and see if we have to grow the list */
+ for (i = 0; hp->h_addr_list[i]; i++)
+ continue;
+ if (i == 0) {
+ syslog(L_ERROR, "%s no_address %s %m", LogName, *q);
+ continue;
+ }
+ if (i == 1) {
+ char **rr;
+ int t = 0;
+ /* Strange DNS ? try this.. */
+ for (rr = hp->h_aliases; *rr != 0; rr++) {
+ if (!inet_aton(*rr, &addr))
+ continue;
+ (*count)++;
+ /* Grow the array */
+ j = rp - *list;
+ *list = xrealloc(*list, *count * sizeof(REMOTEHOST));
+ rp = *list + j;
+
+ make_sin( (struct sockaddr_in *)&rp->Address, &addr );
+ rp->Name = xstrdup (*q);
+ rp->Label = xstrdup (peer_params.Label);
+ rp->Email = xstrdup(peer_params.Email);
+ rp->Comment = xstrdup(peer_params.Comment);
+ rp->Streaming = peer_params.Streaming;
+ rp->Skip = peer_params.Skip;
+ rp->NoResendId = peer_params.NoResendId;
+ rp->Nolist = peer_params.Nolist;
+ rp->Password = xstrdup(peer_params.Password);
+ rp->Identd = xstrdup(peer_params.Identd);
+ rp->Patterns = peer_params.Pattern != NULL ?
+ RCCommaSplit(xstrdup(peer_params.Pattern)) : NULL;
+ rp->MaxCnx = peer_params.MaxCnx;
+ rp->HoldTime = peer_params.HoldTime;
+ rp++;
+ t++;
+ }
+ if (t == 0) {
+ /* Just one, no need to grow. */
+ make_sin( (struct sockaddr_in *)&rp->Address,
+ (struct in_addr *)hp->h_addr_list[0] );
+ rp->Name = xstrdup (*q);
+ rp->Label = xstrdup (peer_params.Label);
+ rp->Email = xstrdup(peer_params.Email);
+ rp->Comment = xstrdup(peer_params.Comment);
+ rp->Streaming = peer_params.Streaming;
+ rp->Skip = peer_params.Skip;
+ rp->NoResendId = peer_params.NoResendId;
+ rp->Nolist = peer_params.Nolist;
+ rp->Password = xstrdup(peer_params.Password);
+ rp->Identd = xstrdup(peer_params.Identd);
+ rp->Patterns = peer_params.Pattern != NULL ?
+ RCCommaSplit(xstrdup(peer_params.Pattern)) : NULL;
+ rp->MaxCnx = peer_params.MaxCnx;
+ rp->HoldTime = peer_params.HoldTime;
+ rp++;
+ continue;
+ }
+ }
+ /* Grow the array */
+ j = rp - *list;
+ *count += i - 1;
+ *list = xrealloc(*list, *count * sizeof(REMOTEHOST));
+ rp = *list + j;
+
+ /* Add all the hosts. */
+ for (i = 0; hp->h_addr_list[i]; i++) {
+ make_sin( (struct sockaddr_in *)&rp->Address,
+ (struct in_addr *)hp->h_addr_list[i] );
+ rp->Name = xstrdup (*q);
+ rp->Label = xstrdup (peer_params.Label);
+ rp->Email = xstrdup(peer_params.Email);
+ rp->Comment = xstrdup(peer_params.Comment);
+ rp->Streaming = peer_params.Streaming;
+ rp->Skip = peer_params.Skip;
+ rp->NoResendId = peer_params.NoResendId;
+ rp->Nolist = peer_params.Nolist;
+ rp->Password = xstrdup(peer_params.Password);
+ rp->Identd = xstrdup(peer_params.Identd);
+ rp->Patterns = peer_params.Pattern != NULL ?
+ RCCommaSplit(xstrdup(peer_params.Pattern)) : NULL;
+ rp->MaxCnx = peer_params.MaxCnx;
+ rp->HoldTime = peer_params.HoldTime;
+ rp++;
+ }
+#endif /* HAVE_INET6 */
+ }
+ free(r[0]);
+ free(r);
+ peer_params.Label = NULL;
+ }
+ else if (groupcount > 0 && group_params->Label != NULL) {
+ RCadddata(data, &infocount, K_END_GROUP, T_STRING, NULL);
+ group_params->Label = NULL;
+ groupcount--;
+ if (groupcount == 0) {
+ /* We are now outside a group block. */
+ free(groups);
+ maxgroup = 0;
+ } else {
+ group_params--;
+ }
+ }
+ else {
+ syslog(L_ERROR, RIGHT_BRACE, LogName, linecount, filename);
+ }
+ continue;
+ }
+
+ /* streaming */
+ if (!strncmp (word, STREAMING, sizeof STREAMING)) {
+ free(word);
+ TEST_CONFIG(K_STREAM, bit);
+ if (bit) {
+ syslog(L_ERROR, DUPLICATE_KEY, LogName, filename, linecount);
+ break;
+ }
+ if ((word = RCreaddata (&linecount, F, &toolong)) == NULL) {
+ break;
+ }
+ if (!strcmp (word, "true"))
+ flag = true;
+ else
+ if (!strcmp (word, "false"))
+ flag = false;
+ else {
+ syslog(L_ERROR, MUST_BE_BOOL, LogName, filename, linecount);
+ break;
+ }
+ RCadddata(data, &infocount, K_STREAM, T_STRING, word);
+ if (peer_params.Label != NULL)
+ peer_params.Streaming = flag;
+ else
+ if (groupcount > 0 && group_params->Label != NULL)
+ group_params->Streaming = flag;
+ else
+ default_params.Streaming = flag;
+ SET_CONFIG(K_STREAM);
+ continue;
+ }
+
+ /* skip */
+ if (!strncmp (word, SKIP, sizeof SKIP)) {
+ free(word);
+ TEST_CONFIG(K_SKIP, bit);
+ if (bit) {
+ syslog(L_ERROR, DUPLICATE_KEY, LogName, filename, linecount);
+ break;
+ }
+ if ((word = RCreaddata (&linecount, F, &toolong)) == NULL) {
+ break;
+ }
+ if (!strcmp (word, "true"))
+ flag = true;
+ else
+ if (!strcmp (word, "false"))
+ flag = false;
+ else {
+ syslog(L_ERROR, MUST_BE_BOOL, LogName, filename, linecount);
+ break;
+ }
+ RCadddata(data, &infocount, K_SKIP, T_STRING, word);
+ if (peer_params.Label != NULL)
+ peer_params.Skip = flag;
+ else
+ if (groupcount > 0 && group_params->Label != NULL)
+ group_params->Skip = flag;
+ else
+ default_params.Skip = flag;
+ SET_CONFIG(K_SKIP);
+ continue;
+ }
+
+ /* noresendid */
+ if (!strncmp (word, NORESENDID, sizeof NORESENDID)) {
+ free(word);
+ TEST_CONFIG(K_NORESENDID, bit);
+ if (bit) {
+ syslog(L_ERROR, DUPLICATE_KEY, LogName, filename, linecount);
+ break;
+ }
+ if ((word = RCreaddata (&linecount, F, &toolong)) == NULL) {
+ break;
+ }
+ if (!strcmp (word, "true"))
+ flag = true;
+ else
+ if (!strcmp (word, "false"))
+ flag = false;
+ else {
+ syslog(L_ERROR, MUST_BE_BOOL, LogName, filename, linecount);
+ break;
+ }
+ RCadddata(data, &infocount, K_NORESENDID, T_STRING, word);
+ if (peer_params.Label != NULL)
+ peer_params.NoResendId = flag;
+ else
+ if (groupcount > 0 && group_params->Label != NULL)
+ group_params->NoResendId = flag;
+ else
+ default_params.NoResendId = flag;
+ SET_CONFIG(K_NORESENDID);
+ continue;
+ }
+
+ /* nolist */
+ if (!strncmp (word, NOLIST, sizeof NOLIST)) {
+ free(word);
+ TEST_CONFIG(K_NOLIST, bit);
+ if (bit) {
+ syslog(L_ERROR, DUPLICATE_KEY, LogName, filename, linecount);
+ break;
+ }
+ if ((word = RCreaddata (&linecount, F, &toolong)) == NULL) {
+ break;
+ }
+ if (!strcmp (word, "true"))
+ flag = true;
+ else
+ if (!strcmp (word, "false"))
+ flag = false;
+ else {
+ syslog(L_ERROR, MUST_BE_BOOL, LogName, filename, linecount);
+ break;
+ }
+ RCadddata(data, &infocount, K_NOLIST, T_STRING, word);
+ if (peer_params.Label != NULL)
+ peer_params.Nolist = flag;
+ else
+ if (groupcount > 0 && group_params->Label != NULL)
+ group_params->Nolist = flag;
+ else
+ default_params.Nolist = flag;
+ SET_CONFIG(K_NOLIST);
+ continue;
+ }
+
+ /* max-connections */
+ if (!strncmp (word, MAX_CONN, sizeof MAX_CONN)) {
+ int max;
+ free(word);
+ TEST_CONFIG(K_MAX_CONN, bit);
+ if (bit) {
+ syslog(L_ERROR, DUPLICATE_KEY, LogName, filename, linecount);
+ break;
+ }
+ if ((word = RCreaddata (&linecount, F, &toolong)) == NULL) {
+ break;
+ }
+ RCadddata(data, &infocount, K_MAX_CONN, T_STRING, word);
+ for (p = word; CTYPE(isdigit, *p) && *p != '\0'; p++);
+ if (!strcmp (word, "none") || !strcmp (word, "unlimited")) {
+ max = 0;
+ } else {
+ if (*p != '\0') {
+ syslog(L_ERROR, MUST_BE_INT, LogName, filename, linecount);
+ break;
+ }
+ max = atoi(word);
+ }
+ if (peer_params.Label != NULL)
+ peer_params.MaxCnx = max;
+ else
+ if (groupcount > 0 && group_params->Label != NULL)
+ group_params->MaxCnx = max;
+ else
+ default_params.MaxCnx = max;
+ SET_CONFIG(K_MAX_CONN);
+ continue;
+ }
+
+ /* hold-time */
+ if (!strncmp (word, HOLD_TIME, sizeof HOLD_TIME)) {
+ free(word);
+ TEST_CONFIG(K_HOLD_TIME, bit);
+ if (bit) {
+ syslog(L_ERROR, DUPLICATE_KEY, LogName, filename, linecount);
+ break;
+ }
+ if ((word = RCreaddata (&linecount, F, &toolong)) == NULL) {
+ break;
+ }
+ RCadddata(data, &infocount, K_HOLD_TIME, T_STRING, word);
+ for (p = word; CTYPE(isdigit, *p) && *p != '\0'; p++);
+ if (*p != '\0') {
+ syslog(L_ERROR, MUST_BE_INT, LogName, filename, linecount);
+ break;
+ }
+ if (peer_params.Label != NULL)
+ peer_params.HoldTime = atoi(word);
+ else
+ if (groupcount > 0 && group_params->Label != NULL)
+ group_params->HoldTime = atoi(word);
+ else
+ default_params.HoldTime = atoi(word);
+ SET_CONFIG(K_HOLD_TIME);
+ continue;
+ }
+
+ /* hostname */
+ if (!strncmp (word, HOSTNAME, sizeof HOSTNAME)) {
+ free(word);
+ TEST_CONFIG(K_HOSTNAME, bit);
+ if (bit) {
+ syslog(L_ERROR, DUPLICATE_KEY, LogName, filename, linecount);
+ break;
+ }
+ if ((word = RCreaddata (&linecount, F, &toolong)) == NULL) {
+ break;
+ }
+ RCadddata(data, &infocount, K_HOSTNAME, T_STRING, word);
+ peer_params.Name = word;
+ SET_CONFIG(K_HOSTNAME);
+ continue;
+ }
+
+ /* password */
+ if (!strncmp (word, PASSWORD, sizeof PASSWORD)) {
+ free(word);
+ TEST_CONFIG(K_PASSWORD, bit);
+ if (bit) {
+ syslog(L_ERROR, DUPLICATE_KEY, LogName, filename, linecount);
+ break;
+ }
+ if ((word = RCreaddata (&linecount, F, &toolong)) == NULL) {
+ break;
+ }
+ RCadddata(data, &infocount, K_PASSWORD, T_STRING, word);
+ if (peer_params.Label != NULL)
+ peer_params.Password = word;
+ else
+ if (groupcount > 0 && group_params->Label != NULL)
+ group_params->Password = word;
+ else
+ default_params.Password = word;
+ SET_CONFIG(K_PASSWORD);
+ continue;
+ }
+
+ /* identd */
+ if (!strncmp (word, IDENTD, sizeof IDENTD)) {
+ free(word);
+ TEST_CONFIG(K_IDENTD, bit);
+ if (bit) {
+ syslog(L_ERROR, DUPLICATE_KEY, LogName, filename, linecount);
+ break;
+ }
+ if ((word = RCreaddata (&linecount, F, &toolong)) == NULL) {
+ break;
+ }
+ RCadddata(data, &infocount, K_IDENTD, T_STRING, word);
+ if (peer_params.Label != NULL)
+ peer_params.Identd = word;
+ else
+ if (groupcount > 0 && group_params->Label != NULL)
+ group_params->Identd = word;
+ else
+ default_params.Identd = word;
+ SET_CONFIG(K_IDENTD);
+ continue;
+ }
+
+ /* patterns */
+ if (!strncmp (word, PATTERNS, sizeof PATTERNS)) {
+ TEST_CONFIG(K_PATTERNS, bit);
+ if (bit) {
+ syslog(L_ERROR, DUPLICATE_KEY, LogName, filename, linecount);
+ break;
+ }
+ free(word);
+ if ((word = RCreaddata (&linecount, F, &toolong)) == NULL) {
+ break;
+ }
+ RCadddata(data, &infocount, K_PATTERNS, T_STRING, word);
+ if (peer_params.Label != NULL)
+ peer_params.Pattern = word;
+ else
+ if (groupcount > 0 && group_params->Label != NULL)
+ group_params->Pattern = word;
+ else
+ default_params.Pattern = word;
+ SET_CONFIG(K_PATTERNS);
+ continue;
+ }
+
+ /* email */
+ if (!strncmp (word, EMAIL, sizeof EMAIL)) {
+ free(word);
+ TEST_CONFIG(K_EMAIL, bit);
+ if (bit) {
+ syslog(L_ERROR, DUPLICATE_KEY, LogName, filename, linecount);
+ break;
+ }
+ if ((word = RCreaddata (&linecount, F, &toolong)) == NULL) {
+ break;
+ }
+ RCadddata(data, &infocount, K_EMAIL, T_STRING, word);
+ if (peer_params.Label != NULL)
+ peer_params.Email = word;
+ else
+ if (groupcount > 0 && group_params->Label != NULL)
+ group_params->Email = word;
+ else
+ default_params.Email = word;
+ SET_CONFIG(K_EMAIL);
+ continue;
+ }
+
+ /* comment */
+ if (!strncmp (word, COMMENT, sizeof COMMENT)) {
+ free(word);
+ TEST_CONFIG(K_COMMENT, bit);
+ if (bit) {
+ syslog(L_ERROR, DUPLICATE_KEY, LogName, filename, linecount);
+ break;
+ }
+ if ((word = RCreaddata (&linecount, F, &toolong)) == NULL) {
+ break;
+ }
+ RCadddata(data, &infocount, K_COMMENT, T_STRING, word);
+ if (peer_params.Label != NULL)
+ peer_params.Comment = word;
+ else
+ if (groupcount > 0 && group_params->Label != NULL)
+ group_params->Comment = word;
+ else
+ default_params.Comment = word;
+ SET_CONFIG(K_COMMENT);
+ continue;
+ }
+
+ if (toolong)
+ syslog(L_ERROR, "%s line too long at %d: %s",
+ LogName, --linecount, filename);
+ else
+ syslog(L_ERROR, "%s Unknown value line %d: %s",
+ LogName, linecount, filename);
+ free(word);
+ break;
+ }
+ free(default_params.Email);
+ free(default_params.Comment);
+ RCadddata(data, &infocount, K_END, T_STRING, NULL);
+
+ if (feof (F)) {
+ if (peer_params.Label != NULL)
+ syslog(L_ERROR, INCOMPLETE_PEER, LogName, peer_params.Label,
+ filename, linecount);
+ if (groupcount > 0 && group_params->Label != NULL)
+ syslog(L_ERROR, INCOMPLETE_GROUP, LogName, group_params->Label,
+ filename, linecount);
+ }
+ else
+ syslog(L_ERROR, "%s Syntax error in %s at or before line %d", LogName,
+ filename, linecount);
+
+ if (Fclose(F) == EOF)
+ syslog(L_ERROR, "%s cant fclose %s %m", LogName, filename);
+
+ free(default_params.Password);
+ free(default_params.Identd);
+}
+
+
+/*
+** Indent a line with 3 * c blanks.
+** Used by RCwritelist().
+*/
+static void
+RCwritelistindent(FILE *F, int c)
+{
+ int i;
+
+ for (i = 0; i < c; i++)
+ fprintf(F, " ");
+}
+
+/*
+** Add double quotes around a string, if needed.
+** Used by RCwritelist().
+*/
+static void
+RCwritelistvalue(FILE *F, char *value)
+{
+ if (*value == '\0' || strchr (value, '\n') ||
+ strchr (value, ' ') || strchr (value, '\t'))
+ fprintf(F, "\"%s\"", value);
+ else
+ fprintf(F, "%s", value);
+}
+
+/*
+** Write the incoming configuration (memory->disk)
+*/
+static void UNUSED
+RCwritelist(char *filename)
+{
+ FILE *F;
+ int i;
+ int inc;
+ char *p;
+ char *q;
+ char *r;
+
+ if ((F = Fopen(filename, "w", TEMPORARYOPEN)) == NULL) {
+ syslog(L_FATAL, "%s cant write %s: %m", LogName, filename);
+ return;
+ }
+
+ /* Write a standard header.. */
+
+ /* Find the filename */
+ p = concatpath(innconf->pathetc, _PATH_INNDHOSTS);
+ for (r = q = p; *p; p++)
+ if (*p == '/')
+ q = p + 1;
+
+ fprintf (F, "## $Revision: 7751 $\n");
+ fprintf (F, "## %s - names and addresses that feed us news\n", q);
+ free(r);
+ fprintf (F, "##\n\n");
+
+ /* ... */
+
+ inc = 0;
+ for (i = 0; RCpeerlistfile[i].key != K_END; i++) {
+ switch (RCpeerlistfile[i].key) {
+ case K_BEGIN_PEER:
+ fputc ('\n', F);
+ RCwritelistindent (F, inc);
+ fprintf(F, "%s %s {\n", PEER, RCpeerlistfile[i].value);
+ inc++;
+ break;
+ case K_BEGIN_GROUP:
+ fputc ('\n', F);
+ RCwritelistindent (F, inc);
+ fprintf(F, "%s %s {\n", GROUP, RCpeerlistfile[i].value);
+ inc++;
+ break;
+ case K_END_PEER:
+ case K_END_GROUP:
+ inc--;
+ RCwritelistindent (F, inc);
+ fprintf(F, "}\n");
+ break;
+ case K_STREAM:
+ RCwritelistindent (F, inc);
+ fprintf(F, "%s\t", STREAMING);
+ RCwritelistvalue (F, RCpeerlistfile[i].value);
+ fputc ('\n', F);
+ break;
+ case K_SKIP:
+ RCwritelistindent (F, inc);
+ fprintf(F, "%s\t", SKIP);
+ RCwritelistvalue (F, RCpeerlistfile[i].value);
+ fputc ('\n', F);
+ break;
+ case K_NORESENDID:
+ RCwritelistindent (F, inc);
+ fprintf(F, "%s\t", NORESENDID);
+ RCwritelistvalue (F, RCpeerlistfile[i].value);
+ fputc ('\n', F);
+ break;
+ case K_NOLIST:
+ RCwritelistindent (F, inc);
+ fprintf(F, "%s\t", NOLIST);
+ RCwritelistvalue (F, RCpeerlistfile[i].value);
+ fputc ('\n', F);
+ break;
+ case K_HOSTNAME:
+ RCwritelistindent (F, inc);
+ fprintf(F, "%s\t", HOSTNAME);
+ RCwritelistvalue (F, RCpeerlistfile[i].value);
+ fputc ('\n', F);
+ break;
+ case K_MAX_CONN:
+ RCwritelistindent (F, inc);
+ fprintf(F, "%s\t", MAX_CONN);
+ RCwritelistvalue (F, RCpeerlistfile[i].value);
+ fputc ('\n', F);
+ break;
+ case K_HOLD_TIME:
+ RCwritelistindent (F, inc);
+ fprintf(F, "%s\t", HOLD_TIME);
+ RCwritelistvalue (F, RCpeerlistfile[i].value);
+ fputc ('\n', F);
+ break;
+ case K_PASSWORD:
+ RCwritelistindent (F, inc);
+ fprintf(F, "%s\t", PASSWORD);
+ RCwritelistvalue (F, RCpeerlistfile[i].value);
+ fputc ('\n', F);
+ break;
+ case K_IDENTD:
+ RCwritelistindent (F, inc);
+ fprintf(F, "%s\t", IDENTD);
+ RCwritelistvalue (F, RCpeerlistfile[i].value);
+ fputc ('\n', F);
+ break;
+ case K_EMAIL:
+ RCwritelistindent (F, inc);
+ fprintf(F, "%s\t", EMAIL);
+ RCwritelistvalue (F, RCpeerlistfile[i].value);
+ fputc ('\n', F);
+ break;
+ case K_PATTERNS:
+ RCwritelistindent (F, inc);
+ fprintf(F, "%s\t", PATTERNS);
+ RCwritelistvalue (F, RCpeerlistfile[i].value);
+ fputc ('\n', F);
+ break;
+ case K_COMMENT:
+ RCwritelistindent (F, inc);
+ fprintf(F, "%s\t", COMMENT);
+ RCwritelistvalue (F, RCpeerlistfile[i].value);
+ fputc ('\n', F);
+ break;
+ default:
+ fprintf(F, "# ***ERROR***\n");
+ }
+ }
+ if (Fclose(F) == EOF)
+ syslog(L_ERROR, "%s cant fclose %s %m", LogName, filename);
+
+}
+
+void
+RCreadlist(void)
+{
+ static char *INNDHOSTS = NULL;
+
+ if (INNDHOSTS == NULL)
+ INNDHOSTS = concatpath(innconf->pathetc, _PATH_INNDHOSTS);
+ StreamingOff = false;
+ RCreadfile(&RCpeerlistfile, &RCpeerlist, &RCnpeerlist, INNDHOSTS);
+ /* RCwritelist("/tmp/incoming.conf.new"); */
+}
+
+/*
+** Find the name of a remote host we've connected to.
+*/
+char *
+RChostname(const CHANNEL *cp)
+{
+ static char buff[SMBUF];
+ REMOTEHOST *rp;
+ int i;
+
+ for (rp = RCpeerlist, i = RCnpeerlist; --i >= 0; rp++)
+ if (RCaddressmatch(&cp->Address, &rp->Address))
+ return rp->Name;
+ strlcpy(buff, sprint_sockaddr((struct sockaddr *)&cp->Address),
+ sizeof(buff));
+ return buff;
+}
+
+/*
+** Find the label name of a remote host we've connected to.
+*/
+char *
+RClabelname(CHANNEL *cp) {
+ REMOTEHOST *rp;
+ int i;
+
+ for (rp = RCpeerlist, i = RCnpeerlist; --i >= 0; rp++) {
+ if (RCaddressmatch(&cp->Address, &rp->Address))
+ return rp->Label;
+ }
+ return NULL;
+}
+
+/*
+** Is the remote site allowed to post to this group?
+*/
+int
+RCcanpost(CHANNEL *cp, char *group)
+{
+ REMOTEHOST *rp;
+ char match;
+ char subvalue;
+ char **argv;
+ char *pat;
+ int i;
+
+ /* Connections from lc.c are from local nnrpd and should always work */
+ if (cp->Address.ss_family == 0)
+ return 1;
+
+ for (rp = RCpeerlist, i = RCnpeerlist; --i >= 0; rp++) {
+ if (!RCaddressmatch(&cp->Address, &rp->Address))
+ continue;
+ if (rp->Patterns == NULL)
+ break;
+ for (match = 0, argv = rp->Patterns; (pat = *argv++) != NULL; ) {
+ subvalue = (*pat != SUB_NEGATE) && (*pat != SUB_POISON) ?
+ 0 : *pat;
+ if (subvalue)
+ pat++;
+ if ((match != subvalue) && uwildmat(group, pat)) {
+ if (subvalue == SUB_POISON)
+ return -1;
+ match = subvalue;
+ }
+ }
+ return !match;
+ }
+ return 1;
+}
+
+
+/*
+** Create the channel.
+*/
+void
+RCsetup(int i)
+{
+#if defined(SO_REUSEADDR)
+ int on;
+#endif /* defined(SO_REUSEADDR) */
+ int j;
+ CHANNEL *rcchan;
+
+ /* This code is called only when inndstart is not being used */
+ if (i < 0) {
+#ifdef HAVE_INET6
+ syslog(L_FATAL, "%s innd MUST be started with inndstart", LogName);
+ exit(1);
+#else
+ /* Create a socket and name it. */
+ struct sockaddr_in server;
+
+ if ((i = socket(AF_INET, SOCK_STREAM, 0)) < 0) {
+ syslog(L_FATAL, "%s cant socket RCreader %m", LogName);
+ exit(1);
+ }
+#if defined(SO_REUSEADDR)
+ on = 1;
+ if (setsockopt(i, SOL_SOCKET, SO_REUSEADDR, &on, sizeof on) < 0)
+ syslog(L_ERROR, "%s cant setsockopt RCreader %m", LogName);
+#endif /* defined(SO_REUSEADDR) */
+ memset(&server, 0, sizeof server);
+ server.sin_port = htons(innconf->port);
+ server.sin_family = AF_INET;
+#ifdef HAVE_SOCKADDR_LEN
+ server.sin_len = sizeof( struct sockaddr_in );
+#endif
+ server.sin_addr.s_addr = htonl(INADDR_ANY);
+ if (innconf->bindaddress) {
+ if (!inet_aton(innconf->bindaddress, &server.sin_addr)) {
+ syslog(L_FATAL, "unable to determine bind ip (%s) %m",
+ innconf->bindaddress);
+ exit(1);
+ }
+ }
+ if (bind(i, (struct sockaddr *)&server, sizeof server) < 0) {
+ syslog(L_FATAL, "%s cant bind RCreader %m", LogName);
+ exit(1);
+ }
+#endif /* HAVE_INET6 */
+ }
+
+ /* Set it up to wait for connections. */
+ if (listen(i, MAXLISTEN) < 0) {
+ j = errno;
+ syslog(L_FATAL, "%s cant listen RCreader %m", LogName);
+ /* some IPv6 systems already listening on any address will
+ return EADDRINUSE when trying to listen on the IPv4 socket */
+ if (j == EADDRINUSE)
+ return;
+ exit(1);
+ }
+
+ rcchan = CHANcreate(i, CTremconn, CSwaiting, RCreader, RCwritedone);
+ syslog(L_NOTICE, "%s rcsetup %s", LogName, CHANname(rcchan));
+ RCHANadd(rcchan);
+
+ for (j = 0 ; j < chanlimit ; j++ ) {
+ if (RCchan[j] == NULL) {
+ break;
+ }
+ }
+ if (j < chanlimit) {
+ RCchan[j] = rcchan;
+ } else if (chanlimit == 0) {
+ /* assuming two file descriptors(AF_INET and AF_INET6) */
+ chanlimit = 2;
+ RCchan = xmalloc(chanlimit * sizeof(CHANNEL **));
+ for (j = 0 ; j < chanlimit ; j++ ) {
+ RCchan[j] = NULL;
+ }
+ RCchan[0] = rcchan;
+ } else {
+ /* extend to double size */
+ RCchan = xrealloc(RCchan, chanlimit * 2 * sizeof(CHANNEL **));
+ for (j = chanlimit ; j < chanlimit * 2 ; j++ ) {
+ RCchan[j] = NULL;
+ }
+ RCchan[chanlimit] = rcchan;
+ chanlimit *= 2;
+ }
+
+ /* Get the list of hosts we handle. */
+ RCreadlist();
+}
+
+
+/*
+** Cleanly shut down the channel.
+*/
+void
+RCclose(void)
+{
+ REMOTEHOST *rp;
+ int i;
+
+ for (i = 0 ; i < chanlimit ; i++) {
+ if (RCchan[i] != NULL) {
+ CHANclose(RCchan[i], CHANname(RCchan[i]));
+ } else {
+ break;
+ }
+ }
+ if (chanlimit != 0)
+ free(RCchan);
+ RCchan = NULL;
+ chanlimit = 0;
+ if (RCpeerlist) {
+ for (rp = RCpeerlist, i = RCnpeerlist; --i >= 0; rp++) {
+ free(rp->Name);
+ free(rp->Label);
+ free(rp->Email);
+ free(rp->Password);
+ free(rp->Identd);
+ free(rp->Comment);
+ if (rp->Patterns) {
+ free(rp->Patterns[0]);
+ free(rp->Patterns);
+ }
+ }
+ free(RCpeerlist);
+ RCpeerlist = NULL;
+ RCnpeerlist = 0;
+ }
+
+ if (RCpeerlistfile) {
+ for (i = 0; RCpeerlistfile[i].key != K_END; i++)
+ if (RCpeerlistfile[i].value != NULL)
+ free(RCpeerlistfile[i].value);
+ free(RCpeerlistfile);
+ RCpeerlistfile = NULL;
+ }
+}
--- /dev/null
+/* $Id: site.c 7748 2008-04-06 13:49:56Z iulius $
+**
+** Routines to implement site-feeding. Mainly working with channels to
+** do buffering and determine what to send.
+*/
+
+#include "config.h"
+#include "clibrary.h"
+
+#include "inn/innconf.h"
+#include "innd.h"
+
+
+static int SITEcount;
+static int SITEhead = NOSITE;
+static int SITEtail = NOSITE;
+static char SITEshell[] = _PATH_SH;
+
+
+/*
+** Called when input is ready to read. Shouldn't happen.
+*/
+static void
+SITEreader(CHANNEL *cp)
+{
+ syslog(L_ERROR, "%s internal SITEreader: %s", LogName, CHANname(cp));
+}
+
+
+/*
+** Called when write is done. No-op.
+*/
+static void
+SITEwritedone(CHANNEL *cp UNUSED)
+{
+}
+
+
+/*
+** Make a site start spooling.
+*/
+static bool
+SITEspool(SITE *sp, CHANNEL *cp)
+{
+ int i;
+ char buff[SPOOLNAMEBUFF];
+ char *name;
+
+ name = sp->SpoolName;
+ i = open(name, O_APPEND | O_CREAT | O_WRONLY, BATCHFILE_MODE);
+ if (i < 0 && errno == EISDIR) {
+ FileGlue(buff, sp->SpoolName, '/', "togo");
+ name = buff;
+ i = open(buff, O_APPEND | O_CREAT | O_WRONLY, BATCHFILE_MODE);
+ }
+ if (i < 0) {
+ i = errno;
+ syslog(L_ERROR, "%s cant open %s %m", sp->Name, name);
+ IOError("site batch file", i);
+ sp->Channel = NULL;
+ return false;
+ }
+ if (cp) {
+ if (cp->fd >= 0)
+ /* syslog(L_ERROR, "DEBUG ERROR SITEspool trashed:%d %s:%d", cp->fd, sp->Name, i);
+ CPU-eating bug, killed by kre. */
+ WCHANremove(cp);
+ RCHANremove(cp);
+ SCHANremove(cp);
+ close(cp->fd);
+ cp->fd = i;
+ return true;
+ }
+ sp->Channel = CHANcreate(i, CTfile, CSwriting, SITEreader, SITEwritedone);
+ if (sp->Channel == NULL) {
+ syslog(L_ERROR, "%s cant channel %m", sp->Name);
+ close(i);
+ return false;
+ }
+ WCHANset(sp->Channel, "", 0);
+ sp->Spooling = true;
+ return true;
+}
+
+
+/*
+** Delete a site from the file writing list. Can be called even if
+** site is not on the list.
+*/
+static void
+SITEunlink(SITE *sp)
+{
+ if (sp->Next != NOSITE || sp->Prev != NOSITE
+ || (SITEhead != NOSITE && sp == &Sites[SITEhead]))
+ SITEcount--;
+
+ if (sp->Next != NOSITE)
+ Sites[sp->Next].Prev = sp->Prev;
+ else if (SITEtail != NOSITE && sp == &Sites[SITEtail])
+ SITEtail = sp->Prev;
+
+ if (sp->Prev != NOSITE)
+ Sites[sp->Prev].Next = sp->Next;
+ else if (SITEhead != NOSITE && sp == &Sites[SITEhead])
+ SITEhead = sp->Next;
+
+ sp->Next = sp->Prev = NOSITE;
+}
+
+
+/*
+** Find the oldest "file feed" site and buffer it.
+*/
+static void
+SITEbufferoldest(void)
+{
+ SITE *sp;
+ struct buffer *bp;
+ struct buffer *out;
+
+ /* Get the oldest user of a file. */
+ if (SITEtail == NOSITE) {
+ syslog(L_ERROR, "%s internal no oldest site found", LogName);
+ SITEcount = 0;
+ return;
+ }
+
+ sp = &Sites[SITEtail];
+ SITEunlink(sp);
+
+ if (sp->Buffered) {
+ syslog(L_ERROR, "%s internal oldest (%s) was buffered", LogName,
+ sp->Name);
+ return;
+ }
+
+ if (sp->Type != FTfile) {
+ syslog(L_ERROR, "%s internal oldest (%s) not FTfile", LogName,
+ sp->Name);
+ return;
+ }
+
+ /* Write out what we can. */
+ WCHANflush(sp->Channel);
+
+ /* Get a buffer for the site. */
+ sp->Buffered = true;
+ bp = &sp->Buffer;
+ bp->used = 0;
+ bp->left = 0;
+ if (bp->size == 0) {
+ bp->size = sp->Flushpoint;
+ bp->data = xmalloc(bp->size);
+ }
+ else {
+ bp->size = sp->Flushpoint;
+ bp->data = xrealloc(bp->data, bp->size);
+ }
+
+ /* If there's any unwritten data, copy it. */
+ out = &sp->Channel->Out;
+ if (out->left) {
+ buffer_set(bp, &out->data[out->used], out->left);
+ out->left = 0;
+ }
+
+ /* Now close the original channel. */
+ CHANclose(sp->Channel, sp->Name);
+ sp->Channel = NULL;
+}
+
+/*
+ * * Bilge Site's Channel out buffer.
+ */
+static bool
+SITECHANbilge(SITE *sp)
+{
+ int fd;
+ int i;
+ char buff[SPOOLNAMEBUFF];
+ char *name;
+
+ name = sp->SpoolName;
+ fd = open(name, O_APPEND | O_CREAT | O_WRONLY, BATCHFILE_MODE);
+ if (fd < 0 && errno == EISDIR) {
+ FileGlue(buff, sp->SpoolName, '/', "togo");
+ name = buff;
+ fd = open(buff, O_APPEND | O_CREAT | O_WRONLY, BATCHFILE_MODE);
+ }
+ if (fd < 0) {
+ int oerrno = errno ;
+ syslog(L_ERROR, "%s cant open %s %m", sp->Name, name);
+ IOError("site batch file",oerrno);
+ sp->Channel = NULL;
+ return false;
+ }
+ while (sp->Channel->Out.left > 0) {
+ i = write(fd, &sp->Channel->Out.data[sp->Channel->Out.used],
+ sp->Channel->Out.left);
+ if(i <= 0) {
+ syslog(L_ERROR,"%s cant spool count %lu", CHANname(sp->Channel),
+ (unsigned long) sp->Channel->Out.left);
+ close(fd);
+ return false;
+ }
+ sp->Channel->Out.left -= i;
+ sp->Channel->Out.used += i;
+ }
+ close(fd);
+ free(sp->Channel->Out.data);
+ sp->Channel->Out.data = xmalloc(SMBUF);
+ sp->Channel->Out.size = SMBUF;
+ sp->Channel->Out.left = 0;
+ sp->Channel->Out.used = 0;
+ return true;
+}
+
+/*
+** Check if we need to write out the site's buffer. If we're buffered
+** or the feed is backed up, this gets a bit complicated.
+*/
+static void
+SITEflushcheck(SITE *sp, struct buffer *bp)
+{
+ int i;
+ CHANNEL *cp;
+
+ /* If we're buffered, and we hit the flushpoint, do an LRU. */
+ if (sp->Buffered) {
+ if (bp->left < sp->Flushpoint)
+ return;
+ while (SITEcount >= MaxOutgoing)
+ SITEbufferoldest();
+ if (!SITEsetup(sp) || sp->Buffered) {
+ syslog(L_ERROR, "%s cant unbuffer %m", sp->Name);
+ return;
+ }
+ WCHANsetfrombuffer(sp->Channel, bp);
+ WCHANadd(sp->Channel);
+ /* Reset buffer; data has been moved. */
+ buffer_set(bp, "", 0);
+ }
+
+ if (PROCneedscan)
+ PROCscan();
+
+ /* Handle buffering. */
+ cp = sp->Channel;
+ i = cp->Out.left;
+ if (i < sp->StopWriting)
+ WCHANremove(cp);
+ if ((sp->StartWriting == 0 || i > sp->StartWriting)
+ && !CHANsleeping(cp)) {
+ if (sp->Type == FTchannel) { /* channel feed, try the write */
+ int j;
+ if (bp->left == 0)
+ return;
+ j = write(cp->fd, &bp->data[bp->used], bp->left);
+ if (j > 0) {
+ bp->left -= j;
+ bp->used += j;
+ i = cp->Out.left;
+ /* Since we just had a successful write, we need to clear the
+ * channel's error counts. - dave@jetcafe.org */
+ cp->BadWrites = 0;
+ cp->BlockedWrites = 0;
+ }
+ if (bp->left <= 0) {
+ /* reset Used, Left on bp, keep channel buffer size from
+ exploding. */
+ bp->used = bp->left = 0;
+ WCHANremove(cp);
+ } else
+ WCHANadd(cp);
+ }
+ else
+ WCHANadd(cp);
+ }
+
+ cp->LastActive = Now.time;
+
+ /* If we're a channel that's getting big, see if we need to spool. */
+ if (sp->Type == FTfile || sp->StartSpooling == 0 || i < sp->StartSpooling)
+ return;
+
+ syslog(L_ERROR, "%s spooling %d bytes", sp->Name, i);
+ if(!SITECHANbilge(sp)) {
+ syslog(L_ERROR, "%s overflow %d bytes", sp->Name, i);
+ return;
+ }
+}
+
+
+/*
+** Send a control line to an exploder.
+*/
+void
+SITEwrite(SITE *sp, const char *text)
+{
+ static char PREFIX[] = { EXP_CONTROL, '\0' };
+ struct buffer *bp;
+
+ if (sp->Buffered)
+ bp = &sp->Buffer;
+ else {
+ if (sp->Channel == NULL)
+ return;
+ sp->Channel->LastActive = Now.time;
+ bp = &sp->Channel->Out;
+ }
+ buffer_append(bp, PREFIX, strlen(PREFIX));
+ buffer_append(bp, text, strlen(text));
+ buffer_append(bp, "\n", 1);
+ if (sp->Channel != NULL)
+ WCHANadd(sp->Channel);
+}
+
+
+/*
+** Send the desired data about an article down a channel.
+*/
+static void
+SITEwritefromflags(SITE *sp, ARTDATA *Data)
+{
+ HDRCONTENT *hc = Data->HdrContent;
+ static char ITEMSEP[] = " ";
+ static char NL[] = "\n";
+ char pbuff[12];
+ char *p;
+ bool Dirty;
+ struct buffer *bp;
+ SITE *spx;
+ int i;
+
+ if (sp->Buffered)
+ bp = &sp->Buffer;
+ else {
+ /* This should not happen, but if we tried to spool and failed,
+ * e.g., because of a bad F param for this site, we can get
+ * into this state. We already logged a message so give up. */
+ if (sp->Channel == NULL)
+ return;
+ bp = &sp->Channel->Out;
+ }
+ for (Dirty = false, p = sp->FileFlags; *p; p++) {
+ switch (*p) {
+ default:
+ syslog(L_ERROR, "%s internal SITEwritefromflags %c", sp->Name, *p);
+ continue;
+ case FEED_BYTESIZE:
+ if (Dirty)
+ buffer_append(bp, ITEMSEP, strlen(ITEMSEP));
+ buffer_append(bp, Data->Bytes + sizeof("Bytes: ") - 1,
+ Data->BytesLength);
+ break;
+ case FEED_FULLNAME:
+ case FEED_NAME:
+ if (Dirty)
+ buffer_append(bp, ITEMSEP, strlen(ITEMSEP));
+ buffer_append(bp, Data->TokenText, sizeof(TOKEN) * 2 + 2);
+ break;
+ case FEED_HASH:
+ if (Dirty)
+ buffer_append(bp, ITEMSEP, strlen(ITEMSEP));
+ buffer_append(bp, "[", 1);
+ buffer_append(bp, HashToText(*(Data->Hash)), sizeof(HASH)*2);
+ buffer_append(bp, "]", 1);
+ break;
+ case FEED_HDR_DISTRIB:
+ if (Dirty)
+ buffer_append(bp, ITEMSEP, strlen(ITEMSEP));
+ buffer_append(bp, HDR(HDR__DISTRIBUTION),
+ HDR_LEN(HDR__DISTRIBUTION));
+ break;
+ case FEED_HDR_NEWSGROUP:
+ if (Dirty)
+ buffer_append(bp, ITEMSEP, strlen(ITEMSEP));
+ buffer_append(bp, HDR(HDR__NEWSGROUPS), HDR_LEN(HDR__NEWSGROUPS));
+ break;
+ case FEED_HEADERS:
+ if (Dirty)
+ buffer_append(bp, NL, strlen(NL));
+ buffer_append(bp, Data->Headers.data, Data->Headers.left);
+ break;
+ case FEED_OVERVIEW:
+ if (Dirty)
+ buffer_append(bp, ITEMSEP, strlen(ITEMSEP));
+ buffer_append(bp, Data->Overview.data, Data->Overview.left);
+ break;
+ case FEED_PATH:
+ if (Dirty)
+ buffer_append(bp, ITEMSEP, strlen(ITEMSEP));
+ if (!Data->Hassamepath || Data->AddAlias || Pathcluster.used) {
+ if (Pathcluster.used)
+ buffer_append(bp, Pathcluster.data, Pathcluster.used);
+ buffer_append(bp, Path.data, Path.used);
+ if (Data->AddAlias)
+ buffer_append(bp, Pathalias.data, Pathalias.used);
+ }
+ if (Data->Hassamecluster)
+ buffer_append(bp, HDR(HDR__PATH) + Pathcluster.used,
+ HDR_LEN(HDR__PATH) - Pathcluster.used);
+ else
+ buffer_append(bp, HDR(HDR__PATH), HDR_LEN(HDR__PATH));
+ break;
+ case FEED_REPLIC:
+ if (Dirty)
+ buffer_append(bp, ITEMSEP, strlen(ITEMSEP));
+ buffer_append(bp, Data->Replic, Data->ReplicLength);
+ break;
+ case FEED_STOREDGROUP:
+ if (Dirty)
+ buffer_append(bp, ITEMSEP, strlen(ITEMSEP));
+ buffer_append(bp, Data->Newsgroups.List[0],
+ Data->StoredGroupLength);
+ break;
+ case FEED_TIMERECEIVED:
+ if (Dirty)
+ buffer_append(bp, ITEMSEP, strlen(ITEMSEP));
+ snprintf(pbuff, sizeof(pbuff), "%ld", (long) Data->Arrived);
+ buffer_append(bp, pbuff, strlen(pbuff));
+ break;
+ case FEED_TIMEPOSTED:
+ if (Dirty)
+ buffer_append(bp, ITEMSEP, strlen(ITEMSEP));
+ snprintf(pbuff, sizeof(pbuff), "%ld", (long) Data->Posted);
+ buffer_append(bp, pbuff, strlen(pbuff));
+ break;
+ case FEED_TIMEEXPIRED:
+ if (Dirty)
+ buffer_append(bp, ITEMSEP, strlen(ITEMSEP));
+ snprintf(pbuff, sizeof(pbuff), "%ld", (long) Data->Expires);
+ buffer_append(bp, pbuff, strlen(pbuff));
+ break;
+ case FEED_MESSAGEID:
+ if (Dirty)
+ buffer_append(bp, ITEMSEP, strlen(ITEMSEP));
+ buffer_append(bp, HDR(HDR__MESSAGE_ID), HDR_LEN(HDR__MESSAGE_ID));
+ break;
+ case FEED_FNLNAMES:
+ if (sp->FNLnames.data) {
+ /* Funnel; write names of our sites that got it. */
+ if (Dirty)
+ buffer_append(bp, ITEMSEP, strlen(ITEMSEP));
+ buffer_append(bp, sp->FNLnames.data, sp->FNLnames.used);
+ }
+ else {
+ /* Not funnel; write names of all sites that got it. */
+ for (spx = Sites, i = nSites; --i >= 0; spx++)
+ if (spx->Sendit) {
+ if (Dirty)
+ buffer_append(bp, ITEMSEP, strlen(ITEMSEP));
+ buffer_append(bp, spx->Name, spx->NameLength);
+ Dirty = true;
+ }
+ }
+ break;
+ case FEED_NEWSGROUP:
+ if (Dirty)
+ buffer_append(bp, ITEMSEP, strlen(ITEMSEP));
+ if (sp->ng)
+ buffer_append(bp, sp->ng->Name, sp->ng->NameLength);
+ else
+ buffer_append(bp, "?", 1);
+ break;
+ case FEED_SITE:
+ if (Dirty)
+ buffer_append(bp, ITEMSEP, strlen(ITEMSEP));
+ buffer_append(bp, Data->Feedsite, Data->FeedsiteLength);
+ break;
+ }
+ Dirty = true;
+ }
+ if (Dirty) {
+ buffer_append(bp, "\n", 1);
+ SITEflushcheck(sp, bp);
+ }
+}
+
+
+/*
+** Send one article to a site.
+*/
+void
+SITEsend(SITE *sp, ARTDATA *Data)
+{
+ int i;
+ char *p;
+ char *temp;
+ char buff[BUFSIZ];
+ char * argv[MAX_BUILTIN_ARGV];
+
+ switch (sp->Type) {
+ default:
+ syslog(L_ERROR, "%s internal SITEsend type %d", sp->Name, sp->Type);
+ break;
+ case FTlogonly:
+ break;
+ case FTfunnel:
+ syslog(L_ERROR, "%s funnel_send", sp->Name);
+ break;
+ case FTfile:
+ case FTchannel:
+ case FTexploder:
+ SITEwritefromflags(sp, Data);
+ break;
+ case FTprogram:
+ /* Set up the argument vector. */
+ if (sp->FNLwantsnames) {
+ i = strlen(sp->Param) + sp->FNLnames.used;
+ if (i + (sizeof(TOKEN) * 2) + 3 >= sizeof buff) {
+ syslog(L_ERROR, "%s toolong need %lu for %s",
+ sp->Name, (unsigned long) (i + (sizeof(TOKEN) * 2) + 3),
+ sp->Name);
+ break;
+ }
+ temp = xmalloc(i + 1);
+ p = strchr(sp->Param, '*');
+ *p = '\0';
+ strlcpy(temp, sp->Param, i + 1);
+ strlcat(temp, sp->FNLnames.data, i + 1);
+ strlcat(temp, &p[1], i + 1);
+ *p = '*';
+ snprintf(buff, sizeof(buff), temp, Data->TokenText);
+ free(temp);
+ }
+ else
+ snprintf(buff, sizeof(buff), sp->Param, Data->TokenText);
+
+ if (NeedShell(buff, (const char **)argv, (const char **)ARRAY_END(argv))) {
+ argv[0] = SITEshell;
+ argv[1] = (char *) "-c";
+ argv[2] = buff;
+ argv[3] = NULL;
+ }
+
+ /* Start the process. */
+ i = Spawn(sp->Nice, 0, (int)fileno(Errlog), (int)fileno(Errlog), argv);
+ if (i >= 0)
+ PROCwatch(i, -1);
+ break;
+ }
+}
+
+
+/*
+** The channel was sleeping because we had to spool our output to
+** a file. Flush and restart.
+*/
+static void
+SITEspoolwake(CHANNEL *cp)
+{
+ SITE *sp;
+ int *ip;
+
+ ip = (int *) cp->Argument;
+ sp = &Sites[*ip];
+ free(cp->Argument);
+ cp->Argument = NULL;
+ if (sp->Channel != cp) {
+ syslog(L_ERROR, "%s internal SITEspoolwake %s got %d, not %d",
+ LogName, sp->Name, cp->fd, sp->Channel->fd);
+ return;
+ }
+ syslog(L_NOTICE, "%s spoolwake", sp->Name);
+ SITEflush(sp, true);
+}
+
+
+/*
+** Start up a process for a channel, or a spool to a file if we can't.
+** Create a channel for the site to talk to.
+*/
+static bool
+SITEstartprocess(SITE *sp)
+{
+ pid_t i;
+ char *argv[MAX_BUILTIN_ARGV];
+ char *process;
+ int *ip;
+ int pan[2];
+
+#if HAVE_SOCKETPAIR
+ /* Create a socketpair. */
+ if (socketpair(PF_UNIX, SOCK_STREAM, 0, pan) < 0) {
+ syslog(L_ERROR, "%s cant socketpair %m", sp->Name);
+ return false;
+ }
+#else
+ /* Create a pipe. */
+ if (pipe(pan) < 0) {
+ syslog(L_ERROR, "%s cant pipe %m", sp->Name);
+ return false;
+ }
+#endif
+ close_on_exec(pan[PIPE_WRITE], true);
+
+ /* Set up the argument vector. */
+ process = xstrdup(sp->Param);
+ if (NeedShell(process, (const char **)argv, (const char **)ARRAY_END(argv))) {
+ argv[0] = SITEshell;
+ argv[1] = (char *) "-c";
+ argv[2] = process;
+ argv[3] = NULL;
+ }
+
+ /* Fork a child. */
+ i = Spawn(sp->Nice, pan[PIPE_READ], (int)fileno(Errlog),
+ (int)fileno(Errlog), argv);
+ if (i > 0) {
+ sp->pid = i;
+ sp->Spooling = false;
+ sp->Process = PROCwatch(i, sp - Sites);
+ close(pan[PIPE_READ]);
+ sp->Channel = CHANcreate(pan[PIPE_WRITE],
+ sp->Type == FTchannel ? CTprocess : CTexploder,
+ CSwriting, SITEreader, SITEwritedone);
+ free(process);
+ return true;
+ }
+
+ /* Error. Switch to spooling. */
+ syslog(L_ERROR, "%s cant spawn spooling %m", sp->Name);
+ close(pan[PIPE_WRITE]);
+ close(pan[PIPE_READ]);
+ free(process);
+ if (!SITEspool(sp, (CHANNEL *)NULL))
+ return false;
+
+ /* We'll try to restart the channel later. */
+ syslog(L_ERROR, "%s cant spawn spooling %m", sp->Name);
+ ip = xmalloc(sizeof(int));
+ *ip = sp - Sites;
+ SCHANadd(sp->Channel, Now.time + innconf->chanretrytime, NULL,
+ SITEspoolwake, ip);
+ return true;
+}
+
+
+/*
+** Set up a site for internal buffering.
+*/
+static void
+SITEbuffer(SITE *sp)
+{
+ struct buffer *bp;
+
+ SITEunlink(sp);
+ sp->Buffered = true;
+ sp->Channel = NULL;
+ bp = &sp->Buffer;
+ buffer_resize(bp, sp->Flushpoint);
+ buffer_set(bp, "", 0);
+ syslog(L_NOTICE, "%s buffered", sp->Name);
+}
+
+
+/*
+** Link a site at the head of the "currently writing to a file" list
+*/
+static void
+SITEmovetohead(SITE *sp)
+{
+ if ((SITEhead == NOSITE) && ((sp->Next != NOSITE) || (sp->Prev != NOSITE)))
+ SITEunlink(sp);
+
+ if ((sp->Next = SITEhead) != NOSITE)
+ Sites[SITEhead].Prev = sp - Sites;
+ sp->Prev = NOSITE;
+
+ SITEhead = sp - Sites;
+ if (SITEtail == NOSITE)
+ SITEtail = sp - Sites;
+
+ SITEcount++;
+}
+
+
+/*
+** Set up a site's feed. This means opening a file or channel if needed.
+*/
+bool
+SITEsetup(SITE *sp)
+{
+ int fd;
+ int oerrno;
+
+ switch (sp->Type) {
+ default:
+ syslog(L_ERROR, "%s internal SITEsetup %d",
+ sp->Name, sp->Type);
+ return false;
+ case FTfunnel:
+ case FTlogonly:
+ case FTprogram:
+ /* Nothing to do here. */
+ break;
+ case FTfile:
+ if (SITEcount >= MaxOutgoing)
+ SITEbuffer(sp);
+ else {
+ sp->Buffered = false;
+ fd = open(sp->Param, O_APPEND | O_CREAT | O_WRONLY, BATCHFILE_MODE);
+ if (fd < 0) {
+ if (errno == EMFILE) {
+ syslog(L_ERROR, "%s cant open %s %m", sp->Name, sp->Param);
+ SITEbuffer(sp);
+ break;
+ }
+ oerrno = errno;
+ syslog(L_NOTICE, "%s cant open %s %m", sp->Name, sp->Param);
+ IOError("site file", oerrno);
+ return false;
+ }
+ SITEmovetohead(sp);
+ sp->Channel = CHANcreate(fd, CTfile, CSwriting,
+ SITEreader, SITEwritedone);
+ syslog(L_NOTICE, "%s opened %s", sp->Name, CHANname(sp->Channel));
+ WCHANset(sp->Channel, "", 0);
+ }
+ break;
+ case FTchannel:
+ case FTexploder:
+ if (!SITEstartprocess(sp))
+ return false;
+ syslog(L_NOTICE, "%s spawned %s", sp->Name, CHANname(sp->Channel));
+ WCHANset(sp->Channel, "", 0);
+ WCHANadd(sp->Channel);
+ break;
+ }
+ return true;
+}
+
+
+/*
+** A site's channel process died; restart it.
+*/
+void
+SITEprocdied(SITE *sp, int process, PROCESS *pp)
+{
+ syslog(pp->Status ? L_ERROR : L_NOTICE, "%s exit %d elapsed %ld pid %ld",
+ sp->Name ? sp->Name : "?", pp->Status,
+ (long) (pp->Collected - pp->Started), (long) pp->Pid);
+ if (sp->Process != process || sp->Name == NULL)
+ /* We already started a new process for this channel
+ * or this site has been dropped. */
+ return;
+ if (sp->Channel != NULL)
+ CHANclose(sp->Channel, CHANname(sp->Channel));
+ sp->Working = SITEsetup(sp);
+ if (!sp->Working) {
+ syslog(L_ERROR, "%s cant restart %m", sp->Name);
+ return;
+ }
+ syslog(L_NOTICE, "%s restarted", sp->Name);
+}
+
+/*
+** A channel is about to be closed; see if any site cares.
+*/
+void
+SITEchanclose(CHANNEL *cp)
+{
+ int i;
+ SITE *sp;
+ int *ip;
+
+ for (i = nSites, sp = Sites; --i >= 0; sp++)
+ if (sp->Channel == cp) {
+ /* Found the site that has this channel. Start that
+ * site spooling, copy any data that might be pending,
+ * and arrange to retry later. */
+ if (!SITEspool(sp, (CHANNEL *)NULL)) {
+ syslog(L_ERROR, "%s loss %lu bytes", sp->Name,
+ (unsigned long) cp->Out.left);
+ return;
+ }
+ WCHANsetfrombuffer(sp->Channel, &cp->Out);
+ WCHANadd(sp->Channel);
+ ip = xmalloc(sizeof(int));
+ *ip = sp - Sites;
+ SCHANadd(sp->Channel, Now.time + innconf->chanretrytime, NULL,
+ SITEspoolwake, ip);
+ break;
+ }
+}
+
+
+/*
+** Flush any pending data waiting to be sent.
+*/
+void
+SITEflush(SITE *sp, const bool Restart)
+{
+ CHANNEL *cp;
+ struct buffer *out;
+ int count;
+
+ if (sp->Name == NULL)
+ return;
+
+ if (Restart)
+ SITEforward(sp, "flush");
+
+ switch (sp->Type) {
+ default:
+ syslog(L_ERROR, "%s internal SITEflush %d", sp->Name, sp->Type);
+ return;
+
+ case FTlogonly:
+ case FTprogram:
+ case FTfunnel:
+ /* Nothing to do here. */
+ return;
+
+ case FTchannel:
+ case FTexploder:
+ /* If spooling, close the file right now -- documented behavior. */
+ if (sp->Spooling && (cp = sp->Channel) != NULL) {
+ WCHANflush(cp);
+ CHANclose(cp, CHANname(cp));
+ sp->Channel = NULL;
+ }
+ break;
+
+ case FTfile:
+ /* We must ensure we have a file open for this site, so if
+ * we're buffered we HACK and pretend we have no sites
+ * for a moment. */
+ if (sp->Buffered) {
+ count = SITEcount;
+ SITEcount = 0;
+ if (!SITEsetup(sp) || sp->Buffered)
+ syslog(L_ERROR, "%s cant unbuffer to flush", sp->Name);
+ else
+ buffer_swap(&sp->Buffer, &sp->Channel->Out);
+ SITEcount += count;
+ }
+ break;
+ }
+
+ /* We're only dealing with files and channels now. */
+ if ((cp = sp->Channel) != NULL)
+ WCHANflush(cp);
+
+ /* Restart the site, copy any pending data. */
+ if (Restart) {
+ if (!SITEsetup(sp))
+ syslog(L_ERROR, "%s cant restart %m", sp->Name);
+ else if (cp != NULL) {
+ if (sp->Buffered) {
+ /* SITEsetup had to buffer us; save any residue. */
+ out = &cp->Out;
+ if (out->left)
+ buffer_set(&sp->Buffer, &out->data[out->used], out->left);
+ }
+ else
+ WCHANsetfrombuffer(sp->Channel, &cp->Out);
+ }
+ }
+ else if (cp != NULL && cp->Out.left) {
+ if (sp->Type == FTfile || sp->Spooling) {
+ /* Can't flush a file? Hopeless. */
+ syslog(L_ERROR, "%s dataloss %lu", sp->Name,
+ (unsigned long) cp->Out.left);
+ return;
+ }
+ /* Must be a working channel; spool and retry. */
+ syslog(L_ERROR, "%s spooling %lu bytes", sp->Name,
+ (unsigned long) cp->Out.left);
+ if (SITEspool(sp, cp))
+ SITEflush(sp, false);
+ return;
+ }
+
+ /* Close the old channel if it was open. */
+ if (cp != NULL) {
+ /* Make sure we have no dangling pointers to it. */
+ if (!Restart)
+ sp->Channel = NULL;
+ CHANclose(cp, sp->Name);
+ if (sp->Type == FTfile)
+ SITEunlink(sp);
+ }
+}
+
+
+/*
+** Flush all sites.
+*/
+void
+SITEflushall(const bool Restart)
+{
+ int i;
+ SITE *sp;
+
+ for (i = nSites, sp = Sites; --i >= 0; sp++)
+ if (sp->Name)
+ SITEflush(sp, Restart);
+}
+
+
+/*
+** Run down the site's pattern list and see if it wants the specified
+** newsgroup.
+*/
+bool
+SITEwantsgroup(SITE *sp, char *name)
+{
+ bool match;
+ bool subvalue;
+ char *pat;
+ char **argv;
+
+ match = SUB_DEFAULT;
+ if (ME.Patterns) {
+ for (argv = ME.Patterns; (pat = *argv++) != NULL; ) {
+ subvalue = *pat != SUB_NEGATE && *pat != SUB_POISON;
+ if (!subvalue)
+ pat++;
+ if ((match != subvalue) && uwildmat(name, pat))
+ match = subvalue;
+ }
+ }
+ for (argv = sp->Patterns; (pat = *argv++) != NULL; ) {
+ subvalue = *pat != SUB_NEGATE && *pat != SUB_POISON;
+ if (!subvalue)
+ pat++;
+ if ((match != subvalue) && uwildmat(name, pat))
+ match = subvalue;
+ }
+ return match;
+}
+
+
+/*
+** Run down the site's pattern list and see if specified newsgroup is
+** considered poison.
+*/
+bool
+SITEpoisongroup(SITE *sp, char *name)
+{
+ bool match;
+ bool poisonvalue;
+ char *pat;
+ char **argv;
+
+ match = SUB_DEFAULT;
+ if (ME.Patterns) {
+ for (argv = ME.Patterns; (pat = *argv++) != NULL; ) {
+ poisonvalue = *pat == SUB_POISON;
+ if (*pat == SUB_NEGATE || *pat == SUB_POISON)
+ pat++;
+ if (uwildmat(name, pat))
+ match = poisonvalue;
+ }
+ }
+ for (argv = sp->Patterns; (pat = *argv++) != NULL; ) {
+ poisonvalue = *pat == SUB_POISON;
+ if (*pat == SUB_NEGATE || *pat == SUB_POISON)
+ pat++;
+ if (uwildmat(name, pat))
+ match = poisonvalue;
+ }
+ return match;
+}
+
+
+/*
+** Find a site.
+*/
+SITE *
+SITEfind(const char *p)
+{
+ int i;
+ SITE *sp;
+
+ for (i = nSites, sp = Sites; --i >= 0; sp++)
+ if (sp->Name && strcasecmp(p, sp->Name) == 0)
+ return sp;
+ return NULL;
+}
+
+
+/*
+** Find the next site that matches this site.
+*/
+SITE *
+SITEfindnext(const char *p, SITE *sp)
+{
+ SITE *end;
+
+ for (sp++, end = &Sites[nSites]; sp < end; sp++)
+ if (sp->Name && strcasecmp(p, sp->Name) == 0)
+ return sp;
+ return NULL;
+}
+
+
+/*
+** Close a site down.
+*/
+void
+SITEfree(SITE *sp)
+{
+ SITE *s;
+ HASHFEEDLIST *hf, *hn;
+ int new;
+ int i;
+
+ if (sp->Channel) {
+ CHANclose(sp->Channel, CHANname(sp->Channel));
+ sp->Channel = NULL;
+ }
+
+ SITEunlink(sp);
+
+ sp->Name = NULL;
+ if (sp->Process > 0) {
+ /* Kill the backpointer so PROCdied won't call us. */
+ PROCunwatch(sp->Process);
+ sp->Process = -1;
+ }
+ if (sp->Entry) {
+ free(sp->Entry);
+ sp->Entry = NULL;
+ }
+ if (sp->Originator) {
+ free(sp->Originator);
+ sp->Originator = NULL;
+ }
+ if (sp->Param) {
+ free(sp->Param);
+ sp->Param = NULL;
+ }
+ if (sp->SpoolName) {
+ free(sp->SpoolName);
+ sp->SpoolName = NULL;
+ }
+ if (sp->Patterns) {
+ free(sp->Patterns);
+ sp->Patterns = NULL;
+ }
+ if (sp->Exclusions) {
+ free(sp->Exclusions);
+ sp->Exclusions = NULL;
+ }
+ if (sp->Distributions) {
+ free(sp->Distributions);
+ sp->Distributions = NULL;
+ }
+ if (sp->Buffer.data) {
+ free(sp->Buffer.data);
+ sp->Buffer.data = NULL;
+ sp->Buffer.size = 0;
+ }
+ if (sp->FNLnames.data) {
+ free(sp->FNLnames.data);
+ sp->FNLnames.data = NULL;
+ sp->FNLnames.size = 0;
+ }
+ if (sp->HashFeedList) {
+ for (hf = sp->HashFeedList; hf; hf = hn) {
+ hn = hf->next;
+ free(hf);
+ }
+ sp->HashFeedList = NULL;
+ }
+
+ /* If this site was a master, find a new one. */
+ if (sp->IsMaster) {
+ for (new = NOSITE, s = Sites, i = nSites; --i >= 0; s++)
+ if (&Sites[s->Master] == sp) {
+ if (new == NOSITE) {
+ s->Master = NOSITE;
+ s->IsMaster = true;
+ new = s - Sites;
+ }
+ else
+ s->Master = new;
+ }
+ sp->IsMaster = false;
+ }
+}
+
+
+/*
+** If a site is an exploder or funnels into one, forward a command
+** to it.
+*/
+void
+SITEforward(SITE *sp, const char *text)
+{
+ SITE *fsp;
+ char buff[SMBUF];
+
+ fsp = sp;
+ if (fsp->Funnel != NOSITE)
+ fsp = &Sites[fsp->Funnel];
+ if (sp->Name == NULL || fsp->Name == NULL)
+ return;
+ if (fsp->Type == FTexploder) {
+ strlcpy(buff, text, sizeof(buff));
+ if (fsp != sp && fsp->FNLwantsnames) {
+ strlcat(buff, " ", sizeof(buff));
+ strlcat(buff, sp->Name, sizeof(buff));
+ }
+ SITEwrite(fsp, buff);
+ }
+}
+
+
+/*
+** Drop a site.
+*/
+void
+SITEdrop(SITE *sp)
+{
+ SITEforward(sp, "drop");
+ SITEflush(sp, false);
+ SITEfree(sp);
+}
+
+
+/*
+** Append info about the current state of the site to the buffer
+*/
+void
+SITEinfo(struct buffer *bp, SITE *sp, const bool Verbose)
+{
+ static char FREESITE[] = "<<No name>>\n\n";
+ char *p;
+ CHANNEL *cp;
+ const char *sep;
+ char buff[BUFSIZ];
+
+ if (sp->Name == NULL) {
+ buffer_append(bp, FREESITE, strlen(FREESITE));
+ return;
+ }
+
+ p = buff;
+ snprintf(buff, sizeof(buff), "%s%s:\t", sp->Name,
+ sp->IsMaster ? "(*)" : "");
+ p += strlen(p);
+
+ if (sp->Type == FTfunnel) {
+ sp = &Sites[sp->Funnel];
+ sprintf(p, "funnel -> %s: ", sp->Name);
+ p += strlen(p);
+ }
+
+ switch (sp->Type) {
+ default:
+ sprintf(p, "unknown feed type %d", sp->Type);
+ p += strlen(p);
+ break;
+ case FTerror:
+ case FTfile:
+ p += strlen(strcpy(p, "file"));
+ if (sp->Buffered) {
+ sprintf(p, " buffered(%lu)", (unsigned long) sp->Buffer.left);
+ p += strlen(p);
+ }
+ else if ((cp = sp->Channel) == NULL)
+ p += strlen(strcpy(p, " no channel?"));
+ else {
+ sprintf(p, " open fd=%d, in mem %lu", cp->fd,
+ (unsigned long) cp->Out.left);
+ p += strlen(p);
+ }
+ break;
+ case FTchannel:
+ p += strlen(strcpy(p, "channel"));
+ goto Common;
+ case FTexploder:
+ p += strlen(strcpy(p, "exploder"));
+Common:
+ if (sp->Process >= 0) {
+ sprintf(p, " pid=%ld", (long) sp->pid);
+ p += strlen(p);
+ }
+ if (sp->Spooling)
+ p += strlen(strcpy(p, " spooling"));
+ if ((cp = sp->Channel) == NULL)
+ p += strlen(strcpy(p, " no channel?"));
+ else {
+ sprintf(p, " fd=%d, in mem %lu", cp->fd,
+ (unsigned long) cp->Out.left);
+ p += strlen(p);
+ }
+ break;
+ case FTfunnel:
+ p += strlen(strcpy(p, "recursive funnel"));
+ break;
+ case FTlogonly:
+ p += strlen(strcpy(p, "log only"));
+ break;
+ case FTprogram:
+ p += strlen(strcpy(p, "program"));
+ if (sp->FNLwantsnames)
+ p += strlen(strcpy(p, " with names"));
+ break;
+ }
+ *p++ = '\n';
+ if (Verbose) {
+ sep = "\t";
+ if (sp->Buffered && sp->Flushpoint) {
+ sprintf(p, "%sFlush @ %ld", sep, sp->Flushpoint);
+ p += strlen(p);
+ sep = "; ";
+ }
+ if (sp->StartWriting || sp->StopWriting) {
+ sprintf(p, "%sWrite [%ld..%ld]", sep,
+ sp->StopWriting, sp->StartWriting);
+ p += strlen(p);
+ sep = "; ";
+ }
+ if (sp->StartSpooling) {
+ sprintf(p, "%sSpool @ %ld", sep, sp->StartSpooling);
+ p += strlen(p);
+ sep = "; ";
+ }
+ if (sep[0] != '\t')
+ *p++ = '\n';
+ if (sp->Spooling && sp->SpoolName) {
+ sprintf(p, "\tSpooling to \"%s\"\n", sp->SpoolName);
+ p += strlen(p);
+ }
+ if ((cp = sp->Channel) != NULL) {
+ sprintf(p, "\tChannel created %.12s",
+ ctime(&cp->Started) + 4);
+ p += strlen(p);
+ sprintf(p, ", last active %.12s\n",
+ ctime(&cp->LastActive) + 4);
+ p += strlen(p);
+ if (cp->Waketime > Now.time) {
+ sprintf(p, "\tSleeping until %.12s\n",
+ ctime(&cp->Waketime) + 4);
+ p += strlen(p);
+ }
+ }
+
+ }
+ buffer_append(bp, buff, p - buff);
+}
--- /dev/null
+/* $Id: status.c 7547 2006-08-26 06:18:14Z eagle $
+**
+** Periodic status reporting.
+*/
+#include "config.h"
+#include "clibrary.h"
+#include "portable/socket.h"
+
+#include "inn/innconf.h"
+#include "innd.h"
+#include "innperl.h"
+
+#define MIN_REFRESH 60 /* 1 min */
+#define HTML_STATUS
+#if defined(HTML_STATUS)
+#define STATUS_FILE "inn_status.html" /* will be in pathhttp */
+#else
+#define STATUS_FILE "inn.status" /* will be in pathlog */
+#endif
+
+typedef struct _STATUS {
+ char name[SMBUF];
+ char ip_addr[64];
+ bool can_stream;
+ unsigned short activeCxn;
+ unsigned short sleepingCxns;
+ time_t seconds;
+ unsigned long accepted;
+ unsigned long refused;
+ unsigned long rejected;
+ unsigned long Duplicate;
+ unsigned long Unwanted_u;
+ unsigned long Unwanted_d;
+ unsigned long Unwanted_g;
+ unsigned long Unwanted_s;
+ unsigned long Unwanted_f;
+ float Size;
+ float DuplicateSize;
+ unsigned long Check;
+ unsigned long Check_send;
+ unsigned long Check_deferred;
+ unsigned long Check_got;
+ unsigned long Check_cybercan;
+ unsigned long Takethis;
+ unsigned long Takethis_Ok;
+ unsigned long Takethis_Err;
+ unsigned long Ihave;
+ unsigned long Ihave_Duplicate;
+ unsigned long Ihave_Deferred;
+ unsigned long Ihave_SendIt;
+ unsigned long Ihave_Cybercan;
+ struct _STATUS *next;
+} STATUS;
+
+static unsigned STATUSlast_time;
+char start_time[50];
+
+static unsigned
+STATUSgettime(void)
+{
+ static int init = 0;
+ static struct timeval start_tv;
+ struct timeval tv;
+
+ if (!init) {
+ gettimeofday(&start_tv, NULL);
+ init++;
+ }
+ gettimeofday(&tv, NULL);
+ return((tv.tv_sec - start_tv.tv_sec) * 1000 +
+ (tv.tv_usec - start_tv.tv_usec) / 1000);
+}
+
+void
+STATUSinit(void)
+{
+ time_t now;
+
+ STATUSlast_time = STATUSgettime(); /* First invocation */
+ now = time (NULL) ;
+ strlcpy(start_time, ctime(&now), sizeof(start_time));
+}
+
+static char *
+PrettySize(float size, char *str)
+{
+ if (size > 1073741824) /* 1024*1024*1024 */
+ sprintf (str, "%.1fGb", size / 1073741824.);
+ else
+ if (size > 1048576) /* 1024*1024 */
+ sprintf (str, "%.1fMb", size / 1048576.);
+ else
+ sprintf (str, "%.1fkb", size / 1024.);
+ return (str);
+}
+
+static void
+STATUSsummary(void)
+{
+ FILE *F;
+ int i, j;
+ CHANNEL *cp;
+ int activeCxn = 0;
+ int sleepingCxns = 0;
+ time_t seconds = 0;
+ unsigned long duplicate = 0;
+ unsigned long offered;
+ unsigned long accepted = 0;
+ unsigned long refused = 0;
+ unsigned long rejected = 0;
+ float size = 0;
+ float DuplicateSize = 0;
+ int peers = 0;
+ char TempString[SMBUF];
+ char *path;
+ STATUS *head, *status, *tmp;
+ char str[9];
+ time_t now;
+
+#if defined(HTML_STATUS)
+ path = concatpath(innconf->pathhttp, STATUS_FILE);
+#else
+ path = concatpath(innconf->pathlog, STATUS_FILE);
+#endif
+ if ((F = Fopen(path, "w", TEMPORARYOPEN)) == NULL) {
+ syslog(L_ERROR, "%s cannot open %s: %m", LogName, path);
+ return;
+ }
+
+#if defined(HTML_STATUS)
+ /* HTML Header */
+
+ fprintf (F,"<HTML>\n<HEAD>\n<META HTTP-EQUIV=\"Refresh\" CONTENT=\"%ld;\">\n",
+ innconf->status < MIN_REFRESH ? MIN_REFRESH : innconf->status);
+ fprintf (F, "<TITLE>%s: incoming feeds</TITLE>\n", innconf->pathhost);
+ fprintf (F, "</HEAD>\n<BODY>\n<PRE>\n") ;
+#endif /* defined(HTML_STATUS) */
+
+ fprintf (F, "%s\n", inn_version_string);
+ fprintf (F, "pid %d started %s\n", (int) getpid(), start_time);
+
+ tmp = head = NULL;
+ for (i = 0; (cp = CHANiter(&i, CTnntp)) != NULL; ) {
+ j = 0;
+ strlcpy(TempString,
+ cp->Address.ss_family == 0 ? "localhost" : RChostname(cp),
+ sizeof(TempString));
+ for (status = head ; status != NULL ; status = status->next) {
+ if (strcmp(TempString, status->name) == 0)
+ break;
+ }
+ if (status == NULL) {
+ status = xmalloc(sizeof(STATUS));
+ peers++; /* a new peer */
+ strlcpy(status->name, TempString, sizeof(status->name));
+ strlcpy(status->ip_addr,
+ sprint_sockaddr((struct sockaddr *)&cp->Address),
+ sizeof(status->ip_addr));
+ status->can_stream = cp->Streaming;
+ status->seconds = status->Size = status->DuplicateSize = 0;
+ status->Ihave = status->Ihave_Duplicate =
+ status->Ihave_Deferred = status->Ihave_SendIt =
+ status->Ihave_Cybercan = 0;
+ status->Check = status->Check_send =
+ status->Check_deferred = status->Check_got =
+ status->Check_cybercan = 0;
+ status->Takethis = status->Takethis_Ok = status->Takethis_Err = 0;
+ status->activeCxn = status->sleepingCxns = 0;
+ status->accepted = 0;
+ status->refused = status->rejected = 0;
+ status->Duplicate = status->Unwanted_u = 0;
+ status->Unwanted_d = status->Unwanted_g = 0;
+ status->Unwanted_s = status->Unwanted_f = 0;
+ status->next = NULL;
+ if (head == NULL)
+ head = status;
+ else
+ tmp->next = status;
+ tmp = status;
+ }
+ if (Now.time - cp->Started > status->seconds)
+ status->seconds = Now.time - cp->Started;
+ if (Now.time - cp->Started > seconds)
+ seconds = Now.time - cp->Started;
+ status->accepted += cp->Received;
+ accepted += cp->Received;
+ status->refused += cp->Refused;
+ refused += cp->Refused;
+ status->rejected += cp->Rejected;
+ rejected += cp->Rejected;
+ status->Duplicate += cp->Duplicate;
+ duplicate += cp->Duplicate;
+ status->Unwanted_u += cp->Unwanted_u;
+ status->Unwanted_d += cp->Unwanted_d;
+ status->Unwanted_g += cp->Unwanted_g;
+ status->Unwanted_s += cp->Unwanted_s;
+ status->Unwanted_f += cp->Unwanted_f;
+ status->Ihave += cp->Ihave;
+ status->Ihave_Duplicate += cp->Ihave_Duplicate;
+ status->Ihave_Deferred += cp->Ihave_Deferred;
+ status->Ihave_SendIt += cp->Ihave_SendIt;
+ status->Ihave_Cybercan += cp->Ihave_Cybercan;
+ status->Check += cp->Check;
+ status->Check_send += cp->Check_send;
+ status->Check_deferred += cp->Check_deferred;
+ status->Check_got += cp->Check_got;
+ status->Check_cybercan += cp->Check_cybercan;
+ status->Takethis += cp->Takethis;
+ status->Takethis_Ok += cp->Takethis_Ok;
+ status->Takethis_Err += cp->Takethis_Err;
+ status->Size += cp->Size;
+ status->DuplicateSize += cp->DuplicateSize;
+ size += cp->Size;
+ DuplicateSize += cp->DuplicateSize;
+ if (CHANsleeping(cp)) {
+ sleepingCxns++;
+ status->sleepingCxns++;
+ } else {
+ activeCxn++;
+ status->activeCxn++;
+ }
+ }
+
+ /* Header */
+ now = time (NULL);
+ strlcpy (TempString, ctime (&now), sizeof(TempString));
+ fprintf (F, "Updated: %s", TempString);
+ fprintf (F, "(peers: %d, active-cxns: %d, sleeping-cxns: %d)\n\n",
+ peers, activeCxn, sleepingCxns);
+
+ fprintf (F, "Mode: %s", Mode == OMrunning ? "running" :
+ Mode == OMpaused ? "paused" :
+ Mode == OMthrottled ? "throttled" : "Unknown");
+ if ((Mode == OMpaused) || (Mode == OMthrottled))
+ fprintf (F, " (%s)", ModeReason);
+
+ /* Global configuration */
+ fprintf (F, "\n\nConfiguration file: %s\n\n", _PATH_CONFIG);
+
+ fprintf (F, "Global configuration parameters:\n");
+ fprintf (F, " Largest Article: %ld bytes\n", innconf->maxartsize);
+ fprintf (F, " Max Incoming connections: ");
+ if (innconf->maxconnections)
+ fprintf (F, "%ld\n", innconf->maxconnections);
+ else
+ fprintf (F, "unlimited\n");
+ fprintf (F, " Max Outgoing file feeds: %d\n", MaxOutgoing);
+ fprintf (F, " Cutoff: ");
+ if (innconf->artcutoff)
+ fprintf (F, "%ld days\n", innconf->artcutoff);
+ else
+ fprintf (F, "none\n");
+ fprintf (F, " Timeout period: %ld seconds\n",
+ (long)TimeOut.tv_sec);
+ if (innconf->remembertrash) {
+ fprintf (F, " Remember Trash: Yes\n");
+ } else {
+ fprintf (F, " Remember Trash: No\n");
+ }
+#if defined(DO_TCL)
+ fprintf (F, " Tcl filtering: %s\n",
+ TCLFilterActive ? "enabled" : "disabled");
+#endif /* defined(DO_TCL) */
+#if defined(DO_PERL)
+ fprintf (F, " Perl filtering: %s\n",
+ PerlFilterActive ? "enabled" : "disabled");
+#endif /* defined(DO_PERL) */
+
+ fputc ('\n', F) ;
+
+ /* Global values */
+ fprintf (F, "global (process)\n");
+ fprintf (F, " seconds: %ld\n", (long) seconds);
+ offered = accepted + refused + rejected;
+ fprintf (F, " offered: %-9ld\n", offered);
+ if (!offered) offered = 1; /* to avoid division by zero */
+ if (!size) size = 1; /* avoid divide by zero here too */
+ fprintf (F, " accepted: %-9ld %%accepted: %.1f%%\n",
+ accepted, (float) accepted / offered * 100);
+ fprintf (F, " refused: %-9ld %%refused: %.1f%%\n",
+ refused, (float) refused / offered * 100);
+ fprintf (F, " rejected: %-9ld %%rejected: %.1f%%\n",
+ rejected, (float) rejected / offered * 100);
+ fprintf (F, " duplicated: %-9ld %%duplicated: %.1f%%\n",
+ duplicate, (float) duplicate / offered * 100);
+ fprintf (F, " bytes: %-7s\n", PrettySize (size + DuplicateSize, str));
+ fprintf (F, " duplicated size: %-7s %%duplicated size: %.1f%%\n",
+ PrettySize(DuplicateSize, str), (float) DuplicateSize / size * 100);
+ fputc ('\n', F) ;
+
+ /* Incoming Feeds */
+ for (status = head ; status != NULL ;) {
+ fprintf (F, "%s\n", status->name);
+ fprintf (F, " seconds: %-7ld ", (long) status->seconds);
+ fprintf (F, " duplicates: %-7ld ", status->Duplicate);
+ fprintf (F, " ip address: %s\n", status->ip_addr);
+ fprintf (F, " offered: %-7ld ",
+ status->accepted + status->refused + status->rejected);
+ fprintf (F, " uw newsgroups: %-7ld ", status->Unwanted_g);
+ fprintf (F, " active cxns: %d\n", status->activeCxn);
+ fprintf (F, " accepted: %-7ld ", status->accepted);
+ fprintf (F, "uw distributions: %-7ld ", status->Unwanted_d);
+ fprintf (F, " sleeping cxns: %d\n", status->sleepingCxns);
+ fprintf (F, " refused: %-7ld ", status->refused);
+ fprintf (F, " unapproved: %-7ld ", status->Unwanted_u);
+ fprintf (F, "want streaming: %s\n",
+ status->can_stream ? "Yes" : "No");
+ fprintf (F, " rejected: %-7ld ", status->rejected);
+ fprintf (F, " filtered: %-7ld ", status->Unwanted_f);
+ fprintf (F, " is streaming: %s\n",
+ (status->Check || status->Takethis) ? "Yes" : "No");
+ fprintf (F, " size: %-8s ", PrettySize(status->Size, str));
+ fprintf (F, " bad sites: %-7ld ", status->Unwanted_s);
+ fprintf (F, "duplicate size: %s\n", PrettySize(status->DuplicateSize, str));
+ fprintf (F, " Protocol:\n");
+ fprintf (F, " Ihave: %-6ld SendIt[%d]: %-6ld Got[%d]: %-6ld Deferred[%d]: %ld\n",
+ status->Ihave, NNTP_SENDIT_VAL, status->Ihave_SendIt,
+ NNTP_HAVEIT_VAL, status->Ihave_Duplicate, NNTP_RESENDIT_VAL,
+ status->Ihave_Deferred);
+ fprintf (F, " Check: %-6ld SendIt[%d]: %-6ld Got[%d]: %-6ld Deferred[%d]: %ld\n",
+ status->Check, NNTP_OK_SENDID_VAL, status->Check_send,
+ NNTP_ERR_GOTID_VAL, status->Check_got, NNTP_RESENDID_VAL,
+ status->Check_deferred);
+ fprintf (F, " Takethis: %-6ld Ok[%d]: %-6ld Error[%d]: %-6ld\n",
+ status->Takethis, NNTP_OK_RECID_VAL, status->Takethis_Ok,
+ NNTP_ERR_FAILID_VAL, status->Takethis_Err);
+ if (innconf->refusecybercancels) {
+ fprintf (F, " Cancelrejects: Ihave[%d]: %-6ld Check[%d]: %-6ld\n",
+ NNTP_HAVEIT_VAL, status->Ihave_Cybercan,
+ NNTP_ERR_GOTID_VAL, status->Check_cybercan);
+ }
+ fputc ('\n', F) ;
+ tmp = status->next;
+ free(status);
+ status = tmp;
+ }
+
+#if defined(HTML_STATUS)
+ /* HTML Footer */
+ fprintf (F,"</PRE>\n</BODY>\n</HTML>\n");
+#endif /* defined(HTML_STATUS) */
+
+ Fclose(F);
+}
+
+void
+STATUSmainloophook(void)
+{
+ unsigned now;
+
+ if (!innconf->status)
+ return;
+ now = STATUSgettime();
+
+ if (now - STATUSlast_time > (unsigned)(innconf->status * 1000)) {
+ STATUSsummary();
+ STATUSlast_time = now;
+ }
+}
--- /dev/null
+/* $Id: tcl.c 6124 2003-01-14 06:03:29Z rra $
+**
+** Support for TCL things
+**
+** By Bob Heiney, Network Systems Laboratory, Digital Equipment Corporation
+*/
+
+#include "config.h"
+#include "clibrary.h"
+
+#include "inn/innconf.h"
+#include "innd.h"
+
+#if defined(DO_TCL)
+
+Tcl_Interp *TCLInterpreter;
+bool TCLFilterActive;
+struct buffer *TCLCurrArticle;
+ARTDATA *TCLCurrData;
+
+static char *TCLSTARTUP = NULL;
+static char *TCLFILTER = NULL;
+
+
+void
+TCLfilter(value)
+ bool value;
+{
+ TCLFilterActive=value;
+
+ syslog(L_NOTICE, "%s tcl filtering %s", LogName,
+ TCLFilterActive ? "enabled" : "disabled");
+}
+
+
+void
+TCLreadfilter(void)
+{
+ int code;
+
+ /* do before reload callback */
+ code = Tcl_Eval(TCLInterpreter, "filter_before_reload");
+ if (code != TCL_OK) {
+ if (strcmp(TCLInterpreter->result,
+ "invalid command name: \"filter_before_reload\"")!=0)
+ syslog(L_ERROR, "%s Tcl filter_before_reload failed: %s",
+ LogName, TCLInterpreter->result);
+ }
+
+ /* read the filter file */
+ if (TCLFILTER == NULL)
+ TCLFILTER = concatpath(innconf->pathfilter, _PATH_TCL_FILTER);
+ code = Tcl_EvalFile(TCLInterpreter, TCLFILTER);
+ if (code != TCL_OK) {
+ syslog(L_ERROR, "%s cant evaluate Tcl filter file: %s", LogName,
+ TCLInterpreter->result);
+ TCLfilter(false);
+ }
+
+ /* do the after callback, discarding any errors */
+ code = Tcl_Eval(TCLInterpreter, "filter_after_reload");
+ if (code != TCL_OK) {
+ if (strcmp(TCLInterpreter->result,
+ "invalid command name: \"filter_after_reload\"")!=0)
+ syslog(L_ERROR, "%s Tcl filter_after_reload failed: %s",
+ LogName, TCLInterpreter->result);
+ }
+}
+
+
+/* makeCheckSum
+ *
+ * Compute a checksum. This function does a one's-complement addition
+ * of a series of 32-bit words. "buflen" is in bytes, not words. This is
+ * hard because the number of bits with which our machine can do arithmetic
+ * is the same as the size of the checksum being created, but our hardware
+ * is 2's-complement and C has no way to check for integer overflow.
+ *
+ * Note that the checksum is returned in network byte order and not host
+ * byte order; this makes it suitable for putting into packets and for
+ * comparing with what is found in packets.
+ */
+
+static uint32_t
+makechecksum(u_char *sumbuf, int buflen)
+{
+ u_char *buf = (u_char *)sumbuf;
+ int32_t len = buflen;
+ int32_t sum;
+ uint32_t bwordl,bwordr,bword,suml,sumr;
+ int rmdr;
+ u_char tbuf[4];
+ u_char *ptbuf;
+
+ suml = 0; sumr = 0;
+
+ len = len/4;
+ rmdr = buflen - 4*len;
+
+ while (len-- > 0) {
+ bwordr = (buf[3] & 0xFF)
+ + ((buf[2] & 0xFF) << 8);
+ bwordl = (buf[1] & 0xFF)
+ + ((buf[0] & 0xFF) << 8);
+ bword = ( bwordl << 16) | bwordr;
+ bword = ntohl(bword);
+ bwordl = (bword >> 16) & 0xFFFF;
+ bwordr = bword & 0xFFFF;
+ sumr += bwordr;
+ if (sumr & ~0xFFFF) {
+ sumr &= 0xFFFF;
+ suml++;
+ }
+ suml += bwordl;
+ if (suml & ~0xFFFF) {
+ suml &= 0xFFFF;
+ sumr++;
+ }
+ buf += 4;
+ }
+ /* if buffer size was not an even multiple of 4 bytes,
+ we have work to do */
+ if (rmdr > 0) {
+ tbuf[3] = 0; tbuf[2] = 0; tbuf[1] = 0;
+ /* tbuf[0] will always be copied into, and tbuf[3] will
+ * always be zero (else this would not be a remainder)
+ */
+ ptbuf = tbuf;
+ while (rmdr--) *ptbuf++ = *buf++;
+ bwordr = (tbuf[3] & 0xFF)
+ + ((tbuf[2] & 0xFF) << 8);
+ bwordl = (tbuf[1] & 0xFF)
+ + ((tbuf[0] & 0xFF) << 8);
+ bword = ( bwordl << 16) | bwordr;
+ bword = ntohl(bword);
+ bwordl = (bword >> 16) & 0xFFFF;
+ bwordr = bword & 0xFFFF;
+ sumr += bwordr;
+ if (sumr & ~0xFFFF) {
+ sumr &= 0xFFFF;
+ suml++;
+ }
+ suml += bwordl;
+ if (suml & ~0xFFFF) {
+ suml &= 0xFFFF;
+ sumr++;
+ }
+ }
+ sum = htonl( (suml << 16) | sumr);
+ return (~sum);
+}
+
+
+int
+TCLCksumArt(ClientData clientData, Tcl_Interp *interp, int argc, char *argv[])
+{
+ char buf[100];
+
+ snprintf(buf, sizeof(buf), "%08x",
+ makechecksum(TCLCurrArticle->data + TCLCurrData->Body,
+ TCLCurrData->Body));
+ Tcl_SetResult(interp, buf, TCL_VOLATILE);
+ return TCL_OK;
+}
+
+
+void
+TCLsetup(void)
+{
+ int code;
+
+ TCLInterpreter = Tcl_CreateInterp();
+ if (TCLSTARTUP == NULL)
+ TCLSTARTUP = concatpath(innconf->pathfilter, _PATH_TCL_STARTUP);
+ code = Tcl_EvalFile(TCLInterpreter, TCLSTARTUP);
+ if (code != TCL_OK) {
+ syslog(L_FATAL, "%s cant read Tcl startup file: %s", LogName,
+ TCLInterpreter->result);
+ exit(1);
+ }
+
+ Tcl_CreateCommand(TCLInterpreter, "checksum_article", TCLCksumArt,
+ NULL, NULL);
+
+ TCLfilter(true);
+ TCLreadfilter();
+}
+
+
+void
+TCLclose(void)
+{
+}
+
+
+#endif /* defined(DO_TCL) */
--- /dev/null
+/* $Id: util.c 6138 2003-01-19 04:13:51Z rra $
+**
+** Various miscellaneous utility functions for innd internal use.
+*/
+
+#include "config.h"
+#include "clibrary.h"
+
+#include "inn/innconf.h"
+#include "libinn.h"
+
+#include "innd.h"
+
+/*
+** Sprintf a long into a buffer with enough leading zero's so that it
+** takes up width characters. Don't add trailing NUL. Return true
+** if it fit. Used for updating high-water marks in the active file
+** in-place.
+*/
+bool
+FormatLong(char *p, unsigned long value, int width)
+{
+ for (p += width - 1; width-- > 0; ) {
+ *p-- = (int)(value % 10) + '0';
+ value /= 10;
+ }
+ return value == 0;
+}
+
+
+/*
+** Glue a string, a char, and a string together. Useful for making
+** filenames.
+*/
+void
+FileGlue(char *p, const char *n1, char c,
+ const char *n2)
+{
+ p += strlen(strcpy(p, n1));
+ *p++ = c;
+ strcpy(p, n2);
+}
+
+
+/*
+** Turn any \r or \n in text into spaces. Used to splice back multi-line
+** headers into a single line.
+*/
+static char *
+Join(char *text)
+{
+ char *p;
+
+ for (p = text; *p; p++)
+ if (*p == '\n' || *p == '\r')
+ *p = ' ';
+ return text;
+}
+
+
+/*
+** Return a short name that won't overrun our bufer or syslog's buffer.
+** q should either be p, or point into p where the "interesting" part is.
+*/
+char *
+MaxLength(const char *p, const char *q)
+{
+ static char buff[80];
+ unsigned int i;
+
+ /* Already short enough? */
+ i = strlen(p);
+ if (i < sizeof buff - 1) {
+ strlcpy(buff, p, sizeof(buff));
+ return Join(buff);
+ }
+
+ /* Simple case of just want the begining? */
+ if ((unsigned)(q - p) < sizeof buff - 4) {
+ strlcpy(buff, p, sizeof(buff) - 3);
+ strlcat(buff, "...", sizeof(buff));
+ }
+ /* Is getting last 10 characters good enough? */
+ else if ((p + i) - q < 10) {
+ strlcpy(buff, p, sizeof(buff) - 13);
+ strlcat(buff, "...", sizeof(buff) - 10);
+ strlcat(buff, &p[i - 10], sizeof(buff));
+ }
+ else {
+ /* Not in last 10 bytes, so use double elipses. */
+ strlcpy(buff, p, sizeof(buff) - 16);
+ strlcat(buff, "...", sizeof(buff) - 13);
+ strlcat(buff, &q[-5], sizeof(buff) - 3);
+ strlcat(buff, "...", sizeof(buff));
+ }
+ return Join(buff);
+}
+
+
+/*
+** Split text into comma-separated fields. Return an allocated
+** NULL-terminated array of the fields within the modified argument that
+** the caller is expected to save or free. We don't use strchr() since
+** the text is expected to be either relatively short or "comma-dense."
+*/
+char **
+CommaSplit(char *text)
+{
+ int i;
+ char *p;
+ char **av;
+ char **save;
+
+ /* How much space do we need? */
+ for (i = 2, p = text; *p; p++)
+ if (*p == ',')
+ i++;
+
+ for (av = save = xmalloc(i * sizeof(char *)), *av++ = p = text; *p; )
+ if (*p == ',') {
+ *p++ = '\0';
+ *av++ = p;
+ }
+ else
+ p++;
+ *av = NULL;
+ return save;
+}
+
+
+/*
+** Set up LISTBUFFER so that data will be put into array
+** it allocates buffer and array for data if needed, otherwise use already
+** allocated one
+*/
+void
+SetupListBuffer(int size, LISTBUFFER *list)
+{
+ /* get space for data to be splitted */
+ if (list->Data == NULL) {
+ list->DataLength = size;
+ list->Data = xmalloc(list->DataLength + 1);
+ } else if (list->DataLength < size) {
+ list->DataLength = size;
+ list->Data = xrealloc(list->Data, list->DataLength + 1);
+ }
+ /* get an array of character pointers. */
+ if (list->List == NULL) {
+ list->ListLength = DEFAULTNGBOXSIZE;
+ list->List = xmalloc(list->ListLength * sizeof(char *));
+ }
+}
+
+
+/*
+** Do we need a shell for the command? If not, av is filled in with
+** the individual words of the command and the command is modified to
+** have NUL's inserted.
+*/
+bool
+NeedShell(char *p, const char **av, const char **end)
+{
+ static const char Metachars[] = ";<>|*?[]{}()#$&=`'\"\\~\n";
+ const char *q;
+
+ /* We don't use execvp(); works for users, fails out of /etc/rc. */
+ if (*p != '/')
+ return true;
+ for (q = p; *q; q++)
+ if (strchr(Metachars, *q) != NULL)
+ return true;
+
+ for (end--; av < end; ) {
+ /* Mark this word, check for shell meta-characters. */
+ for (*av++ = p; *p && !ISWHITE(*p); p++)
+ continue;
+
+ /* If end of list, we're done. */
+ if (*p == '\0') {
+ *av = NULL;
+ return false;
+ }
+
+ /* Skip whitespace, find next word. */
+ for (*p++ = '\0'; ISWHITE(*p); p++)
+ continue;
+ if (*p == '\0') {
+ *av = NULL;
+ return false;
+ }
+ }
+
+ /* Didn't fit. */
+ return true;
+}
+
+
+/*
+** Spawn a process, with I/O redirected as needed. Return the PID or -1
+** (and a syslog'd message) on error.
+*/
+pid_t
+Spawn(int niceval, int fd0, int fd1, int fd2, char * const av[])
+{
+ static char NOCLOSE[] = "%s cant close %d in %s %m";
+ static char NODUP2[] = "%s cant dup2 %d to %d in %s %m";
+ pid_t i;
+
+ /* Fork; on error, give up. If not using the patched dbz, make
+ * this call fork! */
+ i = fork();
+ if (i == -1) {
+ syslog(L_ERROR, "%s cant fork %s %m", LogName, av[0]);
+ return -1;
+ }
+
+ /* If parent, do nothing. */
+ if (i > 0)
+ return i;
+
+ /* Child -- do any I/O redirection. */
+ if (fd0 != 0) {
+ if (dup2(fd0, 0) < 0) {
+ syslog(L_FATAL, NODUP2, LogName, fd0, 0, av[0]);
+ _exit(1);
+ }
+ if (fd0 != fd1 && fd0 != fd2 && close(fd0) < 0)
+ syslog(L_ERROR, NOCLOSE, LogName, fd0, av[0]);
+ }
+ if (fd1 != 1) {
+ if (dup2(fd1, 1) < 0) {
+ syslog(L_FATAL, NODUP2, LogName, fd1, 1, av[0]);
+ _exit(1);
+ }
+ if (fd1 != fd2 && close(fd1) < 0)
+ syslog(L_ERROR, NOCLOSE, LogName, fd1, av[0]);
+ }
+ if (fd2 != 2) {
+ if (dup2(fd2, 2) < 0) {
+ syslog(L_FATAL, NODUP2, LogName, fd2, 2, av[0]);
+ _exit(1);
+ }
+ if (close(fd2) < 0)
+ syslog(L_ERROR, NOCLOSE, LogName, fd2, av[0]);
+ }
+ close_on_exec(0, false);
+ close_on_exec(1, false);
+ close_on_exec(2, false);
+
+ /* Nice our child if we're supposed to. */
+ if (niceval != 0 && nice(niceval) == -1)
+ syslog(L_ERROR, "SERVER cant nice child to %d: %m", niceval);
+
+ /* Start the desired process (finally!). */
+ execv(av[0], av);
+ syslog(L_FATAL, "%s cant exec in %s %m", LogName, av[0]);
+ _exit(1);
+
+ /* Not reached. */
+ return -1;
+}
+
+/*
+** We ran out of space or other I/O error, throttle ourselves.
+*/
+void
+ThrottleIOError(const char *when)
+{
+ char buff[SMBUF];
+ const char * p;
+ int oerrno;
+
+ if (Mode == OMrunning) {
+ oerrno = errno;
+ if (Reservation) {
+ free(Reservation);
+ Reservation = NULL;
+ }
+ snprintf(buff, sizeof(buff), "%s writing %s file -- throttling",
+ strerror(oerrno), when);
+ if ((p = CCblock(OMthrottled, buff)) != NULL)
+ syslog(L_ERROR, "%s cant throttle %s", LogName, p);
+ syslog(L_FATAL, "%s throttle %s", LogName, buff);
+ errno = oerrno;
+ ThrottledbyIOError = true;
+ }
+}
+
+/*
+** No matching storage.conf, throttle ourselves.
+*/
+void
+ThrottleNoMatchError(void)
+{
+ char buff[SMBUF];
+ const char *p;
+
+ if (Mode == OMrunning) {
+ if (Reservation) {
+ free(Reservation);
+ Reservation = NULL;
+ }
+ snprintf(buff, sizeof(buff), "%s storing article -- throttling",
+ SMerrorstr);
+ if ((p = CCblock(OMthrottled, buff)) != NULL)
+ syslog(L_ERROR, "%s cant throttle %s", LogName, p);
+ syslog(L_FATAL, "%s throttle %s", LogName, buff);
+ ThrottledbyIOError = true;
+ }
+}
+
+void
+InndHisOpen(void)
+{
+ char *histpath;
+ int flags;
+ size_t synccount;
+
+ histpath = concatpath(innconf->pathdb, _PATH_HISTORY);
+ if (innconf->hismethod == NULL) {
+ sysdie("hismethod is not defined");
+ /*NOTREACHED*/
+ }
+
+ flags = HIS_RDWR | (INND_DBZINCORE ? HIS_MMAP : HIS_ONDISK);
+ History = HISopen(histpath, innconf->hismethod, flags);
+ if (!History) {
+ sysdie("SERVER can't open history %s", histpath);
+ /*NOTREACHED*/
+ }
+ free(histpath);
+ HISsetcache(History, 1024 * innconf->hiscachesize);
+ synccount = innconf->icdsynccount;
+ HISctl(History, HISCTLS_SYNCCOUNT, &synccount);
+}
+
+void
+InndHisClose(void)
+{
+ if (History == NULL)
+ return;
+ if (!HISclose(History)) {
+ char *histpath;
+
+ histpath = concatpath(innconf->pathdb, _PATH_HISTORY);
+ sysdie("SERVER can't close history %s", histpath);
+ free(histpath);
+ }
+ History = NULL;
+}
+
+bool
+InndHisWrite(const char *key, time_t arrived, time_t posted, time_t expires,
+ TOKEN *token)
+{
+ bool r = HISwrite(History, key, arrived, posted, expires, token);
+
+ if (r != true)
+ IOError("history write", errno);
+ return r;
+}
+
+bool
+InndHisRemember(const char *key)
+{
+ bool r = HISremember(History, key, Now.time);
+
+ if (r != true)
+ IOError("history remember", errno);
+ return r;
+}
+
+void
+InndHisLogStats(void)
+{
+ struct histstats stats = HISstats(History);
+
+ syslog(L_NOTICE, "ME HISstats %d hitpos %d hitneg %d missed %d dne",
+ stats.hitpos, stats.hitneg, stats.misses, stats.dne);
+}
+
+
--- /dev/null
+/* $Id: wip.c 6124 2003-01-14 06:03:29Z rra $
+**
+** Routines for keeping track of incoming articles, articles that haven't
+** acked from a duplex channel feed, and history caching.
+**
+** WIP stands for work-in-progress
+*/
+
+#include "config.h"
+#include "clibrary.h"
+
+#include "inn/innconf.h"
+#include "innd.h"
+
+#define WIPTABLESIZE 1024
+#define WIP_ARTMAX 300 /* innfeed default max send time */
+
+static WIP *WIPtable[WIPTABLESIZE]; /* Top level of the WIP hash table */
+
+void
+WIPsetup(void)
+{
+ memset(WIPtable, '\0', sizeof(WIPtable));
+}
+
+/* Add a new entry into the table. It is the appilications responsiblity to
+ to call WIPinprogress or WIPbyid first. */
+WIP *
+WIPnew(const char *messageid, CHANNEL *cp)
+{
+ HASH hash;
+ unsigned long bucket;
+ WIP *new;
+
+ hash = Hash(messageid, strlen(messageid));
+ memcpy(&bucket, &hash,
+ sizeof(bucket) < sizeof(hash) ? sizeof(bucket) : sizeof(hash));
+ bucket = bucket % WIPTABLESIZE;
+
+ new = xmalloc(sizeof(WIP));
+ new->MessageID = hash;
+ new->Timestamp = Now.time;
+ new->Chan = cp;
+ /* Link the new entry into the list */
+ new->Next = WIPtable[bucket];
+ WIPtable[bucket] = new;
+ return new;
+}
+
+void
+WIPprecomfree(CHANNEL *cp)
+{
+ WIP *cur;
+ int i;
+ if (cp == NULL)
+ return;
+
+ for (i = 0 ; i < PRECOMMITCACHESIZE ; i++) {
+ cur = cp->PrecommitWIP[i];
+ if (cur != (WIP *)NULL) {
+ WIPfree(cur);
+ }
+ }
+}
+
+void
+WIPfree(WIP *wp)
+{
+ unsigned long bucket;
+ WIP *cur;
+ WIP *prev = NULL;
+ int i;
+ /* This is good error checking, but also allows us to
+ WIPfree(WIPbymessageid(id))
+ without having to check if id exists first */
+ if (wp == NULL)
+ return;
+
+ memcpy(&bucket, &wp->MessageID,
+ sizeof(bucket) < sizeof(HASH) ? sizeof(bucket) : sizeof(HASH));
+ bucket = bucket % WIPTABLESIZE;
+
+ for (i = 0 ; i < PRECOMMITCACHESIZE ; i++) {
+ if (wp->Chan->PrecommitWIP[i] == wp) {
+ wp->Chan->PrecommitWIP[i] = (WIP *)NULL;
+ break;
+ }
+ }
+ for (cur = WIPtable[bucket]; (cur != NULL) && (cur != wp); prev = cur, cur = cur->Next);
+
+ if (cur == NULL)
+ return;
+
+ if (prev == NULL)
+ WIPtable[bucket] = cur->Next;
+ else
+ prev->Next = cur->Next;
+
+ /* unlink the entry and free the memory */
+ free(wp);
+}
+
+/* Check if the given messageid is being transfered on another channel. If
+ Add is true then add the given message-id to the current channel */
+bool
+WIPinprogress(const char *msgid, CHANNEL *cp, bool Precommit)
+{
+ WIP *wp;
+ int i;
+
+ if ((wp = WIPbyid(msgid)) != NULL) {
+ if(wp->Chan->ArtBeg == 0)
+ i = 0;
+ else {
+ i = wp->Chan->ArtMax;
+ if(i > WIP_ARTMAX)
+ i = WIP_ARTMAX;
+ }
+
+ if ((Now.time - wp->Timestamp) < (i + innconf->wipcheck))
+ return true;
+ if ((Now.time - wp->Timestamp) > (i + innconf->wipexpire)) {
+ for (i = 0 ; i < PRECOMMITCACHESIZE ; i++) {
+ if (wp->Chan->PrecommitWIP[i] == wp) {
+ wp->Chan->PrecommitWIP[i] = (WIP *)NULL;
+ break;
+ }
+ }
+ WIPfree(wp);
+ WIPinprogress(msgid, cp, Precommit);
+ return false;
+ }
+ if (wp->Chan == cp)
+ return true;
+ return false;
+ }
+ wp = WIPnew(msgid, cp);
+ if (Precommit) {
+ if (cp->PrecommitiCachenext == PRECOMMITCACHESIZE)
+ cp->PrecommitiCachenext = 0;
+ if (cp->PrecommitWIP[cp->PrecommitiCachenext])
+ WIPfree(cp->PrecommitWIP[cp->PrecommitiCachenext]);
+ cp->PrecommitWIP[cp->PrecommitiCachenext++] = wp;
+ } else {
+ WIPfree(WIPbyhash(cp->CurrentMessageIDHash));
+ cp->CurrentMessageIDHash = wp->MessageID;
+ }
+ return false;
+}
+
+WIP *
+WIPbyid(const char *messageid)
+{
+ HASH hash;
+ unsigned long bucket;
+ WIP *wp;
+
+ hash = Hash(messageid, strlen(messageid));
+ memcpy(&bucket, &hash,
+ sizeof(bucket) < sizeof(hash) ? sizeof(bucket) : sizeof(hash));
+ bucket = bucket % WIPTABLESIZE;
+
+ /* Traverse the list until we find a match or find the head again */
+ for (wp = WIPtable[bucket]; wp != NULL; wp = wp->Next)
+ if (!memcmp(&hash, &wp->MessageID, sizeof(HASH)))
+ return wp;
+
+ return NULL;
+}
+
+WIP *
+WIPbyhash(const HASH hash)
+{
+ unsigned long bucket;
+ WIP *wp;
+
+ memcpy(&bucket, &hash,
+ sizeof(bucket) < sizeof(hash) ? sizeof(bucket) : sizeof(hash));
+ bucket = bucket % WIPTABLESIZE;
+
+ /* Traverse the list until we find a match or find the head again */
+ for (wp = WIPtable[bucket]; wp != NULL; wp = wp->Next)
+ if (!memcmp(&hash, &wp->MessageID, sizeof(HASH)))
+ return wp;
+
+ return NULL;
+}
--- /dev/null
+## $Id: Makefile 7727 2008-04-06 07:59:46Z iulius $
+
+include ../Makefile.global
+
+top = ..
+CFLAGS = $(GCFLAGS) $(SASLINC)
+
+ALL = innfeed procbatch startinnfeed imapfeed
+
+SOURCES = article.c buffer.c config_l.c config_y.c \
+ endpoint.c host.c innlistener.c main.c misc.c \
+ startinnfeed.c tape.c version.c
+
+INCLUDES = article.h buffer.h configfile.h config_y.h connection.h \
+ endpoint.h host.h innfeed.h innlistener.h misc.h tape.h
+
+# The objects linked into innfeed. All SOURCES except startinnfeed.
+OBJECTS = article.o buffer.o config_l.o config_y.o \
+ endpoint.o host.o innlistener.o main.o misc.o tape.o \
+ version.o
+
+all: $(ALL)
+
+warnings:
+ $(MAKE) COPT='$(WARNINGS)' all
+
+install: all
+ $(LI_XPRI) innfeed $D$(PATHBIN)/innfeed
+ $(LI_XPRI) imapfeed $D$(PATHBIN)/imapfeed
+ $(CP_XPRI) procbatch $D$(PATHBIN)/procbatch
+ @ME=`$(WHOAMI)` ; \
+ if [ x"$$ME" = xroot ] ; then \
+ echo $(LI_SPRI) startinnfeed $D$(PATHBIN)/startinnfeed ; \
+ $(LI_SPRI) startinnfeed $D$(PATHBIN)/startinnfeed ; \
+ else \
+ echo $(LI_XPRI) startinnfeed $D$(PATHBIN)/startinnfeed ; \
+ $(LI_XPRI) startinnfeed $D$(PATHBIN)/startinnfeed ; \
+ echo '' ; \
+ echo '========================' ; \
+ echo 'NOTE NOTE NOTE NOTE NOTE' ; \
+ ls -l $D$(PATHBIN)/startinnfeed ; \
+ echo '$D$(PATHBIN)/startinnfeed needs to be installed setuid root' ; \
+ echo '' ; echo ; \
+ fi
+
+
+clean:
+ rm -f *.o $(ALL) version.c innfeed-convcfg
+ rm -f profiled innfeedp
+ rm -rf .libs
+
+clobber distclean: clean
+ rm -f tags y.tab.c y.tab.h lex.yy.c config_y.c config_y.h
+
+tags: $(SOURCES) $(INCLUDES)
+ $(CTAGS) $(SOURCES) $(INCLUDES)
+
+$(FIXSCRIPT):
+ @echo Run configure before running make. See INSTALL for details.
+ @exit 1
+
+
+## Compilation rules.
+
+INNFEEDLIBS = $(LIBSTORAGE) $(LIBHIST) $(LIBINN) $(EXTSTORAGELIBS) \
+ $(SASLLIB) $(LIBS)
+
+config_y.c config_y.h: configfile.y
+ $(YACC) -d $?
+ mv y.tab.h config_y.h
+ mv y.tab.c config_y.c
+
+config_l.c: configfile.l
+ $(LEX) $?
+ mv lex.yy.c config_l.c
+
+version.c: Makefile ../Makefile.global
+ version=`echo '$(VERSION) ($(VERSION_EXTRA))' | sed 's/ ()//'` ; \
+ echo 'const char *versionInfo = "innfeed' "$$version\" ;" > $@
+
+innfeed: $(OBJECTS) connection.o $(LIBSTORAGE) $(LIBINN)
+ $(LIBLD) $(LDFLAGS) -o $@ $(OBJECTS) connection.o $(INNFEEDLIBS)
+
+imapfeed: $(OBJECTS) imap_connection.o $(LIBSTORAGE) $(LIBINN)
+ $(LIBLD) $(LDFLAGS) -o $@ $(OBJECTS) imap_connection.o $(INNFEEDLIBS)
+
+procbatch: procbatch.in $(FIXSCRIPT)
+ $(FIXSCRIPT) procbatch.in
+
+startinnfeed: startinnfeed.o $(LIBINN)
+ $(LIBLD) $(LDFLAGS) -o $@ startinnfeed.o $(LIBINN) $(LIBS)
+
+# Not normally built.
+innfeed-convcfg: innfeed-convcfg.in $(FIXSCRIPT)
+ $(FIXSCRIPT) -i innfeed-convcfg.in
+
+tst: config_y.c config_l.c
+ gcc -DWANT_MAIN -o tst -g -Wall config_y.c config_l.c -ly -ll
+
+
+## Profiling. These rules have not been checked for a while and may need
+## some work.
+
+profiled: innfeedp
+ date >$@
+
+innfeedp: $(SOURCES)
+ rm -f $(OBJECTS)
+ $(MAKEPROFILING) innfeed
+ mv innfeed innfeedp
+ rm -f $(OBJECTS)
+
+
+## Dependencies. Default list, below, is probably good enough.
+
+depend: Makefile $(SOURCES)
+ $(MAKEDEPEND) '$(CFLAGS)' $(SOURCES)
+
+# DO NOT DELETE THIS LINE -- make depend depends on it.
+article.o: article.c innfeed.h ../include/inn/timer.h \
+ ../include/inn/defines.h ../include/inn/system.h ../include/config.h \
+ ../include/inn/defines.h ../include/clibrary.h ../include/config.h \
+ ../include/portable/mmap.h ../include/config.h \
+ ../include/inn/messages.h ../include/libinn.h ../include/storage.h \
+ article.h misc.h buffer.h endpoint.h
+buffer.o: buffer.c innfeed.h ../include/inn/timer.h \
+ ../include/inn/defines.h ../include/inn/system.h ../include/config.h \
+ ../include/inn/defines.h ../include/clibrary.h ../include/config.h \
+ ../include/inn/messages.h ../include/libinn.h buffer.h misc.h
+config_l.o: config_l.c innfeed.h ../include/inn/timer.h \
+ ../include/inn/defines.h ../include/inn/system.h ../include/libinn.h \
+ ../include/inn/defines.h ../include/config.h configfile.h config_y.h \
+ misc.h ../include/config.h
+config_y.o: config_y.c innfeed.h ../include/inn/timer.h \
+ ../include/inn/defines.h ../include/inn/system.h ../include/config.h \
+ ../include/inn/defines.h ../include/clibrary.h ../include/config.h \
+ ../include/inn/messages.h ../include/libinn.h configfile.h misc.h
+endpoint.o: endpoint.c innfeed.h ../include/inn/timer.h \
+ ../include/inn/defines.h ../include/inn/system.h ../include/config.h \
+ ../include/inn/defines.h ../include/clibrary.h ../include/config.h \
+ ../include/portable/socket.h ../include/config.h \
+ ../include/portable/time.h ../include/inn/innconf.h \
+ ../include/inn/messages.h ../include/libinn.h buffer.h misc.h \
+ configfile.h endpoint.h host.h
+host.o: host.c innfeed.h ../include/inn/timer.h ../include/inn/defines.h \
+ ../include/inn/system.h ../include/config.h ../include/inn/defines.h \
+ ../include/clibrary.h ../include/config.h ../include/portable/socket.h \
+ ../include/config.h ../include/inn/innconf.h ../include/inn/messages.h \
+ ../include/libinn.h article.h misc.h buffer.h configfile.h connection.h \
+ endpoint.h host.h innlistener.h tape.h
+innlistener.o: innlistener.c innfeed.h ../include/inn/timer.h \
+ ../include/inn/defines.h ../include/inn/system.h ../include/config.h \
+ ../include/inn/defines.h ../include/clibrary.h ../include/config.h \
+ ../include/inn/messages.h ../include/libinn.h article.h misc.h buffer.h \
+ configfile.h endpoint.h host.h innlistener.h ../include/nntp.h tape.h
+main.o: main.c innfeed.h ../include/inn/timer.h ../include/inn/defines.h \
+ ../include/inn/system.h ../include/config.h ../include/inn/defines.h \
+ ../include/clibrary.h ../include/config.h ../include/portable/socket.h \
+ ../include/config.h ../include/portable/time.h ../include/inn/innconf.h \
+ ../include/inn/messages.h ../include/libinn.h ../include/storage.h \
+ article.h misc.h buffer.h configfile.h connection.h endpoint.h host.h \
+ innlistener.h tape.h
+misc.o: misc.c innfeed.h ../include/inn/timer.h ../include/inn/defines.h \
+ ../include/inn/system.h ../include/config.h ../include/inn/defines.h \
+ ../include/clibrary.h ../include/config.h ../include/inn/messages.h \
+ ../include/libinn.h endpoint.h misc.h tape.h
+startinnfeed.o: startinnfeed.c ../include/config.h \
+ ../include/inn/defines.h ../include/inn/system.h ../include/clibrary.h \
+ ../include/config.h ../include/inn/innconf.h ../include/inn/defines.h \
+ ../include/inn/messages.h ../include/libinn.h
+tape.o: tape.c innfeed.h ../include/inn/timer.h ../include/inn/defines.h \
+ ../include/inn/system.h ../include/config.h ../include/inn/defines.h \
+ ../include/clibrary.h ../include/config.h ../include/inn/innconf.h \
+ ../include/inn/messages.h ../include/libinn.h article.h misc.h \
+ configfile.h endpoint.h tape.h
+version.o: version.c
--- /dev/null
+This is version 1.0 of the INN feeder program `innfeed.'
+It is written in ANSI C and tries to be POSIX.1 compliant. This software
+was originally written and maintained by James Brister <brister@vix.com>
+and is now part of INN. The features of it are:
+
+ 1. Handles the STREAM extenstion to NNTP.
+ 2. Will open multiple connections to the remote host to parallel
+ feed.
+ 3. Will handle multiple remote hosts.
+ 4. Will tear down idle connections.
+ 5. Runs as a channel/funnel feed from INN, or by reading a funnel
+ file.
+ 6. Will stop issuing CHECK commands and go straight to TAKETHIS if
+ the remote responds affermatively to enough CHECKs.
+ 7. It will go back to issuing CHECKs if enough TAKETHIS commands fail.
+
+
+Changes for 0.10.1
+
+ 1. Config file inclusion now works via the syntax:
+
+ $INCLUDE pathname
+
+ Config files can be included up to a nesting depth of 10. Line
+ numbers and file names are not properly reported yet on errors
+ when includes are used, though.
+
+ 2. Signal handling is tidied up a bit. If your compiler doesn't
+ support the ``volatile'' keyword, then see the comment in
+ sysconfig.h.
+
+ 3. If you have a stdio library that hash limit on open files lower
+ then the process limit for open plain files (all flavours of
+ SunOS), then a new config file variable ``stdio-fdmax'' can be
+ used to give that upper bound. When set, all new network
+ connections will be limited to file descriptors over this value,
+ leaving the lower file descriptors free for stdio. See
+ innfeed.conf(5) for more details. Remember that the config file
+ value overrides any compiled in value.
+
+Changes for 0.10
+
+ 1. A major change has been made to the config file. The new format
+ is quite extensible and will let new data items be added in the
+ future without changing the basic format. There's a new option
+ ``-C'' (for ``check'') that will make innfeed read the config
+ file and report on any errors and then exit. This will let you
+ verify things before locking them into a newsfeeds file entry. A
+ program has been added ``innfeed-convcfg'' that will read your
+ old config off the command line (or stdin), and will write a new
+ version to stdout.
+
+ The new config file structure permits non-peer-specific items to
+ be declared (like the location of the status file, or whether to
+ wrap the generated status file in HTML). This is part of the
+ included sample:
+
+ use-mmap: true
+ news-spool: /var/news/spool/articles
+ backlog-directory: /var/news/spool/innfeed
+ pid-file: innfeed.pid
+ status-file: innfeed.status
+ gen-html: false
+ log-file: innfeed.log
+ backlog-factor: 1.10
+ connection-stats: false
+ max-reconnect-time: 3600
+
+ so only option you'll probably need now is the ``-c'' option to
+ locate the config file, and as this is also compiled in, you may
+ not even need that.
+
+ See the innfeed.conf(5) man page for more details on config file
+ structure.
+
+ 2. The backlog file handling is changed slightly:
+
+ - The .output file is always kept open (until rotation time).
+ - The .output file is allowed to grow for at least 30
+ seconds (or the value defined by the key
+ backlog-rotate-period in the config file). This prevents
+ thrashing of backlog files.
+ - The hand-prepared file is checked for only every 600
+ seconds maximum (or the value defined by the key
+ backlog-new), not every time the files are rotated.
+ - The stating of the three backlog files is reduced
+ dramatically.
+
+ 3. Signal handling is changed so that they are more synchronous
+ with other activity. This should stop the frequent core-dumps
+ that occured when running in funnel file mode and sending
+ SIGTERM or SIGALRM.
+
+ 4. A bug related to zero-length articles was fixed. They will now be
+ logged.
+
+ 5. More information is in the innfeed.status file, including the
+ reasons return by the remote when it is throttled.
+
+ 6. SIGEMT is now a trigger for closing and reopening all the
+ backlog files. If you have scripts that need to fool with the
+ backlogs, then have the scripts move the backlogs out of the way
+ and then send the SIGEMT.
+
+Changes for 0.9.3
+
+ 1. If your system supports mmap() *and* if you have your articles
+ stored on disk in NNTP-ready format (rare), then you can have
+ innfeed mmap article data to save on memory (thanks to Dave
+ Lawrence). There is an important issue with this:
+
+ if you try to have innfeed handle too many articles (by
+ running many connections and/or high max-check values in
+ innfeed.conf) at once, then your system (not your process)
+ may run out of free vnodes (global file descriptors), as a
+ vnode is used as long as the file is mmaped. So be careful.
+
+ If your articles are not in NNTP format then this will be
+ noticed and the article will be pulled into memory for fixing up
+ (and then immediately munmap'd). You can disable use of MMAP if
+ you've built it in by using the '-M' flag. I tried mixing
+ mmap'ing and articles not in NNTP format and it was a real
+ performance loss. I'll be trying it differently later.
+
+ 2. If innfeed is asked to send an article to a host it knows
+ nothing about, or which it cannot acquire the required lock for
+ (which causes the "ME locked cannot setup peer ..." and "ME
+ unconfigured peer" syslog messages), then innfeed will deposit
+ the article information into a file matching the pattern
+ innfeed-dropped.* in the backlog directory (TAPE_DIRECTORY in
+ config.h). This file will not be processed in any manner -- it's
+ up to you to decide what to do with it (wait for innfeed to exit
+ before doing anything with it, or send innfeed a SIGHUP to get
+ it to reread its config file, which will roll this file).
+
+ 4. The output backlog files will now be kept below a certain byte
+ limit. This happens via the ``-e'' option. If, after writing to
+ an output file, the new length is bigger than the given limit
+ (multiplied by a fudge factor defined in config.h -- default of
+ 1.10) then the file will be shrunk down to this size (or slightly
+ smaller to find the end of line boundary). The front of the file
+ will be removed to do this. This means lost articles for the
+ entries removed.
+
+ 3. A SIGHUP will make the config be reloaded.
+
+ 4. The .checkpoint files have been dropped in favour of scribbling
+ the offset into the input file itself.
+
+ 5. When the process exits normally a final syslog entry covering
+ all of the peers over the life of the process is written. It
+ looks like:
+
+ Jan 12 15:51:53 data innfeed.tester[24189]: ME global
+ seconds 2472 offered 43820 accepted 10506
+ refused 31168 rejected 1773 missing 39
+
+ 6. SIGALARM now rolls the input file, rather than the log
+ file. This is useful in funnel file mode when you move the input
+ file and tell innd to flush it, then send innfeed the signal.
+
+ 7. The location of the pid file, config file and status file, can
+ now be relative, in which case they're relative to the backlog
+ directory.
+
+ 8. stdin stdout and stderr are initialized properly when innfeed is
+ started by a process that has closed them.
+
+ 9. Various values in config.h have changed (paths to look more like
+ values used in inn 1.5 and others to support point #7 above
+ more easily.)
+
+ 10. procbatch.pl can now 'require' innshellvars.pl that comes with
+ 1.5. The default is not to. You nead to do a one line tweak if
+ you want it to. The defaults in procbatch.pl match the new
+ defaults of innfeed.
+
+ 11. Core files that get generated on purpose will be done so in
+ CORE_DIRECTORY (as defined in config.h), if that is defined to a
+ pathname. If CORE_DIRECTORY is defined to be NULL (the default
+ now), then the core will be generated in the backlog directory (as
+ possibly modified by the '-b' option).
+
+
+Changes for 0.9.2
+
+ 1. Now includes David Lawrence's patches to handle funnel files.
+
+ 2. EAGAIN errors on read and writes are caught and dealt with (of
+ interest to Solaris `victims').
+
+ 3. It is now much faster at servicing the file descriptor attached
+ to innd. This means it is faster at recognising it has been
+ flushed and at dropping connections. This means fewer
+ conflicts with new innfeeds starting before the old one has
+ finished up. It is still a good net-citizen and it finishes the
+ commands already started, so the fast response is only as fast
+ as your slowest peer, but it no longer tries to send
+ everything it had queued internally, and locks get released much
+ quicker.
+
+ 4. Includes Michael Hucka's patch to make the innfeed.status output
+ neater.
+
+ 5. Includes Andy Vasilyev's HTML-in-innfeed.status patch (but you
+ have to enable it in config.h).
+
+ 6. Added a '-a' (top of news spool) and a '-p' (pid file path)
+ option.
+
+Changes for 0.9
+
+ 1. Format of innfeed.conf file changed slightly (for per-peer
+ streaming specs).
+ 2. Including Greg Patten's innlog.pl (taken from
+ ftp://loose.apana.org.au/pub/local/innlog/innlog.pl)
+ 3. Added Christophe Wolfhugel's patch to permit a per-peer
+ restriction on using streaming.
+ 4. More robust handling of peers that return bad responses (no long
+ just calling abort).
+
+Changes for 0.8.5
+
+ 1. Massive syslog messages cleanup courtesy of kre.
+ 2. The innlog.awk-patch hash been dropped from the distribution
+ until the new syslog messages are dealt with.
+ 3. State machine more robust in the face of unexpected responses
+ from remote. Connection gets torn down and bad response's
+ logged.
+ 4. The fixed timers (article reception timeout, read timeout,
+ and flush timeout) are all adjusted by up to +/-10% so that
+ things aren't quite so synchronised.
+ 5. The innfeed.status file has been expanded and reformatted to
+ include more information.
+
+Changes for 0.8.4
+
+ 1. A change in the handing off of articles to connections in order to
+ encourage connections that were opened due to activity spikes,
+ to close down sooner.
+ 2. The backlog files are no longer concatenated together at process
+ startup, but the .input is simply used if it exists, and if not
+ then the hand-dropped file is used first and the .output file
+ second.
+ 3. The innfeed.status is no longer updated by a innfeed that is in
+ its death-throws.
+ 4. Specifically catch the 480 response code from NNRPD when we try
+ to give it an IHAVE.
+ 5. The connection reestablishment time gets properly increased when
+ the connection fails to go through (up to and including the
+ reading of the banner message).
+ 6. Bug fix that occasionally had articles sit in a connection and
+ never get processed.
+ 7. Bug fix in the counter of number of sleeping connections.
+ 8. Bug fix in config file parsing.
+ 9. Procbatch.pl included.
+
+Changes for version 0.8.1
+
+ 1. various bug fixes.
+ 2. core files generated by ASSERT are (possibly) put in a seperate
+ directory to ease debugging are
+
+Changes for version 0.8
+
+ 1. The implicit state machine in the Connection objects has been
+ made explicit.
+ 2. Various bug fixes.
+
+Changes for version 0.7.1
+
+ 1. Pulled the source to inet_addr.c from the bind distribution.
+ (Solaris and some others don't have it).
+
+Changes for version 0.7
+
+ 1. The backlog file mechanism has been completely reworked. There are
+ now only two backlog files: one for output and on for input. The
+ output file becomes the input file when the input file is
+ exhausted.
+ 2. Much less strenuous use of writev. Solaris and other sv4r
+ machines have an amazingly low value for the maximum number of
+ iovecs that can be passed into writev.
+ 3. Proper connection cleanup (QUIT issued) at shutdown.
+ 4. A lock is taken out in the backlog directory for each peer. To feed
+ the same peer from two different instances of innfeed (with a
+ batch file for example), then you must use another directory.
+ 5. Creating a file in the backlog directory with the same name as the
+ peer, the that file will be used next time backlog files are
+ processed. Its format must be:
+
+ pathname msgid
+
+ where pathname is absolute, or relative to the top of the news
+ spool.
+ 6. More command line options.
+ 7. Dynamic peer creation. If the proper command line option is
+ used (-y) and innfeed is to told to feed a peer that it doesn't
+ have in its config file, then it will create a new binding to
+ the new peer. The ip name must be the same as the peername,
+ i.e. if innd tells innfeed about a peer fooBarBat, then
+ gethostbyname("fooBarBat") better work.
+ 8. Connections will be periodically torn down (1 hour is the
+ default), even if they're active, so that non-innd peers don't
+ have problems with their history files being kept open for too
+ long.
+ 9. The input backlog files are checkpointed every 30 seconds
+ so that a crash while processing a large backlog doesn't require
+ starting over from the beginning.
+
+Changes for version 0.6
+
+ 1. Logging of spooling of backlog only happens once per
+ stats-logging period.
+
+Bugs/Problems/Notes etc:
+
+ 1. There is no graceful handling of file descriptor exhaustion.
+
+ 2. If the following situation occurs:
+
+ - articles on disk are NOT in NNTP-ready format.
+ - innfeed was built with HAVE_MMAP defined.
+ - memory usage is higher than expected
+
+ try running innfeed with the '-M' flag (or recompiling with
+ HAVE_MMAP undefined). Solaris, and possibly other SVR4 machines,
+ waste a lot of swap space.
+
+ 3. On the stats logging the 'offered' may not equal the sum of the
+ other fields. This is because the stats at that moment were
+ generated while waiting for a response to a command to come
+ back. Innfeed considers an article ``offered'' when it sends the
+ command, not when it gets a response back. Perhaps this should
+ change.
+
+ 4. If all the Connections for a peer are idle and a new backlog file
+ is dropped in by hand, then it will not be picked up until the
+ next time it gets an article from innd for that peer. This will
+ be fixed in a later version, but for now, if the peer is likely
+ to be idle for a long time, then flush the process.
+
+ 5. Adding a backlog file by hand does not cause extra Connections to
+ be automatically created, only the existing Connections will use
+ the file. If the extra load requires new Connections to be built
+ when innd delivers new articles for tranmission, then they too
+ will use the file, but this a side effect and not a direct
+ consequence. This means if you want to run in '-x' mode, then
+ make sure your config file entry states the correct number of
+ initial connections, as they're all the Connections that will be
+ created.
+
+ 6. If '-x' is used and the config file has an entry for a peer that
+ has no batch file to process, then innfeed will not exit after
+ all batch files have been finished--it will just site there idle.
+
+ 7. If the remote is running inn and only has you in the nnrp.access
+ file, then innfeed will end up talking to nnrpd. Innfeed will
+ try every 30 seconds to reconnect to a server that will accept
+ IHAVE commands. i.e. there is no exponential back of retry
+ attempt. This is because the connection is considered good once
+ the MODE STREAM command has been accepted or rejected (and nnrpd
+ rejects it).
+
+Futures:
+
+ 1. Innfeed will eventually take exploder commands.
+
+ 2. The config file will be revamped to allow for more global
+ options etc and run-time configuration. Too much is compile-time
+ dependant at the moment.
+
+ 3. The connection retry time will get more sophisticated to catch
+ problems like the nnrpd issue mentioned above.
+
+ 4. Include the number of takesthis/check/ihave commands issued in
+ the log entries.
+
+ 5. Heaps more stuff requested that's buried in my mail folders.
+
+
+Any compliments, complaints, requests, porting issues etc. should go to
+inn-bugs@isc.org.
+
+Many thanks to the following people for extra help (above and beyond the
+call of duty) with pateches, beta testing and/or suggestions:
+
+ Christophe Wolfhugel <wolf@pasteur.fr>
+ Robert Elz <kre@munnari.oz.au>
+ Russell Vincent <vincent@ucthpx.uct.ac.za>
+ Paul Vixie <paul@vix.com>
+ Stephen Stuart <stuart@pa.dec.com>
+ John T. Stapleton <stapes@mro.dec.com>
+ Alan Barrett <apb@iafrica.com>
+ Lee McLoughlin <lmjm@doc.ic.ac.uk>
+ Dan Ellis <ellis@mail.microserve.net>
+ Katsuhiro Kondou <kondou@uxd.fc.nec.co.jp>
+ Marc G. Fournier <scrappy@ki.net>
+ Steven Bauer <sbauer@msmailgw.sdsmt.edu>
+ Richard Perini <rpp@ci.com.au>
+ Per Hedeland <per@erix.ericsson.se>
+ Clayton O'Neill <coneill@premier.net>
+ Dave Pascoe <dave@mathworks.com>
+ Michael Handler <handler@netaxs.com>
+ Petr Lampa <lampa@fee.vutbr.cz>
+ David Lawrence <tale@uu.net>
+ Don Lewis <Don.Lewis@tsc.tdk.com>
+ Landon Curt Noll <noll@sgi.com>
+
+If I've forgotten anybody, please let me know.
+
+Thanks also to the ISC for sponsoring this work.
--- /dev/null
+/* $Id: article.c 7285 2005-06-07 06:38:24Z eagle $
+**
+** The Article class for innfeed.
+**
+** Written by James Brister <brister@vix.com>
+**
+** The implementation of the Article class. Articles are the abstraction for
+** the actual news articles. They are a reference counted object because they
+** need to be shared by multiple Host and Connection objects. When an Article
+** is created it verifies that the actual file exists. When a Connection
+** wants the article's contents for transmission the Article reads the data
+** off disk and returns a set of Buffer objects. The Article holds onto these
+** Buffers so that the next Connection that wants to transmit it won't have
+** to wait for a disk read to be done again.
+*/
+
+#include "innfeed.h"
+#include "config.h"
+#include "clibrary.h"
+#include "portable/mmap.h"
+
+#include <assert.h>
+#include <errno.h>
+#include <fcntl.h>
+#if HAVE_LIMITS_H
+# include <limits.h>
+#endif
+#include <sys/stat.h>
+#include <syslog.h>
+
+#include "inn/messages.h"
+#include "libinn.h"
+#include "storage.h"
+
+#include "article.h"
+#include "buffer.h"
+#include "endpoint.h"
+
+#if defined (NDEBUG)
+#define VALIDATE_HASH_TABLE() (void (0))
+#else
+#define VALIDATE_HASH_TABLE() hashValidateTable ()
+#endif
+
+extern bool useMMap ;
+
+struct map_info_s {
+ ARTHANDLE *arthandle;
+ const void *mMapping;
+ size_t size;
+ int refCount;
+ struct map_info_s *next;
+};
+
+typedef struct map_info_s *MapInfo;
+
+struct article_s
+{
+ int refCount ; /* the reference count on this article */
+ char *fname ; /* the file name of the article */
+ char *msgid ; /* the msgid of the article (INN tells us) */
+ Buffer contents ; /* the buffer of the actual on disk stuff */
+ Buffer *nntpBuffers ; /* list of buffers for transmisson */
+ MapInfo mapInfo; /* arthandle and mMapping */
+ bool loggedMissing ; /* true if article is missing and we logged */
+ bool articleOk ; /* true until we know otherwise. */
+ bool inWireFormat ; /* true if ->contents is \r\n/dot-escaped */
+} ;
+
+struct hash_entry_s {
+ struct hash_entry_s *next ;
+ struct hash_entry_s *prev ;
+ struct hash_entry_s *nextTime ;
+ struct hash_entry_s *prevTime ;
+ unsigned int hash ;
+ Article article ;
+};
+
+typedef struct hash_entry_s *HashEntry ;
+
+ /*
+ * Private functions
+ */
+
+static Buffer artGetContents (Article article) ; /* Return the buffer that
+ fillContents() filled
+ up. */
+
+ /* Log statistics on article memory usage. */
+static void logArticleStats (TimeoutId id, void *data) ;
+
+static bool fillContents (Article article) ; /* Read the article's bits
+ off the disk. */
+
+ /* Append buffer B to the buffer array BUFFS. */
+static void appendBuffer (Buffer b, Buffer **buffs, int *newSpot, int *curLen);
+
+static bool prepareArticleForNNTP (Article article) ; /* Do the necessary
+ CR-LF stuff */
+
+static bool artFreeContents (Article art) ; /* Tell the Article to release
+ its contents buffer if
+ possible. */
+
+static void artUnmap (Article art) ; /* munmap an mmap()ed
+ article */
+
+
+ /*
+ * Hash table routine declarations.
+ */
+
+ /* Returns a has value for the given string */
+static unsigned int hashString (const char *string) ;
+
+ /* Locates the article with the given message ID, in the has table. */
+static Article hashFindArticle (const char *msgid) ;
+
+ /* Puts the given article in the hash table. */
+static void hashAddArticle (Article article) ;
+
+ /* Removes the given article from the has table */
+static bool hashRemoveArticle (Article article) ;
+
+ /* Does some simple-minded hash table validation */
+static void hashValidateTable (void) ;
+
+
+
+ /*
+ * Private data
+ */
+
+
+static unsigned int missingArticleCount ; /* Number of articles that were missing */
+
+static bool logMissingArticles ; /* if true then we log the details on a
+ missing article. */
+
+static unsigned int preparedBytes ; /* The number of areticle bytes read in so
+ far of disk (will wrap around) */
+
+static unsigned int preparedNewlines ; /* The number of newlines found in all the
+ preparedBytes */
+
+static unsigned int avgCharsPerLine ; /* The average number of characters per
+ line over all articles. */
+
+static bool rolledOver ; /* true if preparedBytes wrapped around */
+
+static unsigned int bytesInUse ; /* count of article contents bytes in use--just
+ the amount read from the article files, not
+ all memory involved in. */
+
+static unsigned int maxBytesInUse ; /* the limit we want to stay under for total
+ bytes resident in memory dedicated to
+ article contents. */
+
+static unsigned int articlesInUse ; /* number of articles currently allocated. */
+
+static unsigned int byteTotal ; /* number of bytes for article contents
+ allocated totally since last log. */
+
+static unsigned int articleTotal ; /* number of articles alloced since last log. */
+
+static TimeoutId articleStatsId ; /* The timer callback id. */
+
+static MapInfo mapInfo;
+
+ /*
+ * Hash Table data
+ */
+
+static HashEntry *hashTable ; /* the has table itself */
+static HashEntry chronList ; /* chronologically ordered. Points at newest */
+
+#define TABLE_SIZE 2048 /* MUST be a power of 2 */
+#define HASH_MASK (TABLE_SIZE - 1)
+#define TABLE_ENTRY(hash) ((hash) & HASH_MASK)
+
+
+
+ /*******************************************************************/
+ /** PUBLIC FUNCTIONS **/
+ /*******************************************************************/
+
+ /* Create a new article object. First looks to see if one with the given
+ message id already exists in the hash table and if so returns that
+ (after incrementing the reference count). */
+Article newArticle (const char *filename, const char *msgid)
+{
+ Article newArt = NULL ;
+
+ TMRstart(TMR_NEWARTICLE);
+ if (hashTable == NULL)
+ { /* first-time through initialization. */
+ int i ;
+
+ ASSERT ((TABLE_SIZE & HASH_MASK) == 0) ;
+ hashTable = xmalloc (sizeof(HashEntry) * TABLE_SIZE) ;
+
+ addPointerFreedOnExit ((char *)hashTable) ;
+
+ for (i = 0 ; i < TABLE_SIZE ; i++)
+ hashTable [i] = NULL ;
+
+ if (ARTICLE_STATS_PERIOD > 0)
+ articleStatsId = prepareSleep (logArticleStats,ARTICLE_STATS_PERIOD,0);
+ }
+
+ /* now look for it in the hash table. We presume the disk file is still
+ ok */
+ if ((newArt = hashFindArticle (msgid)) == NULL)
+ {
+ newArt = xcalloc (1, sizeof(struct article_s)) ;
+
+ newArt->fname = xstrdup (filename) ;
+ newArt->msgid = xstrdup (msgid) ;
+
+ newArt->contents = NULL ;
+ newArt->mapInfo = NULL ;
+ newArt->refCount = 1 ;
+ newArt->loggedMissing = false ;
+ newArt->articleOk = true ;
+ newArt->inWireFormat = false ;
+
+ d_printf (3,"Adding a new article(%p): %s\n", (void *)newArt, msgid) ;
+
+ articlesInUse++ ;
+ articleTotal++ ;
+
+ hashAddArticle (newArt) ;
+ }
+ else
+ {
+ if (strcmp (filename,newArt->fname) != 0)
+ warn ("ME two filenames for same article: %s, %s", filename,
+ newArt->fname) ;
+
+ newArt->refCount++ ;
+ d_printf (2,"Reusing existing article for %s\nx",msgid) ;
+ }
+ TMRstop(TMR_NEWARTICLE);
+ return newArt ;
+}
+
+
+ /* Decrement the reference count on the article and free up its memory if
+ the ref count gets to 0. */
+void delArticle (Article article)
+{
+ if (article == NULL)
+ return ;
+
+ ASSERT (article->refCount > 0) ;
+
+ if (--(article->refCount) == 0)
+ {
+ bool removed = hashRemoveArticle (article) ;
+
+ ASSERT (removed == true) ;
+
+ d_printf (2,"Cleaning up article (%p): %s\n",
+ (void *)article, article->msgid) ;
+
+ if (article->contents != NULL)
+ {
+ if (article->mapInfo)
+ artUnmap(article);
+ else
+ bytesInUse -= bufferDataSize (article->contents) ;
+
+ if (article->nntpBuffers != NULL)
+ freeBufferArray (article->nntpBuffers) ;
+
+ delBuffer (article->contents) ;
+ }
+
+ articlesInUse-- ;
+
+ free (article->fname) ;
+ free (article->msgid) ;
+ free (article) ;
+ }
+
+ VALIDATE_HASH_TABLE () ;
+}
+
+
+void gPrintArticleInfo (FILE *fp, unsigned int indentAmt)
+{
+ char indent [INDENT_BUFFER_SIZE] ;
+ unsigned int i ;
+
+ for (i = 0 ; i < MIN(INDENT_BUFFER_SIZE - 1,indentAmt) ; i++)
+ indent [i] = ' ' ;
+ indent [i] = '\0' ;
+
+ fprintf (fp,"%sGlobal Article information : (count %d) {\n",
+ indent, articlesInUse) ;
+
+ fprintf (fp,"%s missingArticleCount : %d\n",indent,missingArticleCount) ;
+ fprintf (fp,"%s logMissingArticles : %d\n",indent,logMissingArticles) ;
+ fprintf (fp,"%s preparedBytes : %d\n",indent,preparedBytes) ;
+ fprintf (fp,"%s preparedNewlines : %d\n",indent,preparedNewlines) ;
+ fprintf (fp,"%s avgCharsPerLine : %d\n",indent,avgCharsPerLine) ;
+ fprintf (fp,"%s rolledOver : %s\n",indent,boolToString (rolledOver)) ;
+ fprintf (fp,"%s bytesInUse : %d\n",indent,bytesInUse) ;
+ fprintf (fp,"%s maxBytesInUse : %d\n",indent,maxBytesInUse) ;
+ fprintf (fp,"%s articlesInUse : %d\n",indent,articlesInUse) ;
+ fprintf (fp,"%s byteTotal : %d\n",indent,byteTotal) ;
+ fprintf (fp,"%s articleTotal : %d\n",indent,articleTotal) ;
+ fprintf (fp,"%s articleStatsId : %d\n",indent,articleStatsId) ;
+
+ {
+ HashEntry he ;
+
+ for (he = chronList ; he != NULL ; he = he->nextTime)
+ printArticleInfo (he->article,fp,indentAmt + INDENT_INCR) ;
+ }
+
+ fprintf (fp,"%s}\n",indent) ;
+}
+
+
+void printArticleInfo (Article art, FILE *fp, unsigned int indentAmt)
+{
+ Buffer *b ;
+ char indent [INDENT_BUFFER_SIZE] ;
+ unsigned int i ;
+
+ for (i = 0 ; i < MIN(INDENT_BUFFER_SIZE - 1,indentAmt) ; i++)
+ indent [i] = ' ' ;
+ indent [i] = '\0' ;
+
+ fprintf (fp,"%sArticle : %p {\n",indent,(void *) art) ;
+ fprintf (fp,"%s article ok : %s\n",indent,boolToString (art->articleOk)) ;
+ fprintf (fp,"%s refcount : %d\n",indent,art->refCount) ;
+ fprintf (fp,"%s filename : %s\n",indent,art->fname) ;
+ fprintf (fp,"%s msgid : %s\n",indent,art->msgid) ;
+
+ fprintf (fp,"%s contents buffer : {\n",indent) ;
+#if 0
+ printBufferInfo (art->contents,fp,indentAmt + INDENT_INCR) ;
+#else
+ fprintf (fp,"%s %p\n",indent,(void *) art->contents) ;
+#endif
+
+ fprintf (fp,"%s }\n",indent) ;
+
+ fprintf (fp,"%s nntp buffers : {\n",indent) ;
+ for (b = art->nntpBuffers ; b != NULL && *b != NULL ; b++)
+#if 0
+ printBufferInfo (*b,fp,indentAmt + INDENT_INCR) ;
+#else
+ fprintf (fp,"%s %p\n",indent,(void *) *b) ;
+#endif
+
+ fprintf (fp,"%s }\n", indent) ;
+
+ fprintf (fp,"%s logged missing : %s\n",
+ indent,boolToString (art->loggedMissing));
+ fprintf (fp,"%s}\n", indent) ;
+
+}
+
+ /* return true if we have or are able to get the contents off the disk */
+bool artContentsOk (Article article)
+{
+ bool rval = false ;
+
+ if ( prepareArticleForNNTP (article) )
+ rval = true ;
+
+ return rval ;
+}
+
+
+ /* bump reference count on the article. */
+Article artTakeRef (Article article)
+{
+ if (article != NULL)
+ article->refCount++ ;
+
+ return article ;
+}
+
+
+ /* return the filename of the article */
+const char *artFileName (Article article)
+{
+ if (article == NULL)
+ return NULL ;
+ else
+ return article->fname ;
+}
+
+
+ /* Get a NULL terminated array of Buffers that is ready for sending via NNTP */
+
+Buffer *artGetNntpBuffers (Article article)
+{
+ if ( !prepareArticleForNNTP (article) )
+ return NULL ;
+
+ return dupBufferArray (article->nntpBuffers) ;
+}
+
+
+ /* return the message id of the article */
+const char *artMsgId (Article article)
+{
+ return article->msgid ;
+}
+
+ /* return size of the article */
+int artSize (Article article)
+{
+ if (article == NULL || article->contents == NULL)
+ return (int)0 ;
+ return (int)bufferDataSize(article->contents);
+}
+
+
+ /* return how many NNTP-ready buffers the article contains */
+unsigned int artNntpBufferCount (Article article)
+{
+ if ( !prepareArticleForNNTP (article) )
+ return 0 ;
+
+ return bufferArrayLen (article->nntpBuffers) ;
+}
+
+
+ /* if VAL is true then all missing articles will be logged. */
+void artLogMissingArticles (bool val)
+{
+ logMissingArticles = val ;
+}
+
+
+ /* set the limit we want to stay under. */
+void artSetMaxBytesInUse (unsigned int val)
+{
+ ASSERT (maxBytesInUse > 0) ; /* can only set one time. */
+ ASSERT (val > 0) ;
+
+ maxBytesInUse = val ;
+}
+
+
+ /**********************************************************************/
+ /** STATIC FUNCTIONS **/
+ /**********************************************************************/
+
+
+ /* return a single buffer that contains the disk image of the article (i.e.
+ not fixed up for NNTP). */
+static Buffer artGetContents (Article article)
+{
+ Buffer rval = NULL ;
+
+ if (article->articleOk)
+ {
+ if (article->contents == NULL)
+ fillContents (article) ;
+
+ if (article->contents != NULL)
+ rval = bufferTakeRef (article->contents) ;
+ }
+
+ return rval ;
+}
+
+ /* arthandle/mMapping needs to be refcounted since a buffer
+ may exist referencing it after delArticle if the remote
+ host sends the return status too soon (diablo, 439). */
+
+static MapInfo getMapInfo(ARTHANDLE *arthandle,
+ const void *mMapping, size_t size)
+{
+ MapInfo m;
+
+ for (m = mapInfo; m; m = m->next) {
+ if (m->arthandle == arthandle &&
+ m->mMapping == mMapping) {
+ m->refCount++;
+ return m;
+ }
+ }
+
+ m = xmalloc(sizeof(struct map_info_s));
+ m->refCount = 1;
+ m->arthandle = arthandle;
+ m->mMapping = mMapping;
+ m->size = size;
+ m->next = mapInfo;
+ mapInfo = m;
+
+ return m;
+}
+
+static void delMapInfo(void *vm)
+{
+ MapInfo i, prev;
+ MapInfo m = (MapInfo)vm;
+
+ if (m == NULL)
+ return;
+
+ if (--(m->refCount) > 0)
+ return;
+
+ if (m->arthandle)
+ SMfreearticle(m->arthandle);
+ else
+ if (munmap(m->mMapping, m->size) < 0)
+ syslog (LOG_NOTICE, "munmap article: %m");
+
+ prev = NULL;
+ for (i = mapInfo; i != m; i = i->next)
+ prev = i;
+
+ if (prev)
+ prev->next = m->next;
+ else
+ mapInfo = m->next;
+
+ free(m);
+}
+
+static void artUnmap (Article article) {
+
+ delMapInfo(article->mapInfo);
+ article->mapInfo = NULL;
+}
+
+
+
+static void logArticleStats (TimeoutId id, void *data UNUSED)
+{
+ ASSERT (id == articleStatsId) ;
+
+ notice ("ME articles active %d bytes %d", articlesInUse, bytesInUse) ;
+ notice ("ME articles total %d bytes %d", articleTotal, byteTotal) ;
+
+ byteTotal = 0 ;
+ articleTotal = 0 ;
+
+ articleStatsId = prepareSleep (logArticleStats,ARTICLE_STATS_PERIOD,0) ;
+}
+
+
+ /* do the actual read of the article off disk into a Buffer that is stored
+ in the Article object. The Article will end up with its contents field
+ having a buffer with the article data in it. This buffer may be
+ holding a mmapped pointer, or it may be simply a regular buffer with
+ the data read off disk into it. In the regular buffer case the
+ contents may be copied around after reading to insert a carriage
+ return before each newline. */
+
+static bool fillContents (Article article)
+{
+ int fd = -1;
+ char *p;
+ static bool maxLimitNotified ;
+ bool opened;
+ size_t articlesize = 0;
+ char *buffer = NULL ;
+ int amt = 0 ;
+ size_t idx = 0, amtToRead ;
+ size_t newBufferSize ;
+ HashEntry h ;
+ ARTHANDLE *arthandle = NULL;
+ const void *mMapping = NULL;
+
+ ASSERT (article->contents == NULL) ;
+
+ TMRstart(TMR_READART);
+ if (maxBytesInUse == 0)
+ maxBytesInUse = SOFT_ARTICLE_BYTE_LIMIT ;
+
+ if (avgCharsPerLine == 0)
+ avgCharsPerLine = 75 ; /* roughly number of characters per line */
+
+ if (IsToken(article->fname)) {
+ opened = ((arthandle = SMretrieve(TextToToken(article->fname), RETR_ALL)) != NULL) ? true : false;
+ if (opened)
+ articlesize = arthandle->len;
+ else {
+ if (SMerrno != SMERR_NOENT && SMerrno != SMERR_UNINIT) {
+ syslog(LOG_ERR, "Could not retrieve %s: %s",
+ article->fname, SMerrorstr);
+ article->articleOk = false;
+ TMRstop(TMR_READART);
+ return false;
+ }
+ }
+ } else {
+ struct stat sb ;
+
+ opened = ((fd = open (article->fname,O_RDONLY,0)) >= 0) ? true : false;
+ arthandle = NULL;
+ if (opened) {
+ if (fstat (fd, &sb) < 0) {
+ article->articleOk = false ;
+ syswarn ("ME oserr fstat %s", article->fname) ;
+ TMRstop(TMR_READART);
+ return false;
+ }
+ if (!S_ISREG (sb.st_mode)) {
+ article->articleOk = false ;
+ warn ("ME article file-type: %s", article->fname) ;
+ TMRstop(TMR_READART);
+ return false;
+ }
+ if (sb.st_size == 0) {
+ article->articleOk = false ;
+ warn ("ME article 0 bytes: %s", article->fname) ;
+ TMRstop(TMR_READART);
+ return false;
+ }
+ articlesize = sb.st_size;
+ }
+ }
+
+ if (!opened) {
+ article->articleOk = false ;
+ missingArticleCount++ ;
+
+ if (logMissingArticles && !article->loggedMissing)
+ {
+ notice ("ME article missing: %s, %s", article->msgid,
+ article->fname) ;
+ article->loggedMissing = true ;
+ }
+ TMRstop(TMR_READART);
+ return false;
+ }
+ amtToRead = articlesize ;
+ newBufferSize = articlesize ;
+
+ if (arthandle || useMMap) {
+ if (arthandle)
+ mMapping = arthandle->data;
+ else
+ mMapping = mmap(NULL, articlesize, PROT_READ,
+ MAP_SHARED, fd, 0);
+
+ if (mMapping == MAP_FAILED) {
+ /* dunno, but revert to plain reading */
+ mMapping = NULL ;
+ syswarn ("ME mmap failure %s (%s)",
+ article->fname,
+ strerror(errno)) ;
+ } else {
+ article->contents = newBufferByCharP(mMapping,
+ articlesize,
+ articlesize);
+ article->mapInfo = getMapInfo(arthandle, mMapping, articlesize);
+ article->mapInfo->refCount++; /* one more for the buffer */
+ bufferSetDeletedCbk(article->contents, delMapInfo, article->mapInfo);
+
+ buffer = bufferBase (article->contents) ;
+ if ((p = strchr(buffer, '\n')) == NULL) {
+ article->articleOk = false;
+ delBuffer (article->contents) ;
+ article->contents = NULL ;
+ warn ("ME munged article %s", article->fname) ;
+ } else {
+ if (p[-1] == '\r') {
+ article->inWireFormat = true ;
+ } else {
+ /* we need to copy the contents into a buffer below */
+ delBuffer (article->contents) ;
+ article->contents = NULL ;
+ }
+ }
+ }
+ }
+
+ if (article->contents == NULL && article->articleOk) {
+ /* an estimate to give some room for nntpPrepareBuffer to use. */
+ newBufferSize *= (1.0 + (1.0 / avgCharsPerLine)) ;
+ newBufferSize ++ ;
+
+ /* if we're going over the limit try to free up some older article's
+ contents. */
+ if (amtToRead + bytesInUse > maxBytesInUse)
+ {
+ for (h = chronList ; h != NULL ; h = h->nextTime)
+ {
+ if (artFreeContents (h->article))
+ if (amtToRead + bytesInUse <= maxBytesInUse)
+ break ;
+ }
+ }
+
+ /* we we couldn't get below, then log it (one time only) */
+ if ((amtToRead + bytesInUse) > maxBytesInUse && maxLimitNotified == false) {
+ maxLimitNotified = true ;
+ notice ("ME exceeding maximum article byte limit: %d (max),"
+ " %lu (cur)", maxBytesInUse,
+ (unsigned long) (amtToRead + bytesInUse)) ;
+ }
+
+ if ((article->contents = newBuffer (newBufferSize)) == NULL)
+ amtToRead = 0 ;
+ else {
+ buffer = bufferBase (article->contents) ;
+ bytesInUse += articlesize ;
+ byteTotal += articlesize ;
+ }
+
+ if (mMapping && buffer != NULL) {
+ memcpy(buffer, mMapping, articlesize);
+ artUnmap(article) ;
+ amtToRead = 0;
+ }
+
+ while (amtToRead > 0) {
+ if ((amt = read (fd, buffer + idx,amtToRead)) <= 0) {
+ syswarn ("ME article read error: %s", article->fname) ;
+ bytesInUse -= articlesize ;
+ byteTotal -= articlesize ;
+ amtToRead = 0 ;
+
+ delBuffer (article->contents) ;
+ article->contents = NULL ;
+ }
+ else {
+ idx += amt ;
+ amtToRead -= amt ;
+ }
+ }
+
+ if (article->contents != NULL) {
+ bufferSetDataSize (article->contents, articlesize) ;
+
+ if ((p = strchr(buffer, '\n')) == NULL) {
+ article->articleOk = false;
+ warn ("ME munged article %s", article->fname) ;
+ }
+ else if (p[-1] == '\r') {
+ article->inWireFormat = true ;
+ }
+ else {
+ if ( nntpPrepareBuffer (article->contents) ) {
+ size_t diff =
+ (bufferDataSize (article->contents) - articlesize) ;
+
+ if (((unsigned int) UINT_MAX) - diff <= preparedBytes) {
+ d_printf (2,"Newline ratio so far: %02.2f\n",
+ ((double) preparedBytes / preparedNewlines)) ;
+ notice ("ME newline to file size ratio: %0.2f (%d/%d)",
+ ((double) preparedBytes)/preparedNewlines,
+ preparedBytes,preparedNewlines) ;
+ preparedBytes = 0 ;
+ preparedNewlines = 0 ;
+ rolledOver = true ;
+ }
+
+ preparedBytes += articlesize ;
+ preparedNewlines += diff ;
+ bytesInUse += diff ;
+ byteTotal += diff ;
+
+ if (preparedBytes > (1024 * 1024)) {
+ avgCharsPerLine =
+ ((double) preparedBytes) / preparedNewlines ;
+ avgCharsPerLine++ ;
+ }
+ article->inWireFormat = true ;
+ } else {
+ warn ("ME internal failed to prepare buffer for NNTP") ;
+ bytesInUse -= articlesize ;
+ byteTotal -= articlesize ;
+
+ delBuffer (article->contents) ;
+ article->contents = NULL ;
+ }
+ }
+ }
+ }
+
+
+ /* If we're not useing storage api, we should close a valid file descriptor */
+ if (!arthandle && (fd >= 0))
+ close (fd) ;
+
+ TMRstop(TMR_READART);
+ return (article->contents != NULL ? true : false) ;
+}
+
+
+
+ /* stick the buffer B into the Buffer array pointed at by BUFFS *BUFFS is
+ reallocated if necessary. NEWSPOT points at the index where B should be
+ put (presumably the end). CURLEN points at the length of the BUFFS and
+ it will get updated if BUFFS is reallocated. */
+static void appendBuffer (Buffer b, Buffer **buffs, int *newSpot, int *curLen)
+{
+ if (*newSpot == *curLen)
+ {
+ *curLen += 10 ;
+ *buffs = xrealloc (*buffs, sizeof(Buffer) * *curLen) ;
+ }
+ (*buffs) [(*newSpot)++] = b ;
+}
+
+
+
+ /* Takes the articles contents buffer and overlays a set of new buffers on
+ top of it. These buffers insert the required carriage return and dot
+ characters as needed */
+static bool prepareArticleForNNTP (Article article)
+{
+ static Buffer dotFirstBuffer ;
+ static Buffer dotBuffer ;
+ static Buffer crlfBuffer ;
+ Buffer *nntpBuffs = NULL ;
+ int buffLen = 0 ;
+ int buffIdx = 0 ;
+ char *start, *end ;
+ Buffer contents ;
+
+ contents = artGetContents (article) ; /* returns a reference */
+
+ TMRstart(TMR_PREPART);
+ if (contents == NULL) {
+ TMRstop(TMR_PREPART);
+ return false ;
+ }
+ else if (article->nntpBuffers != NULL)
+ {
+ delBuffer (contents) ;
+ TMRstop(TMR_PREPART);
+ return true ; /* already done */
+ }
+
+ if (dotBuffer == NULL)
+ {
+ dotBuffer = newBufferByCharP (".\r\n",3,3) ;
+ dotFirstBuffer = newBufferByCharP ("\r\n.",3,3) ;
+ crlfBuffer = newBufferByCharP ("\r\n",2,2) ;
+ }
+
+ /* overlay a set of buffers on top of the articles contents buffer. This
+ is a real speed loss at the moment, so by default it's disabled (by
+ calling artBitFiddleContents(true) in main(). */
+ if (article->inWireFormat == false)
+ {
+ end = bufferBase (contents) ;
+ do
+ {
+ start = end ;
+
+ while (*end && *end != '\n')
+ end++ ;
+
+ appendBuffer (newBufferByCharP (start, (size_t) (end - start),
+ (size_t) (end - start)),
+ &nntpBuffs,&buffIdx,&buffLen) ;
+
+ if (*end != '\0')
+ end++ ;
+
+ }
+ while (*end != '\0') ;
+
+ appendBuffer (bufferTakeRef (dotBuffer), &nntpBuffs,&buffIdx,&buffLen) ;
+ appendBuffer (NULL,&nntpBuffs,&buffIdx,&buffLen) ;
+ }
+ else
+ {
+ /* we already fixed the contents up when we read in the article */
+ nntpBuffs = xmalloc (sizeof(Buffer) * 3) ;
+ nntpBuffs [0] = newBufferByCharP (bufferBase (contents),
+ bufferDataSize (contents),
+ bufferDataSize (contents)) ;
+ if (article->mapInfo) {
+ article->mapInfo->refCount++;
+ bufferSetDeletedCbk(nntpBuffs[0], delMapInfo, article->mapInfo);
+ }
+ nntpBuffs [1] = NULL ;
+ }
+
+
+ delBuffer (contents) ; /* the article is still holding a reference */
+ article->nntpBuffers = nntpBuffs ;
+ TMRstop(TMR_PREPART);
+ return true ;
+}
+
+
+ /* free the contents of the buffers if article is the only thing holding a
+ reference. Returns true if it could, false if it couldn't */
+static bool artFreeContents (Article art)
+{
+ if (art->contents == NULL)
+ return false ;
+
+ if (art->nntpBuffers != NULL)
+ {
+ if (bufferRefCount (art->nntpBuffers[0]) > 1)
+ return false ;
+ else
+ {
+ freeBufferArray (art->nntpBuffers) ;
+ art->nntpBuffers = NULL ;
+ }
+ }
+
+ ASSERT (bufferRefCount (art->contents) == 1) ;
+
+ if (art->mapInfo)
+ artUnmap(art);
+ else
+ bytesInUse -= bufferDataSize (art->contents) ;
+
+ delBuffer (art->contents) ;
+
+ art->contents = NULL ;
+
+ return true ;
+}
+
+
+
+
+
+
+
+
+ /**********************************************************************/
+ /* Private hash table and routines for storing articles */
+ /**********************************************************************/
+
+
+
+
+ /* Hash function lifted from perl 5 */
+static unsigned int hashString (const char *string)
+{
+ unsigned int i ;
+
+ for (i = 0 ; string && *string ; string++)
+ i = 33 * i + (u_char) *string ;
+
+ return i ;
+}
+
+
+ /* find the article in the has table and return it. */
+static Article hashFindArticle (const char *msgid)
+{
+ unsigned int hash = hashString (msgid) ;
+ HashEntry h ;
+
+ for (h = hashTable [TABLE_ENTRY(hash)] ; h != NULL ; h = h->next)
+ if (hash == h->hash && strcmp (msgid,h->article->msgid) == 0)
+ break ;
+
+ return (h == NULL ? NULL : h->article) ;
+}
+
+
+ /* add the article to the hash table. */
+static void hashAddArticle (Article article)
+{
+ unsigned int hash = hashString (article->msgid) ;
+ HashEntry h ;
+ HashEntry ne ;
+
+ h = hashTable [TABLE_ENTRY(hash)] ;
+
+ ne = xmalloc (sizeof(struct hash_entry_s));
+
+ ne->article = article ;
+ ne->hash = hash ;
+ ne->next = hashTable [TABLE_ENTRY(hash)] ;
+ ne->prev = NULL ;
+
+ if (h != NULL)
+ h->prev = ne ;
+
+ hashTable [TABLE_ENTRY(hash)] = ne ;
+
+ ne->nextTime = chronList ;
+ ne->prevTime = NULL ;
+
+ if (chronList != NULL)
+ chronList->prevTime = ne ;
+
+ chronList = ne ;
+}
+
+
+ /* remove the article from the hash table and chronological list.
+ Does not delete the article itself. */
+static bool hashRemoveArticle (Article article)
+{
+ unsigned int hash = hashString (article->msgid) ;
+ HashEntry h ;
+
+ for (h = hashTable [TABLE_ENTRY(hash)] ; h != NULL ; h = h->next)
+ if (hash == h->hash && strcmp (article->msgid,h->article->msgid) == 0)
+ break ;
+
+ if (h == NULL)
+ return false ;
+
+ if (h == hashTable [TABLE_ENTRY(hash)])
+ {
+ hashTable [TABLE_ENTRY(hash)] = h->next ;
+ if (h->next != NULL)
+ h->next->prev = NULL ;
+ }
+ else
+ {
+ h->prev->next = h->next ;
+ if (h->next != NULL)
+ h->next->prev = h->prev ;
+ }
+
+ if (chronList == h)
+ {
+ chronList = h->nextTime ;
+ if (chronList != NULL)
+ chronList->prevTime = NULL ;
+ }
+ else
+ {
+ h->prevTime->nextTime = h->nextTime ;
+ if (h->nextTime != NULL)
+ h->nextTime->prevTime = h->prevTime ;
+ }
+
+ free (h) ;
+
+ return true ;
+}
+
+#define HASH_VALIDATE_BUCKET_COUNT 1 /* hash buckets to check per call */
+
+static void hashValidateTable (void)
+{
+ static int hbn = 0 ;
+ int i ;
+ HashEntry he ;
+
+#if ! defined (NDEBUG)
+ for (i = 0 ; i < HASH_VALIDATE_BUCKET_COUNT ; i++)
+ {
+ for (he = hashTable [hbn] ; he != NULL ; he = he->next)
+ ASSERT (he->article->refCount > 0) ;
+ if (++hbn >= TABLE_SIZE)
+ hbn = 0 ;
+ }
+#endif
+}
--- /dev/null
+/* $Id: article.h 6648 2004-01-25 20:07:11Z rra $
+**
+** The public interface to articles.
+**
+** Written by James Brister <brister@vix.com>
+**
+** The public interface to articles. The articles are implemented via
+** reference counting. This interface provides the methods for getting the
+** contents of the article.
+**
+** When an Article is created there's a chance that another copy of it
+** already exists. For example if the Article is pulled out of a Tape for a
+** particular host it may already be in existance in some other host. This
+** class will manage this situation to prevent multiple copies of the article
+** being in core.
+*/
+
+#if ! defined ( article_h__ )
+#define article_h__
+
+#include <stdio.h>
+
+#include "misc.h"
+
+
+ /* Create a new Article object. FILENAME is the path of the file the */
+ /* article is in. MSGID is the news message id of the article */
+Article newArticle (const char *filename, const char *msgid) ;
+
+ /* delete the given article. Just decrements refcount and then FREEs if the
+ refcount is 0. */
+void delArticle (Article article) ;
+
+void gPrintArticleInfo (FILE *fp, unsigned int indentAmt) ;
+
+ /* print some debugging info about the article. */
+void printArticleInfo (Article art, FILE *fp, unsigned int indentAmt) ;
+
+ /* return true if this article's file still exists. */
+bool artFileIsValid (Article article) ;
+
+ /* return true if we have the article's contents (calling this may trigger
+ the reading off the disk). */
+bool artContentsOk (Article article) ;
+
+ /* increments reference count and returns a copy of article that can be
+ kept (or passed off to someone else) */
+Article artTakeRef (Article article) ;
+
+ /* return the pathname of the file the article is in. */
+const char *artFileName (Article article) ;
+
+ /* return a list of buffers suitable for giving to an endpoint. The return
+ value can (must) be given to freeBufferArray */
+Buffer *artGetNntpBuffers (Article article) ;
+
+ /* return the message id stoed in the article object */
+const char *artMsgId (Article article) ;
+
+ /* return size of the article */
+int artSize (Article article) ;
+
+ /* return the number of buffers that artGetNntpBuffers() would return. */
+unsigned int artNntpBufferCount (Article article) ;
+
+ /* tell the Article class to log (or not) missing articles as they occur. */
+void artLogMissingArticles (bool val) ;
+
+ /* if VAL is true, then when an article is read off disk the necesary
+ carriage returns are inserted instead of setting up iovec-style buffers
+ for writev. Useful for systems like solaris that have very small max
+ number of iovecs that writev can take. Must be called only once before
+ the first article is created. */
+void artBitFiddleContents (bool val) ;
+
+ /* set the limit on the number of bytes in all articles (this is not a hard
+ limit). Can only be called one time before any articles are created. */
+void artSetMaxBytesInUse (unsigned int val) ;
+
+#endif /* article_h__ */
--- /dev/null
+/* $Id: buffer.c 7285 2005-06-07 06:38:24Z eagle $
+**
+** The Buffer class for innfeed.
+**
+** Written by James Brister <brister@vix.com>
+**
+** The implementation of the Buffer class. Buffers are reference counted
+** objects that abstract memory regions in a way similar to struct iovec.
+*/
+
+#include "innfeed.h"
+#include "config.h"
+#include "clibrary.h"
+#include <assert.h>
+
+#include "inn/messages.h"
+#include "libinn.h"
+
+#include "buffer.h"
+
+
+static Buffer gBufferList = NULL ;
+static Buffer bufferPool = NULL ;
+static unsigned int bufferCount = 0 ;
+static unsigned int bufferByteCount = 0 ;
+
+
+struct buffer_s
+{
+ int refCount ;
+ char *mem ;
+ size_t memSize ; /* the length of mem */
+ size_t dataSize ; /* amount that has actual data in it. */
+ bool deletable ;
+ void (*bufferDeletedCbk)(void *);
+ void *bufferDeletedCbkData;
+ struct buffer_s *next ;
+ struct buffer_s *prev ;
+};
+
+#define BUFFER_POOL_SIZE ((4096 - 2 * (sizeof (void *))) / (sizeof (struct buffer_s)))
+
+static void fillBufferPool (void)
+{
+ unsigned int i ;
+
+ bufferPool = xmalloc (sizeof(struct buffer_s) * BUFFER_POOL_SIZE) ;
+
+ for (i = 0; i < BUFFER_POOL_SIZE - 1; i++)
+ bufferPool[i] . next = &(bufferPool [i + 1]) ;
+ bufferPool [BUFFER_POOL_SIZE-1] . next = NULL ;
+}
+
+
+Buffer newBuffer (size_t size)
+{
+ Buffer nb ;
+
+ if (bufferPool == NULL)
+ fillBufferPool() ;
+
+ nb = bufferPool;
+ ASSERT (nb != NULL) ;
+ bufferPool = bufferPool->next ;
+
+ nb->refCount = 1 ;
+
+ nb->mem = xmalloc (size + 1) ;
+
+ nb->mem [size] = '\0' ;
+ nb->memSize = size ;
+ nb->dataSize = 0 ;
+ nb->deletable = true ;
+ nb->bufferDeletedCbk = NULL;
+ nb->bufferDeletedCbkData = NULL;
+
+ bufferByteCount += size + 1 ;
+ bufferCount++ ;
+
+ nb->next = gBufferList ;
+ nb->prev = NULL;
+ if (gBufferList != NULL)
+ gBufferList->prev = nb ;
+ gBufferList = nb ;
+
+#if 0
+ d_printf (1,"Creating a DELETABLE buffer %p\n",nb) ;
+#endif
+
+ return nb ;
+}
+
+
+Buffer newBufferByCharP (const char *ptr, size_t size, size_t dataSize)
+{
+ Buffer nb ;
+
+ if (bufferPool == NULL)
+ fillBufferPool() ;
+
+ nb = bufferPool;
+ ASSERT (nb != NULL) ;
+ bufferPool = bufferPool->next ;
+
+ nb->refCount = 1 ;
+ nb->mem = (char *) ptr ; /* cast away const */
+ nb->memSize = size ;
+ nb->dataSize = dataSize ;
+ nb->deletable = false ;
+ nb->bufferDeletedCbk = NULL;
+ nb->bufferDeletedCbkData = NULL;
+
+ nb->next = gBufferList ;
+ nb->prev = NULL;
+ if (gBufferList != NULL)
+ gBufferList->prev = nb ;
+ gBufferList = nb ;
+
+ bufferCount++ ;
+#if 0
+ d_printf (1,"Creating a NON-DELETABLE buffer %p\n",nb) ;
+#endif
+
+ return nb ;
+}
+
+
+void delBuffer (Buffer buff)
+{
+ if (buff != NULL && --(buff->refCount) == 0)
+ {
+#if 0
+ d_printf (1,"Freeing a %s buffer (%p)\n",
+ (buff->deletable ? "DELETABLE" : "NON-DELETABLE"), buff) ;
+#endif
+
+ bufferCount-- ;
+ if (buff->deletable)
+ {
+ bufferByteCount -= (buff->memSize + 1) ;
+ free (buff->mem) ;
+ buff->mem = NULL ;
+ }
+
+ if (buff->bufferDeletedCbk) {
+ (buff->bufferDeletedCbk)(buff->bufferDeletedCbkData);
+ buff->bufferDeletedCbk = NULL;
+ buff->bufferDeletedCbkData = NULL;
+ }
+
+ if (buff->next != NULL)
+ buff->next->prev = buff->prev ;
+ if (buff->prev != NULL)
+ buff->prev->next = buff->next ;
+ else
+ {
+ ASSERT(gBufferList == buff) ;
+ gBufferList = buff->next ;
+ }
+
+ buff->next = bufferPool ;
+ bufferPool = buff ;
+ }
+}
+
+Buffer bufferTakeRef (Buffer buff)
+{
+ ASSERT (buff != NULL) ;
+
+ if (buff != NULL)
+ buff->refCount++ ;
+
+ return buff ;
+}
+
+
+void gPrintBufferInfo (FILE *fp, unsigned int indentAmt)
+{
+ Buffer b ;
+ char indent [INDENT_BUFFER_SIZE] ;
+ unsigned int i ;
+
+ for (i = 0 ; i < MIN(INDENT_BUFFER_SIZE - 1,indentAmt) ; i++)
+ indent [i] = ' ' ;
+ indent [i] = '\0' ;
+
+ fprintf (fp,"%sGlobal Buffer List : (count %d) {\n",indent,bufferCount) ;
+
+ for (b = gBufferList ; b != NULL ; b = b->next)
+ printBufferInfo (b,fp,indentAmt + INDENT_INCR) ;
+
+ fprintf (fp,"%s}\n",indent) ;
+}
+
+void printBufferInfo (Buffer buffer, FILE *fp, unsigned int indentAmt)
+{
+ char indent [INDENT_BUFFER_SIZE] ;
+ char bufferStart [256] ;
+ unsigned int i ;
+
+ for (i = 0 ; i < MIN(INDENT_BUFFER_SIZE - 1,indentAmt) ; i++)
+ indent [i] = ' ' ;
+ indent [i] = '\0' ;
+
+ fprintf (fp,"%sBuffer : %p {\n",indent,(void *) buffer) ;
+
+ if (buffer == NULL)
+ {
+ fprintf (fp,"%s}\n",indent) ;
+ return ;
+ }
+
+ i = MIN(sizeof (bufferStart) - 1,buffer->dataSize) ;
+ memcpy (bufferStart,buffer->mem,i) ;
+ bufferStart[i] = '\0';
+
+ fprintf (fp,"%s refcount : %d\n",indent,buffer->refCount) ;
+ fprintf (fp,"%s data-size : %ld\n",indent,(long) buffer->dataSize) ;
+ fprintf (fp,"%s mem-size : %ld\n",indent,(long) buffer->memSize) ;
+ fprintf (fp,"%s base : %p\n", indent,(void *) buffer->mem) ;
+ fprintf (fp,"%s deletable : %s\n",indent,boolToString(buffer->deletable));
+ fprintf (fp,"%s buffer [0:%ld] : \"%s\"\n",
+ indent, (long) i, bufferStart) ;
+ fprintf (fp,"%s}\n",indent) ;
+}
+
+
+void *bufferBase (Buffer buff)
+{
+ return buff->mem ;
+}
+
+size_t bufferSize (Buffer buff)
+{
+ return buff->memSize ;
+}
+
+size_t bufferDataSize (Buffer buff)
+{
+ return buff->dataSize ;
+}
+
+void bufferIncrDataSize (Buffer buff, size_t size)
+{
+ if (buff->dataSize + size > buff->memSize)
+ die ("Trying to make a buffer data size bigger than its memory alloc");
+ else
+ buff->dataSize += size ;
+}
+
+void bufferDecrDataSize (Buffer buff, size_t size)
+{
+ ASSERT (size > buff->dataSize) ;
+
+ buff->dataSize -= size ;
+}
+
+void bufferSetDataSize (Buffer buff, size_t size)
+{
+ buff->dataSize = size ;
+
+ ASSERT (buff->dataSize <= buff->memSize) ;
+}
+
+void bufferSetDeletedCbk (Buffer buff, void (*cbk)(void *), void *data)
+{
+ ASSERT(buff->bufferDeletedCbk == NULL &&
+ buff->bufferDeletedCbk != cbk);
+ ASSERT(buff->bufferDeletedCbkData == NULL &&
+ buff->bufferDeletedCbkData != data);
+ buff->bufferDeletedCbk = cbk;
+ buff->bufferDeletedCbkData = data;
+}
+
+void freeBufferArray (Buffer *buffs)
+{
+ Buffer *b = buffs ;
+
+ while (b && *b)
+ {
+ delBuffer (*b) ;
+ b++ ;
+ }
+
+ if (buffs)
+ free (buffs) ;
+}
+
+
+ /* Allocate an array and put all the arguments (the last of which must be
+ NULL) into it. The terminating NULL is put in the returned array. */
+Buffer *makeBufferArray (Buffer buff, ...)
+{
+ va_list ap ;
+ size_t cLen = 10, idx = 0 ;
+ Buffer *ptr, p ;
+
+ ptr = xcalloc (cLen, sizeof(Buffer)) ;
+
+ ptr [idx++] = buff ;
+
+ va_start (ap, buff) ;
+ do
+ {
+ p = va_arg (ap, Buffer) ;
+ if (idx == cLen)
+ {
+ cLen += 10 ;
+ ptr = xrealloc (ptr, sizeof(Buffer) * cLen) ;
+ }
+ ptr [idx++] = p ;
+ }
+ while (p != NULL) ;
+ va_end (ap) ;
+
+ return ptr ;
+}
+
+
+bool isDeletable (Buffer buff)
+{
+ return buff->deletable ;
+}
+
+
+
+ /* Dups the array including taking out references on the Buffers inside */
+Buffer *dupBufferArray (Buffer *array)
+{
+ Buffer *newArr ;
+ int count = 0 ;
+
+ while (array && array [count] != NULL)
+ count++ ;
+
+ newArr = xmalloc (sizeof(Buffer) * (count + 1)) ;
+
+ for (count = 0 ; array [count] != NULL ; count++)
+ newArr [count] = bufferTakeRef (array [count]) ;
+
+ newArr [count] = NULL ;
+
+ return newArr ;
+}
+
+
+unsigned int bufferArrayLen (Buffer *array)
+{
+ unsigned int count = 0 ;
+
+ if (array != NULL)
+ while (*array != NULL)
+ {
+ count++ ;
+ array++ ;
+ }
+
+ return count ;
+}
+
+
+bool copyBuffer (Buffer dest, Buffer src)
+{
+ char *baseDest = bufferBase (dest) ;
+ char *baseSrc = bufferBase (src) ;
+ unsigned int amt = bufferDataSize (src) ;
+
+ if (amt > bufferSize (dest))
+ return false ;
+
+ memcpy (baseDest, baseSrc, amt) ;
+
+ bufferSetDataSize (dest,amt) ;
+
+ return true ;
+}
+
+
+unsigned int bufferRefCount (Buffer buf)
+{
+ return buf->refCount ;
+}
+
+
+void bufferAddNullByte (Buffer buff)
+{
+ char *p = bufferBase (buff) ;
+
+ p [buff->dataSize] = '\0' ;
+}
+
+
+ /* append the src buffer to the dest buffer growing the dest as needed.
+ Can only be done to deletable buffers. */
+bool concatBuffer (Buffer dest, Buffer src)
+{
+ ASSERT (dest->deletable) ;
+
+ if ( !dest->deletable )
+ return false ; /* yeah, i know this is taken care of above */
+
+ if ((dest->dataSize + src->dataSize) > dest->memSize)
+ {
+ char *newMem = xcalloc (dest->dataSize + src->dataSize + 1, 1) ;
+
+ bufferByteCount += ((dest->dataSize + src->dataSize) - dest->memSize) ;
+
+ memcpy (newMem, dest->mem, dest->dataSize) ;
+
+ ASSERT (dest->mem != NULL) ;
+ free (dest->mem) ;
+
+ dest->mem = newMem ;
+ dest->memSize = dest->dataSize + src->dataSize ; /* yep. 1 less */
+ }
+
+ memcpy (&dest->mem[dest->dataSize], src->mem, dest->dataSize) ;
+
+ dest->dataSize += src->dataSize ;
+
+ return true ;
+}
+
+
+ /* realloc the buffer's memory to increase the size by AMT */
+bool expandBuffer (Buffer buff, size_t amt)
+{
+ d_printf (2,"Expanding buffer....\n") ;
+
+ if (!buff->deletable)
+ return false ;
+
+ bufferByteCount += amt ;
+ buff->memSize += amt ;
+
+ buff->mem = xrealloc (buff->mem, buff->memSize + 1) ;
+
+ return true ;
+}
+
+
+ /* Take a buffer and shift the contents around to add the necessary CR
+ before every line feed and a '.' before every '.' at the start of a
+ line. */
+bool nntpPrepareBuffer (Buffer buffer)
+{
+ int msize, newDsize, dsize, extra ;
+ char *base, p, *src, *dst ;
+ bool needfinal = false ;
+
+ ASSERT (buffer != NULL) ;
+
+ dsize = buffer->dataSize ;
+ msize = buffer->memSize - 1 ;
+ base = buffer->mem ;
+
+ extra = 3 ;
+ p = '\0' ;
+ for (src = base + dsize - 1 ; src > base ; )
+ {
+ if (*src == '\n')
+ {
+ extra++ ;
+ if (p == '.')
+ extra++ ;
+ }
+ p = *src-- ;
+ }
+ if (*src == '\n')
+ {
+ extra++ ;
+ if (p == '.')
+ extra++ ;
+ }
+
+ if (dsize > 0 && base [dsize - 1] != '\n')
+ {
+ needfinal = true ;
+ extra += 2 ;
+ }
+
+ newDsize = dsize + extra ;
+
+ if (msize - dsize < extra)
+ {
+ d_printf (2,"Expanding buffer in nntpPrepareBuffer (from %d to %d)\n",
+ msize, msize + (extra - (msize - dsize))) ;
+
+ if ( !expandBuffer (buffer, extra - (msize - dsize)) )
+ {
+ d_printf (1,"Expand failed...\n") ;
+ return false ;
+ }
+
+ ASSERT (dsize == (int) buffer->dataSize) ;
+
+ base = buffer->mem ;
+ }
+
+ base [newDsize] = '\0' ;
+ base [newDsize - 1] = '\n' ;
+ base [newDsize - 2] = '\r' ;
+ base [newDsize - 3] = '.' ;
+ newDsize -= 3 ;
+ extra -= 3 ;
+
+ if (needfinal)
+ {
+ base [newDsize - 1] = '\n' ;
+ base [newDsize - 2] = '\r' ;
+ newDsize -= 2 ;
+ extra -= 2 ;
+ }
+
+ if (extra)
+ {
+ p = '\0';
+ src = base + dsize - 1 ;
+ dst = base + newDsize - 1 ;
+ while (1)
+ {
+ if (*src == '\n')
+ {
+ if (p == '.')
+ {
+ *dst-- = '.' ;
+ extra-- ;
+ }
+ *dst-- = '\n' ;
+ *dst = '\r' ;
+ if (--extra <= 0)
+ break ;
+ p = '\0' ;
+ dst-- ;
+ src-- ;
+ }
+ else
+ p = *dst-- = *src-- ;
+ }
+ ASSERT(dst >= base && src >= base) ;
+ }
+
+ newDsize += 3;
+ if (needfinal)
+ newDsize += 2 ;
+
+ bufferSetDataSize (buffer,newDsize) ;
+
+ return true ;
+}
--- /dev/null
+/* $Id: buffer.h 7285 2005-06-07 06:38:24Z eagle $
+**
+** The public interface to the Buffer class.
+**
+** Written by James Brister <brister@vix.com>
+**
+** The Buffer class encapsulates a region of memory. It provides reference
+** counting so that redundant memory allocation and copying is minimized. A
+** good example of this is the need to write the same data (e.g. an article
+** body) out more than one socket. The data is stored in a Buffer and the
+** Buffer is given to each EndPoint that is to write it. With the Refcount
+** appropriately set the EndPoints can release their references at will and
+** the Buffer will be cleaned up when necessary.
+*/
+
+#if ! defined ( buffer_h__ )
+#define buffer_h__
+
+
+#include <sys/types.h>
+#include <stdio.h>
+
+#include "misc.h"
+
+
+/*
+ * Create a new Buffer object and initialize it.
+ */
+Buffer newBuffer (size_t size) ;
+
+/* Create a new Buffer object around the preallocted PTR, which is SIZE
+ bytes long. The data size of the Buffer is set to DATASIZE. When then
+ buffer is released it will not delete the ptr (this is useful to have
+ Buffers around contant strings, or Buffers around other Buffers) */
+Buffer newBufferByCharP (const char *ptr, size_t size, size_t dataSize) ;
+
+/*
+ * give up interest in the Buffer. Decrement refcount and delete if no
+ * more referants
+ */
+void delBuffer (Buffer buff) ;
+
+ /* print some debugging information about the buffer. */
+void printBufferInfo (Buffer buffer, FILE *fp, unsigned int indentAmt) ;
+
+ /* print debugging information about all outstanding buffers. */
+void gPrintBufferInfo (FILE *fp, unsigned int indentAmt) ;
+
+/* increment reference counts so that the buffer object can be */
+/* handed off to another function without it being deleted when that */
+/* function calls bufferDelete(). Returns buff, so the call can be */
+/* used in function arg list. */
+Buffer bufferTakeRef (Buffer buff) ;
+
+/* returns the address of the base of the memory owned by the Buffer */
+void *bufferBase (Buffer buff) ;
+
+/* returns the size of the memory buffer has available. This always returns
+ 1 less than the real size so that there's space left for a null byte
+ when needed. The extra byte is accounted for when the newBuffer function
+ is called. */
+size_t bufferSize (Buffer) ;
+
+/* return the amount of data actually in the buffer */
+size_t bufferDataSize (Buffer buff) ;
+
+/* increment the size of the buffer's data region */
+void bufferIncrDataSize (Buffer buff, size_t size) ;
+
+/* decrement the size of the buffer's data region */
+void bufferDecrDataSize (Buffer buff, size_t size) ;
+
+/* set the size of the data actually in the buffer */
+void bufferSetDataSize (Buffer buff, size_t size) ;
+
+/* callback to be called when buffer gets deleted */
+void bufferSetDeletedCbk (Buffer buff, void (*cbk)(void *), void *data);
+
+/* walk down the BUFFS releasing each buffer and then freeing BUFFS itself */
+void freeBufferArray (Buffer *buffs) ;
+
+/* All arguments are non-NULL Buffers, except for the last which must be
+ NULL. Creates a free'able array and puts all the buffers into it (does
+ not call bufferTakeRef on them). */
+Buffer *makeBufferArray (Buffer buff, ...) ;
+
+/* returns true if the buffer was created via
+ newBuffer (vs. newBufferByCharP) */
+bool isDeletable (Buffer buff) ;
+
+/* Dups the array including taking out references on the Buffers
+ inside. Return value must be freed (or given to freeBufferArray) */
+Buffer *dupBufferArray (Buffer *array) ;
+
+/* return the number of non-NULL elements in the array. */
+unsigned int bufferArrayLen (Buffer *array) ;
+
+/* copies the contents of the SRC buffer into the DEST buffer and sets the
+ data size appropriately. Returns false if src is bigger than dest. */
+bool copyBuffer (Buffer dest, Buffer src) ;
+
+/* return the ref count on the buffer */
+unsigned int bufferRefCount (Buffer buff) ;
+
+/* insert a null byte at the end of the data region */
+void bufferAddNullByte (Buffer buff) ;
+
+/* append the data of src to the data of dest, if dest is big enough */
+bool concatBuffer (Buffer dest, Buffer src) ;
+
+/* expand the buffer's memory by the given AMT */
+bool expandBuffer (Buffer buff, size_t amt) ;
+
+/* Expand the buffer (if necessary) and insert carriage returns before very
+ line feed. Adjusts the data size. The base address may change
+ afterwards. */
+bool nntpPrepareBuffer (Buffer buffer) ;
+
+#endif /* buffer_h__ */
--- /dev/null
+/* A lexical scanner generated by flex */
+
+/* Scanner skeleton version:
+ * $Header$
+ */
+
+#define FLEX_SCANNER
+#define YY_FLEX_MAJOR_VERSION 2
+#define YY_FLEX_MINOR_VERSION 5
+
+#include <stdio.h>
+#include <errno.h>
+
+/* cfront 1.2 defines "c_plusplus" instead of "__cplusplus" */
+#ifdef c_plusplus
+#ifndef __cplusplus
+#define __cplusplus
+#endif
+#endif
+
+
+#ifdef __cplusplus
+
+#include <stdlib.h>
+#ifndef _WIN32
+#include <unistd.h>
+#endif
+
+/* Use prototypes in function declarations. */
+#define YY_USE_PROTOS
+
+/* The "const" storage-class-modifier is valid. */
+#define YY_USE_CONST
+
+#else /* ! __cplusplus */
+
+#if __STDC__
+
+#define YY_USE_PROTOS
+#define YY_USE_CONST
+
+#endif /* __STDC__ */
+#endif /* ! __cplusplus */
+
+#ifdef __TURBOC__
+ #pragma warn -rch
+ #pragma warn -use
+#include <io.h>
+#include <stdlib.h>
+#define YY_USE_CONST
+#define YY_USE_PROTOS
+#endif
+
+#ifdef YY_USE_CONST
+#define yyconst const
+#else
+#define yyconst
+#endif
+
+
+#ifdef YY_USE_PROTOS
+#define YY_PROTO(proto) proto
+#else
+#define YY_PROTO(proto) ()
+#endif
+
+/* Returned upon end-of-file. */
+#define YY_NULL 0
+
+/* Promotes a possibly negative, possibly signed char to an unsigned
+ * integer for use as an array index. If the signed char is negative,
+ * we want to instead treat it as an 8-bit unsigned char, hence the
+ * double cast.
+ */
+#define YY_SC_TO_UI(c) ((unsigned int) (unsigned char) c)
+
+/* Enter a start condition. This macro really ought to take a parameter,
+ * but we do it the disgusting crufty way forced on us by the ()-less
+ * definition of BEGIN.
+ */
+#define BEGIN yy_start = 1 + 2 *
+
+/* Translate the current start state into a value that can be later handed
+ * to BEGIN to return to the state. The YYSTATE alias is for lex
+ * compatibility.
+ */
+#define YY_START ((yy_start - 1) / 2)
+#define YYSTATE YY_START
+
+/* Action number for EOF rule of a given start state. */
+#define YY_STATE_EOF(state) (YY_END_OF_BUFFER + state + 1)
+
+/* Special action meaning "start processing a new file". */
+#define YY_NEW_FILE yyrestart( yyin )
+
+#define YY_END_OF_BUFFER_CHAR 0
+
+/* Size of default input buffer. */
+#define YY_BUF_SIZE 16384
+
+typedef struct yy_buffer_state *YY_BUFFER_STATE;
+
+extern int yyleng;
+extern FILE *yyin, *yyout;
+
+#define EOB_ACT_CONTINUE_SCAN 0
+#define EOB_ACT_END_OF_FILE 1
+#define EOB_ACT_LAST_MATCH 2
+
+/* The funky do-while in the following #define is used to turn the definition
+ * int a single C statement (which needs a semi-colon terminator). This
+ * avoids problems with code like:
+ *
+ * if ( condition_holds )
+ * yyless( 5 );
+ * else
+ * do_something_else();
+ *
+ * Prior to using the do-while the compiler would get upset at the
+ * "else" because it interpreted the "if" statement as being all
+ * done when it reached the ';' after the yyless() call.
+ */
+
+/* Return all but the first 'n' matched characters back to the input stream. */
+
+#define yyless(n) \
+ do \
+ { \
+ /* Undo effects of setting up yytext. */ \
+ *yy_cp = yy_hold_char; \
+ YY_RESTORE_YY_MORE_OFFSET \
+ yy_c_buf_p = yy_cp = yy_bp + n - YY_MORE_ADJ; \
+ YY_DO_BEFORE_ACTION; /* set up yytext again */ \
+ } \
+ while ( 0 )
+
+#define unput(c) yyunput( c, yytext_ptr )
+
+/* The following is because we cannot portably get our hands on size_t
+ * (without autoconf's help, which isn't available because we want
+ * flex-generated scanners to compile on their own).
+ */
+typedef unsigned int yy_size_t;
+
+
+struct yy_buffer_state
+ {
+ FILE *yy_input_file;
+
+ char *yy_ch_buf; /* input buffer */
+ char *yy_buf_pos; /* current position in input buffer */
+
+ /* Size of input buffer in bytes, not including room for EOB
+ * characters.
+ */
+ yy_size_t yy_buf_size;
+
+ /* Number of characters read into yy_ch_buf, not including EOB
+ * characters.
+ */
+ int yy_n_chars;
+
+ /* Whether we "own" the buffer - i.e., we know we created it,
+ * and can realloc() it to grow it, and should free() it to
+ * delete it.
+ */
+ int yy_is_our_buffer;
+
+ /* Whether this is an "interactive" input source; if so, and
+ * if we're using stdio for input, then we want to use getc()
+ * instead of fread(), to make sure we stop fetching input after
+ * each newline.
+ */
+ int yy_is_interactive;
+
+ /* Whether we're considered to be at the beginning of a line.
+ * If so, '^' rules will be active on the next match, otherwise
+ * not.
+ */
+ int yy_at_bol;
+
+ /* Whether to try to fill the input buffer when we reach the
+ * end of it.
+ */
+ int yy_fill_buffer;
+
+ int yy_buffer_status;
+#define YY_BUFFER_NEW 0
+#define YY_BUFFER_NORMAL 1
+ /* When an EOF's been seen but there's still some text to process
+ * then we mark the buffer as YY_EOF_PENDING, to indicate that we
+ * shouldn't try reading from the input source any more. We might
+ * still have a bunch of tokens to match, though, because of
+ * possible backing-up.
+ *
+ * When we actually see the EOF, we change the status to "new"
+ * (via yyrestart()), so that the user can continue scanning by
+ * just pointing yyin at a new input file.
+ */
+#define YY_BUFFER_EOF_PENDING 2
+ };
+
+static YY_BUFFER_STATE yy_current_buffer = 0;
+
+/* We provide macros for accessing buffer states in case in the
+ * future we want to put the buffer states in a more general
+ * "scanner state".
+ */
+#define YY_CURRENT_BUFFER yy_current_buffer
+
+
+/* yy_hold_char holds the character lost when yytext is formed. */
+static char yy_hold_char;
+
+static int yy_n_chars; /* number of characters read into yy_ch_buf */
+
+
+int yyleng;
+
+/* Points to current character in buffer. */
+static char *yy_c_buf_p = (char *) 0;
+static int yy_init = 1; /* whether we need to initialize */
+static int yy_start = 0; /* start state number */
+
+/* Flag which is used to allow yywrap()'s to do buffer switches
+ * instead of setting up a fresh yyin. A bit of a hack ...
+ */
+static int yy_did_buffer_switch_on_eof;
+
+void yyrestart YY_PROTO(( FILE *input_file ));
+
+void yy_switch_to_buffer YY_PROTO(( YY_BUFFER_STATE new_buffer ));
+void yy_load_buffer_state YY_PROTO(( void ));
+YY_BUFFER_STATE yy_create_buffer YY_PROTO(( FILE *file, int size ));
+void yy_delete_buffer YY_PROTO(( YY_BUFFER_STATE b ));
+void yy_init_buffer YY_PROTO(( YY_BUFFER_STATE b, FILE *file ));
+void yy_flush_buffer YY_PROTO(( YY_BUFFER_STATE b ));
+#define YY_FLUSH_BUFFER yy_flush_buffer( yy_current_buffer )
+
+YY_BUFFER_STATE yy_scan_buffer YY_PROTO(( char *base, yy_size_t size ));
+YY_BUFFER_STATE yy_scan_string YY_PROTO(( yyconst char *yy_str ));
+YY_BUFFER_STATE yy_scan_bytes YY_PROTO(( yyconst char *bytes, int len ));
+
+static void *yy_flex_alloc YY_PROTO(( yy_size_t ));
+static void *yy_flex_realloc YY_PROTO(( void *, yy_size_t ));
+static void yy_flex_free YY_PROTO(( void * ));
+
+#define yy_new_buffer yy_create_buffer
+
+#define yy_set_interactive(is_interactive) \
+ { \
+ if ( ! yy_current_buffer ) \
+ yy_current_buffer = yy_create_buffer( yyin, YY_BUF_SIZE ); \
+ yy_current_buffer->yy_is_interactive = is_interactive; \
+ }
+
+#define yy_set_bol(at_bol) \
+ { \
+ if ( ! yy_current_buffer ) \
+ yy_current_buffer = yy_create_buffer( yyin, YY_BUF_SIZE ); \
+ yy_current_buffer->yy_at_bol = at_bol; \
+ }
+
+#define YY_AT_BOL() (yy_current_buffer->yy_at_bol)
+
+typedef unsigned char YY_CHAR;
+FILE *yyin = (FILE *) 0, *yyout = (FILE *) 0;
+typedef int yy_state_type;
+extern char *yytext;
+#define yytext_ptr yytext
+
+static yy_state_type yy_get_previous_state YY_PROTO(( void ));
+static yy_state_type yy_try_NUL_trans YY_PROTO(( yy_state_type current_state ));
+static int yy_get_next_buffer YY_PROTO(( void ));
+static void yy_fatal_error YY_PROTO(( yyconst char msg[] ));
+
+/* Done after the current pattern has been matched and before the
+ * corresponding action - sets up yytext.
+ */
+#define YY_DO_BEFORE_ACTION \
+ yytext_ptr = yy_bp; \
+ yyleng = (int) (yy_cp - yy_bp); \
+ yy_hold_char = *yy_cp; \
+ *yy_cp = '\0'; \
+ yy_c_buf_p = yy_cp;
+
+#define YY_NUM_RULES 19
+#define YY_END_OF_BUFFER 20
+static yyconst short int yy_accept[55] =
+ { 0,
+ 0, 0, 7, 7, 20, 18, 11, 1, 15, 10,
+ 19, 16, 2, 18, 18, 3, 4, 18, 8, 7,
+ 19, 18, 11, 15, 10, 0, 0, 17, 16, 18,
+ 18, 18, 8, 7, 13, 0, 0, 17, 18, 18,
+ 18, 0, 12, 18, 5, 18, 0, 9, 18, 14,
+ 18, 18, 6, 0
+ } ;
+
+static yyconst int yy_ec[256] =
+ { 0,
+ 1, 1, 1, 1, 1, 1, 1, 1, 2, 3,
+ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
+ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
+ 1, 2, 1, 4, 5, 6, 1, 1, 7, 1,
+ 1, 1, 1, 1, 8, 9, 1, 10, 10, 10,
+ 10, 10, 10, 10, 10, 10, 10, 11, 1, 1,
+ 1, 1, 1, 1, 1, 1, 12, 13, 14, 1,
+ 15, 1, 16, 1, 1, 17, 1, 18, 19, 20,
+ 1, 21, 1, 1, 22, 1, 1, 1, 1, 1,
+ 1, 23, 1, 1, 1, 1, 24, 24, 1, 1,
+
+ 25, 24, 15, 1, 1, 1, 1, 1, 1, 24,
+ 19, 20, 1, 26, 1, 24, 27, 24, 1, 1,
+ 1, 1, 28, 1, 29, 1, 1, 1, 1, 1,
+ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
+ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
+ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
+ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
+ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
+ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
+ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
+
+ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
+ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
+ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
+ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
+ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
+ 1, 1, 1, 1, 1
+ } ;
+
+static yyconst int yy_meta[30] =
+ { 0,
+ 1, 2, 3, 4, 5, 1, 6, 1, 1, 7,
+ 5, 1, 1, 1, 1, 1, 1, 1, 1, 1,
+ 1, 1, 7, 7, 1, 7, 1, 1, 1
+ } ;
+
+static yyconst short int yy_base[62] =
+ { 0,
+ 0, 100, 28, 30, 105, 0, 102, 107, 0, 0,
+ 80, 25, 107, 15, 23, 0, 0, 86, 0, 99,
+ 107, 0, 98, 0, 0, 92, 32, 88, 34, 78,
+ 24, 78, 0, 87, 107, 78, 75, 65, 18, 25,
+ 57, 54, 107, 43, 0, 45, 54, 0, 38, 107,
+ 37, 33, 0, 107, 51, 58, 65, 72, 79, 86,
+ 88
+ } ;
+
+static yyconst short int yy_def[62] =
+ { 0,
+ 54, 1, 55, 55, 54, 56, 54, 54, 57, 58,
+ 59, 56, 54, 56, 56, 56, 56, 56, 60, 54,
+ 54, 56, 54, 57, 58, 54, 61, 56, 56, 56,
+ 56, 56, 60, 54, 54, 54, 54, 56, 56, 56,
+ 56, 54, 54, 56, 56, 56, 54, 56, 56, 54,
+ 56, 56, 56, 0, 54, 54, 54, 54, 54, 54,
+ 54
+ } ;
+
+static yyconst short int yy_nxt[137] =
+ { 0,
+ 6, 7, 8, 9, 10, 6, 11, 12, 6, 12,
+ 13, 6, 6, 6, 14, 6, 6, 6, 6, 15,
+ 6, 6, 6, 6, 6, 6, 6, 16, 17, 20,
+ 21, 20, 21, 28, 29, 30, 31, 40, 35, 44,
+ 30, 36, 28, 29, 44, 45, 53, 31, 40, 52,
+ 45, 19, 19, 19, 19, 19, 19, 19, 22, 51,
+ 50, 49, 48, 47, 22, 24, 24, 24, 46, 24,
+ 24, 24, 25, 25, 38, 25, 25, 25, 25, 26,
+ 26, 43, 26, 26, 26, 26, 33, 42, 34, 33,
+ 33, 33, 33, 37, 37, 41, 39, 38, 35, 23,
+
+ 34, 32, 27, 23, 54, 18, 5, 54, 54, 54,
+ 54, 54, 54, 54, 54, 54, 54, 54, 54, 54,
+ 54, 54, 54, 54, 54, 54, 54, 54, 54, 54,
+ 54, 54, 54, 54, 54, 54
+ } ;
+
+static yyconst short int yy_chk[137] =
+ { 0,
+ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
+ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
+ 1, 1, 1, 1, 1, 1, 1, 1, 1, 3,
+ 3, 4, 4, 12, 12, 14, 15, 31, 27, 39,
+ 14, 27, 29, 29, 39, 40, 52, 15, 31, 51,
+ 40, 55, 55, 55, 55, 55, 55, 55, 56, 49,
+ 47, 46, 44, 42, 56, 57, 57, 57, 41, 57,
+ 57, 57, 58, 58, 38, 58, 58, 58, 58, 59,
+ 59, 37, 59, 59, 59, 59, 60, 36, 34, 60,
+ 60, 60, 60, 61, 61, 32, 30, 28, 26, 23,
+
+ 20, 18, 11, 7, 5, 2, 54, 54, 54, 54,
+ 54, 54, 54, 54, 54, 54, 54, 54, 54, 54,
+ 54, 54, 54, 54, 54, 54, 54, 54, 54, 54,
+ 54, 54, 54, 54, 54, 54
+ } ;
+
+static yy_state_type yy_last_accepting_state;
+static char *yy_last_accepting_cpos;
+
+/* The intent behind this definition is that it'll catch
+ * any uses of REJECT which flex missed.
+ */
+#define REJECT reject_used_but_not_detected
+#define yymore() yymore_used_but_not_detected
+#define YY_MORE_ADJ 0
+#define YY_RESTORE_YY_MORE_OFFSET
+char *yytext;
+#line 1 "configfile.l"
+#define INITIAL 0
+#line 2 "configfile.l"
+/* $Id: config_l.c 6145 2003-01-19 19:42:22Z rra $
+**
+** A flex input file for the innfeed config file.
+**
+** Written by James Brister <brister@vix.com>
+*/
+
+#include "innfeed.h"
+
+#include <errno.h>
+#include <stdlib.h>
+#include <string.h>
+#include <syslog.h>
+
+#include "libinn.h"
+
+#include "configfile.h"
+#include "config_y.h"
+#include "misc.h"
+
+#if ! defined (FLEX_SCANNER)
+#error "You must use FLEX to process the lex input file."
+#endif
+
+#if defined (FLEX_DEBUG)
+#define YY_USER_INIT yy_flex_debug = (getenv ("YYDEBUG") == NULL ? 0 : 1)
+#endif
+
+/* We never use this function but flex always defines it, so silence the
+ warnings about it. */
+static void yyunput(int, char *) UNUSED;
+
+char *strPtr = 0 ;
+int strPtrLen = 0 ;
+int strIdx = 0 ;
+int sawBsl ;
+int lineCount = 0 ;
+int current ;
+
+static void strAppend (int ch);
+static void strAppend (int ch)
+{
+ if (strIdx == strPtrLen)
+ {
+ if (strPtr == 0)
+ strPtr = xmalloc (strPtrLen = 50) ;
+ else
+ strPtr = xrealloc (strPtr,strPtrLen += 10) ;
+ }
+ strPtr [strIdx++] = ch ;
+}
+
+#define MAX_INCLUDE_DEPTH 11
+struct includeFile {
+ YY_BUFFER_STATE state;
+ char *name ;
+} include_stack[MAX_INCLUDE_DEPTH];
+int include_stack_ptr = 0;
+
+#define incl 1
+
+#line 474 "lex.yy.c"
+
+/* Macros after this point can all be overridden by user definitions in
+ * section 1.
+ */
+
+#ifndef YY_SKIP_YYWRAP
+#ifdef __cplusplus
+extern "C" int yywrap YY_PROTO(( void ));
+#else
+extern int yywrap YY_PROTO(( void ));
+#endif
+#endif
+
+#ifndef YY_NO_UNPUT
+static void yyunput YY_PROTO(( int c, char *buf_ptr ));
+#endif
+
+#ifndef yytext_ptr
+static void yy_flex_strncpy YY_PROTO(( char *, yyconst char *, int ));
+#endif
+
+#ifdef YY_NEED_STRLEN
+static int yy_flex_strlen YY_PROTO(( yyconst char * ));
+#endif
+
+#ifndef YY_NO_INPUT
+#ifdef __cplusplus
+static int yyinput YY_PROTO(( void ));
+#else
+static int input YY_PROTO(( void ));
+#endif
+#endif
+
+#if YY_STACK_USED
+static int yy_start_stack_ptr = 0;
+static int yy_start_stack_depth = 0;
+static int *yy_start_stack = 0;
+#ifndef YY_NO_PUSH_STATE
+static void yy_push_state YY_PROTO(( int new_state ));
+#endif
+#ifndef YY_NO_POP_STATE
+static void yy_pop_state YY_PROTO(( void ));
+#endif
+#ifndef YY_NO_TOP_STATE
+static int yy_top_state YY_PROTO(( void ));
+#endif
+
+#else
+#define YY_NO_PUSH_STATE 1
+#define YY_NO_POP_STATE 1
+#define YY_NO_TOP_STATE 1
+#endif
+
+#ifdef YY_MALLOC_DECL
+YY_MALLOC_DECL
+#else
+#if __STDC__
+#ifndef __cplusplus
+#include <stdlib.h>
+#endif
+#else
+/* Just try to get by without declaring the routines. This will fail
+ * miserably on non-ANSI systems for which sizeof(size_t) != sizeof(int)
+ * or sizeof(void*) != sizeof(int).
+ */
+#endif
+#endif
+
+/* Amount of stuff to slurp up with each read. */
+#ifndef YY_READ_BUF_SIZE
+#define YY_READ_BUF_SIZE 8192
+#endif
+
+/* Copy whatever the last rule matched to the standard output. */
+
+#ifndef ECHO
+/* This used to be an fputs(), but since the string might contain NUL's,
+ * we now use fwrite().
+ */
+#define ECHO (void) fwrite( yytext, yyleng, 1, yyout )
+#endif
+
+/* Gets input and stuffs it into "buf". number of characters read, or YY_NULL,
+ * is returned in "result".
+ */
+#ifndef YY_INPUT
+#define YY_INPUT(buf,result,max_size) \
+ if ( yy_current_buffer->yy_is_interactive ) \
+ { \
+ int c = '*', n; \
+ for ( n = 0; n < max_size && \
+ (c = getc( yyin )) != EOF && c != '\n'; ++n ) \
+ buf[n] = (char) c; \
+ if ( c == '\n' ) \
+ buf[n++] = (char) c; \
+ if ( c == EOF && ferror( yyin ) ) \
+ YY_FATAL_ERROR( "input in flex scanner failed" ); \
+ result = n; \
+ } \
+ else \
+ { \
+ errno=0; \
+ while ( (result = fread(buf, 1, max_size, yyin))==0 && ferror(yyin)) \
+ { \
+ if( errno != EINTR) \
+ { \
+ YY_FATAL_ERROR( "input in flex scanner failed" ); \
+ break; \
+ } \
+ errno=0; \
+ clearerr(yyin); \
+ } \
+ }
+#endif
+
+/* No semi-colon after return; correct usage is to write "yyterminate();" -
+ * we don't want an extra ';' after the "return" because that will cause
+ * some compilers to complain about unreachable statements.
+ */
+#ifndef yyterminate
+#define yyterminate() return YY_NULL
+#endif
+
+/* Number of entries by which start-condition stack grows. */
+#ifndef YY_START_STACK_INCR
+#define YY_START_STACK_INCR 25
+#endif
+
+/* Report a fatal error. */
+#ifndef YY_FATAL_ERROR
+#define YY_FATAL_ERROR(msg) yy_fatal_error( msg )
+#endif
+
+/* Default declaration of generated scanner - a define so the user can
+ * easily add parameters.
+ */
+#ifndef YY_DECL
+#define YY_DECL int yylex YY_PROTO(( void ))
+#endif
+
+/* Code executed at the beginning of each rule, after yytext and yyleng
+ * have been set up.
+ */
+#ifndef YY_USER_ACTION
+#define YY_USER_ACTION
+#endif
+
+/* Code executed at the end of each rule. */
+#ifndef YY_BREAK
+#define YY_BREAK break;
+#endif
+
+#define YY_RULE_SETUP \
+ if ( yyleng > 0 ) \
+ yy_current_buffer->yy_at_bol = \
+ (yytext[yyleng - 1] == '\n'); \
+ YY_USER_ACTION
+
+YY_DECL
+ {
+ register yy_state_type yy_current_state;
+ register char *yy_cp, *yy_bp;
+ register int yy_act;
+
+#line 67 "configfile.l"
+
+
+#line 642 "lex.yy.c"
+
+ if ( yy_init )
+ {
+ yy_init = 0;
+
+#ifdef YY_USER_INIT
+ YY_USER_INIT;
+#endif
+
+ if ( ! yy_start )
+ yy_start = 1; /* first start state */
+
+ if ( ! yyin )
+ yyin = stdin;
+
+ if ( ! yyout )
+ yyout = stdout;
+
+ if ( ! yy_current_buffer )
+ yy_current_buffer =
+ yy_create_buffer( yyin, YY_BUF_SIZE );
+
+ yy_load_buffer_state();
+ }
+
+ while ( 1 ) /* loops until end-of-file is reached */
+ {
+ yy_cp = yy_c_buf_p;
+
+ /* Support of yytext. */
+ *yy_cp = yy_hold_char;
+
+ /* yy_bp points to the position in yy_ch_buf of the start of
+ * the current run.
+ */
+ yy_bp = yy_cp;
+
+ yy_current_state = yy_start;
+ yy_current_state += YY_AT_BOL();
+yy_match:
+ do
+ {
+ register YY_CHAR yy_c = yy_ec[YY_SC_TO_UI(*yy_cp)];
+ if ( yy_accept[yy_current_state] )
+ {
+ yy_last_accepting_state = yy_current_state;
+ yy_last_accepting_cpos = yy_cp;
+ }
+ while ( yy_chk[yy_base[yy_current_state] + yy_c] != yy_current_state )
+ {
+ yy_current_state = (int) yy_def[yy_current_state];
+ if ( yy_current_state >= 55 )
+ yy_c = yy_meta[(unsigned int) yy_c];
+ }
+ yy_current_state = yy_nxt[yy_base[yy_current_state] + (unsigned int) yy_c];
+ ++yy_cp;
+ }
+ while ( yy_base[yy_current_state] != 107 );
+
+yy_find_action:
+ yy_act = yy_accept[yy_current_state];
+ if ( yy_act == 0 )
+ { /* have to back up */
+ yy_cp = yy_last_accepting_cpos;
+ yy_current_state = yy_last_accepting_state;
+ yy_act = yy_accept[yy_current_state];
+ }
+
+ YY_DO_BEFORE_ACTION;
+
+
+do_action: /* This label is used only to access EOF actions. */
+
+
+ switch ( yy_act )
+ { /* beginning of action switch */
+ case 0: /* must back up */
+ /* undo the effects of YY_DO_BEFORE_ACTION */
+ *yy_cp = yy_hold_char;
+ yy_cp = yy_last_accepting_cpos;
+ yy_current_state = yy_last_accepting_state;
+ goto yy_find_action;
+
+case 1:
+YY_RULE_SETUP
+#line 69 "configfile.l"
+lineCount++ ;
+ YY_BREAK
+case 2:
+YY_RULE_SETUP
+#line 71 "configfile.l"
+{ return (COLON) ; }
+ YY_BREAK
+case 3:
+YY_RULE_SETUP
+#line 73 "configfile.l"
+{ return (LBRACE) ; }
+ YY_BREAK
+case 4:
+YY_RULE_SETUP
+#line 75 "configfile.l"
+{ return (RBRACE) ; }
+ YY_BREAK
+case 5:
+YY_RULE_SETUP
+#line 77 "configfile.l"
+{ return (PEER) ; }
+ YY_BREAK
+case 6:
+YY_RULE_SETUP
+#line 79 "configfile.l"
+BEGIN(incl);
+ YY_BREAK
+case 7:
+YY_RULE_SETUP
+#line 81 "configfile.l"
+/* eat the whitespace before include filename */
+ YY_BREAK
+case 8:
+YY_RULE_SETUP
+#line 83 "configfile.l"
+{
+ if (include_stack_ptr == MAX_INCLUDE_DEPTH - 1)
+ {
+ int i ;
+ fprintf( stderr, "Includes nested too deeply:\n" );
+ for (i = 1 ; i <= include_stack_ptr ; i++)
+ fprintf (stderr,"\t%s\n",include_stack[i].name) ;
+
+ syslog (LOG_ERR, "includes nested to deeply") ;
+ exit( 1 );
+ }
+
+ if ((yyin = fopen(yytext,"r")) == NULL)
+ {
+ syslog (LOG_CRIT,"include file fopen failed: %s %s",
+ yytext,strerror(errno));
+ fprintf (stderr,"include file fopen failed: %s %s\n",
+ yytext,strerror(errno));
+ exit (1) ;
+ }
+ else
+ {
+ d_printf (1,"Including (%d) from %s\n",
+ include_stack_ptr + 1,yytext) ;
+ include_stack[include_stack_ptr].state = YY_CURRENT_BUFFER;
+ include_stack[++include_stack_ptr].name = xstrdup (yytext) ;
+ yy_switch_to_buffer(yy_create_buffer(yyin, YY_BUF_SIZE));
+ }
+
+ BEGIN(INITIAL);
+}
+ YY_BREAK
+case YY_STATE_EOF(INITIAL):
+case YY_STATE_EOF(incl):
+#line 115 "configfile.l"
+{
+ if ( include_stack_ptr <= 0 )
+ yyterminate();
+ else
+ {
+ free (include_stack[include_stack_ptr].name) ;
+ yy_delete_buffer(YY_CURRENT_BUFFER);
+ yy_switch_to_buffer(include_stack[--include_stack_ptr].state);
+ }
+}
+ YY_BREAK
+case 9:
+YY_RULE_SETUP
+#line 126 "configfile.l"
+{ return (GROUP) ; }
+ YY_BREAK
+case 10:
+YY_RULE_SETUP
+#line 128 "configfile.l"
+{ (void) 0 ; }
+ YY_BREAK
+case 11:
+YY_RULE_SETUP
+#line 130 "configfile.l"
+{ (void) 1 ; }
+ YY_BREAK
+case 12:
+YY_RULE_SETUP
+#line 132 "configfile.l"
+{
+ switch (yytext[2]) {
+ case '\\': yylval.chr = '\\' ; break ;
+ case 'a': yylval.chr = 007 ; break ;
+ case 'b': yylval.chr = 010 ; break ;
+ case 'f': yylval.chr = 014 ; break ;
+ case 'n': yylval.chr = 012 ; break ;
+ case 'r': yylval.chr = 015 ; break ;
+ case 't': yylval.chr = 011 ; break ;
+ case 'v': yylval.chr = 013 ; break ;
+ }
+ return (CHAR) ; }
+ YY_BREAK
+case 13:
+YY_RULE_SETUP
+#line 145 "configfile.l"
+{ yylval.chr = yytext[1] ; return (CHAR) ; }
+ YY_BREAK
+case 14:
+YY_RULE_SETUP
+#line 147 "configfile.l"
+{ yylval.chr = (char)strtol(&yytext[2], (char **)NULL, 8);
+ return (CHAR) ;}
+ YY_BREAK
+case 15:
+YY_RULE_SETUP
+#line 150 "configfile.l"
+{{
+ int i ;
+
+ for (i = 1, strIdx = 0, sawBsl = 0 ; ; i++)
+ {
+ if (i < yyleng)
+ current = yytext [i] ;
+ else
+ current = input() ;
+
+ if (current != EOF)
+ {
+ switch (current)
+ {
+ case '\\':
+ if (sawBsl)
+ {
+ strAppend (current) ;
+ sawBsl = 0 ;
+ }
+ else
+ sawBsl = 1 ;
+ break ;
+
+ case '\n':
+ if (!sawBsl)
+ strAppend(current) ;
+ sawBsl = 0 ;
+ lineCount++ ;
+ break ;
+
+ case '\"':
+ if (sawBsl)
+ {
+ strAppend (current) ;
+ sawBsl = 0 ;
+ }
+ else
+ {
+ strAppend ('\0') ;
+ yylval.string = strPtr ;
+ strPtr = 0 ;
+ strPtrLen = strIdx = 0 ;
+ return (XSTRING) ;
+ }
+ break ;
+
+ case 'a':
+ case 'b':
+ case 'f':
+ case 'n':
+ case 'r':
+ case 't':
+ case 'v':
+ if (sawBsl)
+ {
+ switch (current)
+ {
+ case 'a': strAppend (007) ; break ;
+ case 'b': strAppend (010) ; break ;
+ case 'f': strAppend (014) ; break ;
+ case 'n': strAppend (012) ; break ;
+ case 'r': strAppend (015) ; break ;
+ case 't': strAppend (011) ; break ;
+ case 'v': strAppend (013) ; break ;
+ }
+ sawBsl = 0 ;
+ }
+ else
+ strAppend (current) ;
+ break ;
+
+ default:
+ strAppend (current) ;
+ sawBsl = 0 ;
+ break ;
+ }
+ }
+ else
+ {
+ return (XSTRING) ;
+ }
+ }
+}}
+ YY_BREAK
+case 16:
+YY_RULE_SETUP
+#line 235 "configfile.l"
+{ yylval.integer = atoi (yytext) ; return (IVAL) ; }
+ YY_BREAK
+case 17:
+YY_RULE_SETUP
+#line 237 "configfile.l"
+{ yylval.real = atof (yytext) ; return (RVAL) ; }
+ YY_BREAK
+case 18:
+YY_RULE_SETUP
+#line 239 "configfile.l"
+{
+ yylval.name = xstrdup (yytext) ;
+ if (strcasecmp (yylval.name,"false") == 0)
+ return (FALSEBVAL) ;
+ else if (strcasecmp (yylval.name,"true") == 0)
+ return (TRUEBVAL) ;
+ else
+ return (WORD) ;
+}
+ YY_BREAK
+case 19:
+YY_RULE_SETUP
+#line 249 "configfile.l"
+ECHO;
+ YY_BREAK
+#line 968 "lex.yy.c"
+
+ case YY_END_OF_BUFFER:
+ {
+ /* Amount of text matched not including the EOB char. */
+ int yy_amount_of_matched_text = (int) (yy_cp - yytext_ptr) - 1;
+
+ /* Undo the effects of YY_DO_BEFORE_ACTION. */
+ *yy_cp = yy_hold_char;
+ YY_RESTORE_YY_MORE_OFFSET
+
+ if ( yy_current_buffer->yy_buffer_status == YY_BUFFER_NEW )
+ {
+ /* We're scanning a new file or input source. It's
+ * possible that this happened because the user
+ * just pointed yyin at a new source and called
+ * yylex(). If so, then we have to assure
+ * consistency between yy_current_buffer and our
+ * globals. Here is the right place to do so, because
+ * this is the first action (other than possibly a
+ * back-up) that will match for the new input source.
+ */
+ yy_n_chars = yy_current_buffer->yy_n_chars;
+ yy_current_buffer->yy_input_file = yyin;
+ yy_current_buffer->yy_buffer_status = YY_BUFFER_NORMAL;
+ }
+
+ /* Note that here we test for yy_c_buf_p "<=" to the position
+ * of the first EOB in the buffer, since yy_c_buf_p will
+ * already have been incremented past the NUL character
+ * (since all states make transitions on EOB to the
+ * end-of-buffer state). Contrast this with the test
+ * in input().
+ */
+ if ( yy_c_buf_p <= &yy_current_buffer->yy_ch_buf[yy_n_chars] )
+ { /* This was really a NUL. */
+ yy_state_type yy_next_state;
+
+ yy_c_buf_p = yytext_ptr + yy_amount_of_matched_text;
+
+ yy_current_state = yy_get_previous_state();
+
+ /* Okay, we're now positioned to make the NUL
+ * transition. We couldn't have
+ * yy_get_previous_state() go ahead and do it
+ * for us because it doesn't know how to deal
+ * with the possibility of jamming (and we don't
+ * want to build jamming into it because then it
+ * will run more slowly).
+ */
+
+ yy_next_state = yy_try_NUL_trans( yy_current_state );
+
+ yy_bp = yytext_ptr + YY_MORE_ADJ;
+
+ if ( yy_next_state )
+ {
+ /* Consume the NUL. */
+ yy_cp = ++yy_c_buf_p;
+ yy_current_state = yy_next_state;
+ goto yy_match;
+ }
+
+ else
+ {
+ yy_cp = yy_c_buf_p;
+ goto yy_find_action;
+ }
+ }
+
+ else switch ( yy_get_next_buffer() )
+ {
+ case EOB_ACT_END_OF_FILE:
+ {
+ yy_did_buffer_switch_on_eof = 0;
+
+ if ( yywrap() )
+ {
+ /* Note: because we've taken care in
+ * yy_get_next_buffer() to have set up
+ * yytext, we can now set up
+ * yy_c_buf_p so that if some total
+ * hoser (like flex itself) wants to
+ * call the scanner after we return the
+ * YY_NULL, it'll still work - another
+ * YY_NULL will get returned.
+ */
+ yy_c_buf_p = yytext_ptr + YY_MORE_ADJ;
+
+ yy_act = YY_STATE_EOF(YY_START);
+ goto do_action;
+ }
+
+ else
+ {
+ if ( ! yy_did_buffer_switch_on_eof )
+ YY_NEW_FILE;
+ }
+ break;
+ }
+
+ case EOB_ACT_CONTINUE_SCAN:
+ yy_c_buf_p =
+ yytext_ptr + yy_amount_of_matched_text;
+
+ yy_current_state = yy_get_previous_state();
+
+ yy_cp = yy_c_buf_p;
+ yy_bp = yytext_ptr + YY_MORE_ADJ;
+ goto yy_match;
+
+ case EOB_ACT_LAST_MATCH:
+ yy_c_buf_p =
+ &yy_current_buffer->yy_ch_buf[yy_n_chars];
+
+ yy_current_state = yy_get_previous_state();
+
+ yy_cp = yy_c_buf_p;
+ yy_bp = yytext_ptr + YY_MORE_ADJ;
+ goto yy_find_action;
+ }
+ break;
+ }
+
+ default:
+ YY_FATAL_ERROR(
+ "fatal flex scanner internal error--no action found" );
+ } /* end of action switch */
+ } /* end of scanning one token */
+ } /* end of yylex */
+
+
+/* yy_get_next_buffer - try to read in a new buffer
+ *
+ * Returns a code representing an action:
+ * EOB_ACT_LAST_MATCH -
+ * EOB_ACT_CONTINUE_SCAN - continue scanning from current position
+ * EOB_ACT_END_OF_FILE - end of file
+ */
+
+static int yy_get_next_buffer()
+ {
+ register char *dest = yy_current_buffer->yy_ch_buf;
+ register char *source = yytext_ptr;
+ register int number_to_move, i;
+ int ret_val;
+
+ if ( yy_c_buf_p > &yy_current_buffer->yy_ch_buf[yy_n_chars + 1] )
+ YY_FATAL_ERROR(
+ "fatal flex scanner internal error--end of buffer missed" );
+
+ if ( yy_current_buffer->yy_fill_buffer == 0 )
+ { /* Don't try to fill the buffer, so this is an EOF. */
+ if ( yy_c_buf_p - yytext_ptr - YY_MORE_ADJ == 1 )
+ {
+ /* We matched a single character, the EOB, so
+ * treat this as a final EOF.
+ */
+ return EOB_ACT_END_OF_FILE;
+ }
+
+ else
+ {
+ /* We matched some text prior to the EOB, first
+ * process it.
+ */
+ return EOB_ACT_LAST_MATCH;
+ }
+ }
+
+ /* Try to read more data. */
+
+ /* First move last chars to start of buffer. */
+ number_to_move = (int) (yy_c_buf_p - yytext_ptr) - 1;
+
+ for ( i = 0; i < number_to_move; ++i )
+ *(dest++) = *(source++);
+
+ if ( yy_current_buffer->yy_buffer_status == YY_BUFFER_EOF_PENDING )
+ /* don't do the read, it's not guaranteed to return an EOF,
+ * just force an EOF
+ */
+ yy_current_buffer->yy_n_chars = yy_n_chars = 0;
+
+ else
+ {
+ int num_to_read =
+ yy_current_buffer->yy_buf_size - number_to_move - 1;
+
+ while ( num_to_read <= 0 )
+ { /* Not enough room in the buffer - grow it. */
+#ifdef YY_USES_REJECT
+ YY_FATAL_ERROR(
+"input buffer overflow, can't enlarge buffer because scanner uses REJECT" );
+#else
+
+ /* just a shorter name for the current buffer */
+ YY_BUFFER_STATE b = yy_current_buffer;
+
+ int yy_c_buf_p_offset =
+ (int) (yy_c_buf_p - b->yy_ch_buf);
+
+ if ( b->yy_is_our_buffer )
+ {
+ int new_size = b->yy_buf_size * 2;
+
+ if ( new_size <= 0 )
+ b->yy_buf_size += b->yy_buf_size / 8;
+ else
+ b->yy_buf_size *= 2;
+
+ b->yy_ch_buf = (char *)
+ /* Include room in for 2 EOB chars. */
+ yy_flex_realloc( (void *) b->yy_ch_buf,
+ b->yy_buf_size + 2 );
+ }
+ else
+ /* Can't grow it, we don't own it. */
+ b->yy_ch_buf = 0;
+
+ if ( ! b->yy_ch_buf )
+ YY_FATAL_ERROR(
+ "fatal error - scanner input buffer overflow" );
+
+ yy_c_buf_p = &b->yy_ch_buf[yy_c_buf_p_offset];
+
+ num_to_read = yy_current_buffer->yy_buf_size -
+ number_to_move - 1;
+#endif
+ }
+
+ if ( num_to_read > YY_READ_BUF_SIZE )
+ num_to_read = YY_READ_BUF_SIZE;
+
+ /* Read in more data. */
+ YY_INPUT( (&yy_current_buffer->yy_ch_buf[number_to_move]),
+ yy_n_chars, num_to_read );
+
+ yy_current_buffer->yy_n_chars = yy_n_chars;
+ }
+
+ if ( yy_n_chars == 0 )
+ {
+ if ( number_to_move == YY_MORE_ADJ )
+ {
+ ret_val = EOB_ACT_END_OF_FILE;
+ yyrestart( yyin );
+ }
+
+ else
+ {
+ ret_val = EOB_ACT_LAST_MATCH;
+ yy_current_buffer->yy_buffer_status =
+ YY_BUFFER_EOF_PENDING;
+ }
+ }
+
+ else
+ ret_val = EOB_ACT_CONTINUE_SCAN;
+
+ yy_n_chars += number_to_move;
+ yy_current_buffer->yy_ch_buf[yy_n_chars] = YY_END_OF_BUFFER_CHAR;
+ yy_current_buffer->yy_ch_buf[yy_n_chars + 1] = YY_END_OF_BUFFER_CHAR;
+
+ yytext_ptr = &yy_current_buffer->yy_ch_buf[0];
+
+ return ret_val;
+ }
+
+
+/* yy_get_previous_state - get the state just before the EOB char was reached */
+
+static yy_state_type yy_get_previous_state()
+ {
+ register yy_state_type yy_current_state;
+ register char *yy_cp;
+
+ yy_current_state = yy_start;
+ yy_current_state += YY_AT_BOL();
+
+ for ( yy_cp = yytext_ptr + YY_MORE_ADJ; yy_cp < yy_c_buf_p; ++yy_cp )
+ {
+ register YY_CHAR yy_c = (*yy_cp ? yy_ec[YY_SC_TO_UI(*yy_cp)] : 1);
+ if ( yy_accept[yy_current_state] )
+ {
+ yy_last_accepting_state = yy_current_state;
+ yy_last_accepting_cpos = yy_cp;
+ }
+ while ( yy_chk[yy_base[yy_current_state] + yy_c] != yy_current_state )
+ {
+ yy_current_state = (int) yy_def[yy_current_state];
+ if ( yy_current_state >= 55 )
+ yy_c = yy_meta[(unsigned int) yy_c];
+ }
+ yy_current_state = yy_nxt[yy_base[yy_current_state] + (unsigned int) yy_c];
+ }
+
+ return yy_current_state;
+ }
+
+
+/* yy_try_NUL_trans - try to make a transition on the NUL character
+ *
+ * synopsis
+ * next_state = yy_try_NUL_trans( current_state );
+ */
+
+#ifdef YY_USE_PROTOS
+static yy_state_type yy_try_NUL_trans( yy_state_type yy_current_state )
+#else
+static yy_state_type yy_try_NUL_trans( yy_current_state )
+yy_state_type yy_current_state;
+#endif
+ {
+ register int yy_is_jam;
+ register char *yy_cp = yy_c_buf_p;
+
+ register YY_CHAR yy_c = 1;
+ if ( yy_accept[yy_current_state] )
+ {
+ yy_last_accepting_state = yy_current_state;
+ yy_last_accepting_cpos = yy_cp;
+ }
+ while ( yy_chk[yy_base[yy_current_state] + yy_c] != yy_current_state )
+ {
+ yy_current_state = (int) yy_def[yy_current_state];
+ if ( yy_current_state >= 55 )
+ yy_c = yy_meta[(unsigned int) yy_c];
+ }
+ yy_current_state = yy_nxt[yy_base[yy_current_state] + (unsigned int) yy_c];
+ yy_is_jam = (yy_current_state == 54);
+
+ return yy_is_jam ? 0 : yy_current_state;
+ }
+
+
+#ifndef YY_NO_UNPUT
+#ifdef YY_USE_PROTOS
+static void yyunput( int c, register char *yy_bp )
+#else
+static void yyunput( c, yy_bp )
+int c;
+register char *yy_bp;
+#endif
+ {
+ register char *yy_cp = yy_c_buf_p;
+
+ /* undo effects of setting up yytext */
+ *yy_cp = yy_hold_char;
+
+ if ( yy_cp < yy_current_buffer->yy_ch_buf + 2 )
+ { /* need to shift things up to make room */
+ /* +2 for EOB chars. */
+ register int number_to_move = yy_n_chars + 2;
+ register char *dest = &yy_current_buffer->yy_ch_buf[
+ yy_current_buffer->yy_buf_size + 2];
+ register char *source =
+ &yy_current_buffer->yy_ch_buf[number_to_move];
+
+ while ( source > yy_current_buffer->yy_ch_buf )
+ *--dest = *--source;
+
+ yy_cp += (int) (dest - source);
+ yy_bp += (int) (dest - source);
+ yy_current_buffer->yy_n_chars =
+ yy_n_chars = yy_current_buffer->yy_buf_size;
+
+ if ( yy_cp < yy_current_buffer->yy_ch_buf + 2 )
+ YY_FATAL_ERROR( "flex scanner push-back overflow" );
+ }
+
+ *--yy_cp = (char) c;
+
+
+ yytext_ptr = yy_bp;
+ yy_hold_char = *yy_cp;
+ yy_c_buf_p = yy_cp;
+ }
+#endif /* ifndef YY_NO_UNPUT */
+
+
+#ifdef __cplusplus
+static int yyinput()
+#else
+static int input()
+#endif
+ {
+ int c;
+
+ *yy_c_buf_p = yy_hold_char;
+
+ if ( *yy_c_buf_p == YY_END_OF_BUFFER_CHAR )
+ {
+ /* yy_c_buf_p now points to the character we want to return.
+ * If this occurs *before* the EOB characters, then it's a
+ * valid NUL; if not, then we've hit the end of the buffer.
+ */
+ if ( yy_c_buf_p < &yy_current_buffer->yy_ch_buf[yy_n_chars] )
+ /* This was really a NUL. */
+ *yy_c_buf_p = '\0';
+
+ else
+ { /* need more input */
+ int offset = yy_c_buf_p - yytext_ptr;
+ ++yy_c_buf_p;
+
+ switch ( yy_get_next_buffer() )
+ {
+ case EOB_ACT_LAST_MATCH:
+ /* This happens because yy_g_n_b()
+ * sees that we've accumulated a
+ * token and flags that we need to
+ * try matching the token before
+ * proceeding. But for input(),
+ * there's no matching to consider.
+ * So convert the EOB_ACT_LAST_MATCH
+ * to EOB_ACT_END_OF_FILE.
+ */
+
+ /* Reset buffer status. */
+ yyrestart( yyin );
+
+ /* fall through */
+
+ case EOB_ACT_END_OF_FILE:
+ {
+ if ( yywrap() )
+ return EOF;
+
+ if ( ! yy_did_buffer_switch_on_eof )
+ YY_NEW_FILE;
+#ifdef __cplusplus
+ return yyinput();
+#else
+ return input();
+#endif
+ }
+
+ case EOB_ACT_CONTINUE_SCAN:
+ yy_c_buf_p = yytext_ptr + offset;
+ break;
+ }
+ }
+ }
+
+ c = *(unsigned char *) yy_c_buf_p; /* cast for 8-bit char's */
+ *yy_c_buf_p = '\0'; /* preserve yytext */
+ yy_hold_char = *++yy_c_buf_p;
+
+ yy_current_buffer->yy_at_bol = (c == '\n');
+
+ return c;
+ }
+
+
+#ifdef YY_USE_PROTOS
+void yyrestart( FILE *input_file )
+#else
+void yyrestart( input_file )
+FILE *input_file;
+#endif
+ {
+ if ( ! yy_current_buffer )
+ yy_current_buffer = yy_create_buffer( yyin, YY_BUF_SIZE );
+
+ yy_init_buffer( yy_current_buffer, input_file );
+ yy_load_buffer_state();
+ }
+
+
+#ifdef YY_USE_PROTOS
+void yy_switch_to_buffer( YY_BUFFER_STATE new_buffer )
+#else
+void yy_switch_to_buffer( new_buffer )
+YY_BUFFER_STATE new_buffer;
+#endif
+ {
+ if ( yy_current_buffer == new_buffer )
+ return;
+
+ if ( yy_current_buffer )
+ {
+ /* Flush out information for old buffer. */
+ *yy_c_buf_p = yy_hold_char;
+ yy_current_buffer->yy_buf_pos = yy_c_buf_p;
+ yy_current_buffer->yy_n_chars = yy_n_chars;
+ }
+
+ yy_current_buffer = new_buffer;
+ yy_load_buffer_state();
+
+ /* We don't actually know whether we did this switch during
+ * EOF (yywrap()) processing, but the only time this flag
+ * is looked at is after yywrap() is called, so it's safe
+ * to go ahead and always set it.
+ */
+ yy_did_buffer_switch_on_eof = 1;
+ }
+
+
+#ifdef YY_USE_PROTOS
+void yy_load_buffer_state( void )
+#else
+void yy_load_buffer_state()
+#endif
+ {
+ yy_n_chars = yy_current_buffer->yy_n_chars;
+ yytext_ptr = yy_c_buf_p = yy_current_buffer->yy_buf_pos;
+ yyin = yy_current_buffer->yy_input_file;
+ yy_hold_char = *yy_c_buf_p;
+ }
+
+
+#ifdef YY_USE_PROTOS
+YY_BUFFER_STATE yy_create_buffer( FILE *file, int size )
+#else
+YY_BUFFER_STATE yy_create_buffer( file, size )
+FILE *file;
+int size;
+#endif
+ {
+ YY_BUFFER_STATE b;
+
+ b = (YY_BUFFER_STATE) yy_flex_alloc( sizeof( struct yy_buffer_state ) );
+ if ( ! b )
+ YY_FATAL_ERROR( "out of dynamic memory in yy_create_buffer()" );
+
+ b->yy_buf_size = size;
+
+ /* yy_ch_buf has to be 2 characters longer than the size given because
+ * we need to put in 2 end-of-buffer characters.
+ */
+ b->yy_ch_buf = (char *) yy_flex_alloc( b->yy_buf_size + 2 );
+ if ( ! b->yy_ch_buf )
+ YY_FATAL_ERROR( "out of dynamic memory in yy_create_buffer()" );
+
+ b->yy_is_our_buffer = 1;
+
+ yy_init_buffer( b, file );
+
+ return b;
+ }
+
+
+#ifdef YY_USE_PROTOS
+void yy_delete_buffer( YY_BUFFER_STATE b )
+#else
+void yy_delete_buffer( b )
+YY_BUFFER_STATE b;
+#endif
+ {
+ if ( ! b )
+ return;
+
+ if ( b == yy_current_buffer )
+ yy_current_buffer = (YY_BUFFER_STATE) 0;
+
+ if ( b->yy_is_our_buffer )
+ yy_flex_free( (void *) b->yy_ch_buf );
+
+ yy_flex_free( (void *) b );
+ }
+
+
+#ifndef _WIN32
+#include <unistd.h>
+#else
+#ifndef YY_ALWAYS_INTERACTIVE
+#ifndef YY_NEVER_INTERACTIVE
+extern int isatty YY_PROTO(( int ));
+#endif
+#endif
+#endif
+
+#ifdef YY_USE_PROTOS
+void yy_init_buffer( YY_BUFFER_STATE b, FILE *file )
+#else
+void yy_init_buffer( b, file )
+YY_BUFFER_STATE b;
+FILE *file;
+#endif
+
+
+ {
+ yy_flush_buffer( b );
+
+ b->yy_input_file = file;
+ b->yy_fill_buffer = 1;
+
+#if YY_ALWAYS_INTERACTIVE
+ b->yy_is_interactive = 1;
+#else
+#if YY_NEVER_INTERACTIVE
+ b->yy_is_interactive = 0;
+#else
+ b->yy_is_interactive = file ? (isatty( fileno(file) ) > 0) : 0;
+#endif
+#endif
+ }
+
+
+#ifdef YY_USE_PROTOS
+void yy_flush_buffer( YY_BUFFER_STATE b )
+#else
+void yy_flush_buffer( b )
+YY_BUFFER_STATE b;
+#endif
+
+ {
+ if ( ! b )
+ return;
+
+ b->yy_n_chars = 0;
+
+ /* We always need two end-of-buffer characters. The first causes
+ * a transition to the end-of-buffer state. The second causes
+ * a jam in that state.
+ */
+ b->yy_ch_buf[0] = YY_END_OF_BUFFER_CHAR;
+ b->yy_ch_buf[1] = YY_END_OF_BUFFER_CHAR;
+
+ b->yy_buf_pos = &b->yy_ch_buf[0];
+
+ b->yy_at_bol = 1;
+ b->yy_buffer_status = YY_BUFFER_NEW;
+
+ if ( b == yy_current_buffer )
+ yy_load_buffer_state();
+ }
+
+
+#ifndef YY_NO_SCAN_BUFFER
+#ifdef YY_USE_PROTOS
+YY_BUFFER_STATE yy_scan_buffer( char *base, yy_size_t size )
+#else
+YY_BUFFER_STATE yy_scan_buffer( base, size )
+char *base;
+yy_size_t size;
+#endif
+ {
+ YY_BUFFER_STATE b;
+
+ if ( size < 2 ||
+ base[size-2] != YY_END_OF_BUFFER_CHAR ||
+ base[size-1] != YY_END_OF_BUFFER_CHAR )
+ /* They forgot to leave room for the EOB's. */
+ return 0;
+
+ b = (YY_BUFFER_STATE) yy_flex_alloc( sizeof( struct yy_buffer_state ) );
+ if ( ! b )
+ YY_FATAL_ERROR( "out of dynamic memory in yy_scan_buffer()" );
+
+ b->yy_buf_size = size - 2; /* "- 2" to take care of EOB's */
+ b->yy_buf_pos = b->yy_ch_buf = base;
+ b->yy_is_our_buffer = 0;
+ b->yy_input_file = 0;
+ b->yy_n_chars = b->yy_buf_size;
+ b->yy_is_interactive = 0;
+ b->yy_at_bol = 1;
+ b->yy_fill_buffer = 0;
+ b->yy_buffer_status = YY_BUFFER_NEW;
+
+ yy_switch_to_buffer( b );
+
+ return b;
+ }
+#endif
+
+
+#ifndef YY_NO_SCAN_STRING
+#ifdef YY_USE_PROTOS
+YY_BUFFER_STATE yy_scan_string( yyconst char *yy_str )
+#else
+YY_BUFFER_STATE yy_scan_string( yy_str )
+yyconst char *yy_str;
+#endif
+ {
+ int len;
+ for ( len = 0; yy_str[len]; ++len )
+ ;
+
+ return yy_scan_bytes( yy_str, len );
+ }
+#endif
+
+
+#ifndef YY_NO_SCAN_BYTES
+#ifdef YY_USE_PROTOS
+YY_BUFFER_STATE yy_scan_bytes( yyconst char *bytes, int len )
+#else
+YY_BUFFER_STATE yy_scan_bytes( bytes, len )
+yyconst char *bytes;
+int len;
+#endif
+ {
+ YY_BUFFER_STATE b;
+ char *buf;
+ yy_size_t n;
+ int i;
+
+ /* Get memory for full buffer, including space for trailing EOB's. */
+ n = len + 2;
+ buf = (char *) yy_flex_alloc( n );
+ if ( ! buf )
+ YY_FATAL_ERROR( "out of dynamic memory in yy_scan_bytes()" );
+
+ for ( i = 0; i < len; ++i )
+ buf[i] = bytes[i];
+
+ buf[len] = buf[len+1] = YY_END_OF_BUFFER_CHAR;
+
+ b = yy_scan_buffer( buf, n );
+ if ( ! b )
+ YY_FATAL_ERROR( "bad buffer in yy_scan_bytes()" );
+
+ /* It's okay to grow etc. this buffer, and we should throw it
+ * away when we're done.
+ */
+ b->yy_is_our_buffer = 1;
+
+ return b;
+ }
+#endif
+
+
+#ifndef YY_NO_PUSH_STATE
+#ifdef YY_USE_PROTOS
+static void yy_push_state( int new_state )
+#else
+static void yy_push_state( new_state )
+int new_state;
+#endif
+ {
+ if ( yy_start_stack_ptr >= yy_start_stack_depth )
+ {
+ yy_size_t new_size;
+
+ yy_start_stack_depth += YY_START_STACK_INCR;
+ new_size = yy_start_stack_depth * sizeof( int );
+
+ if ( ! yy_start_stack )
+ yy_start_stack = (int *) yy_flex_alloc( new_size );
+
+ else
+ yy_start_stack = (int *) yy_flex_realloc(
+ (void *) yy_start_stack, new_size );
+
+ if ( ! yy_start_stack )
+ YY_FATAL_ERROR(
+ "out of memory expanding start-condition stack" );
+ }
+
+ yy_start_stack[yy_start_stack_ptr++] = YY_START;
+
+ BEGIN(new_state);
+ }
+#endif
+
+
+#ifndef YY_NO_POP_STATE
+static void yy_pop_state()
+ {
+ if ( --yy_start_stack_ptr < 0 )
+ YY_FATAL_ERROR( "start-condition stack underflow" );
+
+ BEGIN(yy_start_stack[yy_start_stack_ptr]);
+ }
+#endif
+
+
+#ifndef YY_NO_TOP_STATE
+static int yy_top_state()
+ {
+ return yy_start_stack[yy_start_stack_ptr - 1];
+ }
+#endif
+
+#ifndef YY_EXIT_FAILURE
+#define YY_EXIT_FAILURE 2
+#endif
+
+#ifdef YY_USE_PROTOS
+static void yy_fatal_error( yyconst char msg[] )
+#else
+static void yy_fatal_error( msg )
+char msg[];
+#endif
+ {
+ (void) fprintf( stderr, "%s\n", msg );
+ exit( YY_EXIT_FAILURE );
+ }
+
+
+
+/* Redefine yyless() so it works in section 3 code. */
+
+#undef yyless
+#define yyless(n) \
+ do \
+ { \
+ /* Undo effects of setting up yytext. */ \
+ yytext[yyleng] = yy_hold_char; \
+ yy_c_buf_p = yytext + n; \
+ yy_hold_char = *yy_c_buf_p; \
+ *yy_c_buf_p = '\0'; \
+ yyleng = n; \
+ } \
+ while ( 0 )
+
+
+/* Internal utility routines. */
+
+#ifndef yytext_ptr
+#ifdef YY_USE_PROTOS
+static void yy_flex_strncpy( char *s1, yyconst char *s2, int n )
+#else
+static void yy_flex_strncpy( s1, s2, n )
+char *s1;
+yyconst char *s2;
+int n;
+#endif
+ {
+ register int i;
+ for ( i = 0; i < n; ++i )
+ s1[i] = s2[i];
+ }
+#endif
+
+#ifdef YY_NEED_STRLEN
+#ifdef YY_USE_PROTOS
+static int yy_flex_strlen( yyconst char *s )
+#else
+static int yy_flex_strlen( s )
+yyconst char *s;
+#endif
+ {
+ register int n;
+ for ( n = 0; s[n]; ++n )
+ ;
+
+ return n;
+ }
+#endif
+
+
+#ifdef YY_USE_PROTOS
+static void *yy_flex_alloc( yy_size_t size )
+#else
+static void *yy_flex_alloc( size )
+yy_size_t size;
+#endif
+ {
+ return (void *) malloc( size );
+ }
+
+#ifdef YY_USE_PROTOS
+static void *yy_flex_realloc( void *ptr, yy_size_t size )
+#else
+static void *yy_flex_realloc( ptr, size )
+void *ptr;
+yy_size_t size;
+#endif
+ {
+ /* The cast to (char *) in the following accommodates both
+ * implementations that use char* generic pointers, and those
+ * that use void* generic pointers. It works with the latter
+ * because both ANSI C and C++ allow castless assignment from
+ * any pointer type to void*, and deal with argument conversions
+ * as though doing an assignment.
+ */
+ return (void *) realloc( (char *) ptr, size );
+ }
+
+#ifdef YY_USE_PROTOS
+static void yy_flex_free( void *ptr )
+#else
+static void yy_flex_free( ptr )
+void *ptr;
+#endif
+ {
+ free( ptr );
+ }
+
+#if YY_MAIN
+int main()
+ {
+ yylex();
+ return 0;
+ }
+#endif
+#line 249 "configfile.l"
+
+
+
+
--- /dev/null
+/* $Id: configfile.h 5801 2002-10-07 07:36:12Z rra $
+**
+** Interface to innfeed's configuration file parser.
+**
+** Written by James Brister <brister@vix.com>
+*/
+
+#if ! defined ( configfile_h__ )
+#define configfile_h__
+
+/* Avoid conflicts with parsedate.y's generated yacc code. */
+#define yy_yyv innfeed_yy_yyv
+#define yyact innfeed_yyact
+#define yychk innfeed_yychk
+#define yydef innfeed_yydef
+#define yyexca innfeed_yyexca
+#define yylval innfeed_yylval
+#define yypact innfeed_yypact
+#define yypgo innfeed_yypgo
+#define yyr1 innfeed_yyr1
+#define yyr2 innfeed_yyr2
+#define yys innfeed_yys
+#define yyv innfeed_yyv
+#define yyval innfeed_yyval
+
+/* pointer to function taking void-star param and returning int. */
+typedef int (*PFIVP)(void *) ;
+
+typedef enum { intval, charval, boolval, realval, stringval, scopeval } tag ;
+
+typedef struct _scope {
+ struct _value *me ;
+ char *scope_type ;
+ int value_count ;
+ int value_idx ;
+ struct _value **values ;
+ struct _scope *parent ;
+} scope ;
+
+typedef struct _value {
+ char *name ;
+ struct _scope *myscope ;
+ tag type ;
+ union {
+ char *charp_val ;
+ char char_val ;
+ double real_val ;
+ int bool_val ;
+ long int_val ;
+ struct _scope *scope_val ;
+ } v ;
+} value ;
+
+extern scope *topScope ;
+extern char *errbuff ;
+
+int isWord (scope *s, const char *name, int inherit) ;
+int isName (scope *s, const char *name, int inherit) ;
+
+int getReal (scope *s, const char *name, double *rval, int inherit) ;
+int getInteger (scope *s, const char *name, long *rval, int inherit) ;
+int getBool (scope *s, const char *name, int *rval, int inherit) ;
+int getString (scope *s, const char *name, char **rval, int inherit) ;
+int getWord (scope *s, const char *name, char **rval, int inherit) ;
+
+void freeScopeTree (scope *s) ;
+char *addInteger (scope *s, const char *name, long val) ;
+char *addChar (scope *s, const char *name, char val) ;
+char *addBoolean (scope *s, const char *name, int val) ;
+char *addName (scope *s, const char *name, char *val) ;
+char *addWord (scope *s, const char *name, char *val) ;
+char *addReal (scope *s, const char *name, double val) ;
+char *addString (scope *s, const char *name, const char *val) ;
+scope *findScope (scope *s, const char *name, int mustExist) ;
+value *findValue (scope *s, const char *name, int inherit) ;
+value *findPeer (const char *name) ;
+value *getNextPeer (int *cookie) ;
+void configAddLoadCallback (PFIVP func,void *arg) ;
+void configRemoveLoadCallback (PFIVP func) ;
+int readConfig (const char *file, FILE *errorDest, int justCheck, int dump) ;
+int buildPeerTable (FILE *fp, scope *currScope);
+void configCleanup (void) ;
+
+#define ARTICLE_TIMEOUT "article-timeout"
+#define BACKLOG_LIMIT "backlog-limit"
+#define INITIAL_CONNECTIONS "initial-connections"
+#define IP_NAME "ip-name"
+#define MAX_CONNECTIONS "max-connections"
+#define MAX_QUEUE_SIZE "max-queue-size"
+#define NO_CHECK_HIGH "no-check-high"
+#define NO_CHECK_LOW "no-check-low"
+#define PORT_NUMBER "port-number"
+#define RESP_TIMEOUT "response-timeout"
+#define STREAMING "streaming"
+#define DROP_DEFERRED "drop-deferred"
+
+#define ISPEER(V) (ISSCOPE(V) && strcmp ((V)->v.scope_val->scope_type,"peer") == 0)
+#define ISSCOPE(V) (V->type == scopeval)
+
+#define INHERIT 1
+#define NO_INHERIT 0
+
+/* Interface between lexer and parser. */
+int yylex (void) ;
+
+#endif /* configfile_h__ */
--- /dev/null
+%{
+/* $Id: configfile.l 6145 2003-01-19 19:42:22Z rra $
+**
+** A flex input file for the innfeed config file.
+**
+** Written by James Brister <brister@vix.com>
+*/
+
+#include "innfeed.h"
+
+#include <errno.h>
+#include <stdlib.h>
+#include <string.h>
+#include <syslog.h>
+
+#include "libinn.h"
+
+#include "configfile.h"
+#include "config_y.h"
+#include "misc.h"
+
+#if ! defined (FLEX_SCANNER)
+#error "You must use FLEX to process the lex input file."
+#endif
+
+#if defined (FLEX_DEBUG)
+#define YY_USER_INIT yy_flex_debug = (getenv ("YYDEBUG") == NULL ? 0 : 1)
+#endif
+
+/* We never use this function but flex always defines it, so silence the
+ warnings about it. */
+static void yyunput(int, char *) UNUSED;
+
+char *strPtr = 0 ;
+int strPtrLen = 0 ;
+int strIdx = 0 ;
+int sawBsl ;
+int lineCount = 0 ;
+int current ;
+
+static void strAppend (int ch);
+static void strAppend (int ch)
+{
+ if (strIdx == strPtrLen)
+ {
+ if (strPtr == 0)
+ strPtr = xmalloc (strPtrLen = 50) ;
+ else
+ strPtr = xrealloc (strPtr,strPtrLen += 10) ;
+ }
+ strPtr [strIdx++] = ch ;
+}
+
+#define MAX_INCLUDE_DEPTH 11
+struct includeFile {
+ YY_BUFFER_STATE state;
+ char *name ;
+} include_stack[MAX_INCLUDE_DEPTH];
+int include_stack_ptr = 0;
+
+%}
+
+%x incl
+
+ID [a-zA-Z][-a-zA-Z0-9._/]+
+
+%%
+
+\n lineCount++ ;
+
+":" { return (COLON) ; }
+
+"{" { return (LBRACE) ; }
+
+"}" { return (RBRACE) ; }
+
+[pP][eE][eE][rR] { return (PEER) ; }
+
+^"$INCLUDE" BEGIN(incl);
+
+<incl>[ \t]* /* eat the whitespace before include filename */
+
+<incl>[^ \t\n]+ {
+ if (include_stack_ptr == MAX_INCLUDE_DEPTH - 1)
+ {
+ int i ;
+ fprintf( stderr, "Includes nested too deeply:\n" );
+ for (i = 1 ; i <= include_stack_ptr ; i++)
+ fprintf (stderr,"\t%s\n",include_stack[i].name) ;
+
+ syslog (LOG_ERR, "includes nested to deeply") ;
+ exit( 1 );
+ }
+
+ if ((yyin = fopen(yytext,"r")) == NULL)
+ {
+ syslog (LOG_CRIT,"include file fopen failed: %s %s",
+ yytext,strerror(errno));
+ fprintf (stderr,"include file fopen failed: %s %s\n",
+ yytext,strerror(errno));
+ exit (1) ;
+ }
+ else
+ {
+ d_printf (1,"Including (%d) from %s\n",
+ include_stack_ptr + 1,yytext) ;
+ include_stack[include_stack_ptr].state = YY_CURRENT_BUFFER;
+ include_stack[++include_stack_ptr].name = xstrdup (yytext) ;
+ yy_switch_to_buffer(yy_create_buffer(yyin, YY_BUF_SIZE));
+ }
+
+ BEGIN(INITIAL);
+}
+
+<<EOF>> {
+ if ( include_stack_ptr <= 0 )
+ yyterminate();
+ else
+ {
+ free (include_stack[include_stack_ptr].name) ;
+ yy_delete_buffer(YY_CURRENT_BUFFER);
+ yy_switch_to_buffer(include_stack[--include_stack_ptr].state);
+ }
+}
+
+[gG][rR][oO][uU][pP] { return (GROUP) ; }
+
+#[^\n]* { (void) 0 ; }
+
+[ \t]+ { (void) 1 ; }
+
+'\\[\\abfnrtv]' {
+ switch (yytext[2]) {
+ case '\\': yylval.chr = '\\' ; break ;
+ case 'a': yylval.chr = 007 ; break ;
+ case 'b': yylval.chr = 010 ; break ;
+ case 'f': yylval.chr = 014 ; break ;
+ case 'n': yylval.chr = 012 ; break ;
+ case 'r': yylval.chr = 015 ; break ;
+ case 't': yylval.chr = 011 ; break ;
+ case 'v': yylval.chr = 013 ; break ;
+ }
+ return (CHAR) ; }
+
+'.' { yylval.chr = yytext[1] ; return (CHAR) ; }
+
+'\\[0-9][0-9][0-9]' { yylval.chr = (char)strtol(&yytext[2], (char **)NULL, 8);
+ return (CHAR) ;}
+
+\"[^\"]* {{
+ int i ;
+
+ for (i = 1, strIdx = 0, sawBsl = 0 ; ; i++)
+ {
+ if (i < yyleng)
+ current = yytext [i] ;
+ else
+ current = input() ;
+
+ if (current != EOF)
+ {
+ switch (current)
+ {
+ case '\\':
+ if (sawBsl)
+ {
+ strAppend (current) ;
+ sawBsl = 0 ;
+ }
+ else
+ sawBsl = 1 ;
+ break ;
+
+ case '\n':
+ if (!sawBsl)
+ strAppend(current) ;
+ sawBsl = 0 ;
+ lineCount++ ;
+ break ;
+
+ case '\"':
+ if (sawBsl)
+ {
+ strAppend (current) ;
+ sawBsl = 0 ;
+ }
+ else
+ {
+ strAppend ('\0') ;
+ yylval.string = strPtr ;
+ strPtr = 0 ;
+ strPtrLen = strIdx = 0 ;
+ return (XSTRING) ;
+ }
+ break ;
+
+ case 'a':
+ case 'b':
+ case 'f':
+ case 'n':
+ case 'r':
+ case 't':
+ case 'v':
+ if (sawBsl)
+ {
+ switch (current)
+ {
+ case 'a': strAppend (007) ; break ;
+ case 'b': strAppend (010) ; break ;
+ case 'f': strAppend (014) ; break ;
+ case 'n': strAppend (012) ; break ;
+ case 'r': strAppend (015) ; break ;
+ case 't': strAppend (011) ; break ;
+ case 'v': strAppend (013) ; break ;
+ }
+ sawBsl = 0 ;
+ }
+ else
+ strAppend (current) ;
+ break ;
+
+ default:
+ strAppend (current) ;
+ sawBsl = 0 ;
+ break ;
+ }
+ }
+ else
+ {
+ return (XSTRING) ;
+ }
+ }
+}}
+
+[-0-9][0-9]* { yylval.integer = atoi (yytext) ; return (IVAL) ; }
+
+[-0-9][0-9]*\.[0-9]* { yylval.real = atof (yytext) ; return (RVAL) ; }
+
+[^#:\'\" \t\n]+ {
+ yylval.name = xstrdup (yytext) ;
+ if (strcasecmp (yylval.name,"false") == 0)
+ return (FALSEBVAL) ;
+ else if (strcasecmp (yylval.name,"true") == 0)
+ return (TRUEBVAL) ;
+ else
+ return (WORD) ;
+}
+
+%%
+
+
+
--- /dev/null
+%{
+/* $Id: configfile.y 6372 2003-05-31 19:48:28Z rra $
+**
+** A yacc input file for the innfeed config file.
+**
+** Written by James Brister <brister@vix.com>
+**
+** This file contains the heart of the innfeed configuration parser, written
+** in yacc. It uses an external lexer generated by flex.
+*/
+
+#include "innfeed.h"
+#include "config.h"
+#include "clibrary.h"
+#include <errno.h>
+#include <ctype.h>
+#include <syslog.h>
+
+#if defined(_HPUX_SOURCE)
+# include <alloca.h>
+#endif
+
+#include "inn/messages.h"
+#include "libinn.h"
+
+#include "configfile.h"
+#include "misc.h"
+
+#define UNKNOWN_SCOPE_TYPE "line %d: unknown scope type: %s"
+#define SYNTAX_ERROR "line %d: syntax error"
+
+extern int lineCount ;
+scope *topScope = NULL ;
+static scope *currScope = NULL ;
+char *errbuff = NULL ;
+
+static void appendName (scope *s, char *p, size_t len) ;
+static char *valueScopedName (value *v) ;
+static void freeValue (value *v) ;
+static char *checkName (scope *s, const char *name) ;
+static void addValue (scope *s, value *v) ;
+static char *addScope (scope *s, const char *name, scope *val) ;
+static void printScope (FILE *fp, scope *s, int indent) ;
+static void printValue (FILE *fp, value *v, int indent) ;
+static scope *newScope (const char *type) ;
+#if 0
+static int strNCaseCmp (const char *a, const char *b, size_t len) ;
+#endif
+
+int yyerror (const char *s) ;
+int yywrap (void) ;
+int yyparse (void) ;
+
+
+#if 0
+int isString (scope *s, const char *name, int inherit)
+{
+ value *v = findValue (s,name,inherit) ;
+
+ return (v != NULL && v->type == stringval) ;
+}
+#endif
+
+int getBool (scope *s, const char *name, int *rval, int inherit)
+{
+ value *v = findValue (s,name,inherit) ;
+
+ if (v == NULL)
+ return 0 ;
+ else if (v->type != boolval)
+ return 0 ;
+
+ *rval = v->v.bool_val ;
+ return 1 ;
+}
+
+
+int getString (scope *s, const char *name, char **rval, int inherit)
+{
+ value *v = findValue (s,name,inherit) ;
+
+ if (v == NULL)
+ return 0 ;
+ else if (v->type != stringval)
+ return 0 ;
+
+ *rval = xstrdup (v->v.charp_val) ;
+ return 1 ;
+}
+
+
+int getReal (scope *s, const char *name, double *rval, int inherit)
+{
+ value *v = findValue (s,name,inherit) ;
+
+ if (v == NULL)
+ return 0 ;
+ else if (v->type != realval)
+ return 0 ;
+
+ *rval = v->v.real_val ;
+ return 1 ;
+}
+
+int getInteger (scope *s, const char *name, long *rval, int inherit)
+{
+ value *v = findValue (s,name,inherit) ;
+
+ if (v == NULL)
+ return 0 ;
+ else if (v->type != intval)
+ return 0 ;
+
+ *rval = v->v.int_val ;
+ return 1 ;
+}
+
+void freeScopeTree (scope *s)
+{
+ int i ;
+
+ if (s == NULL)
+ return ;
+
+ if (s->parent == NULL && s->me != NULL)
+ { /* top level scope */
+ free (s->me->name) ;
+ free (s->me) ;
+ }
+
+
+ for (i = 0 ; i < s->value_idx ; i++)
+ if (s->values[i] != NULL)
+ freeValue (s->values [i]) ;
+
+ free (s->values) ;
+ free (s->scope_type) ;
+
+ s->parent = NULL ;
+ s->values = NULL ;
+
+ free (s) ;
+}
+
+
+char *addInteger (scope *s, const char *name, long val)
+{
+ value *v ;
+ char *error ;
+
+ if ((error = checkName (currScope,name)) != NULL)
+ return error ;
+
+ v = (value *) calloc (1,sizeof (value)) ;
+ v->name = xstrdup (name) ;
+ v->type = intval ;
+ v->v.int_val = val ;
+
+ addValue (s,v) ;
+
+ return NULL ;
+}
+
+char *addChar (scope *s, const char *name, char val)
+{
+ value *v ;
+ char *error ;
+
+ if ((error = checkName (currScope,name)) != NULL)
+ return error ;
+
+ v = (value *) calloc (1,sizeof (value)) ;
+ v->name = xstrdup (name) ;
+ v->type = charval ;
+ v->v.char_val = val ;
+
+ addValue (s,v) ;
+
+ return NULL ;
+}
+
+char *addBoolean (scope *s, const char *name, int val)
+{
+ value *v ;
+ char *error ;
+
+ if ((error = checkName (currScope,name)) != NULL)
+ return error ;
+
+ v = (value *) calloc (1,sizeof (value)) ;
+ v->name = xstrdup (name) ;
+ v->type = boolval ;
+ v->v.bool_val = val ;
+
+ addValue (s,v) ;
+
+ return NULL ;
+}
+
+char *addReal (scope *s, const char *name, double val)
+{
+ value *v ;
+ char *error ;
+
+ if ((error = checkName (currScope,name)) != NULL)
+ return error ;
+
+ v = (value *) calloc (1,sizeof (value)) ;
+ v->name = xstrdup (name) ;
+ v->type = realval ;
+ v->v.real_val = val ;
+
+ addValue (s,v) ;
+
+ return NULL ;
+}
+
+char *addString (scope *s, const char *name, const char *val)
+{
+ value *v ;
+ char *error ;
+
+ if ((error = checkName (currScope,name)) != NULL)
+ return error ;
+
+ v = (value *) calloc (1,sizeof (value)) ;
+ v->name = xstrdup (name) ;
+ v->type = stringval ;
+ v->v.charp_val = xstrdup (val) ;
+
+ addValue (s,v) ;
+
+ return NULL ;
+}
+
+value *findValue (scope *s, const char *name, int inherit)
+{
+ const char *p ;
+
+ if (name == NULL || *name == '\0')
+ return NULL ;
+
+ if (*name == ':')
+ return findValue (topScope,name + 1,0) ;
+ else if (s == NULL)
+ return findValue (topScope,name,0) ;
+ else
+ {
+ int i ;
+
+ if ((p = strchr (name,':')) == NULL)
+ p = name + strlen (name) ;
+
+ for (i = 0 ; i < s->value_idx ; i++)
+ {
+ if (strlen (s->values[i]->name) == (size_t) (p - name) &&
+ strncmp (s->values[i]->name,name,p - name) == 0)
+ {
+ if (*p == '\0') /* last segment of name */
+ return s->values[i] ;
+ else if (s->values[i]->type != scopeval)
+ errbuff = xstrdup ("Component not a scope") ;
+ else
+ return findValue (s->values[i]->v.scope_val,p + 1,0) ;
+ }
+ }
+
+ /* not in this scope. Go up if inheriting values and only if no ':'
+ in name */
+ if (inherit && *p == '\0')
+ return findValue (s->parent,name,inherit) ;
+ }
+
+ return NULL ;
+}
+
+/* find the scope that name belongs to. If mustExist is true then the name
+ must be a fully scoped name of a value. relative scopes start at s. */
+scope *findScope (scope *s, const char *name, int mustExist)
+{
+ scope *p = NULL ;
+ char *q ;
+ int i ;
+
+
+ if ((q = strchr (name,':')) == NULL)
+ {
+ if (!mustExist)
+ p = s ;
+ else
+ for (i = 0 ; p == NULL && i < s->value_idx ; i++)
+ if (strcmp (s->values[i]->name,name) == 0)
+ p = s ;
+
+ return p ;
+ }
+ else if (*name == ':')
+ {
+ while (s->parent != NULL)
+ s = s->parent ;
+
+ return findScope (s,name + 1,mustExist) ;
+ }
+ else
+ {
+ for (i = 0 ; i < s->value_idx ; i++)
+ if (strncmp (s->values[i]->name,name,q - name) == 0)
+ if (s->values[i]->type == scopeval)
+ return findScope (s->values[i]->v.scope_val,q + 1,mustExist) ;
+ }
+
+ return NULL ;
+}
+
+/****************************************************************************/
+/* */
+/****************************************************************************/
+
+
+static void appendName (scope *s, char *p, size_t len)
+{
+ if (s == NULL)
+ return ;
+ else
+ {
+ appendName (s->parent,p,len) ;
+ strlcat (p,s->me->name,len) ;
+ strlcat (p,":",len) ;
+ }
+}
+
+static char *valueScopedName (value *v)
+{
+ scope *p = v->myscope ;
+ int len = strlen (v->name) ;
+ char *q ;
+
+ while (p != NULL)
+ {
+ len += strlen (p->me->name) + 1 ;
+ p = p->parent ;
+ }
+ len++;
+
+ q = xmalloc (len) ;
+ q [0] = '\0' ;
+ appendName (v->myscope,q,len) ;
+ strlcat (q,v->name,len) ;
+
+ return q ;
+}
+
+static void freeValue (value *v)
+{
+ free (v->name) ;
+ switch (v->type)
+ {
+ case scopeval:
+ freeScopeTree (v->v.scope_val) ;
+ break ;
+
+ case stringval:
+ free (v->v.charp_val) ;
+ break ;
+
+ default:
+ break ;
+ }
+ free (v) ;
+}
+
+static char *checkName (scope *s, const char *name)
+{
+ int i ;
+ char *error = NULL ;
+
+ if (s == NULL)
+ return NULL ;
+
+ for (i = 0 ; i < s->value_idx ; i++)
+ {
+ char *n = NULL ;
+
+ if (strcmp (name,s->values [i]->name) == 0) {
+ n = valueScopedName (s->values[i]) ;
+ error = concat ("Two definitions of ", n, (char *) 0) ;
+ free (n) ;
+ return error ;
+ }
+ }
+
+ return error ;
+}
+
+
+static void addValue (scope *s, value *v)
+{
+ v->myscope = s ;
+
+ if (s == NULL)
+ return ;
+
+ if (s->value_count == s->value_idx)
+ {
+ if (s->values == 0)
+ {
+ s->values = (value **) calloc (10,sizeof (value *)) ;
+ s->value_count = 10 ;
+ }
+ else
+ {
+ s->value_count += 10 ;
+ s->values = (value **) realloc (s->values,
+ sizeof (value *) * s->value_count);
+ }
+ }
+
+ s->values [s->value_idx++] = v ;
+}
+
+
+
+static char *addScope (scope *s, const char *name, scope *val)
+{
+ value *v ;
+ char *error ;
+
+ if ((error = checkName (s,name)) != NULL)
+ return error ;
+
+ v = (value *) calloc (1,sizeof (value)) ;
+ v->name = xstrdup (name) ;
+ v->type = scopeval ;
+ v->v.scope_val = val ;
+ val->me = v ;
+ val->parent = s ;
+
+ addValue (s,v) ;
+
+ currScope = val ;
+
+ return NULL ;
+}
+
+
+static void printScope (FILE *fp, scope *s, int indent)
+{
+ int i ;
+ for (i = 0 ; i < s->value_idx ; i++)
+ printValue (fp,s->values [i],indent + 5) ;
+}
+
+static void printValue (FILE *fp, value *v, int indent)
+{
+ int i ;
+
+ for (i = 0 ; i < indent ; i++)
+ fputc (' ',fp) ;
+
+ switch (v->type)
+ {
+ case intval:
+ fprintf (fp,"%s : %ld # INTEGER\n",v->name,v->v.int_val) ;
+ break ;
+
+ case stringval:
+ fprintf (fp,"%s : \"",v->name) ;
+ {
+ char *p = v->v.charp_val ;
+ while (*p)
+ {
+ if (*p == '"' || *p == '\\')
+ fputc ('\\',fp) ;
+ fputc (*p,fp) ;
+ p++ ;
+ }
+ }
+ fprintf (fp,"\" # STRING\n") ;
+ break ;
+
+ case charval:
+ fprintf (fp,"%s : %c",v->name,047) ;
+ switch (v->v.char_val)
+ {
+ case '\\':
+ fprintf (fp,"\\\\") ;
+ break ;
+
+ default:
+ if (CTYPE (isprint, v->v.char_val))
+ fprintf (fp,"%c",v->v.char_val) ;
+ else
+ fprintf (fp,"\\%03o",v->v.char_val) ;
+ }
+ fprintf (fp,"%c # CHARACTER\n",047) ;
+ break ;
+
+ case realval:
+ fprintf (fp,"%s : %f # REAL\n",v->name,v->v.real_val) ;
+ break ;
+
+ case boolval:
+ fprintf (fp,"%s : %s # BOOLEAN\n",
+ v->name,(v->v.bool_val ? "true" : "false")) ;
+ break ;
+
+ case scopeval:
+ fprintf (fp,"%s %s { # SCOPE\n",v->v.scope_val->scope_type,v->name) ;
+ printScope (fp,v->v.scope_val,indent + 5) ;
+ for (i = 0 ; i < indent ; i++)
+ fputc (' ',fp) ;
+ fprintf (fp,"}\n") ;
+ break ;
+
+ default:
+ fprintf (fp,"UNKNOWN value type: %d\n",v->type) ;
+ exit (1) ;
+ }
+}
+
+
+
+static scope *newScope (const char *type)
+{
+ scope *t ;
+ int i ;
+
+ t = (scope *) calloc (1,sizeof (scope)) ;
+ t->parent = NULL ;
+ t->scope_type = xstrdup (type) ;
+
+ for (i = 0 ; t->scope_type[i] != '\0' ; i++)
+ t->scope_type[i] = tolower (t->scope_type[i]) ;
+
+ return t ;
+}
+
+
+
+#if 0
+static int strNCaseCmp (const char *a, const char *b, size_t len)
+{
+ while (a && b && *a && *b && (tolower (*a) == tolower (*b)) && len > 0)
+ a++, b++, len-- ;
+
+ if (a == NULL && b == NULL)
+ return 0 ;
+ else if (a == NULL)
+ return 1 ;
+ else if (b == NULL)
+ return -1 ;
+ else if (*a == '\0' && *b == '\0')
+ return 0 ;
+ else if (*a == '\0')
+ return 1 ;
+ else if (*b == '\0')
+ return -1 ;
+ else if (*a < *b)
+ return 1 ;
+ else if (*a > *b)
+ return -1 ;
+ else
+ return 0 ;
+
+ abort () ;
+}
+#endif
+
+#define BAD_KEY "line %d: illegal key name: %s"
+#define NON_ALPHA "line %d: keys must start with a letter: %s"
+
+static char *keyOk (const char *key)
+{
+ const char *p = key ;
+ char *rval ;
+
+ if (key == NULL)
+ {
+ rval = xmalloc (strlen ("line : NULL key") + 15) ;
+ sprintf (rval,"line %d: NULL key", lineCount) ;
+ return rval ;
+ }
+ else if (*key == '\0')
+ {
+ rval = xmalloc (strlen ("line : EMPTY KEY") + 15) ;
+ sprintf (rval,"line %d: EMPTY KEY", lineCount) ;
+ return rval ;
+ }
+
+ if (!CTYPE(isalpha, *p))
+ {
+ rval = xmalloc (strlen (NON_ALPHA) + strlen (key) + 15) ;
+ sprintf (rval,NON_ALPHA,lineCount, key) ;
+ return rval ;
+ }
+
+ p++ ;
+ while (*p)
+ {
+ if (!(CTYPE (isalnum, *p) || *p == '_' || *p == '-'))
+ {
+ rval = xmalloc (strlen (BAD_KEY) + strlen (key) + 15) ;
+ sprintf (rval,BAD_KEY,lineCount,key) ;
+ return rval ;
+ }
+ p++ ;
+ }
+
+ return NULL ;
+}
+
+static PFIVP *funcs = NULL ;
+static void **args = NULL ;
+static int funcCount ;
+static int funcIdx ;
+
+void configAddLoadCallback (PFIVP func,void *arg)
+{
+ if (func == NULL)
+ return ;
+
+ if (funcIdx == funcCount)
+ {
+ funcCount += 10 ;
+ if (funcs == NULL)
+ {
+ funcs = xmalloc (sizeof (PFIVP) * funcCount);
+ args = xmalloc (sizeof (void *) * funcCount) ;
+ }
+ else
+ {
+ funcs = xrealloc (funcs,sizeof (PFIVP) * funcCount);
+ args = xrealloc (args,sizeof (void *) * funcCount) ;
+ }
+ }
+
+ args [funcIdx] = arg ;
+ funcs [funcIdx++] = func ;
+
+}
+
+
+void configRemoveLoadCallback (PFIVP func)
+{
+ int i, j ;
+
+ for (i = 0 ; i < funcIdx ; i++)
+ if (funcs [i] == func)
+ break ;
+
+ for (j = i ; j < funcIdx - 1 ; j++)
+ {
+ funcs [j] = funcs [j + 1] ;
+ args [j] = args [j + 1] ;
+ }
+
+ if (funcIdx > 1 && i < funcIdx)
+ {
+ funcs [i - 2] = funcs [i - 1] ;
+ args [i - 2] = args [i - 1] ;
+ }
+
+ if (funcIdx > 0 && i < funcIdx)
+ funcIdx-- ;
+}
+
+
+static int doCallbacks (void)
+{
+ int i ;
+ int rval = 1 ;
+
+ for (i = 0 ; i < funcIdx ; i++)
+ if (funcs [i] != NULL)
+ rval = (funcs[i](args [i]) && rval) ;
+
+ return rval ;
+}
+
+
+
+
+
+static char *key ;
+%}
+
+%union{
+ scope *scp ;
+ value *val ;
+ char *name ;
+ int integer ;
+ double real ;
+ char *string ;
+ char chr ;
+}
+
+%token PEER
+%token GROUP
+%token IVAL
+%token RVAL
+%token NAME
+%token XSTRING
+%token SCOPE
+%token COLON
+%token LBRACE
+%token RBRACE
+%token TRUEBVAL
+%token FALSEBVAL
+%token CHAR
+%token WORD
+%token IP_ADDRESS
+
+%type <integer> IVAL
+%type <real> RVAL
+%type <string> XSTRING
+%type <chr> CHAR
+%type <name> TRUEBVAL FALSEBVAL WORD
+
+%%
+input: {
+ lineCount = 1 ;
+ addScope (NULL,"",newScope ("")) ;
+ topScope = currScope ;
+ } entries { if (!doCallbacks()) YYABORT ; } ;
+
+scope: entries ;
+
+entries:
+ | entries entry
+ | entries error {
+ errbuff = xmalloc (strlen(SYNTAX_ERROR) + 12) ;
+ sprintf (errbuff,SYNTAX_ERROR,lineCount) ;
+ YYABORT ;
+ }
+ ;
+
+entry: PEER WORD LBRACE {
+ errbuff = addScope (currScope,$2,newScope ("peer")) ;
+ free ($2) ;
+ if (errbuff != NULL) YYABORT ;
+ } scope RBRACE {
+ currScope = currScope->parent ;
+ }
+ | GROUP WORD LBRACE {
+ errbuff = addScope (currScope,$2,newScope ("group")) ;
+ free ($2) ;
+ if (errbuff != NULL) YYABORT ;
+ } scope RBRACE {
+ currScope = currScope->parent ;
+ }
+ | WORD WORD LBRACE {
+ errbuff = xmalloc (strlen(UNKNOWN_SCOPE_TYPE) + 15 +
+ strlen ($1)) ;
+ sprintf (errbuff,UNKNOWN_SCOPE_TYPE,lineCount,$1) ;
+ free ($1) ;
+ free ($2) ;
+ YYABORT ;
+ }
+ | WORD {
+ if ((errbuff = keyOk($1)) != NULL) {
+ YYABORT ;
+ } else
+ key = $1 ;
+ } COLON value ;
+
+value: WORD {
+ if ((errbuff = addString (currScope, key, $1)) != NULL)
+ YYABORT ;
+ free (key) ;
+ free ($1) ;
+ }
+ | IVAL {
+ if ((errbuff = addInteger(currScope, key, $1)) != NULL)
+ YYABORT;
+ free (key) ;
+ }
+ | TRUEBVAL {
+ if ((errbuff = addBoolean (currScope, key, 1)) != NULL)
+ YYABORT ;
+ free (key) ;
+ free ($1) ;
+ }
+ | FALSEBVAL {
+ if ((errbuff = addBoolean (currScope, key, 0)) != NULL)
+ YYABORT ;
+ free (key) ;
+ free ($1) ;
+ }
+ | RVAL {
+ if ((errbuff = addReal (currScope, key, $1)) != NULL)
+ YYABORT ;
+ free (key) ;
+ }
+ | XSTRING {
+ if ((errbuff = addString (currScope, key, $1)) != NULL)
+ YYABORT;
+ free (key) ;
+ }
+ | CHAR {
+ if ((errbuff = addChar (currScope, key, $1)) != NULL)
+ YYABORT ;
+ free (key) ;
+ }
+;
+
+%%
+
+int yyerror (const char *s)
+{
+#undef FMT
+#define FMT "line %d: %s"
+
+ errbuff = xmalloc (strlen (s) + strlen (FMT) + 20) ;
+ sprintf (errbuff,FMT,lineCount,s) ;
+
+ return 0 ;
+}
+
+int yywrap (void)
+{
+ return 1 ;
+}
+
+extern FILE *yyin ;
+int yydebug ;
+
+#define NO_INHERIT 0
+
+
+#if ! defined (WANT_MAIN)
+
+struct peer_table_s
+{
+ char *peerName ;
+ value *peerValue ;
+} ;
+
+static struct peer_table_s *peerTable ;
+static int peerTableCount ;
+static int peerTableIdx ;
+
+void configCleanup (void)
+{
+ int i ;
+
+ for (i = 0 ; i < peerTableIdx ; i++)
+ free (peerTable[i].peerName) ;
+ free (peerTable) ;
+
+ freeScopeTree (topScope);
+ free (funcs) ;
+ free (args) ;
+}
+
+
+int buildPeerTable (FILE *fp, scope *s)
+{
+ int rval = 1 ;
+ int i, j ;
+
+ for (i = 0 ; i < s->value_idx ; i++)
+ {
+ if (ISSCOPE (s->values[i]) && ISPEER (s->values[i]))
+ {
+ for (j = 0 ; j < peerTableIdx ; j++)
+ {
+ if (strcmp (peerTable[j].peerName,s->values[i]->name) == 0)
+ {
+ logOrPrint (LOG_ERR,fp,
+ "ME config: two peers with the same name: %s",
+ peerTable[j].peerName) ;
+ rval = 0 ;
+ break ;
+ }
+ }
+
+ if (j == peerTableIdx)
+ {
+ if (peerTableCount == peerTableIdx)
+ {
+ peerTableCount += 10 ;
+ if (peerTable == NULL)
+ peerTable = xmalloc (sizeof(struct peer_table_s)
+ * peerTableCount) ;
+ else
+ peerTable = xrealloc (peerTable,
+ sizeof(struct peer_table_s)
+ * peerTableCount) ;
+ }
+
+ peerTable[peerTableIdx].peerName = xstrdup (s->values[i]->name);
+ peerTable[peerTableIdx].peerValue = s->values[i] ;
+ peerTableIdx++ ;
+ }
+ }
+ else if (ISSCOPE (s->values[i]))
+ rval = (buildPeerTable (fp,s->values[i]->v.scope_val) && rval) ;
+ }
+
+ return rval ;
+}
+
+
+/* read the config file. Any errors go to errorDest if it is non-NULL,
+ otherwise they are syslogged. If justCheck is true then return after
+ parsing */
+static int inited = 0 ;
+int readConfig (const char *file, FILE *errorDest, int justCheck, int dump)
+{
+ scope *oldTop = topScope ;
+ FILE *fp ;
+ int rval ;
+
+ if (!inited)
+ {
+ inited = 1 ;
+ yydebug = (getenv ("YYDEBUG") == NULL ? 0 : 1) ;
+ if (yydebug)
+ atexit (configCleanup) ;
+ }
+
+ if (file == NULL || strlen (file) == 0 || !fileExistsP (file))
+ {
+ logOrPrint (LOG_ERR,errorDest,
+ "ME config aborting, no such config file: %s",
+ file ? file : "(null)") ;
+ d_printf (1,"No such config file: %s\n", file ? file : "(null)") ;
+ exit (1) ;
+ }
+
+ if ((fp = fopen (file,"r")) == NULL)
+ {
+ logOrPrint (LOG_ERR,errorDest, "ME config aborting fopen %s: %s",
+ file, strerror (errno)) ;
+ exit (1) ;
+ }
+
+ logOrPrint (LOG_NOTICE,errorDest,"loading %s", file) ;
+
+ yyin = fp ;
+
+ topScope = NULL ;
+
+ rval = yyparse () ;
+
+ fclose (fp) ;
+
+ if (rval != 0) /* failure */
+ {
+ freeScopeTree (topScope) ;
+ if (justCheck)
+ freeScopeTree (oldTop) ;
+ else
+ topScope = oldTop ;
+ topScope = NULL ;
+
+ if (errbuff != NULL)
+ {
+ if (errorDest != NULL)
+ fprintf (errorDest,"config file error: %s\n",errbuff) ;
+ else
+ warn ("ME config file error: %s", errbuff) ;
+
+ free (errbuff) ;
+ }
+
+ return 0 ;
+ }
+
+ if (dump)
+ {
+ fprintf (errorDest ? errorDest : stderr,"Parsed config file:\n") ;
+ printScope (errorDest ? errorDest : stderr,topScope,-5) ;
+ fprintf (errorDest ? errorDest : stderr,"\n") ;
+ }
+
+ if (justCheck)
+ {
+ freeScopeTree (topScope) ;
+ freeScopeTree (oldTop) ;
+
+ topScope = NULL ;
+ }
+ else
+ {
+ for (peerTableIdx-- ; peerTableIdx >= 0 ; peerTableIdx--)
+ {
+ free (peerTable [peerTableIdx].peerName) ;
+ peerTable [peerTableIdx].peerName = NULL ;
+ peerTable [peerTableIdx].peerValue = NULL ;
+ }
+ peerTableIdx = 0 ;
+
+ if (!buildPeerTable (errorDest,topScope))
+ logAndExit (1,"Failed to build list of peers") ;
+ }
+
+ return 1 ;
+}
+
+
+value *getNextPeer (int *cookie)
+{
+ value *rval ;
+
+ if (*cookie < 0 || *cookie >= peerTableIdx)
+ return NULL ;
+
+ rval = peerTable[*cookie].peerValue ;
+
+ (*cookie)++ ;
+
+ return rval ;
+}
+
+
+value *findPeer (const char *name)
+{
+ value *v = NULL ;
+ int i ;
+
+ for (i = 0 ; i < peerTableIdx ; i++)
+ if (strcmp (peerTable[i].peerName,name) == 0)
+ {
+ v = peerTable[i].peerValue ;
+ break ;
+ }
+
+ return v ;
+}
+
+#endif
+
+#if defined (WANT_MAIN)
+int main (int argc, char **argv) {
+ if ( yyparse() )
+ printf ("parsing failed: %s\n",errbuff ? errbuff : "NONE") ;
+ else
+ {
+ printScope (stdout,topScope,-5) ;
+
+ if (argc == 3)
+ {
+#if 0
+ printf ("Looking for %s of type %s: ",argv[2],argv[1]) ;
+ if (strncmp (argv[1],"int",3) == 0)
+ {
+ int i = 0 ;
+
+ if (!getInteger (topScope,argv[2],&i))
+ printf ("wasn't found.\n") ;
+ else
+ printf (" %d\n",i) ;
+ }
+ else if (strncmp (argv[1],"real",4) == 0)
+ {
+ double d = 0.0 ;
+
+ if (!getReal (topScope,argv[2],&d))
+ printf ("wasn't found.\n") ;
+ else
+ printf (" %0.5f\n",d) ;
+ }
+#else
+ value *v = findValue (topScope,argv[1],1) ;
+
+ if (v == NULL)
+ printf ("Can't find %s\n",argv[1]) ;
+ else
+ {
+ long ival = 987654 ;
+
+ if (getInteger (v->v.scope_val,argv[2],&ival,1))
+ printf ("Getting %s : %ld",argv[2],ival) ;
+ else
+ printf ("Name is not legal: %s\n",argv[2]) ;
+ }
+#endif
+ }
+ else if (argc == 2)
+ {
+#if 1
+ value *v = findValue (topScope,argv[1],1) ;
+
+ if (v == NULL)
+ printf ("Can't find %s\n",argv[1]) ;
+ else
+ {
+ printf ("Getting %s : ",argv[1]) ;
+ printValue (stdout,v,0) ;
+ }
+#else
+ if (findScope (topScope,argv[1],1) == NULL)
+ printf ("Can't find the scope of %s\n",argv[1]) ;
+#endif
+ }
+ }
+
+ freeScopeTree (topScope) ;
+
+ return 0 ;
+}
+#endif /* defined (WANT_MAIN) */
--- /dev/null
+/* $Id: connection.c 7793 2008-04-26 08:15:40Z iulius $
+**
+** The implementation of the innfeed Connection class.
+**
+** Written by James Brister <brister@vix.com>
+**
+** The Connection object is what manages the NNTP protocol. If the remote
+** doesn't do streaming, then the standard IHAVE lock-step protcol is
+** performed. In the streaming situation we have two cases. One where we must
+** send CHECK commands, and the other where we can directly send TAKETHIS
+** commands without a prior CHECK.
+**
+** The Connection object maintains four article queues. The first one is
+** where new articles are put if they need to have an IHAVE or CHECK command
+** sent for them. The second queue is where the articles move from the first
+** after their IHAVE/CHECK command is sent, but the reply has not yet been
+** seen. The third queue is where articles go after the IHAVE/CHECK reply has
+** been seen (and the reply says to send the article). It is articles in the
+** third queue that have the TAKETHIS command sent, or the body of an IHAVE.
+** The third queue is also where new articles go if the connection is running
+** in no-CHECK mode. The fourth queue is where the articles move to from the
+** third queue after their IHAVE-body or TAKETHIS command has been sent. When
+** the response to the IHAVE-body or TAKETHIS is received the articles are
+** removed from the fourth queue and the Host object controlling this
+** Connection is notified of the success or failure of the transfer.
+**
+** The whole system is event-driven by the EndPoint class and the Host via
+** calls to prepareRead() and prepareWrite() and prepareSleep().
+**
+**
+** We should probably store the results of gethostbyname in the connection so
+** we can rotate through the address when one fails for connecting. Perhaps
+** the gethostbyname should be done in the Host and the connection should
+** just be given the address to use.
+**
+** Should we worry about articles being stuck on a queue for ever if the
+** remote forgets to send a response to a CHECK?
+**
+** Perhaps instead of killing the connection on some of the more simple
+** errors, we should perhaps try to flush the input and keep going.
+**
+** Worry about counter overflow.
+**
+** Worry about stats gathering when switch to no-check mode.
+**
+** XXX if issueQUIT() has a problem and the state goes to cxnDeadS this is
+** not handled properly everywhere yet.
+*/
+
+#include "innfeed.h"
+#include "config.h"
+#include "clibrary.h"
+#include "portable/socket.h"
+#include "portable/time.h"
+
+#include <assert.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <netdb.h>
+#include <signal.h>
+#include <syslog.h>
+
+#if defined (__FreeBSD__)
+# include <sys/ioctl.h>
+#endif
+
+#include "inn/messages.h"
+#include "libinn.h"
+
+#include "article.h"
+#include "buffer.h"
+#include "configfile.h"
+#include "connection.h"
+#include "endpoint.h"
+#include "host.h"
+
+#if defined (NDEBUG)
+#define VALIDATE_CONNECTION(x) ((void) 0)
+#else
+#define VALIDATE_CONNECTION(x) validateConnection (x)
+#endif
+
+extern char **PointersFreedOnExit ;
+extern const char *pidFile ;
+
+/*
+ * Private types.
+ */
+
+/* We keep a linked list of articles the connection is trying to transmit */
+typedef struct art_holder_s
+{
+ Article article ;
+ struct art_holder_s *next ;
+} *ArtHolder ;
+
+
+typedef enum {
+ cxnStartingS, /* the connection's start state. */
+ cxnWaitingS, /* not connected. Waiting for an article. */
+ cxnConnectingS, /* in the middle of connecting */
+ cxnIdleS, /* open and ready to feed, has empty queues */
+ cxnIdleTimeoutS, /* timed out in the idle state */
+ cxnFeedingS, /* in the processes of feeding articles */
+ cxnSleepingS, /* blocked on reestablishment timer */
+ cxnFlushingS, /* am waiting for queues to drain to bounce connection. */
+ cxnClosingS, /* have been told to close down permanently when queues drained */
+ cxnDeadS /* connection is dead. */
+} CxnState ;
+
+/* The Connection class */
+struct connection_s
+{
+ Host myHost ; /* the host who owns the connection */
+ EndPoint myEp ; /* the endpoint the connection talks through */
+ unsigned int ident ; /* an identifier for syslogging. */
+ CxnState state ; /* the state the connection is in */
+
+
+ /*
+ * The Connection maintains 4 queue of articles.
+ */
+ ArtHolder checkHead ; /* head of article list to do CHECK/IHAVE */
+ ArtHolder checkRespHead ; /* head of list waiting on CHECK/IHAVE
+ response */
+ ArtHolder takeHead ; /* head of list of articles to send
+ TAKETHIS/IHAVE-body */
+ ArtHolder takeRespHead ; /* list of articles waiting on
+ TAKETHIS/IHAVE-body response */
+ unsigned int articleQTotal ; /* number of articles in all four queues */
+ ArtHolder missing ; /* head of missing list */
+
+
+ Buffer respBuffer ; /* buffer all responses are read into */
+
+ char *ipName ; /* the ip name (possibly quad) of the remote */
+
+ unsigned int maxCheck ; /* the max number of CHECKs to send */
+ unsigned short port ; /* the port number to use */
+
+ /*
+ * Timeout values and their callback IDs
+ */
+
+ /* Timer for max amount of time between receiving articles from the
+ Host */
+ unsigned int articleReceiptTimeout ;
+ TimeoutId artReceiptTimerId ;
+
+ /* Timer for the max amount of time to wait for a response from the
+ remote */
+ unsigned int readTimeout ;
+ TimeoutId readBlockedTimerId ;
+
+ /* Timer for the max amount of time to wait for a any amount of data
+ to be written to the remote */
+ unsigned int writeTimeout ;
+ TimeoutId writeBlockedTimerId ;
+
+ /* Timer for the max number of seconds to keep the network connection
+ up (long lasting connections give older nntp servers problems). */
+ unsigned int flushTimeout ;
+ TimeoutId flushTimerId ;
+
+ /* Timer for the number of seconds to sleep before attempting a
+ reconnect. */
+ unsigned int sleepTimeout ;
+ TimeoutId sleepTimerId ;
+
+
+ bool loggedNoCr ; /* true if we logged the NOCR_MSG */
+ bool immedRecon ; /* true if we recon immediately after flushing. */
+ bool doesStreaming ; /* true if remote will handle streaming */
+ bool authenticated ; /* true if remote authenticated */
+ bool quitWasIssued ; /* true if QUIT command was sent. */
+ bool needsChecks ; /* true if we issue CHECK commands in
+ streaming mode (rather than just sending
+ TAKETHIS commands) */
+
+ time_t timeCon ; /* the time the connect happened (including
+ the MODE STREAM command). */
+
+ /*
+ * STATISTICS
+ */
+ unsigned int artsTaken ; /* the number of articles INN gave this cxn */
+ unsigned int checksIssued ; /* the number of CHECKS/IHAVES we
+ sent. Note that if we're running in
+ no-CHECK mode, then we add in the
+ TAKETHIS commands too */
+ unsigned int checksRefused ; /* the number of response 435/438 */
+ unsigned int takesRejected ; /* the number of response 437/439 recevied */
+ unsigned int takesOkayed ; /* the number of response 235/239 received */
+
+ double takesSizeRejected ;
+ double takesSizeOkayed ;
+
+ double onThreshold ; /* for no-CHECK mode */
+ double offThreshold ; /* for no-CHECK mode */
+ double filterValue ; /* current value of IIR filter */
+ double lowPassFilter ; /* time constant for IIR filter */
+
+ Connection next ; /* for global list. */
+};
+
+static Connection gCxnList = NULL ;
+static unsigned int gCxnCount = 0 ;
+static unsigned int max_reconnect_period = MAX_RECON_PER ;
+static unsigned int init_reconnect_period = INIT_RECON_PER ;
+#if 0
+static bool inited = false ;
+#endif
+static Buffer dotFirstBuffer ;
+static Buffer dotBuffer ;
+static Buffer crlfBuffer ;
+
+
+/***************************************************
+ *
+ * Private function declarations.
+ *
+ ***************************************************/
+
+
+/* I/O Callbacks */
+static void connectionDone (EndPoint e, IoStatus i, Buffer *b, void *d) ;
+static void getBanner (EndPoint e, IoStatus i, Buffer *b, void *d) ;
+static void getAuthUserResponse (EndPoint e, IoStatus i, Buffer *b, void *d) ;
+static void getAuthPassResponse (EndPoint e, IoStatus i, Buffer *b, void *d) ;
+static void getModeResponse (EndPoint e, IoStatus i, Buffer *b, void *d) ;
+static void responseIsRead (EndPoint e, IoStatus i, Buffer *b, void *d) ;
+static void quitWritten (EndPoint e, IoStatus i, Buffer *b, void *d) ;
+static void ihaveBodyDone (EndPoint e, IoStatus i, Buffer *b, void *d) ;
+static void commandWriteDone (EndPoint e, IoStatus i, Buffer *b, void *d) ;
+static void modeCmdIssued (EndPoint e, IoStatus i, Buffer *b, void *d) ;
+static void authUserIssued (EndPoint e, IoStatus i, Buffer *b, void *d) ;
+static void authPassIssued (EndPoint e, IoStatus i, Buffer *b, void *d) ;
+static void writeProgress (EndPoint e, IoStatus i, Buffer *b, void *d) ;
+
+
+/* Timer callbacks */
+static void responseTimeoutCbk (TimeoutId id, void *data) ;
+static void writeTimeoutCbk (TimeoutId id, void *data) ;
+static void reopenTimeoutCbk (TimeoutId id, void *data) ;
+static void flushCxnCbk (TimeoutId, void *data) ;
+static void articleTimeoutCbk (TimeoutId id, void *data) ;
+
+/* Work callbacks */
+static void cxnWorkProc (EndPoint ep, void *data) ;
+
+
+static void cxnSleepOrDie (Connection cxn) ;
+
+/* Response processing. */
+static void processResponse205 (Connection cxn, char *response) ;
+static void processResponse238 (Connection cxn, char *response) ;
+static void processResponse431 (Connection cxn, char *response) ;
+static void processResponse438 (Connection cxn, char *response) ;
+static void processResponse239 (Connection cxn, char *response) ;
+static void processResponse439 (Connection cxn, char *response) ;
+static void processResponse235 (Connection cxn, char *response) ;
+static void processResponse335 (Connection cxn, char *response) ;
+static void processResponse400 (Connection cxn, char *response) ;
+static void processResponse435 (Connection cxn, char *response) ;
+static void processResponse436 (Connection cxn, char *response) ;
+static void processResponse437 (Connection cxn, char *response) ;
+static void processResponse480 (Connection cxn, char *response) ;
+static void processResponse503 (Connection cxn, char *response) ;
+
+
+/* Misc functions */
+static void cxnSleep (Connection cxn) ;
+static void cxnDead (Connection cxn) ;
+static void cxnIdle (Connection cxn) ;
+static void noSuchMessageId (Connection cxn, unsigned int responseCode,
+ const char *msgid, const char *response) ;
+static void abortConnection (Connection cxn) ;
+static void resetConnection (Connection cxn) ;
+static void deferAllArticles (Connection cxn) ;
+static void deferQueuedArticles (Connection cxn) ;
+static void doSomeWrites (Connection cxn) ;
+static bool issueIHAVE (Connection cxn) ;
+static void issueIHAVEBody (Connection cxn) ;
+static bool issueStreamingCommands (Connection cxn) ;
+static Buffer buildCheckBuffer (Connection cxn) ;
+static Buffer *buildTakethisBuffers (Connection cxn, Buffer checkBuffer) ;
+static void issueQUIT (Connection cxn) ;
+static void initReadBlockedTimeout (Connection cxn) ;
+static int prepareWriteWithTimeout (EndPoint endp, Buffer *buffers,
+ EndpRWCB done, Connection cxn) ;
+static void delConnection (Connection cxn) ;
+static void incrFilter (Connection cxn) ;
+static void decrFilter (Connection cxn) ;
+static bool writesNeeded (Connection cxn) ;
+static void validateConnection (Connection cxn) ;
+static const char *stateToString (CxnState state) ;
+
+static void issueModeStream (EndPoint e, Connection cxn) ;
+static void issueAuthUser (EndPoint e, Connection cxn) ;
+static void issueAuthPass (EndPoint e, Connection cxn) ;
+
+static void prepareReopenCbk (Connection cxn) ;
+
+
+/* Article queue management routines. */
+static ArtHolder newArtHolder (Article art) ;
+static void delArtHolder (ArtHolder artH) ;
+static bool remArtHolder (ArtHolder art, ArtHolder *head, unsigned int *count) ;
+static void appendArtHolder (ArtHolder artH, ArtHolder *head, unsigned int *count) ;
+static ArtHolder artHolderByMsgId (const char *msgid, ArtHolder head) ;
+
+static int fudgeFactor (int initVal) ;
+
+
+
+
+/***************************************************
+ *
+ * Public functions implementation.
+ *
+ ***************************************************/
+
+
+int cxnConfigLoadCbk (void *data UNUSED)
+{
+ long iv ;
+ int rval = 1 ;
+ FILE *fp = (FILE *) data ;
+
+ if (getInteger (topScope,"max-reconnect-time",&iv,NO_INHERIT))
+ {
+ if (iv < 1)
+ {
+ rval = 0 ;
+ logOrPrint (LOG_ERR,fp,
+ "ME config: value of %s (%ld) in %s cannot be less"
+ " than 1. Using %ld", "max-reconnect-time",
+ iv,"global scope",(long) MAX_RECON_PER);
+ iv = MAX_RECON_PER ;
+ }
+ }
+ else
+ iv = MAX_RECON_PER ;
+ max_reconnect_period = (unsigned int) iv ;
+
+ if (getInteger (topScope,"initial-reconnect-time",&iv,NO_INHERIT))
+ {
+ if (iv < 1)
+ {
+ rval = 0 ;
+ logOrPrint (LOG_ERR,fp,
+ "ME config: value of %s (%ld) in %s cannot be less"
+ " than 1. Using %ld", "initial-reconnect-time",
+ iv,"global scope",(long)INIT_RECON_PER);
+ iv = INIT_RECON_PER ;
+ }
+ }
+ else
+ iv = INIT_RECON_PER ;
+ init_reconnect_period = (unsigned int) iv ;
+
+ return rval ;
+}
+
+
+
+
+\f
+/*
+ * Create a new Connection object and return it. All fields are
+ * initialized to reasonable values.
+ */
+Connection newConnection (Host host,
+ unsigned int id,
+ const char *ipname,
+ unsigned int articleReceiptTimeout,
+ unsigned int portNum,
+ unsigned int respTimeout,
+ unsigned int flushTimeout,
+ double lowPassLow,
+ double lowPassHigh,
+ double lowPassFilter)
+{
+ Connection cxn ;
+ bool croak = false ;
+
+ if (ipname == NULL)
+ {
+ d_printf (1,"NULL ipname in newConnection\n") ;
+ croak = true ;
+ }
+
+ if (ipname && strlen (ipname) == 0)
+ {
+ d_printf (1,"Empty ipname in newConnection\n") ;
+ croak = true ;
+ }
+
+ if (croak)
+ return NULL ;
+
+ cxn = xcalloc (1, sizeof(struct connection_s));
+
+ cxn->myHost = host ;
+ cxn->myEp = NULL ;
+ cxn->ident = id ;
+
+ cxn->checkHead = NULL ;
+ cxn->checkRespHead = NULL ;
+ cxn->takeHead = NULL ;
+ cxn->takeRespHead = NULL ;
+
+ cxn->articleQTotal = 0 ;
+ cxn->missing = NULL ;
+
+ cxn->respBuffer = newBuffer (BUFFER_SIZE) ;
+ ASSERT (cxn->respBuffer != NULL) ;
+
+ cxn->ipName = xstrdup (ipname) ;
+ cxn->port = portNum ;
+
+ /* Time out the higher numbered connections faster */
+ cxn->articleReceiptTimeout = articleReceiptTimeout * 10.0 / (10.0 + id) ;
+ cxn->artReceiptTimerId = 0 ;
+
+ cxn->readTimeout = respTimeout ;
+ cxn->readBlockedTimerId = 0 ;
+
+ cxn->writeTimeout = respTimeout ; /* XXX should be a separate value */
+ cxn->writeBlockedTimerId = 0 ;
+
+ cxn->flushTimeout = fudgeFactor (flushTimeout) ;
+ cxn->flushTimerId = 0 ;
+
+ cxn->onThreshold = lowPassHigh * lowPassFilter / 100.0 ;
+ cxn->offThreshold = lowPassLow * lowPassFilter / 100.0 ;
+ cxn->lowPassFilter = lowPassFilter;
+
+ cxn->sleepTimerId = 0 ;
+ cxn->sleepTimeout = init_reconnect_period ;
+
+ resetConnection (cxn) ;
+
+ cxn->next = gCxnList ;
+ gCxnList = cxn ;
+ gCxnCount++ ;
+
+ cxn->state = cxnStartingS ;
+
+ return cxn ;
+}
+
+
+
+
+\f
+/* Create a new endpoint hooked to a non-blocking socket that is trying to
+ * connect to the host info stored in the Connection. On fast machines
+ * connecting locally the connect() may have already succeeded when this
+ * returns, but typically the connect will still be running and when it
+ * completes. The Connection will be notified via a write callback setup by
+ * prepareWrite below. If nothing goes wrong then this will return true
+ * (even if the connect() has not yet completed). If something fails
+ * (hostname lookup etc.) then it returns false (and the Connection is left
+ * in the sleeping state)..
+ *
+ * Pre-state Reason cxnConnect called
+ * --------- ------------------------
+ * cxnStartingS Connection owner issued call.
+ * cxnWaitingS side effect of cxnTakeArticle() call
+ * cxnConnecting side effect of cxnFlush() call
+ * cxnSleepingS side effect of reopenTimeoutCbk() call.
+ */
+bool cxnConnect (Connection cxn)
+{
+ const struct sockaddr_storage cxnAddr, cxnSelf ;
+ const struct sockaddr *retAddr;
+ int fd, rval ;
+ const char *peerName = hostPeerName (cxn->myHost) ;
+ char msgbuf[100];
+ const struct sockaddr_in *bind_addr = hostBindAddr (cxn->myHost) ;
+ int family = 0;
+#ifdef HAVE_INET6
+ char paddr[INET6_ADDRSTRLEN];
+ const struct sockaddr_in6 *bind_addr6 = hostBindAddr6 (cxn->myHost) ;
+#endif
+
+ ASSERT (cxn->myEp == NULL) ;
+
+ if (!(cxn->state == cxnStartingS ||
+ cxn->state == cxnWaitingS ||
+ cxn->state == cxnFlushingS ||
+ cxn->state == cxnSleepingS))
+ {
+ warn ("%s:%d cxnsleep connection in bad state: %s",
+ hostPeerName (cxn->myHost), cxn->ident,
+ stateToString (cxn->state)) ;
+ cxnSleepOrDie (cxn) ;
+ return false;
+ }
+
+ if (cxn->state == cxnWaitingS)
+ ASSERT (cxn->articleQTotal == 1) ;
+ else
+ ASSERT (cxn->articleQTotal == 0) ;
+
+ cxn->state = cxnConnectingS ;
+
+#ifdef HAVE_INET6
+ family = hostAddrFamily (cxn->myHost);
+#endif
+ retAddr = hostIpAddr (cxn->myHost, family) ;
+
+ if (retAddr == NULL)
+ {
+ cxnSleepOrDie (cxn) ;
+ return false ;
+ }
+
+ memcpy( (void *)&cxnAddr, retAddr, SA_LEN(retAddr) );
+
+#ifdef HAVE_INET6
+ if( cxnAddr.ss_family == AF_INET6 )
+ {
+ ((struct sockaddr_in6 *)&cxnAddr)->sin6_port = htons(cxn->port) ;
+ fd = socket (PF_INET6, SOCK_STREAM, 0);
+ }
+ else
+#endif
+ {
+ ((struct sockaddr_in *)&cxnAddr)->sin_port = htons(cxn->port) ;
+ fd = socket (PF_INET, SOCK_STREAM, 0);
+ }
+ if (fd < 0)
+ {
+ syswarn ("%s:%d cxnsleep can't create socket", peerName, cxn->ident) ;
+ d_printf (1,"Can't get a socket: %s\n", strerror (errno)) ;
+
+ cxnSleepOrDie (cxn) ;
+
+ return false ;
+ }
+
+#ifdef HAVE_INET6
+ /* bind to a specified IPv6 address */
+ if( (cxnAddr.ss_family == AF_INET6) && bind_addr6 )
+ {
+ memcpy( (void *)&cxnSelf, bind_addr6, sizeof(struct sockaddr_in6) );
+ if (bind (fd, (struct sockaddr *) &cxnSelf,
+ sizeof(struct sockaddr_in6)) < 0)
+ {
+ snprintf(msgbuf, sizeof(msgbuf), "bind (%s): %%m",
+ inet_ntop(AF_INET6, bind_addr6->sin6_addr.s6_addr,
+ paddr, sizeof(paddr)) );
+
+ syslog (LOG_ERR, msgbuf) ;
+
+ cxnSleepOrDie (cxn) ;
+
+ return false ;
+ }
+ }
+ else
+#endif
+ /* bind to a specified IPv4 address */
+#ifdef HAVE_INET6
+ if ( (cxnAddr.ss_family == AF_INET) && bind_addr )
+#else
+ if (bind_addr)
+#endif
+ {
+ memcpy( (void *)&cxnSelf, bind_addr, sizeof(struct sockaddr_in) );
+ if (bind (fd, (struct sockaddr *) &cxnSelf,
+ sizeof(struct sockaddr_in) ) < 0)
+ {
+ snprintf(msgbuf, sizeof(msgbuf), "bind (%s): %%m",
+ inet_ntoa(bind_addr->sin_addr));
+ syslog (LOG_ERR, msgbuf) ;
+
+ cxnSleepOrDie (cxn) ;
+
+ return false ;
+ }
+ }
+
+ /* set our file descriptor to non-blocking */
+#if defined (O_NONBLOCK)
+ rval = fcntl (fd, F_GETFL, 0) ;
+ if (rval >= 0)
+ rval = fcntl (fd, F_SETFL, rval | O_NONBLOCK) ;
+#else
+ {
+ int state = 1 ;
+ rval = ioctl (fd, FIONBIO, (char *) &state) ;
+ }
+#endif
+
+ if (rval < 0)
+ {
+ syswarn ("%s:%d cxnsleep can't set socket non-blocking", peerName,
+ cxn->ident) ;
+ close (fd) ;
+
+ cxnSleepOrDie (cxn) ;
+
+ return false ;
+ }
+
+ rval = connect (fd, (struct sockaddr *) &cxnAddr,
+ SA_LEN((struct sockaddr *)&cxnAddr)) ;
+ if (rval < 0 && errno != EINPROGRESS)
+ {
+ syswarn ("%s:%d connect", peerName, cxn->ident) ;
+ hostIpFailed (cxn->myHost) ;
+ close (fd) ;
+
+ cxnSleepOrDie (cxn) ;
+
+ return false ;
+ }
+
+ if ((cxn->myEp = newEndPoint (fd)) == NULL)
+ {
+ /* If this happens, then fd was bigger than what select could handle,
+ so endpoint.c refused to create the new object. */
+ close (fd) ;
+ cxnSleepOrDie (cxn) ;
+ return false ;
+ }
+
+
+ if (rval < 0)
+ /* when the write callback gets done the connection went through */
+ prepareWrite (cxn->myEp, NULL, NULL, connectionDone, cxn) ;
+ else
+ connectionDone (cxn->myEp, IoDone, NULL, cxn) ;
+
+ /* connectionDone() could set state to sleeping */
+ return (cxn->state == cxnConnectingS ? true : false) ;
+}
+
+
+
+
+\f
+/* Put the Connection into the wait state.
+ *
+ * Pre-state Reason cxnWait called
+ * --------- ------------------------
+ * cxnStartingS - Connection owner called cxnWait()
+ * cxnSleepingS - side effect of cxnFlush() call.
+ * cxnConnectingS - side effect of cxnFlush() call.
+ * cxnFlushingS - side effect of receiving response 205
+ * and Connection had no articles when
+ * cxnFlush() was issued.
+ * - prepareRead failed.
+ * - I/O failed.
+ *
+ */
+void cxnWait (Connection cxn)
+{
+ ASSERT (cxn->state == cxnStartingS ||
+ cxn->state == cxnSleepingS ||
+ cxn->state == cxnConnectingS ||
+ cxn->state == cxnFeedingS ||
+ cxn->state == cxnFlushingS) ;
+ VALIDATE_CONNECTION (cxn) ;
+
+ abortConnection (cxn) ;
+
+ cxn->state = cxnWaitingS ;
+
+ hostCxnWaiting (cxn->myHost,cxn) ; /* tell our Host we're waiting */
+}
+
+
+
+
+\f
+/* Tells the Connection to flush itself (i.e. push out all articles,
+ * issue a QUIT and drop the network connection. If necessary a
+ * reconnect will be done immediately after. Called by the Host, or
+ * by the timer callback.
+ *
+ * Pre-state Reason cxnFlush called
+ * --------- ------------------------
+ * ALL (except cxnDeadS - Connection owner called cxnFlush()
+ * and cxnStartingS)
+ * cxnFeedingS - side effect of flushCxnCbk() call.
+ */
+void cxnFlush (Connection cxn)
+{
+ ASSERT (cxn != NULL) ;
+ ASSERT (cxn->state != cxnStartingS) ;
+ ASSERT (cxn->state != cxnDeadS) ;
+ VALIDATE_CONNECTION (cxn) ;
+
+ switch (cxn->state)
+ {
+ case cxnSleepingS:
+ cxnWait (cxn) ;
+ break ;
+
+ case cxnConnectingS:
+ cxnWait (cxn) ;
+ cxnConnect (cxn) ;
+ break ;
+
+ case cxnIdleTimeoutS:
+ case cxnIdleS:
+ ASSERT (cxn->articleQTotal == 0) ;
+ if (cxn->state != cxnIdleTimeoutS)
+ clearTimer (cxn->artReceiptTimerId) ;
+ clearTimer (cxn->flushTimerId) ;
+ cxn->state = cxnFlushingS ;
+ issueQUIT (cxn) ;
+ break ;
+
+ case cxnClosingS:
+ case cxnFlushingS:
+ case cxnWaitingS:
+ if (cxn->articleQTotal == 0 && !writeIsPending (cxn->myEp))
+ issueQUIT (cxn) ;
+ break ;
+
+ case cxnFeedingS:
+ /* we only reconnect immediately if we're not idle when cxnFlush()
+ is called. */
+ if (!cxn->immedRecon)
+ {
+ cxn->immedRecon = (cxn->articleQTotal > 0 ? true : false) ;
+ d_printf (1,"%s:%d immediate reconnect for a cxnFlush()\n",
+ hostPeerName (cxn->myHost), cxn->ident) ;
+ }
+
+ clearTimer (cxn->flushTimerId) ;
+
+ cxn->state = cxnFlushingS ;
+
+ if (cxn->articleQTotal == 0 && !writeIsPending (cxn->myEp))
+ issueQUIT (cxn) ;
+ break ;
+
+ default:
+ die ("Bad connection state: %s\n",stateToString (cxn->state)) ;
+ }
+}
+
+
+\f
+/*
+ * Tells the Connection to dump all articles that are queued and to issue a
+ * QUIT as quickly as possible. Much like cxnClose, except queued articles
+ * are not sent, but are given back to the Host.
+ */
+void cxnTerminate (Connection cxn)
+{
+ ASSERT (cxn != NULL) ;
+ ASSERT (cxn->state != cxnDeadS) ;
+ ASSERT (cxn->state != cxnStartingS) ;
+ VALIDATE_CONNECTION (cxn) ;
+
+ switch (cxn->state)
+ {
+ case cxnFeedingS:
+ d_printf (1,"%s:%d Issuing terminate\n",
+ hostPeerName (cxn->myHost), cxn->ident) ;
+
+ clearTimer (cxn->flushTimerId) ;
+
+ cxn->state = cxnClosingS ;
+
+ deferQueuedArticles (cxn) ;
+ if (cxn->articleQTotal == 0)
+ issueQUIT (cxn) ; /* send out the QUIT if we can */
+ break ;
+
+ case cxnIdleTimeoutS:
+ case cxnIdleS:
+ ASSERT (cxn->articleQTotal == 0) ;
+ if (cxn->state != cxnIdleTimeoutS)
+ clearTimer (cxn->artReceiptTimerId) ;
+ clearTimer (cxn->flushTimerId) ;
+ cxn->state = cxnClosingS ;
+ issueQUIT (cxn) ;
+ break ;
+
+ case cxnFlushingS: /* we are in the middle of a periodic close. */
+ d_printf (1,"%s:%d Connection already being flushed\n",
+ hostPeerName (cxn->myHost),cxn->ident);
+ cxn->state = cxnClosingS ;
+ if (cxn->articleQTotal == 0)
+ issueQUIT (cxn) ; /* send out the QUIT if we can */
+ break ;
+
+ case cxnClosingS:
+ d_printf (1,"%s:%d Connection already closing\n",
+ hostPeerName (cxn->myHost),cxn->ident) ;
+ break ;
+
+ case cxnWaitingS:
+ case cxnConnectingS:
+ case cxnSleepingS:
+ cxnDead (cxn) ;
+ break ;
+
+ default:
+ die ("Bad connection state: %s\n",stateToString (cxn->state)) ;
+ }
+
+ VALIDATE_CONNECTION (cxn) ;
+
+ if (cxn->state == cxnDeadS)
+ {
+ d_printf (1,"%s:%d Deleting connection\n",hostPeerName (cxn->myHost),
+ cxn->ident) ;
+
+ delConnection (cxn) ;
+ }
+}
+
+
+\f
+/* Tells the Connection to do a disconnect and then when it is
+ * disconnected to delete itself.
+ *
+ * Pre-state Reason cxnClose called
+ * --------- ------------------------
+ * ALL (except cxnDeadS - Connecton owner called directly.
+ * and cxnStartingS).
+ */
+void cxnClose (Connection cxn)
+{
+ ASSERT (cxn != NULL) ;
+ ASSERT (cxn->state != cxnDeadS) ;
+ ASSERT (cxn->state != cxnStartingS) ;
+ VALIDATE_CONNECTION (cxn) ;
+
+ switch (cxn->state)
+ {
+ case cxnFeedingS:
+ d_printf (1,"%s:%d Issuing disconnect\n",
+ hostPeerName (cxn->myHost), cxn->ident) ;
+
+ clearTimer (cxn->flushTimerId) ;
+
+ cxn->state = cxnClosingS ;
+
+ if (cxn->articleQTotal == 0)
+ issueQUIT (cxn) ; /* send out the QUIT if we can */
+ break ;
+
+ case cxnIdleS:
+ case cxnIdleTimeoutS:
+ ASSERT (cxn->articleQTotal == 0) ;
+ if (cxn->state != cxnIdleTimeoutS)
+ clearTimer (cxn->artReceiptTimerId) ;
+ clearTimer (cxn->flushTimerId) ;
+ cxn->state = cxnClosingS ;
+ issueQUIT (cxn) ;
+ break ;
+
+ case cxnFlushingS: /* we are in the middle of a periodic close. */
+ d_printf (1,"%s:%d Connection already being flushed\n",
+ hostPeerName (cxn->myHost),cxn->ident);
+ cxn->state = cxnClosingS ;
+ if (cxn->articleQTotal == 0)
+ issueQUIT (cxn) ; /* send out the QUIT if we can */
+ break ;
+
+ case cxnClosingS:
+ d_printf (1,"%s:%d Connection already closing\n",
+ hostPeerName (cxn->myHost),cxn->ident) ;
+ break ;
+
+ case cxnWaitingS:
+ case cxnConnectingS:
+ case cxnSleepingS:
+ cxnDead (cxn) ;
+ break ;
+
+ default:
+ die ("Bad connection state: %s\n",stateToString (cxn->state)) ;
+ }
+
+ VALIDATE_CONNECTION (cxn) ;
+
+ if (cxn->state == cxnDeadS)
+ {
+ d_printf (1,"%s:%d Deleting connection\n",hostPeerName (cxn->myHost),
+ cxn->ident) ;
+
+ delConnection (cxn) ;
+ }
+}
+
+
+
+
+\f
+/* This is what the Host calls to get us to tranfer an article. If
+ * we're running the IHAVE sequence, then we can't take it if we've
+ * got an article already. If we're running the CHECK/TAKETHIS
+ * sequence, then we'll take as many as we can (up to our MAXCHECK
+ * limit).
+ */
+bool cxnTakeArticle (Connection cxn, Article art)
+{
+ bool rval = true ;
+
+ ASSERT (cxn != NULL) ;
+ VALIDATE_CONNECTION (cxn) ;
+
+ if ( !cxnQueueArticle (cxn,art) ) /* might change cxnIdleS to cxnFeedingS */
+ return false ;
+
+ if (!(cxn->state == cxnConnectingS ||
+ cxn->state == cxnFeedingS ||
+ cxn->state == cxnWaitingS))
+ {
+ warn ("%s:%d cxnsleep connection in bad state: %s",
+ hostPeerName (cxn->myHost), cxn->ident,
+ stateToString (cxn->state)) ;
+ cxnSleepOrDie (cxn) ;
+ return false ;
+ }
+
+ if (cxn->state != cxnWaitingS) /* because articleQTotal == 1 */
+ VALIDATE_CONNECTION (cxn) ;
+ else
+ ASSERT (cxn->articleQTotal == 1) ;
+
+ switch (cxn->state)
+ {
+ case cxnWaitingS:
+ cxnConnect (cxn) ;
+ break ;
+
+ case cxnFeedingS:
+ doSomeWrites (cxn) ;
+ break ;
+
+ case cxnConnectingS:
+ break ;
+
+ default:
+ die ("Bad connection state: %s\n",stateToString (cxn->state)) ;
+ }
+
+ return rval ;
+}
+
+
+
+
+\f
+/* if there's room in the Connection then stick the article on the
+ * queue, otherwise return false.
+ */
+bool cxnQueueArticle (Connection cxn, Article art)
+{
+ ArtHolder newArt ;
+ bool rval = false ;
+
+ ASSERT (cxn != NULL) ;
+ ASSERT (cxn->state != cxnStartingS) ;
+ ASSERT (cxn->state != cxnDeadS) ;
+ VALIDATE_CONNECTION (cxn) ;
+
+ switch (cxn->state)
+ {
+ case cxnClosingS:
+ d_printf (5,"%s:%d Refusing article due to closing\n",
+ hostPeerName (cxn->myHost),cxn->ident) ;
+ break ;
+
+ case cxnFlushingS:
+ d_printf (5,"%s:%d Refusing article due to flushing\n",
+ hostPeerName (cxn->myHost),cxn->ident) ;
+ break ;
+
+ case cxnSleepingS:
+ d_printf (5,"%s:%d Refusing article due to sleeping\n",
+ hostPeerName (cxn->myHost),cxn->ident) ;
+ break ;
+
+ case cxnWaitingS:
+ rval = true ;
+ newArt = newArtHolder (art) ;
+ appendArtHolder (newArt, &cxn->checkHead, &cxn->articleQTotal) ;
+ break ;
+
+ case cxnConnectingS:
+ if (cxn->articleQTotal != 0)
+ break ;
+ rval = true ;
+ newArt = newArtHolder (art) ;
+ appendArtHolder (newArt, &cxn->checkHead, &cxn->articleQTotal) ;
+ break ;
+
+ case cxnIdleS:
+ case cxnFeedingS:
+ if (cxn->articleQTotal >= cxn->maxCheck)
+ d_printf (5, "%s:%d Refusing article due to articleQTotal >= maxCheck (%d > %d)\n",
+ hostPeerName (cxn->myHost), cxn->ident,
+ cxn->articleQTotal, cxn->maxCheck) ;
+ else
+ {
+ rval = true ;
+ newArt = newArtHolder (art) ;
+ if (cxn->needsChecks)
+ appendArtHolder (newArt, &cxn->checkHead, &cxn->articleQTotal) ;
+ else
+ appendArtHolder (newArt, &cxn->takeHead, &cxn->articleQTotal) ;
+ if (cxn->state == cxnIdleS)
+ {
+ cxn->state = cxnFeedingS ;
+ clearTimer (cxn->artReceiptTimerId) ;
+ }
+ }
+ break ;
+
+ default:
+ die ("Invalid state: %s\n", stateToString (cxn->state)) ;
+ }
+
+ if (rval)
+ {
+ d_printf (5,"%s:%d accepting article %s\n",hostPeerName (cxn->myHost),
+ cxn->ident,artMsgId (art)) ;
+
+ cxn->artsTaken++ ;
+ }
+
+ return rval ;
+}
+
+
+
+
+\f
+/*
+ * generate a log message for activity. Usually called by the Connection's
+ * owner
+ */
+void cxnLogStats (Connection cxn, bool final)
+{
+ const char *peerName ;
+ time_t now = theTime() ;
+
+ ASSERT (cxn != NULL) ;
+
+ /* only log stats when in one of these three states. */
+ switch (cxn->state)
+ {
+ case cxnFeedingS:
+ case cxnFlushingS:
+ case cxnClosingS:
+ break ;
+
+ default:
+ return ;
+ }
+
+ peerName = hostPeerName (cxn->myHost) ;
+
+ notice ("%s:%d %s seconds %ld offered %d accepted %d refused %d"
+ " rejected %d accsize %.0f rejsize %.0f", peerName, cxn->ident,
+ (final ? "final" : "checkpoint"), (long) (now - cxn->timeCon),
+ cxn->checksIssued, cxn->takesOkayed, cxn->checksRefused,
+ cxn->takesRejected, cxn->takesSizeOkayed, cxn->takesSizeRejected) ;
+
+ if (final)
+ {
+ cxn->artsTaken = 0 ;
+ cxn->checksIssued = 0 ;
+ cxn->checksRefused = 0 ;
+ cxn->takesRejected = 0 ;
+ cxn->takesOkayed = 0 ;
+ cxn->takesSizeRejected = 0 ;
+ cxn->takesSizeOkayed = 0 ;
+
+ if (cxn->timeCon > 0)
+ cxn->timeCon = theTime() ;
+ }
+}
+
+
+
+
+\f
+/*
+ * return the number of articles the connection will accept.
+ */
+size_t cxnQueueSpace (Connection cxn)
+{
+ int rval = 0 ;
+
+ ASSERT (cxn != NULL) ;
+
+ if (cxn->state == cxnFeedingS ||
+ cxn->state == cxnIdleS ||
+ cxn->state == cxnConnectingS ||
+ cxn->state == cxnWaitingS)
+ rval = cxn->maxCheck - cxn->articleQTotal ;
+
+ return rval ;
+}
+
+
+
+
+\f
+/*
+ * Print info on all the connections that currently exist.
+ */
+void gPrintCxnInfo (FILE *fp, unsigned int indentAmt)
+{
+ char indent [INDENT_BUFFER_SIZE] ;
+ unsigned int i ;
+ Connection cxn ;
+
+ for (i = 0 ; i < MIN(INDENT_BUFFER_SIZE - 1,indentAmt) ; i++)
+ indent [i] = ' ' ;
+ indent [i] = '\0' ;
+
+ fprintf (fp,"%sGlobal Connection list : (count %d) {\n",
+ indent,gCxnCount) ;
+ for (cxn = gCxnList ; cxn != NULL ; cxn = cxn->next)
+ printCxnInfo (cxn,fp,indentAmt + INDENT_INCR) ;
+ fprintf (fp,"%s}\n",indent) ;
+}
+
+
+
+
+\f
+/*
+ * Print the info about the given connection.
+ */
+void printCxnInfo (Connection cxn, FILE *fp, unsigned int indentAmt)
+{
+ char indent [INDENT_BUFFER_SIZE] ;
+ unsigned int i ;
+ ArtHolder artH ;
+
+ for (i = 0 ; i < MIN(INDENT_BUFFER_SIZE - 1,indentAmt) ; i++)
+ indent [i] = ' ' ;
+ indent [i] = '\0' ;
+
+ fprintf (fp,"%sConnection : %p {\n",indent, (void *) cxn) ;
+ fprintf (fp,"%s host : %p\n",indent, (void *) cxn->myHost) ;
+ fprintf (fp,"%s endpoint : %p\n",indent, (void *) cxn->myEp) ;
+ fprintf (fp,"%s state : %s\n",indent, stateToString (cxn->state)) ;
+ fprintf (fp,"%s ident : %d\n",indent,cxn->ident) ;
+ fprintf (fp,"%s ip-name : %s\n", indent, cxn->ipName) ;
+ fprintf (fp,"%s port-number : %d\n",indent,cxn->port) ;
+ fprintf (fp,"%s max-checks : %d\n",indent,cxn->maxCheck) ;
+ fprintf (fp,"%s does-streaming : %s\n",indent,
+ boolToString (cxn->doesStreaming)) ;
+ fprintf (fp,"%s authenticated : %s\n",indent,
+ boolToString (cxn->authenticated)) ;
+ fprintf (fp,"%s quitWasIssued : %s\n",indent,
+ boolToString (cxn->quitWasIssued)) ;
+ fprintf (fp,"%s needs-checks : %s\n",indent,
+ boolToString (cxn->needsChecks)) ;
+
+ fprintf (fp,"%s time-connected : %ld\n",indent,(long) cxn->timeCon) ;
+ fprintf (fp,"%s articles from INN : %d\n",indent,cxn->artsTaken) ;
+ fprintf (fp,"%s articles offered : %d\n",indent,
+ cxn->checksIssued) ;
+ fprintf (fp,"%s articles refused : %d\n",indent,
+ cxn->checksRefused) ;
+ fprintf (fp,"%s articles rejected : %d\n",indent,
+ cxn->takesRejected) ;
+ fprintf (fp,"%s articles accepted : %d\n",indent,
+ cxn->takesOkayed) ;
+ fprintf (fp,"%s low-pass upper limit : %0.6f\n", indent,
+ cxn->onThreshold) ;
+ fprintf (fp,"%s low-pass lower limit : %0.6f\n", indent,
+ cxn->offThreshold) ;
+ fprintf (fp,"%s low-pass filter tc : %0.6f\n", indent,
+ cxn->lowPassFilter) ;
+ fprintf (fp,"%s low-pass filter : %0.6f\n", indent,
+ cxn->filterValue) ;
+
+ fprintf (fp,"%s article-timeout : %d\n",indent,cxn->articleReceiptTimeout) ;
+ fprintf (fp,"%s article-callback : %d\n",indent,cxn->artReceiptTimerId) ;
+
+ fprintf (fp,"%s response-timeout : %d\n",indent,cxn->readTimeout) ;
+ fprintf (fp,"%s response-callback : %d\n",indent,cxn->readBlockedTimerId) ;
+
+ fprintf (fp,"%s write-timeout : %d\n",indent,cxn->writeTimeout) ;
+ fprintf (fp,"%s write-callback : %d\n",indent,cxn->writeBlockedTimerId) ;
+
+ fprintf (fp,"%s flushTimeout : %d\n",indent,cxn->flushTimeout) ;
+ fprintf (fp,"%s flushTimerId : %d\n",indent,cxn->flushTimerId) ;
+
+ fprintf (fp,"%s reopen wait : %d\n",indent,cxn->sleepTimeout) ;
+ fprintf (fp,"%s reopen id : %d\n",indent,cxn->sleepTimerId) ;
+
+ fprintf (fp,"%s CHECK queue {\n",indent) ;
+ for (artH = cxn->checkHead ; artH != NULL ; artH = artH->next)
+ printArticleInfo (artH->article,fp,indentAmt + INDENT_INCR) ;
+ fprintf (fp,"%s }\n",indent) ;
+
+ fprintf (fp,"%s CHECK Response queue {\n",indent) ;
+ for (artH = cxn->checkRespHead ; artH != NULL ; artH = artH->next)
+ printArticleInfo (artH->article,fp,indentAmt + INDENT_INCR) ;
+ fprintf (fp,"%s }\n",indent) ;
+
+ fprintf (fp,"%s TAKE queue {\n",indent) ;
+ for (artH = cxn->takeHead ; artH != NULL ; artH = artH->next)
+ printArticleInfo (artH->article,fp,indentAmt + INDENT_INCR) ;
+ fprintf (fp,"%s }\n",indent) ;
+
+ fprintf (fp,"%s TAKE response queue {\n",indent) ;
+ for (artH = cxn->takeRespHead ; artH != NULL ; artH = artH->next)
+ printArticleInfo (artH->article,fp,indentAmt + INDENT_INCR) ;
+ fprintf (fp,"%s }\n",indent) ;
+
+ fprintf (fp,"%s response buffer {\n",indent) ;
+ printBufferInfo (cxn->respBuffer,fp,indentAmt + INDENT_INCR) ;
+ fprintf (fp,"%s }\n",indent) ;
+
+ fprintf (fp,"%s}\n",indent) ;
+}
+
+
+
+
+\f
+/*
+ * return the number of articles the connection will accept.
+ */
+bool cxnCheckstate (Connection cxn)
+{
+ bool rval = false ;
+
+ ASSERT (cxn != NULL) ;
+
+ if (cxn->state == cxnFeedingS ||
+ cxn->state == cxnIdleS ||
+ cxn->state == cxnConnectingS)
+ rval = true ;
+
+ return rval ;
+}
+
+
+
+
+\f
+/**********************************************************************/
+/** STATIC PRIVATE FUNCTIONS **/
+/**********************************************************************/
+
+
+/*
+ * ENDPOINT CALLBACK AREA.
+ *
+ * All the functions in this next section are callbacks fired by the
+ * EndPoint objects/class (either timers or i/o completion callbacks)..
+ */
+
+\f
+/*
+ * this is the first stage of the NNTP FSM. This function is called
+ * when the tcp/ip network connection is setup and we should get
+ * ready to read the banner message. When this function returns the
+ * state of the Connection will still be cxnConnectingS unless
+ * something broken, in which case it probably went into the
+ * cxnSleepingS state.
+ */
+static void connectionDone (EndPoint e, IoStatus i, Buffer *b, void *d)
+{
+ Buffer *readBuffers ;
+ Connection cxn = (Connection) d ;
+ const char *peerName ;
+ int optval;
+ socklen_t size ;
+
+ ASSERT (b == NULL) ;
+ ASSERT (cxn->state == cxnConnectingS) ;
+ ASSERT (!writeIsPending (cxn->myEp)) ;
+
+ size = sizeof (optval) ;
+ peerName = hostPeerName (cxn->myHost) ;
+
+ if (i != IoDone)
+ {
+ errno = endPointErrno (e) ;
+ syswarn ("%s:%d cxnsleep i/o failed", peerName, cxn->ident) ;
+
+ cxnSleepOrDie (cxn) ;
+ }
+ else if (getsockopt (endPointFd (e), SOL_SOCKET, SO_ERROR,
+ (char *) &optval, &size) != 0)
+ {
+ /* This is bad. Can't even get the SO_ERROR value out of the socket */
+ syswarn ("%s:%d cxnsleep internal getsockopt", peerName, cxn->ident) ;
+
+ cxnSleepOrDie (cxn) ;
+ }
+ else if (optval != 0)
+ {
+ /* if the connect failed then the only way to know is by getting
+ the SO_ERROR value out of the socket. */
+ errno = optval ;
+ syswarn ("%s:%d cxnsleep connect", peerName, cxn->ident) ;
+ hostIpFailed (cxn->myHost) ;
+
+ cxnSleepOrDie (cxn) ;
+ }
+ else
+ {
+ readBuffers = makeBufferArray (bufferTakeRef (cxn->respBuffer), NULL) ;
+
+ if ( !prepareRead (e, readBuffers, getBanner, cxn, 1) )
+ {
+ warn ("%s:%d cxnsleep prepare read failed", peerName, cxn->ident) ;
+
+ cxnSleepOrDie (cxn) ;
+ }
+ else
+ {
+ initReadBlockedTimeout (cxn) ;
+
+ /* set up the callback for closing down the connection at regular
+ intervals (due to problems with long running nntpd). */
+ if (cxn->flushTimeout > 0)
+ cxn->flushTimerId = prepareSleep (flushCxnCbk,
+ cxn->flushTimeout,cxn) ;
+
+ /* The state doesn't change yet until we've read the banner and
+ tried the MODE STREAM command. */
+ }
+ }
+ VALIDATE_CONNECTION (cxn) ;
+}
+
+
+
+
+\f
+/*
+ * This is called when we are so far in the connection setup that
+ * we're confident it'll work. If the connection is IPv6, remove
+ * the IPv4 addresses from the address list.
+ */
+static void connectionIfIpv6DeleteIpv4Addr (Connection cxn)
+{
+#ifdef HAVE_INET6
+ struct sockaddr_storage ss;
+ socklen_t len = sizeof(ss);
+
+ if (getpeername (endPointFd (cxn->myEp), (struct sockaddr *)&ss, &len) < 0)
+ return;
+ if (ss.ss_family != AF_INET6)
+ return;
+
+ hostDeleteIpv4Addr (cxn->myHost);
+#endif
+}
+
+
+
+
+
+/*
+ * Called when the banner message has been read off the wire and is
+ * in the buffer(s). When this function returns the state of the
+ * Connection will still be cxnConnectingS unless something broken,
+ * in which case it probably went into the cxnSleepiongS state.
+ */
+static void getBanner (EndPoint e, IoStatus i, Buffer *b, void *d)
+{
+ Buffer *readBuffers ;
+ Connection cxn = (Connection) d ;
+ char *p = bufferBase (b[0]) ;
+ int code ;
+ bool isOk = false ;
+ const char *peerName ;
+ char *rest ;
+
+ ASSERT (e == cxn->myEp) ;
+ ASSERT (b[0] == cxn->respBuffer) ;
+ ASSERT (b[1] == NULL) ;
+ ASSERT (cxn->state == cxnConnectingS) ;
+ ASSERT (!writeIsPending (cxn->myEp));
+
+
+ peerName = hostPeerName (cxn->myHost) ;
+
+ bufferAddNullByte (b[0]) ;
+
+ if (i != IoDone)
+ {
+ errno = endPointErrno (cxn->myEp) ;
+ syswarn ("%s:%d cxnsleep can't read banner", peerName, cxn->ident) ;
+ hostIpFailed (cxn->myHost) ;
+
+ cxnSleepOrDie (cxn) ;
+ }
+ else if (strchr (p, '\n') == NULL)
+ { /* partial read. expand buffer and retry */
+ expandBuffer (b[0], BUFFER_EXPAND_AMOUNT) ;
+ readBuffers = makeBufferArray (bufferTakeRef (b[0]), NULL) ;
+
+ if ( !prepareRead (e, readBuffers, getBanner, cxn, 1) )
+ {
+ warn ("%s:%d cxnsleep prepare read failed", peerName, cxn->ident) ;
+
+ cxnSleepOrDie (cxn) ;
+ }
+ }
+ else if ( !getNntpResponse (p, &code, &rest) )
+ {
+ trim_ws (p) ;
+
+ warn ("%s:%d cxnsleep response format: %s", peerName, cxn->ident, p) ;
+
+ cxnSleepOrDie (cxn) ;
+ }
+ else
+ {
+ trim_ws (p) ;
+
+ switch (code)
+ {
+ case 200: /* normal */
+ case 201: /* can transfer but not post -- old nntpd */
+ isOk = true ;
+ break ;
+
+ case 400:
+ cxnSleepOrDie (cxn) ;
+ hostIpFailed (cxn->myHost) ;
+ hostCxnBlocked (cxn->myHost, cxn, rest) ;
+ break ;
+
+ case 502:
+ warn ("%s:%d cxnsleep no permission to talk: %s", peerName,
+ cxn->ident, p) ;
+ cxnSleepOrDie (cxn) ;
+ hostIpFailed (cxn->myHost) ;
+ hostCxnBlocked (cxn->myHost, cxn, rest) ;
+ break ;
+
+ default:
+ warn ("%s:%d cxnsleep response unknown banner: %d %s", peerName,
+ cxn->ident, code, p) ;
+ d_printf (1,"%s:%d Unknown response code: %d: %s\n",
+ hostPeerName (cxn->myHost),cxn->ident, code, p) ;
+ cxnSleepOrDie (cxn) ;
+ hostIpFailed (cxn->myHost) ;
+ hostCxnBlocked (cxn->myHost, cxn, rest) ;
+ break ;
+ }
+
+ if ( isOk )
+ {
+ /* If we got this far and the connection is IPv6, remove
+ the IPv4 addresses from the address list. */
+ connectionIfIpv6DeleteIpv4Addr (cxn);
+
+ if (hostUsername (cxn->myHost) != NULL
+ && hostPassword (cxn->myHost) != NULL)
+ issueAuthUser (e,cxn);
+ else
+ issueModeStream (e,cxn);
+ }
+ }
+ freeBufferArray (b) ;
+}
+
+
+
+
+\f
+static void issueAuthUser (EndPoint e, Connection cxn)
+{
+ Buffer authUserBuffer;
+ Buffer *authUserCmdBuffers,*readBuffers;
+ size_t lenBuff = 0 ;
+ char *t ;
+
+ /* 17 == strlen("AUTHINFO USER \r\n\0") */
+ lenBuff = (17 + strlen (hostUsername (cxn->myHost))) ;
+ authUserBuffer = newBuffer (lenBuff) ;
+ t = bufferBase (authUserBuffer) ;
+
+ sprintf (t, "AUTHINFO USER %s\r\n", hostUsername (cxn->myHost)) ;
+ bufferSetDataSize (authUserBuffer, strlen (t)) ;
+
+ authUserCmdBuffers = makeBufferArray (authUserBuffer, NULL) ;
+
+ if ( !prepareWriteWithTimeout (e, authUserCmdBuffers, authUserIssued,
+ cxn) )
+ {
+ die ("%s:%d fatal prepare write for authinfo user failed",
+ hostPeerName (cxn->myHost), cxn->ident) ;
+ }
+
+ bufferSetDataSize (cxn->respBuffer, 0) ;
+
+ readBuffers = makeBufferArray (bufferTakeRef(cxn->respBuffer),NULL);
+
+ if ( !prepareRead (e, readBuffers, getAuthUserResponse, cxn, 1) )
+ {
+ warn ("%s:%d cxnsleep prepare read failed", hostPeerName (cxn->myHost),
+ cxn->ident) ;
+ freeBufferArray (readBuffers) ;
+ cxnSleepOrDie (cxn) ;
+ }
+
+}
+
+
+
+
+
+\f
+static void issueAuthPass (EndPoint e, Connection cxn)
+{
+ Buffer authPassBuffer;
+ Buffer *authPassCmdBuffers,*readBuffers;
+ size_t lenBuff = 0 ;
+ char *t ;
+
+ /* 17 == strlen("AUTHINFO PASS \r\n\0") */
+ lenBuff = (17 + strlen (hostPassword (cxn->myHost))) ;
+ authPassBuffer = newBuffer (lenBuff) ;
+ t = bufferBase (authPassBuffer) ;
+
+ sprintf (t, "AUTHINFO PASS %s\r\n", hostPassword (cxn->myHost)) ;
+ bufferSetDataSize (authPassBuffer, strlen (t)) ;
+
+ authPassCmdBuffers = makeBufferArray (authPassBuffer, NULL) ;
+
+ if ( !prepareWriteWithTimeout (e, authPassCmdBuffers, authPassIssued,
+ cxn) )
+ {
+ die ("%s:%d fatal prepare write for authinfo pass failed",
+ hostPeerName (cxn->myHost), cxn->ident) ;
+ }
+
+ bufferSetDataSize (cxn->respBuffer, 0) ;
+
+ readBuffers = makeBufferArray (bufferTakeRef(cxn->respBuffer),NULL);
+
+ if ( !prepareRead (e, readBuffers, getAuthPassResponse, cxn, 1) )
+ {
+ warn ("%s:%d cxnsleep prepare read failed", hostPeerName (cxn->myHost),
+ cxn->ident) ;
+ freeBufferArray (readBuffers) ;
+ cxnSleepOrDie (cxn) ;
+ }
+
+}
+
+
+
+
+
+\f
+static void issueModeStream (EndPoint e, Connection cxn)
+{
+ Buffer *modeCmdBuffers,*readBuffers ;
+ Buffer modeBuffer ;
+ char *p;
+
+#define MODE_CMD "MODE STREAM\r\n"
+
+ modeBuffer = newBuffer (strlen (MODE_CMD) + 1) ;
+ p = bufferBase (modeBuffer) ;
+
+ /* now issue the MODE STREAM command */
+ d_printf (1,"%s:%d Issuing the streaming command: %s\n",
+ hostPeerName (cxn->myHost),cxn->ident,MODE_CMD) ;
+
+ strcpy (p, MODE_CMD) ;
+
+ bufferSetDataSize (modeBuffer, strlen (p)) ;
+
+ modeCmdBuffers = makeBufferArray (modeBuffer, NULL) ;
+
+ if ( !prepareWriteWithTimeout (e, modeCmdBuffers, modeCmdIssued,
+ cxn) )
+ {
+ die ("%s:%d fatal prepare write for mode stream failed",
+ hostPeerName (cxn->myHost), cxn->ident) ;
+ }
+
+ bufferSetDataSize (cxn->respBuffer, 0) ;
+
+ readBuffers = makeBufferArray (bufferTakeRef(cxn->respBuffer),NULL);
+
+ if ( !prepareRead (e, readBuffers, getModeResponse, cxn, 1) )
+ {
+ warn ("%s:%d cxnsleep prepare read failed", hostPeerName (cxn->myHost),
+ cxn->ident) ;
+ freeBufferArray (readBuffers) ;
+ cxnSleepOrDie (cxn) ;
+ }
+}
+
+
+
+
+\f
+/*
+ *
+ */
+static void getAuthUserResponse (EndPoint e, IoStatus i, Buffer *b, void *d)
+{
+ Connection cxn = (Connection) d ;
+ int code ;
+ char *p = bufferBase (b[0]) ;
+ Buffer *buffers ;
+ const char *peerName ;
+
+ ASSERT (e == cxn->myEp) ;
+ ASSERT (b [0] == cxn->respBuffer) ;
+ ASSERT (b [1] == NULL) ; /* only ever one buffer on this read */
+ ASSERT (cxn->state == cxnConnectingS) ;
+ VALIDATE_CONNECTION (cxn) ;
+
+ peerName = hostPeerName (cxn->myHost) ;
+
+ bufferAddNullByte (b[0]) ;
+
+ d_printf (1,"%s:%d Processing authinfo user response: %s", /* no NL */
+ hostPeerName (cxn->myHost), cxn->ident, p) ;
+
+ if (i == IoDone && writeIsPending (cxn->myEp))
+ {
+ /* badness. should never happen */
+ warn ("%s:%d cxnsleep authinfo command still pending", peerName,
+ cxn->ident) ;
+
+ cxnSleepOrDie (cxn) ;
+ }
+ else if (i != IoDone)
+ {
+ if (i != IoEOF)
+ {
+ errno = endPointErrno (e) ;
+ syswarn ("%s:%d cxnsleep can't read response", peerName, cxn->ident);
+ }
+ cxnSleepOrDie (cxn) ;
+ }
+ else if (strchr (p, '\n') == NULL)
+ {
+ /* partial read */
+ expandBuffer (b [0], BUFFER_EXPAND_AMOUNT) ;
+
+ buffers = makeBufferArray (bufferTakeRef (b [0]), NULL) ;
+ if ( !prepareRead (e, buffers, getAuthUserResponse, cxn, 1) )
+ {
+ warn ("%s:%d cxnsleep prepare read failed", peerName, cxn->ident) ;
+ freeBufferArray (buffers) ;
+ cxnSleepOrDie (cxn) ;
+ }
+ }
+ else
+ {
+ clearTimer (cxn->readBlockedTimerId) ;
+
+ if ( !getNntpResponse (p, &code, NULL) )
+ {
+ warn ("%s:%d cxnsleep response to AUTHINFO USER: %s", peerName,
+ cxn->ident, p) ;
+
+ cxnSleepOrDie (cxn) ;
+ }
+ else
+ {
+ notice ("%s:%d connected", peerName, cxn->ident) ;
+
+ switch (code)
+ {
+ case 381:
+ issueAuthPass (e,cxn);
+ break ;
+
+ default:
+ warn ("%s:%d cxnsleep response to AUTHINFO USER: %s", peerName,
+ cxn->ident, p) ;
+ cxn->authenticated = true;
+ issueModeStream (e,cxn);
+ break ;
+ }
+
+ }
+ }
+}
+
+
+
+
+\f
+/*
+ *
+ */
+static void getAuthPassResponse (EndPoint e, IoStatus i, Buffer *b, void *d)
+{
+ Connection cxn = (Connection) d ;
+ int code ;
+ char *p = bufferBase (b[0]) ;
+ Buffer *buffers ;
+ const char *peerName ;
+
+ ASSERT (e == cxn->myEp) ;
+ ASSERT (b [0] == cxn->respBuffer) ;
+ ASSERT (b [1] == NULL) ; /* only ever one buffer on this read */
+ ASSERT (cxn->state == cxnConnectingS) ;
+ VALIDATE_CONNECTION (cxn) ;
+
+ peerName = hostPeerName (cxn->myHost) ;
+
+ bufferAddNullByte (b[0]) ;
+
+ d_printf (1,"%s:%d Processing authinfo pass response: %s", /* no NL */
+ hostPeerName (cxn->myHost), cxn->ident, p) ;
+
+ if (i == IoDone && writeIsPending (cxn->myEp))
+ {
+ /* badness. should never happen */
+ warn ("%s:%d cxnsleep authinfo command still pending", peerName,
+ cxn->ident) ;
+
+ cxnSleepOrDie (cxn) ;
+ }
+ else if (i != IoDone)
+ {
+ if (i != IoEOF)
+ {
+ errno = endPointErrno (e) ;
+ syswarn ("%s:%d cxnsleep can't read response", peerName, cxn->ident);
+ }
+ cxnSleepOrDie (cxn) ;
+ }
+ else if (strchr (p, '\n') == NULL)
+ {
+ /* partial read */
+ expandBuffer (b [0], BUFFER_EXPAND_AMOUNT) ;
+
+ buffers = makeBufferArray (bufferTakeRef (b [0]), NULL) ;
+ if ( !prepareRead (e, buffers, getAuthPassResponse, cxn, 1) )
+ {
+ warn ("%s:%d cxnsleep prepare read failed", peerName, cxn->ident) ;
+ freeBufferArray (buffers) ;
+ cxnSleepOrDie (cxn) ;
+ }
+ }
+ else
+ {
+ clearTimer (cxn->readBlockedTimerId) ;
+
+ if ( !getNntpResponse (p, &code, NULL) )
+ {
+ warn ("%s:%d cxnsleep response to AUTHINFO PASS: %s", peerName,
+ cxn->ident, p) ;
+
+ cxnSleepOrDie (cxn) ;
+ }
+ else
+ {
+ switch (code)
+ {
+ case 281:
+ notice ("%s:%d authenticated", peerName, cxn->ident) ;
+ cxn->authenticated = true ;
+ issueModeStream (e,cxn);
+ break ;
+
+ default:
+ warn ("%s:%d cxnsleep response to AUTHINFO PASS: %s", peerName,
+ cxn->ident, p) ;
+ cxnSleepOrDie (cxn) ;
+ break ;
+ }
+
+ }
+ }
+}
+
+
+
+
+\f
+/*
+ * Process the remote's response to our MODE STREAM command. This is where
+ * the Connection moves into the cxnFeedingS state. If the remote has given
+ * us a good welcome banner, but then immediately dropped the connection,
+ * we'll arrive here with the MODE STREAM command still queued up.
+ */
+static void getModeResponse (EndPoint e, IoStatus i, Buffer *b, void *d)
+{
+ Connection cxn = (Connection) d ;
+ int code ;
+ char *p = bufferBase (b[0]) ;
+ Buffer *buffers ;
+ const char *peerName ;
+
+ ASSERT (e == cxn->myEp) ;
+ ASSERT (b [0] == cxn->respBuffer) ;
+ ASSERT (b [1] == NULL) ; /* only ever one buffer on this read */
+ ASSERT (cxn->state == cxnConnectingS) ;
+ VALIDATE_CONNECTION (cxn) ;
+
+ peerName = hostPeerName (cxn->myHost) ;
+
+ bufferAddNullByte (b[0]) ;
+
+ d_printf (1,"%s:%d Processing mode response: %s", /* no NL */
+ hostPeerName (cxn->myHost), cxn->ident, p) ;
+
+ if (i == IoDone && writeIsPending (cxn->myEp))
+ { /* badness. should never happen */
+ warn ("%s:%d cxnsleep mode stream command still pending", peerName,
+ cxn->ident) ;
+
+ cxnSleepOrDie (cxn) ;
+ }
+ else if (i != IoDone)
+ {
+ if (i != IoEOF)
+ {
+ errno = endPointErrno (e) ;
+ syswarn ("%s:%d cxnsleep can't read response", peerName, cxn->ident);
+ }
+ cxnSleepOrDie (cxn) ;
+ }
+ else if (strchr (p, '\n') == NULL)
+ { /* partial read */
+ expandBuffer (b [0], BUFFER_EXPAND_AMOUNT) ;
+
+ buffers = makeBufferArray (bufferTakeRef (b [0]), NULL) ;
+ if ( !prepareRead (e, buffers, getModeResponse, cxn, 1) )
+ {
+ warn ("%s:%d cxnsleep prepare read failed", peerName, cxn->ident) ;
+ freeBufferArray (buffers) ;
+ cxnSleepOrDie (cxn) ;
+ }
+ }
+ else
+ {
+ clearTimer (cxn->readBlockedTimerId) ;
+
+ if ( !getNntpResponse (p, &code, NULL) )
+ {
+ warn ("%s:%d cxnsleep response to MODE STREAM: %s", peerName,
+ cxn->ident, p) ;
+
+ cxnSleepOrDie (cxn) ;
+ }
+ else
+ {
+ if (!cxn->authenticated)
+ notice ("%s:%d connected", peerName, cxn->ident) ;
+
+ switch (code)
+ {
+ case 203: /* will do streaming */
+ hostRemoteStreams (cxn->myHost, cxn, true) ;
+
+ if (hostWantsStreaming (cxn->myHost))
+ {
+ cxn->doesStreaming = true ;
+ cxn->maxCheck = hostMaxChecks (cxn->myHost) ;
+ }
+ else
+ cxn->maxCheck = 1 ;
+
+ break ;
+
+ default: /* won't do it */
+ hostRemoteStreams (cxn->myHost, cxn, false) ;
+ cxn->maxCheck = 1 ;
+ break ;
+ }
+
+ /* now we consider ourselves completly connected. */
+ cxn->timeCon = theTime () ;
+ if (cxn->articleQTotal == 0)
+ cxnIdle (cxn) ;
+ else
+ cxn->state = cxnFeedingS ;
+
+ /* one for the connection and one for the buffer array */
+ ASSERT (cxn->authenticated || bufferRefCount (cxn->respBuffer) == 2) ;
+
+ /* there was only one line in there, right? */
+ bufferSetDataSize (cxn->respBuffer, 0) ;
+ buffers = makeBufferArray (bufferTakeRef (cxn->respBuffer), NULL) ;
+
+ /* sleepTimeout get changed at each failed attempt, so reset. */
+ cxn->sleepTimeout = init_reconnect_period ;
+
+ if ( !prepareRead (cxn->myEp, buffers, responseIsRead, cxn, 1) )
+ {
+ freeBufferArray (buffers) ;
+
+ cxnSleepOrDie (cxn) ;
+ }
+ else
+ {
+ /* now we wait for articles from our Host, or we have some
+ articles already. On infrequently used connections, the
+ network link is torn down and rebuilt as needed. So we may
+ be rebuilding the connection here in which case we have an
+ article to send. */
+ if (writesNeeded (cxn) || hostGimmeArticle (cxn->myHost,cxn))
+ doSomeWrites (cxn) ;
+ }
+ }
+ }
+
+ freeBufferArray (b) ;
+}
+
+
+
+
+\f
+/*
+ * called when a response has been read from the socket. This is
+ * where the bulk of the processing starts.
+ */
+static void responseIsRead (EndPoint e, IoStatus i, Buffer *b, void *d)
+{
+ Connection cxn = (Connection) d ;
+ char *response ;
+ char *endr ;
+ char *bufBase ;
+ unsigned int respSize ;
+ int code ;
+ char *rest = NULL ;
+ Buffer buf ;
+ Buffer *bArr ;
+ const char *peerName ;
+
+ ASSERT (e == cxn->myEp) ;
+ ASSERT (b != NULL) ;
+ ASSERT (b [1] == NULL) ;
+ ASSERT (b [0] == cxn->respBuffer) ;
+ ASSERT (cxn->state == cxnFeedingS ||
+ cxn->state == cxnIdleS ||
+ cxn->state == cxnClosingS ||
+ cxn->state == cxnFlushingS) ;
+ VALIDATE_CONNECTION (cxn) ;
+
+ bufferAddNullByte (b [0]) ;
+
+ peerName = hostPeerName (cxn->myHost) ;
+
+ if (i != IoDone)
+ { /* uh oh. */
+ if (i != IoEOF)
+ {
+ errno = endPointErrno (e) ;
+ syswarn ("%s:%d cxnsleep can't read response", peerName, cxn->ident);
+ }
+ freeBufferArray (b) ;
+
+ cxnLogStats (cxn,true) ;
+
+ if (cxn->state == cxnClosingS)
+ {
+ cxnDead (cxn) ;
+ delConnection (cxn) ;
+ }
+ else
+ cxnSleep (cxn) ;
+
+ return ;
+ }
+
+ buf = b [0] ;
+ bufBase = bufferBase (buf) ;
+
+ /* check that we have (at least) a full line response. If not expand
+ the buffer and resubmit the read. */
+ if (strchr (bufBase, '\n') == 0)
+ {
+ if (!expandBuffer (buf, BUFFER_EXPAND_AMOUNT))
+ {
+ warn ("%s:%d cxnsleep can't expand input buffer", peerName,
+ cxn->ident) ;
+ freeBufferArray (b) ;
+
+ cxnSleepOrDie (cxn) ;
+ }
+ else if ( !prepareRead (cxn->myEp, b, responseIsRead, cxn, 1))
+ {
+ warn ("%s:%d cxnsleep prepare read failed", peerName, cxn->ident) ;
+ freeBufferArray (b) ;
+
+ cxnSleepOrDie (cxn) ;
+ }
+
+ return ;
+ }
+
+
+ freeBufferArray (b) ; /* connection still has reference to buffer */
+
+
+ /*
+ * Now process all the full responses that we have.
+ */
+ response = bufBase ;
+ respSize = bufferDataSize (cxn->respBuffer) ;
+
+ while ((endr = strchr (response, '\n')) != NULL)
+ {
+ char *next = endr + 1 ;
+
+ if (*next == '\r')
+ next++ ;
+
+ endr-- ;
+ if (*endr != '\r')
+ endr++ ;
+
+ if (next - endr != 2 && !cxn->loggedNoCr)
+ {
+ /* only a newline there. we'll live with it */
+ warn ("%s:%d remote not giving out CR characters", peerName,
+ cxn->ident) ;
+ cxn->loggedNoCr = true ;
+ }
+
+ *endr = '\0' ;
+
+ if ( !getNntpResponse (response, &code, &rest) )
+ {
+ warn ("%s:%d cxnsleep response format: %s", peerName, cxn->ident,
+ response) ;
+ cxnSleepOrDie (cxn) ;
+
+ return ;
+ }
+
+ d_printf (5,"%s:%d Response %d: %s\n", peerName, cxn->ident, code, response) ;
+
+ /* now handle the response code. I'm not using symbolic names on
+ purpose--the numbers are all you see in the RFC's. */
+ switch (code)
+ {
+ case 205: /* OK response to QUIT. */
+ processResponse205 (cxn, response) ;
+ break ;
+
+
+
+ /* These three are from the CHECK command */
+ case 238: /* no such article found */
+ /* Do not incrFilter (cxn) now, wait till after
+ subsequent TAKETHIS */
+ processResponse238 (cxn, response) ;
+ break ;
+
+ case 431: /* try again later (also for TAKETHIS) */
+ decrFilter (cxn) ;
+ if (hostDropDeferred (cxn->myHost))
+ processResponse438 (cxn, response) ;
+ else
+ processResponse431 (cxn, response) ;
+ break ;
+
+ case 438: /* already have it */
+ decrFilter (cxn) ;
+ processResponse438 (cxn, response) ;
+ break ;
+
+
+
+ /* These are from the TAKETHIS command */
+ case 239: /* article transferred OK */
+ incrFilter (cxn) ;
+ processResponse239 (cxn, response) ;
+ break ;
+
+ case 439: /* article rejected */
+ decrFilter (cxn) ;
+ processResponse439 (cxn, response) ;
+ break ;
+
+
+
+ /* These are from the IHAVE command */
+ case 335: /* send article */
+ processResponse335 (cxn, response) ;
+ break ;
+
+ case 435: /* article not wanted */
+ processResponse435 (cxn, response) ;
+ break ;
+
+ case 436: /* transfer failed try again later */
+ if (cxn->takeRespHead == NULL && hostDropDeferred (cxn->myHost))
+ processResponse435 (cxn, response) ;
+ else
+ processResponse436 (cxn, response) ;
+ break ;
+
+ case 437: /* article rejected */
+ processResponse437 (cxn, response) ;
+ break ;
+
+ case 400: /* has stopped accepting articles */
+ processResponse400 (cxn, response) ;
+ break ;
+
+
+
+ case 235: /* article transfered OK (IHAVE-body) */
+ processResponse235 (cxn, response) ;
+ break ;
+
+
+ case 480: /* Transfer permission denied. */
+ processResponse480 (cxn,response) ;
+ break ;
+
+ case 503: /* remote timeout. */
+ processResponse503 (cxn,response) ;
+ break ;
+
+ default:
+ warn ("%s:%d cxnsleep response unknown: %d %s", peerName,
+ cxn->ident, code, response) ;
+ cxnSleepOrDie (cxn) ;
+ break ;
+ }
+
+ VALIDATE_CONNECTION (cxn) ;
+
+ if (cxn->state != cxnFeedingS && cxn->state != cxnClosingS &&
+ cxn->state != cxnFlushingS && cxn->state != cxnIdleS /* XXX */)
+ break ; /* connection is terminated */
+
+ response = next ;
+ }
+
+ d_printf (5,"%s:%d done with responses\n",hostPeerName (cxn->myHost),
+ cxn->ident) ;
+
+ switch (cxn->state)
+ {
+ case cxnIdleS:
+ case cxnFeedingS:
+ case cxnClosingS:
+ case cxnFlushingS:
+ /* see if we need to drop in to or out of no-CHECK mode */
+ if (cxn->state == cxnFeedingS && cxn->doesStreaming)
+ {
+ if ((cxn->filterValue > cxn->onThreshold) && cxn->needsChecks) {
+ cxn->needsChecks = false;
+ hostLogNoCheckMode (cxn->myHost, true,
+ cxn->offThreshold/cxn->lowPassFilter,
+ cxn->filterValue/cxn->lowPassFilter,
+ cxn->onThreshold/cxn->lowPassFilter) ;
+ /* on and log */
+ } else if ((cxn->filterValue < cxn->offThreshold) &&
+ !cxn->needsChecks) {
+ cxn->needsChecks = true;
+ hostLogNoCheckMode (cxn->myHost, false,
+ cxn->offThreshold/cxn->lowPassFilter,
+ cxn->filterValue/cxn->lowPassFilter,
+ cxn->onThreshold/cxn->lowPassFilter) ;
+ /* off and log */
+ }
+ }
+
+ /* Now handle possible remaining partial reponse and set up for
+ next read. */
+ if (*response != '\0')
+ { /* partial response */
+ unsigned int leftAmt = respSize - (response - bufBase) ;
+
+ d_printf (2,"%s:%d handling a partial response\n",
+ hostPeerName (cxn->myHost),cxn->ident) ;
+
+ /* first we shift what's left in the buffer down to the
+ bottom, if needed, or just expand the buffer */
+ if (response != bufBase)
+ {
+ /* so next read appends */
+ memmove (bufBase, response, leftAmt) ;
+ bufferSetDataSize (cxn->respBuffer, leftAmt) ;
+ }
+ else if (!expandBuffer (cxn->respBuffer, BUFFER_EXPAND_AMOUNT))
+ die ("%s:%d cxnsleep can't expand input buffer", peerName,
+ cxn->ident) ;
+ }
+ else
+ bufferSetDataSize (cxn->respBuffer, 0) ;
+
+ bArr = makeBufferArray (bufferTakeRef (cxn->respBuffer), NULL) ;
+
+ if ( !prepareRead (e, bArr, responseIsRead, cxn, 1) )
+ {
+ warn ("%s:%d cxnsleep prepare read failed", peerName, cxn->ident) ;
+ freeBufferArray (bArr) ;
+ cxnWait (cxn) ;
+ return ;
+ }
+ else
+ {
+ /* only setup the timer if we're still waiting for a response
+ to something. There's not necessarily a 1-to-1 mapping
+ between reads and writes in streaming mode. May have been
+ set already above (that would be unlikely I think). */
+ VALIDATE_CONNECTION (cxn) ;
+
+ d_printf (5,"%s:%d about to do some writes\n",
+ hostPeerName (cxn->myHost),cxn->ident) ;
+
+ doSomeWrites (cxn) ;
+
+ /* If the read timer is (still) running, update it to give
+ those terminally slow hosts that take forever to drain
+ the network buffers and just dribble out responses the
+ benefit of the doubt. XXX - maybe should just increase
+ timeout for these! */
+ if (cxn->readBlockedTimerId)
+ cxn->readBlockedTimerId = updateSleep (cxn->readBlockedTimerId,
+ responseTimeoutCbk,
+ cxn->readTimeout,
+ cxn) ;
+ }
+ VALIDATE_CONNECTION (cxn) ;
+ break ;
+
+ case cxnWaitingS: /* presumably after a code 205 or 400 */
+ case cxnConnectingS: /* presumably after a code 205 or 400 */
+ case cxnSleepingS: /* probably after a 480 */
+ break ;
+
+ case cxnDeadS:
+ delConnection (cxn) ;
+ break ;
+
+ case cxnStartingS:
+ default:
+ die ("Bad connection state: %s\n",stateToString (cxn->state)) ;
+ }
+}
+
+
+
+
+\f
+/*
+ * called when the write of the QUIT command has completed.
+ */
+static void quitWritten (EndPoint e, IoStatus i, Buffer *b, void *d)
+{
+ Connection cxn = (Connection) d ;
+ const char *peerName ;
+
+ peerName = hostPeerName (cxn->myHost) ;
+
+ clearTimer (cxn->writeBlockedTimerId) ;
+
+ ASSERT (cxn->myEp == e) ;
+ VALIDATE_CONNECTION (cxn) ;
+
+ if (i != IoDone)
+ {
+ errno = endPointErrno (e) ;
+ syswarn ("%s:%d cxnsleep can't write QUIT", peerName, cxn->ident) ;
+ if (cxn->state == cxnClosingS)
+ {
+ cxnDead (cxn) ;
+ delConnection (cxn) ;
+ }
+ else
+ cxnWait (cxn) ;
+ }
+ else
+ /* The QUIT command has been sent, so start the response timer. */
+ initReadBlockedTimeout (cxn) ;
+
+ freeBufferArray (b) ;
+}
+
+
+
+
+\f
+/*
+ * called when the write of the IHAVE-body data is finished
+ */
+static void ihaveBodyDone (EndPoint e, IoStatus i, Buffer *b, void *d)
+{
+ Connection cxn = (Connection) d ;
+
+ ASSERT (e == cxn->myEp) ;
+
+ clearTimer (cxn->writeBlockedTimerId) ;
+
+ if (i != IoDone)
+ {
+ errno = endPointErrno (e) ;
+ syswarn ("%s:%d cxnsleep can't write IHAVE body",
+ hostPeerName (cxn->myHost), cxn->ident) ;
+
+ cxnLogStats (cxn,true) ;
+
+ if (cxn->state == cxnClosingS)
+ {
+ cxnDead (cxn) ;
+ delConnection (cxn) ;
+ }
+ else
+ cxnSleep (cxn) ;
+ }
+ else
+ /* The article has been sent, so start the response timer. */
+ initReadBlockedTimeout (cxn) ;
+
+
+ freeBufferArray (b) ;
+
+ return ;
+}
+
+
+
+
+\f
+/*
+ * Called when a command set (IHAVE, CHECK, TAKETHIS) has been
+ * written to the remote.
+ */
+static void commandWriteDone (EndPoint e, IoStatus i, Buffer *b, void *d)
+{
+ Connection cxn = (Connection) d ;
+ const char *peerName ;
+
+ ASSERT (e == cxn->myEp) ;
+
+ peerName = hostPeerName (cxn->myHost) ;
+
+ freeBufferArray (b) ;
+
+ clearTimer (cxn->writeBlockedTimerId) ;
+
+ if (i != IoDone)
+ {
+ errno = endPointErrno (e) ;
+ syswarn ("%s:%d cxnsleep can't write command", peerName, cxn->ident) ;
+
+ cxnLogStats (cxn,true) ;
+
+ if (cxn->state == cxnClosingS)
+ {
+ cxnDead (cxn) ;
+ delConnection (cxn) ;
+ }
+ else
+ {
+ /* XXX - so cxnSleep() doesn't die in VALIDATE_CONNECTION () */
+ deferAllArticles (cxn) ;
+ cxnIdle (cxn) ;
+
+ cxnSleep (cxn) ;
+ }
+ }
+ else
+ {
+ /* Some(?) hosts return the 439 response even before we're done
+ sending, so don't go idle until here */
+ if (cxn->state == cxnFeedingS && cxn->articleQTotal == 0)
+ cxnIdle (cxn) ;
+ else
+ /* The command set has been sent, so start the response timer.
+ XXX - we'd like finer grained control */
+ initReadBlockedTimeout (cxn) ;
+
+ if ( cxn->doesStreaming )
+ doSomeWrites (cxn) ; /* pump data as fast as possible */
+ /* XXX - will clear the read timeout */
+ }
+}
+
+
+
+
+\f
+/*
+ * Called when the MODE STREAM command has been written down the pipe.
+ */
+static void modeCmdIssued (EndPoint e, IoStatus i, Buffer *b, void *d)
+{
+ Connection cxn = (Connection) d ;
+
+ ASSERT (e == cxn->myEp) ;
+
+ clearTimer (cxn->writeBlockedTimerId) ;
+
+ /* The mode command has been sent, so start the response timer */
+ initReadBlockedTimeout (cxn) ;
+
+ if (i != IoDone)
+ {
+ d_printf (1,"%s:%d MODE STREAM command failed to write\n",
+ hostPeerName (cxn->myHost), cxn->ident) ;
+
+ syswarn ("%s:%d cxnsleep can't write MODE STREAM",
+ hostPeerName (cxn->myHost), cxn->ident) ;
+
+ cxnSleepOrDie (cxn) ;
+ }
+
+ freeBufferArray (b) ;
+}
+
+
+
+
+\f
+/*
+ * Called when the AUTHINFO USER command has been written down the pipe.
+ */
+static void authUserIssued (EndPoint e, IoStatus i, Buffer *b, void *d)
+{
+ Connection cxn = (Connection) d ;
+
+ ASSERT (e == cxn->myEp) ;
+
+ clearTimer (cxn->writeBlockedTimerId) ;
+
+ /* The authinfo user command has been sent, so start the response timer */
+ initReadBlockedTimeout (cxn) ;
+
+ if (i != IoDone)
+ {
+ d_printf (1,"%s:%d AUTHINFO USER command failed to write\n",
+ hostPeerName (cxn->myHost), cxn->ident) ;
+
+ syswarn ("%s:%d cxnsleep can't write AUTHINFO USER",
+ hostPeerName (cxn->myHost), cxn->ident) ;
+
+ cxnSleepOrDie (cxn) ;
+ }
+
+ freeBufferArray (b) ;
+}
+
+
+
+
+
+\f
+/*
+ * Called when the AUTHINFO USER command has been written down the pipe.
+ */
+static void authPassIssued (EndPoint e, IoStatus i, Buffer *b, void *d)
+{
+ Connection cxn = (Connection) d ;
+
+ ASSERT (e == cxn->myEp) ;
+
+ clearTimer (cxn->writeBlockedTimerId) ;
+
+ /* The authinfo pass command has been sent, so start the response timer */
+ initReadBlockedTimeout (cxn) ;
+
+ if (i != IoDone)
+ {
+ d_printf (1,"%s:%d AUTHINFO PASS command failed to write\n",
+ hostPeerName (cxn->myHost), cxn->ident) ;
+
+ syswarn ("%s:%d cxnsleep can't write AUTHINFO PASS",
+ hostPeerName (cxn->myHost), cxn->ident) ;
+
+ cxnSleepOrDie (cxn) ;
+ }
+
+ freeBufferArray (b) ;
+}
+
+
+
+
+
+\f
+/*
+ * Called whenever some amount of data has been written to the pipe but
+ * more data remains to be written
+ */
+static void writeProgress (EndPoint e UNUSED, IoStatus i, Buffer *b UNUSED,
+ void *d)
+{
+ Connection cxn = (Connection) d ;
+
+ ASSERT (i == IoProgress) ;
+
+ if (cxn->writeTimeout > 0)
+ cxn->writeBlockedTimerId = updateSleep (cxn->writeBlockedTimerId,
+ writeTimeoutCbk, cxn->writeTimeout,
+ cxn) ;
+}
+
+
+
+
+\f
+/*
+ * Timers.
+ */
+
+/*
+ * This is called when the timeout for the reponse from the remote
+ * goes off. We tear down the connection and notify our host.
+ */
+static void responseTimeoutCbk (TimeoutId id, void *data)
+{
+ Connection cxn = (Connection) data ;
+ const char *peerName ;
+
+ ASSERT (id == cxn->readBlockedTimerId) ;
+ ASSERT (cxn->state == cxnConnectingS ||
+ cxn->state == cxnFeedingS ||
+ cxn->state == cxnFlushingS ||
+ cxn->state == cxnClosingS) ;
+ VALIDATE_CONNECTION (cxn) ;
+
+ /* XXX - let abortConnection clear readBlockedTimerId, otherwise
+ VALIDATE_CONNECTION() will croak */
+
+ peerName = hostPeerName (cxn->myHost) ;
+
+ warn ("%s:%d cxnsleep non-responsive connection", peerName, cxn->ident) ;
+ d_printf (1,"%s:%d shutting down non-repsonsive connection\n",
+ hostPeerName (cxn->myHost), cxn->ident) ;
+
+ cxnLogStats (cxn,true) ;
+
+ if (cxn->state == cxnClosingS)
+ {
+ abortConnection (cxn) ;
+ delConnection (cxn) ;
+ }
+ else
+ cxnSleep (cxn) ; /* will notify the Host */
+}
+
+
+
+
+\f
+/*
+ * This is called when the data write timeout for the remote
+ * goes off. We tear down the connection and notify our host.
+ */
+static void writeTimeoutCbk (TimeoutId id, void *data)
+{
+ Connection cxn = (Connection) data ;
+ const char *peerName ;
+
+ ASSERT (id == cxn->writeBlockedTimerId) ;
+ ASSERT (cxn->state == cxnConnectingS ||
+ cxn->state == cxnFeedingS ||
+ cxn->state == cxnFlushingS ||
+ cxn->state == cxnClosingS) ;
+ VALIDATE_CONNECTION (cxn) ;
+
+ /* XXX - let abortConnection clear writeBlockedTimerId, otherwise
+ VALIDATE_CONNECTION() will croak */
+
+ peerName = hostPeerName (cxn->myHost) ;
+
+ warn ("%s:%d cxnsleep write timeout", peerName, cxn->ident) ;
+ d_printf (1,"%s:%d shutting down non-responsive connection\n",
+ hostPeerName (cxn->myHost), cxn->ident) ;
+
+ cxnLogStats (cxn,true) ;
+
+ if (cxn->state == cxnClosingS)
+ {
+ abortConnection (cxn) ;
+ delConnection (cxn) ;
+ }
+ else
+ cxnSleep (cxn) ; /* will notify the Host */
+}
+
+
+
+
+\f
+/*
+ * Called by the EndPoint class when the timer goes off
+ */
+void reopenTimeoutCbk (TimeoutId id, void *data)
+{
+ Connection cxn = (Connection) data ;
+
+ ASSERT (id == cxn->sleepTimerId) ;
+
+ cxn->sleepTimerId = 0 ;
+
+ if (cxn->state != cxnSleepingS)
+ {
+ warn ("%s:%d cxnsleep connection in bad state: %s",
+ hostPeerName (cxn->myHost), cxn->ident,
+ stateToString (cxn->state)) ;
+ cxnSleepOrDie (cxn) ;
+ }
+ else
+ cxnConnect (cxn) ;
+}
+
+
+
+
+\f
+/*
+ * timeout callback to close down long running connection.
+ */
+static void flushCxnCbk (TimeoutId id, void *data)
+{
+ Connection cxn = (Connection) data ;
+
+ ASSERT (id == cxn->flushTimerId) ;
+ VALIDATE_CONNECTION (cxn) ;
+
+ cxn->flushTimerId = 0 ;
+
+ if (!(cxn->state == cxnFeedingS || cxn->state == cxnConnectingS ||
+ cxn->state == cxnIdleS))
+ {
+ warn ("%s:%d cxnsleep connection in bad state: %s",
+ hostPeerName (cxn->myHost), cxn->ident,
+ stateToString (cxn->state)) ;
+ cxnSleepOrDie (cxn) ;
+ }
+ else
+ {
+ d_printf (1,"%s:%d Handling periodic connection close.\n",
+ hostPeerName (cxn->myHost), cxn->ident) ;
+
+ notice ("%s:%d periodic close", hostPeerName (cxn->myHost), cxn->ident) ;
+
+ cxnFlush (cxn) ;
+ }
+}
+
+
+
+
+\f
+/*
+ * Timer callback for when the connection has not received an
+ * article from INN. When that happens we tear down the network
+ * connection to help recycle fds
+ */
+static void articleTimeoutCbk (TimeoutId id, void *data)
+{
+ Connection cxn = (Connection) data ;
+ const char *peerName = hostPeerName (cxn->myHost) ;
+
+ ASSERT (cxn->artReceiptTimerId == id) ;
+ VALIDATE_CONNECTION (cxn) ;
+
+ cxn->artReceiptTimerId = 0 ;
+
+ if (cxn->state != cxnIdleS)
+ {
+ warn ("%s:%d cxnsleep connection in bad state: %s",
+ hostPeerName (cxn->myHost), cxn->ident,
+ stateToString (cxn->state)) ;
+ cxnSleepOrDie (cxn) ;
+
+ return ;
+ }
+
+ /* it's doubtful (right?) that this timer could go off and there'd
+ still be articles in the queue. */
+ if (cxn->articleQTotal > 0)
+ {
+ warn ("%s:%d idle connection still has articles", peerName, cxn->ident) ;
+ }
+ else
+ {
+ notice ("%s:%d idle tearing down connection", peerName, cxn->ident) ;
+ cxn->state = cxnIdleTimeoutS ;
+ cxnFlush (cxn) ;
+ }
+}
+
+
+
+
+\f
+/*
+ * function to be called when the fd is not ready for reading, but there is
+ * an article on tape or in the queue to be done. Things are done this way
+ * so that a Connection doesn't hog time trying to find the next good
+ * article for writing. With a large backlog of expired articles that would
+ * take a long time. Instead the Connection just tries its next article on
+ * tape or queue, and if that's no good then it registers this callback so
+ * that other Connections have a chance of being serviced.
+ */
+static void cxnWorkProc (EndPoint ep UNUSED, void *data)
+{
+ Connection cxn = (Connection) data ;
+
+ d_printf (2,"%s:%d calling work proc\n",
+ hostPeerName (cxn->myHost),cxn->ident) ;
+
+ if (writesNeeded (cxn))
+ doSomeWrites (cxn) ; /* may re-register the work proc... */
+ else if (cxn->state == cxnFlushingS || cxn->state == cxnClosingS)
+ {
+ if (cxn->articleQTotal == 0)
+ issueQUIT (cxn) ;
+ }
+ else
+ d_printf (2,"%s:%d no writes were needed....\n",
+ hostPeerName (cxn->myHost), cxn->ident) ;
+}
+
+
+
+/****************************************************************************
+ *
+ * END EndPoint callback area.
+ *
+ ****************************************************************************/
+
+
+
+
+\f
+/****************************************************************************
+ *
+ * REPONSE CODE PROCESSING.
+ *
+ ***************************************************************************/
+
+
+/*
+ * A connection needs to sleep, but if it's closing it needs to die instead.
+ */
+static void cxnSleepOrDie (Connection cxn)
+{
+ if (cxn->state == cxnClosingS)
+ cxnDead (cxn) ;
+ else
+ cxnSleep (cxn) ;
+}
+
+
+/*
+ * Handle the response 205 to our QUIT command, which means the
+ * remote is going away and we can happily cleanup
+ */
+static void processResponse205 (Connection cxn, char *response UNUSED)
+{
+ bool immedRecon ;
+
+ VALIDATE_CONNECTION (cxn) ;
+
+ if (!(cxn->state == cxnFeedingS ||
+ cxn->state == cxnIdleS ||
+ cxn->state == cxnFlushingS ||
+ cxn->state == cxnClosingS))
+ {
+ warn ("%s:%d cxnsleep connection in bad state: %s",
+ hostPeerName (cxn->myHost), cxn->ident,
+ stateToString (cxn->state)) ;
+ cxnSleepOrDie (cxn) ;
+ return ;
+ }
+
+ switch (cxn->state)
+ {
+ case cxnFlushingS:
+ case cxnClosingS:
+ ASSERT (cxn->articleQTotal == 0) ;
+
+ cxnLogStats (cxn,true) ;
+
+ immedRecon = cxn->immedRecon ;
+
+ hostCxnDead (cxn->myHost,cxn) ;
+
+ if (cxn->state == cxnFlushingS && immedRecon)
+ {
+ abortConnection (cxn) ;
+ if (!cxnConnect (cxn))
+ notice ("%s:%d flush re-connect failed",
+ hostPeerName (cxn->myHost), cxn->ident) ;
+ }
+ else if (cxn->state == cxnFlushingS)
+ cxnWait (cxn) ;
+ else
+ cxnDead (cxn) ;
+ break ;
+
+ case cxnIdleS:
+ case cxnFeedingS:
+ /* this shouldn't ever happen... */
+ warn ("%s:%d cxnsleep response unexpected: %d",
+ hostPeerName (cxn->myHost), cxn->ident, 205) ;
+ cxnSleepOrDie (cxn) ;
+ break ;
+
+ default:
+ die ("Bad connection state: %s\n",stateToString (cxn->state)) ;
+ }
+}
+
+
+
+
+\f
+/*
+ * Handle a response code of 238 which is the "no such article"
+ * reply to the CHECK command (i.e. remote wants it).
+ */
+static void processResponse238 (Connection cxn, char *response)
+{
+ char *msgid ;
+ ArtHolder artHolder ;
+
+ if (!cxn->doesStreaming)
+ {
+ warn ("%s:%d cxnsleep unexpected streaming response for non-streaming"
+ " connection: %s", hostPeerName (cxn->myHost), cxn->ident,
+ response) ;
+ cxnSleepOrDie (cxn) ;
+ return ;
+ }
+
+ if (!(cxn->state == cxnFlushingS ||
+ cxn->state == cxnFeedingS ||
+ cxn->state == cxnClosingS))
+ {
+ warn ("%s:%d cxnsleep connection in bad state: %s",
+ hostPeerName (cxn->myHost), cxn->ident,
+ stateToString (cxn->state)) ;
+ cxnSleepOrDie (cxn) ;
+ return ;
+ }
+
+ VALIDATE_CONNECTION (cxn) ;
+
+ msgid = getMsgId (response) ;
+
+ if (cxn->checkRespHead == NULL) /* peer is confused */
+ {
+ warn ("%s:%d cxnsleep response unexpected: %d",
+ hostPeerName (cxn->myHost),cxn->ident,238) ;
+ cxnSleepOrDie (cxn) ;
+ }
+ else if (msgid == NULL || strlen (msgid) == 0 ||
+ (artHolder = artHolderByMsgId (msgid, cxn->checkRespHead)) == NULL)
+ noSuchMessageId (cxn,238,msgid,response) ;
+ else
+ {
+ /* now remove the article from the check queue and move it onto the
+ transmit queue. Another function wil take care of transmitting */
+ remArtHolder (artHolder, &cxn->checkRespHead, &cxn->articleQTotal) ;
+ if (cxn->state != cxnClosingS)
+ appendArtHolder (artHolder, &cxn->takeHead, &cxn->articleQTotal) ;
+ else
+ {
+ hostTakeBackArticle (cxn->myHost, cxn, artHolder->article) ;
+ delArtHolder (artHolder) ;
+ }
+ }
+
+ if (msgid != NULL)
+ free (msgid) ;
+}
+
+
+
+
+\f
+/*
+ * process the response "try again later" to the CHECK command If this
+ * returns true then the connection is still usable.
+ */
+static void processResponse431 (Connection cxn, char *response)
+{
+ char *msgid ;
+ ArtHolder artHolder ;
+
+ if (!cxn->doesStreaming)
+ {
+ warn ("%s:%d cxnsleep unexpected streaming response for non-streaming"
+ " connection: %s", hostPeerName (cxn->myHost), cxn->ident,
+ response) ;
+ cxnSleepOrDie (cxn) ;
+ return ;
+ }
+
+ if (!(cxn->state == cxnFlushingS ||
+ cxn->state == cxnFeedingS ||
+ cxn->state == cxnClosingS))
+ {
+ warn ("%s:%d cxnsleep connection in bad state: %s",
+ hostPeerName (cxn->myHost), cxn->ident,
+ stateToString (cxn->state)) ;
+ cxnSleepOrDie (cxn) ;
+ return ;
+ }
+
+ VALIDATE_CONNECTION (cxn) ;
+
+ msgid = getMsgId (response) ;
+
+ if (cxn->checkRespHead == NULL) /* peer is confused */
+ {
+ warn ("%s:%d cxnsleep response unexpected: %d",
+ hostPeerName (cxn->myHost),cxn->ident,431) ;
+ cxnSleepOrDie (cxn) ;
+ }
+ else if (msgid == NULL || strlen (msgid) == 0 ||
+ (artHolder = artHolderByMsgId (msgid, cxn->checkRespHead)) == NULL)
+ noSuchMessageId (cxn,431,msgid,response) ;
+ else
+ {
+ remArtHolder (artHolder, &cxn->checkRespHead, &cxn->articleQTotal) ;
+ if (cxn->articleQTotal == 0)
+ cxnIdle (cxn) ;
+ hostArticleDeferred (cxn->myHost, cxn, artHolder->article) ;
+ delArtHolder (artHolder) ;
+ }
+
+ if (msgid != NULL)
+ free (msgid) ;
+}
+
+
+
+
+\f
+/*
+ * process the "already have it" response to the CHECK command. If this
+ * returns true then the connection is still usable.
+ */
+static void processResponse438 (Connection cxn, char *response)
+{
+ char *msgid ;
+ ArtHolder artHolder ;
+
+ if (!cxn->doesStreaming)
+ {
+ warn ("%s:%d cxnsleep unexpected streaming response for non-streaming"
+ " connection: %s", hostPeerName (cxn->myHost), cxn->ident,
+ response) ;
+ cxnSleepOrDie (cxn) ;
+ return ;
+ }
+
+ if (!(cxn->state == cxnFlushingS ||
+ cxn->state == cxnFeedingS ||
+ cxn->state == cxnClosingS))
+ {
+ warn ("%s:%d cxnsleep connection in bad state: %s",
+ hostPeerName (cxn->myHost), cxn->ident,
+ stateToString (cxn->state)) ;
+ cxnSleepOrDie (cxn) ;
+ return ;
+ }
+
+ VALIDATE_CONNECTION (cxn) ;
+
+ msgid = getMsgId (response) ;
+
+ if (cxn->checkRespHead == NULL) /* peer is confused */
+ {
+ warn ("%s:%d cxnsleep response unexpected: %d",
+ hostPeerName (cxn->myHost),cxn->ident,438) ;
+ cxnSleepOrDie (cxn) ;
+ }
+ else if (msgid == NULL || strlen (msgid) == 0 ||
+ (artHolder = artHolderByMsgId (msgid, cxn->checkRespHead)) == NULL)
+ noSuchMessageId (cxn,438,msgid,response) ;
+ else
+ {
+ cxn->checksRefused++ ;
+
+ remArtHolder (artHolder, &cxn->checkRespHead, &cxn->articleQTotal) ;
+ if (cxn->articleQTotal == 0)
+ cxnIdle (cxn) ;
+ hostArticleNotWanted (cxn->myHost, cxn, artHolder->article);
+ delArtHolder (artHolder) ;
+ }
+
+ if (msgid != NULL)
+ free (msgid) ;
+}
+
+
+
+
+\f
+/*
+ * process the "article transferred ok" response to the TAKETHIS.
+ */
+static void processResponse239 (Connection cxn, char *response)
+{
+ char *msgid ;
+ ArtHolder artHolder ;
+
+ if (!cxn->doesStreaming)
+ {
+ warn ("%s:%d cxnsleep unexpected streaming response for non-streaming"
+ " connection: %s", hostPeerName (cxn->myHost), cxn->ident,
+ response) ;
+ cxnSleepOrDie (cxn) ;
+ return ;
+ }
+
+ if (!(cxn->state == cxnFlushingS ||
+ cxn->state == cxnFeedingS ||
+ cxn->state == cxnClosingS))
+ {
+ warn ("%s:%d cxnsleep connection in bad state: %s",
+ hostPeerName (cxn->myHost), cxn->ident,
+ stateToString (cxn->state)) ;
+ cxnSleepOrDie (cxn) ;
+ return ;
+ }
+
+ VALIDATE_CONNECTION (cxn) ;
+
+ msgid = getMsgId (response) ;
+
+ if (cxn->takeRespHead == NULL) /* peer is confused */
+ {
+ warn ("%s:%d cxnsleep response unexpected: %d",
+ hostPeerName (cxn->myHost),cxn->ident,239) ;
+ cxnSleepOrDie (cxn) ;
+ }
+ else if (msgid == NULL || strlen (msgid) == 0 ||
+ (artHolder = artHolderByMsgId (msgid, cxn->takeRespHead)) == NULL)
+ noSuchMessageId (cxn,239,msgid,response) ;
+ else
+ {
+ cxn->takesOkayed++ ;
+ cxn->takesSizeOkayed += artSize(artHolder->article);
+
+ remArtHolder (artHolder, &cxn->takeRespHead, &cxn->articleQTotal) ;
+ if (cxn->articleQTotal == 0)
+ cxnIdle (cxn) ;
+ hostArticleAccepted (cxn->myHost, cxn, artHolder->article) ;
+ delArtHolder (artHolder) ;
+ }
+
+ if (msgid != NULL)
+ free (msgid) ;
+}
+
+
+\f
+/*
+ * Set the thresholds for no-CHECK mode; negative means leave existing value
+ */
+
+void cxnSetCheckThresholds (Connection cxn,
+ double lowFilter, double highFilter,
+ double lowPassFilter)
+{
+ /* Adjust current value for new scaling */
+ if (cxn->lowPassFilter > 0.0)
+ cxn->filterValue = cxn->filterValue / cxn->lowPassFilter * lowPassFilter;
+
+ /* Stick in new values */
+ if (highFilter >= 0)
+ cxn->onThreshold = highFilter * lowPassFilter / 100.0;
+ if (lowFilter >= 0)
+ cxn->offThreshold = lowFilter * lowPassFilter / 100.0;
+ cxn->lowPassFilter = lowPassFilter;
+}
+
+\f
+/*
+ * Blow away the connection gracelessly and immedately clean up
+ */
+void cxnNuke (Connection cxn)
+{
+ abortConnection (cxn) ;
+ hostCxnDead (cxn->myHost,cxn) ;
+ delConnection(cxn) ;
+}
+
+\f
+/*
+ * process a "article rejected do not try again" response to the
+ * TAKETHIS.
+ */
+static void processResponse439 (Connection cxn, char *response)
+{
+ char *msgid ;
+ ArtHolder artHolder ;
+
+ if (!cxn->doesStreaming)
+ {
+ warn ("%s:%d cxnsleep unexpected streaming response for non-streaming"
+ " connection: %s", hostPeerName (cxn->myHost), cxn->ident,
+ response) ;
+ cxnSleepOrDie (cxn) ;
+ return ;
+ }
+
+ if (!(cxn->state == cxnFlushingS ||
+ cxn->state == cxnFeedingS ||
+ cxn->state == cxnClosingS))
+ {
+ warn ("%s:%d cxnsleep connection in bad state: %s",
+ hostPeerName (cxn->myHost), cxn->ident,
+ stateToString (cxn->state)) ;
+ cxnSleepOrDie (cxn) ;
+ return ;
+ }
+
+ VALIDATE_CONNECTION (cxn) ;
+
+ msgid = getMsgId (response) ;
+
+ if (cxn->takeRespHead == NULL) /* peer is confused */
+ {
+ /* NNTPRelay return 439 for check <messid> if messid is bad */
+ if (cxn->checkRespHead == NULL) /* peer is confused */
+ {
+ warn ("%s:%d cxnsleep response unexpected: %d",
+ hostPeerName (cxn->myHost),cxn->ident,439) ;
+ cxnSleepOrDie (cxn) ;
+ }
+ else
+ {
+ if ((artHolder = artHolderByMsgId (msgid, cxn->checkRespHead)) == NULL)
+ noSuchMessageId (cxn,439,msgid,response) ;
+ else
+ {
+ cxn->checksRefused++ ;
+ remArtHolder (artHolder, &cxn->checkRespHead, &cxn->articleQTotal) ;
+ if (cxn->articleQTotal == 0)
+ cxnIdle (cxn) ;
+ hostArticleNotWanted (cxn->myHost, cxn, artHolder->article);
+ delArtHolder (artHolder) ;
+ }
+ }
+ }
+ else if (msgid == NULL || strlen (msgid) == 0 ||
+ (artHolder = artHolderByMsgId (msgid, cxn->takeRespHead)) == NULL)
+ noSuchMessageId (cxn,439,msgid,response) ;
+ else
+ {
+ cxn->takesRejected++ ;
+ cxn->takesSizeRejected += artSize(artHolder->article);
+
+ remArtHolder (artHolder, &cxn->takeRespHead, &cxn->articleQTotal) ;
+ /* Some(?) hosts return the 439 response even before we're done
+ sending */
+ if (cxn->articleQTotal == 0 && !writeIsPending(cxn->myEp))
+ cxnIdle (cxn) ;
+ hostArticleRejected (cxn->myHost, cxn, artHolder->article) ;
+ delArtHolder (artHolder) ;
+ }
+
+ if (msgid != NULL)
+ free (msgid) ;
+}
+
+
+
+
+
+\f
+/*
+ * process the "article transferred ok" response to the IHAVE-body.
+ */
+static void processResponse235 (Connection cxn, char *response UNUSED)
+{
+ ArtHolder artHolder ;
+
+ if (cxn->doesStreaming)
+ {
+ warn ("%s:%d cxnsleep unexpected non-streaming response for"
+ " streaming connection: %s", hostPeerName (cxn->myHost),
+ cxn->ident,response) ;
+ cxnSleepOrDie (cxn) ;
+ return ;
+ }
+
+ if (!(cxn->state == cxnFlushingS ||
+ cxn->state == cxnFeedingS ||
+ cxn->state == cxnClosingS))
+ {
+ warn ("%s:%d cxnsleep connection in bad state: %s",
+ hostPeerName (cxn->myHost), cxn->ident,
+ stateToString (cxn->state)) ;
+ cxnSleepOrDie (cxn) ;
+ return ;
+ }
+
+ ASSERT (cxn->articleQTotal == 1) ;
+ ASSERT (cxn->takeRespHead != NULL) ;
+ VALIDATE_CONNECTION (cxn) ;
+
+ if (cxn->takeRespHead == NULL) /* peer is confused */
+ {
+ warn ("%s:%d cxnsleep response unexpected: %d",
+ hostPeerName (cxn->myHost),cxn->ident,235) ;
+ cxnSleepOrDie (cxn) ;
+ }
+ else
+ {
+ /* now remove the article from the queue and tell the Host to forget
+ about it. */
+ artHolder = cxn->takeRespHead ;
+
+ cxn->takeRespHead = NULL ;
+ cxn->articleQTotal = 0 ;
+ cxn->takesOkayed++ ;
+ cxn->takesSizeOkayed += artSize(artHolder->article);
+
+ if (cxn->articleQTotal == 0)
+ cxnIdle (cxn) ;
+
+ hostArticleAccepted (cxn->myHost, cxn, artHolder->article) ;
+ delArtHolder (artHolder) ;
+ }
+}
+
+
+
+
+\f
+/*
+ * process the "send article to be transfered" reponse to the IHAVE.
+ */
+static void processResponse335 (Connection cxn, char *response UNUSED)
+{
+ if (cxn->doesStreaming)
+ {
+ warn ("%s:%d cxnsleep unexpected non-streaming response for"
+ " streaming connection: %s", hostPeerName (cxn->myHost),
+ cxn->ident,response) ;
+ cxnSleepOrDie (cxn) ;
+ return ;
+ }
+
+ if (!(cxn->state == cxnFlushingS ||
+ cxn->state == cxnFeedingS ||
+ cxn->state == cxnClosingS))
+ {
+ warn ("%s:%d cxnsleep connection in bad state: %s",
+ hostPeerName (cxn->myHost), cxn->ident,
+ stateToString (cxn->state)) ;
+ cxnSleepOrDie (cxn) ;
+ return ;
+ }
+
+ if (cxn->checkRespHead == NULL)
+ {
+ warn ("%s:%d cxnsleep response unexpected: %d",
+ hostPeerName (cxn->myHost),cxn->ident,335) ;
+ cxnSleepOrDie (cxn) ;
+ }
+ else
+ {
+ VALIDATE_CONNECTION (cxn) ;
+ /* now move the article into the third queue */
+ cxn->takeHead = cxn->checkRespHead ;
+ cxn->checkRespHead = NULL ;
+
+ issueIHAVEBody (cxn) ;
+ }
+}
+
+
+
+
+\f
+/*
+ * process the "not accepting articles" response. This could be to any of
+ * the IHAVE/CHECK/TAKETHIS command, but not the banner--that's handled
+ * elsewhere.
+ */
+static void processResponse400 (Connection cxn, char *response)
+{
+ if (!(cxn->state == cxnFlushingS ||
+ cxn->state == cxnFeedingS ||
+ cxn->state == cxnIdleS ||
+ cxn->state == cxnClosingS))
+ {
+ warn ("%s:%d cxnsleep connection in bad state: %s",
+ hostPeerName (cxn->myHost), cxn->ident,
+ stateToString (cxn->state)) ;
+ cxnSleepOrDie (cxn) ;
+ return ;
+ }
+
+ VALIDATE_CONNECTION (cxn) ;
+
+ /* We may get a response 400 multiple times when in streaming mode. */
+ notice ("%s:%d remote cannot accept articles: %s",
+ hostPeerName(cxn->myHost), cxn->ident, response) ;
+
+ /* right here there may still be data queued to write and so we'll fail
+ trying to issue the quit ('cause a write will be pending). Furthermore,
+ the data pending may be half way through an command, and so just
+ tossing the buffer is nt sufficient. But figuring out where we are and
+ doing a tidy job is hard */
+ if (writeIsPending (cxn->myEp))
+ cxnSleepOrDie (cxn) ;
+ else
+ {
+ if (cxn->articleQTotal > 0)
+ {
+ /* Defer the articles here so that cxnFlush() doesn't set up an
+ immediate reconnect. */
+ deferAllArticles (cxn) ;
+ clearTimer (cxn->readBlockedTimerId) ;
+ /* XXX - so cxnSleep() doesn't die when it validates the connection */
+ cxnIdle (cxn) ;
+ }
+ /* XXX - it would be nice if we QUIT first, but we'd have to go
+ into a state where we just search for the 205 response, and
+ only go into the sleep state at that point */
+ cxnSleepOrDie (cxn) ;
+ }
+}
+
+
+
+
+\f
+/*
+ * process the "not wanted" reponse to the IHAVE.
+ */
+static void processResponse435 (Connection cxn, char *response UNUSED)
+{
+ ArtHolder artHolder ;
+
+ if (cxn->doesStreaming)
+ {
+ warn ("%s:%d cxnsleep unexpected non-streaming response for"
+ " streaming connection: %s", hostPeerName (cxn->myHost),
+ cxn->ident,response) ;
+ cxnSleepOrDie (cxn) ;
+ return ;
+ }
+
+ if (!(cxn->state == cxnFlushingS ||
+ cxn->state == cxnFeedingS ||
+ cxn->state == cxnClosingS))
+ {
+ warn ("%s:%d cxnsleep connection in bad state: %s",
+ hostPeerName (cxn->myHost), cxn->ident,
+ stateToString (cxn->state)) ;
+ cxnSleepOrDie (cxn) ;
+ return ;
+ }
+
+ /* Some servers, such as early versions of Diablo, had a bug where they'd
+ respond with a 435 code (which should only be used for refusing an
+ article before it was offered) after an article has been sent. */
+ if (cxn->checkRespHead == NULL)
+ {
+ warn ("%s:%d cxnsleep response unexpected: %d",
+ hostPeerName (cxn->myHost), cxn->ident, 435) ;
+ cxnSleepOrDie (cxn) ;
+ return ;
+ }
+
+ ASSERT (cxn->articleQTotal == 1) ;
+ VALIDATE_CONNECTION (cxn) ;
+
+ cxn->articleQTotal-- ;
+ cxn->checksRefused++ ;
+
+ artHolder = cxn->checkRespHead ;
+ cxn->checkRespHead = NULL ;
+
+ if (cxn->articleQTotal == 0 && !writeIsPending(cxn->myEp))
+ cxnIdle (cxn) ;
+
+ hostArticleNotWanted (cxn->myHost, cxn, artHolder->article) ;
+ delArtHolder (artHolder) ;
+
+#if 0
+ d_printf (1,"%s:%d On exiting 435 article queue total is %d (%d %d %d %d)\n",
+ hostPeerName (cxn->myHost), cxn->ident,
+ cxn->articleQTotal,
+ (int) (cxn->checkHead != NULL),
+ (int) (cxn->checkRespHead != NULL),
+ (int) (cxn->takeHead != NULL),
+ (int) (cxn->takeRespHead != NULL));
+#endif
+}
+
+
+
+
+\f
+/*
+ * process the "transfer failed" response to the IHAVE-body, (seems this
+ * can come from the IHAVE too).
+ */
+static void processResponse436 (Connection cxn, char *response UNUSED)
+{
+ ArtHolder artHolder ;
+
+ if (cxn->doesStreaming)
+ {
+ warn ("%s:%d cxnsleep unexpected non-streaming response for"
+ " streaming connection: %s", hostPeerName (cxn->myHost),
+ cxn->ident,response) ;
+ cxnSleepOrDie (cxn) ;
+ return ;
+ }
+
+ if (!(cxn->state == cxnFlushingS ||
+ cxn->state == cxnFeedingS ||
+ cxn->state == cxnClosingS))
+ {
+ warn ("%s:%d cxnsleep connection in bad state: %s",
+ hostPeerName (cxn->myHost), cxn->ident,
+ stateToString (cxn->state)) ;
+ cxnSleepOrDie (cxn) ;
+ return ;
+ }
+
+ ASSERT (cxn->articleQTotal == 1) ;
+ ASSERT (cxn->takeRespHead != NULL || cxn->checkRespHead != NULL) ;
+ VALIDATE_CONNECTION (cxn) ;
+
+ cxn->articleQTotal-- ;
+
+ if (cxn->takeRespHead != NULL) /* IHAVE-body command barfed */
+ {
+ artHolder = cxn->takeRespHead ;
+ cxn->takeRespHead = NULL ;
+ }
+ else /* IHAVE command barfed */
+ {
+ artHolder = cxn->checkRespHead ;
+ cxn->checkRespHead = NULL ;
+ }
+
+ if (cxn->articleQTotal == 0 && !writeIsPending(cxn->myEp))
+ cxnIdle (cxn) ;
+
+ hostArticleDeferred (cxn->myHost, cxn, artHolder->article) ;
+ delArtHolder (artHolder) ;
+}
+
+
+
+
+\f
+/*
+ * Process the "article rejected do not try again" response to the
+ * IHAVE-body.
+ */
+static void processResponse437 (Connection cxn, char *response UNUSED)
+{
+ ArtHolder artHolder ;
+
+ if (cxn->doesStreaming)
+ {
+ warn ("%s:%d cxnsleep unexpected non-streaming response for"
+ " streaming connection: %s", hostPeerName (cxn->myHost),
+ cxn->ident,response) ;
+ cxnSleepOrDie (cxn) ;
+ return ;
+ }
+
+ if (!(cxn->state == cxnFlushingS ||
+ cxn->state == cxnFeedingS ||
+ cxn->state == cxnClosingS))
+ {
+ warn ("%s:%d cxnsleep connection in bad state: %s",
+ hostPeerName (cxn->myHost), cxn->ident,
+ stateToString (cxn->state)) ;
+ cxnSleepOrDie (cxn) ;
+ return ;
+ }
+
+ ASSERT (cxn->articleQTotal == 1) ;
+ ASSERT (cxn->takeRespHead != NULL) ;
+ VALIDATE_CONNECTION (cxn) ;
+
+ cxn->articleQTotal-- ;
+ cxn->takesRejected++ ;
+
+ artHolder = cxn->takeRespHead ;
+ cxn->takeRespHead = NULL ;
+ cxn->takesSizeRejected += artSize(artHolder->article);
+
+ /* Some servers return the 437 response before we're done sending. */
+ if (cxn->articleQTotal == 0 && !writeIsPending (cxn->myEp))
+ cxnIdle (cxn) ;
+
+ hostArticleRejected (cxn->myHost, cxn, artHolder->article) ;
+ delArtHolder (artHolder) ;
+}
+
+
+/* Process the response 480 Transfer permission defined. We're probably
+ talking to a remote nnrpd on a system that forgot to put us in
+ the hosts.nntp */
+static void processResponse480 (Connection cxn, char *response UNUSED)
+{
+ if (cxn->doesStreaming)
+ {
+ warn ("%s:%d cxnsleep unexpected non-streaming response for"
+ " streaming connection: %s", hostPeerName (cxn->myHost),
+ cxn->ident,response) ;
+ cxnSleepOrDie (cxn) ;
+ return ;
+ }
+
+ if (!(cxn->state == cxnFlushingS ||
+ cxn->state == cxnFeedingS ||
+ cxn->state == cxnClosingS))
+ {
+ warn ("%s:%d cxnsleep connection in bad state: %s",
+ hostPeerName (cxn->myHost), cxn->ident,
+ stateToString (cxn->state)) ;
+ cxnSleepOrDie (cxn) ;
+ return ;
+ }
+
+ VALIDATE_CONNECTION (cxn) ;
+
+ warn ("%s:%d cxnsleep transfer permission denied",
+ hostPeerName (cxn->myHost), cxn->ident) ;
+
+ if (cxn->state == cxnClosingS)
+ cxnDead (cxn) ;
+ else
+ cxnSleep (cxn) ;
+}
+
+
+
+
+\f
+/*
+ * Handle the response 503, which means the timeout of nnrpd.
+ */
+static void processResponse503 (Connection cxn, char *response UNUSED)
+{
+ bool immedRecon ;
+
+ VALIDATE_CONNECTION (cxn) ;
+
+ if (!(cxn->state == cxnFeedingS ||
+ cxn->state == cxnIdleS ||
+ cxn->state == cxnFlushingS ||
+ cxn->state == cxnClosingS))
+ {
+ warn ("%s:%d cxnsleep connection in bad state: %s",
+ hostPeerName (cxn->myHost), cxn->ident,
+ stateToString (cxn->state)) ;
+ cxnSleepOrDie (cxn) ;
+ return ;
+ }
+
+ if (cxn->articleQTotal != 0)
+ notice ("%s:%d flush re-connect failed", hostPeerName (cxn->myHost),
+ cxn->ident) ;
+
+ cxnLogStats (cxn,true) ;
+
+ immedRecon = cxn->immedRecon ;
+
+ hostCxnDead (cxn->myHost,cxn) ;
+
+ if (cxn->state == cxnFlushingS && immedRecon)
+ {
+ abortConnection (cxn) ;
+ if (!cxnConnect (cxn))
+ notice ("%s:%d flush re-connect failed", hostPeerName (cxn->myHost),
+ cxn->ident) ;
+ }
+ else if (cxn->state == cxnFlushingS)
+ cxnWait (cxn) ;
+ else
+ cxnDead (cxn) ;
+
+}
+
+
+
+
+\f
+/****************************************************************************
+ *
+ * END REPONSE CODE PROCESSING.
+ *
+ ***************************************************************************/
+
+
+
+
+\f
+/*
+ * puts the Connection into the sleep state.
+ */
+static void cxnSleep (Connection cxn)
+{
+ ASSERT (cxn != NULL) ;
+ ASSERT (cxn->state == cxnFlushingS ||
+ cxn->state == cxnIdleS ||
+ cxn->state == cxnFeedingS ||
+ cxn->state == cxnConnectingS) ;
+ VALIDATE_CONNECTION (cxn) ;
+
+ abortConnection (cxn) ;
+
+ prepareReopenCbk (cxn) ; /* XXX - we don't want to reopen if idle */
+ cxn->state = cxnSleepingS ;
+
+ /* tell our Host we're asleep so it doesn't try to give us articles */
+ hostCxnSleeping (cxn->myHost,cxn) ;
+}
+
+
+
+static void cxnDead (Connection cxn)
+{
+ ASSERT (cxn != NULL) ;
+ VALIDATE_CONNECTION (cxn) ;
+
+ abortConnection (cxn) ;
+ cxn->state = cxnDeadS ;
+}
+
+
+
+/*
+ * Sets the idle timer. If no articles arrive before the timer expires, the
+ * connection will be closed.
+ */
+static void cxnIdle (Connection cxn)
+{
+ ASSERT (cxn != NULL) ;
+ ASSERT (cxn->state == cxnFeedingS || cxn->state == cxnConnectingS ||
+ cxn->state == cxnFlushingS || cxn->state == cxnClosingS) ;
+ ASSERT (cxn->articleQTotal == 0) ;
+ ASSERT (cxn->writeBlockedTimerId == 0) ;
+ ASSERT (!writeIsPending (cxn->myEp)) ;
+ ASSERT (cxn->sleepTimerId == 0) ;
+
+ if (cxn->state == cxnFeedingS || cxn->state == cxnConnectingS)
+ {
+ if (cxn->articleReceiptTimeout > 0)
+ {
+ clearTimer (cxn->artReceiptTimerId) ;
+ cxn->artReceiptTimerId = prepareSleep (articleTimeoutCbk,
+ cxn->articleReceiptTimeout,
+ cxn) ;
+ }
+
+ if (cxn->readTimeout > 0 && cxn->state == cxnFeedingS)
+ clearTimer (cxn->readBlockedTimerId) ;
+
+ cxn->state = cxnIdleS ;
+ASSERT (cxn->readBlockedTimerId == 0) ;
+ }
+}
+
+
+
+
+\f
+/*
+ * Called when a response from the remote refers to a non-existant
+ * message-id. The network connection is aborted and the Connection
+ * object goes into sleep mode.
+ */
+static void noSuchMessageId (Connection cxn, unsigned int responseCode,
+ const char *msgid, const char *response)
+{
+ const char *peerName = hostPeerName (cxn->myHost) ;
+
+ if (msgid == NULL || strlen (msgid) == 0)
+ warn ("%s:%d cxnsleep message-id missing in reponse code %d: %s",
+ peerName, cxn->ident, responseCode, response) ;
+ else
+ warn ("%s:%d cxnsleep message-id invalid message-id in reponse code"
+ " %d: %s", peerName, cxn->ident, responseCode, msgid) ;
+
+ cxnLogStats (cxn,true) ;
+
+ if (cxn->state != cxnClosingS)
+ cxnSleep (cxn) ;
+ else
+ cxnDead (cxn) ;
+}
+
+
+
+
+\f
+/*
+ * a processing error has occured (for example in parsing a response), or
+ * we're at the end of the FSM and we're cleaning up.
+ */
+static void abortConnection (Connection cxn)
+{
+ ASSERT (cxn != NULL) ;
+ VALIDATE_CONNECTION (cxn) ;
+
+ /* cxn->myEp could be NULL if we get here during cxnConnect (via
+ cxnWait()) */
+ if (cxn->myEp != NULL)
+ {
+
+ delEndPoint (cxn->myEp) ;
+ cxn->myEp = NULL ;
+ }
+
+ clearTimer (cxn->sleepTimerId) ;
+ clearTimer (cxn->artReceiptTimerId) ;
+ clearTimer (cxn->readBlockedTimerId) ;
+ clearTimer (cxn->writeBlockedTimerId) ;
+ clearTimer (cxn->flushTimerId) ;
+
+ deferAllArticles (cxn) ; /* give any articles back to Host */
+
+ bufferSetDataSize (cxn->respBuffer,0) ;
+
+ resetConnection (cxn) ;
+
+ if (cxn->state == cxnFeedingS ||
+ cxn->state == cxnIdleS ||
+ cxn->state == cxnFlushingS ||
+ cxn->state == cxnClosingS)
+ hostCxnDead (cxn->myHost,cxn) ;
+}
+
+
+
+
+/*
+ * Set up the callback used when the Connection is sleeping (i.e. will try
+ * to reopen the connection).
+ */
+static void prepareReopenCbk (Connection cxn)
+{
+ ASSERT (cxn->sleepTimerId == 0) ;
+
+ if (!(cxn->state == cxnConnectingS ||
+ cxn->state == cxnIdleS ||
+ cxn->state == cxnFeedingS ||
+ cxn->state == cxnFlushingS ||
+ cxn->state == cxnStartingS))
+ {
+ warn ("%s:%d cxnsleep connection in bad state: %s",
+ hostPeerName (cxn->myHost), cxn->ident,
+ stateToString (cxn->state)) ;
+ cxnSleepOrDie (cxn) ;
+ return ;
+ }
+
+ d_printf (1,"%s:%d Setting up a reopen callback\n",
+ hostPeerName (cxn->myHost), cxn->ident) ;
+
+ cxn->sleepTimerId = prepareSleep (reopenTimeoutCbk, cxn->sleepTimeout, cxn) ;
+
+ /* bump the sleep timer amount each time to wait longer and longer. Gets
+ reset in resetConnection() */
+ cxn->sleepTimeout *= 2 ;
+ if (cxn->sleepTimeout > max_reconnect_period)
+ cxn->sleepTimeout = max_reconnect_period ;
+}
+
+
+
+
+\f
+/*
+ * (re)set all state variables to inital condition.
+ */
+static void resetConnection (Connection cxn)
+{
+ ASSERT (cxn != NULL) ;
+
+ bufferSetDataSize (cxn->respBuffer,0) ;
+
+ cxn->loggedNoCr = false ;
+ cxn->maxCheck = 1 ;
+ cxn->immedRecon = false ;
+ cxn->doesStreaming = false ; /* who knows, next time around maybe... */
+ cxn->authenticated = false ;
+ cxn->quitWasIssued = false ;
+ cxn->needsChecks = true ;
+ cxn->timeCon = 0 ;
+
+ cxn->artsTaken = 0 ;
+ cxn->checksIssued = 0 ;
+ cxn->checksRefused = 0 ;
+ cxn->takesRejected = 0 ;
+ cxn->takesOkayed = 0 ;
+ cxn->takesSizeRejected = 0 ;
+ cxn->takesSizeOkayed = 0 ;
+
+ cxn->filterValue = 0.0 ;
+}
+
+
+\f
+/*
+ * Give back all articles that are queued, but not actually in progress.
+ * XXX merge come of this with deferAllArticles
+ */
+static void deferQueuedArticles (Connection cxn)
+{
+ ArtHolder p, q ;
+
+ for (q = NULL, p = cxn->checkHead ; p != NULL ; p = q)
+ {
+ q = p->next ;
+ hostTakeBackArticle (cxn->myHost, cxn, p->article) ;
+ delArtHolder (p) ;
+ cxn->articleQTotal-- ;
+ }
+ cxn->checkHead = NULL ;
+
+ for (q = NULL, p = cxn->takeHead ; cxn->doesStreaming && p != NULL ; p = q)
+ {
+ q = p->next ;
+ hostTakeBackArticle (cxn->myHost, cxn, p->article) ;
+ delArtHolder (p) ;
+ cxn->articleQTotal-- ;
+ }
+ cxn->takeHead = NULL ;
+}
+
+
+\f
+/*
+ * Give back any articles we have to the Host for later retrys.
+ */
+static void deferAllArticles (Connection cxn)
+{
+ ArtHolder p, q ;
+
+ for (q = NULL, p = cxn->checkHead ; p != NULL ; p = q)
+ {
+ q = p->next ;
+ hostTakeBackArticle (cxn->myHost, cxn, p->article) ;
+ delArtHolder (p) ;
+ cxn->articleQTotal-- ;
+ }
+ cxn->checkHead = NULL ;
+
+ for (q = NULL, p = cxn->checkRespHead ; p != NULL ; p = q)
+ {
+ q = p->next ;
+ hostTakeBackArticle (cxn->myHost, cxn, p->article) ;
+ delArtHolder (p) ;
+ cxn->articleQTotal-- ;
+ }
+ cxn->checkRespHead = NULL ;
+
+ for (q = NULL, p = cxn->takeHead ; p != NULL ; p = q)
+ {
+ q = p->next ;
+ hostTakeBackArticle (cxn->myHost, cxn, p->article) ;
+ delArtHolder (p) ;
+ cxn->articleQTotal-- ;
+ }
+ cxn->takeHead = NULL ;
+
+ for (q = NULL, p = cxn->takeRespHead ; p != NULL ; p = q)
+ {
+ q = p->next ;
+ hostTakeBackArticle (cxn->myHost, cxn, p->article) ;
+ delArtHolder (p) ;
+ cxn->articleQTotal-- ;
+ }
+ cxn->takeRespHead = NULL ;
+
+ ASSERT (cxn->articleQTotal == 0) ;
+}
+
+
+
+
+\f
+/*
+ * Called when there's an article to be pushed out to the remote. Even if
+ * the Connection has an article it's possible that nothing will be written
+ * (e.g. if the article on the queue doesn't exist any more)
+ */
+static void doSomeWrites (Connection cxn)
+{
+ bool doneSome = false ;
+
+ /* If there's a write pending we can't do anything now. */
+ if ( writeIsPending (cxn->myEp) )
+ return ;
+ else if ( writesNeeded (cxn) ) /* something on a queue. */
+ {
+ if (cxn->doesStreaming)
+ doneSome = issueStreamingCommands (cxn) ;
+ else
+ doneSome = issueIHAVE (cxn) ;
+
+ /* doneSome will be false if article(s) were gone, but if the Host
+ has something available, then it would have been put on the queue
+ for next time around. */
+ if (!doneSome)
+ {
+ if (writesNeeded (cxn)) /* Host gave us something */
+ addWorkCallback (cxn->myEp,cxnWorkProc,cxn) ; /* for next time. */
+ else if (cxn->articleQTotal == 0)
+ {
+ /* if we were in cxnFeedingS, then issueStreamingCommands
+ already called cxnIdle(). */
+ if (cxn->state == cxnClosingS || cxn->state == cxnFlushingS)
+ issueQUIT (cxn) ; /* and nothing to wait for... */
+ }
+ }
+ }
+ else if (cxn->state == cxnClosingS || cxn->state == cxnFlushingS)
+ { /* nothing to do... */
+ if (cxn->articleQTotal == 0)
+ issueQUIT (cxn) ; /* and nothing to wait for before closing */
+ }
+}
+
+
+
+
+\f
+/* Queue up a buffer with the IHAVE command in it for the article at
+ * the head of the transmisson queue.
+ *
+ * If the article is missing, then the Host will be notified and
+ * another article may be put on the Connections queue. This new
+ * article is ignored for now, but a work callback is registered so
+ * that it can be looked at later.
+ */
+static bool issueIHAVE (Connection cxn)
+{
+ Buffer ihaveBuff, *writeArr ;
+ ArtHolder artH ;
+ Article article ;
+ const char *msgid ;
+ char *p ;
+ unsigned int tmp ;
+ size_t bufLen = 256 ;
+ bool rval = false ;
+
+ ASSERT (!cxn->doesStreaming) ;
+ ASSERT (cxn->state == cxnFlushingS ||
+ cxn->state == cxnFeedingS ||
+ cxn->state == cxnClosingS) ;
+ ASSERT (cxn->articleQTotal == 1) ;
+ ASSERT (cxn->checkHead != NULL) ;
+ ASSERT (writeIsPending (cxn->myEp) == false) ;
+ VALIDATE_CONNECTION (cxn) ;
+
+ artH = cxn->checkHead ;
+ article = cxn->checkHead->article ;
+ msgid = artMsgId (artH->article) ;
+
+ ASSERT (msgid != NULL) ;
+ ASSERT (article != NULL) ;
+
+ if ((tmp = (strlen (msgid) + 10)) > bufLen)
+ bufLen = tmp ;
+
+ ihaveBuff = newBuffer (bufLen) ;
+
+ ASSERT (ihaveBuff != NULL) ;
+
+ p = bufferBase (ihaveBuff) ;
+ sprintf (p, "IHAVE %s\r\n", msgid) ;
+ bufferSetDataSize (ihaveBuff, strlen (p)) ;
+
+ d_printf (5,"%s:%d Command IHAVE %s\n",
+ hostPeerName (cxn->myHost),cxn->ident,msgid) ;
+
+ writeArr = makeBufferArray (ihaveBuff, NULL) ;
+ if ( !prepareWriteWithTimeout (cxn->myEp, writeArr, commandWriteDone,
+ cxn) )
+ {
+ die ("%s:%d fatal prepare write for IHAVE failed",
+ hostPeerName (cxn->myHost), cxn->ident) ;
+ }
+
+ /* now move the article to the second queue */
+ cxn->checkRespHead = cxn->checkHead ;
+ cxn->checkHead = NULL ;
+
+ cxn->checksIssued++ ;
+ hostArticleOffered (cxn->myHost, cxn) ;
+
+ rval = true ;
+
+ return rval ;
+}
+
+
+
+
+\f
+/*
+ * Do a prepare write with the article body as the body portion of the
+ * IHAVE command
+ */
+static void issueIHAVEBody (Connection cxn)
+{
+ Buffer *writeArray ;
+ Article article ;
+
+ ASSERT (cxn != NULL) ;
+ ASSERT (!cxn->doesStreaming) ;
+ ASSERT (cxn->state == cxnFlushingS ||
+ cxn->state == cxnFeedingS ||
+ cxn->state == cxnClosingS) ;
+ ASSERT (cxn->articleQTotal == 1) ;
+ ASSERT (cxn->takeHead != NULL) ;
+ VALIDATE_CONNECTION (cxn) ;
+
+ article = cxn->takeHead->article ;
+ ASSERT (article != NULL) ;
+
+ if (cxn->state != cxnClosingS)
+ writeArray = artGetNntpBuffers (article) ;
+ else
+ writeArray = NULL ;
+
+ if (writeArray == NULL)
+ {
+ /* missing article (expired for example) will get us here. */
+ if (dotBuffer == NULL)
+ {
+ dotBuffer = newBufferByCharP (".\r\n",3,3) ;
+ dotFirstBuffer = newBufferByCharP ("\r\n.",3,3) ;
+ crlfBuffer = newBufferByCharP ("\r\n",2,2) ;
+ }
+
+ /* we'll just write the empty buffer and the remote will complain
+ with 437 */
+ writeArray = makeBufferArray (bufferTakeRef (dotBuffer),NULL) ;
+ }
+
+
+ if ( !prepareWriteWithTimeout (cxn->myEp, writeArray, ihaveBodyDone, cxn) )
+ {
+ die ("%s:%d fatal prepare write failed in issueIHAVEBody",
+ hostPeerName (cxn->myHost), cxn->ident) ;
+ }
+ else
+ {
+ d_printf (5,"%s:%d prepared write for IHAVE body.\n",
+ hostPeerName (cxn->myHost),cxn->ident) ;
+ }
+
+ /* now move the article to the last queue */
+ cxn->takeRespHead = cxn->takeHead ;
+ cxn->takeHead = NULL ;
+
+ return ;
+}
+
+
+
+
+\f
+/* Process the two command queues. Slaps all the CHECKs together and
+ * then does the TAKETHIS commands.
+ *
+ * If no articles on the queue(s) are valid, then the Host is
+ * notified. It may queue up new articles on the Connection, but
+ * these are ignored for now. A work proc is registered so the
+ * articles can be processed later.
+ */
+static bool issueStreamingCommands (Connection cxn)
+{
+ Buffer checkBuffer = NULL ; /* the buffer with the CHECK commands in it. */
+ Buffer *writeArray = NULL ;
+ ArtHolder p, q ;
+ bool rval = false ;
+
+ ASSERT (cxn != NULL) ;
+ ASSERT (cxn->myEp != NULL) ;
+ ASSERT (cxn->doesStreaming) ;
+ VALIDATE_CONNECTION (cxn) ;
+
+ checkBuffer = buildCheckBuffer (cxn) ; /* may be null if none to issue */
+
+ if (checkBuffer != NULL)
+ {
+ /* Now shift the articles to their new queue. */
+ for (p = cxn->checkRespHead ; p != NULL && p->next != NULL ; p = p->next)
+ /* nada--finding end of queue*/ ;
+
+ if (p == NULL)
+ cxn->checkRespHead = cxn->checkHead ;
+ else
+ p->next = cxn->checkHead ;
+
+ cxn->checkHead = NULL ;
+ }
+
+
+ writeArray = buildTakethisBuffers (cxn,checkBuffer) ; /* may be null */
+
+ /* If not null, then writeArray will have checkBuffer (if it wasn't NULL)
+ in the first spot and the takethis buffers after that. */
+ if (writeArray)
+ {
+ if ( !prepareWriteWithTimeout (cxn->myEp, writeArray,
+ commandWriteDone, cxn) )
+ {
+ die ("%s:%d fatal prepare write for STREAMING commands failed",
+ hostPeerName (cxn->myHost), cxn->ident) ;
+ }
+
+ rval = true ;
+
+ /* now shift articles over to their new queue. */
+ for (p = cxn->takeRespHead ; p != NULL && p->next != NULL ; p = p->next)
+ /* nada--finding end of queue */ ;
+
+ if (p == NULL)
+ cxn->takeRespHead = cxn->takeHead ;
+ else
+ p->next = cxn->takeHead ;
+
+ cxn->takeHead = NULL ;
+ }
+
+ /* we defer the missing article notification to here because if there
+ was a big backlog of missing articles *and* we're running in
+ no-CHECK mode, then the Host would be putting bad articles on the
+ queue we're taking them off of. */
+ if (cxn->missing && cxn->articleQTotal == 0)
+ cxnIdle (cxn) ;
+ for (p = cxn->missing ; p != NULL ; p = q)
+ {
+ hostArticleIsMissing (cxn->myHost, cxn, p->article) ;
+ q = p->next ;
+ delArtHolder (p) ;
+ }
+ cxn->missing = NULL ;
+
+ return rval ;
+}
+
+
+
+
+\f
+/*
+ * build up the buffer of all the CHECK commands.
+ */
+static Buffer buildCheckBuffer (Connection cxn)
+{
+ ArtHolder p ;
+ size_t lenBuff = 0 ;
+ Buffer checkBuffer = NULL ;
+ const char *peerName = hostPeerName (cxn->myHost) ;
+
+ p = cxn->checkHead ;
+ while (p != NULL)
+ {
+ Article article = p->article ;
+ const char *msgid ;
+
+ msgid = artMsgId (article) ;
+
+ lenBuff += (8 + strlen (msgid)) ; /* 8 == strlen("CHECK \r\n") */
+ p = p->next ;
+ }
+
+ if (lenBuff > 0)
+ lenBuff++ ; /* for the null byte */
+
+ /* now build up the single buffer that contains all the CHECK commands */
+ if (lenBuff > 0)
+ {
+ char *t ;
+ size_t tlen = 0 ;
+
+ checkBuffer = newBuffer (lenBuff) ;
+ t = bufferBase (checkBuffer) ;
+
+ p = cxn->checkHead ;
+ while (p != NULL)
+ {
+ const char *msgid = artMsgId (p->article) ;
+
+ sprintf (t,"CHECK %s\r\n", msgid) ;
+ d_printf (5,"%s:%d Command %s\n", peerName, cxn->ident, t) ;
+
+ tlen += strlen (t) ;
+
+ while ( *t ) t++ ;
+
+ cxn->checksIssued++ ;
+ hostArticleOffered (cxn->myHost,cxn) ;
+
+ p = p->next ;
+ }
+
+ ASSERT (tlen + 1 == lenBuff) ;
+
+ bufferSetDataSize (checkBuffer, tlen) ;
+ }
+
+ return checkBuffer ;
+}
+
+
+
+
+
+\f
+/*
+ * Construct and array of TAKETHIS commands and the command bodies. Any
+ * articles on the queue that are missing will be removed and the Host will
+ * be informed.
+ */
+static Buffer *buildTakethisBuffers (Connection cxn, Buffer checkBuffer)
+{
+ size_t lenArray = 0 ;
+ ArtHolder p, q ;
+ Buffer *rval = NULL ;
+ const char *peerName = hostPeerName (cxn->myHost) ;
+
+ if (checkBuffer != NULL)
+ lenArray++ ;
+
+ if (cxn->takeHead != NULL) /* some TAKETHIS commands to be done. */
+ {
+ Buffer takeBuffer ;
+ unsigned int takeBuffLen ;
+ unsigned int writeIdx = 0 ;
+
+ /* count up all the buffers we'll be writing. One extra each time for
+ the TAKETHIS command buffer*/
+ for (p = cxn->takeHead ; p != NULL ; p = p->next)
+ if (artContentsOk (p->article))
+ lenArray += (1 + artNntpBufferCount (p->article)) ;
+
+ /* now allocate the array for the buffers and put them all in it */
+ /* 1 for the terminator */
+ rval = xmalloc (sizeof(Buffer) * (lenArray + 1)) ;
+
+ if (checkBuffer != NULL)
+ rval [writeIdx++] = checkBuffer ;
+
+ q = NULL ;
+ p = cxn->takeHead ;
+ while (p != NULL)
+ {
+ char *t ;
+ const char *msgid ;
+ Article article ;
+ Buffer *articleBuffers ;
+ int i, nntpLen ;
+
+ article = p->article ;
+ nntpLen = artNntpBufferCount (article) ;
+ msgid = artMsgId (article) ;
+
+ if (nntpLen == 0)
+ { /* file no longer valid so drop from queue */
+ ArtHolder ta = p ;
+
+ if (q == NULL) /* it's the first in the queue */
+ cxn->takeHead = p->next ;
+ else
+ q->next = p->next ;
+
+ p = p->next ;
+ ASSERT (cxn->articleQTotal > 0) ;
+ cxn->articleQTotal-- ;
+
+ ta->next = cxn->missing ;
+ cxn->missing = ta ;
+ }
+ else
+ {
+ articleBuffers = artGetNntpBuffers (article) ;
+
+ /* set up the buffer with the TAKETHIS command in it.
+ 12 == strlen ("TAKETHIS \n\r") */
+ takeBuffLen = 12 + strlen (msgid) ;
+ takeBuffer = newBuffer (takeBuffLen) ;
+ t = bufferBase (takeBuffer) ;
+
+ sprintf (t, "TAKETHIS %s\r\n", msgid) ;
+ bufferSetDataSize (takeBuffer, strlen (t)) ;
+
+ d_printf (5,"%s:%d Command %s\n", peerName, cxn->ident, t) ;
+
+ ASSERT (writeIdx <= lenArray) ;
+ rval [writeIdx++] = takeBuffer ;
+
+ /* now add all the buffers that make up the body of the TAKETHIS
+ command */
+ for (i = 0 ; i < nntpLen ; i++)
+ {
+ ASSERT (writeIdx <= lenArray) ;
+ rval [writeIdx++] = bufferTakeRef (articleBuffers [i]) ;
+ }
+
+ freeBufferArray (articleBuffers) ;
+
+ if ( !cxn->needsChecks )
+ {
+ /* this isn't quite right. An article may be counted
+ twice if we switch to no-CHECK mode after its
+ CHECK was issued, but before its TAKETHIS was done
+ just now. I'm not going to worry unless someone
+ complains. */
+
+ cxn->checksIssued++ ;
+ hostArticleOffered (cxn->myHost,cxn) ;
+ }
+
+ q = p ;
+ p = p->next ;
+ }
+ }
+
+ if (writeIdx > 0)
+ rval [writeIdx] = NULL ;
+ else
+ { /* all articles were missing and no CHECKS */
+ free (rval) ;
+ rval = NULL ;
+ }
+ }
+ else if (checkBuffer != NULL) /* no TAKETHIS to do, but some CHECKS */
+ rval = makeBufferArray (checkBuffer, NULL) ;
+
+ return rval ;
+}
+
+
+
+
+\f
+/*
+ * for one reason or another we need to disconnect gracefully. We send a
+ * QUIT command.
+ */
+static void issueQUIT (Connection cxn)
+{
+ Buffer quitBuffer, *writeArray ;
+ const char *peerName = hostPeerName (cxn->myHost) ;
+
+ ASSERT (cxn->takeHead == NULL) ;
+ ASSERT (cxn->checkHead == NULL) ;
+ VALIDATE_CONNECTION (cxn) ;
+
+ if (cxn->quitWasIssued)
+ return ;
+
+ if (writeIsPending (cxn->myEp))
+ {
+ warn ("%s:%d internal QUIT while write pending", peerName,
+ cxn->ident) ;
+
+ if (cxn->state == cxnClosingS)
+ cxnDead (cxn) ;
+ else
+ cxnWait (cxn) ;
+ }
+ else
+ {
+ quitBuffer = newBuffer (7) ;
+ strcpy (bufferBase (quitBuffer), "QUIT\r\n") ;
+ bufferSetDataSize (quitBuffer, 6) ;
+
+ writeArray = makeBufferArray (quitBuffer, NULL) ;
+
+ d_printf (1,"%s:%d Sending a quit command\n",
+ hostPeerName (cxn->myHost),cxn->ident) ;
+
+ cxn->quitWasIssued = true ; /* not exactly true, but good enough */
+
+ if ( !prepareWriteWithTimeout (cxn->myEp, writeArray, quitWritten,
+ cxn) )
+ {
+ die ("%s:%d fatal prepare write for QUIT command failed", peerName,
+ cxn->ident) ;
+ }
+ }
+}
+
+
+
+
+\f
+/*
+ * Set up the timer for the blocked reads
+ */
+static void initReadBlockedTimeout (Connection cxn)
+{
+ ASSERT (cxn != NULL) ;
+ASSERT (cxn->state != cxnIdleS ) ;
+
+ /* set up the response timer. */
+ clearTimer (cxn->readBlockedTimerId) ;
+
+ if (cxn->readTimeout > 0)
+ cxn->readBlockedTimerId = prepareSleep (responseTimeoutCbk, cxn->readTimeout, cxn) ;
+}
+
+
+
+
+\f
+/*
+ * Set up the timer for the blocked reads
+ */
+static int prepareWriteWithTimeout (EndPoint endp,
+ Buffer *buffers,
+ EndpRWCB done,
+ Connection cxn)
+{
+ /* Clear the read timer, since we can't expect a response until everything
+ is sent.
+ XXX - would be nice to have a timeout for reponses if we're sending a
+ string of commands. */
+ clearTimer (cxn->readBlockedTimerId) ;
+
+ /* set up the write timer. */
+ clearTimer (cxn->writeBlockedTimerId) ;
+
+ if (cxn->writeTimeout > 0)
+ cxn->writeBlockedTimerId = prepareSleep (writeTimeoutCbk, cxn->writeTimeout,
+ cxn) ;
+
+ /* set up the write. */
+ return prepareWrite (endp, buffers, writeProgress, done, cxn) ;
+}
+
+
+
+
+\f
+/*
+ * Does the actual deletion of a connection and all its private data.
+ */
+static void delConnection (Connection cxn)
+{
+ bool shutDown;
+ Connection c, q;
+
+ if (cxn == NULL)
+ return ;
+
+ d_printf (1,"Deleting connection: %s:%d\n",
+ hostPeerName (cxn->myHost),cxn->ident) ;
+
+ for (c = gCxnList, q = NULL ; c != NULL ; q = c, c = c->next)
+ if (c == cxn)
+ {
+ if (gCxnList == c)
+ gCxnList = gCxnList->next ;
+ else
+ q->next = c->next ;
+ break ;
+ }
+
+ ASSERT (c != NULL) ;
+
+ if (cxn->myEp != NULL)
+ delEndPoint (cxn->myEp) ;
+
+ ASSERT (cxn->checkHead == NULL) ;
+ ASSERT (cxn->checkRespHead == NULL) ;
+ ASSERT (cxn->takeHead == NULL) ;
+ ASSERT (cxn->takeRespHead == NULL) ;
+
+ delBuffer (cxn->respBuffer) ;
+
+ /* tell the Host we're outta here. */
+ shutDown = hostCxnGone (cxn->myHost, cxn) ;
+
+ cxn->ident = 0 ;
+ cxn->timeCon = 0 ;
+
+ free (cxn->ipName) ;
+
+ clearTimer (cxn->artReceiptTimerId) ;
+ clearTimer (cxn->readBlockedTimerId) ;
+ clearTimer (cxn->writeBlockedTimerId) ;
+ clearTimer (cxn->flushTimerId) ;
+
+ free (cxn) ;
+
+ if (shutDown)
+ {
+ /* exit program if that was the last connexion for the last host */
+ /* XXX what about if there are ever multiple listeners?
+ XXX this will be executed if all hosts on only one of the
+ XXX listeners have gone */
+ time_t now = theTime () ;
+ char dateString [30] ;
+ char **p = PointersFreedOnExit ;
+
+ /* finish out all outstanding memory */
+ while (*p)
+ free (*p++) ;
+ free (PointersFreedOnExit) ;
+ freeTimeoutQueue () ;
+
+ strlcpy (dateString,ctime (&now), sizeof(dateString)) ;
+
+ notice ("ME finishing at %s", dateString) ;
+
+ exit (0) ;
+ }
+}
+
+
+
+
+\f
+/*
+ * Bump up the value of the low pass filter on the connection.
+ */
+static void incrFilter (Connection cxn)
+{
+ cxn->filterValue *= (1.0 - (1.0 / cxn->lowPassFilter)) ;
+ cxn->filterValue += 1.0 ;
+}
+
+
+
+
+\f
+/*
+ * decrement the value of the low pass filter on the connection.
+ */
+static void decrFilter (Connection cxn)
+{
+ cxn->filterValue *= (1.0 - (1.0 / cxn->lowPassFilter)) ;
+}
+
+
+
+
+\f
+/*
+ * return true if we have articles we need to issue commands for.
+ */
+static bool writesNeeded (Connection cxn)
+{
+ return (cxn->checkHead != NULL || cxn->takeHead != NULL ? true : false) ;
+}
+
+
+
+
+\f
+/*
+ * do some simple tests to make sure it's OK.
+ */
+static void validateConnection (Connection cxn)
+{
+ unsigned int i ;
+ unsigned int old ;
+ ArtHolder p ;
+
+ i = 0 ;
+
+ /* count up the articles the Connection has and make sure that matches. */
+ for (p = cxn->takeHead ; p != NULL ; p = p->next)
+ i++ ;
+ d_printf (4,"TAKE queue: %d\n",i) ;
+ old = i ;
+
+ for (p = cxn->takeRespHead ; p != NULL ; p = p->next)
+ i++ ;
+ d_printf (4,"TAKE response queue: %d\n",i - old) ;
+ old = i ;
+
+ for (p = cxn->checkHead ; p != NULL ; p = p->next)
+ i++ ;
+ d_printf (4,"CHECK queue: %d\n",i - old) ;
+ old = i ;
+
+ for (p = cxn->checkRespHead ; p != NULL ; p = p->next)
+ i++ ;
+ d_printf (4,"CHECK response queue: %d\n",i - old) ;
+
+ ASSERT (i == cxn->articleQTotal) ;
+
+ switch (cxn->state)
+ {
+ case cxnConnectingS:
+ ASSERT (cxn->doesStreaming == false) ;
+ ASSERT (cxn->articleQTotal <= 1) ;
+ ASSERT (cxn->artReceiptTimerId == 0) ;
+ ASSERT (cxn->sleepTimerId == 0) ;
+ /* ASSERT (cxn->timeCon == 0) ; */
+ break ;
+
+ case cxnWaitingS:
+ ASSERT (cxn->articleQTotal == 0) ;
+ ASSERT (cxn->myEp == NULL) ;
+ ASSERT (cxn->artReceiptTimerId == 0) ;
+ ASSERT (cxn->readBlockedTimerId == 0) ;
+ ASSERT (cxn->writeBlockedTimerId == 0) ;
+ ASSERT (cxn->flushTimerId == 0) ;
+ ASSERT (cxn->sleepTimerId == 0) ;
+ ASSERT (cxn->timeCon == 0) ;
+ break ;
+
+ case cxnFlushingS:
+ case cxnClosingS:
+ if (!cxn->doesStreaming)
+ ASSERT (cxn->articleQTotal <= 1) ;
+ ASSERT (cxn->artReceiptTimerId == 0) ;
+ ASSERT (cxn->flushTimerId == 0) ;
+ ASSERT (cxn->sleepTimerId == 0) ;
+ ASSERT (cxn->timeCon != 0) ;
+ ASSERT (cxn->doesStreaming || cxn->maxCheck == 1) ;
+ break ;
+
+ case cxnFeedingS:
+ if (cxn->doesStreaming)
+ /* Some(?) hosts return the 439 response even before we're done
+ sending, so don't go idle until here */
+ ASSERT (cxn->articleQTotal > 0 || writeIsPending (cxn->myEp)) ;
+ else
+ ASSERT (cxn->articleQTotal == 1) ;
+ if (cxn->readTimeout > 0 && !writeIsPending (cxn->myEp) &&
+ cxn->checkRespHead != NULL && cxn->takeRespHead != NULL)
+ ASSERT (cxn->readBlockedTimerId != 0) ;
+ if (cxn->writeTimeout > 0 && writeIsPending (cxn->myEp))
+ ASSERT (cxn->writeBlockedTimerId != 0) ;
+ ASSERT (cxn->sleepTimerId == 0) ;
+ ASSERT (cxn->timeCon != 0) ;
+ ASSERT (cxn->doesStreaming || cxn->maxCheck == 1) ;
+ break;
+
+ case cxnIdleS:
+ ASSERT (cxn->articleQTotal == 0) ;
+ if (cxn->articleReceiptTimeout > 0)
+ ASSERT (cxn->artReceiptTimerId != 0) ;
+ ASSERT (cxn->readBlockedTimerId == 0) ;
+ ASSERT (cxn->writeBlockedTimerId == 0) ;
+ ASSERT (cxn->sleepTimerId == 0) ;
+ ASSERT (cxn->timeCon != 0) ;
+ ASSERT (!writeIsPending (cxn->myEp)) ;
+ break ;
+
+ case cxnIdleTimeoutS:
+ ASSERT (cxn->articleQTotal == 0) ;
+ ASSERT (cxn->artReceiptTimerId == 0) ;
+ ASSERT (cxn->writeBlockedTimerId == 0) ;
+ ASSERT (cxn->sleepTimerId == 0) ;
+ ASSERT (cxn->timeCon != 0) ;
+ ASSERT (!writeIsPending (cxn->myEp)) ;
+ break ;
+
+ case cxnSleepingS:
+ ASSERT (cxn->articleQTotal == 0) ;
+ ASSERT (cxn->myEp == NULL) ;
+ ASSERT (cxn->artReceiptTimerId == 0) ;
+ ASSERT (cxn->readBlockedTimerId == 0) ;
+ ASSERT (cxn->writeBlockedTimerId == 0) ;
+ ASSERT (cxn->flushTimerId == 0) ;
+ ASSERT (cxn->timeCon == 0) ;
+ break ;
+
+ case cxnStartingS:
+ ASSERT (cxn->articleQTotal == 0) ;
+ ASSERT (cxn->myEp == NULL) ;
+ ASSERT (cxn->artReceiptTimerId == 0) ;
+ ASSERT (cxn->readBlockedTimerId == 0) ;
+ ASSERT (cxn->writeBlockedTimerId == 0) ;
+ ASSERT (cxn->flushTimerId == 0) ;
+ ASSERT (cxn->sleepTimerId == 0) ;
+ ASSERT (cxn->timeCon == 0) ;
+ break ;
+
+ case cxnDeadS:
+ break ;
+ }
+}
+
+
+
+
+\f
+/*
+ * Generate a printable string of the parameter.
+ */
+static const char *stateToString (CxnState state)
+{
+ static char rval [64] ;
+
+ switch (state)
+ {
+ case cxnStartingS:
+ strlcpy (rval,"cxnStartingS", sizeof(rval)) ;
+ break ;
+
+ case cxnWaitingS:
+ strlcpy (rval,"cxnWaitingS", sizeof(rval)) ;
+ break ;
+
+ case cxnConnectingS:
+ strlcpy (rval,"cxnConnectingS", sizeof(rval)) ;
+ break ;
+
+ case cxnIdleS:
+ strlcpy (rval,"cxnIdleS", sizeof(rval)) ;
+ break ;
+
+ case cxnIdleTimeoutS:
+ strlcpy (rval,"cxnIdleTimeoutS", sizeof(rval)) ;
+ break ;
+
+ case cxnFeedingS:
+ strlcpy (rval,"cxnFeedingS", sizeof(rval)) ;
+ break ;
+
+ case cxnSleepingS:
+ strlcpy (rval,"cxnSleepingS", sizeof(rval)) ;
+ break ;
+
+ case cxnFlushingS:
+ strlcpy (rval,"cxnFlushingS", sizeof(rval)) ;
+ break ;
+
+ case cxnClosingS:
+ strlcpy (rval,"cxnClosingS", sizeof(rval)) ;
+ break ;
+
+ case cxnDeadS:
+ strlcpy (rval,"cxnDeadS", sizeof(rval)) ;
+ break ;
+
+ default:
+ snprintf (rval,sizeof(rval),"UNKNOWN STATE: %d",state) ;
+ break ;
+ }
+
+ return rval ;
+}
+
+
+
+
+\f
+/****************************************************************************
+ *
+ * Functions for managing the internal queue of Articles on each Connection.
+ *
+ ****************************************************************************/
+
+static ArtHolder newArtHolder (Article article)
+{
+ ArtHolder a = xmalloc (sizeof(struct art_holder_s)) ;
+
+ a->article = article ;
+ a->next = NULL ;
+
+ return a ;
+}
+
+
+
+
+\f
+/*
+ * Deletes the article holder
+ */
+static void delArtHolder (ArtHolder artH)
+{
+ if (artH != NULL)
+ free (artH) ;
+}
+
+
+
+
+\f
+/*
+ * remove the article holder from the queue. Adjust the count and if nxtPtr
+ * points at the element then adjust that too.
+ */
+static bool remArtHolder (ArtHolder artH, ArtHolder *head, unsigned int *count)
+{
+ ArtHolder h, i ;
+
+ ASSERT (head != NULL) ;
+ ASSERT (count != NULL) ;
+
+ h = *head ;
+ i = NULL ;
+ while (h != NULL && h != artH)
+ {
+ i = h ;
+ h = h->next ;
+ }
+
+ if (h == NULL)
+ return false ;
+
+ if (i == NULL)
+ *head = (*head)->next ;
+ else
+ i->next = artH->next ;
+
+ (*count)-- ;
+
+ return true ;
+}
+
+
+
+
+\f
+/*
+ * append the ArticleHolder to the queue
+ */
+static void appendArtHolder (ArtHolder artH, ArtHolder *head, unsigned int *count)
+{
+ ArtHolder p ;
+
+ ASSERT (head != NULL) ;
+ ASSERT (count != NULL) ;
+
+ for (p = *head ; p != NULL && p->next != NULL ; p = p->next)
+ /* nada */ ;
+
+ if (p == NULL)
+ *head = artH ;
+ else
+ p->next = artH ;
+
+ artH->next = NULL ;
+ (*count)++ ;
+}
+
+
+
+
+\f
+/*
+ * find the article holder on the queue by comparing the message-id.
+ */
+static ArtHolder artHolderByMsgId (const char *msgid, ArtHolder head)
+{
+ while (head != NULL)
+ {
+ if (strcmp (msgid, artMsgId (head->article)) == 0)
+ return head ;
+
+ head = head->next ;
+ }
+
+ return NULL ;
+}
+
+
+
+/*
+ * Randomize a numeber by the given percentage
+ */
+
+static int fudgeFactor (int initVal)
+{
+ int newValue ;
+ static bool seeded ;
+
+ if ( !seeded )
+ {
+ time_t t = theTime () ;
+
+ /* this may have been done already in endpoint.c. Is that a problem??? */
+ srand (t) ;
+ seeded = true ;
+ }
+
+ newValue = initVal + (initVal / 10 - (rand() % (initVal / 5)));
+
+ return newValue ;
+}
--- /dev/null
+/* $Id: connection.h 6648 2004-01-25 20:07:11Z rra $
+**
+** The public interface to the Connection class.
+**
+** Written by James Brister <brister@vix.com>
+**
+** The Connection class encapulates an NNTP protocol endpoint (either regular
+** or extended with the streaming protocol). Each Connection is owned by a
+** single Host object.
+**
+** It manages the network connection (via an EndPoint) the the pumping of
+** articles to the remote host. It gets these articles from its Host object.
+** If the remote doesn't handle the streaming extension, then the Connection
+** will only manage one article at a time. If the remote handles the
+** extension, then the connection will queue up articles while sending the
+** CHECK and TAKETHIS commands.
+**
+** If the network connection drops while the Connection object has articles
+** queued up, then it will hand them back to its Host object.
+*/
+
+#if ! defined ( connection_h__ )
+#define connection_h__
+
+
+#include <time.h>
+#include <stdio.h>
+
+#include "misc.h"
+
+
+ /*
+ * Create a new Connection.
+ *
+ * HOST is the host object we're owned by.
+ * IDENT is an identifier to be added to syslog entries so we can tell
+ * what's happening on different connections to the same peer.
+ * IPNAME is the name (or ip address) of the remote)
+ * MAXTOUT is the maximum amount of time to wait for a response before
+ * considering the remote host dead.
+ * PORTNUM is the portnum to contact on the remote end.
+ * RESPTIMEOUT is the amount of time to wait for a response from a remote
+ * before considering the connection dead.
+ * CLOSEPERIOD is the number of seconds after connecting that the
+ * connections should be closed down and reinitialized (due to problems
+ * with old NNTP servers that hold history files open. Value of 0 means
+ * no close down.
+ */
+Connection newConnection (Host host,
+ unsigned int ident,
+ const char *ipname,
+ unsigned int artTout,
+ unsigned int portNum,
+ unsigned int respTimeout,
+ unsigned int closePeriod,
+ double lowPassLow,
+ double lowPassHigh,
+ double lowPassFilter) ;
+
+ /* Causes the Connection to build the network connection. */
+bool cxnConnect (Connection cxn) ;
+
+ /* puts the connection into the wait state (i.e. waits for an article
+ before initiating a connect). Can only be called right after
+ newConnection returns, or while the Connection is in the (internal)
+ Sleeping state. */
+void cxnWait (Connection cxn) ;
+
+ /* The Connection will disconnect as if cxnDisconnect were called and then
+ it automatically reconnects to the remote. */
+void cxnFlush (Connection cxn) ;
+
+ /* The Connection sends remaining articles, then issues a QUIT and then
+ deletes itself */
+void cxnClose (Connection cxn) ;
+
+ /* The Connection drops all queueed articles, then issues a QUIT and then
+ deletes itself */
+void cxnTerminate (Connection cxn) ;
+
+ /* Blow away the connection gracelessly and immedately clean up */
+void cxnNuke (Connection cxn) ;
+
+ /* Tells the Connection to take the article and handle its
+ transmission. If it can't (due to queue size or whatever), then the
+ function returns false. The connection assumes ownership of the
+ article if it accepts it (returns true). */
+bool cxnTakeArticle (Connection cxn, Article art) ;
+
+ /* Tell the Connection to take the article (if it can) for later
+ processing. Assumes ownership of it if it takes it. */
+bool cxnQueueArticle (Connection cxn, Article art) ;
+
+ /* generate a syslog message for the connections activity. Called by Host. */
+void cxnLogStats (Connection cxn, bool final) ;
+
+ /* return the number of articles the connection can be given. This lets
+ the host shovel in as many as possible. May be zero. */
+size_t cxnQueueSpace (Connection cxn) ;
+
+ /* adjust the mode no-CHECK filter values */
+void cxnSetCheckThresholds (Connection cxn,
+ double lowFilter, double highFilter,
+ double lowPassFilter) ;
+
+ /* print some debugging info. */
+void gPrintCxnInfo (FILE *fp, unsigned int indentAmt) ;
+void printCxnInfo (Connection cxn, FILE *fp, unsigned int indentAmt) ;
+
+/* config file load callback */
+int cxnConfigLoadCbk (void *data) ;
+
+/* check connection state is in cxnWaitingS, cxnConnectingS or cxnIdleS */
+bool cxnCheckstate (Connection cxn) ;
+
+#endif /* connection_h__ */
--- /dev/null
+/* $Id: endpoint.c 7738 2008-04-06 09:33:33Z iulius $
+**
+** The implementation of the innfeed EndPoint object class.
+**
+** Written by James Brister <brister@vix.com>
+**
+** The EndPoint class is what gives the illusion (sort of, kind of) of
+** threading. Basically it controls a select loop and a set of EndPoint
+** objects. Each EndPoint has a file descriptor it is interested in. The
+** users of the EndPoint tell the EndPoints to notify them when a read or
+** write has been completed (or simple if the file descriptor is read or
+** write ready).
+*/
+
+#include "innfeed.h"
+#include "config.h"
+#include "clibrary.h"
+#include "portable/socket.h"
+#include "portable/time.h"
+
+#include <assert.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <signal.h>
+#include <sys/stat.h>
+#include <sys/uio.h>
+#include <syslog.h>
+
+#ifdef HAVE_LIMITS_H
+# include <limits.h>
+#endif
+
+#ifdef HAVE_SYS_SELECT_H
+# include <sys/select.h>
+#endif
+
+#include "inn/innconf.h"
+#include "inn/messages.h"
+#include "libinn.h"
+
+#include "buffer.h"
+#include "configfile.h"
+#include "endpoint.h"
+#include "host.h"
+
+static const char *const timer_name[] = {
+ "idle", "blstats", "stsfile", "newart", "readart", "prepart", "read",
+ "write", "cb"
+};
+
+#if ! defined (NSIG)
+#define NSIG 32
+#endif
+
+
+ /* This is the structure that is the EndPoint */
+struct endpoint_s
+{
+ /* fields for managing multiple reads into the inBuffer. */
+ Buffer *inBuffer ; /* list of buffers to read into */
+ unsigned int inBufferIdx ; /* where is list we're at. */
+ size_t inIndex ; /* where in current read we're at */
+ size_t inMinLen ; /* minimum amount to read */
+ size_t inAmtRead ; /* amount read so far */
+ EndpRWCB inCbk ; /* callback for when read complete */
+ void *inClientData ; /* callback data */
+
+ /* fields for managing multiple writes from the outBuffer */
+ Buffer *outBuffer ; /* list of buffers to write */
+ unsigned int outBufferIdx ; /* index into buffer list to start write */
+ size_t outIndex ; /* where in current buffer we write from */
+ size_t outSize ; /* total of all the buffers */
+ size_t outAmtWritten ; /* amount written so far */
+ EndpRWCB outProgressCbk ; /* callback when done */
+ EndpRWCB outDoneCbk ; /* callback when done */
+ void *outClientData ; /* callback data */
+
+ EndpWorkCbk workCbk ; /* callback to run if no I/O to do */
+ void *workData ; /* data for callback */
+
+ int myFd ; /* the file descriptor we're handling */
+ int myErrno ; /* the errno when I/O fails */
+
+ double selectHits ; /* indicates how often it's ready */
+};
+
+
+
+ /* A private structure. These hold the information on the timer callbacks. */
+typedef struct timerqelem_s
+{
+ TimeoutId id ; /* the id we gave out */
+ time_t when ; /* The time the timer should go off */
+ EndpTCB func ; /* the function to call */
+ void *data ; /* the client callback data */
+ struct timerqelem_s *next ; /* next in the queue */
+} *TimerElem, TimerElemStruct ;
+
+
+
+ /* set to 1 elsewhere if you want stderr to get what's also being written
+ in doWrite. */
+int debugWrites ;
+
+extern const char *InputFile ;
+
+static EndPoint mainEndPoint ;
+static bool mainEpIsReg = false ;
+static unsigned int stdioFdMax = MAX_STDIO_FD ;
+
+time_t PrivateTime;
+
+
+typedef void (*sigfn) (int) ;
+static sigfn *sigHandlers ;
+
+static volatile sig_atomic_t *sigFlags ;
+
+
+
+ /* private functions */
+static IoStatus doRead (EndPoint endp) ;
+static IoStatus doWrite (EndPoint endp) ;
+static IoStatus doExcept (EndPoint endp) ;
+static void pipeHandler (int s) ;
+static void signalHandler (int s) ;
+static int hitCompare (const void *v1, const void *v2) ;
+static void reorderPriorityList (void) ;
+static TimerElem newTimerElem (TimeoutId i, time_t w, EndpTCB f, void *d) ;
+static TimeoutId timerElemAdd (time_t when, EndpTCB func, void *data) ;
+static struct timeval *getTimeout (struct timeval *tout) ;
+static void doTimeout (void) ;
+static void handleSignals (void) ;
+
+#if 0
+static int ff_set (fd_set *set, unsigned int start) ;
+static int ff_free (fd_set *set, unsigned int start) ;
+#endif
+static void endpointCleanup (void) ;
+
+
+ /* Private data */
+static size_t maxEndPoints ;
+
+static EndPoint *endPoints ; /* endpoints indexed on fd */
+static EndPoint *priorityList ; /* endpoints indexed on priority */
+
+static int absHighestFd = 0 ; /* never goes down */
+static int highestFd = -1 ;
+static unsigned int endPointCount = 0 ;
+static unsigned int priorityCount = 0 ;
+
+static fd_set rdSet ;
+static fd_set wrSet ;
+static fd_set exSet ;
+
+static int keepSelecting ;
+
+static TimerElem timeoutQueue ;
+static TimerElem timeoutPool ;
+static TimeoutId nextId ;
+static int timeoutQueueLength ;
+
+
+
+
+ /* Create a new EndPoint and hook it to the give file descriptor. All
+ fields are initialized to appropriate values. On the first time this
+ function is called the global data structs that manages lists of
+ endpoints are intialized. */
+static bool inited = false ;
+
+EndPoint newEndPoint (int fd)
+{
+ EndPoint ep ;
+
+ if (!inited)
+ {
+ inited = true ;
+ atexit (endpointCleanup) ;
+ }
+
+ if (fd < 0)
+ return NULL ;
+
+ /* try to dup the fd to a larger number to leave lower values free for
+ broken stdio implementations. */
+ if (stdioFdMax > 0 && ((unsigned int) fd) <= stdioFdMax)
+ {
+ int newfd = fcntl(fd, F_DUPFD, stdioFdMax + 1);
+ if (newfd >= 0)
+ {
+ d_printf (1,"Dupped fd %d to %d\n",fd,newfd) ;
+ if (close (fd) != 0)
+ syswarn ("ME oserr close (%d)", fd) ;
+ }
+ else
+ {
+ d_printf (1,"Couldn't dup fd %d to above %d\n",fd,stdioFdMax) ;
+ newfd = fd ;
+ }
+
+ fd = newfd ;
+ }
+
+ if ((unsigned int) fd >= maxEndPoints)
+ {
+ unsigned int i = maxEndPoints ;
+
+ maxEndPoints = (((fd + 256) / 256) * 256); /* round up to nearest 256 */
+ if (endPoints == NULL)
+ {
+ endPoints = xmalloc (sizeof(EndPoint) * maxEndPoints) ;
+ priorityList = xmalloc (sizeof(EndPoint) * maxEndPoints) ;
+ }
+ else
+ {
+ endPoints = xrealloc (endPoints,sizeof(EndPoint) * maxEndPoints) ;
+ priorityList = xrealloc (priorityList,
+ sizeof(EndPoint) * maxEndPoints) ;
+ }
+
+ for ( ; i < maxEndPoints ; i++)
+ endPoints [i] = priorityList [i] = NULL ;
+ }
+
+ ASSERT (endPoints [fd] == NULL) ;
+
+ if (fd > absHighestFd)
+ {
+ static bool sizelogged = false ;
+
+#if defined (FD_SETSIZE)
+ if (fd >= FD_SETSIZE)
+ {
+ sizelogged = true ;
+ warn ("ME fd (%d) looks too big (%d -- FD_SETSIZE)", fd,
+ FD_SETSIZE) ;
+ return NULL ;
+ }
+#else
+ if (fd > (sizeof (fd_set) * CHAR_BIT))
+ {
+ sizelogged = true ;
+ warn ("ME fd (%d) looks too big (%d -- sizeof (fd_set) * CHAR_BIT)",
+ fd, (sizeof (fd_set) * CHAR_BIT)) ;
+ return NULL ;
+ }
+#endif
+
+ absHighestFd = fd ;
+ }
+
+ ep = xcalloc (1, sizeof(struct endpoint_s)) ;
+
+ ep->inBuffer = NULL ;
+ ep->inBufferIdx = 0 ;
+ ep->inIndex = 0 ;
+ ep->inMinLen = 0 ;
+ ep->inAmtRead = 0 ;
+ ep->inCbk = NULL ;
+ ep->inClientData = NULL ;
+
+ ep->outBuffer = 0 ;
+ ep->outBufferIdx = 0 ;
+ ep->outIndex = 0 ;
+ ep->outSize = 0 ;
+ ep->outProgressCbk = NULL ;
+ ep->outDoneCbk = NULL ;
+ ep->outClientData = NULL ;
+ ep->outAmtWritten = 0 ;
+
+ ep->workCbk = NULL ;
+ ep->workData = NULL ;
+
+ ep->myFd = fd ;
+ ep->myErrno = 0 ;
+
+ ep->selectHits = 0.0 ;
+
+ endPoints [fd] = ep ;
+ priorityList [priorityCount++] = ep ;
+ endPointCount++ ;
+
+ highestFd = (fd > highestFd ? fd : highestFd) ;
+
+ return ep ;
+}
+
+
+
+/* Delete the given endpoint. The files descriptor is closed and the two
+ Buffer arrays are released. */
+
+void delEndPoint (EndPoint ep)
+{
+ unsigned int idx ;
+
+ if (ep == NULL)
+ return ;
+
+ ASSERT (endPoints [ep->myFd] == ep) ;
+
+ if (mainEndPoint == ep)
+ mainEndPoint = NULL ;
+
+ if (ep->inBuffer != NULL)
+ freeBufferArray (ep->inBuffer) ;
+
+ if (ep->outBuffer != NULL)
+ freeBufferArray (ep->outBuffer) ;
+
+ close (ep->myFd) ;
+
+ /* remove from selectable bits */
+ FD_CLR (ep->myFd,&rdSet) ;
+ FD_CLR (ep->myFd,&wrSet) ;
+ FD_CLR (ep->myFd,&exSet) ;
+
+ /* Adjust the global arrays to account for deleted endpoint. */
+ endPoints [ep->myFd] = NULL ;
+ if (ep->myFd == highestFd)
+ while (endPoints [highestFd] == NULL && highestFd >= 0)
+ highestFd-- ;
+
+ for (idx = 0 ; idx < priorityCount ; idx++)
+ if (priorityList [idx] == ep)
+ break ;
+
+ ASSERT (idx < priorityCount) ; /* i.e. was found */
+ ASSERT (priorityList [idx] == ep) ; /* redundant */
+
+ /* this hole will removed in the reorder routine */
+ priorityList [idx] = NULL ;
+
+ endPointCount-- ;
+
+ free (ep) ;
+}
+
+int endPointFd (EndPoint endp)
+{
+ ASSERT (endp != NULL) ;
+
+ return endp->myFd ;
+}
+
+
+
+
+/* Request a read to be done next time there's data. The endpoint
+ * ENDP is what will do the read. BUFF is the Buffer the data should
+ * go into. FUNC is the callback function to call when the read is
+ * complete. CLIENTDATA is the client data to pass back into the
+ * callback function. MINLEN is the minimum amount of data to
+ * read. If MINLEN is 0 then then BUFF must be filled, otherwise at
+ * least MINLEN bytes must be read.
+ *
+ * BUFF can be null, in which case no read is actually done, but the
+ * callback function will be called still. This is useful for
+ * listening sockets.
+ *
+ * Returns 0 if the read couln't be prepared (for example if a read
+ * is already outstanding).
+ */
+
+int prepareRead (EndPoint endp,
+ Buffer *buffers,
+ EndpRWCB func,
+ void *clientData,
+ int minlen)
+{
+ int bufferSizeTotal = 0 ;
+ int idx ;
+
+ ASSERT (endp != NULL) ;
+
+ if (endp->inBuffer != NULL || FD_ISSET (endp->myFd,&rdSet))
+ return 0 ; /* something already there */
+
+ for (idx = 0 ; buffers != NULL && buffers [idx] != NULL ; idx++)
+ {
+ size_t bs = bufferSize (buffers [idx]) ;
+ size_t bds = bufferDataSize (buffers [idx]) ;
+
+ bufferSizeTotal += (bs - bds) ;
+ }
+
+ endp->inBuffer = buffers ;
+ endp->inBufferIdx = 0 ;
+ endp->inIndex = 0 ;
+ endp->inMinLen = (minlen > 0 ? minlen : bufferSizeTotal) ;
+ endp->inCbk = func ;
+ endp->inAmtRead = 0 ;
+ endp->inClientData = clientData ;
+
+ FD_SET (endp->myFd, &rdSet) ;
+ if ( InputFile == NULL )
+ FD_SET (endp->myFd, &exSet) ;
+
+ return 1 ;
+}
+
+
+
+/* Request a write to be done at a later point. ENDP is the EndPoint
+ * to do the write. BUFF is the Buffer to write from. FUNC is the
+ * function to call when the write is complete. CLIENTDATA is some
+ * data to hand back to the callback function.
+ *
+ * If BUFF is null, then no write will actually by done, but the
+ * callback function will still be called. This is useful for
+ * connecting sockets.
+ *
+ * Returns 0 if the write couldn't be prepared (like if a write is
+ * still in process.
+ */
+int prepareWrite (EndPoint endp,
+ Buffer *buffers,
+ EndpRWCB progress,
+ EndpRWCB done,
+ void *clientData)
+{
+ int bufferSizeTotal = 0 ;
+ int idx ;
+
+ ASSERT (endp != NULL) ;
+
+ if (endp->outBuffer != NULL || FD_ISSET (endp->myFd,&wrSet))
+ return 0 ; /* something already there */
+
+ for (idx = 0 ; buffers != NULL && buffers [idx] != NULL ; idx++)
+ bufferSizeTotal += bufferDataSize (buffers [idx]) ;
+
+ endp->outBuffer = buffers ;
+ endp->outBufferIdx = 0 ;
+ endp->outIndex = 0 ;
+ endp->outProgressCbk = progress ;
+ endp->outDoneCbk = done ;
+ endp->outClientData = clientData ;
+ endp->outSize = bufferSizeTotal ;
+ endp->outAmtWritten = 0 ;
+
+ FD_SET (endp->myFd, &wrSet) ;
+ FD_SET (endp->myFd, &exSet) ;
+
+ return 1 ;
+}
+
+
+/* Cancel the pending read. */
+void cancelRead (EndPoint endp)
+{
+ FD_CLR (endp->myFd,&rdSet) ;
+ if (!FD_ISSET (endp->myFd, &wrSet))
+ FD_CLR (endp->myFd,&exSet) ;
+
+ freeBufferArray (endp->inBuffer) ;
+
+ endp->inBuffer = NULL ;
+ endp->inBufferIdx = 0 ;
+ endp->inIndex = 0 ;
+ endp->inMinLen = 0 ;
+ endp->inAmtRead = 0 ;
+ endp->inCbk = NULL ;
+ endp->inClientData = NULL ;
+}
+
+
+/* cancel all pending writes. The first len bytes of the queued write are
+ copied to buffer. The number of bytes copied (if it is less than *len) is
+ copied to len. If no write was outstanding then len will have 0 stored in
+ it. */
+void cancelWrite (EndPoint endp, char *buffer UNUSED, size_t *len UNUSED)
+{
+ FD_CLR (endp->myFd, &wrSet) ;
+ if (!FD_ISSET (endp->myFd, &rdSet))
+ FD_CLR (endp->myFd, &exSet) ;
+
+#if 0
+#error XXX need to copy data to buffer and *len
+#endif
+
+ freeBufferArray (endp->outBuffer) ;
+
+ endp->outBuffer = NULL ;
+ endp->outBufferIdx = 0 ;
+ endp->outIndex = 0 ;
+ endp->outProgressCbk = NULL ;
+ endp->outDoneCbk = NULL ;
+ endp->outClientData = NULL ;
+ endp->outSize = 0 ;
+ endp->outAmtWritten = 0 ;
+}
+
+/* queue up a new timeout request. to go off at a specific time. */
+TimeoutId prepareWake (EndpTCB func, time_t timeToWake, void *clientData)
+{
+ TimeoutId id ;
+ time_t now = theTime() ;
+
+ if (now > timeToWake)
+ return 0 ;
+
+ id = timerElemAdd (timeToWake,func,clientData) ;
+
+#if 0
+ d_printf (1,"Preparing wake %d at date %ld for %d seconds\n",
+ (int) id, (long) now, timeToWake - now) ;
+#endif
+
+ return id ;
+}
+
+
+/* queue up a new timeout request to off TIMETOSLEEP seconds from now */
+TimeoutId prepareSleep (EndpTCB func, int timeToSleep, void *clientData)
+{
+ time_t now = theTime() ;
+ TimeoutId id ;
+
+ id = timerElemAdd (now + timeToSleep,func,clientData) ;
+
+#if 0
+ d_printf (1,"Preparing sleep %d at date %ld for %d seconds\n",
+ (int) id, (long) now, timeToSleep) ;
+#endif
+
+ return id ;
+}
+
+
+/* Updates a an existing timeout request to go off TIMETOSLEEP seconds from
+ now, or queues a new request. Always returns a new ID. */
+TimeoutId updateSleep (TimeoutId tid, EndpTCB func, int timeToSleep,
+ void *clientData)
+{
+ if (tid == 0)
+ return prepareSleep (func, timeToSleep, clientData) ;
+ else
+ {
+ /* XXX - quick and dirty but CPU wasteful implementation */
+ removeTimeout (tid) ;
+ return prepareSleep (func, timeToSleep, clientData) ;
+ }
+}
+
+
+/* Remove a timeout that was previously prepared. 0 is a legal value that
+ is just ignored. */
+bool removeTimeout (TimeoutId tid)
+{
+ TimerElem n = timeoutQueue ;
+ TimerElem p = NULL ;
+
+ if (tid == 0)
+ return true ;
+
+ while (n != NULL && n->id != tid)
+ {
+ p = n ;
+ n = n->next ;
+ }
+
+ if (n == NULL)
+ return false ;
+
+ if (p == NULL) /* at the head. */
+ timeoutQueue = n->next ;
+ else
+ p->next = n->next ;
+
+ n->next = timeoutPool ;
+ timeoutPool = n ;
+
+ timeoutQueueLength-- ;
+
+ return true ;
+}
+
+
+/* The main routine. This is a near-infinite loop that drives the whole
+ program. */
+void Run (void)
+{
+ fd_set rSet ;
+ fd_set wSet ;
+ fd_set eSet ;
+ unsigned long last_summary = 0 ;
+
+ keepSelecting = 1 ;
+ xsignal (SIGPIPE, pipeHandler) ;
+
+ while (keepSelecting)
+ {
+ struct timeval timeout ;
+ struct timeval *twait ;
+ int sval ;
+ unsigned int idx ;
+ bool modifiedTime = false ;
+
+ twait = getTimeout (&timeout) ;
+
+ memcpy (&rSet,&rdSet,sizeof (rdSet)) ;
+ memcpy (&wSet,&wrSet,sizeof (wrSet)) ;
+ memcpy (&eSet,&exSet,sizeof (exSet)) ;
+
+ if (highestFd < 0 && twait == NULL) /* no fds and no timeout */
+ break ;
+ else if (twait != NULL && (twait->tv_sec != 0 || twait->tv_usec != 0))
+ {
+ /* if we have any workprocs registered we poll rather than
+ block on the fds */
+ for (idx = 0 ; idx < priorityCount ; idx++)
+ if (priorityList [idx] != NULL &&
+ priorityList [idx]->workCbk != NULL)
+ {
+ modifiedTime = true ;
+ twait->tv_sec = 0 ;
+ twait->tv_usec = 0 ;
+
+ break ;
+ }
+ }
+
+ /* calculate host backlog statistics */
+ TMRstart(TMR_BACKLOGSTATS);
+ gCalcHostBlStat ();
+ TMRstop(TMR_BACKLOGSTATS);
+
+ TMRstart(TMR_IDLE);
+ sval = select (highestFd + 1, &rSet, &wSet, &eSet, twait) ;
+ TMRstop(TMR_IDLE);
+
+ timePasses () ;
+ if (innconf->timer)
+ {
+ unsigned long now = TMRnow () ;
+ if (last_summary == 0
+ || (long) (now - last_summary) > (innconf->timer * 1000))
+ {
+ TMRsummary ("ME", timer_name) ;
+ last_summary = now;
+ }
+ }
+
+ if (sval == 0 && twait == NULL)
+ die ("No fd's ready and no timeouts") ;
+ else if (sval < 0 && errno == EINTR)
+ {
+ handleSignals () ;
+ }
+ else if (sval < 0)
+ {
+ syswarn ("ME exception: select failed: %d", sval) ;
+ stopRun () ;
+ }
+ else if (sval > 0)
+ {
+ IoStatus rval ;
+ int readyCount = sval ;
+ int endpointsServiced = 1 ;
+
+ handleSignals() ;
+
+ for (idx = 0 ; idx < priorityCount ; idx++)
+ {
+ EndPoint ep = priorityList [idx] ;
+ bool specialCheck = false ;
+
+ if (readyCount > 0 && ep != NULL)
+ {
+ int fd = ep->myFd ;
+ int selectHit = 0, readMiss = 0, writeMiss = 0 ;
+
+ /* Every SELECT_RATIO times we service an endpoint in this
+ loop we check to see if the mainEndPoint fd is ready to
+ read or write. If so we process it and do the current
+ endpoint next time around. */
+ if (((endpointsServiced % (SELECT_RATIO + 1)) == 0) &&
+ ep != mainEndPoint && mainEndPoint != NULL &&
+ !mainEpIsReg)
+ {
+ fd_set trSet, twSet ;
+ struct timeval tw ;
+ int checkRead = FD_ISSET (mainEndPoint->myFd,&rdSet) ;
+ int checkWrite = FD_ISSET (mainEndPoint->myFd,&wrSet) ;
+
+ endpointsServiced++;
+
+ if (checkRead || checkWrite)
+ {
+ fd = mainEndPoint->myFd ;
+
+ tw.tv_sec = tw.tv_usec = 0 ;
+ memset (&trSet,0,sizeof (trSet)) ;
+ memset (&twSet,0,sizeof (twSet)) ;
+
+ if (checkRead)
+ FD_SET (fd,&trSet) ;
+ if (checkWrite)
+ FD_SET (fd,&twSet) ;
+
+ sval = select (fd + 1,&trSet,&twSet,0,&tw) ;
+
+ if (sval > 0)
+ {
+ idx-- ;
+ ep = mainEndPoint ;
+ specialCheck = true ;
+ if (checkRead && FD_ISSET (fd,&trSet))
+ {
+ FD_SET (fd,&rSet) ;
+ readyCount++ ;
+ }
+ if (checkWrite && FD_ISSET (fd,&twSet))
+ {
+ FD_SET (fd,&wSet) ;
+ readyCount++ ;
+ }
+ }
+ else if (sval < 0)
+ {
+ syswarn ("ME exception: select failed: %d",
+ sval) ;
+ stopRun () ;
+ return ;
+ }
+ else
+ fd = ep->myFd ; /* back to original fd. */
+ }
+ }
+
+ FD_CLR (fd, &exSet) ;
+
+ if (FD_ISSET (fd,&rSet))
+ {
+ readyCount-- ;
+ endpointsServiced++ ;
+ selectHit = 1 ;
+
+ if ((rval = doRead (ep)) != IoIncomplete)
+ {
+ Buffer *buff = ep->inBuffer ;
+
+ FD_CLR (fd, &rdSet) ;
+
+ /* incase callback wants to issue read */
+ ep->inBuffer = NULL ;
+
+ if (ep->inCbk != NULL)
+ (*ep->inCbk) (ep,rval,buff,ep->inClientData) ;
+ else
+ freeBufferArray (buff) ;
+ }
+ else
+ {
+ if ( InputFile == NULL )
+ FD_SET (ep->myFd, &exSet) ;
+ }
+ }
+ else if (FD_ISSET(fd,&rdSet))
+ readMiss = 1;
+
+ /* get it again as the read callback may have deleted the */
+ /* endpoint */
+ if (specialCheck)
+ ep = mainEndPoint ;
+ else
+ ep = priorityList [idx] ;
+
+ if (readyCount > 0 && ep != NULL && FD_ISSET (fd,&wSet))
+ {
+ readyCount-- ;
+ endpointsServiced++ ;
+ selectHit = 1 ;
+
+ if ((rval = doWrite (ep)) != IoIncomplete &&
+ rval != IoProgress)
+ {
+ Buffer *buff = ep->outBuffer ;
+
+ FD_CLR (fd, &wrSet) ;
+
+ /* incase callback wants to issue a write */
+ ep->outBuffer = NULL ;
+
+ if (ep->outDoneCbk != NULL)
+ (*ep->outDoneCbk) (ep,rval,buff,ep->outClientData) ;
+ else
+ freeBufferArray (buff) ;
+ }
+ else if (rval == IoProgress)
+ {
+ Buffer *buff = ep->outBuffer ;
+
+ if (ep->outProgressCbk != NULL)
+ (*ep->outProgressCbk) (ep,rval,buff,ep->outClientData) ;
+ }
+ else
+ {
+ FD_SET (ep->myFd, &exSet) ;
+ }
+ }
+ else if (FD_ISSET(fd,&wrSet))
+ writeMiss = 1;
+
+ /* get it again as the write callback may have deleted the */
+ /* endpoint */
+ if (specialCheck)
+ ep = mainEndPoint ;
+ else
+ ep = priorityList [idx] ;
+
+ if (ep != NULL)
+ {
+ ep->selectHits *= 0.9 ;
+ if (selectHit)
+ ep->selectHits += 1.0 ;
+ else if (readMiss && writeMiss)
+ ep->selectHits -= 1.0 ;
+ }
+
+ if (readyCount > 0 && ep != NULL && FD_ISSET (fd,&eSet))
+ doExcept (ep) ;
+ }
+ }
+
+ reorderPriorityList () ;
+ }
+ else if (sval == 0 && !modifiedTime)
+ doTimeout () ;
+
+ /* now we're done processing all read fds and/or the
+ timeout(s). Next we do the work callbacks for all the endpoints
+ whose fds weren't ready. */
+ for (idx = 0 ; idx < priorityCount ; idx++)
+ {
+ EndPoint ep = priorityList [idx] ;
+
+ if (ep != NULL)
+ {
+ int fd = ep->myFd ;
+
+ if ( !FD_ISSET (fd,&rSet) && !FD_ISSET (fd,&wSet) )
+ if (ep->workCbk != NULL)
+ {
+ EndpWorkCbk func = ep->workCbk ;
+ void *data = ep->workData ;
+
+ ep->workCbk = NULL ;
+ ep->workData = NULL ;
+ TMRstart(TMR_CALLBACK);
+ func (ep,data) ;
+ TMRstop(TMR_CALLBACK);
+ }
+
+ }
+ }
+ }
+}
+
+void *addWorkCallback (EndPoint endp, EndpWorkCbk cbk, void *data)
+{
+ void *oldBk = endp->workData ;
+
+ endp->workCbk = cbk ;
+ endp->workData = data ;
+
+ return oldBk ;
+}
+
+/* Tell the Run routine to stop next time around. */
+void stopRun (void)
+{
+ keepSelecting = 0 ;
+}
+
+
+int endPointErrno (EndPoint endp)
+{
+ return endp->myErrno ;
+}
+
+bool readIsPending (EndPoint endp)
+{
+ return (endp->inBuffer != NULL ? true : false) ;
+}
+
+bool writeIsPending (EndPoint endp)
+{
+ return (endp->outBuffer != NULL ? true : false) ;
+}
+
+void setMainEndPoint (EndPoint endp)
+{
+ struct stat buf ;
+
+ mainEndPoint = endp ;
+ if (endp->myFd >= 0 && fstat (endp->myFd,&buf) < 0)
+ syslog (LOG_ERR,"Can't fstat mainEndPoint fd (%d): %m", endp->myFd) ;
+ else if (endp->myFd < 0)
+ syslog (LOG_ERR,"Negative fd for mainEndPoint???") ;
+ else
+ mainEpIsReg = (S_ISREG(buf.st_mode) ? true : false) ;
+}
+
+int getMainEndPointFd (void)
+{
+ return(mainEndPoint->myFd) ;
+}
+
+void freeTimeoutQueue (void)
+{
+ TimerElem p, n ;
+
+ p = timeoutQueue ;
+ while (p)
+ {
+ n = p->next ;
+ p->next = timeoutPool ;
+ timeoutPool = p;
+ p = n ;
+ timeoutQueueLength-- ;
+ }
+}
+
+
+/***********************************************************************/
+/* STATIC FUNCTIONS BELOW HERE */
+/***********************************************************************/
+
+
+/*
+ * called when the file descriptor on this endpoint is read ready.
+ */
+static IoStatus doRead (EndPoint endp)
+{
+ int i = 0 ;
+ unsigned int idx ;
+ unsigned int bCount = 0 ;
+ struct iovec *vp = NULL ;
+ Buffer *buffers = endp->inBuffer ;
+ unsigned int currIdx = endp->inBufferIdx ;
+ size_t amt = 0 ;
+ IoStatus rval = IoIncomplete ;
+
+ TMRstart(TMR_READ);
+ for (i = currIdx ; buffers && buffers [i] != NULL ; i++)
+ bCount++ ;
+
+ bCount = (bCount > IOV_MAX ? IOV_MAX : bCount) ;
+
+ i = 0 ;
+
+ /* now set up the iovecs for the readv */
+ if (bCount > 0)
+ {
+ char *bbase ;
+ size_t bds, bs ;
+
+ vp = xcalloc (bCount, sizeof(struct iovec)) ;
+
+ bbase = bufferBase (buffers[currIdx]) ;
+ bds = bufferDataSize (buffers[currIdx]) ;
+ bs = bufferSize (buffers [currIdx]) ;
+
+ /* inIndex is an index in the virtual array of the read, not directly
+ into the buffer. */
+ vp[0].iov_base = bbase + bds + endp->inIndex ;
+ vp[0].iov_len = bs - bds - endp->inIndex ;
+
+ amt = vp[0].iov_len ;
+
+ for (idx = currIdx + 1 ; idx < bCount ; idx++)
+ {
+ bbase = bufferBase (buffers[idx]) ;
+ bds = bufferDataSize (buffers[idx]) ;
+ bs = bufferSize (buffers [idx]) ;
+
+ vp [idx].iov_base = bbase ;
+ vp [idx].iov_len = bs - bds ;
+ amt += (bs - bds) ;
+ }
+
+ i = readv (endp->myFd,vp,(int) bCount) ;
+
+ if (i > 0)
+ {
+ size_t readAmt = (size_t) i ;
+
+ endp->inAmtRead += readAmt ;
+
+ /* check if we filled the first buffer */
+ if (readAmt >= (size_t) vp[0].iov_len)
+ { /* we did */
+ bufferIncrDataSize (buffers[currIdx], vp[0].iov_len) ;
+ readAmt -= vp [0].iov_len ;
+ endp->inBufferIdx++ ;
+ }
+ else
+ {
+ endp->inIndex += readAmt ;
+ bufferIncrDataSize (buffers[currIdx], readAmt) ;
+ readAmt = 0 ;
+ }
+
+ /* now check the rest of the buffers */
+ for (idx = 1 ; readAmt > 0 ; idx++)
+ {
+ ASSERT (idx < bCount) ;
+
+ bs = bufferSize (buffers [currIdx + idx]) ;
+ bbase = bufferBase (buffers [currIdx + idx]) ;
+ bds = bufferDataSize (buffers [currIdx + idx]) ;
+
+ if (readAmt >= (bs - bds))
+ {
+ bufferSetDataSize (buffers [currIdx + idx],bs) ;
+ readAmt -= bs ;
+ endp->inBufferIdx++ ;
+ }
+ else
+ {
+ endp->inIndex = readAmt ;
+ bufferIncrDataSize (buffers [currIdx + idx], readAmt) ;
+ memset (bbase + bds + readAmt, 0, bs - bds - readAmt) ;
+ readAmt = 0 ;
+ }
+ }
+
+ if (endp->inAmtRead >= endp->inMinLen)
+ {
+ endp->inIndex = 0 ;
+ rval = IoDone ;
+ }
+ }
+ else if (i < 0 && errno != EINTR && errno != EAGAIN)
+ {
+ endp->myErrno = errno ;
+ rval = IoFailed ;
+ }
+ else if (i < 0 && errno == EINTR)
+ {
+ handleSignals () ;
+ }
+ else if (i == 0)
+ rval = IoEOF ;
+ else /* i < 0 && errno == EAGAIN */
+ rval = IoIncomplete ;
+
+ free (vp) ;
+ }
+ else
+ rval = IoDone ;
+ TMRstop(TMR_READ);
+ return rval ;
+}
+
+/* called when the file descriptor on the endpoint is write ready. */
+static IoStatus doWrite (EndPoint endp)
+{
+ unsigned int idx ;
+ int i = 0 ;
+ size_t amt = 0 ;
+ unsigned int bCount = 0 ;
+ struct iovec *vp = NULL ;
+ Buffer *buffers = endp->outBuffer ;
+ unsigned int currIdx = endp->outBufferIdx ;
+ IoStatus rval = IoIncomplete ;
+
+ TMRstart(TMR_WRITE);
+ for (i = currIdx ; buffers && buffers [i] != NULL ; i++)
+ bCount++ ;
+
+ bCount = (bCount > IOV_MAX ? IOV_MAX : bCount) ;
+
+ i = 0 ;
+
+ if (bCount > 0)
+ {
+ vp = xcalloc (bCount, sizeof(struct iovec)) ;
+
+ vp[0].iov_base = bufferBase (buffers [currIdx]) ;
+ vp[0].iov_base = (char *) vp[0].iov_base + endp->outIndex ;
+ vp[0].iov_len = bufferDataSize (buffers [currIdx]) - endp->outIndex ;
+
+ amt = vp[0].iov_len ;
+
+ for (idx = 1 ; idx < bCount ; idx++)
+ {
+ vp [idx].iov_base = bufferBase (buffers [idx + currIdx]) ;
+ vp [idx].iov_len = bufferDataSize (buffers [idx + currIdx]) ;
+ amt += vp[idx].iov_len ;
+ }
+
+#if 1
+ if (debugWrites)
+ {
+ /* nasty mixing, but stderr is unbuffered usually. It's debugging only */
+ d_printf (5,"About to write this:================================\n") ;
+ writev (2,vp,bCount) ;
+ d_printf (5,"end=================================================\n") ;
+ }
+
+#endif
+
+ ASSERT (endp->myFd >= 0) ;
+ ASSERT (vp != 0) ;
+ ASSERT (bCount > 0) ;
+
+ i = writev (endp->myFd,vp,(int) bCount) ;
+
+ if (i > 0)
+ {
+ size_t writeAmt = (size_t) i ;
+
+ endp->outAmtWritten += writeAmt ;
+
+ /* now figure out which buffers got completely written */
+ for (idx = 0 ; writeAmt > 0 ; idx++)
+ {
+ if (writeAmt >= (size_t) vp[idx].iov_len)
+ {
+ endp->outBufferIdx++ ;
+ endp->outIndex = 0 ;
+ writeAmt -= vp [idx].iov_len ;
+ }
+ else
+ {
+ /* this buffer was not completly written */
+ endp->outIndex += writeAmt ;
+ writeAmt = 0 ;
+ }
+ }
+
+ if (endp->outAmtWritten == endp->outSize)
+ rval = IoDone ;
+ else
+ rval = IoProgress ;
+ }
+ else if (i < 0 && errno == EINTR)
+ {
+ handleSignals () ;
+ }
+ else if (i < 0 && errno == EAGAIN)
+ {
+ rval = IoIncomplete ;
+ }
+ else if (i < 0)
+ {
+ endp->myErrno = errno ;
+ rval = IoFailed ;
+ }
+ else
+ d_printf (1,"Wrote 0 bytes in doWrite()?\n") ;
+
+ free (vp) ;
+ }
+ else
+ rval = IoDone ;
+
+ TMRstop(TMR_WRITE);
+ return rval ;
+}
+
+
+static IoStatus doExcept (EndPoint endp)
+{
+ int optval;
+ socklen_t size ;
+ int fd = endPointFd (endp) ;
+
+ if (getsockopt (fd, SOL_SOCKET, SO_ERROR,
+ (char *) &optval, &size) != 0)
+ syswarn ("ME exception: getsockopt (%d)", fd) ;
+ else if (optval != 0)
+ {
+ errno = optval ;
+ syswarn ("ME exception: fd %d", fd) ;
+ }
+ else
+ syswarn ("ME exception: fd %d: Unknown error", fd) ;
+
+#if 0
+ sleep (5) ;
+ abort () ;
+#endif
+
+ /* Not reached */
+ return IoFailed ;
+}
+
+#if 0
+static void endPointPrint (EndPoint ep, FILE *fp)
+{
+ fprintf (fp,"EndPoint [%p]: fd [%d]\n",(void *) ep, ep->myFd) ;
+}
+#endif
+
+static void signalHandler (int s)
+{
+ sigFlags[s] = 1 ;
+#ifndef HAVE_SIGACTION
+ xsignal (s, signalHandler) ;
+#endif
+}
+
+
+static void pipeHandler (int s)
+{
+ xsignal (s, pipeHandler) ;
+}
+
+
+/* compare the hit ratio of two endpoint for qsort. We're sorting the
+ endpoints on their relative activity */
+static int hitCompare (const void *v1, const void *v2)
+{
+ const struct endpoint_s *e1 = *((const struct endpoint_s * const *) v1) ;
+ const struct endpoint_s *e2 = *((const struct endpoint_s * const *) v2) ;
+ double e1Hit = e1->selectHits ;
+ double e2Hit = e2->selectHits ;
+
+ if (e1 == mainEndPoint)
+ return -1 ;
+ else if (e2 == mainEndPoint)
+ return 1 ;
+ else if (e1Hit < e2Hit)
+ return 1 ;
+ else if (e1Hit > e2Hit)
+ return -1 ;
+
+ return 0 ;
+}
+
+
+
+/* We maintain the endpoints in order of the percent times they're ready
+ for read/write when they've been selected. This helps us favour the more
+ active endpoints. */
+static void reorderPriorityList (void)
+{
+ unsigned int i, j ;
+ static int thisTime = 4;
+
+ /* only sort every 4th time since it's so expensive */
+ if (--thisTime > 0)
+ return ;
+
+ thisTime = 4;
+
+ for (i = j = 0; i < priorityCount; i++)
+ if (priorityList [i] != NULL)
+ {
+ if (i != j)
+ priorityList [j] = priorityList [i] ;
+ j++ ;
+ }
+
+ for (i = j; i < priorityCount; i++)
+ priorityList [ i ] = NULL;
+
+ priorityCount = j;
+
+ qsort (priorityList, (size_t)priorityCount, sizeof (EndPoint), &hitCompare);
+}
+
+
+#define TIMEOUT_POOL_SIZE ((4096 - 2 * (sizeof (void *))) / (sizeof (TimerElemStruct)))
+
+/* create a new timeout data structure properly initialized. */
+static TimerElem newTimerElem (TimeoutId i, time_t w, EndpTCB f, void *d)
+{
+ TimerElem p ;
+
+ if (timeoutPool == NULL)
+ {
+ unsigned int j ;
+
+ timeoutPool = xmalloc (sizeof(TimerElemStruct) * TIMEOUT_POOL_SIZE) ;
+
+ for (j = 0; j < TIMEOUT_POOL_SIZE - 1; j++)
+ timeoutPool[j] . next = &(timeoutPool [j + 1]) ;
+ timeoutPool [TIMEOUT_POOL_SIZE-1] . next = NULL ;
+ }
+
+ p = timeoutPool ;
+ timeoutPool = timeoutPool->next ;
+
+ ASSERT (p != NULL) ;
+
+ p->id = i ;
+ p->when = w ;
+ p->func = f ;
+ p->data = d ;
+ p->next = NULL ;
+
+ return p ;
+}
+
+
+
+/* add a new timeout structure to the global list. */
+static TimeoutId timerElemAdd (time_t when, EndpTCB func, void *data)
+{
+ TimerElem p = newTimerElem (++nextId ? nextId : ++nextId,when,func,data) ;
+ TimerElem n = timeoutQueue ;
+ TimerElem q = NULL ;
+
+ while (n != NULL && n->when <= when)
+ {
+ q = n ;
+ n = n->next ;
+ }
+
+ if (n == NULL && q == NULL) /* empty list so put at head */
+ timeoutQueue = p ;
+ else if (q == NULL) /* put at head of list */
+ {
+ p->next = timeoutQueue ;
+ timeoutQueue = p ;
+ }
+ else if (n == NULL) /* put at end of list */
+ q->next = p ;
+ else /* in middle of list */
+ {
+ p->next = q->next ;
+ q->next = p ;
+ }
+
+ timeoutQueueLength++ ;
+
+ return p->id ;
+}
+
+
+/* Fills in TOUT with the timeout to use on the next call to
+ * select. Returns TOUT. If there is no timeout, then returns NULL. If the
+ * timeout has already passed, then it calls the timeout handling routine
+ * first.
+ */
+static struct timeval *getTimeout (struct timeval *tout)
+{
+ struct timeval *rval = NULL ;
+
+ if (timeoutQueue != NULL)
+ {
+ time_t now = theTime() ;
+
+ while (timeoutQueue && now > timeoutQueue->when)
+ doTimeout () ;
+
+ if (timeoutQueue != NULL && now == timeoutQueue->when)
+ {
+ tout->tv_sec = 0 ;
+ tout->tv_usec = 0 ;
+ rval = tout ;
+ }
+ else if (timeoutQueue != NULL)
+ {
+ tout->tv_sec = timeoutQueue->when - now ;
+ tout->tv_usec = 0 ;
+ rval = tout ;
+ }
+ }
+
+ return rval ;
+}
+
+
+
+
+
+
+static void doTimeout (void)
+{
+ EndpTCB cbk = timeoutQueue->func ;
+ void *data = timeoutQueue->data ;
+ TimerElem p = timeoutQueue ;
+ TimeoutId tid = timeoutQueue->id ;
+
+ timeoutQueue = timeoutQueue->next ;
+
+ p->next = timeoutPool ;
+ timeoutPool = p ;
+
+ timeoutQueueLength-- ;
+
+ if (cbk)
+ (*cbk) (tid, data) ; /* call the callback function */
+}
+
+
+
+
+
+#if defined (WANT_MAIN)
+
+
+#define BUFF_SIZE 100
+
+
+void timerCallback (void *cd) ;
+void timerCallback (void *cd)
+{
+ d_printf (1,"Callback \n") ;
+}
+
+
+void lineIsWritten (EndPoint ep, IoStatus status, Buffer *buffer, void *data);
+void lineIsWritten (EndPoint ep, IoStatus status, Buffer *buffer, void *data)
+{
+ int i ;
+
+ if (status == IoDone)
+ d_printf (1,"LINE was written\n") ;
+ else
+ {
+ int oldErrno = errno ;
+
+ errno = endPointErrno (ep) ;
+ perror ("write failed") ;
+ errno = oldErrno ;
+ }
+
+ for (i = 0 ; buffer && buffer [i] ; i++)
+ delBuffer (buffer [i]) ;
+}
+
+void lineIsRead (EndPoint myEp, IoStatus status, Buffer *buffer, void *data);
+void lineIsRead (EndPoint myEp, IoStatus status, Buffer *buffer, void *d)
+{
+ Buffer *writeBuffers, *readBuffers ;
+ Buffer newBuff1, newBuff2 ;
+ Buffer newInputBuffer ;
+ char *data, *p ;
+ size_t len ;
+
+ if (status == IoFailed)
+ {
+ int oldErrno = errno ;
+
+ errno = endPointErrno (myEp) ;
+ perror ("read failed") ;
+ errno = oldErrno ;
+
+ return ;
+ }
+ else if (status == IoEOF)
+ {
+ d_printf (1,"EOF on endpoint.\n") ;
+ delEndPoint (myEp) ;
+
+ return ;
+ }
+
+
+ data = bufferBase (buffer[0]) ;
+ len = bufferDataSize (buffer[0]) ;
+
+ if (data [len - 1] == '\r' || data [len - 1] == '\n')
+ bufferDecrDataSize (buffer [0],1) ;
+ if (data [len - 1] == '\r' || data [len - 1] == '\n')
+ bufferDecrDataSize (buffer [0],1) ;
+
+ data [len] = '\0' ;
+
+ d_printf (1,"Got a line: %s\n", data) ;
+
+ newBuff1 = newBuffer (len + 50) ;
+ newBuff2 = newBuffer (len + 50) ;
+ newInputBuffer = newBuffer (BUFF_SIZE) ;
+
+ p = bufferBase (newBuff1) ;
+ strcpy (p, "Thanks for that \"") ;
+ bufferSetDataSize (newBuff1,strlen (p)) ;
+
+ p = bufferBase (newBuff2) ;
+ strcpy (p,"\" very tasty\n") ;
+ bufferSetDataSize (newBuff2,strlen (p)) ;
+
+ writeBuffers = makeBufferArray (newBuff1,buffer[0],newBuff2,NULL) ;
+ readBuffers = makeBufferArray (newInputBuffer,NULL) ;
+
+ prepareWrite (myEp,writeBuffers,lineIsWritten,NULL) ;
+ prepareRead (myEp,readBuffers,lineIsRead,NULL,1) ;
+
+#if 0
+ myEp->registerWake (&timerCallback,theTime() + 7,0) ;
+#endif
+}
+
+
+static void printDate (TimeoutId tid, void *data) ;
+static void printDate (TimeoutId tid, void *data)
+{
+ time_t t ;
+
+ t = theTime() ;
+
+ d_printf (1,"Timeout (%d) time now is %ld %s",
+ (int) tid,(long) t,ctime(&t)) ;
+
+ if (timeoutQueue == NULL)
+ {
+ int ti = (rand () % 10) + 1 ;
+
+ prepareSleep (printDate,ti,data) ;
+ }
+}
+
+TimeoutId rm ;
+
+static void Timeout (TimeoutId tid, void *data) ;
+static void Timeout (TimeoutId tid, void *data)
+{
+ static int seeded ;
+ static int howMany ;
+ static int i ;
+ time_t t = theTime() ;
+
+ if ( !seeded )
+ {
+ srand (t) ;
+ seeded = 1 ;
+ }
+
+ d_printf (1,"Timeout (%d) time now is %ld %s",
+ (int) tid, (long) t,ctime(&t)) ;
+
+ if (timeoutQueue != NULL && timeoutQueue->next != NULL)
+ d_printf (1,"%s timeout id %d\n",
+ (removeTimeout (rm) ? "REMOVED" : "FAILED TO REMOVE"), rm) ;
+ rm = 0 ;
+
+ howMany = (rand() % 10) + (timeoutQueue == NULL ? 1 : 0) ;
+
+ for (i = 0 ; i < howMany ; i++ )
+ {
+ TimeoutId id ;
+ int count = (rand () % 30) + 1 ;
+
+ id = (i % 2 == 0 ? prepareSleep (Timeout,count,data)
+ : prepareWake (Timeout,t + count,data)) ;
+
+ if (rm == 0)
+ rm = id ;
+ }
+}
+
+
+void newConn (EndPoint ep, IoStatus status, Buffer *buffer, void *d) ;
+void newConn (EndPoint ep, IoStatus status, Buffer *buffer, void *d)
+{
+ EndPoint newEp ;
+ struct sockaddr_in in ;
+ Buffer *readBuffers ;
+ Buffer newBuff = newBuffer (BUFF_SIZE) ;
+ int len = sizeof (in) ;
+ int fd ;
+
+ memset (&in, 0, sizeof (in)) ;
+
+ fd = accept (ep->myFd, (struct sockaddr *) &in, &len) ;
+
+ if (fd < 0)
+ {
+ perror ("::accept") ;
+ return ;
+ }
+
+ newEp = newEndPoint (fd) ;
+
+ prepareRead (ep, NULL, newConn,NULL,0) ;
+
+ readBuffers = makeBufferArray (newBuff,NULL) ;
+
+ prepareRead (newEp, readBuffers, lineIsRead, NULL, 1) ;
+
+ d_printf (1,"Set up a new connection\n");
+}
+
+
+int main (int argc, char **argv)
+{
+ EndPoint accConn ;
+ struct sockaddr_in accNet ;
+ int accFd = socket (AF_INET,SOCK_STREAM,0) ;
+ unsigned short port = atoi (argc > 1 ? argv[1] : "10000") ;
+ time_t t = theTime() ;
+
+
+ program = strrchr (argv[0],'/') ;
+
+ if (!program)
+ program = argv [0] ;
+ else
+ program++ ;
+
+ ASSERT (accFd >= 0) ;
+
+ memset (&accNet,0,sizeof (accNet)) ;
+ accNet.sin_family = AF_INET ;
+ accNet.sin_addr.s_addr = htonl (INADDR_ANY) ;
+ accNet.sin_port = htons (port) ;
+
+#ifdef LOG_PERROR
+ openlog (program, LOG_PERROR | LOG_PID, LOG_NEWS) ;
+#else
+ openlog (program, LOG_PID, LOG_NEWS) ;
+#endif
+
+ if (bind (accFd, (struct sockaddr *) &accNet, sizeof (accNet)) < 0)
+ {
+ perror ("bind: %m") ;
+ exit (1) ;
+ }
+
+ listen (accFd,5) ;
+
+ accConn = newEndPoint (accFd) ;
+
+ prepareRead (accConn,NULL,newConn,NULL,0) ;
+
+ prepareSleep (Timeout,5,(void *) 0x10) ;
+
+ t = theTime() ;
+ d_printf (1,"Time now is %s",ctime(&t)) ;
+
+ prepareWake (printDate,t + 16,NULL) ;
+
+ Run () ;
+
+ return 0;
+}
+#endif /* WANT_MAIN */
+
+/* Probably doesn't do the right thing for SIGCHLD */
+void setSigHandler (int signum, void (*ptr)(int))
+{
+ unsigned int i ;
+
+ if (sigHandlers == NULL)
+ {
+ sigHandlers = xmalloc (sizeof(sigfn) * NSIG) ;
+ sigFlags = xmalloc (sizeof(sig_atomic_t) * NSIG) ;
+ for (i = 0 ; i < NSIG ; i++)
+ {
+ sigHandlers [i] = NULL ;
+ sigFlags [i] = 0 ;
+ }
+ }
+
+ if (signum >= NSIG)
+ {
+ syslog (LOG_ERR,"ME signal number bigger than NSIG: %d vs %d",
+ signum,NSIG) ;
+ return ;
+ }
+
+ if (xsignal (signum, signalHandler) == SIG_ERR)
+ die ("signal failed: %s", strerror(errno)) ;
+
+ sigHandlers[signum] = ptr ;
+}
+
+static void handleSignals (void)
+{
+ int i ;
+#if defined(USE_SIGVEC)
+ int mask ;
+#endif
+
+ for (i = 1; i < NSIG; i++)
+ {
+ if (sigFlags[i])
+ {
+#if defined(USE_SIGACTION)
+ sigset_t set, oset ;
+
+ if (sigemptyset (&set) != 0 || sigaddset (&set, i) != 0)
+ die ("sigemptyset or sigaddset failed") ;
+ if (sigprocmask (SIG_BLOCK, &set, &oset) != 0)
+ die ("sigprocmask failed: %s", strerror(errno)) ;
+#elif defined(USE_SIGVEC)
+# ifndef sigmask
+# define sigmask(s) (1 << ((s) - 1))
+# endif
+ int mask ;
+
+ mask = sigblock (sigmask(i)) ;
+#elif defined(USE_SIGSET)
+ if (sighold (i) != 0)
+ die ("sighold failed: %s", strerror(errno)) ;
+#else
+ /* hope for the best */
+#endif
+
+ sigFlags[i] = 0;
+
+ if (sigHandlers[i] != NULL &&
+ sigHandlers[i] != SIG_IGN &&
+ sigHandlers[i] != SIG_DFL)
+ (sigHandlers[i])(i) ;
+
+#if defined(USE_SIGACTION)
+ if (sigprocmask (SIG_SETMASK, &oset, (sigset_t *)NULL) != 0)
+ die ("sigprocmask failed: %s", strerror(errno)) ;
+#elif defined(USE_SIGVEC)
+ sigsetmask (mask) ;
+#elif defined(USE_SIGSET)
+ if (sigrelse (i) != 0)
+ die ("sigrelse failed: %s", strerror(errno)) ;
+#else
+ /* hope for the best */
+#endif
+ }
+ }
+}
+
+
+int endpointConfigLoadCbk (void *data)
+{
+ FILE *fp = (FILE *) data ;
+ long ival ;
+ int rval = 1 ;
+
+ if (getInteger (topScope,"stdio-fdmax",&ival,NO_INHERIT))
+ {
+ stdioFdMax = ival ;
+
+#if ! defined (FD_SETSIZE)
+
+ if (stdioFdMax > 0)
+ {
+ logOrPrint (LOG_ERR,fp,NO_STDIO_FDMAX) ;
+ stdioFdMax = 0 ;
+ rval = 0 ;
+ }
+
+#else
+
+ if (stdioFdMax > FD_SETSIZE)
+ {
+ logOrPrint (LOG_ERR,fp,
+ "ME config: value of %s (%ld) in %s is higher"
+ " than maximum of %ld. Using %ld","stdio-fdmax",
+ ival,"global scope",
+ (long) FD_SETSIZE, (long) FD_SETSIZE) ;
+ stdioFdMax = FD_SETSIZE ;
+ rval = 0 ;
+ }
+
+#endif
+
+ }
+ else
+ stdioFdMax = 0 ;
+
+ return rval ;
+}
+
+
+
+#if 0
+/* definitely not the fastest, but the most portable way to find the first
+ set bit in a mask */
+static int ff_set (fd_set *set,unsigned int start)
+{
+ unsigned int i ;
+
+ for (i = start ; i < FD_SETSIZE ; i++)
+ if (FD_ISSET (i,set))
+ return (int) i ;
+
+ return -1 ;
+}
+
+
+static int ff_free (fd_set *set, unsigned int start)
+{
+ unsigned int i ;
+
+ for (i = start ; i < FD_SETSIZE ; i++)
+ if (!FD_ISSET (i,set))
+ return i ;
+
+
+ return -1 ;
+}
+#endif
+
+
+static void endpointCleanup (void)
+{
+ free (endPoints) ;
+ free (priorityList) ;
+ free (sigHandlers) ;
+ endPoints = NULL ;
+ priorityList = NULL ;
+ sigHandlers = NULL ;
+}
--- /dev/null
+/* $Id: endpoint.h 6648 2004-01-25 20:07:11Z rra $
+**
+** The public interface to the Endpoint class.
+**
+** Written by James Brister <brister@vix.com>
+**
+** The EndPoint objects are encapsulations of file descriptors that normally
+** do blocking i/o (i.e. NOT fd's hooked to a disk file). The EndPoint class
+** provides methods for reqesting read/writes to happen when next possible
+** and for the requestor to be notified when the i/o is complete (or failed
+** for some reason). Facilities for timeout notifications are provided too.
+**
+** We should add a way to cancel prepared read/write.
+*/
+
+#if ! defined ( endpoint_h__ )
+#define endpoint_h__
+
+#include "misc.h"
+
+
+#define clearTimer(timerId) \
+ do {\
+ if((timerId)!=0) { \
+ removeTimeout(timerId); \
+ timerId=0; \
+ } \
+ }while(0)
+
+
+
+/* These typedefs really lives in misc.h
+ *
+ *****************************************
+ *
+ * The basic (opqaue to the outside world) type.
+ *
+ * typedef struct endpoint_s *EndPoint ;
+ *
+ *****************************************
+ *
+ * The returns status of an IO request
+ *
+ * typedef enum {
+ * IoDone, i/o completed successfully
+ * IoIncomplete, i/o still got more to read/write
+ * IoProgress, i/o still got more to read/write
+ * IoFailed i/o failed
+ * } IoStatus ;
+ *
+ * The completion callbacks are never called with the status IoIncomplete or
+ * IoProgress.
+ *
+ *****************************************
+ *
+ * typedef for function callback when IO is complete (or failed).
+ * E is the EndPoint
+ * I is the status of the IO
+ * B is the buffer the IO was to read to or write from.
+ * D is the client data originally given to prepare{Write,Read}
+ *
+ * typedef void (*EndpRWCB) (EndPoint e, IoStatus i, Buffer b, void *d) ;
+ *
+ *****************************************
+ *
+ * typedef for function callback when a timer has gone off. D is the client
+ * data given to prepare{Sleep,Wake}
+ *
+ * typedef void (*EndpTCB) (void *d) ;
+ *
+ */
+
+/* create a new EndPoint hooked up to the given file descriptor */
+EndPoint newEndPoint (int fd) ;
+
+/* shutdown the file descriptor and delete the endpoint. */
+void delEndPoint (EndPoint endp) ;
+
+/* return the file descriptor the endpoint is managing */
+int endPointFd (EndPoint endp) ;
+
+/* Request a read when available. Reads MINLEN bytes into the
+ * buffers in BUFFS. BUFFS is an array of Buffers, the last of which
+ * must be NULL. Note that ownership of BUFFS is never asserted, but
+ * the ownership of the Buffers in it is. So if an EndPoint is
+ * deleted while a read is pending the Buffers will be released, but
+ * the array won't be. If MINLEN is 0 then the buffers must be
+ * filled. The FUNC function gets called when the read is
+ * complete. CLIENTDATA is simply passed back to the
+ * callback. Returns non-zero if can be scheduled for processing.
+ */
+int prepareRead (EndPoint endp,
+ Buffer *buffs,
+ EndpRWCB func,
+ void *clientData,
+ int minlen) ;
+
+/* Request a write when possible. All the data in the buffers in
+ * BUFFS will be written out the endpoint. BUFFS is a NULL
+ * terminated array of Buffers. See prepareWrite for a discussion on
+ * the ownership of BUFFS and the Buffers inside BUFFS. The PROGRESS
+ * callback function will be called and the CLIENTDATA value will be
+ * passed through to it whenever any data is written except for the
+ * final write. The DONE callback function will be called and the
+ * CLIENTDATA value will be passed through to it after the final write.
+ * Returns non-zero if scheduled succesfully.
+ */
+int prepareWrite (EndPoint endp,
+ Buffer *buffs,
+ EndpRWCB progress,
+ EndpRWCB done,
+ void *clientData) ;
+
+/* cancel any outstanding reads. */
+void cancelRead (EndPoint endp) ;
+
+/* cancel any outstanding writes. */
+void cancelWrite (EndPoint endp, char *buffer, size_t *len) ;
+
+/* return true if prepareRead has been called, but not serviced yet */
+bool readIsPending (EndPoint endp) ;
+
+/* Request a wakeup at a given time. */
+TimeoutId prepareWake (EndpTCB func,
+ time_t timeToWake,
+ void *clientData) ;
+
+/* return true if prepareWrite has been called, but not serviced yet */
+bool writeIsPending (EndPoint endp) ;
+
+/* Requests a wakeup TIMETOSLEEP seconds from now. */
+TimeoutId prepareSleep (EndpTCB func,
+ int timeToSleep,
+ void *clientData) ;
+
+/* Updates tid to wakeup TIMETOSLEEP seconds from now. */
+TimeoutId updateSleep (TimeoutId tid,
+ EndpTCB func,
+ int timeToSleep,
+ void *clientData) ;
+
+ /* Set up a function to be called whenever the endpoint's file descriptor
+ is NOT ready. This is called after all other fd-ready endpoints have
+ been serviced. */
+void *addWorkCallback (EndPoint endp, EndpWorkCbk cbk, void *data) ;
+
+void setSigHandler (int sig, void (*)(int)) ;
+
+/* remove the timeout that was previously requested. Retuesn true if
+ succesfully removed, false otherwise. 0 is a legal parameter value, in
+ which case the function simply returns. */
+bool removeTimeout (TimeoutId tid) ;
+
+/* start the select loop. An initial prepare(Read|Write) or a timeout
+ better have been setup. Doesn't return unless stopRun called */
+void Run (void) ;
+
+/* stops the Run loop and makes Run() return */
+void stopRun (void) ;
+
+/* returns the errno the endpoint got. */
+int endPointErrno (EndPoint endp) ;
+
+/* Tell the EndPoint class that the given EndPoint should always be
+ considered first for servicing (i.e. the EndPoint connectied to innd) */
+void setMainEndPoint (EndPoint endp) ;
+
+/* returns the fd of the main endpoint */
+int getMainEndPointFd (void) ;
+
+void freeTimeoutQueue (void) ;
+
+int endpointConfigLoadCbk (void *data) ;
+
+
+/*
+ * kre's cool idea for reducing the number of times time() is called.
+ */
+extern time_t PrivateTime;
+
+#define theTime() (PrivateTime ? PrivateTime : time(&PrivateTime))
+#define timePasses() (PrivateTime = 0)
+
+#endif /* endpoint_h__ */
--- /dev/null
+/* $Id: host.c 7833 2008-05-18 20:04:35Z iulius $
+**
+** The implementation of the innfeed Host class.
+**
+** Written by James Brister <brister@vix.com>
+*/
+
+#include "innfeed.h"
+#include "config.h"
+#include "clibrary.h"
+#include "portable/socket.h"
+
+#include <assert.h>
+#include <ctype.h>
+#include <errno.h>
+#include <float.h>
+#include <math.h>
+#include <netdb.h>
+#include <syslog.h>
+#include <sys/param.h>
+
+#ifdef HAVE_LIMITS_H
+# include <limits.h>
+#endif
+
+#include "inn/innconf.h"
+#include "inn/messages.h"
+#include "libinn.h"
+
+#include "article.h"
+#include "buffer.h"
+#include "configfile.h"
+#include "connection.h"
+#include "endpoint.h"
+#include "host.h"
+#include "innlistener.h"
+#include "tape.h"
+
+#define REQ 1
+#define NOTREQ 0
+#define NOTREQNOADD 2
+
+#define VALUE_OK 0
+#define VALUE_TOO_HIGH 1
+#define VALUE_TOO_LOW 2
+#define VALUE_MISSING 3
+#define VALUE_WRONG_TYPE 4
+
+#define METHOD_STATIC 0
+#define METHOD_APS 1
+#define METHOD_QUEUE 2
+#define METHOD_COMBINED 3
+
+/* the limit of number of connections open when a host is
+ set to 0 to mean "infinite" */
+#define MAXCON 500
+#define MAXCONLIMIT(xx) ((xx==0)?MAXCON:xx)
+
+#define BACKLOGFILTER 0.7
+#define BACKLOGLWM 20.0
+#define BACKLOGHWM 50.0
+
+/* time between retrying blocked hosts in seconds */
+#define TRYBLOCKEDHOSTPERIOD 120
+
+extern char *configFile ;
+#if defined(hpux) || defined(__hpux) || defined(_SCO_DS)
+extern int h_errno;
+#endif
+
+/* the host keeps a couple lists of these */
+typedef struct proc_q_elem
+{
+ Article article ;
+ struct proc_q_elem *next ;
+ struct proc_q_elem *prev ;
+ time_t whenToRequeue ;
+} *ProcQElem ;
+
+typedef struct host_param_s
+{
+ char *peerName;
+ char *ipName;
+ struct sockaddr_in *bindAddr;
+#ifdef HAVE_INET6
+ struct sockaddr_in6 *bindAddr6;
+#endif
+ int family;
+ unsigned int articleTimeout;
+ unsigned int responseTimeout;
+ unsigned int initialConnections;
+ unsigned int absMaxConnections;
+ unsigned int maxChecks;
+ unsigned short portNum;
+ bool forceIPv4;
+ unsigned int closePeriod;
+ unsigned int dynamicMethod;
+ bool wantStreaming;
+ bool dropDeferred;
+ bool minQueueCxn;
+ double lowPassLow; /* as percentages */
+ double lowPassHigh;
+ double lowPassFilter;
+ unsigned int backlogLimit ;
+ unsigned int backlogLimitHigh ;
+ double backlogFactor ;
+ double dynBacklogFilter ;
+ double dynBacklogLowWaterMark ;
+ double dynBacklogHighWaterMark ;
+ bool backlogFeedFirst ;
+ char *username;
+ char *password;
+} *HostParams ;
+
+struct host_s
+{
+ InnListener listener ; /* who created me. */
+ struct sockaddr **ipAddrs ; /* the ip addresses of the remote */
+ int nextIpAddr ; /* the next ip address to hand out */
+
+ Connection *connections ; /* NULL-terminated list of all connections */
+ bool *cxnActive ; /* true if the corresponding cxn is active */
+ bool *cxnSleeping ; /* true if the connection is sleeping */
+ unsigned int maxConnections; /* maximum no of cxns controlled by method */
+ unsigned int activeCxns ; /* number of connections currently active */
+ unsigned int sleepingCxns ; /* number of connections currently sleeping */
+ Connection blockedCxn ; /* the first connection to get the 400 banner*/
+ Connection notThisCxn ; /* don't offer articles to this connection */
+
+ HostParams params; /* Parameters from config file */
+
+ bool remoteStreams ; /* true if remote supports streaming */
+
+ ProcQElem queued ; /* articles done nothing with yet. */
+ ProcQElem queuedTail ;
+
+ ProcQElem processed ; /* articles given to a Connection */
+ ProcQElem processedTail ;
+
+ ProcQElem deferred ; /* articles which have been deferred by */
+ ProcQElem deferredTail ; /* a connection */
+
+ TimeoutId statsId ; /* timeout id for stats logging. */
+ TimeoutId ChkCxnsId ; /* timeout id for dynamic connections */
+ TimeoutId deferredId ; /* timeout id for deferred articles */
+
+ Tape myTape ;
+
+ bool backedUp ; /* set to true when all cxns are full */
+ unsigned int backlog ; /* number of arts in `queued' queue */
+ unsigned int deferLen ; /* number of arts in `deferred' queue */
+
+ bool loggedModeOn ; /* true if we logged going into no-CHECK mode */
+ bool loggedModeOff ; /* true if we logged going out of no-CHECK mode */
+
+ bool loggedBacklog ; /* true if we already logged the fact */
+ bool notifiedChangedRemBlckd ; /* true if we logged a new response 400 */
+ bool removeOnReload ; /* true if host should be removed at end of
+ * config reload
+ */
+ bool isDynamic; /* true if host created dynamically */
+
+ /* these numbers get reset periodically (after a 'final' logging). */
+ unsigned int artsOffered ; /* # of articles we offered to remote. */
+ unsigned int artsAccepted ; /* # of articles succesfully transferred */
+ unsigned int artsNotWanted ; /* # of articles remote already had */
+ unsigned int artsRejected ; /* # of articles remote rejected */
+ unsigned int artsDeferred ; /* # of articles remote asked us to retry */
+ unsigned int artsMissing ; /* # of articles whose file was missing. */
+ unsigned int artsToTape ; /* # of articles given to tape */
+ unsigned int artsQueueOverflow ; /* # of articles that overflowed `queued' */
+ unsigned int artsCxnDrop ; /* # of articles caught in dead cxn */
+ unsigned int artsHostSleep ; /* # of articles spooled by sleeping host */
+ unsigned int artsHostClose ; /* # of articles caught by closing host */
+ unsigned int artsFromTape ; /* # of articles we pulled off tape */
+ double artsSizeAccepted ; /* size of articles succesfully transferred */
+ double artsSizeRejected ; /* size of articles remote rejected */
+
+ /* Dynamic Peerage - MGF */
+ unsigned int artsProcLastPeriod ; /* # of articles processed in last period */
+ unsigned int secsInLastPeriod ; /* Number of seconds in last period */
+ unsigned int lastCheckPoint ; /* total articles at end of last period */
+ unsigned int lastSentCheckPoint ; /* total articles sent end of last period */
+ unsigned int lastTotalCheckPoint ; /* total articles total end of last period */
+ bool maxCxnChk ; /* check for maxConnections */
+ time_t lastMaxCxnTime ; /* last time a maxConnections increased */
+ time_t lastChkTime; /* last time a check was made for maxConnect */
+ unsigned int nextCxnTimeChk ; /* next check for maxConnect */
+
+ double backlogFilter; /* IIR filter for size of backlog */
+
+ /* These numbers are as above, but for the life of the process. */
+ unsigned int gArtsOffered ;
+ unsigned int gArtsAccepted ;
+ unsigned int gArtsNotWanted ;
+ unsigned int gArtsRejected ;
+ unsigned int gArtsDeferred ;
+ unsigned int gArtsMissing ;
+ unsigned int gArtsToTape ;
+ unsigned int gArtsQueueOverflow ;
+ unsigned int gArtsCxnDrop ;
+ unsigned int gArtsHostSleep ;
+ unsigned int gArtsHostClose ;
+ unsigned int gArtsFromTape ;
+ double gArtsSizeAccepted ;
+ double gArtsSizeRejected ;
+ unsigned int gCxnQueue ;
+ unsigned int gNoQueue ;
+
+ time_t firstConnectTime ; /* time of first connect. */
+ time_t connectTime ; /* the time the first connection was fully
+ set up (MODE STREAM and everything
+ else). */
+ time_t spoolTime ; /* the time the Host had to revert to
+ spooling articles to tape. */
+ time_t lastSpoolTime ; /* the time the last time the Host had to
+ revert to spooling articles to tape. */
+ time_t nextIpLookup ; /* time of last IP name resolution */
+
+ char *blockedReason ; /* what the 400 from the remote says. */
+
+ Host next ; /* for global list of hosts. */
+
+ unsigned long dlAccum ; /* cumulative deferLen */
+ unsigned int blNone ; /* number of times the backlog was 0 */
+ unsigned int blFull ; /* number of times the backlog was full */
+ unsigned int blQuartile[4] ; /* number of times in each quartile */
+ unsigned long blAccum ; /* cumulative backlog for computing mean */
+ unsigned int blCount ; /* the sample count */
+};
+
+/* A holder for the info we got out of the config file, but couldn't create
+ the Host object for (normally due to lock-file problems).*/
+
+typedef struct host_holder_s
+{
+ HostParams params;
+ struct host_holder_s *next ;
+} *HostHolder ;
+
+
+/* These numbers are as above, but for all hosts over
+ the life of the process. */
+long procArtsOffered ;
+long procArtsAccepted ;
+long procArtsNotWanted ;
+long procArtsRejected ;
+long procArtsDeferred ;
+long procArtsMissing ;
+double procArtsSizeAccepted ;
+double procArtsSizeRejected ;
+long procArtsToTape ;
+long procArtsFromTape ;
+
+static HostParams defaultParams=NULL;
+
+static HostHolder blockedHosts ; /* lists of hosts we can't lock */
+static TimeoutId tryBlockedHostsId = 0 ;
+static time_t lastStatusLog ;
+
+ /*
+ * Host object private methods.
+ */
+static void articleGone (Host host, Connection cxn, Article article) ;
+static void hostStopSpooling (Host host) ;
+static void hostStartSpooling (Host host) ;
+static void hostLogStats (Host host, bool final) ;
+static void hostStatsTimeoutCbk (TimeoutId tid, void *data) ;
+static void hostDeferredArtCbk (TimeoutId tid, void *data) ;
+static void backlogToTape (Host host) ;
+static void queuesToTape (Host host) ;
+static bool amClosing (Host host) ;
+static void hostLogStatus (void) ;
+static void hostPrintStatus (Host host, FILE *fp) ;
+static int validateBool (FILE *fp, const char *name,
+ int required, bool setval,
+ scope * sc, unsigned int inh);
+static int validateReal (FILE *fp, const char *name, double low,
+ double high, int required, double setval,
+ scope * sc, unsigned int inh);
+static int validateInteger (FILE *fp, const char *name,
+ long low, long high, int required, long setval,
+ scope * sc, unsigned int inh);
+
+static HostParams newHostParams(HostParams p);
+static void freeHostParams(HostParams params);
+
+static HostHolder FindBlockedHost(const char *name);
+static void addBlockedHost(HostParams params);
+static void tryBlockedHosts(TimeoutId tid, void *data);
+static Host newHost (InnListener listener, HostParams p);
+
+static HostParams getHostInfo (void);
+static HostParams hostDetails (scope *s,
+ char *name,
+ bool isDefault,
+ FILE *fp);
+
+static Host findHostByName (char *name) ;
+static void hostCleanup (void) ;
+static void hostAlterMaxConnections(Host host,
+ unsigned int absMaxCxns, unsigned int maxCxns,
+ bool makeConnect);
+
+/* article queue management functions */
+static Article remHead (ProcQElem *head, ProcQElem *tail) ;
+static void queueArticle (Article article, ProcQElem *head, ProcQElem *tail,
+ time_t when) ;
+static bool remArticle (Article article, ProcQElem *head, ProcQElem *tail) ;
+
+
+
+
+
+/*
+ * Host class data
+ */
+
+/* if true then when a Host logs its stats, it has all its connections
+ log theirs too. */
+static bool logConnectionStats = (bool) LOG_CONNECTION_STATS ;
+
+/* The frequency in seconds with which a Host will log its stats. */
+static time_t statsPeriod = STATS_PERIOD ;
+static time_t statsResetPeriod = STATS_RESET_PERIOD ;
+
+static Host gHostList = NULL ;
+
+static unsigned int gHostCount = 0 ;
+
+static unsigned int maxIpNameLen = 0 ;
+static unsigned int maxPeerNameLen = 0 ;
+
+static unsigned int hostHighwater = HOST_HIGHWATER ;
+static time_t start ;
+static char startTime [30] ; /* for ctime(3) */
+static pid_t myPid ;
+
+static char *statusFile = NULL ;
+static unsigned int dnsRetPeriod ;
+static unsigned int dnsExpPeriod ;
+
+bool genHtml = false ;
+
+/*******************************************************************/
+/* PUBLIC FUNCTIONS */
+/*******************************************************************/
+
+
+/* function called when the config file is loaded */
+int hostConfigLoadCbk (void *data)
+{
+ int rval = 1, bval ;
+ long iv ;
+ FILE *fp = (FILE *) data ;
+ char *p ;
+
+
+ d_printf(1,"hostConfigLoadCbk\n");
+
+ if (defaultParams)
+ {
+ freeHostParams(defaultParams);
+ defaultParams=NULL;
+ }
+
+ /* get optional global defaults */
+ if (getInteger (topScope,"dns-retry",&iv,NO_INHERIT))
+ {
+ if (iv < 1)
+ {
+ rval = 0 ;
+ logOrPrint (LOG_ERR,fp,
+ "ME config: value of %s (%ld) in %s cannot be less"
+ " than 1. Using %ld","dns-retry",
+ iv,"global scope",(long)DNS_RETRY_PERIOD) ;
+ iv = DNS_RETRY_PERIOD ;
+ }
+ }
+ else
+ iv = DNS_RETRY_PERIOD ;
+ dnsRetPeriod = (unsigned int) iv ;
+
+
+ if (getInteger (topScope,"dns-expire",&iv,NO_INHERIT))
+ {
+ if (iv < 1)
+ {
+ rval = 0 ;
+ logOrPrint (LOG_ERR,fp,
+ "ME config: value of %s (%ld) in %s cannot be less"
+ " than 1. Using %ld","dns-expire",iv,
+ "global scope",(long)DNS_EXPIRE_PERIOD) ;
+ iv = DNS_EXPIRE_PERIOD ;
+ }
+ }
+ else
+ iv = DNS_EXPIRE_PERIOD ;
+ dnsExpPeriod = (unsigned int) iv ;
+
+ if (getBool (topScope,"gen-html",&bval,NO_INHERIT))
+ genHtml = (bval ? true : false) ;
+ else
+ genHtml = GEN_HTML ;
+
+ if (getString (topScope,"status-file",&p,NO_INHERIT))
+ {
+ hostSetStatusFile (p) ;
+ free (p) ;
+ }
+ else
+ hostSetStatusFile (INNFEED_STATUS) ;
+
+
+ if (getBool (topScope,"connection-stats",&bval,NO_INHERIT))
+ logConnectionStats = (bval ? true : false) ;
+ else
+ logConnectionStats = (LOG_CONNECTION_STATS ? true : false) ;
+
+
+ if (getInteger (topScope,"host-queue-highwater", &iv,NO_INHERIT))
+ {
+ if (iv < 0)
+ {
+ rval = 0 ;
+ logOrPrint (LOG_ERR,fp,
+ "ME config: value of %s (%ld) in %s cannot be less"
+ " than 0. Using %ld","host-queue-highwater",
+ iv,"global scope",(long) HOST_HIGHWATER) ;
+ iv = HOST_HIGHWATER ;
+ }
+ }
+ else
+ iv = HOST_HIGHWATER ;
+ hostHighwater = (unsigned int) iv ;
+
+ if (getInteger (topScope,"stats-period",&iv,NO_INHERIT))
+ {
+ if (iv < 0)
+ {
+ rval = 0 ;
+ logOrPrint (LOG_ERR,fp,
+ "ME config: value of %s (%ld) in %s cannot be less"
+ " than 0. Using %ld","stats-period",
+ iv,"global scope",(long)STATS_PERIOD) ;
+ iv = STATS_PERIOD ;
+ }
+ }
+ else
+ iv = STATS_PERIOD ;
+ statsPeriod = (unsigned int) iv ;
+
+
+ if (getInteger (topScope,"stats-reset",&iv,NO_INHERIT))
+ {
+ if (iv < 0)
+ {
+ rval = 0 ;
+ logOrPrint (LOG_ERR,fp,
+ "ME config: value of %s (%ld) in %s cannot be less"
+ " than 0. Using %ld","stats-reset",iv,
+ "global scope",(long)STATS_RESET_PERIOD) ;
+ iv = STATS_RESET_PERIOD ;
+ }
+ }
+ else
+ iv = STATS_RESET_PERIOD ;
+ statsResetPeriod = (unsigned int) iv ;
+
+ defaultParams=hostDetails(topScope, NULL, true, fp);
+ ASSERT(defaultParams!=NULL);
+
+ return rval ;
+}
+
+/*
+ * make a new HostParams structure copying an existing one
+ * or from compiled defaults
+ */
+
+HostParams newHostParams(HostParams p)
+{
+ HostParams params;
+
+ params = xmalloc (sizeof(struct host_param_s)) ;
+
+ if (p != NULL)
+ {
+ /* Copy old stuff in */
+ memcpy ((char *) params, (char *) p, sizeof(struct host_param_s));
+ if (params->peerName)
+ params->peerName = xstrdup(params->peerName);
+ if (params->ipName)
+ params->ipName = xstrdup(params->ipName);
+ if (params->bindAddr)
+ {
+ struct sockaddr_in *s = params->bindAddr;
+ params->bindAddr = xmalloc(sizeof(*s));
+ memcpy(params->bindAddr, s, sizeof(*s));
+ }
+#ifdef HAVE_INET6
+ if (params->bindAddr6)
+ {
+ struct sockaddr_in6 *s = params->bindAddr6;
+ params->bindAddr6 = xmalloc(sizeof(*s));
+ memcpy(params->bindAddr6, s, sizeof(*s));
+ }
+#endif
+ }
+ else
+ {
+ /* Fill in defaults */
+ params->peerName=NULL;
+ params->ipName=NULL;
+ params->bindAddr=NULL;
+#ifdef HAVE_INET6
+ params->bindAddr6=NULL;
+#endif
+ params->family = 0;
+ params->articleTimeout=ARTTOUT;
+ params->responseTimeout=RESPTOUT;
+ params->initialConnections=INIT_CXNS;
+ params->absMaxConnections=MAX_CXNS;
+ params->maxChecks=MAX_Q_SIZE;
+ params->portNum=PORTNUM;
+ params->forceIPv4=FORCE_IPv4;
+ params->closePeriod=CLOSE_PERIOD;
+ params->dynamicMethod=METHOD_STATIC;
+ params->wantStreaming=STREAM;
+ params->dropDeferred=false;
+ params->minQueueCxn=false;
+ params->lowPassLow=NOCHECKLOW;
+ params->lowPassHigh=NOCHECKHIGH;
+ params->lowPassFilter=FILTERVALUE;
+ params->backlogLimit=BLOGLIMIT;
+ params->backlogLimitHigh=BLOGLIMIT_HIGH ;
+ params->backlogFactor=LIMIT_FUDGE ;
+ params->dynBacklogFilter = BACKLOGFILTER ;
+ params->dynBacklogLowWaterMark = BACKLOGLWM;
+ params->dynBacklogHighWaterMark = BACKLOGHWM;
+ params->backlogFeedFirst=false;
+ params->username=NULL;
+ params->password=NULL;
+ }
+ return (params);
+}
+
+/*
+ * Free up a param structure
+ */
+
+void freeHostParams(HostParams params)
+{
+ ASSERT(params != NULL);
+ if (params->peerName)
+ free (params->peerName) ;
+ if (params->ipName)
+ free (params->ipName) ;
+ if (params->bindAddr)
+ free (params->bindAddr) ;
+#ifdef HAVE_INET6
+ if (params->bindAddr6)
+ free (params->bindAddr6) ;
+#endif
+ free (params) ;
+}
+
+static void hostReconfigure(Host h, HostParams params)
+{
+ unsigned int i, absMaxCxns ;
+ double oldBacklogFilter ;
+
+ if (strcmp(h->params->ipName, params->ipName) != 0)
+ {
+ free (h->params->ipName) ;
+ h->params->ipName = xstrdup (params->ipName) ;
+ h->nextIpLookup = theTime () ;
+ }
+
+ /* Put in new parameters
+ Unfortunately we can't blat on top of absMaxConnections
+ as we need to do some resizing here
+ */
+
+ ASSERT (h->params != NULL);
+
+ oldBacklogFilter = h->params->dynBacklogFilter;
+ i = h->params->absMaxConnections; /* keep old value */
+ absMaxCxns = params->absMaxConnections;
+ /* Use this set of params and allocate, and free
+ * up the old
+ */
+ freeHostParams(h->params);
+ h->params = params;
+ h->params->absMaxConnections = i; /* restore old value */
+
+ /* If the backlog filter value has changed, reset the
+ * filter as the value therein will be screwy
+ */
+ if (h->params->dynBacklogFilter != oldBacklogFilter)
+ h->backlogFilter = ((h->params->dynBacklogLowWaterMark
+ + h->params->dynBacklogHighWaterMark)
+ /200.0 /(1.0-h->params->dynBacklogFilter));
+
+ /* We call this anyway - it does nothing if the values
+ * haven't changed. This is because doing things like
+ * just changing "dynamic-method" requires this call
+ * to be made
+ */
+ hostAlterMaxConnections(h, absMaxCxns, h->maxConnections, false);
+
+ for ( i = 0 ; i < MAXCONLIMIT(h->params->absMaxConnections) ; i++ )
+ if (h->connections[i] != NULL)
+ cxnSetCheckThresholds (h->connections[i],
+ h->params->lowPassLow,
+ h->params->lowPassHigh,
+ h->params->lowPassFilter) ;
+
+ /* XXX how to handle initCxns change? */
+}
+
+
+void configHosts (bool talkSelf)
+{
+ Host nHost, h, q ;
+ HostHolder hh, hi ;
+ HostParams params;
+
+ /* Remove the current blocked host list */
+ for (hh = blockedHosts, hi = NULL ; hh != NULL ; hh = hi)
+ {
+ freeHostParams(hh->params);
+ hi = hh->next ;
+ free (hh) ;
+ }
+ blockedHosts = NULL ;
+
+ closeDroppedArticleFile () ;
+ openDroppedArticleFile () ;
+
+ while ((params = getHostInfo ()) !=NULL )
+ {
+ h = findHostByName (params->peerName) ;
+ /* We know the host isn't blocked as we cleared the blocked list */
+ /* Have we already got this host up and running ?*/
+ if ( h != NULL )
+ {
+ hostReconfigure(h, params);
+ h->removeOnReload = false ; /* Don't remove at the end */
+ }
+ else
+ {
+
+ /* It's a host we haven't seen from the config file before */
+ nHost = newHost (mainListener, params);
+
+ if (nHost == NULL)
+ {
+ addBlockedHost(params);
+
+ warn ("ME locked cannot setup peer %s", params->peerName) ;
+ }
+ else
+ {
+ if (params->initialConnections == 0 && talkSelf)
+ notice ("%s config ignored batch mode with initial"
+ " connection count of 0", params->peerName) ;
+
+ if ( !listenerAddPeer (mainListener,nHost) )
+ die ("failed to add a new peer\n") ;
+ }
+ }
+
+ }
+
+
+ for (h = gHostList; h != NULL; h = q)
+ {
+ q = h->next ;
+ if (h->removeOnReload)
+ {
+ if (h->isDynamic)
+ {
+ /* change to the new default parameters */
+ params = newHostParams(defaultParams);
+ ASSERT(params->peerName == NULL);
+ ASSERT(params->ipName == NULL);
+ ASSERT(h->params->peerName != NULL);
+ ASSERT(h->params->ipName != NULL);
+ params->peerName = xstrdup(h->params->peerName);
+ params->ipName = xstrdup(h->params->ipName);
+ hostReconfigure(h, params);
+ h->removeOnReload = true;
+ }
+ else
+ hostClose (h) ; /* h may be deleted in here. */
+ }
+ else
+ /* prime it for the next config file read */
+ h->removeOnReload = true ;
+ }
+
+ hostLogStatus () ;
+}
+
+
+void hostAlterMaxConnections(Host host,
+ unsigned int absMaxCxns, unsigned int maxCxns,
+ bool makeConnect)
+{
+ unsigned int lAbsMaxCxns;
+ unsigned int i;
+
+ /* Fix 0 unlimited case */
+ lAbsMaxCxns = MAXCONLIMIT(absMaxCxns);
+
+ /* Don't accept 0 for maxCxns */
+ maxCxns=MAXCONLIMIT(maxCxns);
+
+ if ( host->params->dynamicMethod == METHOD_STATIC)
+ {
+ /* If running static, ignore the maxCxns passed in, we'll
+ just use absMaxCxns
+ */
+ maxCxns = lAbsMaxCxns;
+ }
+
+ if ( maxCxns > lAbsMaxCxns)
+ {
+ /* ensure maxCxns is of the correct form */
+ maxCxns = lAbsMaxCxns;
+ }
+
+ if ((maxCxns < host->maxConnections) && (host->connections != NULL))
+ {
+ /* We are going to have to nuke some connections, as the current
+ max is now greater than the new max
+ */
+ for ( i = host->maxConnections ; i > maxCxns ; i-- )
+ {
+ /* XXX this is harsh, and arguably there could be a
+ cleaner way of doing it. the problem being addressed
+ by doing it this way is that eventually a connection
+ closed cleanly via cxnClose can end up ultimately
+ calling hostCxnDead after h->maxConnections has
+ been lowered and the relevant arrays downsized.
+ If trashing the old, unallocated space in
+ hostCxnDead doesn't kill the process, the
+ ASSERT against h->maxConnections surely will.
+ */
+ if (host->connections[i - 1] != NULL)
+ {
+ cxnLogStats (host->connections [i-1], true) ;
+ cxnNuke (host->connections[i-1]) ;
+ host->connections[i-1] = NULL;
+ }
+ }
+ host->maxConnections = maxCxns ;
+ }
+
+ if (host->connections)
+ for (i = host->maxConnections ; i <= MAXCONLIMIT(host->params->absMaxConnections) ; i++)
+ {
+ /* Ensure we've got an empty values only beyond the maxConnection
+ water mark.
+ */
+ ASSERT (host->connections[i] == NULL);
+ }
+
+ if ((lAbsMaxCxns != MAXCONLIMIT(host->params->absMaxConnections)) ||
+ (host->connections == NULL))
+ {
+ /* we need to change the size of the connection array */
+ if (host->connections == NULL)
+ {
+ /* not yet allocated */
+
+ host->connections = xcalloc (lAbsMaxCxns + 1, sizeof(Connection)) ;
+
+ ASSERT (host->cxnActive == NULL);
+ host->cxnActive = xcalloc (lAbsMaxCxns, sizeof(bool)) ;
+
+ ASSERT (host->cxnSleeping == NULL) ;
+ host->cxnSleeping = xcalloc (lAbsMaxCxns, sizeof(bool)) ;
+
+ for (i = 0 ; i < lAbsMaxCxns ; i++)
+ {
+ host->connections [i] = NULL ;
+ host->cxnActive[i] = false ;
+ host->cxnSleeping[i] = false ;
+ }
+ host->connections[lAbsMaxCxns] = NULL;
+ }
+ else
+ {
+ host->connections =
+ xrealloc (host->connections,
+ sizeof(Connection) * (lAbsMaxCxns + 1));
+ host->cxnActive = xrealloc (host->cxnActive,
+ sizeof(bool) * lAbsMaxCxns) ;
+ host->cxnSleeping = xrealloc (host->cxnSleeping,
+ sizeof(bool) * lAbsMaxCxns) ;
+
+ if (lAbsMaxCxns > MAXCONLIMIT(host->params->absMaxConnections))
+ {
+ for (i = MAXCONLIMIT(host->params->absMaxConnections) ;
+ i < lAbsMaxCxns ; i++)
+ {
+ host->connections[i+1] = NULL; /* array always 1 larger */
+ host->cxnActive[i] = false ;
+ host->cxnSleeping[i] = false ;
+ }
+ }
+ }
+ host->params->absMaxConnections = absMaxCxns;
+ }
+ /* if maximum was raised, establish the new connexions
+ (but don't start using them).
+ */
+ if ( maxCxns > host->maxConnections)
+ {
+ i = host->maxConnections ;
+ /* need to set host->maxConnections before cxnWait() */
+ host->maxConnections = maxCxns;
+
+ while ( i < maxCxns )
+ {
+ host->cxnActive [i] = false ;
+ host->cxnSleeping [i] = false ;
+ /* create a new connection */
+ host->connections [i] =
+ newConnection (host, i,
+ host->params->ipName,
+ host->params->articleTimeout,
+ host->params->portNum,
+ host->params->responseTimeout,
+ host->params->closePeriod,
+ host->params->lowPassLow,
+ host->params->lowPassHigh,
+ host->params->lowPassFilter) ;
+
+ /* connect if low enough numbered, or we were forced to */
+ if ((i < host->params->initialConnections) || makeConnect)
+ cxnConnect (host->connections [i]) ;
+ else
+ cxnWait (host->connections [i]) ;
+ i++ ;
+ }
+ }
+
+}
+
+/*
+ * Find a host on the blocked host list
+ */
+
+static HostHolder FindBlockedHost(const char *name)
+{
+ HostHolder hh = blockedHosts;
+ while (hh != NULL)
+ if ((hh->params) && (hh->params->peerName) &&
+ (strcmp(name,hh->params->peerName) == 0))
+ return hh;
+ else
+ hh=hh->next;
+ return NULL;
+}
+
+static void addBlockedHost(HostParams params)
+{
+ HostHolder hh;
+
+ hh = xmalloc (sizeof(struct host_holder_s)) ;
+ /* Use this set of params */
+
+ hh->params = params;
+
+ hh->next = blockedHosts ;
+ blockedHosts = hh ;
+}
+
+/*
+ * We iterate through the blocked host list and try and reconnect ones
+ * where we couldn't get a lock
+ */
+static void tryBlockedHosts(TimeoutId tid UNUSED , void *data UNUSED )
+{
+ HostHolder hh,hi;
+ HostParams params;
+
+ hh = blockedHosts; /* Get start of our queue */
+ blockedHosts = NULL ; /* remove them all from the queue of hosts */
+
+ while (hh != NULL)
+ {
+ params = hh->params;
+ hi= hh->next;
+ free(hh);
+ hh = hi;
+
+ if (params && params->peerName)
+ {
+ if (findHostByName(params->peerName)!=NULL)
+ {
+ /* Wierd, someone's managed to start it when it's on
+ * the blocked list. Just silently discard.
+ */
+ freeHostParams(params);
+ }
+ else
+ {
+ Host nHost;
+ nHost = newHost (mainListener, params);
+
+ if (nHost == NULL)
+ {
+ addBlockedHost(params);
+
+ warn ("ME locked cannot setup peer %s", params->peerName) ;
+ }
+ else
+ {
+ d_printf(1,"Unblocked host %s\n",params->peerName);
+
+ if (params->initialConnections == 0 &&
+ listenerIsDummy(mainListener) /*talk to self*/)
+ notice ("%s config ignored batch mode with initial"
+ " connection count of 0", params->peerName) ;
+
+ if ( !listenerAddPeer (mainListener,nHost) )
+ die ("failed to add a new peer\n") ;
+ }
+ }
+ }
+ }
+ tryBlockedHostsId = prepareSleep(tryBlockedHosts,
+ TRYBLOCKEDHOSTPERIOD, NULL);
+}
+
+
+/*
+ * Create a new Host object with default parameters. Called by the
+ * InnListener.
+ */
+
+Host newDefaultHost (InnListener listener,
+ const char *name)
+{
+ HostParams p;
+ Host h = NULL;
+
+ if (FindBlockedHost(name)==NULL)
+ {
+
+ p=newHostParams(defaultParams);
+ ASSERT(p!=NULL);
+
+ /* relies on fact listener and names are null in default*/
+ p->peerName=xstrdup(name);
+ p->ipName=xstrdup(name);
+
+ h=newHost (listener,p);
+ if (h==NULL)
+ {
+ /* Couldn't get a lock - add to list of blocked peers */
+ addBlockedHost(p);
+
+ warn ("ME locked cannot setup peer %s", p->peerName);
+
+ return NULL;
+ }
+
+ h->isDynamic = true;
+ h->removeOnReload = true;
+
+ notice ("ME unconfigured peer %s added", p->peerName) ;
+ }
+ return h;
+}
+
+/*
+ * Create a new host and attach the supplied param structure
+ */
+
+static bool inited = false ;
+Host newHost (InnListener listener, HostParams p)
+{
+ Host nh ;
+
+ ASSERT (p->maxChecks > 0) ;
+
+ if (!inited)
+ {
+ inited = true ;
+ atexit (hostCleanup) ;
+ }
+
+ /*
+ * Once only, init the first blocked host check
+ */
+ if (tryBlockedHostsId==0)
+ tryBlockedHostsId = prepareSleep(tryBlockedHosts,
+ TRYBLOCKEDHOSTPERIOD, NULL);
+
+ nh = xcalloc (1, sizeof(struct host_s)) ;
+
+ nh->params = p;
+ nh->listener = listener;
+
+ nh->connections = NULL; /* We'll get these allocated later */
+ nh->cxnActive = NULL;
+ nh->cxnSleeping = NULL;
+
+ nh->activeCxns = 0 ;
+ nh->sleepingCxns = 0 ;
+
+ nh->blockedCxn = NULL ;
+ nh->notThisCxn = NULL ;
+
+ nh->queued = NULL ;
+ nh->queuedTail = NULL ;
+
+ nh->processed = NULL ;
+ nh->processedTail = NULL ;
+
+ nh->deferred = NULL ;
+ nh->deferredTail = NULL ;
+
+ nh->statsId = 0 ;
+ nh->ChkCxnsId = 0 ;
+ nh->deferredId = 0;
+
+ nh->myTape = newTape (nh->params->peerName,
+ listenerIsDummy (nh->listener)) ;
+ if (nh->myTape == NULL)
+ { /* tape couldn't be locked, probably */
+ free (nh->connections) ;
+ free (nh->cxnActive) ;
+ free (nh->cxnSleeping) ;
+
+ free (nh) ;
+ return NULL ; /* note we don't free up p */
+ }
+
+ nh->backedUp = false ;
+ nh->backlog = 0 ;
+ nh->deferLen = 0 ;
+
+ nh->loggedBacklog = false ;
+ nh->loggedModeOn = false ;
+ nh->loggedModeOff = false ;
+ nh->notifiedChangedRemBlckd = false ;
+ nh->removeOnReload = false ; /* ready for config file reload */
+ nh->isDynamic = false ;
+
+ nh->artsOffered = 0 ;
+ nh->artsAccepted = 0 ;
+ nh->artsNotWanted = 0 ;
+ nh->artsRejected = 0 ;
+ nh->artsDeferred = 0 ;
+ nh->artsMissing = 0 ;
+ nh->artsToTape = 0 ;
+ nh->artsQueueOverflow = 0 ;
+ nh->artsCxnDrop = 0 ;
+ nh->artsHostSleep = 0 ;
+ nh->artsHostClose = 0 ;
+ nh->artsFromTape = 0 ;
+ nh->artsSizeAccepted = 0 ;
+ nh->artsSizeRejected = 0 ;
+
+ nh->artsProcLastPeriod = 0;
+ nh->secsInLastPeriod = 0;
+ nh->lastCheckPoint = 0;
+ nh->lastSentCheckPoint = 0;
+ nh->lastTotalCheckPoint = 0;
+ nh->maxCxnChk = true;
+ nh->lastMaxCxnTime = time(0);
+ nh->lastChkTime = time(0);
+ nh->nextCxnTimeChk = 30;
+ nh->backlogFilter = ((nh->params->dynBacklogLowWaterMark
+ + nh->params->dynBacklogHighWaterMark)
+ /200.0 /(1.0-nh->params->dynBacklogFilter));
+
+ nh->gArtsOffered = 0 ;
+ nh->gArtsAccepted = 0 ;
+ nh->gArtsNotWanted = 0 ;
+ nh->gArtsRejected = 0 ;
+ nh->gArtsDeferred = 0 ;
+ nh->gArtsMissing = 0 ;
+ nh->gArtsToTape = 0 ;
+ nh->gArtsQueueOverflow = 0 ;
+ nh->gArtsCxnDrop = 0 ;
+ nh->gArtsHostSleep = 0 ;
+ nh->gArtsHostClose = 0 ;
+ nh->gArtsFromTape = 0 ;
+ nh->gArtsSizeAccepted = 0 ;
+ nh->gArtsSizeRejected = 0 ;
+ nh->gCxnQueue = 0 ;
+ nh->gNoQueue = 0 ;
+
+ nh->firstConnectTime = 0 ;
+ nh->connectTime = 0 ;
+
+ nh->spoolTime = 0 ;
+
+ nh->blNone = 0 ;
+ nh->blFull = 0 ;
+ nh->blQuartile[0] = nh->blQuartile[1] = nh->blQuartile[2] =
+ nh->blQuartile[3] = 0 ;
+ nh->dlAccum = 0;
+ nh->blAccum = 0;
+ nh->blCount = 0;
+
+
+ nh->maxConnections = 0; /* we currently have no connections allocated */
+
+ /* Note that the following will override the initialCxns specified as
+ maxCxns if we are on non-dyamic feed
+ */
+ hostAlterMaxConnections(nh, nh->params->absMaxConnections,
+ nh->params->initialConnections, false);
+
+ nh->next = gHostList ;
+ gHostList = nh ;
+ gHostCount++ ;
+
+ if (maxIpNameLen == 0)
+ {
+ start = theTime() ;
+ strlcpy (startTime,ctime (&start),sizeof (startTime)) ;
+ myPid = getpid() ;
+ }
+
+ if (strlen (nh->params->ipName) > maxIpNameLen)
+ maxIpNameLen = strlen (nh->params->ipName) ;
+ if (strlen (nh->params->peerName) > maxPeerNameLen)
+ maxPeerNameLen = strlen (nh->params->peerName) ;
+
+ return nh ;
+}
+
+struct sockaddr *hostIpAddr (Host host, int family)
+{
+ int i ;
+ struct sockaddr **newIpAddrPtrs = NULL;
+ struct sockaddr_storage *newIpAddrs = NULL;
+ struct sockaddr *returnAddr;
+
+ ASSERT(host->params != NULL);
+
+ /* check to see if need to look up the host name */
+ if (host->nextIpLookup <= theTime())
+ {
+#ifdef HAVE_INET6
+ int gai_ret;
+ struct addrinfo *res, *p;
+ struct addrinfo hints;
+
+ memset(&hints, 0, sizeof(hints));
+ hints.ai_family = family ? family : AF_UNSPEC;
+ hints.ai_socktype = SOCK_STREAM;
+#ifdef AI_ADDRCONFIG
+ hints.ai_flags = AI_ADDRCONFIG;
+#endif
+ if((gai_ret = getaddrinfo(host->params->ipName, NULL, &hints, &res)) != 0
+ || res == NULL)
+ {
+ warn ("%s can't resolve hostname %s: %s", host->params->peerName,
+ host->params->ipName, gai_ret == 0 ? "no addresses returned"
+ : gai_strerror(gai_ret)) ;
+ }
+ else
+ {
+ /* figure number of pointers that need space */
+ i = 0;
+ for ( p = res ; p ; p = p->ai_next ) ++i;
+
+ newIpAddrPtrs = (struct sockaddr **)
+ xmalloc ( (i + 1) * sizeof(struct sockaddr *) );
+
+ newIpAddrs = (struct sockaddr_storage *)
+ xmalloc ( i * sizeof(struct sockaddr_storage) );
+
+ i = 0;
+ /* copy the addresses from the getaddrinfo linked list */
+ for( p = res ; p ; p = p->ai_next )
+ {
+ memcpy( &newIpAddrs[i], p->ai_addr, p->ai_addrlen );
+ newIpAddrPtrs[i] = (struct sockaddr *)(&newIpAddrs[i]);
+ ++i;
+ }
+ newIpAddrPtrs[i] = NULL ;
+ freeaddrinfo( res );
+ }
+#else
+ struct hostent *hostEnt ;
+ struct in_addr ipAddr;
+
+ /* see if the ipName we're given is a dotted quad */
+ if ( !inet_aton (host->params->ipName,&ipAddr) )
+ {
+ if ((hostEnt = gethostbyname (host->params->ipName)) == NULL)
+ {
+ warn ("%s can't resolve hostname %s: %s", host->params->peerName,
+ host->params->ipName, hstrerror(h_errno)) ;
+ }
+ else
+ {
+ /* figure number of pointers that need space */
+ for (i = 0 ; hostEnt->h_addr_list[i] ; i++)
+ ;
+
+ newIpAddrPtrs = xmalloc ((i + 1) * sizeof(struct sockaddr *));
+ newIpAddrs = xmalloc (i * sizeof(struct sockaddr_storage));
+
+ /* copy the addresses from gethostbyname() static space */
+ i = 0;
+ for (i = 0 ; hostEnt->h_addr_list[i] ; i++)
+ {
+ make_sin( (struct sockaddr_in *)(&newIpAddrs[i]),
+ (struct in_addr *)(hostEnt->h_addr_list[i]) );
+ newIpAddrPtrs[i] = (struct sockaddr *)(&newIpAddrs[i]);
+ }
+ newIpAddrPtrs[i] = NULL ;
+ }
+ }
+ else
+ {
+ newIpAddrPtrs = (struct sockaddr **)
+ xmalloc ( 2 * sizeof( struct sockaddr * ) );
+ newIpAddrs = (struct sockaddr_storage *)
+ xmalloc ( sizeof( struct sockaddr_storage ) );
+
+ make_sin( (struct sockaddr_in *)newIpAddrs, &ipAddr );
+ newIpAddrPtrs[0] = (struct sockaddr *)newIpAddrs;
+ newIpAddrPtrs[1] = NULL;
+ }
+#endif
+
+ if (newIpAddrs)
+ {
+ if (host->ipAddrs)
+ {
+ if(host->ipAddrs[0])
+ free (host->ipAddrs[0]);
+ free (host->ipAddrs) ;
+ }
+ host->ipAddrs = newIpAddrPtrs ;
+ host->nextIpAddr = 0 ;
+ host->nextIpLookup = theTime () + dnsExpPeriod ;
+ }
+ else
+ {
+ /* failed to setup new addresses */
+ host->nextIpLookup = theTime () + dnsRetPeriod ;
+ }
+ }
+
+ if (host->ipAddrs)
+ returnAddr = host->ipAddrs[host->nextIpAddr] ;
+ else
+ returnAddr = NULL ;
+
+ return returnAddr ;
+}
+
+
+#ifdef HAVE_INET6
+/*
+ * Delete IPv4 addresses from the address list.
+ */
+void hostDeleteIpv4Addr (Host host)
+{
+ int i, j;
+
+ if (!host->ipAddrs)
+ return;
+ for (i = 0, j = 0; host->ipAddrs[i]; i++) {
+ if (host->ipAddrs[i]->sa_family != AF_INET)
+ host->ipAddrs[j++] = host->ipAddrs[i];
+ }
+ host->ipAddrs[j] = 0;
+ if (host->nextIpAddr >= j)
+ host->nextIpAddr = 0;
+}
+#endif
+
+
+void hostIpFailed (Host host)
+{
+ if (host->ipAddrs)
+ if (host->ipAddrs[++host->nextIpAddr] == NULL)
+ host->nextIpAddr = 0 ;
+}
+
+
+void gPrintHostInfo (FILE *fp, unsigned int indentAmt)
+{
+ Host h ;
+ char indent [INDENT_BUFFER_SIZE] ;
+ unsigned int i ;
+
+ for (i = 0 ; i < MIN(INDENT_BUFFER_SIZE - 1,indentAmt) ; i++)
+ indent [i] = ' ' ;
+ indent [i] = '\0' ;
+
+ fprintf (fp,"%sGlobal Host list : (count %d) {\n",indent,gHostCount) ;
+
+ for (h = gHostList ; h != NULL ; h = h->next)
+ printHostInfo (h,fp,indentAmt + INDENT_INCR) ;
+
+ fprintf (fp,"%s}\n",indent) ;
+}
+
+
+void printHostInfo (Host host, FILE *fp, unsigned int indentAmt)
+{
+ char indent [INDENT_BUFFER_SIZE] ;
+ unsigned int i ;
+ ProcQElem qe ;
+ double cnt = (host->blCount) ? (host->blCount) : 1.0;
+
+ for (i = 0 ; i < MIN(INDENT_BUFFER_SIZE - 1,indentAmt) ; i++)
+ indent [i] = ' ' ;
+ indent [i] = '\0' ;
+
+ fprintf (fp,"%sHost : %p {\n",indent,(void *) host) ;
+
+ if (host == NULL)
+ {
+ fprintf (fp,"%s}\n",indent) ;
+ return ;
+ }
+
+ fprintf (fp,"%s peer-name : %s\n",indent,host->params->peerName) ;
+ fprintf (fp,"%s ip-name : %s\n",indent,host->params->ipName) ;
+#ifdef HAVE_INET6
+ if (host->params->family == AF_INET6)
+ {
+ fprintf (fp,"%s bindaddress : none\n",indent);
+ }
+ else
+#endif
+ {
+ fprintf (fp,"%s bindaddress : %s\n",indent,
+ host->params->bindAddr == NULL ||
+ host->params->bindAddr->sin_addr.s_addr == 0 ? "any" :
+ inet_ntoa(host->params->bindAddr->sin_addr));
+ }
+#ifdef HAVE_INET6
+ if (host->params->family == AF_INET)
+ {
+ fprintf (fp,"%s bindaddress6 : none\n",indent);
+ }
+ else
+ {
+ char buf[128];
+ fprintf (fp,"%s bindaddress6 : %s\n",indent,
+ host->params->bindAddr6 == NULL ? "any" :
+ inet_ntop(AF_INET6, &host->params->bindAddr6->sin6_addr,
+ buf, sizeof(buf)));
+ }
+#endif
+ fprintf (fp,"%s abs-max-connections : %d\n",indent,
+ host->params->absMaxConnections) ;
+ fprintf (fp,"%s active-connections : %d\n",indent,host->activeCxns) ;
+ fprintf (fp,"%s sleeping-connections : %d\n",indent,host->sleepingCxns) ;
+ fprintf (fp,"%s initial-connections : %d\n",indent,
+ host->params->initialConnections) ;
+ fprintf (fp,"%s want-streaming : %s\n",indent,
+ boolToString (host->params->wantStreaming)) ;
+ fprintf (fp,"%s drop-deferred : %s\n",indent,
+ boolToString (host->params->dropDeferred)) ;
+ fprintf (fp,"%s min-queue-connection : %s\n",indent,
+ boolToString (host->params->minQueueCxn)) ;
+ fprintf (fp,"%s remote-streams : %s\n",indent,
+ boolToString (host->remoteStreams)) ;
+ fprintf (fp,"%s max-checks : %d\n",indent,host->params->maxChecks) ;
+ fprintf (fp,"%s article-timeout : %d\n",indent,
+ host->params->articleTimeout) ;
+ fprintf (fp,"%s response-timeout : %d\n",indent,
+ host->params->responseTimeout) ;
+ fprintf (fp,"%s close-period : %d\n",indent,
+ host->params->closePeriod) ;
+ fprintf (fp,"%s port : %d\n",indent,host->params->portNum) ;
+ fprintf (fp,"%s dynamic-method : %d\n",indent,
+ host->params->dynamicMethod) ;
+ fprintf (fp,"%s dynamic-backlog-filter : %2.1f\n",indent,
+ host->params->dynBacklogFilter) ;
+ fprintf (fp,"%s dynamic-backlog-lwm : %2.1f\n",indent,
+ host->params->dynBacklogLowWaterMark) ;
+ fprintf (fp,"%s dynamic-backlog-hwm : %2.1f\n",indent,
+ host->params->dynBacklogHighWaterMark) ;
+ fprintf (fp,"%s no-check on : %2.1f\n",indent,
+ host->params->lowPassHigh) ;
+ fprintf (fp,"%s no-check off : %2.1f\n",indent,
+ host->params->lowPassLow) ;
+ fprintf (fp,"%s no-check filter : %2.1f\n",indent,
+ host->params->lowPassFilter) ;
+ fprintf (fp,"%s backlog-limit : %d\n",indent,
+ host->params->backlogLimit) ;
+ fprintf (fp,"%s backlog-limit-high : %d\n",indent,
+ host->params->backlogLimitHigh) ;
+ fprintf (fp,"%s backlog-factor : %2.1f\n",indent,
+ host->params->backlogFactor) ;
+ fprintf (fp,"%s max-connections : %d\n",indent,
+ host->maxConnections) ;
+ fprintf (fp,"%s backlog-feed-first : %s\n",indent,
+ boolToString (host->params->backlogFeedFirst)) ;
+
+
+ fprintf (fp,"%s statistics-id : %d\n",indent,host->statsId) ;
+ fprintf (fp,"%s ChkCxns-id : %d\n",indent,host->ChkCxnsId) ;
+ fprintf (fp,"%s deferred-id : %d\n",indent,host->deferredId) ;
+ fprintf (fp,"%s backed-up : %s\n",indent,boolToString (host->backedUp));
+ fprintf (fp,"%s backlog : %d\n",indent,host->backlog) ;
+ fprintf (fp,"%s deferLen : %d\n",indent,host->deferLen) ;
+ fprintf (fp,"%s loggedModeOn : %s\n",indent,
+ boolToString (host->loggedModeOn)) ;
+ fprintf (fp,"%s loggedModeOff : %s\n",indent,
+ boolToString (host->loggedModeOff)) ;
+ fprintf (fp,"%s logged-backlog : %s\n",indent,
+ boolToString (host->loggedBacklog)) ;
+ fprintf (fp,"%s streaming-type changed : %s\n",indent,
+ boolToString (host->notifiedChangedRemBlckd)) ;
+ fprintf (fp,"%s articles offered : %d\n",indent,host->artsOffered) ;
+ fprintf (fp,"%s articles accepted : %d\n",indent,host->artsAccepted) ;
+ fprintf (fp,"%s articles not wanted : %d\n",indent,
+ host->artsNotWanted) ;
+ fprintf (fp,"%s articles rejected : %d\n",indent,host->artsRejected);
+ fprintf (fp,"%s articles deferred : %d\n",indent,host->artsDeferred) ;
+ fprintf (fp,"%s articles missing : %d\n",indent,host->artsMissing) ;
+ fprintf (fp,"%s articles spooled : %d\n",indent,host->artsToTape) ;
+ fprintf (fp,"%s because of queue overflow : %d\n",indent,
+ host->artsQueueOverflow) ;
+ fprintf (fp,"%s when the we closed the host : %d\n",indent,
+ host->artsHostClose) ;
+ fprintf (fp,"%s because the host was asleep : %d\n",indent,
+ host->artsHostSleep) ;
+ fprintf (fp,"%s articles unspooled : %d\n",indent,host->artsFromTape) ;
+ fprintf (fp,"%s articles requeued from dropped connections : %d\n",indent,
+ host->artsCxnDrop) ;
+
+ fprintf (fp,"%s process articles offered : %d\n",indent,
+ host->gArtsOffered) ;
+ fprintf (fp,"%s process articles accepted : %d\n",indent,
+ host->gArtsAccepted) ;
+ fprintf (fp,"%s process articles not wanted : %d\n",indent,
+ host->gArtsNotWanted) ;
+ fprintf (fp,"%s process articles rejected : %d\n",indent,
+ host->gArtsRejected);
+ fprintf (fp,"%s process articles deferred : %d\n",indent,
+ host->gArtsDeferred) ;
+ fprintf (fp,"%s process articles missing : %d\n",indent,
+ host->gArtsMissing) ;
+ fprintf (fp,"%s process articles spooled : %d\n",indent,
+ host->gArtsToTape) ;
+ fprintf (fp,"%s because of queue overflow : %d\n",indent,
+ host->gArtsQueueOverflow) ;
+ fprintf (fp,"%s when the we closed the host : %d\n",indent,
+ host->gArtsHostClose) ;
+ fprintf (fp,"%s because the host was asleep : %d\n",indent,
+ host->gArtsHostSleep) ;
+ fprintf (fp,"%s process articles unspooled : %d\n",indent,
+ host->gArtsFromTape) ;
+ fprintf (fp,"%s process articles requeued from dropped connections : %d\n",
+ indent, host->gArtsCxnDrop) ;
+
+ fprintf (fp,"%s average (mean) defer length : %.1f\n", indent,
+ (double) host->dlAccum / cnt) ;
+ fprintf (fp,"%s average (mean) queue length : %.1f\n", indent,
+ (double) host->blAccum / cnt) ;
+ fprintf (fp,"%s percentage of the time empty : %.1f\n", indent,
+ 100.0 * host->blNone / cnt) ;
+ fprintf (fp,"%s percentage of the time >0%%-25%% : %.1f\n", indent,
+ 100.0 * host->blQuartile[0] / cnt) ;
+ fprintf (fp,"%s percentage of the time 25%%-50%% : %.1f\n", indent,
+ 100.0 * host->blQuartile[1] / cnt) ;
+ fprintf (fp,"%s percentage of the time 50%%-75%% : %.1f\n", indent,
+ 100.0 * host->blQuartile[2] / cnt) ;
+ fprintf (fp,"%s percentage of the time 75%%-<100%% : %.1f\n", indent,
+ 100.0 * host->blQuartile[3] / cnt) ;
+ fprintf (fp,"%s percentage of the time full : %.1f\n", indent,
+ 100.0 * host->blFull / cnt) ;
+ fprintf (fp,"%s number of samples : %u\n", indent, host->blCount) ;
+
+ fprintf (fp,"%s firstConnectTime : %s",indent,
+ ctime (&host->firstConnectTime));
+ fprintf (fp,"%s connectTime : %s",indent,ctime (&host->connectTime));
+ fprintf (fp,"%s spoolTime : %s",indent,ctime (&host->spoolTime)) ;
+ fprintf (fp,"%s last-spool-time : %s",indent,
+ ctime (&host->lastSpoolTime)) ;
+
+#if 0
+ fprintf (fp,"%s tape {\n",indent) ;
+ printTapeInfo (host->myTape,fp,indentAmt + INDENT_INCR) ;
+ fprintf (fp,"%s }\n",indent) ;
+#else
+ fprintf (fp,"%s tape : %p\n",indent,(void *) host->myTape) ;
+#endif
+
+ fprintf (fp,"%s QUEUED articles {\n",indent) ;
+ for (qe = host->queued ; qe != NULL ; qe = qe->next)
+ {
+#if 0
+ printArticleInfo (qe->article,fp,indentAmt + INDENT_INCR) ;
+#else
+ fprintf (fp,"%s %p\n",indent,(void *) qe->article) ;
+#endif
+ }
+
+ fprintf (fp,"%s }\n",indent) ;
+
+ fprintf (fp,"%s IN PROCESS articles {\n",indent) ;
+ for (qe = host->processed ; qe != NULL ; qe = qe->next)
+ {
+#if 0
+ printArticleInfo (qe->article,fp,indentAmt + INDENT_INCR) ;
+#else
+ fprintf (fp,"%s %p\n",indent,(void *) qe->article) ;
+#endif
+ }
+
+ fprintf (fp,"%s }\n",indent) ;
+ fprintf (fp,"%s DEFERRED articles {\n",indent) ;
+ for (qe = host->deferred ; qe != NULL ; qe = qe->next)
+ {
+#if 0
+ printArticleInfo (qe->article,fp,indentAmt + INDENT_INCR) ;
+#else
+ fprintf (fp,"%s %p\n",indent,(void *) qe->article) ;
+#endif
+ }
+
+ fprintf (fp,"%s }\n",indent) ;
+ fprintf (fp,"%s DEFERRED articles {\n",indent) ;
+ for (qe = host->deferred ; qe != NULL ; qe = qe->next)
+ {
+#if 0
+ printArticleInfo (qe->article,fp,indentAmt + INDENT_INCR) ;
+#else
+ fprintf (fp,"%s %p\n",indent,(void *) qe->article) ;
+#endif
+ }
+
+ fprintf (fp,"%s }\n",indent) ;
+
+
+
+ fprintf (fp,"%s Connections {\n",indent) ;
+ for (i = 0 ; i < host->maxConnections ; i++)
+ {
+#if 0
+ if (host->connections[i] != NULL)
+ printCxnInfo (*cxn,fp,indentAmt + INDENT_INCR) ;
+#else
+ fprintf (fp,"%s %p\n",indent,(void *) host->connections[i]) ;
+#endif
+ }
+ fprintf (fp,"%s }\n",indent) ;
+
+ fprintf (fp,"%s Active Connections {\n%s ",indent,indent) ;
+ for (i = 0 ; i < host->maxConnections ; i++)
+ if (host->cxnActive[i])
+ fprintf (fp," [%d:%p]",i,(void *) host->connections[i]) ;
+ fprintf (fp,"\n%s }\n",indent) ;
+
+ fprintf (fp,"%s Sleeping Connections {\n%s ",indent,indent) ;
+ for (i = 0 ; i < host->maxConnections ; i++)
+ if (host->cxnSleeping[i])
+ fprintf (fp," [%d:%p]",i,(void *) host->connections[i]) ;
+ fprintf (fp,"\n%s }\n",indent) ;
+
+ fprintf (fp,"%s}\n",indent) ;
+}
+
+
+
+
+
+
+\f
+/* close down all the connections of the Host. All articles that are in
+ * processes are still pushed out and then a QUIT is issued. The Host will
+ * also spool all inprocess articles to tape incase the process is about to
+ * be killed (they'll be refused next time around). When all Connections
+ * report that they're gone, then the Host will delete itself.
+ */
+void hostClose (Host host)
+{
+ unsigned int i ;
+ unsigned int cxnCount ;
+
+ d_printf (1,"Closing host %s\n",host->params->peerName) ;
+
+ queuesToTape (host) ;
+ delTape (host->myTape) ;
+ host->myTape = NULL ;
+
+ hostLogStats (host,true) ;
+
+ clearTimer (host->statsId) ;
+ clearTimer (host->ChkCxnsId) ;
+ clearTimer (host->deferredId) ;
+
+ host->connectTime = 0 ;
+
+ /* when we call cxnTerminate() on the last Connection, the Host objects
+ will end up getting deleted out from under us (via hostCxnGone()). If
+ we are running with a malloc that scribbles over memory after freeing
+ it, then we'd fail in the second for loop test. Trying to access
+ host->maxConnections. */
+ for (i = 0, cxnCount = 0 ; i < host->maxConnections ; i++)
+ cxnCount += (host->connections [i] != NULL ? 1 : 0) ;
+ for (i = 0 ; i < cxnCount ; i++)
+ if (host->connections[i] != NULL)
+ cxnTerminate (host->connections [i]) ;
+}
+
+\f
+/*
+ * check if host should get more connections opened, or some closed...
+ */
+void hostChkCxns(TimeoutId tid UNUSED, void *data) {
+ Host host = (Host) data;
+ unsigned int currArticles, currSentArticles, currTotalArticles, newMaxCxns ;
+ double lastAPS, currAPS, percentTaken, ratio ;
+ double backlogRatio, backlogMult;
+
+ if(!host->maxCxnChk)
+ return;
+
+ ASSERT(host->params != NULL);
+
+ if(host->secsInLastPeriod > 0)
+ lastAPS = host->artsProcLastPeriod / (host->secsInLastPeriod * 1.0);
+ else
+ lastAPS = host->artsProcLastPeriod * 1.0;
+
+ newMaxCxns = host->maxConnections;
+
+ currArticles = (host->gArtsAccepted + host->gArtsRejected +
+ (host->gArtsNotWanted / 4)) - host->lastCheckPoint ;
+
+ host->lastCheckPoint = (host->gArtsAccepted + host->gArtsRejected +
+ (host->gArtsNotWanted / 4));
+
+ currSentArticles = host->gArtsAccepted + host->gArtsRejected
+ - host->lastSentCheckPoint ;
+
+ host->lastSentCheckPoint = host->gArtsAccepted + host->gArtsRejected;
+
+ currTotalArticles = host->gArtsAccepted + host->gArtsRejected
+ + host->gArtsRejected + host->gArtsQueueOverflow
+ - host->lastTotalCheckPoint ;
+
+ host->lastTotalCheckPoint = host->gArtsAccepted + host->gArtsRejected
+ + host->gArtsRejected + host->gArtsQueueOverflow ;
+
+ currAPS = currArticles / (host->nextCxnTimeChk * 1.0) ;
+
+ percentTaken = currSentArticles * 1.0 /
+ ((currTotalArticles==0)?1:currTotalArticles);
+
+ /* Get how full the queue is currently */
+ backlogRatio = (host->backlog * 1.0 / hostHighwater);
+ backlogMult = 1.0/(1.0-host->params->dynBacklogFilter);
+
+ d_printf(1,"%s hostChkCxns - entry filter=%3.3f blmult=%3.3f blratio=%3.3f\n",host->params->peerName,host->backlogFilter, backlogMult, backlogRatio);
+
+ ratio = 0.0; /* ignore APS by default */
+
+ switch (host->params->dynamicMethod)
+ {
+ case METHOD_COMBINED:
+ /* When a high % of articles is being taken, take notice of the
+ * APS values. However for smaller %s, quickly start to ignore this
+ * and concentrate on queue sizes
+ */
+ ratio = percentTaken * percentTaken;
+ /* nobreak; */
+ case METHOD_QUEUE:
+ /* backlogFilter is an IIR filtered version of the backlogRatio.
+ */
+ host->backlogFilter *= host->params->dynBacklogFilter;
+ /* Penalise anything over the backlog HWM twice as severely
+ * (otherwise we end up feeding some sites constantly
+ * just below the HWM. This way random noise makes
+ * such sites jump to one more connection
+ *
+ * Use factor (1-ratio) so if ratio is near 1 we ignore this
+ */
+ if (backlogRatio>host->params->dynBacklogLowWaterMark/100.0)
+ host->backlogFilter += (backlogRatio+1.0)/2.0 * (1.0-ratio);
+ else
+ host->backlogFilter += backlogRatio * (1.0-ratio);
+
+ /*
+ * Now bump it around for APS too
+ */
+ if ((currAPS - lastAPS) >= 0.1)
+ host->backlogFilter += ratio*((currAPS - lastAPS) + 1.0);
+ else if ((currAPS - lastAPS) < -.2)
+ host->backlogFilter -= ratio;
+
+ d_printf(1,"%s hostChkCxns - entry hwm=%3.3f lwm=%3.3f new=%3.3f [%3.3f,%3.3f]\n",
+ host->params->peerName,host->params->dynBacklogHighWaterMark,
+ host->params->dynBacklogLowWaterMark,host->backlogFilter,
+ (host->params->dynBacklogLowWaterMark * backlogMult / 100.0),
+ (host->params->dynBacklogHighWaterMark * backlogMult / 100.0));
+
+ if (host->backlogFilter <
+ (host->params->dynBacklogLowWaterMark * backlogMult / 100.0))
+ newMaxCxns--;
+ else if (host->backlogFilter >
+ (host->params->dynBacklogHighWaterMark * backlogMult / 100.0))
+ newMaxCxns++;
+ break;
+ case METHOD_STATIC:
+ /* well not much to do, just check maxConnection = absMaxConnections */
+ ASSERT (host->maxConnections == MAXCONLIMIT(host->params->absMaxConnections));
+ break;
+ case METHOD_APS:
+ if ((currAPS - lastAPS) >= 0.1)
+ newMaxCxns += (int)(currAPS - lastAPS) + 1 ;
+ else if ((currAPS - lastAPS) < -.2)
+ newMaxCxns--;
+ break;
+ }
+
+ d_printf(1, "hostChkCxns: Chngs %f\n", currAPS - lastAPS);
+
+ if (newMaxCxns < 1) newMaxCxns=1;
+ if (newMaxCxns > MAXCONLIMIT(host->params->absMaxConnections))
+ newMaxCxns = MAXCONLIMIT(host->params->absMaxConnections);
+
+ if (newMaxCxns != host->maxConnections)
+ {
+ notice ("%s hostChkCxns - maxConnections was %d now %d",
+ host->params->peerName, host->maxConnections,newMaxCxns);
+
+ host->backlogFilter= ((host->params->dynBacklogLowWaterMark
+ + host->params->dynBacklogHighWaterMark)
+ /200.0 * backlogMult);
+ host->artsProcLastPeriod = currArticles ;
+ host->secsInLastPeriod = host->nextCxnTimeChk ;
+
+ /* Alter MaxConnections and in doing so ensure we connect new
+ cxns immediately if we are adding stuff
+ */
+ hostAlterMaxConnections(host, host->params->absMaxConnections,
+ newMaxCxns, true);
+ }
+
+ if(host->nextCxnTimeChk <= 240) host->nextCxnTimeChk *= 2;
+ else host->nextCxnTimeChk = 300;
+ d_printf(1, "prepareSleep hostChkCxns, %d\n", host->nextCxnTimeChk);
+ host->ChkCxnsId = prepareSleep(hostChkCxns, host->nextCxnTimeChk, host);
+}
+
+\f
+/*
+ * have the Host transmit the Article if possible.
+ */
+void hostSendArticle (Host host, Article article)
+{
+ ASSERT(host->params != NULL);
+ if (host->spoolTime > 0)
+ { /* all connections are asleep */
+ host->artsHostSleep++ ;
+ host->gArtsHostSleep++ ;
+ host->artsToTape++ ;
+ host->gArtsToTape++ ;
+ procArtsToTape++ ;
+ tapeTakeArticle (host->myTape, article) ;
+ return ;
+ }
+
+ /* at least one connection is feeding or waiting and there's no backlog */
+ if (host->queued == NULL)
+ {
+ unsigned int idx ;
+ Article extraRef ;
+ Connection cxn = NULL ;
+
+ extraRef = artTakeRef (article) ; /* the referrence we give away */
+
+ /* stick on the queue of articles we've handed off--we're hopeful. */
+ queueArticle (article,&host->processed,&host->processedTail, 0) ;
+
+ if (host->params->minQueueCxn) {
+ Connection x_cxn = NULL ;
+ unsigned int x_queue = host->params->maxChecks + 1 ;
+
+ for (idx = 0 ; x_queue > 0 && idx < host->maxConnections ; idx++)
+ if ((cxn = host->connections[idx]) != host->notThisCxn) {
+ if (!host->cxnActive [idx]) {
+ if (!host->cxnSleeping [idx]) {
+ if (cxnTakeArticle (cxn, extraRef)) {
+ host->gNoQueue++ ;
+ return ;
+ } else
+ d_printf (1,"%s Inactive connection %d refused an article\n",
+ host->params->peerName,idx) ;
+ }
+ } else {
+ unsigned int queue = host->params->maxChecks - cxnQueueSpace (cxn) ;
+ if (queue < x_queue) {
+ x_queue = queue ;
+ x_cxn = cxn ;
+ }
+ }
+ }
+
+ if (x_cxn != NULL && cxnTakeArticle (x_cxn, extraRef)) {
+ if (x_queue == 0) host->gNoQueue++ ;
+ else host->gCxnQueue += x_queue ;
+ return ;
+ }
+
+ } else {
+
+ /* first we try to give it to one of our active connections. We
+ simply start at the bottom and work our way up. This way
+ connections near the end of the list will get closed sooner from
+ idleness. */
+ for (idx = 0 ; idx < host->maxConnections ; idx++)
+ {
+ if (host->cxnActive [idx] &&
+ (cxn = host->connections[idx]) != host->notThisCxn &&
+ cxnTakeArticle (cxn, extraRef)) {
+ unsigned int queue = host->params->maxChecks - cxnQueueSpace (cxn) - 1;
+ if (queue == 0) host->gNoQueue++ ;
+ else host->gCxnQueue += queue ;
+ return ;
+ }
+ }
+
+ /* Wasn't taken so try to give it to one of the waiting connections. */
+ for (idx = 0 ; idx < host->maxConnections ; idx++)
+ if (!host->cxnActive [idx] && !host->cxnSleeping [idx] &&
+ (cxn = host->connections[idx]) != host->notThisCxn)
+ {
+ if (cxnTakeArticle (cxn, extraRef)) {
+ unsigned int queue = host->params->maxChecks - cxnQueueSpace (cxn) - 1;
+ if (queue == 0) host->gNoQueue++ ;
+ else host->gCxnQueue += queue ;
+ return ;
+ } else
+ d_printf (1,"%s Inactive connection %d refused an article\n",
+ host->params->peerName,idx) ;
+ }
+ }
+
+ /* this'll happen if all connections are feeding and all
+ their queues are full, or if those not feeding are asleep. */
+ d_printf (1, "Couldn't give the article to a connection\n") ;
+
+ delArticle (extraRef) ;
+
+ remArticle (article,&host->processed,&host->processedTail) ;
+ if (!cxnCheckstate (cxn))
+ {
+ host->artsToTape++ ;
+ host->gArtsToTape++ ;
+ procArtsToTape++ ;
+ tapeTakeArticle (host->myTape,article) ;
+ return ;
+ }
+ }
+
+ /* either all the per connection queues were full or we already had
+ a backlog, so there was no sense in checking. */
+ queueArticle (article,&host->queued,&host->queuedTail, 0) ;
+
+ host->backlog++ ;
+ backlogToTape (host) ;
+}
+
+
+
+
+
+
+\f
+/*
+ * called by the Host's connection when the remote is refusing postings
+ * from us becasue we're not allowed (banner code 400).
+ */
+void hostCxnBlocked (Host host, Connection cxn, char *reason)
+{
+ ASSERT(host->params != NULL);
+#ifndef NDEBUG
+ {
+ unsigned int i ;
+
+ for (i = 0 ; i < host->maxConnections ; i++)
+ if (host->connections [i] == cxn)
+ ASSERT (host->cxnActive [i] == false) ;
+ }
+#endif
+
+ if (host->blockedReason == NULL)
+ host->blockedReason = xstrdup (reason) ;
+
+ if (host->activeCxns == 0 && host->spoolTime == 0)
+ {
+ host->blockedCxn = cxn ; /* to limit log notices */
+ notice ("%s remote cannot accept articles initial: %s",
+ host->params->peerName, reason) ;
+ }
+ else if (host->activeCxns > 0 && !host->notifiedChangedRemBlckd)
+ {
+ notice ("%s remote cannot accept articles change: %s",
+ host->params->peerName, reason) ;
+ host->notifiedChangedRemBlckd = true ;
+ }
+ else if (host->spoolTime != 0 && host->blockedCxn == cxn)
+ {
+ notice ("%s remote cannot accept articles still: %s",
+ host->params->peerName, reason) ;
+ }
+
+}
+
+
+
+
+
+
+\f
+/*
+ * Called by the Connection when it gets a response back to the MODE
+ * STREAM command. It's now that we consider the connection usable.
+ */
+void hostRemoteStreams (Host host, Connection cxn, bool doesStreaming)
+{
+ unsigned int i ;
+
+ host->blockedCxn = NULL ;
+ if (host->blockedReason != NULL)
+ free (host->blockedReason) ;
+ host->blockedReason = NULL ;
+
+ /* we may have told the connection to quit while it was in the middle
+ of connecting */
+ if (amClosing (host))
+ return ;
+
+ if (host->connectTime == 0) /* first connection for this cycle. */
+ {
+ if (doesStreaming && host->params->wantStreaming)
+ notice ("%s remote MODE STREAM", host->params->peerName) ;
+ else if (doesStreaming)
+ notice ("%s remote MODE STREAM disabled", host->params->peerName) ;
+ else
+ notice ("%s remote MODE STREAM failed", host->params->peerName) ;
+
+ if (host->spoolTime > 0)
+ hostStopSpooling (host) ;
+
+ /* set up the callback for statistics logging. */
+ if (host->statsId != 0)
+ clearTimer (host->statsId) ;
+ host->statsId = prepareSleep (hostStatsTimeoutCbk, statsPeriod, host) ;
+
+ if (host->ChkCxnsId != 0)
+ clearTimer (host->ChkCxnsId);
+ host->ChkCxnsId = prepareSleep (hostChkCxns, 30, host) ;
+
+ host->remoteStreams = (host->params->wantStreaming ? doesStreaming : false) ;
+
+ host->connectTime = theTime() ;
+ if (host->firstConnectTime == 0)
+ host->firstConnectTime = host->connectTime ;
+ }
+ else if (host->remoteStreams != doesStreaming && host->params->wantStreaming)
+ notice ("%s remote MODE STREAM change", host->params->peerName) ;
+
+ for (i = 0 ; i < host->maxConnections ; i++)
+ if (host->connections [i] == cxn)
+ {
+ host->cxnActive [i] = true ;
+ if (host->cxnSleeping [i])
+ host->sleepingCxns-- ;
+ host->cxnSleeping [i] = false ;
+ break ;
+ }
+
+ ASSERT (i != host->maxConnections) ;
+
+ host->activeCxns++ ;
+
+ hostLogStatus () ;
+}
+
+
+
+
+
+
+\f
+/*
+ * Called by the connection when it is no longer connected to the
+ * remote. Perhaps due to getting a code 400 to an IHAVE, or due to a
+ * periodic close.
+ */
+void hostCxnDead (Host host, Connection cxn)
+{
+ unsigned int i ;
+
+ for (i = 0 ; i < host->maxConnections ; i++)
+ if (host->connections [i] == cxn)
+ {
+ if (host->cxnActive [i]) /* won't be active if got 400 on banner */
+ {
+ host->cxnActive [i] = false ;
+ host->activeCxns-- ;
+
+ if (!amClosing (host) && host->activeCxns == 0)
+ {
+ clearTimer (host->statsId) ;
+ clearTimer (host->ChkCxnsId) ;
+ hostLogStats (host,true) ;
+ host->connectTime = 0 ;
+ }
+ }
+ else if (host->cxnSleeping [i]) /* cxnNuke can be called on sleepers */
+ {
+ host->cxnSleeping [i] = false ;
+ host->sleepingCxns-- ;
+ }
+
+ break ;
+ }
+
+ ASSERT (i < host->maxConnections) ;
+ hostLogStatus () ;
+}
+
+
+
+
+
+
+\f
+/*
+ * Called by the Connection when it is going to sleep so the Host won't
+ * bother trying to give it Articles
+ */
+void hostCxnSleeping (Host host, Connection cxn)
+{
+ unsigned int i ;
+
+ for (i = 0 ; i < host->maxConnections ; i++)
+ if (host->connections [i] == cxn)
+ {
+ if (!host->cxnSleeping [i])
+ {
+ host->cxnSleeping [i] = true ;
+ host->sleepingCxns++ ;
+ }
+
+ if (host->spoolTime == 0 && host->sleepingCxns >= host->maxConnections)
+ hostStartSpooling (host) ;
+
+ break ;
+ }
+
+ ASSERT (i < host->maxConnections) ;
+
+ hostLogStatus () ;
+}
+
+
+
+
+
+
+\f
+/*
+ * Called by the Connection when it goes into the waiting state.
+ */
+void hostCxnWaiting (Host host, Connection cxn)
+{
+ unsigned int i ;
+
+ for (i = 0 ; i < host->maxConnections ; i++)
+ if (host->connections [i] == cxn)
+ {
+ if (host->cxnSleeping [i])
+ host->sleepingCxns-- ;
+ host->cxnSleeping [i] = false ;
+ break ;
+ }
+
+ ASSERT (i < host->maxConnections) ;
+
+ if (host->spoolTime > 0)
+ hostStopSpooling (host) ;
+
+ hostLogStatus () ;
+}
+
+
+
+
+
+
+\f
+/*
+ * Called by the Connection when it is about to delete itself.
+ */
+bool hostCxnGone (Host host, Connection cxn)
+{
+ unsigned int i;
+ bool oneThere = false ;
+ char msgstr[SMBUF] ;
+
+ /* forget about the Connection and see if we are still holding any live
+ connections still. */
+ for (i = 0 ; i < host->maxConnections ; i++)
+ if (host->connections [i] == cxn)
+ {
+ if (!amClosing (host))
+ {
+ warn ("%s:%d connection vanishing", host->params->peerName, i) ;
+ }
+ host->connections [i] = NULL ;
+ if (host->cxnActive [i])
+ {
+ host->cxnActive [i] = false ;
+ host->activeCxns-- ;
+ }
+ else if (host->cxnSleeping [i])
+ {
+ host->cxnSleeping [i] = false ;
+ host->sleepingCxns-- ;
+ }
+ }
+ else if (host->connections [i] != NULL)
+ oneThere = true ;
+
+ /* remove the host if it has no connexions */
+ if ( !oneThere )
+ {
+ time_t now = theTime() ;
+ unsigned int hostsLeft ;
+
+ if (host->firstConnectTime > 0) {
+ snprintf(msgstr, sizeof(msgstr), "accsize %.0f rejsize %.0f",
+ host->gArtsSizeAccepted, host->gArtsSizeRejected);
+ notice ("%s global seconds %ld offered %d accepted %d refused %d"
+ " rejected %d missing %d %s spooled %d unspooled %d",
+ host->params->peerName, (long) (now - host->firstConnectTime),
+ host->gArtsOffered, host->gArtsAccepted,
+ host->gArtsNotWanted, host->gArtsRejected,
+ host->gArtsMissing, msgstr,
+ host->gArtsToTape, host->gArtsFromTape) ;
+ }
+
+ hostsLeft = listenerHostGone (host->listener, host) ;
+ delHost (host) ;
+
+ if (hostsLeft == 0) {
+ snprintf(msgstr, sizeof(msgstr), "accsize %.0f rejsize %.0f",
+ procArtsSizeAccepted, procArtsSizeRejected);
+ notice ("ME global seconds %ld offered %ld accepted %ld refused %ld"
+ " rejected %ld missing %ld %s spooled %ld unspooled %ld",
+ (long) (now - start),
+ procArtsOffered, procArtsAccepted,
+ procArtsNotWanted,procArtsRejected,
+ procArtsMissing, msgstr,
+ procArtsToTape, procArtsFromTape) ;
+ }
+
+ /* return true if that was the last host */
+ return (hostsLeft == 0 ? true : false) ;
+ }
+
+ /* return false because there is still at least one host (this one) */
+ return false ;
+}
+
+
+
+
+
+
+\f
+/*
+ * The connections has offered an article to the remote.
+ */
+void hostArticleOffered (Host host, Connection cxn UNUSED)
+{
+ host->artsOffered++ ;
+ host->gArtsOffered++ ;
+ procArtsOffered++ ;
+}
+
+
+
+
+
+
+\f
+/*
+ * Article was succesfully transferred.
+ */
+void hostArticleAccepted (Host host, Connection cxn, Article article)
+{
+ const char *filename = artFileName (article) ;
+ const char *msgid = artMsgId (article) ;
+ double len = artSize (article);
+
+ d_printf (5,"Article %s (%s) was transferred\n", msgid, filename) ;
+
+ host->artsAccepted++ ;
+ host->gArtsAccepted++ ;
+ procArtsAccepted++ ;
+ host->artsSizeAccepted += len ;
+ host->gArtsSizeAccepted += len ;
+ procArtsSizeAccepted += len ;
+
+ /* host has two references to the article here... the parameter `article'
+ and the queue */
+
+ delArticle (article) ; /* drop the parameter reference */
+
+ if (!amClosing (host))
+ articleGone (host,cxn,article) ; /* and the one in the queue */
+}
+
+
+
+
+
+
+\f
+/*
+ * remote said no thanks to an article.
+ */
+void hostArticleNotWanted (Host host, Connection cxn, Article article)
+{
+ const char *filename = artFileName (article) ;
+ const char *msgid = artMsgId (article) ;
+
+ d_printf (5,"Article %s (%s) was not wanted\n", msgid, filename) ;
+
+ host->artsNotWanted++ ;
+ host->gArtsNotWanted++ ;
+ procArtsNotWanted++ ;
+
+
+ /* host has two references to the article here... `article' and the
+ queue */
+
+ delArticle (article) ; /* drop the `article' reference */
+
+ if (!amClosing (host))
+ articleGone (host,cxn,article) ; /* and the one in the queue */
+}
+
+
+
+
+
+
+\f
+/*
+ * remote rejected the article after it was was transferred
+ */
+void hostArticleRejected (Host host, Connection cxn, Article article)
+{
+ const char *filename = artFileName (article) ;
+ const char *msgid = artMsgId (article) ;
+ double len = artSize (article);
+
+ d_printf (5,"Article %s (%s) was rejected\n", msgid, filename) ;
+
+ host->artsRejected++ ;
+ host->gArtsRejected++ ;
+ procArtsRejected++ ;
+ host->artsSizeRejected += len ;
+ host->gArtsSizeRejected += len ;
+ procArtsSizeRejected += len ;
+
+ /* host has two references to the article here... `article' and the queue */
+
+ delArticle (article) ; /* drop the `article' reference */
+
+ if (!amClosing (host))
+ articleGone (host,cxn,article) ;
+}
+
+
+
+
+
+
+\f
+/*
+ * The remote wants us to retry the article later.
+ */
+void hostArticleDeferred (Host host, Connection cxn, Article article)
+{
+ host->artsDeferred++ ;
+ host->gArtsDeferred++ ;
+ procArtsDeferred++ ;
+
+
+ if (!amClosing (host))
+ {
+ Article extraRef ;
+ int deferTimeout = 5 ; /* XXX - should be tunable */
+ time_t now = theTime() ;
+
+ extraRef = artTakeRef (article) ; /* hold a reference until requeued */
+ articleGone (host,cxn,article) ; /* drop from the queue */
+
+ if (host->deferred == NULL)
+ {
+ if (host->deferredId != 0)
+ clearTimer (host->deferredId) ;
+ host->deferredId = prepareSleep (hostDeferredArtCbk, deferTimeout,
+ host) ;
+ }
+
+ queueArticle (article,&host->deferred,&host->deferredTail,
+ now + deferTimeout) ;
+ host->deferLen++ ;
+ backlogToTape (host) ;
+ delArticle (extraRef) ;
+ }
+ else
+ delArticle(article); /*drop parameter reference if not sent to tape*/
+}
+
+
+
+
+
+
+\f
+/*
+ * The Connection is giving the article back to the Host, but it doesn't
+ * want a new one in return.
+ */
+void hostTakeBackArticle (Host host, Connection cxn UNUSED, Article article)
+{
+ if (!amClosing (host))
+ {
+ Article extraRef ;
+
+ host->artsCxnDrop++ ;
+ host->gArtsCxnDrop++ ;
+ extraRef = artTakeRef (article) ; /* hold a reference until requeued */
+ articleGone (host,NULL,article) ; /* drop from the queue */
+ host->notThisCxn = cxn;
+ hostSendArticle (host, article) ; /* requeue it */
+ host->notThisCxn = NULL;
+ delArticle (extraRef) ;
+ }
+ else
+ delArticle(article); /*drop parameter reference if not sent to tape*/
+
+}
+
+
+
+
+
+
+\f
+/*
+ * The disk file for the article is no longer valid
+ */
+void hostArticleIsMissing (Host host, Connection cxn, Article article)
+{
+ const char *filename = artFileName (article) ;
+ const char *msgid = artMsgId (article) ;
+
+ d_printf (5, "%s article is missing %s %s\n", host->params->peerName, msgid, filename) ;
+
+ host->artsMissing++ ;
+ host->gArtsMissing++ ;
+ procArtsMissing++ ;
+
+ /* host has two references to the article here... `article' and the
+ queue */
+
+ delArticle (article) ; /* drop the `article' reference */
+
+ if (!amClosing (host))
+ articleGone (host,cxn,article) ; /* and the one in the queue */
+}
+
+
+
+
+
+
+\f
+/* The Connection wants something to do. This is called by the Connection
+ * after it has transferred an article. This is what keeps the pipes full
+ * of data off the tapes if the input from inn is idle.
+ */
+bool hostGimmeArticle (Host host, Connection cxn)
+{
+ Article article = NULL ;
+ bool gaveSomething = false ;
+ size_t amtToGive = cxnQueueSpace (cxn) ; /* may be more than one */
+ int feed = 0 ;
+
+ if (amClosing (host))
+ {
+ d_printf (5,"%s no article to give due to closing\n",host->params->peerName) ;
+
+ return false ;
+ }
+
+ if (amtToGive == 0)
+ d_printf (5,"%s Queue space is zero....\n",host->params->peerName) ;
+
+ while (amtToGive > 0)
+ {
+ bool tookIt ;
+ unsigned int queue = host->params->maxChecks - amtToGive ;
+
+ if (host->params->backlogFeedFirst) {
+ if ((article = getArticle (host->myTape)) != NULL)
+ feed = 2;
+ else if ((article = remHead (&host->queued,&host->queuedTail)) != NULL)
+ feed = 1;
+ else
+ feed = 3;
+ }
+ else {
+ if ((article = remHead (&host->queued,&host->queuedTail)) != NULL)
+ feed = 1;
+ else if ((article = getArticle (host->myTape)) != NULL)
+ feed = 2;
+ else
+ feed = 3;
+ }
+
+ switch (feed) {
+ case 1:
+ host->backlog-- ;
+ tookIt = cxnQueueArticle (cxn,artTakeRef (article)) ;
+
+ ASSERT (tookIt == true) ;
+
+ if (queue == 0) host->gNoQueue++ ;
+ else host->gCxnQueue += queue ;
+
+ queueArticle (article,&host->processed,&host->processedTail, 0) ;
+ amtToGive-- ;
+
+ gaveSomething = true ;
+ break ;
+
+ case 2:
+ /* go to the tapes */
+ tookIt = cxnQueueArticle (cxn,artTakeRef (article)) ;
+
+ ASSERT (tookIt == true) ;
+
+ if (queue == 0) host->gNoQueue++ ;
+ else host->gCxnQueue += queue ;
+
+ host->artsFromTape++ ;
+ host->gArtsFromTape++ ;
+ procArtsFromTape++ ;
+ queueArticle (article,&host->processed,&host->processedTail, 0) ;
+ amtToGive-- ;
+
+ gaveSomething = true ;
+
+ break ;
+
+ case 3:
+ /* we had nothing left to give... */
+
+ if (host->processed == NULL) /* and if nothing outstanding... */
+ listenerHostIsIdle (host->listener,host) ; /* tell our owner */
+
+ amtToGive = 0 ;
+
+ break ;
+ }
+ }
+
+ return gaveSomething ;
+}
+
+
+
+
+
+
+\f
+/*
+ * get the name that INN uses for this host
+ */
+const char *hostPeerName (Host host)
+{
+ ASSERT (host != NULL) ;
+
+ return host->params->peerName ;
+}
+
+/*
+ * get the IPv4 bindaddress
+ */
+const struct sockaddr_in *hostBindAddr (Host host)
+{
+ ASSERT (host != NULL) ;
+
+ return host->params->bindAddr ;
+}
+
+#ifdef HAVE_INET6
+/*
+ * get the IPv6 bindaddress
+ */
+const struct sockaddr_in6 *hostBindAddr6 (Host host)
+{
+ ASSERT (host != NULL) ;
+
+ return host->params->bindAddr6 ;
+}
+
+/*
+ * get the address family
+ */
+int hostAddrFamily (Host host)
+{
+ ASSERT (host != NULL) ;
+
+ return host->params->family ;
+}
+#endif
+
+/*
+ * get the username and password for authentication
+ */
+const char *hostUsername (Host host)
+{
+ ASSERT (host != NULL) ;
+
+ return host->params->username ;
+}
+const char *hostPassword (Host host)
+{
+ ASSERT (host != NULL) ;
+
+ return host->params->password ;
+}
+
+
+/* return true if the Connections for this host should attempt to do
+ streaming. */
+bool hostWantsStreaming (Host host)
+{
+ return host->params->wantStreaming ;
+}
+
+unsigned int hostMaxChecks (Host host)
+{
+ return host->params->maxChecks ;
+}
+
+bool hostDropDeferred (Host host)
+{
+ return host->params->dropDeferred ;
+}
+
+
+
+
+
+
+\f
+/**********************************************************************/
+/** CLASS FUNCTIONS **/
+/**********************************************************************/
+
+/*
+ * Set the state of whether each Connection is told to log its stats when
+ * its controlling Host logs its stats.
+ */
+void hostLogConnectionStats (bool val)
+{
+ logConnectionStats = val ;
+}
+
+
+bool hostLogConnectionStatsP (void)
+{
+ return logConnectionStats ;
+}
+
+
+
+/*
+ * Called by one of the Host's Connection's when it (the Connection)
+ * switches into or out of no-CHECK mode.
+ */
+void hostLogNoCheckMode (Host host, bool on, double low, double cur, double high)
+{
+ if (on && host->loggedModeOn == false)
+ {
+ notice ("%s mode no-CHECK entered [%.2f,%.2f,%.2f]",
+ host->params->peerName, low, cur, high) ;
+ host->loggedModeOn = true ;
+ }
+ else if (!on && host->loggedModeOff == false)
+ {
+ notice ("%s mode no-CHECK exited [%.2f,%.2f,%.2f]",
+ host->params->peerName, low, cur, high) ;
+ host->loggedModeOff = true ;
+ }
+}
+
+
+
+void hostSetStatusFile (const char *filename)
+{
+ FILE *fp ;
+
+ if (filename == NULL)
+ die ("Can't set status file name with a NULL filename\n") ;
+ else if (*filename == '\0')
+ die ("Can't set status file name with a empty string\n") ;
+
+ if (*filename == '/')
+ statusFile = xstrdup (filename) ;
+ else
+ statusFile = concatpath (innconf->pathlog,filename) ;
+
+ if ((fp = fopen (statusFile,"w")) == NULL)
+ {
+ syslog (LOG_ERR,"Status file is not a valid pathname: %s",
+ statusFile) ;
+ free (statusFile) ;
+ statusFile = NULL ;
+ }
+ else
+ fclose (fp) ;
+}
+
+void gHostStats (void)
+{
+ Host h ;
+ time_t now = theTime() ;
+ char msgstr[SMBUF] ;
+
+ for (h = gHostList ; h != NULL ; h = h->next)
+ if (h->firstConnectTime > 0) {
+ snprintf(msgstr, sizeof(msgstr), "accsize %.0f rejsize %.0f",
+ h->gArtsSizeAccepted, h->gArtsSizeRejected);
+ notice ("%s global seconds %ld offered %d accepted %d refused %d"
+ " rejected %d missing %d %s spooled %d unspooled %d",
+ h->params->peerName,
+ (long) (now - h->firstConnectTime),
+ h->gArtsOffered, h->gArtsAccepted,
+ h->gArtsNotWanted, h->gArtsRejected,
+ h->gArtsMissing, msgstr,
+ h->gArtsToTape, h->gArtsFromTape) ;
+ }
+}
+
+
+
+/**********************************************************************/
+/** PRIVATE FUNCTIONS **/
+/**********************************************************************/
+
+
+
+
+#define INHERIT 1
+#define NO_INHERIT 0
+
+\f
+static HostParams hostDetails (scope *s,
+ char *name,
+ bool isDefault,
+ FILE *fp)
+{
+ long iv ;
+ int bv, vival, inherit ;
+ HostParams p;
+ char * q;
+ double rv, l, h ;
+ value * v;
+
+ p=newHostParams(isDefault?NULL:defaultParams);
+
+ if (isDefault)
+ {
+ ASSERT (name==NULL);
+ }
+ else
+ {
+ if (name)
+ {
+ p->peerName=xstrdup(name);
+ }
+
+ if (s != NULL)
+ {
+ if (getString (s,IP_NAME,&q,NO_INHERIT))
+ p->ipName = q ;
+ else
+ p->ipName = xstrdup (name) ;
+ }
+
+ if (getString (s,"username",&q,NO_INHERIT))
+ p->username = q;
+ if (getString (s,"password",&q,NO_INHERIT))
+ p->password = q;
+
+ if (p->username != NULL && p->password == NULL)
+ logOrPrint (LOG_ERR,fp,"cannot find password for %s",p->peerName);
+ if (p->username == NULL && p->password != NULL)
+ logOrPrint (LOG_ERR,fp,"cannot find username for %s",p->peerName);
+
+ }
+
+#ifdef HAVE_INET6
+ if (getString(s,"bindaddress6",&q,isDefault?NO_INHERIT:INHERIT))
+ {
+ struct addrinfo *res, hints;
+
+ if (strcmp(q, "none") == 0)
+ p->family = AF_INET;
+ else if (p->family == AF_INET)
+ p->family = 0;
+
+ if (strcmp(q, "any") != 0 && strcmp(q, "all") != 0 &&
+ strcmp(q, "none") != 0)
+ {
+ memset( &hints, 0, sizeof( hints ) );
+ hints.ai_flags = AI_NUMERICHOST;
+ if( getaddrinfo( q, NULL, &hints, &res ) )
+ {
+ logOrPrint (LOG_ERR, fp,
+ "unable to determine IPv6 bind address for %s",
+ p->peerName) ;
+ }
+ else
+ {
+ p->bindAddr6 = (struct sockaddr_in6 *) xmalloc (res->ai_addrlen);
+ memcpy( p->bindAddr6, res->ai_addr, res->ai_addrlen );
+ }
+ }
+ }
+#endif
+
+ if (getString(s,"bindaddress",&q,isDefault?NO_INHERIT:INHERIT))
+ {
+ struct in_addr addr ;
+
+#ifdef HAVE_INET6
+ if (strcmp(q, "none") == 0) {
+ if (p->family) {
+ logOrPrint (LOG_ERR,fp,"cannot set both bindaddress and bindaddress6"
+ " to \"none\" -- ignoring them for %s",p->peerName);
+ p->family = 0;
+ } else {
+ p->family = AF_INET6;
+ }
+ } else if (p->family == AF_INET6)
+ p->family = 0;
+#endif
+
+ if (strcmp(q, "any") != 0 && strcmp(q, "all") != 0 &&
+ strcmp(q, "none") != 0)
+ {
+ if (!inet_aton(q,&addr))
+ {
+ logOrPrint (LOG_ERR, fp,
+ "unable to determine IPv4 bind address for %s",
+ p->peerName) ;
+ }
+ else
+ {
+ p->bindAddr = (struct sockaddr_in *)
+ xmalloc (sizeof(struct sockaddr_in));
+ make_sin( (struct sockaddr_in *)p->bindAddr, &addr );
+ }
+ }
+ }
+
+ /* check required global defaults are there and have good values */
+
+
+#define GETINT(sc,f,n,min,max,req,val,inh) \
+ vival = validateInteger(f,n,min,max,req,val,sc,inh); \
+ if (isDefault) do{ \
+ if(vival==VALUE_WRONG_TYPE) \
+ { \
+ logOrPrint(LOG_CRIT,fp,"cannot continue"); \
+ exit(1); \
+ } \
+ else if(vival != VALUE_OK) \
+ val = 0; \
+ } while(0); \
+ iv = 0 ; \
+ getInteger (sc,n,&iv,inh) ; \
+ val = (unsigned int) iv ;
+
+#define GETREAL(sc,f,n,min,max,req,val,inh) \
+ vival = validateReal(f,n,min,max,req,val,sc,inh); \
+ if (isDefault) do{ \
+ if(vival==VALUE_WRONG_TYPE) \
+ { \
+ logOrPrint(LOG_CRIT,fp,"cannot continue"); \
+ exit(1); \
+ } \
+ else if(vival != VALUE_OK) \
+ rv = 0; \
+ } while(0); \
+ rv = 0 ; \
+ getReal (sc,n,&rv,inh) ; \
+ val = rv ;
+
+#define GETBOOL(sc,f,n,req,val,inh) \
+ vival = validateBool(f,n,req,val,sc,inh); \
+ if (isDefault) do{ \
+ if(vival==VALUE_WRONG_TYPE) \
+ { \
+ logOrPrint(LOG_CRIT,fp,"cannot continue"); \
+ exit(1); \
+ } \
+ else if(vival != VALUE_OK) \
+ bv = 0; \
+ } while(0); \
+ bv = 0 ; \
+ getBool (sc,n,&bv,inh) ; \
+ val = (bv ? true : false);
+
+ inherit = isDefault?NO_INHERIT:INHERIT;
+ GETINT(s,fp,"article-timeout",0,LONG_MAX,REQ,p->articleTimeout, inherit);
+ GETINT(s,fp,"response-timeout",0,LONG_MAX,REQ,p->responseTimeout, inherit);
+ GETINT(s,fp,"close-period",0,LONG_MAX,REQ,p->closePeriod, inherit);
+ GETINT(s,fp,"initial-connections",0,LONG_MAX,REQ,p->initialConnections, inherit);
+ GETINT(s,fp,"max-connections",0,LONG_MAX,REQ,p->absMaxConnections, inherit);
+ GETINT(s,fp,"max-queue-size",1,LONG_MAX,REQ,p->maxChecks, inherit);
+ GETBOOL(s,fp,"streaming",REQ,p->wantStreaming, inherit);
+ GETBOOL(s,fp,"drop-deferred",REQ,p->dropDeferred, inherit);
+ GETBOOL(s,fp,"min-queue-connection",REQ,p->minQueueCxn, inherit);
+ GETREAL(s,fp,"no-check-high",0.0,100.0,REQ,p->lowPassHigh, inherit);
+ GETREAL(s,fp,"no-check-low",0.0,100.0,REQ,p->lowPassLow, inherit);
+ GETREAL(s,fp,"no-check-filter",0.1,DBL_MAX,REQ,p->lowPassFilter, inherit);
+ GETINT(s,fp,"port-number",0,LONG_MAX,REQ,p->portNum, inherit);
+ GETINT(s,fp,"backlog-limit",0,LONG_MAX,REQ,p->backlogLimit, inherit);
+
+#ifdef HAVE_INET6
+ GETBOOL(s,fp,"force-ipv4",NOTREQ,p->forceIPv4,inherit);
+ if (p->forceIPv4)
+ p->family = AF_INET;
+#endif
+
+ if (findValue (s,"backlog-factor",inherit) == NULL &&
+ findValue (s,"backlog-limit-high",inherit) == NULL)
+ {
+ logOrPrint (LOG_ERR,fp,
+ "ME config: must define at least one of backlog-factor"
+ " and backlog-limit-high. Adding %s: %f", "backlog-factor",
+ LIMIT_FUDGE) ;
+ addReal (s,"backlog-factor",LIMIT_FUDGE) ;
+ rv = 0 ;
+ }
+
+ GETBOOL(s,fp,"backlog-feed-first",NOTREQ,p->backlogFeedFirst, inherit);
+
+ /* Innfeed should emit a warning if backlog-feed-first is set
+ to "true" for any peer that doesn't have max-connections and
+ initial-connections both set to "1" */
+ if ((p->backlogFeedFirst)
+ && ((p->initialConnections <= 1) || (p->absMaxConnections != 1)))
+ {
+ if (p->peerName != NULL)
+ logOrPrint (LOG_WARNING,fp,
+ "ME config: innfeed will make more than one connection"
+ " to peer %s, but backlog-feed-first is set", p->peerName);
+ else
+ logOrPrint (LOG_WARNING,fp,
+ "ME config: innfeed will make more than one connection"
+ " to peer, but backlog-feed-first is set");
+ }
+
+ GETINT(s,fp,"backlog-limit-high",0,LONG_MAX,NOTREQNOADD,p->backlogLimitHigh, inherit);
+ GETREAL(s,fp,"backlog-factor",1.0,DBL_MAX,NOTREQNOADD,p->backlogFactor, inherit);
+
+ GETINT(s,fp,"dynamic-method",0,3,REQ,p->dynamicMethod, inherit);
+ GETREAL(s,fp,"dynamic-backlog-filter",0.0,DBL_MAX,REQ,p->dynBacklogFilter, inherit);
+ GETREAL(s,fp,"dynamic-backlog-low",0.0,100.0,REQ,p->dynBacklogLowWaterMark, inherit);
+ GETREAL(s,fp,"dynamic-backlog-high",0.0,100.0,REQ,p->dynBacklogHighWaterMark, inherit);
+
+ l=p->lowPassLow;
+ h=p->lowPassHigh;
+ if (l > h)
+ {
+ logOrPrint (LOG_ERR,fp,
+ "ME config: no-check-low value greater than no-check-high"
+ " (%f vs %f). Setting to %f and %f", l, h, NOCHECKLOW,
+ NOCHECKHIGH) ;
+ rv = 0 ;
+ v = findValue (s,"no-check-low",NO_INHERIT) ;
+ v->v.real_val = p->lowPassLow = NOCHECKLOW ;
+ v = findValue (s,"no-check-high",NO_INHERIT) ;
+ v->v.real_val = p->lowPassHigh = NOCHECKHIGH ;
+ }
+ else if (h - l < 5.0)
+ logOrPrint (LOG_WARNING,fp,
+ "ME config: no-check-low and no-check-high are close"
+ " together (%f vs %f)",l,h) ;
+
+ return p;
+}
+
+
+
+
+static HostParams getHostInfo (void)
+{
+ static int idx = 0 ;
+ value *v ;
+ scope *s ;
+ HostParams p=NULL;
+
+ bool isGood = false ;
+
+ if (topScope == NULL)
+ return p;
+
+ while ((v = getNextPeer (&idx)) != NULL)
+ {
+ if (!ISPEER (v))
+ continue ;
+
+ s = v->v.scope_val ;
+
+ p=hostDetails(s,v->name,false,NULL);
+
+ isGood = true ;
+
+ break ;
+ }
+
+ if (v == NULL)
+ idx = 0 ; /* start over next time around */
+
+ return p;
+}
+
+
+/*
+ * fully delete and clean up the Host object.
+ */
+void delHost (Host host)
+{
+ Host h,q ;
+
+ for (h = gHostList, q = NULL ; h != NULL ; q = h, h = h->next)
+ if (h == host)
+ {
+ if (gHostList == h)
+ gHostList = gHostList->next ;
+ else
+ q->next = h->next ;
+ break ;
+ }
+
+ ASSERT (h != NULL) ;
+
+ delTape (host->myTape) ;
+
+ free (host->connections) ;
+ free (host->cxnActive) ;
+ free (host->cxnSleeping) ;
+ free (host->params->peerName) ;
+ free (host->params->ipName) ;
+
+ if (host->ipAddrs)
+ {
+ if(host->ipAddrs[0])
+ free (host->ipAddrs[0]);
+ free (host->ipAddrs) ;
+ }
+
+ free (host) ;
+ gHostCount-- ;
+}
+
+
+\f
+static Host findHostByName (char *name)
+{
+ Host h;
+
+ for (h = gHostList; h != NULL; h = h->next)
+ if ( strcmp(h->params->peerName, name) == 0 )
+ return h;
+
+ return NULL;
+}
+
+
+\f
+/*
+ * the article can be dropped from the process queue and the connection can
+ * take a new article if there are any to be had.
+ */
+static void articleGone (Host host, Connection cxn, Article article)
+{
+ if ( !remArticle (article,&host->processed,&host->processedTail) )
+ die ("remArticle in articleGone failed") ;
+
+ delArticle (article) ;
+
+ if (cxn != NULL)
+ hostGimmeArticle (host,cxn) ; /* may not give anything over */
+}
+
+
+
+
+
+
+\f
+/*
+ * One of the Connections for this Host has reestablished itself, so stop
+ * spooling article info to disk.
+ */
+static void hostStopSpooling (Host host)
+{
+ ASSERT (host->spoolTime != 0) ;
+
+ clearTimer (host->statsId) ;
+ hostLogStats (host,true) ;
+
+ host->spoolTime = 0 ;
+}
+
+
+
+
+
+
+\f
+/*
+ * No connections are active and we're getting response 201 or 400 (or some
+ * such) so that we need to start spooling article info to disk.
+ */
+static void hostStartSpooling (Host host)
+{
+ ASSERT (host->spoolTime == 0) ;
+
+ queuesToTape (host) ;
+
+ hostLogStats (host,true) ;
+
+ host->spoolTime = theTime() ;
+ if (host->firstConnectTime == 0)
+ host->firstConnectTime = host->spoolTime ;
+
+ /* don't want to log too frequently */
+ if (SPOOL_LOG_PERIOD > 0 &&
+ (host->spoolTime - host->lastSpoolTime) > SPOOL_LOG_PERIOD)
+ {
+ notice ("%s spooling no active connections", host->params->peerName) ;
+ host->lastSpoolTime = host->spoolTime ;
+ }
+
+ host->connectTime = 0 ;
+
+ host->notifiedChangedRemBlckd = false ;
+
+ clearTimer (host->statsId) ;
+ host->statsId = prepareSleep (hostStatsTimeoutCbk, statsPeriod, host) ;
+}
+
+
+
+
+
+
+\f
+/*
+ * Time to log the statistics for the Host. If FINAL is true then the
+ * counters will be reset.
+ */
+static void hostLogStats (Host host, bool final)
+{
+ time_t now = theTime() ;
+ time_t *startPeriod ;
+ double cnt = (host->blCount) ? (host->blCount) : 1.0;
+ char msgstr[SMBUF] ;
+
+ if (host->spoolTime == 0 && host->connectTime == 0)
+ return ; /* host has never connected and never started spooling*/
+
+ startPeriod = (host->spoolTime != 0 ? &host->spoolTime : &host->connectTime);
+
+ if (now - *startPeriod >= statsResetPeriod)
+ final = true ;
+
+ if (host->spoolTime != 0)
+ notice ("%s %s seconds %ld spooled %d on_close %d sleeping %d",
+ host->params->peerName, (final ? "final" : "checkpoint"),
+ (long) (now - host->spoolTime), host->artsToTape,
+ host->artsHostClose, host->artsHostSleep) ;
+ else {
+ snprintf(msgstr, sizeof(msgstr), "accsize %.0f rejsize %.0f",
+ host->artsSizeAccepted, host->artsSizeRejected);
+ notice ("%s %s seconds %ld offered %d accepted %d refused %d rejected %d"
+ " missing %d %s spooled %d on_close %d unspooled %d"
+ " deferred %d/%.1f requeued %d"
+ " queue %.1f/%d:%.0f,%.0f,%.0f,%.0f,%.0f,%.0f",
+ host->params->peerName, (final ? "final" : "checkpoint"),
+ (long) (now - host->connectTime),
+ host->artsOffered, host->artsAccepted,
+ host->artsNotWanted, host->artsRejected,
+ host->artsMissing, msgstr,
+ host->artsToTape,
+ host->artsHostClose, host->artsFromTape,
+ host->artsDeferred, (double)host->dlAccum/cnt,
+ host->artsCxnDrop,
+ (double)host->blAccum/cnt, hostHighwater,
+ (100.0*host->blNone)/cnt,
+ (100.0*host->blQuartile[0])/cnt, (100.0*host->blQuartile[1])/cnt,
+ (100.0*host->blQuartile[2])/cnt, (100.0*host->blQuartile[3])/cnt,
+ (100.0*host->blFull)/cnt) ;
+ }
+
+ if (logConnectionStats)
+ {
+ unsigned int i ;
+
+ for (i = 0 ; i < host->maxConnections ; i++)
+ if (host->connections [i] != NULL && host->cxnActive [i])
+ cxnLogStats (host->connections [i],final) ;
+ }
+
+ /* one 'spooling backlog' message per stats logging period */
+ host->loggedBacklog = false ;
+ host->loggedModeOn = host->loggedModeOff = false ;
+
+ if (final)
+ {
+ host->artsOffered = 0 ;
+ host->artsAccepted = 0 ;
+ host->artsNotWanted = 0 ;
+ host->artsRejected = 0 ;
+ host->artsDeferred = 0 ;
+ host->artsMissing = 0 ;
+ host->artsToTape = 0 ;
+ host->artsQueueOverflow = 0 ;
+ host->artsCxnDrop = 0 ;
+ host->artsHostSleep = 0 ;
+ host->artsHostClose = 0 ;
+ host->artsFromTape = 0 ;
+ host->artsSizeAccepted = 0 ;
+ host->artsSizeRejected = 0 ;
+
+ *startPeriod = theTime () ; /* in of case STATS_RESET_PERIOD */
+ }
+
+ /* reset these each log period */
+ host->blNone = 0 ;
+ host->blFull = 0 ;
+ host->blQuartile[0] = host->blQuartile[1] = host->blQuartile[2] =
+ host->blQuartile[3] = 0;
+ host->dlAccum = 0;
+ host->blAccum = 0;
+ host->blCount = 0;
+
+#if 0
+ /* XXX turn this section on to get a snapshot at each log period. */
+ if (gPrintInfo != NULL)
+ gPrintInfo () ;
+#endif
+}
+
+
+
+
+
+
+\f
+
+static double
+convsize(double size, char **tsize)
+{
+ double dsize;
+ static char tTB[]="TB";
+ static char tGB[]="GB";
+ static char tMB[]="MB";
+ static char tKB[]="KB";
+ static char tB []="B";
+
+ if (size/((double)1024*1024*1024*1000)>=1.) {
+ dsize=size/((double)1024*1024*1024*1024);
+ *tsize=tTB;
+ } else if (size/(1024*1024*1000)>=1.) {
+ dsize=size/(1024*1024*1024);
+ *tsize=tGB;
+ } else if (size/(1024*1000)>=1.) {
+ dsize=size/(1024*1024);
+ *tsize=tMB;
+ } else if (size/1000>=1.) {
+ dsize=size/1024;
+ *tsize=tKB;
+ } else {
+ dsize=size;
+ *tsize=tB;
+ }
+ return dsize;
+}
+
+
+/*
+ * Log the status of the Hosts.
+ */
+extern char *versionInfo ;
+static void hostLogStatus (void)
+{
+ FILE *fp = NULL ;
+ Host h ;
+ bool anyToLog = false ;
+ unsigned int peerNum = 0, actConn = 0, slpConn = 0, maxcon = 0 ;
+ static bool logged = false ;
+ static bool flogged = false ;
+
+ if (statusFile == NULL && !logged)
+ {
+ syslog (LOG_ERR,"No status file to write to") ;
+ logged = true ;
+ return ;
+ }
+
+ logged = false ;
+
+ for (h = gHostList ; h != NULL ; h = h->next)
+ if (h->myTape != NULL) /* the host deletes its tape when it's closing */
+ {
+ anyToLog = true ;
+ peerNum++ ;
+ actConn += h->activeCxns ;
+ slpConn += h->sleepingCxns ;
+ maxcon += h->maxConnections ;
+ }
+
+ if (!anyToLog)
+ return ;
+
+ lastStatusLog = theTime() ;
+
+ TMRstart(TMR_STATUSFILE);
+ if ((fp = fopen (statusFile,"w")) == NULL)
+ {
+ if ( !flogged )
+ syswarn ("ME oserr status file open: %s", statusFile) ;
+ flogged = true ;
+ }
+ else
+ {
+ char timeString [30] ;
+ time_t now ;
+ long sec ;
+ long offered ;
+ double size, totalsize;
+ char *tsize;
+
+ flogged = false ;
+
+ now = time (NULL) ;
+ sec = (long) (now - start) ;
+ strlcpy (timeString,ctime (&now),sizeof (timeString)) ;
+
+ if (genHtml)
+ {
+ fprintf (fp, "<HTML>\n"
+ "<HEAD>\n"
+ "<META HTTP-EQUIV=\"Refresh\" CONTENT=\"300;\">\n"
+ "</HEAD>\n"
+ "<BODY>\n") ;
+ fprintf (fp, "\n");
+ fprintf (fp, "<PRE>\n");
+ }
+
+ fprintf (fp,"%s\npid %d started %s\nUpdated: %s",
+ versionInfo,(int) myPid,startTime,timeString) ;
+ fprintf (fp,"(peers: %d active-cxns: %d sleeping-cxns: %d idle-cxns: %d)\n\n",
+ peerNum, actConn, slpConn,(maxcon - (actConn + slpConn))) ;
+
+ fprintf (fp,"Configuration file: %s\n\n",configFile) ;
+
+ if (genHtml)
+ {
+ fprintf (fp, "</PRE>\n");
+ fprintf (fp,"<UL>\n");
+ for (h = gHostList ; h != NULL ; h = h->next)
+ fprintf (fp,"<LI><A href=\"#%s\">%s</A></LI>\n",
+ h->params->peerName, h->params->peerName);
+ fprintf (fp,"</UL>\n\n");
+ fprintf (fp,"<PRE>\n");
+ }
+
+ mainLogStatus (fp) ;
+ listenerLogStatus (fp) ;
+
+/*
+Default peer configuration parameters:
+ article timeout: 600 initial connections: 1
+ response timeout: 300 max connections: 5
+ close period: 6000 max checks: 25
+ want streaming: true dynamic method: 1
+ no-check on: 95.0% dynamic backlog low: 25%
+ no-check off: 90.0% dynamic backlog high: 50%
+ no-check filter: 50.0 dynamic backlog filter: 0.7
+ backlog low limit: 1024 port num: 119
+ backlog high limit: 1280 backlog feed first: false
+ backlog factor: 1.1
+*/
+ fprintf(fp,"%sDefault peer configuration parameters:%s\n",
+ genHtml ? "<B>" : "", genHtml ? "</B>" : "") ;
+ fprintf(fp," article timeout: %-5d initial connections: %d\n",
+ defaultParams->articleTimeout,
+ defaultParams->initialConnections) ;
+ fprintf(fp," response timeout: %-5d max connections: %d\n",
+ defaultParams->responseTimeout,
+ defaultParams->absMaxConnections) ;
+ fprintf(fp," close period: %-5d max checks: %d\n",
+ defaultParams->closePeriod,
+ defaultParams->maxChecks) ;
+ fprintf(fp," want streaming: %-5s dynamic method: %d\n",
+ defaultParams->wantStreaming ? "true " : "false",
+ defaultParams->dynamicMethod) ;
+ fprintf(fp," no-check on: %-2.1f%% dynamic backlog low: %-2.1f%%\n",
+ defaultParams->lowPassHigh,
+ defaultParams->dynBacklogLowWaterMark) ;
+ fprintf(fp," no-check off: %-2.1f%% dynamic backlog high: %-2.1f%%\n",
+ defaultParams->lowPassLow,
+ defaultParams->dynBacklogHighWaterMark) ;
+ fprintf(fp," no-check filter: %-2.1f dynamic backlog filter: %-2.1f\n",
+ defaultParams->lowPassFilter,
+ defaultParams->dynBacklogFilter) ;
+ fprintf(fp," backlog limit low: %-7d drop-deferred: %s\n",
+ defaultParams->backlogLimit,
+ defaultParams->dropDeferred ? "true " : "false");
+ fprintf(fp," backlog limit high: %-7d min-queue-cxn: %s\n",
+ defaultParams->backlogLimitHigh,
+ defaultParams->minQueueCxn ? "true " : "false");
+ fprintf(fp," backlog feed first: %s\n",
+ defaultParams->backlogFeedFirst ? "true " : "false");
+ fprintf(fp," backlog factor: %1.1f\n\n",
+ defaultParams->backlogFactor);
+
+ tapeLogGlobalStatus (fp) ;
+
+ fprintf (fp,"\n") ;
+ fprintf(fp,"%sglobal (process)%s\n",
+ genHtml ? "<B>" : "", genHtml ? "</B>" : "") ;
+
+ fprintf (fp, " seconds: %ld\n", sec) ;
+ if (sec == 0) sec = 1 ;
+ offered = procArtsOffered ? procArtsOffered : 1 ;
+ totalsize = procArtsSizeAccepted+procArtsSizeRejected ;
+ if (totalsize == 0) totalsize = 1. ;
+
+ fprintf (fp, " offered: %-5ld\t%6.2f art/s\n",
+ procArtsOffered,
+ (double)procArtsOffered/sec) ;
+ fprintf (fp, " accepted: %-5ld\t%6.2f art/s\t%5.1f%%\n",
+ procArtsAccepted,
+ (double)procArtsAccepted/sec,
+ (double)procArtsAccepted*100./offered) ;
+ fprintf (fp, " refused: %-5ld\t%6.2f art/s\t%5.1f%%\n",
+ procArtsNotWanted,
+ (double)procArtsNotWanted/sec,
+ (double)procArtsNotWanted*100./offered) ;
+ fprintf (fp, " rejected: %-5ld\t%6.2f art/s\t%5.1f%%\n",
+ procArtsRejected,
+ (double)procArtsRejected/sec,
+ (double)procArtsRejected*100./offered) ;
+ fprintf (fp, " missing: %-5ld\t%6.2f art/s\t%5.1f%%\n",
+ procArtsMissing,
+ (double)procArtsMissing/sec,
+ (double)procArtsMissing*100./offered) ;
+ fprintf (fp, " deferred: %-5ld\t%6.2f art/s\t%5.1f%%\n",
+ procArtsDeferred,
+ (double)procArtsDeferred/sec,
+ (double)procArtsDeferred*100./offered) ;
+
+ size=convsize(procArtsSizeAccepted, &tsize);
+ fprintf (fp, "accpt size: %.3g %s", size, tsize) ;
+ size=convsize(procArtsSizeAccepted/sec, &tsize);
+ fprintf (fp, " \t%6.3g %s/s\t%5.1f%%\n",
+ size, tsize,
+ procArtsSizeAccepted*100./totalsize) ;
+
+ size=convsize(procArtsSizeRejected, &tsize);
+ fprintf (fp, "rejct size: %.3g %s", size, tsize) ;
+ size=convsize(procArtsSizeRejected/sec, &tsize);
+ fprintf (fp, " \t%6.3g %s/s\t%5.1f%%\n",
+ size, tsize,
+ procArtsSizeRejected*100./totalsize) ;
+
+ fprintf (fp, "\n");
+
+ for (h = gHostList ; h != NULL ; h = h->next)
+ hostPrintStatus (h,fp) ;
+
+ if (genHtml)
+ {
+ fprintf (fp,"</PRE>\n") ;
+ fprintf (fp,"</BODY>\n") ;
+ fprintf (fp,"</HTML>\n") ;
+ }
+
+ fclose (fp) ;
+ }
+ TMRstop(TMR_STATUSFILE);
+}
+
+/*
+ * This prints status information for each host. An example of the
+ * format of the output is:
+ *
+ * sitename
+ * seconds: 351 art. timeout: 400 ip name: foo.bar
+ * offered: 1194 resp. timeout: 240 port: 119
+ * accepted: 178 want streaming: yes active cxns: 6
+ * refused: 948 is streaming: yes sleeping cxns: 0
+ * rejected: 31 max checks: 25 initial cxns: 5
+ * missing: 0 no-check on: 95.0% idle cxns: 4
+ * deferred: 0 no-check off: 95.0% max cxns: 8/10
+ * requeued: 0 no-check fltr: 50.0 queue length: 0.0/200
+ * spooled: 0 dynamic method: 0 empty: 100.0%
+ *[overflow]: 0 dyn b'log low: 25% >0%-25%: 0.0%
+ *[on_close]: 0 dyn b'log high: 50% 25%-50%: 0.0%
+ *[sleeping]: 0 dyn b'log stat: 37% 50%-75%: 0.0%
+ * unspooled: 0 dyn b'log fltr: 0.7 75%-<100%: 0.0%
+ * no queue: 1234 avr.cxns queue: 0.0 full: 0.0%
+ *accpt size: 121.1 MB drop-deferred: false defer length: 0
+ *rejct size: 27.1 MB min-queue-cxn: false
+ * backlog low limit: 1000000
+ * backlog high limit: 2000000 (factor 2.0)
+ * backlog shrinkage: 0 bytes (from current file)
+ * offered: 1.13 art/s accepted: 0.69 art/s (101.71 KB/s)
+ * refused: 0.01 art/s rejected: 0.42 art/s (145.11 KB/s)
+ * missing 0 spooled 0
+ *
+ */
+static void hostPrintStatus (Host host, FILE *fp)
+{
+ time_t now = theTime() ;
+ double cnt = (host->blCount) ? (host->blCount) : 1.0;
+ double size;
+ char *tsize;
+ char buf[]="1234.1 MB";
+
+ ASSERT (host != NULL) ;
+ ASSERT (fp != NULL) ;
+
+ if (genHtml)
+ fprintf (fp,"<A name=\"%s\"><B>%s</B></A>",host->params->peerName,
+ host->params->peerName);
+ else
+ fprintf (fp,"%s",host->params->peerName);
+
+ if (host->blockedReason != NULL)
+ fprintf (fp," (remote status: ``%s'')",host->blockedReason) ;
+
+ fputc ('\n',fp) ;
+
+ fprintf (fp, " seconds: %-7ld art. timeout: %-5d ip name: %s\n",
+ host->firstConnectTime > 0 ? (long)(now - host->firstConnectTime) : 0,
+ host->params->articleTimeout, host->params->ipName) ;
+
+ fprintf (fp, " offered: %-7ld resp. timeout: %-5d port: %d\n",
+ (long) host->gArtsOffered, host->params->responseTimeout,
+ host->params->portNum);
+
+ fprintf (fp, " accepted: %-7ld want streaming: %s active cxns: %d\n",
+ (long) host->gArtsAccepted,
+ (host->params->wantStreaming ? "yes" : "no "),
+ host->activeCxns) ;
+
+ fprintf (fp, " refused: %-7ld is streaming: %s sleeping cxns: %d\n",
+ (long) host->gArtsNotWanted,
+ (host->remoteStreams ? "yes" : "no "),
+ host->sleepingCxns) ;
+
+ fprintf (fp, " rejected: %-7ld max checks: %-5d initial cxns: %d\n",
+ (long) host->gArtsRejected, host->params->maxChecks,
+ host->params->initialConnections) ;
+
+ fprintf (fp, " missing: %-7ld no-check on: %-3.1f%% idle cxns: %d\n",
+ (long) host->gArtsMissing, host->params->lowPassHigh,
+ host->maxConnections - (host->activeCxns + host->sleepingCxns)) ;
+
+ fprintf (fp, " deferred: %-7ld no-check off: %-3.1f%% max cxns: %d/%d\n",
+ (long) host->gArtsDeferred, host->params->lowPassLow,
+ host->maxConnections, host->params->absMaxConnections) ;
+
+ fprintf (fp, " requeued: %-7ld no-check fltr: %-3.1f queue length: %-3.1f/%d\n",
+ (long) host->gArtsCxnDrop, host->params->lowPassFilter,
+ (double)host->blAccum / cnt, hostHighwater) ;
+
+ fprintf (fp, " spooled: %-7ld dynamic method: %-5d empty: %-3.1f%%\n",
+ (long) host->gArtsToTape,
+ host->params->dynamicMethod,
+ 100.0 * host->blNone / cnt) ;
+
+ fprintf (fp, "[overflow]: %-7ld dyn b'log low: %-3.1f%% >0%%-25%%: %-3.1f%%\n",
+ (long) host->gArtsQueueOverflow,
+ host->params->dynBacklogLowWaterMark,
+ 100.0 * host->blQuartile[0] / cnt) ;
+
+ fprintf (fp, "[on_close]: %-7ld dyn b'log high: %-3.1f%% 25%%-50%%: %-3.1f%%\n",
+ (long) host->gArtsHostClose,
+ host->params->dynBacklogHighWaterMark,
+ 100.0 * host->blQuartile[1] / cnt) ;
+
+ fprintf (fp, "[sleeping]: %-7ld dyn b'log stat: %-3.1f%% 50%%-75%%: %-3.1f%%\n",
+ (long) host->gArtsHostSleep,
+ host->backlogFilter*100.0*(1.0-host->params->dynBacklogFilter),
+ 100.0 * host->blQuartile[2] / cnt) ;
+
+ fprintf (fp, " unspooled: %-7ld dyn b'log fltr: %-3.1f 75%%-<100%%: %-3.1f%%\n",
+ (long) host->gArtsFromTape,
+ host->params->dynBacklogLowWaterMark,
+ 100.0 * host->blQuartile[3] / cnt) ;
+
+ fprintf (fp, " no queue: %-7ld avr.cxns queue: %-3.1f full: %-3.1f%%\n",
+ (long) host->gNoQueue,
+ (double) host->gCxnQueue / (host->gArtsOffered ? host->gArtsOffered :1) ,
+ 100.0 * host->blFull / cnt) ;
+ size=convsize(host->gArtsSizeAccepted, &tsize);
+ snprintf(buf,sizeof(buf),"%.3g %s", size, tsize);
+ fprintf (fp, "accpt size: %-8s drop-deferred: %-5s defer length: %-3.1f\n",
+ buf, host->params->dropDeferred ? "true " : "false",
+ (double)host->dlAccum / cnt) ;
+ size=convsize(host->gArtsSizeRejected, &tsize);
+ snprintf(buf,sizeof(buf),"%.3g %s", size, tsize);
+ fprintf (fp, "rejct size: %-8s min-queue-cxn: %s\n",
+ buf, host->params->minQueueCxn ? "true " : "false");
+
+ tapeLogStatus (host->myTape,fp) ;
+
+ {
+ time_t sec = (time_t) (now - host->connectTime);
+ double or, ar, rr, jr;
+ double ars, jrs;
+ char *tars, *tjrs;
+ if (sec != 0) {
+ or = (double) host->artsOffered / (double) sec;
+ ar = (double) host->artsAccepted / (double) sec;
+ rr = (double) host->artsNotWanted / (double) sec;
+ jr = (double) host->artsRejected / (double) sec;
+ ars = convsize (host->artsSizeAccepted/sec, &tars);
+ jrs = convsize (host->artsSizeRejected/sec, &tjrs);
+ fprintf(fp, " offered: %5.2f art/s accepted: %5.2f art/s, %.3g %s/s\n",
+ or, ar, ars, tars);
+ fprintf(fp, " refused: %5.2f art/s rejected: %5.2f art/s, %.3g %s/s\n",
+ rr, jr, jrs, tjrs);
+ }
+ fprintf(fp, " missing %d spooled %d\n",
+ host->artsMissing, host->artsToTape);
+ }
+
+#ifdef XXX_STATSHACK
+ {
+ time_t now = time(NULL), sec = (long) (now - host->connectTime);
+ float or, ar, rr, jr;
+
+ if (sec != 0) {
+ or = (float) host->artsOffered / (float) sec;
+ ar = (float) host->artsAccepted / (float) sec;
+ rr = (float) host->artsNotWanted / (float) sec;
+ jr = (float) host->artsRejected / (float) sec;
+ fprintf(fp, "\t\tor %02.2f ar %02.2f rr %02.2f jr %02.2f\n",
+ or, ar, rr, jr);
+ }
+ fprintf(fp, "\tmissing %d spooled %d\n",
+ host->artsMissing,host->backlogSpooled);
+ }
+#endif /* XXX_STATSHACK */
+
+ fprintf (fp, "\n\n");
+}
+
+
+
+
+
+
+\f
+/*
+ * The callback function for the statistics timer to call.
+ */
+static void hostStatsTimeoutCbk (TimeoutId tid UNUSED, void *data)
+{
+ Host host = (Host) data ;
+ time_t now = theTime () ;
+
+ ASSERT (tid == host->statsId) ;
+
+ if (!amClosing (host))
+ hostLogStats (host, false) ;
+
+ if (now - lastStatusLog >= statsPeriod)
+ hostLogStatus () ;
+
+ host->statsId = prepareSleep (hostStatsTimeoutCbk, statsPeriod, host) ;
+}
+
+
+/*
+ * The callback function for the deferred article timer to call.
+ */
+static void hostDeferredArtCbk (TimeoutId tid UNUSED, void *data)
+{
+ Host host = (Host) data ;
+ time_t now = theTime () ;
+ Article article ;
+
+ ASSERT (tid == host->deferredId) ;
+
+ while (host->deferred && host->deferred->whenToRequeue <= now)
+ {
+ article = remHead (&host->deferred,&host->deferredTail) ;
+ host->deferLen-- ;
+ hostSendArticle (host, article) ; /* requeue it */
+ }
+
+ if (host->deferred)
+ host->deferredId = prepareSleep (hostDeferredArtCbk,
+ host->deferred->whenToRequeue - now,
+ host) ;
+ else
+ host->deferredId = 0;
+}
+
+
+/* if the host has too many unprocessed articles so we send some to the tape. */
+static void backlogToTape (Host host)
+{
+ Article article ;
+
+ while ((host->backlog + host->deferLen) > hostHighwater)
+ {
+ if (!host->loggedBacklog)
+ {
+ host->loggedBacklog = true ;
+ }
+
+ if (host->deferred != NULL)
+ {
+ article = remHead (&host->deferred,&host->deferredTail) ;
+ host->deferLen--;
+ }
+ else
+ {
+ article = remHead (&host->queued,&host->queuedTail) ;
+ host->backlog--;
+ }
+
+ ASSERT(article != NULL);
+
+ host->artsQueueOverflow++ ;
+ host->gArtsQueueOverflow++ ;
+ host->artsToTape++ ;
+ host->gArtsToTape++ ;
+ procArtsToTape++ ;
+ tapeTakeArticle (host->myTape,article) ;
+ }
+}
+
+
+
+
+
+
+\f
+/*
+ * Returns true of the Host is in the middle of closing down.
+ */
+static bool amClosing (Host host)
+{
+ return (host->myTape == NULL ? true : false) ;
+}
+
+
+
+
+
+
+\f
+/*
+ * flush all queued articles all the way out to disk.
+ */
+static void queuesToTape (Host host)
+{
+ Article art ;
+
+ while ((art = remHead (&host->processed,&host->processedTail)) != NULL)
+ {
+ host->artsHostClose++ ;
+ host->gArtsHostClose++ ;
+ host->artsToTape++ ;
+ host->gArtsToTape++ ;
+ procArtsToTape++ ;
+ tapeTakeArticle (host->myTape,art) ;
+ }
+
+ while ((art = remHead (&host->queued,&host->queuedTail)) != NULL)
+ {
+ host->backlog-- ;
+ host->artsHostClose++ ;
+ host->gArtsHostClose++ ;
+ host->artsToTape++ ;
+ host->gArtsToTape++ ;
+ procArtsToTape++ ;
+ tapeTakeArticle (host->myTape,art) ;
+ }
+
+ while ((art = remHead (&host->deferred,&host->deferredTail)) != NULL)
+ {
+ host->deferLen-- ;
+ host->artsHostClose++ ;
+ host->gArtsHostClose++ ;
+ host->artsToTape++ ;
+ host->gArtsToTape++ ;
+ procArtsToTape++ ;
+ tapeTakeArticle (host->myTape,art) ;
+ }
+
+ while ((art = remHead (&host->deferred,&host->deferredTail)) != NULL)
+ {
+ host->deferLen-- ;
+ host->artsHostClose++ ;
+ host->gArtsHostClose++ ;
+ host->artsToTape++ ;
+ host->gArtsToTape++ ;
+ procArtsToTape++ ;
+ tapeTakeArticle (host->myTape,art) ;
+ }
+}
+
+
+
+
+
+
+\f
+#define QUEUE_ELEM_POOL_SIZE ((4096 - 2 * (sizeof (void *))) / (sizeof (struct proc_q_elem)))
+
+static ProcQElem queueElemPool ;
+
+/*
+ * Add an article to the given queue.
+ */
+static void queueArticle (Article article, ProcQElem *head, ProcQElem *tail,
+ time_t when)
+{
+ ProcQElem elem ;
+
+ if (queueElemPool == NULL)
+ {
+ unsigned int i ;
+
+ queueElemPool =
+ xmalloc (sizeof(struct proc_q_elem) * QUEUE_ELEM_POOL_SIZE) ;
+
+ for (i = 0; i < QUEUE_ELEM_POOL_SIZE - 1; i++)
+ queueElemPool[i] . next = &(queueElemPool [i + 1]) ;
+ queueElemPool [QUEUE_ELEM_POOL_SIZE-1] . next = NULL ;
+ }
+
+ elem = queueElemPool ;
+ ASSERT (elem != NULL) ;
+ queueElemPool = queueElemPool->next ;
+
+ elem->article = article ;
+ elem->next = NULL ;
+ elem->prev = *tail ;
+ elem->whenToRequeue = when ;
+ if (*tail != NULL)
+ (*tail)->next = elem ;
+ else
+ *head = elem ;
+ *tail = elem ;
+}
+
+
+
+
+
+
+\f
+/*
+ * remove the article from the queue
+ */
+static bool remArticle (Article article, ProcQElem *head, ProcQElem *tail)
+{
+ ProcQElem elem ;
+
+ ASSERT (head != NULL) ;
+ ASSERT (tail != NULL) ;
+
+ /* we go backwards down the list--probably faster */
+ elem = *tail ;
+ while (elem != NULL && elem->article != article)
+ elem = elem->prev ;
+
+ if (elem != NULL)
+ {
+ if (elem->prev != NULL)
+ elem->prev->next = elem->next ;
+ if (elem->next != NULL)
+ elem->next->prev = elem->prev ;
+ if (*head == elem)
+ *head = elem->next ;
+ if (*tail == elem)
+ *tail = elem->prev ;
+
+ elem->next = queueElemPool ;
+ queueElemPool = elem ;
+
+ return true ;
+ }
+ else
+ return false ;
+}
+
+
+
+
+
+
+\f
+/*
+ * remove the article that's at the head of the queue and return
+ * it. Returns NULL if the queue is empty.
+ */
+static Article remHead (ProcQElem *head, ProcQElem *tail)
+{
+ ProcQElem elem ;
+ Article art ;
+
+ ASSERT (head != NULL) ;
+ ASSERT (tail != NULL) ;
+ ASSERT ((*head == NULL && *tail == NULL) ||
+ (*head != NULL && *tail != NULL)) ;
+
+ if (*head == NULL)
+ return NULL ;
+
+ elem = *head ;
+ art = elem->article ;
+ *head = elem->next ;
+ if (elem->next != NULL)
+ elem->next->prev = NULL ;
+
+ if (*tail == elem)
+ *tail = NULL ;
+
+ elem->next = queueElemPool ;
+ queueElemPool = elem ;
+
+ return art ;
+}
+
+
+
+static int validateInteger (FILE *fp, const char *name,
+ long low, long high, int required, long setval,
+ scope * sc, unsigned int inh)
+{
+ int rval = VALUE_OK ;
+ value *v ;
+ scope *s ;
+ char *p = strrchr (name,':') ;
+
+ v = findValue (sc,name,inh) ;
+ if (v == NULL && required != NOTREQNOADD)
+ {
+ s = findScope (sc,name,0) ;
+ addInteger (s,p ? p + 1 : name,setval) ;
+ if (required == REQ)
+ {
+ rval = VALUE_MISSING ;
+ logOrPrint (LOG_ERR,fp,
+ "ME config: no definition for required key %s",name) ;
+ }
+ else if (required)
+ logOrPrint (LOG_INFO,fp,
+ "ME config: adding missing key/value %s: %ld",name
+ ,setval) ;
+ }
+ else if (v != NULL && v->type != intval)
+ {
+ rval = VALUE_WRONG_TYPE ;
+ logOrPrint (LOG_ERR,fp,"ME config: value of %s is not an integer",name) ;
+ }
+ else if (v != NULL && low != LONG_MIN && v->v.int_val < low)
+ {
+ rval = VALUE_TOO_LOW ;
+ logOrPrint (LOG_ERR,fp,
+ "ME config: value of %s (%ld) in %s is lower than minimum"
+ " of %ld. Using %ld",name,v->v.int_val,
+ "global scope",low,low) ;
+ v->v.int_val = low ;
+ }
+ else if (v != NULL && high != LONG_MAX && v->v.int_val > high)
+ {
+ rval = VALUE_TOO_HIGH ;
+ logOrPrint(LOG_ERR,fp,
+ "ME config: value of %s (%ld) in %s is higher than maximum"
+ " of %ld. Using %ld",name,v->v.int_val,
+ "global scope",high,high);
+ v->v.int_val = high ;
+ }
+
+ return rval ;
+}
+
+
+
+static int validateReal (FILE *fp, const char *name, double low,
+ double high, int required, double setval,
+ scope * sc, unsigned int inh)
+{
+ int rval = VALUE_OK ;
+ value *v ;
+ scope *s ;
+ char *p = strrchr (name,':') ;
+
+ v = findValue (sc,name,inh) ;
+ if (v == NULL && required != NOTREQNOADD)
+ {
+ s = findScope (sc,name,0) ;
+ addReal (s,p ? p + 1 : name,setval) ;
+ if (required == REQ)
+ {
+ rval = VALUE_MISSING ;
+ logOrPrint (LOG_ERR,fp,
+ "ME config: no definition for required key %s",name) ;
+ }
+ else
+ logOrPrint (LOG_INFO,fp,
+ "ME config: adding missing key/value %s: %f",name,
+ setval) ;
+ }
+ else if (v != NULL && v->type != realval)
+ {
+ rval = VALUE_WRONG_TYPE ;
+ logOrPrint (LOG_ERR,fp,
+ "ME config: value of %s is not a floating point number",
+ name) ;
+ }
+ else if (v != NULL && low != -DBL_MAX && v->v.real_val < low)
+ {
+ logOrPrint (LOG_ERR,fp,
+ "ME config: value of %s (%f) is lower than minimum of %f",
+ name,v->v.real_val,low) ;
+ v->v.real_val = setval ;
+ }
+ else if (v != NULL && high != DBL_MAX && v->v.real_val > high)
+ {
+ logOrPrint (LOG_ERR,fp,
+ "ME config: value of %s (%f) is higher than maximum of %f",
+ name,v->v.real_val,high) ;
+ v->v.real_val = setval ;
+ }
+
+ return rval ;
+}
+
+
+
+static int validateBool (FILE *fp, const char *name, int required, bool setval,
+ scope * sc, unsigned int inh)
+{
+ int rval = VALUE_OK ;
+ value *v ;
+ scope *s ;
+ char *p = strrchr (name,':') ;
+
+ v = findValue (sc,name,inh) ;
+ if (v == NULL && required != NOTREQNOADD)
+ {
+ s = findScope (sc,name,0) ;
+ addBoolean (s,p ? p + 1 : name, setval ? 1 : 0) ;
+ if (required == REQ)
+ {
+ rval = VALUE_MISSING ;
+ logOrPrint (LOG_ERR,fp,
+ "ME config: no definition for required key %s",name) ;
+ }
+ else
+ logOrPrint (LOG_INFO,fp,
+ "ME config: adding missing key/value %s: %s",name,
+ (setval ? "true" : "false")) ;
+ }
+ else if (v != NULL && v->type != boolval)
+ {
+ rval = VALUE_WRONG_TYPE ;
+ logOrPrint (LOG_ERR,fp,"ME config: value of %s is not a boolean",name) ;
+ }
+
+ return rval ;
+}
+
+
+void gCalcHostBlStat (void)
+{
+ Host h ;
+
+ for (h = gHostList ; h != NULL ; h = h->next)
+ {
+ h->dlAccum += h->deferLen ;
+ h->blAccum += h->backlog ;
+ if (h->backlog == 0)
+ h->blNone++ ;
+ else if (h->backlog >= (hostHighwater - h->deferLen))
+ h->blFull++ ;
+ else
+ h->blQuartile[(4*h->backlog) / (hostHighwater - h->deferLen)]++ ;
+ h->blCount++ ;
+ }
+}
+static void hostCleanup (void)
+{
+ if (statusFile != NULL)
+ free (statusFile) ;
+ statusFile = NULL ;
+}
--- /dev/null
+/* $Id: host.h 7778 2008-04-17 21:27:22Z iulius $
+**
+** The public interface to the Host class.
+**
+** Written by James Brister <brister@vix.com>
+**
+** The Host class represents the remote news system that we're feeding. A
+** Host object has possibly multiple connections to the remote system which
+** it sends articles down. It is given the articles by other objects
+** (typically the InnListener), and once taken it assumes all responsibility
+** for transmission or temporary storage on network failures etc.
+*/
+
+#if ! defined ( host_h__ )
+#define host_h__
+
+
+#include <stdio.h>
+
+#include "misc.h"
+
+/*
+ * Functions from elsewhere used by host.c
+ */
+
+extern void mainLogStatus (FILE *fp) ;
+
+
+/*
+ * Functions used by the InnListener
+ */
+
+
+/*
+ * Create a new Host object.
+ *
+ * NAME is the name that INN uses.
+ * IPNAME is the name the networking code uses (or the ascii dotted quad
+ * IP address).
+ * ARTTIMEOUT is the max amount of time we'll wait for a new article
+ * from INN before considering the connection unused and we'll close
+ * down.
+ * RESPTIMEOUT is the max amount of time we'll wait for any reponse
+ * from a remote. Past this we'll close down the network connection.
+ * INITIALCXNS is the number of Connections to create at Host creation time.
+ * MAXCXNS is the maximum number of parallel connections to the
+ * remote host we can run at any one time.
+ * MAXCHECK is the maximum number of nntp CHECK commands to be outstanding
+ * on a connection before opening up a new connection (or refusing
+ * new articles if we get to MAXCXNS).
+ * PORTNUM is the port number on the remote host we should talk to.
+ * CLOSEPERIOD is the number of seconds after connecting that the
+ * connections should be closed down and reinitialized (due to problems
+ * with old NNTP servers that hold history files open. Value of 0 means
+ * dont close down.
+ * STREAMING is a boolean flag to tell if the Host wants its Connections to
+ * do streaming or not.
+ * LOWPASSHIGH is the high value for the low-pass filter.
+ * LOWPASSLOW is the low value for the low-pass filter.
+ */
+
+void configHosts (bool talkSelf) ;
+
+/* print some debugging info. */
+void gPrintHostInfo (FILE *fp, unsigned int indentAmt) ;
+void printHostInfo (Host host, FILE *fp, unsigned int indentAmt) ;
+
+/* Delete the host object. Drops all the active connections immediately
+ (i.e. no QUIT) . */
+void delHost (Host host) ;
+
+/* Get a new default host object */
+Host newDefaultHost (InnListener listener,
+ const char *name);
+
+/* gently close down all the host's connections (issue QUITs). */
+void hostClose (Host host) ;
+
+/* gently close down all active connections (issue QUITs) and recreate
+ them immediately */
+void hostFlush (Host host) ;
+
+/* have the HOST transmit the ARTICLE, or, failing that, store article
+ information for later attempts. */
+void hostSendArticle (Host host, Article article) ;
+
+/* return an IP address for the host */
+struct sockaddr *hostIpAddr (Host host, int family) ;
+
+/* Delete all IPv4 addresses from the address list */
+void hostDeleteIpv4Addr (Host host);
+
+/* mark the current IP address as failed and rotate to the next one */
+void hostIpFailed (Host host) ;
+
+/*
+ * Functions used by the Connection to indicate Connection state.
+ */
+
+/* called by the Host's connection when the remote is refusing
+ postings. Code 400 in the banner */
+void hostCxnBlocked (Host host, Connection cxn, char *reason) ;
+
+/* called by the Connection when it has determined if the remote supports
+ the streaming extension or not. */
+void hostRemoteStreams (Host host, Connection cxn, bool doesStream) ;
+
+/* Called by the connection when it is no longer connected to the
+ remote. Perhaps due to getting a code 400 to an IHAVE. */
+void hostCxnDead (Host host, Connection cxn) ;
+
+/* Called when the Connection deletes itself */
+bool hostCxnGone (Host host, Connection cxn) ;
+
+/* Called when the Connection goes to sleep. */
+void hostCxnSleeping (Host host, Connection cxn) ;
+
+/* Called when the Connection starts waiting for articles. */
+void hostCxnWaiting (Host host, Connection cxn) ;
+
+
+
+/* Called when the connection has sent an IHAVE or a CHECK, or a TAKETHIS
+ when in no-check mode.*/
+void hostArticleOffered (Host host, Connection cxn) ;
+
+/* called by the Connection when the article was transferred. */
+void hostArticleAccepted (Host host, Connection cxn, Article article) ;
+
+/* Called by the connection when the remote answered 435 or 438 */
+void hostArticleNotWanted (Host host, Connection cxn, Article article) ;
+
+/* Called by the connection when the remote answered 437 or 439 */
+void hostArticleRejected (Host host, Connection cxn, Article article) ;
+
+/* Called when the connection when the remote answered 400 or 431 or 436 */
+void hostArticleDeferred (Host host, Connection cxn, Article article) ;
+
+/* Called by the connection if it discovers the file is gone. */
+void hostArticleIsMissing (Host host, Connection cxn, Article article) ;
+
+
+/* Called by the connection when it wants to defer articles, but it
+ doesn't want the Host to queue any news on it. */
+void hostTakeBackArticle (Host host, Connection cxn, Article article) ;
+
+
+/* called by the Connection when it is idle and wants to get things
+ moving. Returns true if there was something to do and the Host called
+ cxnQueueArticle() . */
+bool hostGimmeArticle (Host host, Connection cxn) ;
+
+/* get the name that INN uses for this host */
+const char *hostPeerName (Host host) ;
+
+/* get the bindaddress */
+const struct sockaddr_in *hostBindAddr(Host host) ;
+#ifdef HAVE_INET6
+const struct sockaddr_in6 *hostBindAddr6(Host host) ;
+int hostAddrFamily (Host host);
+#endif
+
+/* get the username and password for authentication */
+const char *hostUsername (Host host) ;
+const char *hostPassword (Host host) ;
+
+/* if VAL is true then each time the host logs its stats all its
+ connections will too. */
+void hostLogConnectionStats (bool val) ;
+bool hostLogConnectionStatsP (void) ;
+
+#if 0
+/* Set the frequency (in seconds) with which we log statistics */
+void hostSetStatsPeriod (unsigned int period) ;
+#endif
+
+/* return whether or not the Connections should attempt to stream. */
+bool hostWantsStreaming (Host host) ;
+
+/* return maxChecks */
+unsigned int hostmaxChecks (Host host);
+
+/* return if we should drop deferred articles */
+bool hostDropDeferred (Host host);
+
+/* return the maximum number of CHECKs that can be outstanding */
+unsigned int hostMaxChecks (Host host) ;
+
+/* Called by the Host's connections when they go into (true) or out of
+ (false) no-CHECK mode. */
+void hostLogNoCheckMode (Host host, bool on, double low, double cur, double high) ;
+
+/* calculate host backlog statistics */
+void gCalcHostBlStat (void) ;
+
+/* calculate host global statistics */
+void gHostStats (void) ;
+
+/* set the pathname of the file to use instead of innfeed.status */
+void hostSetStatusFile (const char *filename) ;
+
+/* function called when config file is loaded. */
+int hostConfigLoadCbk (void *data) ;
+
+void hostChkCxns(TimeoutId tid, void *data);
+
+#endif /* host_h__ */
--- /dev/null
+/* $Id: imap_connection.c 7103 2004-12-23 22:36:27Z rra $
+**
+** Feed articles to an IMAP server via LMTP and IMAP.
+**
+** Written by Tim Martin.
+**
+** Instead of feeding articles via nntp to another host this feeds the
+** messages via lmtp to a host and the control messages (cancel's etc..) it
+** performs via IMAP. This means it has 2 active connections at any given
+** time and 2 queues.
+**
+** When an article comes in it is immediatly placed in the lmtp queue. When
+** an article is picked off the lmtp queue for processing first check if it's
+** a control message. If so, place it in the IMAP queue. If not, attempt to
+** deliver via LMTP.
+**
+** This attempts to follow the exact same api as connection.c.
+**
+** TODO:
+**
+** feed to smtp
+** security layers? <--punt on for now
+** authname/password per connection object
+** untagged IMAP messages
+*/
+
+#include "config.h"
+#include "clibrary.h"
+#include "portable/socket.h"
+#include <ctype.h>
+#include <errno.h>
+#include <netdb.h>
+#include <netdb.h>
+#include <time.h>
+#include <syslog.h>
+
+#include "inn/messages.h"
+#include "libinn.h"
+
+#include "buffer.h"
+#include "connection.h"
+#include "endpoint.h"
+#include "host.h"
+#include "innfeed.h"
+#include "article.h"
+#include "configfile.h"
+
+#ifdef HAVE_SASL
+# include <sasl/sasl.h>
+#endif
+
+#ifndef MAXHOSTNAMELEN
+#define MAXHOSTNAMELEN 1024
+#endif
+
+#define IMAP_PORT 143
+
+#ifdef SMTPMODE
+# define LMTP_PORT 25
+#else
+# define LMTP_PORT 2003
+#endif
+
+#define IMAP_TAGLENGTH 6
+
+#define QUEUE_MAX_SIZE 250
+
+#define DOSOMETHING_TIMEOUT 60
+
+
+
+/* external */
+extern char *deliver_username;
+extern char *deliver_authname;
+extern char *deliver_password;
+extern char *deliver_realm;
+extern char *deliver_rcpt_to;
+extern char *deliver_to_header;
+
+
+char hostname[MAXHOSTNAMELEN];
+char *mailfrom_name = NULL; /* default to no return path */
+
+#ifdef HAVE_SASL
+static int initialized_sasl = 0; /* weather sasl_client_init() has been called */
+#endif
+
+/* states the imap connection may be in */
+typedef enum {
+
+ IMAP_DISCONNECTED = 1,
+ IMAP_WAITING,
+
+ IMAP_CONNECTED_NOTAUTH,
+
+ IMAP_READING_INTRO,
+
+ IMAP_WRITING_CAPABILITY,
+ IMAP_READING_CAPABILITY,
+
+ IMAP_WRITING_STARTAUTH,
+ IMAP_READING_STEPAUTH,
+ IMAP_WRITING_STEPAUTH,
+
+ IMAP_IDLE_AUTHED,
+ IMAP_WRITING_NOOP,
+ IMAP_READING_NOOP,
+
+ IMAP_WRITING_CREATE,
+ IMAP_READING_CREATE,
+
+ IMAP_WRITING_DELETE,
+ IMAP_READING_DELETE,
+
+ IMAP_WRITING_SELECT,
+ IMAP_READING_SELECT,
+
+ IMAP_WRITING_SEARCH,
+ IMAP_READING_SEARCH,
+
+ IMAP_WRITING_STORE,
+ IMAP_READING_STORE,
+
+ IMAP_WRITING_CLOSE,
+ IMAP_READING_CLOSE,
+
+ IMAP_WRITING_QUIT,
+ IMAP_READING_QUIT
+
+} imap_state_t;
+
+typedef enum {
+ LMTP_DISCONNECTED = 1,
+ LMTP_WAITING,
+
+ LMTP_CONNECTED_NOTAUTH,
+
+ LMTP_READING_INTRO,
+
+ LMTP_WRITING_LHLO,
+ LMTP_READING_LHLO,
+
+ LMTP_WRITING_STARTAUTH,
+ LMTP_READING_STEPAUTH,
+ LMTP_WRITING_STEPAUTH,
+
+ LMTP_AUTHED_IDLE,
+ LMTP_WRITING_NOOP,
+ LMTP_READING_NOOP,
+
+ LMTP_READING_RSET,
+ LMTP_READING_MAILFROM,
+ LMTP_READING_RCPTTO,
+ LMTP_READING_DATA,
+ LMTP_READING_CONTENTS,
+
+ LMTP_WRITING_UPTODATA,
+ LMTP_WRITING_CONTENTS,
+
+ LMTP_WRITING_QUIT,
+ LMTP_READING_QUIT
+
+} lmtp_state_t;
+
+typedef struct imap_capabilities_s {
+
+ int imap4; /* does server support imap4bis? */
+ int logindisabled; /* does the server allow the login command? */
+
+ char *saslmechs; /* supported SASL mechanisms */
+
+} imap_capabilities_t;
+
+typedef struct lmtp_capabilities_s {
+
+ int Eightbitmime;
+ int EnhancedStatusCodes;
+ int pipelining;
+
+ char *saslmechs;
+
+} lmtp_capabilities_t;
+
+typedef enum {
+ STAT_CONT = 0,
+ STAT_NO = 1,
+ STAT_OK = 2,
+ STAT_FAIL = 3
+} imt_stat;
+
+/* Message types */
+typedef enum {
+ DELIVER,
+ CREATE_FOLDER,
+ CANCEL_MSG,
+ DELETE_FOLDER
+} control_type_t;
+
+typedef struct control_item_s {
+
+ Article article;
+ char *folder;
+ char *msgid; /* only for cancel's */
+ unsigned long uid; /* only for cancel's */
+
+} control_item_t;
+
+typedef struct article_queue_s {
+
+ control_type_t type;
+
+ time_t arrived;
+ time_t nextsend; /* time we should next try to send article */
+
+ int trys;
+
+ int counts_toward_size;
+
+ union {
+ Article article;
+ control_item_t *control;
+ void *generic;
+ } data;
+
+ struct article_queue_s *next;
+
+} article_queue_t;
+
+typedef struct Q_s {
+
+ article_queue_t *head;
+
+ article_queue_t *tail;
+
+ int size;
+
+} Q_t;
+
+typedef struct connection_s {
+
+ /* common stuff */
+ char *ServerName;
+
+ char *lmtp_respBuffer; /* buffer all responses are read into */
+ Buffer lmtp_rBuffer; /* buffer all responses are read into */
+
+ Host myHost ; /* the host who owns the connection */
+
+ time_t timeCon ; /* the time the connect happened
+ (last auth succeeded) */
+
+ int issue_quit; /* Three states:
+ * 0 - don't do anything
+ * 1 - after issue quit enter wait state
+ * 2 - after issue quit reconnect
+ * 3 - after issue quit delete connection
+ * 4 - nuke cxn
+ */
+
+ /* Statistics */
+ int lmtp_succeeded;
+ int lmtp_failed;
+
+ int cancel_succeeded;
+ int cancel_failed;
+
+ int create_succeeded;
+ int create_failed;
+
+ int remove_succeeded;
+ int remove_failed;
+
+
+ /* LMTP stuff */
+ int lmtp_port;
+ lmtp_state_t lmtp_state;
+#ifdef HAVE_SASL
+ sasl_conn_t *saslconn_lmtp;
+#endif /* HAVE_SASL */
+ int sockfd_lmtp;
+
+ time_t lmtp_timeCon ;
+
+ EndPoint lmtp_endpoint;
+ unsigned int ident ; /* an identifier for syslogging. */
+
+ lmtp_capabilities_t *lmtp_capabilities;
+
+ int lmtp_disconnects;
+ char *lmtp_tofree_str;
+
+ article_queue_t *current_article;
+ Buffer *current_bufs;
+ int current_rcpts_issued;
+ int current_rcpts_okayed;
+
+ /* Timer for the max amount of time to wait for a response from the
+ remote */
+ unsigned int lmtp_readTimeout ;
+ TimeoutId lmtp_readBlockedTimerId ;
+
+ /* Timer for the max amount of time to wait for a any amount of data
+ to be written to the remote */
+ unsigned int lmtp_writeTimeout ;
+ TimeoutId lmtp_writeBlockedTimerId ;
+
+ /* Timer for the number of seconds to sleep before attempting a
+ reconnect. */
+ unsigned int lmtp_sleepTimeout ;
+ TimeoutId lmtp_sleepTimerId ;
+
+ /* Timer for max amount between queueing some articles and trying to send them */
+ unsigned int dosomethingTimeout ;
+ TimeoutId dosomethingTimerId ;
+
+ Q_t lmtp_todeliver_q;
+
+
+
+ /* IMAP stuff */
+ int imap_port;
+#ifdef HAVE_SASL
+ sasl_conn_t *imap_saslconn;
+#endif /* HAVE_SASL */
+
+ char *imap_respBuffer;
+ Buffer imap_rBuffer;
+ EndPoint imap_endpoint;
+
+ imap_capabilities_t *imap_capabilities;
+
+ int imap_sockfd;
+
+ time_t imap_timeCon ;
+
+ imap_state_t imap_state;
+ int imap_disconnects;
+ char *imap_tofree_str;
+
+ char imap_currentTag[IMAP_TAGLENGTH];
+ int imap_tag_num;
+
+ /* Timer for the max amount of time to wait for a response from the
+ remote */
+ unsigned int imap_readTimeout ;
+ TimeoutId imap_readBlockedTimerId ;
+
+ /* Timer for the max amount of time to wait for a any amount of data
+ to be written to the remote */
+ unsigned int imap_writeTimeout ;
+ TimeoutId imap_writeBlockedTimerId ;
+
+ /* Timer for the number of seconds to sleep before attempting a
+ reconnect. */
+ unsigned int imap_sleepTimeout ;
+ TimeoutId imap_sleepTimerId ;
+
+ Q_t imap_controlMsg_q;
+
+ article_queue_t *current_control;
+
+ struct connection_s *next;
+
+} connection_t;
+
+static Connection gCxnList = NULL ;
+static unsigned int gCxnCount= 0 ;
+static unsigned int max_reconnect_period = MAX_RECON_PER ;
+static unsigned int init_reconnect_period = INIT_RECON_PER;
+
+typedef enum {
+ RET_OK = 0,
+ RET_FAIL = 1,
+ RET_QUEUE_EMPTY,
+ RET_EXCEEDS_SIZE,
+ RET_NO_FULLLINE,
+ RET_NO,
+ RET_ARTICLE_BAD
+} conn_ret;
+
+
+/********** Private Function Declarations *************/
+
+static void lmtp_readCB (EndPoint e, IoStatus i, Buffer *b, void *d);
+static void imap_readCB (EndPoint e, IoStatus i, Buffer *b, void *d);
+static void imap_writeCB (EndPoint e, IoStatus i, Buffer *b, void *d);
+static void lmtp_writeCB (EndPoint e, IoStatus i, Buffer *b, void *d);
+
+static conn_ret lmtp_Connect(connection_t *cxn);
+static conn_ret imap_Connect(connection_t *cxn);
+
+static void prepareReopenCbk (Connection cxn, int type);
+
+static void lmtp_readTimeoutCbk (TimeoutId id, void *data);
+static void imap_readTimeoutCbk (TimeoutId id, void *data);
+
+static void dosomethingTimeoutCbk (TimeoutId id, void *data);
+
+static conn_ret WriteToWire_imapstr(connection_t *cxn, char *str, int slen);
+static conn_ret WriteToWire_lmtpstr(connection_t *cxn, char *str, int slen);
+
+static conn_ret WriteToWire(connection_t *cxn, EndpRWCB callback,
+ EndPoint endp, Buffer *array);
+static void lmtp_sendmessage(connection_t *cxn, Article justadded);
+static void imap_ProcessQueue(connection_t *cxn);
+
+static conn_ret FindHeader(Buffer *bufs, const char *header, char **start, char **end);
+static conn_ret PopFromQueue(Q_t *q, article_queue_t **item);
+
+enum failure_type {
+ MSG_SUCCESS = 0,
+ MSG_FAIL_DELIVER = 1,
+ MSG_GIVE_BACK = 2,
+ MSG_MISSING = 3
+};
+
+static void QueueForgetAbout(connection_t *cxn, article_queue_t *item,
+ enum failure_type failed);
+
+static void delConnection (Connection cxn);
+static void DeleteIfDisconnected(Connection cxn);
+static void DeferAllArticles(connection_t *cxn, Q_t *q);
+
+static void lmtp_Disconnect(connection_t *cxn);
+static void imap_Disconnect(connection_t *cxn);
+static conn_ret imap_listenintro(connection_t *cxn);
+
+static void imap_writeTimeoutCbk (TimeoutId id, void *data);
+static void lmtp_writeTimeoutCbk (TimeoutId id, void *data);
+
+/******************** PRIVATE FUNCTIONS ***************************/
+
+static const char *imap_stateToString(int state)
+{
+ switch (state)
+ {
+ case IMAP_DISCONNECTED: return "disconnected";
+ case IMAP_WAITING: return "waiting";
+ case IMAP_CONNECTED_NOTAUTH: return "connected (unauthenticated)";
+ case IMAP_READING_INTRO: return "reading intro";
+ case IMAP_WRITING_CAPABILITY: return "writing CAPABILITY";
+ case IMAP_READING_CAPABILITY: return "reading CAPABILITY";
+ case IMAP_WRITING_STARTAUTH: return "writing AUTHENTICATE";
+ case IMAP_READING_STEPAUTH: return "reading stepauth";
+ case IMAP_WRITING_STEPAUTH: return "writing stepauth";
+ case IMAP_IDLE_AUTHED: return "idle (authenticated)";
+ case IMAP_WRITING_NOOP: return "writing NOOP";
+ case IMAP_READING_NOOP: return "reading NOOP response";
+ case IMAP_WRITING_CREATE: return "writing CREATE";
+ case IMAP_READING_CREATE: return "reading CREATE response";
+ case IMAP_WRITING_DELETE: return "writing DELETE command";
+ case IMAP_READING_DELETE: return "reading DELETE response";
+ case IMAP_WRITING_SELECT: return "writing SELECT";
+ case IMAP_READING_SELECT: return "reading SELECT response";
+ case IMAP_WRITING_SEARCH: return "writing SEARCH";
+ case IMAP_READING_SEARCH: return "reading SEARCH response";
+ case IMAP_WRITING_STORE: return "writing STORE";
+ case IMAP_READING_STORE: return "reading STORE response";
+ case IMAP_WRITING_CLOSE: return "writing CLOSE";
+ case IMAP_READING_CLOSE: return "reading CLOSE response";
+ case IMAP_WRITING_QUIT: return "writing LOGOUT";
+ case IMAP_READING_QUIT: return "reading LOGOUT response";
+ default: return "Unknown state";
+ }
+}
+
+static const char *lmtp_stateToString(int state)
+{
+ switch(state)
+ {
+ case LMTP_DISCONNECTED: return "disconnected";
+ case LMTP_WAITING: return "waiting";
+ case LMTP_CONNECTED_NOTAUTH: return "connected (unauthenticated)";
+ case LMTP_READING_INTRO: return "reading intro";
+ case LMTP_WRITING_LHLO: return "writing LHLO";
+ case LMTP_READING_LHLO: return "reading LHLO response";
+ case LMTP_WRITING_STARTAUTH: return "writing AUTH";
+ case LMTP_READING_STEPAUTH: return "reading stepauth";
+ case LMTP_WRITING_STEPAUTH: return "writing stepauth";
+ case LMTP_AUTHED_IDLE: return "idle (authenticated)";
+ case LMTP_WRITING_NOOP: return "writing NOOP";
+ case LMTP_READING_NOOP: return "reading NOOP response";
+ case LMTP_READING_RSET: return "reading RSET response";
+ case LMTP_READING_MAILFROM: return "reading MAIL FROM response";
+ case LMTP_READING_RCPTTO: return "reading RCPT TO response";
+ case LMTP_READING_DATA: return "reading DATA response";
+ case LMTP_READING_CONTENTS: return "reading contents response";
+ case LMTP_WRITING_UPTODATA:
+ return "writing RSET, MAIL FROM, RCPT TO, DATA commands";
+ case LMTP_WRITING_CONTENTS: return "writing contents of message";
+ case LMTP_WRITING_QUIT: return "writing QUIT";
+ case LMTP_READING_QUIT: return "reading QUIT";
+ default: return "unknown state";
+ }
+}
+
+/******************************* Queue functions ***********************************/
+
+/*
+ * Add a message to a generic queue
+ *
+ * q - the queue adding to
+ * item - the data to add to the queue
+ * type - the type of item it is (i.e. cancel,lmtp,etc..)
+ * addsmsg - weather this should be counted toward the queue size
+ * this is for control msg's that create multiple queue items.
+ * For example a cancel message canceling a message in multiple
+ * newsgroups will create >1 queue item but we only want it to count
+ * once towards the queue
+ * must - wheather we must take it even though it may put us over our max size
+ */
+
+static conn_ret AddToQueue(Q_t *q, void *item,
+ control_type_t type, int addsmsg, bool must)
+{
+ article_queue_t *newentry;
+
+ if (must == false)
+ {
+ if (q->size >= QUEUE_MAX_SIZE)
+ {
+ return RET_EXCEEDS_SIZE;
+ }
+ } else {
+ if (q->size >= QUEUE_MAX_SIZE * 10)
+ {
+ d_printf(0, "Queue has grown way too much. Dropping article\n");
+ return RET_FAIL;
+ }
+ }
+
+ /* add to the end of our queue */
+ newentry = xmalloc(sizeof(article_queue_t));
+
+ newentry->type = type;
+
+ /* send as soon as possible */
+ newentry->nextsend = newentry->arrived = time(NULL);
+
+ newentry->trys = 0;
+
+ newentry->data.generic = item;
+ newentry->next = NULL;
+ newentry->counts_toward_size = addsmsg;
+
+ /* add to end of queue */
+ if (q->tail == NULL)
+ {
+ q->head = newentry;
+ q->tail = newentry;
+ } else {
+
+ q->tail->next = newentry;
+ q->tail = newentry;
+ }
+
+ q->size+=addsmsg;
+
+ return RET_OK;
+}
+
+/*
+ * Pop an item from the queue
+ *
+ * q - the queue to pop from
+ * item - where the item shall be placed upon sucess
+ *
+ */
+
+static conn_ret PopFromQueue(Q_t *q, article_queue_t **item)
+{
+ /* if queue empty return error */
+ if ( q->head == NULL)
+ {
+ return RET_QUEUE_EMPTY;
+ }
+
+ /* set what we return */
+ *item = q->head;
+
+ q->head = q->head->next;
+ if (q->head == NULL) q->tail = NULL;
+
+ q->size-=(*item)->counts_toward_size;
+
+ return RET_OK;
+}
+
+/*
+ * ReQueue an item. Will either put it back in the queue for another try
+ * or forget about it
+ *
+ * cxn - our connection object (needed so forget about things)
+ * q - the queue to requeue to
+ * entry - the item to put back
+ */
+
+static void ReQueue(connection_t *cxn, Q_t *q, article_queue_t *entry)
+{
+ /* look at the time it's been here */
+ entry->nextsend = time(NULL) + (entry->trys *30); /* xxx better formula? */
+
+ entry->trys++;
+
+ /* give up after 5 tries xxx configurable??? */
+ if (entry->trys >= 5)
+ {
+ QueueForgetAbout(cxn, entry, MSG_FAIL_DELIVER);
+ return;
+ }
+
+
+ /* ok let's add back to the end of the queue */
+ entry->next = NULL;
+
+ /* add to end of queue */
+ if (q->tail == NULL)
+ {
+ q->head = entry;
+ q->tail = entry;
+ } else {
+ q->tail->next = entry;
+ q->tail = entry;
+ }
+
+ q->size+=entry->counts_toward_size;
+}
+
+
+
+/*
+ * Forget about an item. Tells host object if we succeeded/failed/etc with the message
+ *
+ * cxn - connection object
+ * item - item
+ * failed - type of failure (see below)
+ *
+ * failed:
+ * 0 - succeeded delivering message
+ * 1 - failed delivering message
+ * 2 - Try to give back to host
+ * 3 - Article missing (i.e. can't find on disk)
+ */
+static void QueueForgetAbout(connection_t *cxn, article_queue_t *item,
+ enum failure_type failed)
+{
+ Article art = NULL;
+
+ switch (item->type)
+ {
+ case DELIVER:
+ if (failed>0)
+ cxn->lmtp_failed++;
+ art = item->data.article;
+ break;
+
+ case CANCEL_MSG:
+ if (failed>0)
+ cxn->cancel_failed++;
+ free(item->data.control->msgid);
+ free(item->data.control->folder);
+
+ if (item->counts_toward_size == 1)
+ art = item->data.control->article;
+
+ free(item->data.control );
+ break;
+
+ case CREATE_FOLDER:
+ if (failed>0)
+ cxn->create_failed++;
+ free(item->data.control->folder);
+
+ art = item->data.control->article;
+
+ free(item->data.control );
+ break;
+
+ case DELETE_FOLDER:
+ if (failed>0)
+ cxn->remove_failed++;
+ free(item->data.control->folder);
+
+ art = item->data.control->article;
+
+ free(item->data.control );
+ break;
+
+ default:
+ d_printf(0, "%s:%d QueueForgetAbout(): "
+ "Unknown type to forget about\n",
+ hostPeerName (cxn->myHost), cxn->ident);
+ break;
+ }
+
+ if (art!=NULL) {
+ switch (failed) {
+ case MSG_SUCCESS:
+ hostArticleAccepted (cxn->myHost, cxn, art);
+ break;
+
+ case MSG_FAIL_DELIVER:
+ hostArticleRejected (cxn->myHost, cxn, art);
+ break;
+
+ case MSG_GIVE_BACK:
+ hostTakeBackArticle (cxn->myHost, cxn, art);
+ break;
+
+ case MSG_MISSING:
+ hostArticleIsMissing(cxn->myHost, cxn, art);
+ break;
+ default:
+ d_printf(0,"%s:%d QueueForgetAbout(): failure type unknown\n",
+ hostPeerName (cxn->myHost), cxn->ident);
+ break;
+ }
+ }
+
+ free(item);
+}
+
+/*
+ * How much space is available in the queue
+ */
+
+static int QueueSpace(Q_t *q)
+{
+ int ret = QUEUE_MAX_SIZE - q->size;
+ if (ret < 0) ret = 0;
+ return ret;
+}
+
+/*
+ * How many items are in the queue
+ */
+
+static int QueueItems(Q_t *q)
+{
+ return q->size;
+}
+
+
+/***************************** END Queue functions ***********************************/
+
+/***************************** Generic Parse Functions *******************************/
+
+/* returns the end of the header */
+
+static char *GetUntil(char *str)
+{
+ while (((*str) != '\0') && ( (*str) != '\r') && ( (*str) != '\n'))
+ {
+ str++;
+ }
+
+ return str;
+}
+
+static char *GotoNextLine(char *str)
+{
+ while (((*str) != '\0') && ( (*str) != '\r') && ( (*str) != '\n'))
+ {
+ str++;
+ }
+
+ if (*str == '\r') str++;
+ if (*str == '\n') str++;
+
+ return str;
+}
+
+/*
+ * Finds the given header in the message
+ * Returns NULL if not found
+ */
+static conn_ret FindHeader(Buffer *bufs, const char *header, char **start,
+ char **end)
+{
+ Buffer b;
+ int size;
+ char *str_base;
+ char *str;
+ int headerlen = strlen(header);
+
+ if (bufs==NULL)
+ {
+ if (start)
+ *start=NULL;
+ return RET_ARTICLE_BAD;
+ }
+
+ b = bufs[0];
+ size = bufferSize(b);
+ str_base = bufferBase(b);
+ str = str_base;
+
+ while ((str - str_base) < size - headerlen)
+ {
+ if (*str == header[0])
+ {
+ if ((strncasecmp(header, str, headerlen)==0) && ( *(str + headerlen)==':'))
+ {
+
+ if (start)
+ {
+ *start = str+headerlen+1;
+
+ /* get rid of leading whitespace */
+ while ( isspace((int) **start))
+ (*start)++;
+ }
+
+ if (end)
+ *end = GetUntil(str+headerlen+1);
+
+ return RET_OK;
+ }
+ } else if (*str == '\n') {
+ /* end of headers */
+ return RET_NO;
+ }
+ str = GotoNextLine(str);
+ }
+
+ return RET_NO;
+}
+
+static conn_ret GetLine(char *buf, char *ret, int retmaxsize)
+{
+ char *str_base;
+ char *str;
+
+ int size = strlen(buf);
+ str_base = buf;
+ str = str_base;
+
+ while ( (*str) != '\0')
+ {
+ if ((*str) == '\n')
+ {
+ if (str-str_base > retmaxsize)
+ {
+ d_printf(0, "Max size exceeded! %s\n",str_base);
+ return RET_FAIL;
+ }
+
+ /* fill in the return string */
+ memcpy(ret, str_base, str-str_base);
+ ret[ str - str_base -1] = '\0';
+
+ memcpy( str_base, str_base + (str-str_base)+1, size - (str-str_base));
+ str_base[size - (str-str_base)]='\0';
+
+ return RET_OK;
+ }
+
+ str++;
+ }
+
+ /* couldn't find a full line */
+ return RET_NO_FULLLINE;
+}
+
+
+
+/************************** END Generic Parse Functions *******************************/
+
+/************************ Writing to Network functions *****************/
+
+static conn_ret WriteToWire(connection_t *cxn, EndpRWCB callback,
+ EndPoint endp, Buffer *array)
+{
+
+ if (array == NULL) return RET_FAIL;
+
+ prepareWrite (endp,
+ array,
+ NULL,
+ callback,
+ cxn);
+
+ return RET_OK;
+}
+
+static conn_ret WriteToWire_str(connection_t *cxn, EndpRWCB callback,
+ EndPoint endp, char *str, int slen)
+{
+ conn_ret result;
+ Buffer buff;
+ Buffer *writeArr;
+
+ if (slen==-1) slen = strlen(str);
+
+ buff = newBufferByCharP(str, slen+1, slen);
+ ASSERT (buff != NULL);
+
+ writeArr = makeBufferArray (buff, NULL) ;
+
+ result = WriteToWire(cxn, callback, endp, writeArr);
+
+ return result;
+}
+
+static conn_ret WriteToWire_imapstr(connection_t *cxn, char *str, int slen)
+{
+ /* prepare the timeouts */
+ clearTimer (cxn->imap_readBlockedTimerId) ;
+
+ /* set up the write timer. */
+ clearTimer (cxn->imap_writeBlockedTimerId) ;
+
+ if (cxn->imap_writeTimeout > 0)
+ cxn->imap_writeBlockedTimerId = prepareSleep (imap_writeTimeoutCbk, cxn->imap_writeTimeout,
+ cxn);
+ cxn->imap_tofree_str = str;
+ return WriteToWire_str(cxn, imap_writeCB, cxn->imap_endpoint, str, slen);
+}
+
+static conn_ret WriteToWire_lmtpstr(connection_t *cxn, char *str, int slen)
+{
+ /* prepare the timeouts */
+ clearTimer (cxn->lmtp_readBlockedTimerId) ;
+
+ /* set up the write timer. */
+ clearTimer (cxn->lmtp_writeBlockedTimerId) ;
+
+ if (cxn->lmtp_writeTimeout > 0)
+ cxn->lmtp_writeBlockedTimerId = prepareSleep (lmtp_writeTimeoutCbk, cxn->lmtp_writeTimeout,
+ cxn) ;
+
+
+
+ cxn->lmtp_tofree_str = str;
+ return WriteToWire_str(cxn, lmtp_writeCB, cxn->lmtp_endpoint, str, slen);
+}
+
+static conn_ret WriteArticle(connection_t *cxn, Buffer *array)
+{
+ int array_len = bufferArrayLen (array);
+ int lup=0;
+ int result;
+
+ for (lup=0;lup<array_len;lup++)
+ {
+ int current_size;
+ Buffer current_buf;
+ char *current_start;
+
+ current_buf = array[lup];
+
+ current_size = bufferDataSize( current_buf );
+
+ current_start = bufferBase( current_buf );
+
+ }
+
+ /* just call writetowire since it's easy */
+ result = WriteToWire(cxn, lmtp_writeCB, cxn->lmtp_endpoint, array);
+
+ if (result!=RET_OK)
+ {
+ return result;
+ }
+
+ cxn->lmtp_state = LMTP_WRITING_CONTENTS;
+
+ return RET_OK;
+}
+
+/************************ END Writing to Network functions *****************/
+
+
+
+/*
+ * Adds a cancel item to the control queue
+ * Cancel item to delete message with <msgid> in <folder>
+ *
+ * cxn - connection object
+ * folder - pointer to start of folder string (this is a pointer into the actual message buffer)
+ * folderlen - length of folder string
+ * msgid - pointer to start of msgid string (this is a pointer into the actual message buffer)
+ * msgidlen - length of msgid string
+ * art - the article for this control message (NULL if this cancel object lacks one)
+ * must - if must be accepted into queue
+ */
+
+static conn_ret addCancelItem(connection_t *cxn,
+ char *folder, int folderlen,
+ char *msgid, int msgidlen,
+ Article art, int must)
+{
+ control_item_t *item;
+ conn_ret result;
+ int i;
+
+ ASSERT(folder); ASSERT(msgid); ASSERT(cxn);
+
+ /* sanity check folder, msgid */
+ for (i = 0; i < folderlen; i++) ASSERT(!isspace((int) folder[i]));
+ for (i = 0; i < msgidlen; i++) ASSERT(!isspace((int) msgid[i]));
+
+ /* create the object */
+ item = xcalloc (1, sizeof(control_item_t));
+
+ item->folder = xcalloc(folderlen+1, 1);
+ memcpy(item->folder, folder, folderlen);
+ item->folder[folderlen] = '\0';
+
+ item->msgid = xcalloc (msgidlen+1, 1);
+ memcpy(item->msgid, msgid, msgidlen);
+ item->msgid[msgidlen] = '\0';
+
+ item->article = art;
+
+ /* try to add to the queue (counts if art isn't null) */
+ result = AddToQueue(&(cxn->imap_controlMsg_q), item, CANCEL_MSG, (art != NULL), must);
+ if (result != RET_OK) {
+ d_printf(1,"%s:%d addCancelItem(): "
+ "I thought we had in space in [imap] queue "
+ "but apparently not\n",
+ hostPeerName (cxn->myHost), cxn->ident);
+
+ /* cleanup */
+ free(item->folder);
+ free(item->msgid);
+ free(item);
+
+ return result;
+ }
+
+ return RET_OK;
+}
+
+static conn_ret AddControlMsg(connection_t *cxn,
+ Article art,
+ Buffer *bufs,
+ char *control_header,
+ char *control_header_end,
+ bool must)
+{
+ char *rcpt_list = NULL, *rcpt_list_end;
+ control_item_t *item;
+ conn_ret res = RET_OK;
+ int t;
+
+ /* make sure contents ok; this also should load it into memory */
+ if (!artContentsOk (art)) {
+ d_printf(0, "%s:%d AddControlMsg(): "
+ "artContentsOk() said article was bad\n",
+ hostPeerName (cxn->myHost), cxn->ident);
+ hostArticleIsMissing (cxn->myHost, cxn, art);
+ return RET_FAIL;
+ }
+
+ res = RET_OK;
+ /* now let's look at the control to see what it is */
+ if (!strncasecmp(control_header,"newgroup",8)) {
+ control_header += 8;
+ t = CREATE_FOLDER;
+ } else if (!strncasecmp(control_header,"rmgroup",7)) {
+ /* jump past "rmgroup" */
+ control_header += 7;
+ t = DELETE_FOLDER;
+ } else if (!strncasecmp(control_header,"cancel",6)) {
+ t = CANCEL_MSG;
+ control_header += 6;
+ } else {
+ /* unrecognized type */
+ char tmp[100];
+ char *endstr;
+ size_t clen;
+
+ endstr = strchr(control_header,'\n');
+ clen = endstr - control_header;
+
+ if (clen > sizeof(tmp)-1) clen = sizeof(tmp)-1;
+
+ memcpy(tmp,control_header, clen);
+ tmp[clen]='\0';
+
+ d_printf(0,"%s:%d Don't understand control header [%s]\n",
+ hostPeerName (cxn->myHost), cxn->ident,tmp);
+ return RET_FAIL;
+ }
+
+ switch (t) {
+ case CREATE_FOLDER:
+ case DELETE_FOLDER:
+ {
+ int folderlen;
+
+ /* go past all white space */
+ while ((*control_header == ' ') &&
+ (control_header != control_header_end)) {
+ control_header++;
+ }
+
+ /* trim trailing whitespace */
+ while (control_header_end[-1] == ' ') {
+ control_header_end--;
+ }
+
+ if (control_header >= control_header_end) {
+ d_printf(0,"%s:%d addControlMsg(): "
+ "newgroup/rmgroup header has no group specified\n",
+ hostPeerName (cxn->myHost), cxn->ident);
+ return RET_FAIL;
+ }
+
+ folderlen = control_header_end - control_header;
+
+ item = xcalloc(1, sizeof(control_item_t));
+
+ item->folder = xcalloc(folderlen + 1, 1);
+ memcpy(item->folder, control_header, folderlen);
+ item->folder[folderlen] = '\0';
+
+ item->article = art;
+
+ if (AddToQueue(&(cxn->imap_controlMsg_q), item, t, 1, must) != RET_OK) {
+ d_printf(1,"%s:%d addCancelItem(): "
+ "I thought we had in space in [imap] queue"
+ " but apparently not\n",
+ hostPeerName (cxn->myHost), cxn->ident);
+ free(item->folder);
+ free(item);
+ return RET_FAIL;
+ }
+
+ break;
+ }
+
+ case CANCEL_MSG:
+ {
+ char *str, *laststart;
+
+ while (((*control_header) == ' ') &&
+ (control_header != control_header_end))
+ {
+ control_header++;
+ }
+
+ if (control_header == control_header_end)
+ {
+ d_printf(0, "%s:%d Control header contains cancel "
+ "with no msgid specified\n",
+ hostPeerName (cxn->myHost), cxn->ident);
+ return RET_FAIL;
+ }
+
+ if (FindHeader(bufs, "Newsgroups", &rcpt_list, &rcpt_list_end)!=RET_OK)
+ {
+ d_printf(0,"%s:%d Cancel msg contains no newsgroups header\n",
+ hostPeerName (cxn->myHost), cxn->ident);
+ return RET_FAIL;
+ }
+
+ str = rcpt_list;
+ laststart = rcpt_list;
+
+ while (str != rcpt_list_end)
+ {
+ if (*str == ',') {
+ /* eliminate leading whitespace */
+ while (((*laststart) ==' ') || ((*laststart)=='\t'))
+ {
+ laststart++;
+ }
+
+ res = addCancelItem(cxn, laststart,
+ str - laststart,
+ control_header,
+ control_header_end - control_header,
+ NULL, must);
+ if (res!=RET_OK) return res;
+
+ laststart = str+1;
+ }
+
+ str++;
+ }
+
+ if (laststart<str)
+ {
+
+ res = addCancelItem(cxn, laststart, str - laststart,
+ control_header,
+ control_header_end - control_header,
+ art, must);
+ if (res!=RET_OK) return res;
+ }
+ break;
+ }
+ default:
+ /* huh?!? */
+ d_printf(0, "%s:%d internal error in addControlMsg()\n",
+ hostPeerName (cxn->myHost), cxn->ident);
+ }
+ return RET_FAIL;
+}
+
+/*
+ * Show msg handling statistics
+ */
+
+static void show_stats(connection_t *cxn)
+{
+ d_printf(0, "%s:%d\n",hostPeerName (cxn->myHost), cxn->ident);
+ d_printf(0, " imap queue = %d lmtp queue = %d\n",
+ QueueItems(&(cxn->imap_controlMsg_q)),
+ QueueItems(&(cxn->lmtp_todeliver_q)));
+ d_printf(0," imap state = %s\n", imap_stateToString(cxn->imap_state));
+ d_printf(0," lmtp state = %s\n", lmtp_stateToString(cxn->lmtp_state));
+ d_printf(0," delivered: yes: %d no: %d\n",
+ cxn->lmtp_succeeded,
+ cxn->lmtp_failed);
+ d_printf(0," cancel: yes: %d no: %d\n",
+ cxn->cancel_succeeded, cxn->cancel_failed);
+ d_printf(0," create: yes: %d no: %d\n",
+ cxn->create_succeeded, cxn->create_failed);
+ d_printf(0," remove: yes: %d no: %d\n",
+ cxn->remove_succeeded, cxn->remove_failed);
+}
+
+/**************************** SASL helper functions ******************************/
+
+#ifdef HAVE_SASL
+/* callback to get userid or authid */
+static int getsimple(void *context __attribute__((unused)),
+ int id,
+ const char **result,
+ unsigned *len)
+{
+ char *username;
+ char *authid;
+
+ if (! result)
+ return SASL_BADPARAM;
+
+
+ switch (id) {
+ case SASL_CB_GETREALM:
+ *result = deliver_realm;
+ if (len)
+ *len = deliver_realm ? strlen(deliver_realm) : 0;
+ break;
+
+ case SASL_CB_USER:
+ *result = deliver_username;
+ if (len)
+ *len = deliver_username ? strlen(deliver_username) : 0;
+ break;
+ case SASL_CB_AUTHNAME:
+ authid=deliver_authname;
+ *result = authid;
+ if (len)
+ *len = authid ? strlen(authid) : 0;
+ break;
+ case SASL_CB_LANGUAGE:
+ *result = NULL;
+ if (len)
+ *len = 0;
+ break;
+ default:
+ return SASL_BADPARAM;
+ }
+ return SASL_OK;
+}
+
+/* callback to get password */
+static int
+getsecret(sasl_conn_t *conn,
+ void *context __attribute__((unused)),
+ int id,
+ sasl_secret_t **psecret)
+{
+ size_t passlen;
+
+ if (! conn || ! psecret || id != SASL_CB_PASS)
+ return SASL_BADPARAM;
+
+ if (deliver_password==NULL)
+ {
+ d_printf(0,"SASL requested a password but I don't have one\n");
+ return SASL_FAIL;
+ }
+
+ passlen = strlen(deliver_password);
+ *psecret = xmalloc(sizeof(sasl_secret_t) + passlen + 1);
+ if (! *psecret)
+ return SASL_FAIL;
+
+ strlcpy((*psecret)->data, deliver_password, passlen + 1);
+ (*psecret)->len = passlen;
+
+ return SASL_OK;
+}
+
+
+/* callbacks we support */
+static sasl_callback_t saslcallbacks[] = {
+ {
+ SASL_CB_GETREALM, &getsimple, NULL
+ }, {
+ SASL_CB_USER, &getsimple, NULL
+ }, {
+ SASL_CB_AUTHNAME, &getsimple, NULL
+ }, {
+ SASL_CB_PASS, &getsecret, NULL
+ }, {
+ SASL_CB_LIST_END, NULL, NULL
+ }
+};
+
+static sasl_security_properties_t *make_secprops(int min,int max)
+{
+ sasl_security_properties_t *ret=
+ xmalloc(sizeof(sasl_security_properties_t));
+
+ ret->maxbufsize=1024;
+ ret->min_ssf=min;
+ ret->max_ssf=max;
+
+ ret->security_flags=0;
+ ret->property_names=NULL;
+ ret->property_values=NULL;
+
+ return ret;
+}
+
+#ifndef NI_WITHSCOPEID
+#define NI_WITHSCOPEID 0
+#endif
+#ifndef NI_MAXHOST
+#define NI_MAXHOST 1025
+#endif
+#ifndef NI_MAXSERV
+#define NI_MAXSERV 32
+#endif
+
+static int iptostring(const struct sockaddr *addr, socklen_t addrlen,
+ char *out, unsigned outlen) {
+ char hbuf[NI_MAXHOST], pbuf[NI_MAXSERV];
+
+ if(!addr || !out) return SASL_BADPARAM;
+
+ getnameinfo(addr, addrlen, hbuf, sizeof(hbuf), pbuf, sizeof(pbuf),
+ NI_NUMERICHOST | NI_WITHSCOPEID | NI_NUMERICSERV);
+
+ if(outlen < strlen(hbuf) + strlen(pbuf) + 2)
+ return SASL_BUFOVER;
+
+ snprintf(out, outlen, "%s;%s", hbuf, pbuf);
+
+ return SASL_OK;
+}
+
+static conn_ret SetSASLProperties(sasl_conn_t *conn, int sock, int minssf, int maxssf)
+{
+ int saslresult;
+ sasl_security_properties_t *secprops=NULL;
+ int addrsize=sizeof(struct sockaddr_in);
+ char localip[60], remoteip[60];
+ struct sockaddr_in saddr_l;
+ struct sockaddr_in saddr_r;
+
+ /* create a security structure and give it to sasl */
+ secprops = make_secprops(minssf, maxssf);
+ if (secprops != NULL)
+ {
+ sasl_setprop(conn, SASL_SEC_PROPS, secprops);
+ free(secprops);
+ }
+
+ if (getpeername(sock,(struct sockaddr *)&saddr_r,&addrsize)!=0)
+ return RET_FAIL;
+
+ if (iptostring((struct sockaddr *)&saddr_r, sizeof(struct sockaddr_in),
+ remoteip, sizeof(remoteip)))
+ return RET_FAIL;
+
+ if (sasl_setprop(conn, SASL_IPREMOTEPORT, remoteip)!=SASL_OK)
+ return RET_FAIL;
+
+ addrsize=sizeof(struct sockaddr_in);
+ if (getsockname(sock,(struct sockaddr *) &saddr_l,&addrsize)!=0)
+ return RET_FAIL;
+
+ if (iptostring((struct sockaddr *)&saddr_l, sizeof(struct sockaddr_in),
+ localip, sizeof(localip)))
+ return RET_FAIL;
+
+ if (sasl_setprop(conn, SASL_IPLOCALPORT, localip)!=SASL_OK)
+ return RET_FAIL;
+
+ return RET_OK;
+}
+#endif /* HAVE_SASL */
+
+/************************** END SASL helper functions ******************************/
+
+/************************* Startup functions **********************************/
+
+static conn_ret Initialize(connection_t *cxn, int respTimeout)
+{
+#ifdef HAVE_SASL
+ conn_ret saslresult;
+#endif /* HAVE_SASL */
+
+ d_printf(1,"%s:%d initializing....\n",
+ hostPeerName (cxn->myHost), cxn->ident);
+
+#ifdef HAVE_SASL
+ /* only call sasl_client_init() once */
+ if (initialized_sasl == 0)
+ {
+ /* Initialize SASL */
+ saslresult=sasl_client_init(saslcallbacks);
+
+ if (saslresult!=SASL_OK)
+ {
+ d_printf(0,
+ "%s:%d Error initializing SASL (sasl_client_init) (%s)\n",
+ hostPeerName (cxn->myHost), cxn->ident,
+ sasl_errstring(saslresult, NULL, NULL));
+ return RET_FAIL;
+ } else {
+ initialized_sasl = 1;
+ }
+ }
+#endif /* HAVE_SASL */
+
+ cxn->lmtp_rBuffer = newBuffer(4096);
+ if (cxn->lmtp_rBuffer == NULL)
+ {
+ d_printf(0, "%s:%d Failure allocating buffer for lmtp_rBuffer\n",
+ hostPeerName (cxn->myHost), cxn->ident);
+ return RET_FAIL;
+ }
+ bufferAddNullByte(cxn->lmtp_rBuffer);
+
+
+ cxn->imap_rBuffer = newBuffer(4096);
+ if (cxn->imap_rBuffer == NULL)
+ {
+ d_printf(0, "%s:%d Failure allocating buffer for imap_rBuffer \n",
+ hostPeerName (cxn->myHost), cxn->ident);
+ return RET_FAIL;
+ }
+ bufferAddNullByte(cxn->imap_rBuffer);
+
+ /* Initialize timeouts */
+ cxn->lmtp_writeTimeout = respTimeout;
+ cxn->lmtp_readTimeout = respTimeout;
+ cxn->imap_writeTimeout = respTimeout;
+ cxn->imap_readTimeout = respTimeout;
+ cxn->lmtp_sleepTimerId = 0 ;
+ cxn->lmtp_sleepTimeout = init_reconnect_period ;
+ cxn->imap_sleepTimerId = 0 ;
+ cxn->imap_sleepTimeout = init_reconnect_period ;
+
+ cxn->dosomethingTimeout = DOSOMETHING_TIMEOUT;
+
+ /* set up the write timer. */
+ clearTimer (cxn->dosomethingTimerId) ;
+
+ if (cxn->dosomethingTimeout > 0)
+ cxn->dosomethingTimerId = prepareSleep (dosomethingTimeoutCbk,
+ cxn->dosomethingTimeout, cxn);
+
+
+
+ return RET_OK;
+}
+
+
+/* initialize the network */
+static conn_ret init_net(char *serverFQDN,
+ int port,
+ int *sock)
+{
+ struct sockaddr_in addr;
+ struct hostent *hp;
+
+ if ((hp = gethostbyname(serverFQDN)) == NULL) {
+ d_printf(0, "gethostbyname(): %s\n", strerror(errno));
+ return RET_FAIL;
+ }
+
+ if (( (*sock) = socket(AF_INET, SOCK_STREAM, 0)) < 0) {
+ d_printf(0, "socket(): %s\n", strerror(errno));
+ return RET_FAIL;
+ }
+
+ addr.sin_family = AF_INET;
+ memcpy(&addr.sin_addr, hp->h_addr, hp->h_length);
+ addr.sin_port = htons(port);
+
+ if (connect( (*sock), (struct sockaddr *) &addr, sizeof (addr)) < 0) {
+ d_printf(0,"connect(): %s\n",
+ strerror(errno));
+ return RET_FAIL;
+ }
+
+ return RET_OK;
+}
+
+
+
+static conn_ret SetupLMTPConnection(connection_t *cxn,
+ char *serverName,
+ int port)
+{
+#ifdef HAVE_SASL
+ int saslresult;
+#endif /* HAVE_SASL */
+ conn_ret result;
+
+ cxn->lmtp_port = port;
+
+ if (serverName==NULL)
+ {
+ d_printf(0, "%s:%d serverName is null\n",
+ hostPeerName (cxn->myHost), cxn->ident);
+ return RET_FAIL;
+ }
+
+#ifdef HAVE_SASL
+ /* Free the SASL connection if we already had one */
+ if (cxn->saslconn_lmtp!=NULL)
+ {
+ sasl_dispose(&cxn->saslconn_lmtp);
+ }
+
+ /* Start SASL */
+ saslresult=sasl_client_new("lmtp",
+ serverName,
+ NULL,
+ NULL,
+ NULL,
+ 0,
+ &cxn->saslconn_lmtp);
+
+ if (saslresult != SASL_OK)
+ {
+
+ d_printf(0, "%s:%d:LMTP Error creating a new SASL connection (%s)\n",
+ hostPeerName (cxn->myHost), cxn->ident,
+ sasl_errstring(saslresult,NULL,NULL));
+ return RET_FAIL;
+ }
+#endif /* HAVE_SASL */
+
+ /* Connect the Socket */
+ result = init_net(serverName,
+ LMTP_PORT, /*port,*/
+ &(cxn->sockfd_lmtp));
+
+ if (result != RET_OK)
+ {
+ d_printf(0, "%s:%d unable to connect to lmtp host\n",
+ hostPeerName (cxn->myHost), cxn->ident);
+ return RET_FAIL;
+ }
+
+ if (cxn->lmtp_respBuffer) free(cxn->lmtp_respBuffer);
+ cxn->lmtp_respBuffer = xmalloc (4096);
+ cxn->lmtp_respBuffer[0]='\0';
+
+ /* Free if we had an existing one */
+ if (cxn->lmtp_endpoint != NULL)
+ {
+ delEndPoint(cxn->lmtp_endpoint);
+ cxn->lmtp_endpoint = NULL;
+ }
+
+ cxn->lmtp_endpoint = newEndPoint(cxn->sockfd_lmtp);
+ if (cxn->lmtp_endpoint == NULL)
+ {
+ d_printf(0, "%s:%d:LMTP failure creating endpoint\n",
+ hostPeerName (cxn->myHost), cxn->ident);
+ return RET_FAIL;
+ }
+
+#ifdef HAVE_SASL
+ /* Set the SASL properties */
+ result = SetSASLProperties(cxn->saslconn_lmtp, cxn->sockfd_lmtp,
+ 0, 0);
+
+ if (result != RET_OK)
+ {
+ d_printf(0,"%s:%d:LMTP error setting SASL properties\n",
+ hostPeerName (cxn->myHost), cxn->ident);
+ return RET_FAIL;
+ }
+#endif /* HAVE_SASL */
+
+
+ return RET_OK;
+}
+
+static conn_ret SetupIMAPConnection(connection_t *cxn,
+ char *serverName,
+ int port)
+{
+#ifdef HAVE_SASL
+ int saslresult;
+#endif /* HAVE_SASL */
+ conn_ret result;
+
+ cxn->imap_port = port;
+
+ if (serverName==NULL)
+ {
+ d_printf(0,"%s:%d:IMAP Servername is null",
+ hostPeerName (cxn->myHost), cxn->ident);
+ return RET_FAIL;
+ }
+
+#ifdef HAVE_SASL
+ /* Free the SASL connection if we already had one */
+ if (cxn->imap_saslconn!=NULL)
+ {
+ sasl_dispose(&cxn->imap_saslconn);
+ }
+
+ /* Start SASL */
+ saslresult=sasl_client_new("imap",
+ serverName,
+ NULL,
+ NULL,
+ NULL,
+ 0,
+ &cxn->imap_saslconn);
+
+ if (saslresult != SASL_OK)
+ {
+ d_printf(0,"%s:%d:IMAP Error creating a new SASL connection (%s)",
+ hostPeerName (cxn->myHost), cxn->ident,
+ sasl_errstring(saslresult,NULL,NULL));
+ return RET_FAIL;
+ }
+#endif /* HAVE_SASL */
+
+ /* Connect the Socket */
+ result = init_net(serverName,
+ port,
+ &(cxn->imap_sockfd));
+
+ if (result != RET_OK)
+ {
+ d_printf(0,"%s:%d:IMAP Unable to start network connection for IMAP",
+ hostPeerName (cxn->myHost), cxn->ident);
+ return RET_FAIL;
+ }
+
+ if (cxn->imap_respBuffer) free(cxn->imap_respBuffer);
+ cxn->imap_respBuffer = xmalloc (4096);
+ cxn->imap_respBuffer[0]='\0';
+
+ /* Free if we had an existing one */
+ if (cxn->imap_endpoint != NULL)
+ {
+ delEndPoint(cxn->imap_endpoint);
+ cxn->imap_endpoint = NULL;
+ }
+
+ cxn->imap_endpoint = newEndPoint(cxn->imap_sockfd);
+ if (cxn->imap_endpoint == NULL)
+ {
+ d_printf(0,"%s:%d:IMAP Failure creating imap endpoint\n",
+ hostPeerName (cxn->myHost), cxn->ident);
+ return RET_FAIL;
+ }
+
+#ifdef HAVE_SASL
+ /* Set the SASL properties */
+ result = SetSASLProperties(cxn->imap_saslconn, cxn->imap_sockfd,
+ 0, 0);
+ if (result != RET_OK)
+ {
+ d_printf(0,"%s:%d:IMAP Error setting sasl properties",
+ hostPeerName (cxn->myHost), cxn->ident);
+ return result;
+ }
+#endif /* HAVE_SASL */
+
+
+ return RET_OK;
+}
+
+/************************* END Startup functions **********************************/
+
+/* Return the response code for this line
+ -1 if it doesn't seem to have one
+*/
+static int ask_code(char *str)
+{
+ int ret = 0;
+
+ if (str==NULL) return -1;
+
+ if (strlen(str) < 3) return -1;
+
+ /* check to make sure 0-2 are digits */
+ if ((isdigit((int) str[0])==0) ||
+ (isdigit((int) str[1])==0) ||
+ (isdigit((int) str[2])==0))
+ {
+ d_printf(0,
+ "Parse error: response does not begin with a code [%s]\n",
+ str);
+ return -1;
+ }
+
+
+ ret = ((str[0]-'0')*100)+
+ ((str[1]-'0')*10)+
+ (str[2]-'0');
+
+ return ret;
+}
+
+/* is this a continuation or not?
+ 220-fdfsd is (1)
+ 220 fdsfs is not (0)
+ */
+
+static int ask_keepgoing(char *str)
+{
+ if (str==NULL) return 0;
+ if (strlen(str) < 4) return 0;
+
+ if (str[3]=='-') return 1;
+
+ return 0;
+}
+
+
+static conn_ret lmtp_listenintro(connection_t *cxn)
+{
+ Buffer *readBuffers;
+
+ /* set up to receive */
+ readBuffers = makeBufferArray (bufferTakeRef (cxn->lmtp_rBuffer), NULL) ;
+ prepareRead(cxn->lmtp_endpoint, readBuffers, lmtp_readCB, cxn, 5);
+
+ cxn->lmtp_state = LMTP_READING_INTRO;
+
+ return RET_OK;
+}
+
+
+
+/************************** IMAP functions ***********************/
+
+static conn_ret imap_Connect(connection_t *cxn)
+{
+ conn_ret result;
+
+ ASSERT(cxn->imap_sleepTimerId == 0);
+
+ /* make the IMAP connection */
+ result = SetupIMAPConnection(cxn,
+ cxn->ServerName,
+ IMAP_PORT);
+
+ /* Listen to the intro and start the authenticating process */
+ result = imap_listenintro(cxn);
+
+ return result;
+}
+
+/*
+ * This is called when the data write timeout for the remote
+ * goes off. We tear down the connection and notify our host.
+ */
+static void imap_writeTimeoutCbk (TimeoutId id UNUSED, void *data)
+{
+ connection_t *cxn = (Connection) data ;
+ const char *peerName ;
+
+ peerName = hostPeerName (cxn->myHost) ;
+
+ syslog (LOG_WARNING, "timeout for %s", peerName);
+ d_printf(0, "%s: shutting down non-responsive IMAP connection (%s)\n",
+ hostPeerName (cxn->myHost),
+ imap_stateToString(cxn->imap_state));
+
+ cxnLogStats (cxn,true) ;
+
+ imap_Disconnect(cxn);
+}
+
+/*
+ * This is called when the timeout for the reponse from the remote
+ * goes off. We tear down the connection and notify our host.
+ */
+static void imap_readTimeoutCbk (TimeoutId id, void *data)
+{
+ Connection cxn = (Connection) data ;
+ const char *peerName ;
+
+ ASSERT (id == cxn->imap_readBlockedTimerId) ;
+
+ peerName = hostPeerName (cxn->myHost) ;
+
+ warn ("%s:%d cxnsleep non-responsive connection", peerName, cxn->ident) ;
+ d_printf(0, "%s:%d shutting down non-responsive IMAP connection (%s)\n",
+ hostPeerName (cxn->myHost), cxn->ident,
+ imap_stateToString(cxn->imap_state));
+
+ cxnLogStats (cxn,true);
+
+ if (cxn->imap_state == IMAP_DISCONNECTED)
+ {
+ imap_Disconnect(cxn);
+ lmtp_Disconnect(cxn);
+ delConnection (cxn) ;
+ }
+ else {
+ imap_Disconnect(cxn);
+ }
+}
+
+/*
+ * Called by the EndPoint class when the timer goes off
+ */
+static void imap_reopenTimeoutCbk (TimeoutId id, void *data)
+{
+ Connection cxn = (Connection) data ;
+
+ ASSERT (id == cxn->imap_sleepTimerId) ;
+
+ cxn->imap_sleepTimerId = 0 ;
+
+ d_printf(1,"%s:%d:IMAP Reopen timer rang. Try to make new connection now\n",
+ hostPeerName (cxn->myHost), cxn->ident) ;
+
+ if (cxn->imap_state != IMAP_DISCONNECTED)
+ {
+ warn ("%s:%d cxnsleep connection in bad state: %s",
+ hostPeerName (cxn->myHost), cxn->ident,
+ imap_stateToString (cxn->imap_state)) ;
+ }
+ else {
+ if (imap_Connect(cxn) != RET_OK)
+ prepareReopenCbk(cxn, 0);
+ }
+}
+
+static void imap_Disconnect(connection_t *cxn)
+{
+ clearTimer (cxn->imap_sleepTimerId) ;
+ cxn->imap_sleepTimerId = 0;
+ clearTimer (cxn->imap_readBlockedTimerId) ;
+ clearTimer (cxn->imap_writeBlockedTimerId) ;
+
+ DeferAllArticles(cxn, &(cxn->imap_controlMsg_q)) ; /* give any articles back to Host */
+
+ cxn->imap_state = IMAP_DISCONNECTED;
+
+ cxn->imap_disconnects++;
+
+ cxn->imap_respBuffer[0]='\0';
+
+ if (cxn->issue_quit == 0)
+ prepareReopenCbk(cxn,0);
+
+ DeleteIfDisconnected(cxn);
+}
+
+/************************** END IMAP functions ***********************/
+
+/************************ LMTP functions **************************/
+
+/*
+ * Create a network lmtp connection
+ * and start listening for the intro string
+ *
+ */
+
+static conn_ret lmtp_Connect(connection_t *cxn)
+{
+ conn_ret result;
+
+ ASSERT(cxn->lmtp_sleepTimerId == 0);
+
+ /* make the LMTP connection */
+ result = SetupLMTPConnection(cxn,
+ cxn->ServerName,
+ LMTP_PORT);
+
+ if (result!=RET_OK) return result;
+
+ /* Listen to the intro */
+ result = lmtp_listenintro(cxn);
+
+ return result;
+}
+
+
+
+static void lmtp_Disconnect(connection_t *cxn)
+{
+ clearTimer (cxn->lmtp_sleepTimerId) ;
+ cxn->lmtp_sleepTimerId = 0;
+ clearTimer (cxn->lmtp_readBlockedTimerId) ;
+ clearTimer (cxn->lmtp_writeBlockedTimerId) ;
+
+ /* give any articles back to Host */
+ DeferAllArticles(cxn, &(cxn->lmtp_todeliver_q)) ;
+
+ cxn->lmtp_state = LMTP_DISCONNECTED;
+
+ cxn->lmtp_disconnects++;
+
+ cxn->lmtp_respBuffer[0]='\0';
+
+ if (cxn->issue_quit == 0)
+ prepareReopenCbk(cxn,1);
+
+ DeleteIfDisconnected(cxn);
+}
+
+
+
+/*
+ * Called by the EndPoint class when the timer goes off
+ */
+static void lmtp_reopenTimeoutCbk (TimeoutId id, void *data)
+{
+ Connection cxn = (Connection) data ;
+
+ ASSERT (id == cxn->lmtp_sleepTimerId) ;
+
+ cxn->lmtp_sleepTimerId = 0 ;
+
+ d_printf(1,"%s:%d:LMTP Reopen timer rang. Try to make new connection now\n",
+ hostPeerName (cxn->myHost), cxn->ident) ;
+
+ if (cxn->lmtp_state != LMTP_DISCONNECTED)
+ {
+ warn ("%s:%d cxnsleep connection in bad state: %s",
+ hostPeerName (cxn->myHost), cxn->ident,
+ lmtp_stateToString (cxn->lmtp_state)) ;
+ }
+ else {
+ if (lmtp_Connect(cxn) != RET_OK)
+ prepareReopenCbk(cxn, 1);
+ }
+}
+
+/*
+ * Set up the callback used when the Connection is sleeping (i.e. will try
+ * to reopen the connection).
+ *
+ * type (0 = imap, 1 = lmtp)
+ */
+static void prepareReopenCbk (Connection cxn, int type)
+{
+ /* xxx check state */
+
+
+
+ if (type == 0) {
+
+ cxn->imap_sleepTimerId = prepareSleep (imap_reopenTimeoutCbk,
+ cxn->imap_sleepTimeout, cxn) ;
+ d_printf (1,"%s:%d IMAP connection error\n"
+ " will try to reconnect in %d seconds\n",
+ hostPeerName (cxn->myHost), cxn->ident,
+ cxn->imap_sleepTimeout) ;
+ } else {
+ cxn->lmtp_sleepTimerId = prepareSleep (lmtp_reopenTimeoutCbk,
+ cxn->lmtp_sleepTimeout, cxn) ;
+ d_printf (1,"%s:%d:LMTP connection error\n"
+ "will try to reconnect in %d seconds\n",
+ hostPeerName (cxn->myHost), cxn->ident,
+ cxn->lmtp_sleepTimeout) ;
+ }
+
+ /* bump the sleep timer amount each time to wait longer and longer. Gets
+ reset in resetConnection() */
+ if (type == 0) {
+ cxn->imap_sleepTimeout *= 2 ;
+ if (cxn->imap_sleepTimeout > max_reconnect_period)
+ cxn->imap_sleepTimeout = max_reconnect_period ;
+ } else {
+ cxn->lmtp_sleepTimeout *= 2 ;
+ if (cxn->lmtp_sleepTimeout > max_reconnect_period)
+ cxn->lmtp_sleepTimeout = max_reconnect_period ;
+ }
+}
+
+/*
+ * This is called when the timeout for the reponse from the remote
+ * goes off. We tear down the connection and notify our host.
+ */
+static void lmtp_readTimeoutCbk (TimeoutId id, void *data)
+{
+ Connection cxn = (Connection) data ;
+ const char *peerName ;
+
+ ASSERT (id == cxn->lmtp_readBlockedTimerId) ;
+
+ peerName = hostPeerName (cxn->myHost) ;
+
+ warn ("%s:%d cxnsleep non-responsive connection", peerName, cxn->ident) ;
+ d_printf(0,"%s:%d shutting down non-responsive LMTP connection (%s)\n",
+ hostPeerName (cxn->myHost), cxn->ident,
+ lmtp_stateToString(cxn->lmtp_state));
+
+ cxnLogStats (cxn,true) ;
+
+ if (cxn->lmtp_state == LMTP_DISCONNECTED) {
+ imap_Disconnect(cxn);
+ lmtp_Disconnect(cxn);
+ delConnection (cxn) ;
+ } else {
+ lmtp_Disconnect(cxn);
+ }
+}
+
+
+
+/*
+ * This is called when the data write timeout for the remote
+ * goes off. We tear down the connection and notify our host.
+ */
+static void lmtp_writeTimeoutCbk (TimeoutId id UNUSED, void *data)
+{
+ connection_t *cxn = (Connection) data ;
+ const char *peerName ;
+
+ peerName = hostPeerName (cxn->myHost) ;
+
+ syslog (LOG_WARNING, "timeout for %s", peerName);
+ d_printf(0, "%s:%d shutting down non-responsive LMTP connection (%s)\n",
+ hostPeerName (cxn->myHost), cxn->ident,
+ lmtp_stateToString(cxn->lmtp_state)) ;
+
+ cxnLogStats (cxn,true);
+
+ lmtp_Disconnect(cxn);
+}
+
+/************************ END LMTP functions **************************/
+
+/************************** LMTP write functions ********************/
+
+static conn_ret lmtp_noop(connection_t *cxn)
+{
+ int result;
+ char *p;
+
+ p = xstrdup("NOOP\r\n");
+ result = WriteToWire_lmtpstr(cxn, p, strlen(p));
+ if (result!=RET_OK) return result;
+
+ cxn->lmtp_state = LMTP_WRITING_NOOP;
+
+ return RET_OK;
+}
+
+static conn_ret lmtp_IssueQuit(connection_t *cxn)
+{
+ int result;
+ char *p;
+
+ p = xstrdup("QUIT\r\n");
+ result = WriteToWire_lmtpstr(cxn, p, strlen(p));
+ if (result!=RET_OK) return result;
+
+ cxn->lmtp_state = LMTP_WRITING_QUIT;
+
+ return RET_OK;
+}
+
+static conn_ret lmtp_getcapabilities(connection_t *cxn)
+{
+ int result;
+ char *p;
+
+ if (cxn->lmtp_capabilities != NULL)
+ {
+ if (cxn->lmtp_capabilities->saslmechs) {
+ free( cxn->lmtp_capabilities->saslmechs);
+ }
+ free( cxn->lmtp_capabilities );
+ cxn->lmtp_capabilities = NULL;
+ }
+
+ cxn->lmtp_capabilities = xcalloc (1, sizeof(lmtp_capabilities_t));
+ cxn->lmtp_capabilities->saslmechs = NULL;
+
+#ifdef SMTPMODE
+ p = concat("EHLO ", hostname, "\r\n", (char *) 0);
+#else
+ p = concat("LHLO ", hostname, "\r\n", (char *) 0);
+#endif /* SMTPMODE */
+
+ result = WriteToWire_lmtpstr(cxn, p, strlen(p));
+ if (result!=RET_OK) return result;
+
+ cxn->lmtp_state = LMTP_WRITING_LHLO;
+
+ return RET_OK;
+}
+
+#ifdef HAVE_SASL
+static conn_ret lmtp_authenticate(connection_t *cxn)
+{
+ int saslresult;
+
+ const char *mechusing;
+ const char *out;
+ unsigned int outlen;
+ char *inbase64;
+ int inbase64len;
+ int status;
+ int result;
+
+ char *p;
+
+ sasl_interact_t *client_interact=NULL;
+
+
+ saslresult=sasl_client_start(cxn->saslconn_lmtp,
+ cxn->lmtp_capabilities->saslmechs,
+ &client_interact,
+ &out, &outlen,
+ &mechusing);
+
+
+
+ if ((saslresult != SASL_OK) &&
+ (saslresult != SASL_CONTINUE)) {
+
+ d_printf(0,"%s:%d:LMTP Error calling sasl_client_start (%s)\n",
+ hostPeerName (cxn->myHost), cxn->ident,
+ sasl_errstring(saslresult, NULL, NULL));
+ return RET_FAIL;
+ }
+
+ d_printf(1,"%s:%d:LMTP Decided to try to authenticate with SASL mechanism=%s\n",
+ hostPeerName (cxn->myHost), cxn->ident,mechusing);
+
+ if (!out)
+ {
+ /* no initial client response */
+ p = concat("AUTH ", mechusing, "\r\n", (char *) 0);
+ } else if (!outlen) {
+ /* empty initial client response */
+ p = concat("AUTH ", mechusing, " =\r\n", (char *) 0);
+ } else {
+ /* initial client response - convert to base64 */
+ inbase64 = xmalloc(outlen*2+10);
+
+ saslresult = sasl_encode64(out, outlen,
+ inbase64, outlen*2+10,
+ (unsigned *) &inbase64len);
+ if (saslresult != SASL_OK) return RET_FAIL;
+ p = concat("AUTH ", mechusing, " ", inbase64, "\r\n", (char *) 0);
+ free(inbase64);
+ }
+
+ result = WriteToWire_lmtpstr(cxn, p, strlen(p));
+
+ cxn->lmtp_state = LMTP_WRITING_STARTAUTH;
+
+ return RET_OK;
+}
+
+static imt_stat lmtp_getauthline(char *str, char **line, int *linelen)
+{
+ char buf[4096];
+ int saslresult;
+ int response_code = -1;
+
+ response_code = ask_code(str);
+
+ if (response_code == 334) {
+
+ /* continue */
+
+ } else if (response_code == 235) {
+
+ /* woohoo! authentication complete */
+ return STAT_OK;
+
+ } else {
+ /* failure of some sort */
+ d_printf(0,"?:?:LMTP Authentication failure (%d) [%s]\n",
+ response_code,str);
+ return STAT_NO;
+ }
+
+ str += 4; /* jump past the "334 " */
+
+ *line = xmalloc(strlen(str)+30);
+ if ((*line)==NULL) {
+ return STAT_NO;
+ }
+
+ /* decode this line */
+ saslresult = sasl_decode64(str, strlen(str),
+ *line, strlen(str)+1, (unsigned *) linelen);
+ if (saslresult != SASL_OK) {
+ d_printf(0,"?:?:LMTP base64 decoding error\n");
+ return STAT_NO;
+ }
+
+ return STAT_CONT;
+}
+#endif /* HAVE_SASL */
+
+static void lmtp_writeCB (EndPoint e UNUSED, IoStatus i UNUSED, Buffer *b,
+ void *d)
+{
+ connection_t *cxn = (connection_t *) d;
+ Buffer *readBuffers;
+
+ clearTimer (cxn->lmtp_writeBlockedTimerId) ;
+
+ /* Free the string that was written */
+ freeBufferArray (b);
+ if (cxn->lmtp_tofree_str!=NULL)
+ {
+ free(cxn->lmtp_tofree_str);
+ cxn->lmtp_tofree_str=NULL;
+ }
+
+ /* set up to receive */
+ readBuffers = makeBufferArray (bufferTakeRef (cxn->lmtp_rBuffer), NULL) ;
+ prepareRead(cxn->lmtp_endpoint, readBuffers, lmtp_readCB, cxn, 5);
+
+ /* set up the response timer. */
+ clearTimer (cxn->lmtp_readBlockedTimerId) ;
+
+ if (cxn->lmtp_readTimeout > 0)
+ cxn->lmtp_readBlockedTimerId = prepareSleep (lmtp_readTimeoutCbk,
+ cxn->lmtp_readTimeout, cxn) ;
+
+
+ switch (cxn->lmtp_state)
+ {
+
+ case LMTP_WRITING_LHLO:
+ cxn->lmtp_state = LMTP_READING_LHLO;
+ break;
+
+ case LMTP_WRITING_STARTAUTH:
+ case LMTP_WRITING_STEPAUTH:
+
+ cxn->lmtp_state = LMTP_READING_STEPAUTH;
+
+ break;
+
+ case LMTP_WRITING_UPTODATA:
+ /* expect result to rset */
+ cxn->lmtp_state = LMTP_READING_RSET;
+ break;
+
+ case LMTP_WRITING_CONTENTS:
+ /* so we sent the whole DATA command
+ let's see what the server responded */
+
+ cxn->lmtp_state = LMTP_READING_CONTENTS;
+
+ break;
+
+ case LMTP_WRITING_NOOP:
+ cxn->lmtp_state = LMTP_READING_NOOP;
+ break;
+
+ case LMTP_WRITING_QUIT:
+ cxn->lmtp_state = LMTP_READING_QUIT;
+ break;
+
+ default:
+
+ d_printf(0,"%s:%d:LMTP Unknown state. Internal error\n",
+ hostPeerName (cxn->myHost), cxn->ident) ;
+
+ break;
+ }
+}
+
+/************************** END LMTP write functions ********************/
+
+/************************** IMAP sending functions ************************/
+
+
+static void imap_writeCB (EndPoint e UNUSED, IoStatus i UNUSED, Buffer *b,
+ void *d)
+{
+ connection_t *cxn = (connection_t *) d;
+ Buffer *readBuffers;
+
+ clearTimer (cxn->imap_writeBlockedTimerId) ;
+
+ /* free the string we just wrote out */
+ freeBufferArray (b);
+ if (cxn->imap_tofree_str!=NULL)
+ {
+ free(cxn->imap_tofree_str);
+ cxn->imap_tofree_str=NULL;
+ }
+
+ /* set up to receive */
+ readBuffers = makeBufferArray (bufferTakeRef (cxn->imap_rBuffer), NULL) ;
+ prepareRead(cxn->imap_endpoint, readBuffers, imap_readCB, cxn, 5);
+
+ /* set up the response timer. */
+ clearTimer (cxn->imap_readBlockedTimerId) ;
+
+ if (cxn->imap_readTimeout > 0)
+ cxn->imap_readBlockedTimerId = prepareSleep(imap_readTimeoutCbk,
+ cxn->imap_readTimeout,
+ cxn);
+
+ switch (cxn->imap_state) {
+ case IMAP_WRITING_CAPABILITY:
+ cxn->imap_state = IMAP_READING_CAPABILITY;
+ break;
+
+ case IMAP_WRITING_STEPAUTH:
+ case IMAP_WRITING_STARTAUTH:
+ cxn->imap_state = IMAP_READING_STEPAUTH;
+ break;
+
+ case IMAP_WRITING_CREATE:
+ cxn->imap_state = IMAP_READING_CREATE;
+ break;
+
+ case IMAP_WRITING_DELETE:
+ cxn->imap_state = IMAP_READING_DELETE;
+ break;
+
+ case IMAP_WRITING_SELECT:
+ cxn->imap_state = IMAP_READING_SELECT;
+ break;
+
+ case IMAP_WRITING_SEARCH:
+ cxn->imap_state = IMAP_READING_SEARCH;
+ break;
+
+ case IMAP_WRITING_STORE:
+ cxn->imap_state = IMAP_READING_STORE;
+ break;
+
+ case IMAP_WRITING_CLOSE:
+ cxn->imap_state = IMAP_READING_CLOSE;
+ break;
+
+ case IMAP_WRITING_NOOP:
+ cxn->imap_state = IMAP_READING_NOOP;
+ break;
+
+ case IMAP_WRITING_QUIT:
+ cxn->imap_state = IMAP_READING_QUIT;
+ break;
+
+ default:
+ d_printf(0,"%s:%d:IMAP invalid connection state\n",
+ hostPeerName (cxn->myHost), cxn->ident) ;
+ imap_Disconnect(cxn);
+ break;
+ }
+}
+
+/*
+ * Tag is already allocated
+ */
+
+static void imap_GetTag(connection_t *cxn)
+{
+ sprintf(cxn->imap_currentTag,"%06d",cxn->imap_tag_num);
+ cxn->imap_tag_num++;
+ if (cxn->imap_tag_num >= 999999)
+ {
+ cxn->imap_tag_num = 0;
+ }
+}
+
+#ifdef HAVE_SASL
+static conn_ret imap_sendAuthStep(connection_t *cxn, char *str)
+{
+ conn_ret result;
+ int saslresult;
+ char in[4096];
+ unsigned int inlen;
+ const char *out;
+ unsigned int outlen;
+ char *inbase64;
+ unsigned int inbase64len;
+
+ /* base64 decode it */
+
+ saslresult = sasl_decode64(str, strlen(str),
+ in, strlen(str)+1, &inlen);
+ if (saslresult != SASL_OK) {
+ d_printf(0,"%s:%d:IMAP base64 decoding error\n",
+ hostPeerName (cxn->myHost), cxn->ident) ;
+ return RET_FAIL;
+ }
+
+ saslresult=sasl_client_step(cxn->imap_saslconn,
+ in,
+ inlen,
+ NULL,
+ &out,
+ &outlen);
+
+ /* check if sasl succeeded */
+ if (saslresult != SASL_OK && saslresult != SASL_CONTINUE) {
+
+ d_printf(0,"%s:%d:IMAP sasl_client_step failed with %s\n",
+ hostPeerName (cxn->myHost), cxn->ident,
+ sasl_errstring(saslresult,NULL,NULL));
+ cxn->imap_state = IMAP_CONNECTED_NOTAUTH;
+ return RET_FAIL;
+ }
+
+ inbase64 = xmalloc(outlen * 2 + 10);
+
+ /* convert to base64 */
+ saslresult = sasl_encode64(out, outlen,
+ inbase64, outlen*2, (unsigned *) &inbase64len);
+
+ if (saslresult != SASL_OK) return RET_FAIL;
+
+ /* append endline */
+ strlcpy(inbase64 + inbase64len, "\r\n", outlen * 2 + 10 - inbase64len);
+ inbase64len+=2;
+
+ /* send to server */
+ result = WriteToWire_imapstr(cxn,inbase64, inbase64len);
+
+ cxn->imap_state = IMAP_WRITING_STEPAUTH;
+
+ return result;
+}
+#endif /* HAVE_SASL */
+
+static conn_ret imap_sendAuthenticate(connection_t *cxn)
+{
+ int result;
+
+ char *p;
+
+#ifdef HAVE_SASL
+ const char *mechusing;
+ char *inbase64;
+ int inbase64len;
+ int saslresult=SASL_NOMECH;
+
+ sasl_interact_t *client_interact=NULL;
+
+ if (cxn->imap_capabilities->saslmechs) {
+ saslresult=sasl_client_start(cxn->imap_saslconn,
+ cxn->imap_capabilities->saslmechs,
+ &client_interact,
+ NULL, NULL,
+ &mechusing);
+ }
+
+
+
+ /* If no mechs try "login" */
+ if (saslresult == SASL_NOMECH)
+ {
+
+#else /* HAVE_SASL */
+
+ { /* always do login */
+
+#endif /* HAVE_SASL */
+ d_printf(1,"%s:%d:IMAP No mechanism found. Trying login method\n",
+ hostPeerName (cxn->myHost), cxn->ident) ;
+
+ if (cxn->imap_capabilities->logindisabled==1)
+ {
+ d_printf(0,"%s:%d:IMAP Login command w/o security layer not allowed on this server\n",
+ hostPeerName (cxn->myHost), cxn->ident);
+ return RET_FAIL;
+ }
+
+ if (deliver_authname==NULL)
+ {
+ d_printf(0,"%s:%d:IMAP Unable to log in because can't find a authname\n",
+ hostPeerName (cxn->myHost), cxn->ident) ;
+ return RET_FAIL;
+ }
+
+ if (deliver_password==NULL)
+ {
+ d_printf(0,"%s:%d:IMAP Unable to log in because can't find a password\n",
+ hostPeerName (cxn->myHost), cxn->ident) ;
+ return RET_FAIL;
+ }
+
+ imap_GetTag(cxn);
+
+ p = concat(cxn->imap_currentTag, " LOGIN ", deliver_authname, " \"",
+ deliver_password, "\"\r\n", (char *) 0);
+
+ result = WriteToWire_imapstr(cxn, p, strlen(p));
+
+ cxn->imap_state = IMAP_WRITING_STARTAUTH;
+
+ return RET_OK;
+ }
+
+#ifdef HAVE_SASL
+ if ((saslresult != SASL_OK) &&
+ (saslresult != SASL_CONTINUE)) {
+
+ d_printf(0,"%s:%d:IMAP Error calling sasl_client_start (%s) mechusing = %s\n",
+ hostPeerName (cxn->myHost), cxn->ident,
+ sasl_errstring(saslresult, NULL, NULL), mechusing);
+ return RET_FAIL;
+ }
+
+ d_printf(1,"%s:%d:IMAP Trying to authenticate to imap with %s mechanism\n",
+ hostPeerName (cxn->myHost), cxn->ident,
+ mechusing);
+
+ imap_GetTag(cxn);
+
+ p = concat(cxn->imap_currentTag, " AUTHENTICATE ", mechusing, "\r\n",
+ (char *) 0);
+ result = WriteToWire_imapstr(cxn, p, strlen(p));
+
+ cxn->imap_state = IMAP_WRITING_STARTAUTH;
+
+ return RET_OK;
+#endif /* HAVE_SASL */
+}
+
+static conn_ret imap_CreateGroup(connection_t *cxn, char *bboard)
+{
+ conn_ret result;
+ char *tosend;
+
+ d_printf(1,"%s:%d:IMAP Ok creating group [%s]\n",
+ hostPeerName (cxn->myHost), cxn->ident,
+ bboard);
+
+ imap_GetTag(cxn);
+
+ tosend = concat(cxn->imap_currentTag, " CREATE ", bboard, "\r\n",
+ (char *) 0);
+
+ result = WriteToWire_imapstr(cxn, tosend, -1);
+ if (result!=RET_OK) return result;
+
+ cxn->imap_state = IMAP_WRITING_CREATE;
+
+ return RET_OK;
+}
+
+static conn_ret imap_DeleteGroup(connection_t *cxn, char *bboard)
+{
+ conn_ret result;
+ char *tosend;
+
+ d_printf(1,"%s:%d:IMAP Ok removing bboard [%s]\n",
+ hostPeerName (cxn->myHost), cxn->ident, bboard);
+
+ imap_GetTag(cxn);
+
+ tosend = concat(cxn->imap_currentTag, " DELETE ", bboard, "\r\n",
+ (char *) 0);
+ result = WriteToWire_imapstr(cxn, tosend, -1);
+ if (result!=RET_OK) return result;
+
+ cxn->imap_state = IMAP_WRITING_DELETE;
+
+ return RET_OK;
+}
+
+static conn_ret imap_CancelMsg(connection_t *cxn, char *newsgroup)
+{
+ conn_ret result;
+ char *tosend;
+
+ ASSERT(newsgroup);
+
+ imap_GetTag(cxn);
+
+ /* select mbox */
+ tosend = concat(cxn->imap_currentTag, " SELECT ", newsgroup, "\r\n",
+ (char *) 0);
+ result = WriteToWire_imapstr(cxn, tosend, -1);
+ if (result != RET_OK) return result;
+
+ cxn->imap_state = IMAP_WRITING_SELECT;
+
+ hostArticleOffered (cxn->myHost, cxn);
+
+ return RET_OK;
+}
+
+static conn_ret imap_sendSearch(connection_t *cxn, char *msgid)
+{
+ conn_ret result;
+ char *tosend;
+
+ ASSERT(msgid);
+
+ imap_GetTag(cxn);
+
+ /* preform search */
+ tosend = concat(cxn->imap_currentTag,
+ " UID SEARCH header \"Message-ID\" \"", msgid, "\"\r\n",
+ (char *) 0);
+ result = WriteToWire_imapstr(cxn, tosend, -1);
+ if (result != RET_OK) return result;
+
+ cxn->imap_state = IMAP_WRITING_SEARCH;
+
+ return RET_OK;
+}
+
+static conn_ret imap_sendKill(connection_t *cxn, unsigned uid)
+{
+ conn_ret result;
+ char *tosend;
+ size_t length;
+
+ imap_GetTag(cxn);
+
+ length = 7 + 50 + 20;
+ tosend = xmalloc(length);
+ snprintf(tosend,length,"%s UID STORE %d +FLAGS.SILENT (\\Deleted)\r\n",
+ cxn->imap_currentTag, uid);
+
+ result = WriteToWire_imapstr(cxn, tosend, -1);
+ if (result != RET_OK) return result;
+
+ cxn->imap_state = IMAP_WRITING_STORE;
+
+ return RET_OK;
+}
+
+static conn_ret imap_sendSimple(connection_t *cxn, const char *atom, int st)
+{
+ char *tosend;
+ conn_ret result;
+
+ imap_GetTag(cxn);
+ tosend = concat(cxn->imap_currentTag, " ", atom, "\r\n", (char *) 0);
+
+ result = WriteToWire_imapstr(cxn, tosend, -1);
+ if (result != RET_OK) return result;
+
+ cxn->imap_state = st;
+
+ return RET_OK;
+}
+
+static conn_ret imap_sendClose(connection_t *cxn)
+{
+ return imap_sendSimple(cxn, "CLOSE", IMAP_WRITING_CLOSE);
+}
+
+static conn_ret imap_sendQuit(connection_t *cxn)
+{
+ return imap_sendSimple(cxn, "LOGOUT", IMAP_WRITING_QUIT);
+}
+
+static conn_ret imap_noop(connection_t *cxn)
+{
+ return imap_sendSimple(cxn, "NOOP", IMAP_WRITING_NOOP);
+}
+
+
+static conn_ret imap_sendCapability(connection_t *cxn)
+{
+ return imap_sendSimple(cxn, "CAPABILITY", IMAP_WRITING_CAPABILITY);
+}
+
+/************************** END IMAP sending functions ************************/
+
+/************************** IMAP reading functions ***************************/
+
+static conn_ret imap_listenintro(connection_t *cxn)
+{
+ Buffer *readBuffers;
+
+ /* set up to receive */
+ readBuffers = makeBufferArray (bufferTakeRef (cxn->imap_rBuffer), NULL) ;
+ prepareRead(cxn->imap_endpoint, readBuffers, imap_readCB, cxn, 5);
+
+ cxn->imap_state = IMAP_READING_INTRO;
+
+ return RET_OK;
+}
+
+static conn_ret imap_ParseCapability(char *string, imap_capabilities_t **caps)
+{
+ char *str = string;
+ char *start = str;
+ size_t mechlen;
+
+ /* allocate the caps structure if it doesn't already exist */
+ if ( (*caps) == NULL)
+ (*caps) = xcalloc(1, sizeof(imap_capabilities_t));
+
+ while ( (*str) != '\0')
+ {
+
+ while (((*str) != '\0') && ((*str)!=' '))
+ {
+ str++;
+ }
+
+ if ( (*str) != '\0')
+ {
+ *str = '\0';
+ str++;
+ }
+
+ if ( strcasecmp(start,"IMAP4")==0)
+ {
+ (*caps)->imap4 = 1;
+ } else if (strcasecmp(start,"LOGINDISABLED")==0) {
+ (*caps)->logindisabled = 1;
+ } else if ( strncmp(start, "AUTH=", 5)==0) {
+
+ if ( (*caps)->saslmechs == NULL)
+ {
+ (*caps)->saslmechs = xstrdup (start + 5);
+ } else {
+ mechlen = strlen((*caps)->saslmechs) + 1;
+ mechlen += strlen(start + 5) + 1;
+ (*caps)->saslmechs = xrealloc((*caps)->saslmechs, mechlen);
+ strlcat((*caps)->saslmechs, " ", mechlen);
+ strlcat((*caps)->saslmechs, start + 5, mechlen);
+ }
+ }
+
+ start = str;
+
+ }
+
+ if ((*caps)->saslmechs) {
+ d_printf(1,"?:?:IMAP parsed capabilities: saslmechs = %s\n",
+ (*caps)->saslmechs);
+ }
+
+ return RET_OK;
+}
+
+
+static void imap_readCB (EndPoint e, IoStatus i, Buffer *b, void *d)
+{
+ connection_t *cxn = (connection_t *) d;
+ Buffer *readBuffers ;
+
+ int okno;
+ char *str;
+ char strbuf[4096];
+ char *linestart;
+ conn_ret ret;
+ char *p;
+
+ p = bufferBase(b[0]);
+
+ /* Add what we got to our internal read buffer */
+ bufferAddNullByte (b[0]) ;
+
+ if (i != IoDone) {
+ errno = endPointErrno(e);
+
+ syslog(LOG_ERR, "%s:%d IMAP i/o failed: %m",
+ hostPeerName (cxn->myHost), cxn->ident);
+ freeBufferArray (b);
+ imap_Disconnect(cxn);
+ return;
+ }
+
+ if (strchr (p, '\n') == NULL) {
+ /* partial read. expand buffer and retry */
+
+ if (expandBuffer (b[0], BUFFER_EXPAND_AMOUNT)==false) {
+ d_printf(0,"%s:%d:IMAP expanding buffer returned false\n",
+ hostPeerName (cxn->myHost), cxn->ident);
+
+ imap_Disconnect(cxn);
+ return;
+ }
+ readBuffers = makeBufferArray (bufferTakeRef (b[0]), NULL) ;
+
+ if (!prepareRead (e, readBuffers, imap_readCB, cxn, 1)) {
+ imap_Disconnect(cxn);
+ }
+
+ freeBufferArray (b);
+ return;
+ }
+
+ clearTimer (cxn->imap_readBlockedTimerId) ;
+
+ /* we got something. add to our buffer and free b */
+
+ strcat(cxn->imap_respBuffer, p);
+
+ bufferSetDataSize( b[0], 0);
+
+ freeBufferArray (b);
+
+
+
+ /* goto here to take another step */
+ reset:
+
+ /* see if we have a full line */
+ ret = GetLine( cxn->imap_respBuffer , strbuf, sizeof(strbuf));
+ str = strbuf;
+ linestart = str;
+
+ /* if we don't have a full line */
+ if ( ret == RET_NO_FULLLINE)
+ {
+
+ readBuffers = makeBufferArray (bufferTakeRef (cxn->imap_rBuffer), NULL) ;
+
+ if ( !prepareRead (e, readBuffers, imap_readCB, cxn, 1) )
+ {
+ imap_Disconnect(cxn);
+ }
+ return;
+
+ } else if (ret!=RET_OK)
+ {
+ return;
+ }
+
+ /* if untagged */
+ if ((str[0]=='*') && (str[1]==' '))
+ {
+ str+=2;
+
+ /* now figure out what kind of untagged it is */
+ if (strncasecmp(str,"CAPABILITY ",11)==0)
+ {
+ str+=11;
+
+ imap_ParseCapability(str,&(cxn->imap_capabilities));
+
+ } else if (strncasecmp(str,"SEARCH",6)==0) {
+
+ str+=6;
+
+ if ( (*str) == ' ')
+ {
+ str++;
+
+ cxn->current_control->data.control->uid = atoi(str);
+
+ d_printf(1,"%s:%d:IMAP i think the UID = %ld\n",
+ hostPeerName (cxn->myHost), cxn->ident,
+ cxn->current_control->data.control->uid);
+ } else {
+ /* it's probably a blank uid (i.e. message doesn't exist) */
+ cxn->current_control->data.control->uid = (unsigned long)-1;
+ }
+
+
+ } else if (strncasecmp(str,"OK ",3)==0) {
+
+ if (cxn->imap_state==IMAP_READING_INTRO)
+ {
+ imap_sendCapability(cxn); /* xxx errors */
+ return;
+
+ } else {
+
+ }
+
+
+ } else {
+ /* untagged command not understood */
+ }
+
+ /* always might be more to look at */
+ goto reset;
+
+ } else if ((str[0]=='+') && (str[1]==' ')) {
+
+ str+=2;
+
+ if (cxn->imap_state == IMAP_READING_STEPAUTH)
+ {
+#ifdef HAVE_SASL
+ if (imap_sendAuthStep(cxn, str)!=RET_OK)
+ {
+ imap_Disconnect(cxn);
+ }
+#else
+ d_printf(0,"%s:%d:IMAP got a '+ ...' without SASL. Something's wrong\n",
+ hostPeerName (cxn->myHost), cxn->ident);
+ imap_Disconnect(cxn);
+#endif /* HAVE_SASL */
+
+ return;
+ } else {
+ d_printf(0,"%s:%d:IMAP got a '+ ...' in state where not expected\n",
+ hostPeerName (cxn->myHost), cxn->ident);
+ imap_Disconnect(cxn);
+ return;
+ }
+
+
+
+ } else if (strncmp(str, cxn->imap_currentTag, IMAP_TAGLENGTH)==0) {
+ /* matches our tag */
+ str += IMAP_TAGLENGTH;
+
+ if (str[0]!=' ')
+ {
+ d_printf(0,"%s:%d:IMAP Parse error: tag with no space afterward\n",
+ hostPeerName (cxn->myHost), cxn->ident);
+ imap_Disconnect(cxn);
+ return;
+ }
+ str++;
+
+ /* should be OK/NO */
+ if (strncmp(str,"OK",2)==0)
+ {
+ okno = 1;
+ } else {
+ okno = 0;
+ }
+
+ switch(cxn->imap_state)
+ {
+ case IMAP_READING_CAPABILITY:
+
+ if (okno==1) {
+ if (imap_sendAuthenticate(cxn)!=RET_OK)
+ {
+ d_printf(0,"%s:%d:IMAP sendauthenticate failed\n",
+ hostPeerName (cxn->myHost), cxn->ident);
+ imap_Disconnect(cxn);
+ }
+ return;
+ } else {
+ d_printf(0,"%s:%d:IMAP CAPABILITY gave a NO response\n",
+ hostPeerName (cxn->myHost), cxn->ident);
+ imap_Disconnect(cxn);
+ }
+ return;
+
+ break;
+ case IMAP_READING_STEPAUTH:
+
+ if (okno == 1) {
+
+ cxn->imap_sleepTimeout = init_reconnect_period ;
+
+ cxn->imap_timeCon = theTime () ;
+ cxn->timeCon = theTime () ;
+
+ d_printf(0,"%s:%d IMAP authentication succeeded\n",
+ hostPeerName (cxn->myHost), cxn->ident);
+
+ cxn->imap_disconnects=0;
+
+ cxn->imap_state = IMAP_IDLE_AUTHED;
+
+ /* try to send a message if we have one */
+
+ imap_ProcessQueue(cxn);
+ } else {
+ d_printf(0,"%s:%d:IMAP Authentication failed with [%s]\n",
+ hostPeerName (cxn->myHost), cxn->ident,str);
+ imap_Disconnect(cxn);
+ }
+
+ return;
+
+ break;
+
+ case IMAP_READING_CREATE:
+
+ if (okno==1) {
+
+ d_printf(1,"%s:%d:IMAP Create of bboard successful\n",
+ hostPeerName (cxn->myHost), cxn->ident);
+
+ cxn->create_succeeded++;
+
+ /* we can delete article now */
+ QueueForgetAbout(cxn, cxn->current_control,
+ MSG_SUCCESS);
+ } else {
+ d_printf(1,"%s:%d:IMAP Create failed with [%s] for %s\n",
+ hostPeerName (cxn->myHost), cxn->ident,str,
+ cxn->current_control->data.control->folder);
+
+ ReQueue(cxn, &(cxn->imap_controlMsg_q), cxn->current_control);
+ }
+
+ imap_ProcessQueue(cxn);
+
+ break;
+
+ case IMAP_READING_DELETE:
+
+ if (okno==1) {
+ d_printf(1,"%s:%d:IMAP Delete successful\n",
+ hostPeerName (cxn->myHost), cxn->ident);
+ cxn->remove_succeeded++;
+
+ /* we can delete article now */
+ QueueForgetAbout(cxn, cxn->current_control,
+ MSG_SUCCESS);
+ } else {
+ d_printf(1,"%s:%d:IMAP Delete mailbox failed with [%s] for %s\n",
+ hostPeerName (cxn->myHost), cxn->ident,str,
+ cxn->current_control->data.control->folder);
+
+ ReQueue(cxn, &(cxn->imap_controlMsg_q), cxn->current_control);
+
+ }
+
+ imap_ProcessQueue(cxn);
+ return;
+
+ break;
+
+ case IMAP_READING_SELECT:
+
+ if (okno==1) {
+
+ imap_sendSearch(cxn, cxn->current_control->data.control->msgid);
+ return;
+
+ } else {
+ d_printf(1,"%s:%d:IMAP Select failed with [%s] for %s\n",
+ hostPeerName (cxn->myHost), cxn->ident,str,
+ cxn->current_control->data.control->folder);
+
+ ReQueue(cxn, &(cxn->imap_controlMsg_q), cxn->current_control);
+
+ cxn->imap_state = IMAP_IDLE_AUTHED;
+
+ imap_ProcessQueue(cxn);
+ return;
+ }
+
+ break;
+
+ case IMAP_READING_SEARCH:
+ /* if no message let's forget about it */
+ if (cxn->current_control->data.control->uid
+ == (unsigned long)-1) {
+ d_printf(2, "%s:%d:IMAP Search didn't find the message\n",
+ hostPeerName (cxn->myHost), cxn->ident);
+ QueueForgetAbout(cxn, cxn->current_control,
+ MSG_FAIL_DELIVER);
+ if (imap_sendClose(cxn) != RET_OK)
+ imap_Disconnect(cxn);
+ return;
+ }
+
+ if (okno==1) {
+ /* we got a uid. let's delete it */
+ if (imap_sendKill(cxn,
+ cxn->current_control->data.control->uid)
+ != RET_OK)
+ imap_Disconnect(cxn);
+ return;
+ } else {
+ d_printf(0, "%s:%d IMAP Received NO response to SEARCH\n",
+ hostPeerName (cxn->myHost), cxn->ident);
+ ReQueue(cxn, &(cxn->imap_controlMsg_q),
+ cxn->current_control);
+
+ if (imap_sendClose(cxn) != RET_OK)
+ imap_Disconnect(cxn);
+ return;
+ }
+ break;
+
+ case IMAP_READING_STORE:
+
+ if (okno==1) {
+
+ d_printf(1,"%s:%d:IMAP Processed a Cancel fully\n",
+ hostPeerName (cxn->myHost), cxn->ident);
+
+ /* we can delete article now */
+ QueueForgetAbout(cxn, cxn->current_control,
+ MSG_SUCCESS);
+
+ cxn->cancel_succeeded++;
+
+ if (imap_sendClose(cxn) != RET_OK)
+ imap_Disconnect(cxn);
+ return;
+
+ } else {
+
+ d_printf(1,"%s:%d:IMAP Store failed\n",
+ hostPeerName (cxn->myHost), cxn->ident);
+ ReQueue(cxn, &(cxn->imap_controlMsg_q), cxn->current_control);
+
+ if (imap_sendClose(cxn) != RET_OK)
+ imap_Disconnect(cxn);
+ return;
+ }
+
+ break;
+
+ case IMAP_READING_NOOP:
+ cxn->imap_state = IMAP_IDLE_AUTHED;
+ return;
+ break;
+
+ case IMAP_READING_CLOSE:
+ if (!okno) {
+ /* we can't do anything about it */
+ d_printf(1,"%s:%d:IMAP Close failed\n",
+ hostPeerName (cxn->myHost), cxn->ident);
+ }
+
+ cxn->imap_state = IMAP_IDLE_AUTHED;
+
+ imap_ProcessQueue(cxn);
+ return;
+ break;
+
+ case IMAP_READING_QUIT:
+
+ /* we don't care if the server said OK or NO just
+ that it said something */
+
+ d_printf(1,"%s:%d:IMAP Read quit response\n",
+ hostPeerName (cxn->myHost), cxn->ident);
+
+ cxn->imap_state = IMAP_DISCONNECTED;
+
+ DeleteIfDisconnected(cxn);
+ break;
+
+
+ default:
+ d_printf(0,"%s:%d:IMAP I don't understand state %d [%s]\n",
+ hostPeerName (cxn->myHost), cxn->ident,
+ cxn->imap_state,str);
+ imap_Disconnect(cxn);
+ break;
+ }
+
+
+ } else {
+ d_printf(0,"%s:%d:IMAP tag (%s) doesn't match what we gave (%s). What's up with that??\n",
+ hostPeerName (cxn->myHost), cxn->ident, str, cxn->imap_currentTag);
+ imap_Disconnect(cxn);
+ }
+
+}
+
+/************************** END IMAP reading functions ***************************/
+
+/*************************** LMTP reading functions ****************************/
+
+static void lmtp_readCB (EndPoint e, IoStatus i, Buffer *b, void *d)
+{
+ connection_t *cxn = (connection_t *) d;
+ char str[4096];
+ Buffer *readBuffers;
+ int result;
+ int response_code;
+ conn_ret ret;
+#ifdef HAVE_SASL
+ int inlen;
+ char *in;
+ int outlen;
+ const char *out;
+ char *inbase64;
+ int inbase64len;
+ imt_stat status;
+ int saslresult;
+ sasl_interact_t *client_interact=NULL;
+#endif /* HAVE_SASL */
+
+ char *p = bufferBase(b[0]);
+
+ bufferAddNullByte (b[0]) ;
+
+ if (i != IoDone) {
+ errno = endPointErrno(e);
+ syslog(LOG_ERR, "%s:%d LMTP i/o failed: %m",
+ hostPeerName (cxn->myHost), cxn->ident);
+
+ freeBufferArray (b);
+ lmtp_Disconnect(cxn);
+ return;
+ }
+
+ if (strchr (p, '\n') == NULL)
+ {
+ /* partial read. expand buffer and retry */
+
+ d_printf(0,"%s:%d:LMTP Partial. retry\n",
+ hostPeerName (cxn->myHost),cxn->ident);
+ expandBuffer (b[0], BUFFER_EXPAND_AMOUNT) ;
+ readBuffers = makeBufferArray (bufferTakeRef (b[0]), NULL) ;
+
+ if ( !prepareRead (e, readBuffers, lmtp_readCB, cxn, 1) )
+ {
+ lmtp_Disconnect(cxn);
+ }
+
+ freeBufferArray (b);
+ return;
+ }
+
+ clearTimer (cxn->lmtp_readBlockedTimerId) ;
+
+ /* Add what we got to our internal read buffer */
+ strcat(cxn->lmtp_respBuffer, p);
+
+ bufferSetDataSize( b[0], 0);
+
+ freeBufferArray (b);
+
+ reset:
+ /* see if we have a full line */
+ ret = GetLine( cxn->lmtp_respBuffer, str, sizeof(str));
+
+ /* get a line */
+ if (ret!=RET_OK)
+ {
+ if (ret!=RET_NO_FULLLINE)
+ {
+ /* was a more serious error */
+ d_printf(0,"%s:%d:LMTP Internal error getting line from server\n",
+ hostPeerName (cxn->myHost),cxn->ident);
+ lmtp_Disconnect(cxn);
+ return;
+ }
+
+ /* set up to receive some more */
+ readBuffers = makeBufferArray (bufferTakeRef (cxn->lmtp_rBuffer), NULL) ;
+ prepareRead(cxn->lmtp_endpoint, readBuffers, lmtp_readCB, cxn, 5);
+ return;
+ }
+
+ switch (cxn->lmtp_state)
+ {
+
+ case LMTP_READING_INTRO:
+
+ if (ask_code(str)!=220)
+ {
+ d_printf(0,"%s:%d:LMTP Initial server msg does not start with 220 (began with %d)\n",
+ hostPeerName (cxn->myHost),cxn->ident,
+ ask_code(str));
+ lmtp_Disconnect(cxn);
+ return;
+ }
+
+ /* the initial intro could have many lines via
+ continuations. see if we need to read more */
+ if (ask_keepgoing(str)==1)
+ {
+ goto reset;
+ }
+
+ result = lmtp_getcapabilities(cxn);
+
+ if (result != RET_OK)
+ {
+ d_printf(0,"%s:%d:LMTP lmtp_getcapabilities() failure\n",
+ hostPeerName (cxn->myHost),cxn->ident);
+ lmtp_Disconnect(cxn);
+ return;
+ }
+
+ break;
+
+ case LMTP_READING_LHLO:
+ /* recieve the response(s) */
+ response_code = ask_code(str);
+
+ if (response_code != 250) /* was none */
+ {
+ d_printf(0,"%s:%d:LMTP Response code unexpected (%d)\n",
+ hostPeerName (cxn->myHost),cxn->ident,
+ response_code);
+ lmtp_Disconnect(cxn);
+ return;
+ }
+
+ /* look for one we know about; ignore all others */
+ if (strncmp(str+4,"8BITMIME",strlen("8BITMIME"))==0)
+ {
+ cxn->lmtp_capabilities->Eightbitmime = 1;
+ } else if (strncmp(str+4, "ENHANCEDSTATUSCODES",
+ strlen("ENHANCEDSTATUSCODES"))==0) {
+ cxn->lmtp_capabilities->EnhancedStatusCodes = 1;
+ } else if (strncmp(str+4, "AUTH",4)==0) {
+ cxn->lmtp_capabilities->saslmechs = xstrdup(str + 4 + 5);
+ } else if (strncmp(str+4,"PIPELINING",strlen("PIPELINING"))==0) {
+ cxn->lmtp_capabilities->pipelining = 1;
+ } else {
+ /* don't care; ignore */
+ }
+
+ /* see if this is the last line of the capability */
+ if (ask_keepgoing(str)==1)
+ {
+ goto reset;
+ } else {
+ /* we require a few capabilities */
+ if (!cxn->lmtp_capabilities->pipelining) {
+ d_printf(0,"%s:%d:LMTP We require PIPELINING\n",
+ hostPeerName (cxn->myHost),cxn->ident);
+
+ lmtp_Disconnect(cxn);
+ return;
+ }
+#ifdef HAVE_SASL
+ if (cxn->lmtp_capabilities->saslmechs) {
+ /* start the authentication */
+ result = lmtp_authenticate(cxn);
+
+ if (result != RET_OK) {
+ d_printf(0,"%s:%d:LMTP lmtp_authenticate() error\n",
+ hostPeerName (cxn->myHost),cxn->ident);
+ lmtp_Disconnect(cxn);
+ return;
+ }
+#else
+ if (0) {
+ /* noop */
+#endif
+ } else {
+ /* either we can't authenticate or the remote server
+ doesn't support it */
+ cxn->lmtp_state = LMTP_AUTHED_IDLE;
+ d_printf(1,"%s:%d:LMTP Even though we can't authenticate"
+ " we're going to try to feed anyway\n",
+ hostPeerName (cxn->myHost),cxn->ident);
+ /* We just assume we don't need to authenticate
+ (great assumption huh?) */
+ hostRemoteStreams (cxn->myHost, cxn, true) ;
+
+ cxn->lmtp_timeCon = theTime () ;
+ cxn->timeCon = theTime () ;
+
+ /* try to send a message if we have one */
+ lmtp_sendmessage(cxn,NULL);
+ return;
+ }
+ }
+ break;
+
+#ifdef HAVE_SASL
+ case LMTP_READING_STEPAUTH:
+ inlen = 0;
+ status = lmtp_getauthline(str, &in, &inlen);
+
+ switch (status)
+ {
+
+ case STAT_CONT:
+
+ saslresult=sasl_client_step(cxn->saslconn_lmtp,
+ in,
+ inlen,
+ &client_interact,
+ &out,
+ &outlen);
+
+ free(in);
+
+ /* check if sasl succeeded */
+ if (saslresult != SASL_OK && saslresult != SASL_CONTINUE) {
+ d_printf(0,"%s:%d:LMTP sasl_client_step(): %s\n",
+ hostPeerName (cxn->myHost),cxn->ident,
+ sasl_errstring(saslresult,NULL,NULL));
+
+ lmtp_Disconnect(cxn);
+ return;
+ }
+
+ /* convert to base64 */
+ inbase64 = xmalloc(outlen*2+10);
+
+ saslresult = sasl_encode64(out, outlen,
+ inbase64, outlen*2+10,
+ (unsigned *) &inbase64len);
+
+ if (saslresult != SASL_OK)
+ {
+ d_printf(0,"%s:%d:LMTP sasl_encode64(): %s\n",
+ hostPeerName (cxn->myHost),cxn->ident,
+ sasl_errstring(saslresult,NULL,NULL));
+
+ lmtp_Disconnect(cxn);
+ return;
+ }
+
+ /* add an endline */
+ strlcpy(inbase64 + inbase64len, "\r\n", outlen * 2 + 10);
+
+ /* send to server */
+ result = WriteToWire_lmtpstr(cxn,inbase64, inbase64len+2);
+
+ if (result != RET_OK)
+ {
+ d_printf(0,"%s:%d:LMTP WriteToWire() failure\n",
+ hostPeerName (cxn->myHost),cxn->ident);
+ lmtp_Disconnect(cxn);
+ return;
+ }
+
+ cxn->lmtp_state = LMTP_WRITING_STEPAUTH;
+ break;
+
+ case STAT_OK:
+ cxn->lmtp_sleepTimeout = init_reconnect_period ;
+
+ d_printf(0,"%s:%d LMTP authentication succeeded\n",
+ hostPeerName (cxn->myHost), cxn->ident);
+
+ cxn->lmtp_disconnects=0;
+
+ hostRemoteStreams (cxn->myHost, cxn, true) ;
+
+ cxn->lmtp_timeCon = theTime () ;
+ cxn->timeCon = theTime () ;
+
+ cxn->lmtp_state = LMTP_AUTHED_IDLE;
+
+
+ /* try to send a message if we have one */
+ lmtp_sendmessage(cxn,NULL);
+ return;
+
+ break;
+
+ default:
+ d_printf(0,"%s:%d:LMTP failed authentication\n",
+ hostPeerName (cxn->myHost),cxn->ident);
+ lmtp_Disconnect(cxn);
+ return;
+ }
+ break;
+#endif /* HAVE_SASL */
+
+ case LMTP_READING_RSET:
+ if (ask_keepgoing(str)) {
+ goto reset;
+ }
+ if (ask_code(str) != 250) {
+ d_printf(0,"%s:%d:LMTP RSET failed with (%d)\n",
+ hostPeerName (cxn->myHost),cxn->ident,
+ ask_code(str));
+ lmtp_Disconnect(cxn);
+ return;
+ }
+
+ /* we pipelined so next we recieve the mail from response */
+ cxn->lmtp_state = LMTP_READING_MAILFROM;
+ goto reset;
+
+ case LMTP_READING_MAILFROM:
+ if (ask_keepgoing(str)) {
+ goto reset;
+ }
+ if (ask_code(str) != 250) {
+ d_printf(0,"%s:%d:LMTP MAILFROM failed with (%d)\n",
+ hostPeerName (cxn->myHost),cxn->ident,
+ ask_code(str));
+ lmtp_Disconnect(cxn);
+ return;
+ }
+
+ /* we pipelined so next we recieve the rcpt's */
+ cxn->lmtp_state = LMTP_READING_RCPTTO;
+ goto reset;
+ break;
+
+ case LMTP_READING_RCPTTO:
+ if (ask_keepgoing(str)) {
+ goto reset;
+ }
+ if (ask_code(str)!=250) {
+ d_printf(1,"%s:%d:LMTP RCPT TO failed with (%d) %s\n",
+ hostPeerName (cxn->myHost),cxn->ident,
+ ask_code(str), str);
+
+ /* if got a 5xx don't try to send anymore */
+ cxn->current_article->trys=100;
+
+ cxn->current_rcpts_issued--;
+ } else {
+ cxn->current_rcpts_okayed++;
+ }
+
+ /* if issued equals number okayed then we're done */
+ if ( cxn->current_rcpts_okayed == cxn->current_rcpts_issued) {
+ cxn->lmtp_state = LMTP_READING_DATA;
+ } else {
+ /* stay in same state */
+ }
+ goto reset;
+ break;
+
+ case LMTP_READING_DATA:
+ if (ask_keepgoing(str)) {
+ goto reset;
+ }
+ if (cxn->current_rcpts_issued == 0) {
+ if (cxn->current_article->trys < 100) {
+ d_printf(1, "%s:%d:LMTP None of the rcpts "
+ "were accepted for this message. Re-queueing\n",
+ hostPeerName (cxn->myHost),cxn->ident);
+ }
+
+ ReQueue(cxn, &(cxn->lmtp_todeliver_q), cxn->current_article);
+
+ cxn->lmtp_state = LMTP_AUTHED_IDLE;
+ lmtp_sendmessage(cxn,NULL);
+ } else {
+ if (WriteArticle(cxn, cxn->current_bufs) != RET_OK)
+ {
+ d_printf(0, "%s:%d:LMTP Error writing article\n",
+ hostPeerName (cxn->myHost),cxn->ident);
+ lmtp_Disconnect(cxn);
+ return;
+ }
+
+ cxn->lmtp_state = LMTP_WRITING_CONTENTS;
+ }
+
+ break;
+
+ case LMTP_READING_CONTENTS:
+ if (ask_keepgoing(str)) {
+ goto reset;
+ }
+
+ /* need 1 response from server for every rcpt */
+ cxn->current_rcpts_issued--;
+
+ if (ask_code(str) != 250) {
+ d_printf(1, "%s:%d:LMTP DATA failed with %d (%s)\n",
+ hostPeerName (cxn->myHost),cxn->ident,
+ ask_code(str), str);
+ cxn->current_rcpts_okayed--;
+ }
+
+ if (cxn->current_rcpts_issued>0) {
+ goto reset;
+ }
+
+ /*
+ * current_rcpts_okayed is number that succeeded
+ *
+ */
+ if (cxn->current_rcpts_okayed == 0) {
+ cxn->lmtp_state = LMTP_AUTHED_IDLE;
+ } else {
+ cxn->lmtp_state = LMTP_AUTHED_IDLE;
+ cxn->lmtp_succeeded++;
+ d_printf(1, "%s:%d:LMTP Woohoo! message accepted\n",
+ hostPeerName (cxn->myHost),cxn->ident);
+ }
+
+ /* we can delete article now */
+ QueueForgetAbout(cxn, cxn->current_article, MSG_SUCCESS);
+
+ /* try to send another if we have one and we're still idle
+ * forgetting the msg might have made us unidle
+ */
+ if (cxn->lmtp_state == LMTP_AUTHED_IDLE) {
+ lmtp_sendmessage(cxn,NULL);
+ }
+
+ break;
+
+ case LMTP_READING_NOOP:
+ if (ask_keepgoing(str)) {
+ goto reset;
+ }
+ cxn->lmtp_state = LMTP_AUTHED_IDLE;
+ break;
+
+ case LMTP_READING_QUIT:
+ d_printf(1,"%s:%d:LMTP read quit\n",
+ hostPeerName (cxn->myHost),cxn->ident);
+
+ cxn->lmtp_state = LMTP_DISCONNECTED;
+
+ DeleteIfDisconnected(cxn);
+ break;
+
+ default:
+
+ d_printf(0,"%s:%d:LMTP Bad state in lmtp_readCB %d\n",
+ hostPeerName (cxn->myHost),cxn->ident,
+ cxn->lmtp_state);
+ lmtp_Disconnect(cxn);
+ return;
+ }
+
+
+}
+
+/*
+ * Add a rcpt to:<foo> to the string
+ *
+ */
+
+static void addrcpt(char *newrcpt, int newrcptlen, char **out, int *outalloc)
+{
+ int size = strlen(*out);
+ int fsize = size;
+ int newsize = size + 9+strlen(deliver_rcpt_to)+newrcptlen+3;
+ char c;
+
+ /* see if we need to grow the string */
+ if (newsize > *outalloc) {
+ (*outalloc) = newsize;
+ (*out) = xrealloc(*out, *outalloc);
+ }
+
+ strlcpy((*out) + size,"RCPT TO:<", newsize - size);
+ size += 9;
+
+ c = newrcpt[newrcptlen];
+ newrcpt[newrcptlen] = '\0';
+ size += snprintf((*out) + size, newsize - size, deliver_rcpt_to, newrcpt);
+ newrcpt[newrcptlen] = c;
+
+ strlcpy((*out) + size, ">\r\n", newsize - size);
+
+ /* has embedded '\n' */
+ d_printf(2, "Attempting to send to: %s", (*out) + fsize);
+}
+
+/*
+ * Takes the newsgroups header value and makes it into a list of RCPT TO:'s we can send over the wire
+ *
+ * in - newsgroups header start
+ * in_end - end of newsgroups header
+ * num - number of rcpt's we created
+ */
+
+static char *ConvertRcptList(char *in, char *in_end, int *num)
+{
+ int retalloc = 400;
+ char *ret = xmalloc(retalloc);
+ char *str = in;
+ char *laststart = in;
+
+ (*num) = 0;
+
+ /* start it off empty */
+ strlcpy(ret, "", retalloc);
+
+ while ( str != in_end)
+ {
+ if ((*str) == ',')
+ {
+ /* eliminate leading whitespace */
+ while (((*laststart) ==' ') || ((*laststart)=='\t'))
+ {
+ laststart++;
+ }
+
+#ifndef SMTPMODE
+ addrcpt(laststart, str - laststart, &ret, &retalloc);
+ (*num)++;
+#endif /* SMTPMODE */
+ laststart = str+1;
+ }
+
+ str++;
+ }
+
+ if (laststart<str)
+ {
+ addrcpt(laststart, str - laststart, &ret, &retalloc);
+ (*num)++;
+ }
+
+ return ret;
+}
+
+static void addto(char *newrcpt, int newrcptlen, const char *sep,
+ char **out, int *outalloc)
+{
+ int size = strlen(*out);
+ int newsize = size + strlen(sep)+1+strlen(deliver_to_header)+newrcptlen+1;
+ char c;
+
+ /* see if we need to grow the string */
+ if (newsize > *outalloc) {
+ (*outalloc) = newsize;
+ (*out) = xrealloc(*out, *outalloc);
+ }
+
+ size += snprintf((*out) + size, newsize - size, "%s<", sep);
+
+ c = newrcpt[newrcptlen];
+ newrcpt[newrcptlen] = '\0';
+ size += snprintf((*out) + size, newsize - size, deliver_to_header,newrcpt);
+ newrcpt[newrcptlen] = c;
+
+ strlcpy((*out) + size, ">", newsize - size);
+}
+
+/*
+ * Takes the newsgroups header value and makes it into a To: header
+ *
+ * in - newsgroups header start
+ * in_end - end of newsgroups header
+ */
+
+static char *BuildToHeader(char *in, char *in_end)
+{
+ int retalloc = 400;
+ char *ret = xmalloc(retalloc);
+ char *str = in;
+ char *laststart = in;
+ const char *sep = "";
+
+ /* start it off with the header name */
+ strlcpy(ret,"To: ", retalloc);
+
+ while ( str != in_end)
+ {
+ if ((*str) == ',')
+ {
+ /* eliminate leading whitespace */
+ while (((*laststart) ==' ') || ((*laststart)=='\t'))
+ {
+ laststart++;
+ }
+
+ addto(laststart, str - laststart, sep, &ret, &retalloc);
+ laststart = str+1;
+
+ /* separate multiple addresses with a comma */
+ sep = ", ";
+ }
+
+ str++;
+ }
+
+ if (laststart<str)
+ {
+ addto(laststart, str - laststart, sep, &ret, &retalloc);
+ }
+
+ /* terminate the header */
+ strlcat(ret, "\n\r", retalloc);
+ return ret;
+}
+
+/*************************** END LMTP reading functions ****************************/
+
+
+
+/*
+ * Process the control message queue. If we run out ask the host for more.
+ *
+ * cxn - connection object
+ */
+
+static void imap_ProcessQueue(connection_t *cxn)
+{
+ article_queue_t *item;
+ int result;
+
+ retry:
+
+ /* pull an article off the queue */
+ result = PopFromQueue(&(cxn->imap_controlMsg_q), &item);
+
+ if (result==RET_QUEUE_EMPTY)
+ {
+ if (cxn->issue_quit)
+ {
+ imap_sendQuit(cxn);
+ return;
+ }
+
+ cxn->imap_state = IMAP_IDLE_AUTHED;
+
+ /* now we wait for articles from our Host, or we have some
+ articles already. On infrequently used connections, the
+ network link is torn down and rebuilt as needed. So we may
+ be rebuilding the connection here in which case we have an
+ article to send. */
+
+ /* make sure imap has _lots_ of space too */
+ if ((QueueItems(&(cxn->lmtp_todeliver_q)) == 0) &&
+ (QueueItems(&(cxn->imap_controlMsg_q)) == 0))
+ {
+ if (hostGimmeArticle (cxn->myHost,cxn)==true)
+ goto retry;
+ }
+
+ return;
+ }
+
+ cxn->current_control = item;
+
+ switch (item->type)
+ {
+ case CREATE_FOLDER:
+ imap_CreateGroup(cxn, item->data.control->folder);
+ break;
+
+ case CANCEL_MSG:
+ imap_CancelMsg(cxn, item->data.control->folder);
+ break;
+
+ case DELETE_FOLDER:
+ imap_DeleteGroup(cxn, item->data.control->folder);
+ break;
+ default:
+ break;
+ }
+
+ return;
+}
+
+
+
+/*
+ *
+ * Pulls a message off the queue and trys to start sending it. If the
+ * message is a control message put it in the control queue and grab
+ * another message. If the message doesn't exist on disk or something
+ * is wrong with it tell the host and try again. If we run out of
+ * messages to get tell the host we want more
+ *
+ * cxn - connection object
+ * justadded - the article that was just added to the queue
+ */
+
+static void lmtp_sendmessage(connection_t *cxn, Article justadded)
+{
+ bool res;
+ conn_ret result;
+ char *p;
+ Buffer *bufs;
+ char *control_header = NULL;
+ char *control_header_end = NULL;
+
+ article_queue_t *item;
+ char *rcpt_list, *rcpt_list_end;
+
+ /* retry point */
+ retry:
+
+ /* pull an article off the queue */
+ result = PopFromQueue(&(cxn->lmtp_todeliver_q), &item);
+
+ if (result==RET_QUEUE_EMPTY)
+ {
+ if (cxn->issue_quit) {
+ lmtp_IssueQuit(cxn);
+ return;
+ }
+ /* now we wait for articles from our Host, or we have some
+ articles already. On infrequently used connections, the
+ network link is torn down and rebuilt as needed. So we may
+ be rebuilding the connection here in which case we have an
+ article to send. */
+
+ /* make sure imap has space too */
+ d_printf(1,"%s:%d stalled waiting for articles\n",
+ hostPeerName (cxn->myHost),cxn->ident);
+ if ((QueueItems(&(cxn->lmtp_todeliver_q)) == 0) &&
+ (QueueItems(&(cxn->imap_controlMsg_q)) == 0)) {
+ if (hostGimmeArticle (cxn->myHost,cxn)==true)
+ goto retry;
+ }
+
+ return;
+ }
+
+ /* make sure contents ok; this also should load it into memory */
+ res = artContentsOk (item->data.article);
+ if (res==false)
+ {
+ if (justadded == item->data.article) {
+ ReQueue(cxn, &(cxn->lmtp_todeliver_q), item);
+ return;
+ } else {
+ /* tell to reject taking this message */
+ QueueForgetAbout(cxn,item, MSG_MISSING);
+ }
+
+ goto retry;
+ }
+
+ /* Check if it's a control message */
+ bufs = artGetNntpBuffers (item->data.article);
+ if (bufs == NULL)
+ {
+ /* tell to reject taking this message */
+ QueueForgetAbout(cxn,item, MSG_MISSING);
+ goto retry;
+ }
+
+ result = FindHeader(bufs, "Control", &control_header, &control_header_end);
+ if (result == RET_OK) {
+ result = AddControlMsg(cxn, item->data.article, bufs,
+ control_header,control_header_end, 1);
+ if (result != RET_OK) {
+ d_printf(1,"%s:%d Error adding to [imap] control queue\n",
+ hostPeerName (cxn->myHost),cxn->ident) ;
+ ReQueue(cxn, &(cxn->lmtp_todeliver_q), item);
+ return;
+ }
+
+ switch(cxn->imap_state) {
+ case IMAP_IDLE_AUTHED:
+ /* we're idle. let's process the queue */
+ imap_ProcessQueue(cxn);
+ break;
+ case IMAP_DISCONNECTED:
+ case IMAP_WAITING:
+ /* Let's connect. Once we're connected we can
+ worry about the message */
+ if (cxn->imap_sleepTimerId == 0) {
+ if (imap_Connect(cxn) != RET_OK) prepareReopenCbk(cxn,0);
+ }
+ break;
+ default:
+ /* we're doing something right now */
+ break;
+ }
+
+ /* all we did was add a control message.
+ we still want to get an lmtp message */
+ goto retry;
+ }
+
+ if (cxn->current_bufs != NULL) {
+ /* freeBufferArray(cxn->current_bufs); */
+ cxn->current_bufs = NULL;
+ }
+ cxn->current_bufs = bufs;
+ cxn->current_article = item;
+
+ /* we make use of pipelining here
+ send:
+ rset
+ mail from
+ rcpt to
+ data
+ */
+
+ /* find out who it's going to */
+ result = FindHeader(cxn->current_bufs, "Newsgroups",
+ &rcpt_list, &rcpt_list_end);
+
+ if ((result != RET_OK) || (rcpt_list == NULL)) {
+ d_printf(1,"%s:%d Didn't find Newsgroups header\n",
+ hostPeerName (cxn->myHost),cxn->ident) ;
+ QueueForgetAbout(cxn, cxn->current_article, MSG_FAIL_DELIVER);
+ goto retry;
+ }
+
+ /* free's original rcpt_list */
+ rcpt_list = ConvertRcptList(rcpt_list, rcpt_list_end,
+ &cxn->current_rcpts_issued);
+ cxn->current_rcpts_okayed = 0;
+
+ if(mailfrom_name == NULL)
+ mailfrom_name = xstrdup("");
+ p = concat("RSET\r\n"
+ "MAIL FROM:<", mailfrom_name, ">\r\n",
+ rcpt_list,
+ "DATA\r\n", (char *) 0);
+
+ cxn->lmtp_state = LMTP_WRITING_UPTODATA;
+ result = WriteToWire_lmtpstr(cxn, p, strlen(p));
+
+ if (result != RET_OK) {
+ d_printf(0,"%s:%d failed trying to write\n",
+ hostPeerName (cxn->myHost),cxn->ident) ;
+ lmtp_Disconnect(cxn);
+ return;
+ }
+
+ /* prepend To: header to article */
+ if (deliver_to_header) {
+ char *to_list, *to_list_end;
+ int i, len;
+
+ result = FindHeader(cxn->current_bufs, "Followup-To",
+ &to_list, &to_list_end);
+
+ if ((result != RET_OK) || (to_list == NULL)) {
+ result = FindHeader(cxn->current_bufs, "Newsgroups",
+ &to_list, &to_list_end);
+ }
+
+ /* free's original to_list */
+ to_list = BuildToHeader(to_list, to_list_end);
+
+ len = bufferArrayLen(cxn->current_bufs);
+ cxn->current_bufs = xrealloc(cxn->current_bufs,
+ sizeof(Buffer) * (len+2));
+ cxn->current_bufs[len+1] = NULL;
+
+ for (i = len; i > 0; i--) {
+ cxn->current_bufs[i] = cxn->current_bufs[i-1];
+ }
+
+ cxn->current_bufs[0] = newBufferByCharP(to_list, strlen(to_list+1),
+ strlen(to_list));
+ }
+
+ hostArticleOffered (cxn->myHost, cxn);
+}
+
+/*
+ * Called by the EndPoint class when the timer goes off
+ */
+static void dosomethingTimeoutCbk (TimeoutId id, void *data)
+{
+ Connection cxn = (Connection) data ;
+
+ ASSERT (id == cxn->dosomethingTimerId) ;
+
+ show_stats(cxn);
+
+ /* we're disconnected but there are things to send */
+ if ((cxn->lmtp_state == LMTP_DISCONNECTED) &&
+ (cxn->lmtp_sleepTimerId == 0) &&
+ QueueItems(&(cxn->lmtp_todeliver_q)) > 0)
+ {
+ if (lmtp_Connect(cxn) != RET_OK)
+ prepareReopenCbk(cxn, 1);
+ }
+
+ if ((cxn->imap_state == IMAP_DISCONNECTED) &&
+ (cxn->imap_sleepTimerId == 0) &&
+ (QueueItems(&(cxn->imap_controlMsg_q)) > 0))
+ {
+ if (imap_Connect(cxn) != RET_OK)
+ prepareReopenCbk(cxn, 0);
+ }
+
+
+ /* if we're idle and there are items to send let's send them */
+ if ((cxn->lmtp_state == LMTP_AUTHED_IDLE) &&
+ QueueItems(&(cxn->lmtp_todeliver_q)) > 0) {
+ lmtp_sendmessage(cxn,NULL);
+ } else if (cxn->lmtp_state == LMTP_AUTHED_IDLE) {
+ lmtp_noop(cxn);
+ }
+
+ if ((cxn->imap_state == IMAP_IDLE_AUTHED) &&
+ (QueueItems(&(cxn->imap_controlMsg_q)) > 0)) {
+ imap_ProcessQueue(cxn);
+ } else if (cxn->imap_state == IMAP_IDLE_AUTHED) {
+ imap_noop(cxn);
+ }
+
+ /* set up the timer. */
+ clearTimer (cxn->dosomethingTimerId) ;
+
+ cxn->dosomethingTimerId = prepareSleep (dosomethingTimeoutCbk,
+ cxn->dosomethingTimeout, cxn);
+}
+
+/* Give all articles in the queue back to the host. We're probably
+ * going to exit soon.
+ * */
+
+static void DeferAllArticles(connection_t *cxn, Q_t *q)
+{
+ article_queue_t *cur;
+ conn_ret ret;
+
+ while (1)
+ {
+ ret = PopFromQueue(q, &cur);
+ if (ret == RET_QUEUE_EMPTY) return;
+
+ if (ret == RET_OK)
+ {
+ QueueForgetAbout(cxn, cur, MSG_GIVE_BACK);
+ } else {
+ d_printf(0,"%s:%d Error emptying queue (deffering all articles)\n",
+ hostPeerName (cxn->myHost),cxn->ident);
+ return;
+ }
+ }
+}
+
+/*
+ * Does the actual deletion of a connection and all its private data.
+ */
+static void delConnection (Connection cxn)
+{
+ bool shutDown;
+ Connection c, q;
+
+ if (cxn == NULL)
+ return ;
+
+ d_printf (1,"Deleting connection: %s:%d\n",
+ hostPeerName (cxn->myHost),cxn->ident) ;
+
+ for (c = gCxnList, q = NULL ; c != NULL ; q = c, c = c->next)
+ if (c == cxn)
+ {
+ if (gCxnList == c)
+ gCxnList = gCxnList->next ;
+ else
+ q->next = c->next ;
+ break ;
+ }
+
+ ASSERT (c != NULL) ;
+
+ if (cxn->lmtp_endpoint != NULL)
+ delEndPoint (cxn->lmtp_endpoint) ;
+ if (cxn->imap_endpoint != NULL)
+ delEndPoint (cxn->imap_endpoint) ;
+
+ delBuffer (cxn->imap_rBuffer) ;
+ delBuffer (cxn->lmtp_rBuffer) ;
+
+ /* tell the Host we're outta here. */
+ shutDown = hostCxnGone (cxn->myHost, cxn) ;
+
+ cxn->ident = 0 ;
+ cxn->timeCon = 0 ;
+
+ free (cxn->ServerName) ;
+
+ clearTimer (cxn->imap_readBlockedTimerId) ;
+ clearTimer (cxn->imap_writeBlockedTimerId) ;
+ clearTimer (cxn->lmtp_readBlockedTimerId) ;
+ clearTimer (cxn->lmtp_writeBlockedTimerId) ;
+
+ clearTimer (cxn->imap_sleepTimerId);
+ cxn->imap_sleepTimerId = 0;
+ clearTimer (cxn->lmtp_sleepTimerId);
+ cxn->lmtp_sleepTimerId = 0;
+
+ clearTimer (cxn->dosomethingTimerId);
+
+ free (cxn->imap_respBuffer);
+ free (cxn->lmtp_respBuffer);
+
+ free (cxn) ;
+
+ if (shutDown)
+ {
+ /* exit program if that was the last connexion for the last host */
+ /* XXX what about if there are ever multiple listeners?
+ XXX this will be executed if all hosts on only one of the
+ XXX listeners have gone */
+ time_t now = theTime () ;
+ char dateString [30] ;
+
+ strlcpy (dateString,ctime (&now),sizeof (dateString)) ;
+ dateString [24] = '\0' ;
+
+ notice ("ME finishing at %s", dateString) ;
+
+ exit (0) ;
+ }
+}
+
+
+/******************** PUBLIC FUNCTIONS ****************************/
+
+
+
+ /*
+ * Create a new Connection.
+ *
+ * HOST is the host object we're owned by.
+ * IDENT is an identifier to be added to syslog entries so we can tell
+ * what's happening on different connections to the same peer.
+ * IPNAME is the name (or ip address) of the remote)
+ * MAXTOUT is the maximum amount of time to wait for a response before
+ * considering the remote host dead.
+ * PORTNUM is the portnum to contact on the remote end.
+ * RESPTIMEOUT is the amount of time to wait for a response from a remote
+ * before considering the connection dead.
+ * CLOSEPERIOD is the number of seconds after connecting that the
+ * connections should be closed down and reinitialized (due to problems
+ * with old NNTP servers that hold history files open. Value of 0 means
+ * no close down.
+ */
+
+Connection newConnection (Host host,
+ unsigned int ident,
+ const char *ipname,
+ unsigned int artTout UNUSED,
+ unsigned int portNum UNUSED,
+ unsigned int respTimeout,
+ unsigned int closePeriod UNUSED,
+ double lowPassLow UNUSED,
+ double lowPassHigh UNUSED,
+ double lowPassFilter UNUSED)
+{
+ Connection cxn;
+ /* check arguments */
+
+ /* allocate connection structure */
+ cxn = xcalloc (1, sizeof(connection_t)) ;
+
+ cxn->ident = ident ;
+ cxn->ServerName = xstrdup (ipname) ;
+ cxn->myHost = host ;
+
+ /* setup mailfrom user */
+ if (gethostname(hostname, MAXHOSTNAMELEN)!=0)
+ {
+ d_printf(0,"%s gethostname failed\n",ipname);
+ return NULL;
+ }
+
+
+ mailfrom_name = concat("news@", hostname, (char *) 0);
+
+ cxn->next = gCxnList ;
+ gCxnList = cxn ;
+ gCxnCount++ ;
+
+ /* init stuff */
+ Initialize(cxn, respTimeout);
+
+ return cxn;
+}
+
+
+/* Causes the Connection to build the network connection. */
+bool cxnConnect (Connection cxn)
+{
+ /* make the lmtp connection */
+ if (lmtp_Connect(cxn) != RET_OK) return false;
+
+ if (imap_Connect(cxn) != RET_OK) return false;
+
+ return true;
+}
+
+
+static void QuitIfIdle(Connection cxn)
+{
+ if ((cxn->lmtp_state == LMTP_AUTHED_IDLE) &&
+ (QueueItems(&(cxn->lmtp_todeliver_q))<=0)) {
+ lmtp_IssueQuit(cxn);
+ }
+ if ((cxn->imap_state == IMAP_IDLE_AUTHED) &&
+ (QueueItems(&(cxn->imap_controlMsg_q))<=0)) {
+ imap_sendQuit(cxn);
+ }
+}
+
+static void DeleteIfDisconnected(Connection cxn)
+{
+ /* we want to shut everything down. if both connections disconnected now we can */
+ if ((cxn->issue_quit >= 1) &&
+ (cxn->lmtp_state == LMTP_DISCONNECTED) &&
+ (cxn->imap_state == IMAP_DISCONNECTED))
+ {
+
+ switch (cxn->issue_quit)
+ {
+ case 1:
+ if (cxn->lmtp_state == LMTP_DISCONNECTED)
+ {
+ cxn->lmtp_state = LMTP_WAITING;
+ cxn->imap_state = IMAP_WAITING;
+ cxn->issue_quit = 0;
+ hostCxnWaiting (cxn->myHost,cxn) ; /* tell our Host we're waiting */
+ }
+ break;
+ case 2:
+ if (cxn->lmtp_state == LMTP_DISCONNECTED)
+ {
+ cxn->issue_quit = 0;
+
+ if (imap_Connect(cxn)!=RET_OK) prepareReopenCbk(cxn,0);
+ if (lmtp_Connect(cxn)!=RET_OK) prepareReopenCbk(cxn,1);
+ }
+ break;
+ case 3:
+ if (cxn->lmtp_state == LMTP_DISCONNECTED)
+ {
+ hostCxnDead (cxn->myHost,cxn) ;
+ delConnection(cxn);
+ }
+ break;
+
+ }
+ }
+}
+
+ /* puts the connection into the wait state (i.e. waits for an article
+ before initiating a connect). Can only be called right after
+ newConnection returns, or while the Connection is in the (internal)
+ Sleeping state. */
+void cxnWait (Connection cxn)
+{
+ cxn->issue_quit = 1;
+
+ QuitIfIdle(cxn);
+}
+
+ /* The Connection will disconnect as if cxnDisconnect were called and then
+ it automatically reconnects to the remote. */
+void cxnFlush (Connection cxn)
+{
+ cxn->issue_quit = 2;
+
+ QuitIfIdle(cxn);
+}
+
+
+
+ /* The Connection sends remaining articles, then issues a QUIT and then
+ deletes itself */
+void cxnClose (Connection cxn)
+{
+ d_printf(0,"%s:%d Closing cxn\n",hostPeerName (cxn->myHost), cxn->ident);
+ cxn->issue_quit = 3;
+
+ QuitIfIdle(cxn);
+
+ DeleteIfDisconnected(cxn);
+}
+
+ /* The Connection drops all queueed articles, then issues a QUIT and then
+ deletes itself */
+void cxnTerminate (Connection cxn)
+{
+ d_printf(0,"%s:%d Terminate\n",hostPeerName (cxn->myHost), cxn->ident);
+
+ cxn->issue_quit = 3;
+
+ /* give any articles back to host in both queues */
+ DeferAllArticles(cxn, &(cxn->lmtp_todeliver_q));
+ DeferAllArticles(cxn, &(cxn->imap_controlMsg_q));
+
+ QuitIfIdle(cxn);
+}
+
+ /* Blow away the connection gracelessly and immedately clean up */
+void cxnNuke (Connection cxn)
+{
+ d_printf(0,"%s:%d Nuking connection\n",cxn->ServerName, cxn->ident);
+
+ cxn->issue_quit = 4;
+
+ /* give any articles back to host in both queues */
+ DeferAllArticles(cxn, &(cxn->lmtp_todeliver_q));
+ DeferAllArticles(cxn, &(cxn->imap_controlMsg_q));
+
+ imap_Disconnect(cxn);
+ lmtp_Disconnect(cxn);
+
+ hostCxnDead (cxn->myHost,cxn);
+ delConnection(cxn);
+}
+
+/*
+ * must
+ * true - must queue article. Don't try sending
+ * false - queue of article may fail. Try sending
+ *
+ * Always adds to lmtp queue even if control message
+ *
+ */
+
+static bool ProcessArticle(Connection cxn, Article art, bool must)
+{
+ conn_ret result;
+
+ /* Don't accept any articles when we're closing down the connection */
+ if (cxn->issue_quit > 1) {
+ return false;
+ }
+
+ /* if it's a regular message let's add it to the queue */
+ result = AddToQueue(&(cxn->lmtp_todeliver_q), art, DELIVER,1,must);
+
+ if (result == RET_EXCEEDS_SIZE) {
+ return false;
+ }
+
+ if (result != RET_OK)
+ {
+ d_printf(0,"%s:%d Error adding to delivery queue\n",
+ hostPeerName (cxn->myHost), cxn->ident);
+ return must;
+ }
+
+ if (must == true) return true;
+
+ switch (cxn->lmtp_state)
+ {
+ case LMTP_WAITING:
+ case LMTP_DISCONNECTED:
+ if (cxn->lmtp_sleepTimerId == 0)
+ if (lmtp_Connect(cxn) != RET_OK) prepareReopenCbk(cxn,1);
+ break;
+
+ case LMTP_AUTHED_IDLE:
+ lmtp_sendmessage(cxn,art);
+ break;
+ default:
+ /* currently doing something */
+ break;
+ }
+
+ return true;
+}
+
+ /* Tells the Connection to take the article and handle its
+ transmission. If it can't (due to queue size or whatever), then the
+ function returns false. The connection assumes ownership of the
+ article if it accepts it (returns true). */
+bool cxnTakeArticle (Connection cxn, Article art)
+{
+ /* if we're closing down always refuse */
+ if (cxn->issue_quit == 1) return false;
+
+ return ProcessArticle (cxn,art,false);
+}
+
+ /* Tell the Connection to take the article (if it can) for later
+ processing. Assumes ownership of it if it takes it. */
+bool cxnQueueArticle (Connection cxn, Article art)
+{
+ return ProcessArticle (cxn,art,true);
+}
+
+/* generate a syslog message for the connections activity. Called by Host. */
+void cxnLogStats (Connection cxn, bool final)
+{
+ const char *peerName ;
+ time_t now = theTime() ;
+ int total, good, bad;
+
+ ASSERT (cxn != NULL) ;
+
+ peerName = hostPeerName (cxn->myHost) ;
+
+ total = cxn->lmtp_succeeded + cxn->lmtp_failed;
+ total += cxn->cancel_succeeded + cxn->cancel_failed;
+ total += cxn->create_succeeded + cxn->create_failed;
+ total += cxn->remove_succeeded + cxn->remove_failed;
+
+ good = cxn->lmtp_succeeded;
+ good += cxn->cancel_succeeded;
+ good += cxn->create_succeeded;
+ good += cxn->remove_succeeded;
+
+ bad = cxn->lmtp_failed;
+ bad += cxn->cancel_failed;
+ bad += cxn->create_failed;
+ bad += cxn->remove_failed;
+ notice ("%s:%d %s seconds %ld accepted %d refused %d rejected %d",
+ peerName, cxn->ident, (final ? "final" : "checkpoint"),
+ (long) (now - cxn->timeCon), total, 0, bad);
+ show_stats(cxn);
+
+ if (final) {
+ cxn->lmtp_succeeded = 0;
+ cxn->lmtp_failed = 0;
+ cxn->cancel_succeeded = 0;
+ cxn->cancel_failed = 0;
+ cxn->create_succeeded = 0;
+ cxn->create_failed = 0;
+ cxn->remove_succeeded = 0;
+ cxn->remove_failed = 0;
+
+ if (cxn->timeCon > 0)
+ cxn->timeCon = theTime() ;
+ }
+
+}
+
+ /* return the number of articles the connection can be given. This lets
+ the host shovel in as many as possible. May be zero. */
+size_t cxnQueueSpace (Connection cxn)
+{
+ int lmtpsize;
+ int imapsize;
+
+ lmtpsize = QueueSpace(&(cxn->lmtp_todeliver_q));
+ imapsize = QueueSpace(&(cxn->imap_controlMsg_q));
+
+ if (lmtpsize >=1) lmtpsize--;
+ if (imapsize >=1) imapsize--;
+
+ d_printf(1,"%s:%d Q Space lmtp size = %d state = %d\n",
+ hostPeerName (cxn->myHost), cxn->ident,
+ lmtpsize,cxn->lmtp_state);
+ d_printf(1,"%s:%d Q Space imap size = %d state = %d\n",
+ hostPeerName (cxn->myHost), cxn->ident,
+ imapsize,cxn->imap_state);
+
+ /* return the smaller of our 2 queues */
+ if (lmtpsize < imapsize)
+ return lmtpsize;
+ else
+ return imapsize;
+}
+
+ /* adjust the mode no-CHECK filter values */
+void cxnSetCheckThresholds (Connection cxn,
+ double lowFilter UNUSED,
+ double highFilter UNUSED,
+ double lowPassFilter UNUSED)
+{
+ d_printf(1,"%s:%d Threshold change. This means nothing to me\n",
+ hostPeerName (cxn->myHost), cxn->ident);
+}
+
+/* print some debugging info. */
+void gPrintCxnInfo (FILE *fp, unsigned int indentAmt)
+{
+ char indent [INDENT_BUFFER_SIZE] ;
+ unsigned int i ;
+ Connection cxn ;
+
+ for (i = 0 ; i < MIN(INDENT_BUFFER_SIZE - 1,indentAmt) ; i++)
+ indent [i] = ' ' ;
+ indent [i] = '\0' ;
+
+ fprintf (fp,"%sGlobal Connection list : (count %d) {\n",
+ indent,gCxnCount) ;
+ for (cxn = gCxnList ; cxn != NULL ; cxn = cxn->next)
+ printCxnInfo (cxn,fp,indentAmt + INDENT_INCR) ;
+ fprintf (fp,"%s}\n",indent) ;
+}
+
+void printCxnInfo (Connection cxn, FILE *fp, unsigned int indentAmt)
+{
+ char indent [INDENT_BUFFER_SIZE] ;
+ unsigned int i ;
+ article_queue_t *artH ;
+
+ for (i = 0 ; i < MIN(INDENT_BUFFER_SIZE - 1,indentAmt) ; i++)
+ indent [i] = ' ' ;
+ indent [i] = '\0' ;
+
+ fprintf (fp,"%sConnection : %p {\n",indent, (void *) cxn) ;
+ fprintf (fp,"%s host : %p\n",indent, (void *) cxn->myHost) ;
+ fprintf (fp,"%s endpoint (imap): %p\n",indent, (void *) cxn->imap_endpoint) ;
+ fprintf (fp,"%s endpoint (lmtp): %p\n",indent, (void *) cxn->lmtp_endpoint) ;
+ fprintf (fp,"%s state (imap) : %s\n",indent, imap_stateToString (cxn->imap_state)) ;
+ fprintf (fp,"%s state (lmtp) : %s\n",indent, lmtp_stateToString (cxn->lmtp_state)) ;
+ fprintf (fp,"%s ident : %d\n",indent,cxn->ident) ;
+ fprintf (fp,"%s ip-name (imap): %s\n", indent, cxn->ServerName) ;
+ fprintf (fp,"%s ip-name (lmtp): %s\n", indent, cxn->ServerName) ;
+ fprintf (fp,"%s port-number (imap) : %d\n",indent,cxn->imap_port) ;
+ fprintf (fp,"%s port-number (lmtp) : %d\n",indent,cxn->lmtp_port) ;
+
+ fprintf (fp,"%s Issuing Quit : %d\n",indent, cxn->issue_quit) ;
+
+ fprintf (fp,"%s time-connected (imap) : %ld\n",indent,(long) cxn->imap_timeCon) ;
+ fprintf (fp,"%s time-connected (lmtp) : %ld\n",indent,(long) cxn->lmtp_timeCon) ;
+ fprintf (fp,"%s articles from INN : %d\n",indent,
+ cxn->lmtp_succeeded+
+ cxn->lmtp_failed+
+ cxn->cancel_succeeded+
+ cxn->cancel_failed+
+ cxn->create_succeeded+
+ cxn->create_failed+
+ cxn->remove_succeeded+
+ cxn->remove_failed+
+ QueueSpace(&(cxn->lmtp_todeliver_q))+
+ QueueSpace(&(cxn->imap_controlMsg_q))
+ );
+ fprintf(fp,"%s LMTP STATS: yes: %d no: %d\n",indent,
+ cxn->lmtp_succeeded, cxn->lmtp_failed);
+ fprintf(fp,"%s control: yes: %d no: %d\n",indent,
+ cxn->cancel_succeeded, cxn->cancel_failed);
+ fprintf(fp,"%s create: yes: %d no: %d\n",indent,
+ cxn->create_succeeded, cxn->create_failed);
+ fprintf(fp,"%s remove: yes: %d no: %d\n",indent,
+ cxn->remove_succeeded, cxn->remove_failed);
+
+ fprintf (fp,"%s response-timeout : %d\n",indent,cxn->imap_readTimeout) ;
+ fprintf (fp,"%s response-callback : %d\n",indent,cxn->imap_readBlockedTimerId) ;
+
+ fprintf (fp,"%s write-timeout : %d\n",indent,cxn->imap_writeTimeout) ;
+ fprintf (fp,"%s write-callback : %d\n",indent,cxn->imap_writeBlockedTimerId) ;
+
+ fprintf (fp,"%s reopen wait : %d\n",indent,cxn->imap_sleepTimeout) ;
+ fprintf (fp,"%s reopen id : %d\n",indent,cxn->imap_sleepTimerId) ;
+
+ fprintf (fp,"%s IMAP queue {\n",indent) ;
+ for (artH = cxn->imap_controlMsg_q.head; artH != NULL ; artH = artH->next)
+ printArticleInfo (artH->data.article,fp,indentAmt + INDENT_INCR) ;
+ fprintf (fp,"%s }\n",indent) ;
+
+ fprintf (fp,"%s LMTP queue {\n",indent) ;
+ for (artH = cxn->lmtp_todeliver_q.head ; artH != NULL ; artH = artH->next)
+ printArticleInfo (artH->data.control->article,fp,indentAmt + INDENT_INCR) ;
+ fprintf (fp,"%s }\n",indent) ;
+
+ fprintf (fp,"%s}\n",indent) ;
+}
+
+/* config file load callback */
+int cxnConfigLoadCbk (void *data UNUSED)
+{
+ long iv ;
+ int rval = 1 ;
+ FILE *fp = (FILE *) data ;
+
+ if (getInteger (topScope,"max-reconnect-time",&iv,NO_INHERIT))
+ {
+ if (iv < 1)
+ {
+ rval = 0 ;
+ logOrPrint (LOG_ERR,fp,
+ "ME config: value of %s (%ld) in %s cannot be less"
+ " than 1. Using %ld", "max-reconnect-time",
+ iv,"global scope",(long) MAX_RECON_PER);
+ iv = MAX_RECON_PER ;
+ }
+ }
+ else
+ iv = MAX_RECON_PER ;
+ max_reconnect_period = (unsigned int) iv ;
+
+ if (getInteger (topScope,"initial-reconnect-time",&iv,NO_INHERIT))
+ {
+ if (iv < 1)
+ {
+ rval = 0 ;
+ logOrPrint (LOG_ERR,fp,
+ "ME config: value of %s (%ld) in %s cannot be less"
+ " than 1. Using %ld", "initial-reconnect-time",
+ iv,"global scope",(long)INIT_RECON_PER);
+ iv = INIT_RECON_PER ;
+ }
+ }
+ else
+ iv = INIT_RECON_PER ;
+ init_reconnect_period = (unsigned int) iv ;
+
+ return rval ;
+}
+
+/* check connection state is in cxnWaitingS, cxnConnectingS or cxnIdleS */
+bool cxnCheckstate (Connection cxn)
+{
+ d_printf(5, "%s:%d Being asked to check state\n",
+ hostPeerName (cxn->myHost), cxn->ident);
+
+ /* return false if either connection is doing something */
+ if (cxn->imap_state > IMAP_IDLE_AUTHED) return false;
+ if (cxn->lmtp_state > LMTP_AUTHED_IDLE) return false;
+
+ return true;
+}
--- /dev/null
+#! /usr/bin/perl
+#
+# Author: James Brister <brister@vix.com> -- berkeley-unix --
+# Start Date: Sun, 19 Jan 1997 21:19:24 +0100
+# Project: INN (innfeed)
+# File: innfeed-convcfg.in
+# RCSId: $Id: innfeed-convcfg.in 2680 1999-11-15 06:37:43Z rra $
+# Description: Read in a old version of innfeed.conf on the command line
+# or on stdin, and write a new version on stdout.
+#
+
+require 'ctime.pl' ;
+
+
+@keyorder = (
+'pid-file',
+'debug-level',
+'use-mmap',
+'log-file',
+'stdio-fdmax',
+'backlog-directory',
+'backlog-rotate-period',
+'backlog-ckpt-period',
+'backlog-newfile-period',
+'dns-retry',
+'dns-expire',
+'close-period',
+'gen-html',
+'status-file',
+'connection-stats',
+'host-queue-highwater',
+'stats-period',
+'stats-reset',
+'max-reconnect-time',
+'initial-reconnect-time',
+'article-timeout',
+'response-timeout',
+'initial-connections',
+'max-connections',
+'max-queue-size',
+'streaming',
+'no-check-high',
+'no-check-low',
+'port-number',
+'backlog-limit',
+'backlog-factor',
+'backlog-limit-highwater'
+);
+
+%procDefaults = (
+'pid-file', 'innfeed.pid',
+'debug-level', '0',
+'use-mmap', 'false',
+'log-file', 'innfeed.log',
+'stdio-fdmax', '0',
+'backlog-directory', 'innfeed',
+'backlog-rotate-period', '60',
+'backlog-ckpt-period', '30',
+'backlog-newfile-period', '600',
+'dns-retry', '900',
+'dns-expire', '86400',
+'close-period', '3600',
+'gen-html', 'false',
+'status-file', 'innfeed.status',
+'connection-stats', 'false',
+'host-queue-highwater', '10',
+'stats-period', '600',
+'stats-reset', '43200',
+'max-reconnect-time', '3600',
+'initial-reconnect-time', '30',
+'article-timeout', '600',
+'response-timeout', '300',
+'initial-connections', '1',
+'max-connections', '5',
+'max-queue-size', '25',
+'streaming', 'true',
+'no-check-high', '195.0',
+'no-check-low', '90.0',
+'port-number', '119',
+'backlog-limit', '0',
+'backlog-factor', '1.10',
+'backlog-limit-highwater', '0',
+ );
+
+%defaultKeys = ('article-timeout', 1,
+ 'response-timeout', 1,
+ 'initial-connections', 1,
+ 'max-connections', 1,
+ 'max-queue-size', 1,
+ 'streaming', 1,
+ 'no-check-high', 1,
+ 'no-check-low', 1,
+ 'port-number', 1,
+ 'backlog-limit', 1) ;
+
+@defaultOrder = ('article-timeout',
+ 'response-timeout',
+ 'initial-connections',
+ 'max-connections',
+ 'max-queue-size',
+ 'streaming',
+ 'no-check-high',
+ 'no-check-low',
+ 'port-number',
+ 'backlog-limit') ;
+
+%formats = () ;
+
+foreach $key (keys %procDefaults) {
+ $max = length ($key) if length ($key) > $max ;
+ if ($procDefaults{$key} =~ /^true$/i || $procDefaults{$key} =~ /^false$/i){
+ $formats{$key} = "%s" ;
+ } elsif ($procDefaults{$key} =~ /^\d+$/) {
+ $formats{$key} = "%d" ;
+ } elsif ($procDefaults{$key} =~ /^\d+\.\d*$/) {
+ $formats{$key} = "%.4f" ;
+ } else {
+ $formats{$key} = "%s" ;
+ }
+}
+
+
+while (<>) {
+ next if /^\s*$/ ;
+ next if /^#/ ;
+
+ chop ;
+ @F = split (':') ;
+
+ if ($F[0] eq "default") {
+ $procDefaults{'article-timeout'} = $F[2] ;
+ $procDefaults{'response-timeout'} = $F[3] ;
+ $procDefaults{'initial-connections'} = $F[4] ;
+ $procDefaults{'max-connections'} = $F[5] ;
+ $procDefaults{'max-queue-size'} = $F[6] ;
+ $procDefaults{'streaming'} = $F[7] ;
+ $procDefaults{'no-check-low'} = $F[8] * 10.0 ;
+ $procDefaults{'no-check-high'} = $F[9] * 10.0 ;
+ $procDefaults{'port-number'} = $F[10] ;
+
+ printf "## This file was automatically generated created by $0\n" ;
+ printf "## On %s##\n\n", &ctime(time) ;
+
+ foreach $key (@keyorder) {
+ next if $defaultKeys{$key} ;
+
+ die "No format for $key\n" unless $formats{$key} ;
+ $format = "%${max}s:\t" . $formats{$key} . "\n" ;
+ printf $format, $key, $procDefaults{$key} ;
+ }
+
+ printf "\n\n## Defaults merged from:\n##\t$_\n\n" ;
+ foreach $key (@defaultOrder) {
+ die "No format for $key\n" unless $formats{$key} ;
+ $format ="%${max}s:\t" . $formats{$key} . "\n" ;
+ printf $format, $key, $procDefaults{$key} ;
+ }
+ print "\n\n\n" ;
+ $gotDefault = 1 ;
+ } elsif (@F == 0) {
+ die "Badly formed line: $0\n" ;
+ } else {
+ if (!$gotDefault) {
+ $gotDefault = 1 ; # warn only one time.
+ warn "No default line was present.\n" ;
+ }
+
+ print "## Peer created from:\n" ;
+ print "##\t$_\n\n" ;
+ printf "peer %s {\n", $F[0] ;
+
+ printf "\tip-name: $F[1]\n" if $F[1] && $F[0] ne $F[1] ;
+ printf "\tarticle-timeout: %d\n", $F[2] if $F[2] ;
+ printf "\tresponse-timeout: %d\n", $F[3] if $F[3] ;
+ printf "\tinitial-connections: %d\n", $F[4] if ($F[4] ne "") ;
+ printf "\tmax-connections: %d\n", $F[5] if ($F[5] ne "") ;
+ printf "\tmax-queue-size: %d\n", $F[6] if ($F[6] ne "") ;
+ printf "\tstreaming: %s\n", $F[7] if ($F[7] ne "") ;
+ printf "\tno-check-high: %0.2f\n", $F[9] * 10.0 if ($F[9] ne "") ;
+ printf "\tno-check-low: %0.2f\n", $F[8] * 10.0 if ($F[8] ne "") ;
+ printf "\tport-number: %d\n", $F[10] if ($F[10] ne "") ;
+
+ print "}\n\n\n" ;
+ }
+}
+
+
--- /dev/null
+/* $Id: innfeed.h 7559 2006-08-28 02:10:39Z eagle $
+**
+** innfeed's configuration values.
+**
+** Written by James Brister <brister@vix.com>
+**
+** The application configuration values. This file is #include'd before any
+** system header files, so it can't rely on any CPP symbols other that what
+** the compiler defines.
+*/
+
+#if ! defined ( innfeed_h__ )
+#define innfeed_h__
+
+#include "inn/timer.h"
+
+/**********************************************************************/
+/* Application specific defines */
+/**********************************************************************/
+
+/* the path to the run-time config file. If relative, then relative to
+ @ETCDIR@. Overridden by ``-c'' option. */
+#define CONFIG_FILE "innfeed.conf"
+
+
+/*
+ * This next section contains things than can be overriden in the config
+ * file. The strings inside each comment is the key used in the
+ * innfeed.conf file to override the value here. See innfeed.conf for a
+ * description of each./
+ */
+
+/* in tape.c */
+#define TAPE_DIRECTORY "innfeed" /* [pathspool]/backlog-directory */
+#define TAPE_HIGHWATER 5 /* backlog-highwater */
+#define TAPE_ROTATE_PERIOD 60 /* backlog-rotate-period */
+#define TAPE_CHECKPOINT_PERIOD 30 /* backlog-ckpt-period */
+#define TAPE_NEWFILE_PERIOD 600 /* backlog-newfile-period */
+#define TAPE_DISABLE false /* no-backlog */
+
+/* in main.c */
+#define PID_FILE "innfeed.pid" /* [pathrun]/pid-file */
+#define LOG_FILE "innfeed.log" /* [pathlog]/log-file */
+
+/* in host.c */
+#define DNS_RETRY_PERIOD 900 /* dns-retry */
+#define DNS_EXPIRE_PERIOD 86400 /* dns-expire */
+#define CLOSE_PERIOD (60 * 60 * 24) /* close-period */
+#define GEN_HTML false /* gen-html */
+#define INNFEED_STATUS "innfeed.status" /* status-file */
+#define LOG_CONNECTION_STATS 0 /* connection-stats */
+#define HOST_HIGHWATER 10 /* host-highwater */
+#define STATS_PERIOD (60 * 10) /* stats-period */
+#define STATS_RESET_PERIOD (60 * 60 * 12) /* stats-reset-period */
+
+#define ARTTOUT 600 /* article-timeout */
+#define RESPTOUT 300 /* response-timeout */
+#define INIT_CXNS 1 /* initial-connections */
+#define MAX_CXNS 2 /* max-connections */
+#define MAX_Q_SIZE 5 /* max-queue-size */
+#define STREAM true /* streaming */
+#define NOCHECKHIGH 95.0 /* no-check-high */
+#define NOCHECKLOW 90.0 /* no-check-low */
+#define PORTNUM 119 /* port-number */
+#define FORCE_IPv4 false /* force using IPv4 */
+#define BLOGLIMIT 0 /* backlog-limit */
+#define LIMIT_FUDGE 1.10 /* backlog-factor */
+#define BLOGLIMIT_HIGH 0 /* backlog-limit-high */
+
+#define INIT_RECON_PER 30 /* initial-reconnect-time */
+#define MAX_RECON_PER (60 * 60 * 1)/* max-reconnect-time */
+
+
+
+
+
+
+
+/****************************************************************************/
+/*
+ * The rest below are not run-time configurable.
+ */
+
+/* If this file exists at startup then it's the same as having done
+ '-d 1' on the command line. This is a cheap way of avoiding continual
+ reloading of the newsfeeds file when debugging. */
+#define DEBUG_FILE "innfeed.debug" /* Relative to pathlog */
+
+/* if defined to a non-zero number, then a snapshot will be printed
+ whenever die() is called (e.g. on assert failure). This can use up a
+ lot of disk space. */
+#define SNAPSHOT_ON_DIE 0
+
+/* the full pathname of the file to get a printed dump of the system when
+ a SIGINT is delivered (or SNAPSHOT_ON_DIE is non-zero--see below). */
+#define SNAPSHOT_FILE "innfeed.snapshot" /* Relative to pathlog */
+
+/* Define this be an existing directory (or NULL). If innfeed deliberatly
+ dumps core it will chdir() to this directory first (if non-NULL). If
+ NULL then it will chdir to TAPE_DIRECTORY (as possibly modified by
+ the '-b' option). */
+#define CORE_DIRECTORY NULL
+
+/* strings that get added to the end of a peer name for generating
+ backlog file names. A peername cannot end in any of these string
+ (e.g. having a peer called 'mypeer.input' will not work) */
+#define OUTPUT_TAIL ".output"
+#define INPUT_TAIL ".input"
+#define LOCK_TAIL ".lock"
+
+/* rough estimate of average article line length (including
+ headers). Smaller number means more efficient article preparation (for
+ transfer), but, if much smaller than reality, then more memory
+ wastage. */
+#define CHARS_PER_LINE 60
+
+/* How many seconds between logging statistics on article allocation.
+ For no logging set to 0 */
+#define ARTICLE_STATS_PERIOD (10 * 60) /* 10 minutes */
+
+/* max number of parallel connections to a single remote. This is just a
+ sanity check for the runtime config file. */
+#define MAX_CONNECTION_COUNT 50
+
+/* default size in bytes for buffers */
+#define BUFFER_SIZE 256
+
+/* amount we expand buffers on partial reads */
+#define BUFFER_EXPAND_AMOUNT 128
+
+/* minimum number of seconds between log messages for starting
+ spooling. i.e. if the connection bounces up and down this will prevent
+ frequent logging of the spooling message. 0 turns off this logging. */
+#define SPOOL_LOG_PERIOD 600
+
+/* some big numbers just for sanity checking */
+#define MAX_MAXCHECKS 10000 /* no more than 10000 articles at a time */
+#define MAX_MAXART_TOUT 86400 /* one day max between articles from inn */
+#define MAX_RESP_TOUT 3600 /* one hour max to wait for response */
+
+/* the check / no-check filter value, i.e. roughly how many past
+ articles we take into account whilst doing the average for
+ check / no-check mode.
+ Ensure it's a float. */
+#define FILTERVALUE 50.0
+
+/* the maximum number of peers we'll handle (not connections) */
+#define MAX_HOSTS 100
+
+/* We try to keep article memory allocation below this limit. Doesn't work
+ very well, though. */
+#define SOFT_ARTICLE_BYTE_LIMIT (1024 * 1024 * 10) /* 10MB */
+
+/* define SELECT_RATIO to the number of times through the main loop before
+ checking on the fd from inn again.... */
+#define SELECT_RATIO 3
+
+
+#if defined (DBTIMES)
+
+ /* some small values for testing things. */
+
+#undef STATS_PERIOD
+#define STATS_PERIOD 30 /* 30 seconds */
+
+#undef STATS_RESET_PERIOD
+#define STATS_RESET_PERIOD (6 * 60) /* 6 minutes */
+
+#undef ARTICLE_STATS_PERIOD
+#define ARTICLE_STATS_PERIOD (6 * 60) /* 7 minutes */
+
+#undef CLOSE_PERIOD
+#define CLOSE_PERIOD (3 * 60) /* 5 minutes */
+
+#endif /* DBTIMES */
+
+
+/* Additional OS-specific defines. These should really be moved into
+ configure at some point. */
+
+/* Some broken system (all SunOS versions) have a lower limit for the
+ maximum number of stdio files that can be open, than the limit of open
+ file the OS will let you have. If this value is > 0 (and ``stdio-fdmax''
+ is *not* used in the config file), then all non-stdio file descriptors
+ will be kept above this value (by dup'ing them). */
+#if defined (sun)
+# if defined (__SVR4)
+# define MAX_STDIO_FD 256
+# else
+# define MAX_STDIO_FD 128
+# endif
+#else
+# define MAX_STDIO_FD 0
+#endif
+
+/* some timer constants */
+
+typedef enum { TMR_IDLE = TMR_APPLICATION, TMR_BACKLOGSTATS,
+ TMR_STATUSFILE, TMR_NEWARTICLE, TMR_READART, TMR_PREPART, TMR_READ,
+ TMR_WRITE, TMR_CALLBACK, TMR_MAX
+} TMRTYPE;
+
+#endif /* innfeed_h__ */
--- /dev/null
+/* $Id: innlistener.c 6716 2004-05-16 20:26:56Z rra $
+**
+** The implementation of the innfeed InnListener class.
+**
+** Written by James Brister <brister@vix.com>
+*/
+
+#include "innfeed.h"
+#include "config.h"
+#include "clibrary.h"
+
+#include <assert.h>
+#include <ctype.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <syslog.h>
+#include <time.h>
+
+#include "inn/messages.h"
+#include "libinn.h"
+
+#include "article.h"
+#include "buffer.h"
+#include "configfile.h"
+#include "endpoint.h"
+#include "host.h"
+#include "innlistener.h"
+#include "nntp.h"
+#include "tape.h"
+
+#define LISTENER_INPUT_BUFFER (1024 * 8) /* byte size of the input buffer */
+#define EOF_SLEEP_TIME 1 /* seconds to sleep when EOF on InputFile */
+
+struct innlistener_s
+{
+ EndPoint myep ;
+
+ Host *myHosts ;
+ size_t hostLen ;
+ Buffer inputBuffer ;
+ bool dummyListener ;
+ bool dynamicPeers ;
+ TimeoutId inputEOFSleepId ;
+
+ InnListener next ;
+};
+
+static unsigned int listenerCount = 0 ;
+static InnListener listenerList = NULL ;
+
+InnListener mainListener ;
+
+static FILE *droppedFp = NULL ;
+static long droppedCount = 0 ;
+static int droppedFileCount = 0 ;
+static char *dropArtFile = NULL ;
+static bool fastExit = false ;
+
+extern const char *pidFile ;
+extern const char *InputFile ;
+extern bool RollInputFile ;
+extern bool genHtml ;
+
+
+static void giveArticleToPeer (InnListener lis,
+ Article article, const char *peerName) ;
+static void newArticleCommand (EndPoint ep, IoStatus i,
+ Buffer *buffs, void *data) ;
+static void wakeUp (TimeoutId id, void *data) ;
+static void writeCheckPoint (int offsetAdjust) ;
+static void dropArticle (const char *peer, Article article) ;
+static void listenerCleanup (void) ;
+
+static bool inited = false ;
+
+
+void listenerLogStatus (FILE *fp)
+{
+ fprintf (fp,"%sListener Status:%s\n",
+ genHtml ? "<B>" : "", genHtml ? "</B>" : "") ;
+ fprintf (fp," Dropped article file: %s\n",dropArtFile) ;
+ fprintf (fp," Dropped article count: %ld\n",(long) droppedCount) ;
+ fprintf (fp,"\n") ;
+}
+
+InnListener newListener (EndPoint endp, bool isDummy, bool dynamicPeers)
+{
+ InnListener l = xcalloc (1, sizeof(struct innlistener_s)) ;
+ Buffer *readArray ;
+
+ if (!inited)
+ {
+ inited = true ;
+ atexit (listenerCleanup) ;
+ }
+
+ l->myep = endp ;
+
+ l->hostLen = MAX_HOSTS ;
+ l->myHosts = xcalloc (l->hostLen, sizeof(Host)) ;
+
+ l->inputBuffer = newBuffer (LISTENER_INPUT_BUFFER) ;
+ l->dummyListener = isDummy ;
+ l->dynamicPeers = dynamicPeers ;
+
+ addPointerFreedOnExit ((char *)bufferBase(l->inputBuffer)) ;
+ addPointerFreedOnExit ((char *)l->myHosts) ;
+ addPointerFreedOnExit ((char *)l) ;
+
+ readArray = makeBufferArray (bufferTakeRef (l->inputBuffer), NULL) ;
+ prepareRead (endp,readArray,newArticleCommand,l,1) ;
+
+ l->next = listenerList ;
+ listenerList = l ;
+
+ listenerCount++ ;
+
+ return l ;
+}
+
+void gPrintListenerInfo (FILE *fp, unsigned int indentAmt)
+{
+ InnListener p ;
+ char indent [INDENT_BUFFER_SIZE] ;
+ unsigned int i ;
+
+ for (i = 0 ; i < MIN(INDENT_BUFFER_SIZE - 1,indentAmt) ; i++)
+ indent [i] = ' ' ;
+ indent [i] = '\0' ;
+
+ fprintf (fp,"%sGlobal InnListener list : %p (count %d) {\n",
+ indent,(void *) listenerList,listenerCount) ;
+ for (p = listenerList ; p != NULL ; p = p->next)
+ printListenerInfo (p,fp,indentAmt + INDENT_INCR) ;
+ fprintf (fp,"%s}\n",indent) ;
+}
+
+
+
+void printListenerInfo (InnListener listener, FILE *fp, unsigned int indentAmt)
+{
+ char indent [INDENT_BUFFER_SIZE] ;
+ unsigned int i ;
+
+ for (i = 0 ; i < MIN(INDENT_BUFFER_SIZE - 1,indentAmt) ; i++)
+ indent [i] = ' ' ;
+ indent [i] = '\0' ;
+
+ fprintf (fp,"%sInnListener : %p {\n",indent,(void *) listener) ;
+ fprintf (fp,"%s endpoint : %p\n", indent,(void *) listener->myep) ;
+ fprintf (fp,"%s dummy-listener : %s\n",indent,
+ boolToString (listener->dummyListener)) ;
+ fprintf (fp,"%s dynamicPeers : %s\n",indent,
+ boolToString (listener->dynamicPeers)) ;
+
+ fprintf (fp,"%s input-buffer {\n",indent) ;
+ printBufferInfo (listener->inputBuffer,fp,indentAmt + INDENT_INCR) ;
+ fprintf (fp,"%s }\n",indent) ;
+
+ fprintf (fp,"%s hosts {\n",indent) ;
+ for (i = 0 ; i < listener->hostLen ; i++)
+ {
+#if 0
+ if (listener->myHosts [i] != NULL)
+ printHostInfo (listener->myHosts [i],fp,indentAmt + INDENT_INCR) ;
+#else
+ fprintf (fp,"%s %p\n",indent,(void *) listener->myHosts[i]) ;
+#endif
+ }
+
+ fprintf (fp,"%s }\n",indent) ;
+
+ fprintf (fp,"%s}\n",indent) ;
+}
+
+ /* Unlink the pidFile if and only if it is our pidFile.
+ There is still a racecondition here but very small. */
+static void unlinkPidFile (void)
+{
+ FILE *fp;
+ char buf[32];
+
+ if ((fp = fopen(pidFile, "r")) == NULL)
+ return;
+
+ if (fgets(buf, 32, fp) != NULL && atoi(buf) == getpid())
+ unlink(pidFile);
+ fclose(fp);
+}
+
+ /* Close down all hosts on this listener. When they're all gone the
+ Listener will be deleted. */
+void shutDown (InnListener l)
+{
+ unsigned int i ;
+ unsigned int count ;
+
+ d_printf (1,"Shutting down the listener\n") ;
+
+ /* When shutting down the mainListener, stop writing to the
+ StatusFile and remove our pidFile. */
+ if (l == mainListener)
+ {
+ /*hostCleanup (); should do this but .. */
+ hostSetStatusFile ("/dev/null");
+ unlinkPidFile();
+ }
+
+ closeDroppedArticleFile () ;
+
+ if (l->myep != NULL)
+ {
+ if (l->inputEOFSleepId != 0)
+ removeTimeout (l->inputEOFSleepId) ;
+ l->inputEOFSleepId = 0 ;
+ delEndPoint (l->myep) ;
+ }
+ l->myep = NULL ;
+
+ for (i = 0, count = 0 ; i < l->hostLen ; i++)
+ if (l->myHosts [i] != NULL)
+ {
+ hostClose (l->myHosts[i]) ;
+ count++ ;
+ }
+
+ if (count == 0 || fastExit)
+ {
+ time_t now = theTime () ;
+ char dateString [30] ;
+
+ gHostStats();
+ strlcpy (dateString,ctime (&now),sizeof (dateString)) ;
+ dateString [24] = '\0' ;
+
+ if (fastExit)
+ notice ("ME finishing (quickly) at %s", dateString) ;
+ else
+ notice ("ME finishing at %s", dateString) ;
+
+ unlinkPidFile();
+ exit (0) ;
+ }
+}
+
+
+bool listenerAddPeer (InnListener listener, Host hostObj)
+{
+ unsigned int i ;
+
+ d_printf (1,"Adding peer: %s\n", hostPeerName (hostObj)) ;
+
+ for (i = 0 ; i < listener->hostLen ; i++)
+ {
+ if (listener->myHosts [i] == NULL)
+ {
+ listener->myHosts [i] = hostObj ;
+
+ return true ;
+ }
+ }
+
+ return false ;
+}
+
+
+/* return true if this listener doesn't ever generate articles. */
+bool listenerIsDummy (InnListener listener)
+{
+ return listener->dummyListener ;
+}
+
+/* Called by the Host when it (the Host) is about to delete itself. */
+unsigned int listenerHostGone (InnListener listener, Host host)
+{
+ unsigned int i ;
+ unsigned int someThere = 0 ;
+
+ d_printf (1,"Host is gone: %s\n", hostPeerName (host)) ;
+
+ for (i = 0 ; i < listener->hostLen ; i++)
+ if (listener->myHosts [i] == host)
+ listener->myHosts [i] = NULL ;
+ else if (listener->myHosts [i] != NULL)
+ someThere++ ;
+
+ return someThere ;
+}
+
+
+/* called by the Host when it has nothing to do. */
+void listenerHostIsIdle (InnListener listener, Host host)
+{
+ ASSERT (listener != NULL) ;
+ ASSERT (host != NULL) ;
+
+ d_printf (1,"Host is idle: %s\n", hostPeerName (host)) ;
+
+ if (!listener->dummyListener)
+ return ;
+
+ /* if this listener is a dummy (i.e. not generating articles cause we're
+ just dealing with backlog files) then forget about the host and when
+ last one is gone we exit. */
+
+ hostClose (host) ;
+}
+
+
+void openInputFile (void)
+{
+ int fd, i, mainFd ;
+ off_t offset ;
+ char buf [32], *p ;
+
+ ASSERT (InputFile && *InputFile) ;
+
+ fd = open(InputFile, O_RDWR) ;
+ if (fd < 0)
+ die ("open %s: %s\n", InputFile, strerror(errno)) ;
+
+ mainFd = getMainEndPointFd() ;
+ if (fd != mainFd)
+ {
+ if (dup2(fd, mainFd) < 0)
+ die ("dup2 %d %d: %s\n", fd, mainFd, strerror(errno)) ;
+ close (fd);
+ }
+
+ i = read(mainFd, buf, sizeof (buf)) ;
+ if (i < 0)
+ die ("read %s: %s\n", InputFile, strerror(errno)) ;
+ else if (i > 0)
+ {
+ p = buf;
+ buf [ sizeof(buf) - 1 ] = '\0';
+ offset = (off_t) strtol (p, &p, 10) ;
+ if (offset > 0 && *p == '\n')
+ lseek (mainFd, offset, SEEK_SET) ;
+ else
+ lseek (mainFd, 0, SEEK_SET) ;
+ }
+ syslog(LOG_NOTICE, "ME opened %s", InputFile);
+}
+
+
+int listenerConfigLoadCbk (void *data UNUSED)
+{
+ int bval ;
+
+ if (getBool (topScope,"fast-exit",&bval,NO_INHERIT))
+ fastExit = (bval ? true : false) ;
+
+ return 1 ;
+}
+
+/**********************************************************************/
+/** STATIC PRIVATE FUNCTIONS **/
+/**********************************************************************/
+
+
+/* EndPoint callback function for when the InnListener's fd is ready for
+ reading. */
+static void newArticleCommand (EndPoint ep, IoStatus i,
+ Buffer *buffs, void *data)
+{
+ InnListener lis = (InnListener) data ;
+ char *msgid, *msgidEnd ;
+ char *fileName, *fileNameEnd ;
+ char *peer, *peerEnd ;
+ char *cmd, *endc ;
+ char *bbase = bufferBase (buffs [0]) ;
+ size_t blen = bufferDataSize (buffs [0]) ;
+ Buffer *readArray ;
+ static int checkPointCounter ;
+ char *s;
+
+ ASSERT (ep == lis->myep) ;
+
+ bufferAddNullByte (buffs [0]) ;
+
+ if (i == IoEOF)
+ {
+ if ( lis == mainListener && InputFile != NULL )
+ {
+ if ( RollInputFile )
+ {
+ syslog(LOG_NOTICE, "ME reached EOF in %s", InputFile);
+ openInputFile () ;
+ RollInputFile = false ;
+ readArray = makeBufferArray (bufferTakeRef (buffs [0]),NULL) ;
+ prepareRead (ep, readArray, newArticleCommand, data, 1) ;
+ }
+ else
+ {
+ lis->inputEOFSleepId =
+ prepareSleep (wakeUp, EOF_SLEEP_TIME, data) ;
+
+ }
+ }
+ else
+ {
+ d_printf (1,"Got EOF on listener\n") ;
+ notice ("ME source lost . Exiting");
+ shutDown (lis) ;
+ }
+ }
+ else if (i == IoFailed)
+ {
+ errno = endPointErrno (ep) ;
+#if HAVE_SOCKETPAIR
+ if (errno != ECONNABORTED)
+#endif
+ syswarn ("ME source read error, exiting") ;
+ d_printf (1,"Got IO Error on listener\n") ;
+ shutDown (lis) ;
+ }
+ else if (strchr (bbase, '\n') == NULL) /* partial read */
+ {
+ /* check for input corrupted by NULs - if they
+ precede the newline, we never get out of here */
+ if (strlen(bbase) < blen)
+ {
+ warn ("ME source format bad, exiting: %s", bbase) ;
+ shutDown (lis) ;
+
+ return ;
+ }
+ if (blen == bufferSize(buffs [0])) {
+ if (!expandBuffer (buffs [0], BUFFER_EXPAND_AMOUNT)) {
+ warn ("ME error expanding input buffer") ;
+ shutDown (lis) ;
+ return ;
+ }
+ }
+ readArray = makeBufferArray (bufferTakeRef (buffs [0]),NULL) ;
+ if (!prepareRead (ep, readArray, newArticleCommand, data, 1)) {
+ warn ("ME error prepare read failed") ;
+ freeBufferArray (readArray) ;
+ shutDown (lis) ;
+ return ;
+ }
+ }
+ else
+ {
+ /* now iterate over each full command we got on the input. */
+ cmd = bbase ;
+ while ((cmd < (bbase + blen)) && ((endc = strchr (cmd,'\n')) != NULL))
+ {
+ Article article ;
+ char *next = endc + 1;
+
+ if (*next == '\r')
+ next++ ;
+
+ endc-- ;
+ if (*endc != '\r')
+ endc++ ;
+
+ *endc = '\0' ;
+
+ /* check for input corrupted by NULs - if they are preceded
+ by newline, we may skip a large chunk without noticing */
+ if (*next == '\0' && next < bbase + blen)
+ {
+ warn ("ME source format bad, exiting: %s", cmd) ;
+ shutDown (lis) ;
+
+ return ;
+ }
+
+ d_printf (2,"INN Command: %s\n", cmd) ;
+
+ /* pick out the leading string (the filename) */
+ if ((fileName = findNonBlankString (cmd,&fileNameEnd)) == NULL)
+ {
+ warn ("ME source format bad, exiting: %s", cmd) ;
+ shutDown (lis) ;
+
+ return ;
+ }
+
+ *fileNameEnd = '\0' ; /* for the benefit of newArticle() */
+
+ /* now pick out the next string (the message id) */
+ if ((msgid = findNonBlankString (fileNameEnd + 1,&msgidEnd)) == NULL)
+ {
+ *fileNameEnd = ' ' ; /* to make syslog work properly */
+ warn ("ME source format bad, exiting: %s", cmd) ;
+ shutDown (lis) ;
+
+ return ;
+ }
+
+ *msgidEnd = '\0' ; /* for the benefit of newArticle() */
+
+ /* now create an article object and give it all the peers on the
+ rest of the command line. Will return null if file is missing. */
+ article = newArticle (fileName, msgid) ;
+ *fileNameEnd = ' ' ;
+
+ /* Check the message ID length */
+ if (strlen(msgid) > NNTP_MSGID_MAXLEN) {
+ warn ("ME message id exceeds limit of %d octets: %s",
+ NNTP_MSGID_MAXLEN, msgid) ;
+ *(msgidEnd+1) = '\0' ;
+ }
+ *msgidEnd = ' ' ;
+
+ /* Check if message ID starts with < and ends with > */
+ if (*msgid != '<' || *(msgidEnd-1) != '>') {
+ warn ("ME source format bad, exiting: %s", cmd) ;
+ *(msgidEnd+1) = '\0';
+ }
+
+ /* now get all the peernames off the rest of the command lines */
+ peerEnd = msgidEnd ;
+ do
+ {
+ *peerEnd = ' ' ;
+
+ /* pick out the next peer name */
+ if ((peer = findNonBlankString (peerEnd + 1,&peerEnd))==NULL)
+ break ; /* even no peer names is OK. */ /* XXX REALLY? */
+
+ *peerEnd = '\0' ;
+
+ /* See if this is a valid peername */
+ for(s = peer; *s; s++)
+ if (!CTYPE(isalnum, *s) && *s != '.' && *s != '-' && *s != '_')
+ break;
+ if (*s != 0) {
+ warn ("ME invalid peername %s", peer) ;
+ continue;
+ }
+ if (article != NULL)
+ giveArticleToPeer (lis,article,peer) ;
+ }
+ while (peerEnd < endc) ;
+
+ delArticle (article) ;
+
+ cmd = next ;
+
+ /* write a checkpoint marker if we've done another large chunk */
+ if (InputFile && *InputFile && ++checkPointCounter == 1000)
+ {
+ /* adjust the seek pointer value by the current location
+ within the input buffer */
+ writeCheckPoint (blen - (cmd - bbase)) ;
+ checkPointCounter = 0 ;
+ }
+
+ }
+
+ if (*cmd != '\0') /* partial command left in buffer */
+ {
+ Buffer *bArr ;
+ unsigned int leftAmt = blen - (cmd - bbase) ;
+
+ ASSERT (cmd != bbase) ;
+ /* first we shift whats left in the buffer down to the bottom */
+ memmove (bbase,cmd,leftAmt) ;
+ bufferSetDataSize (buffs [0],leftAmt) ;
+
+ bArr = makeBufferArray (bufferTakeRef (buffs [0]),NULL) ;
+
+ if ( !prepareRead (lis->myep, bArr, newArticleCommand, lis, 1) )
+ {
+ warn ("ME error prepare read failed") ;
+
+ freeBufferArray (bArr) ;
+
+ shutDown (lis) ;
+
+ return ;
+ }
+ }
+ else if ( !readIsPending (lis->myep) )
+ { /* XXX read should never be pending here */
+ Buffer *bArr = makeBufferArray (bufferTakeRef (buffs [0]),NULL) ;
+
+ bufferSetDataSize (buffs [0],0) ;
+
+ if ( !prepareRead (lis->myep, bArr, newArticleCommand, lis, 1) )
+ {
+ warn ("ME error prepare read failed") ;
+
+ shutDown (lis) ;
+
+ return ;
+ }
+ }
+ }
+
+ freeBufferArray (buffs) ;
+}
+
+/* EndPoint callback function for when the sleep due to
+ having reached EOF on InputFile is done. */
+static void wakeUp (TimeoutId id, void *data)
+{
+ InnListener lis = (InnListener) data ;
+ Buffer *readArray ;
+
+ ASSERT (id == lis->inputEOFSleepId) ;
+
+ lis->inputEOFSleepId = 0 ;
+ readArray = makeBufferArray (bufferTakeRef (lis->inputBuffer), NULL) ;
+ prepareRead (lis->myep,readArray,newArticleCommand,lis,1) ;
+}
+
+
+/* Find the Host object for the peer and hand off a reference to the
+ article for it to transmit. */
+static void giveArticleToPeer (InnListener lis,
+ Article article, const char *peerName)
+{
+ unsigned int i ;
+
+ for (i = 0 ; i < lis->hostLen ; i++)
+ if (lis->myHosts[i] != NULL)
+ if (strcmp (peerName,hostPeerName (lis->myHosts [i])) == 0)
+ {
+ d_printf (1,"Giving article to peer: %s\n", peerName) ;
+ hostSendArticle (lis->myHosts [i],artTakeRef (article)) ;
+ break ;
+ }
+
+ if (i == lis->hostLen)
+ {
+ d_printf (1,"Failed to give article to peer: -%s-\n", peerName) ;
+
+ if (lis->dynamicPeers)
+ {
+ Host newHostObj;
+
+ d_printf (1, "Adding peer dynamically\n") ;
+
+ newHostObj = newDefaultHost (lis, peerName);
+
+ if (newHostObj == NULL)
+ {
+ /* Most likely we couldn't get the lock, i.e. the
+ peer is blocked.
+ */
+ dropArticle (peerName,article) ;
+ }
+ else if ( !listenerAddPeer (lis, newHostObj) )
+ {
+ /* XXX need to remember we've gone over the limit and not try
+ to add any more. */
+ warn ("ME internal too many hosts. (max is %lu)",
+ (unsigned long) lis->hostLen) ;
+ dropArticle (peerName,article) ;
+ }
+ else
+ {
+ d_printf (1,"Giving article to peer: %s\n", peerName) ;
+ hostSendArticle (newHostObj,artTakeRef (article)) ;
+ }
+ }
+ else
+ {
+ dropArticle (peerName,article) ;
+ }
+ }
+}
+
+
+static void writeCheckPoint (int offsetAdjust)
+{
+ char offsetString[16], *writePointer ;
+ off_t offset ;
+ int writeBytes, writeReturn, mainFd ;
+
+ mainFd = getMainEndPointFd() ;
+ offset = lseek (mainFd, 0, SEEK_CUR) ;
+ if (offset < 0)
+ syslog (LOG_ERR, "ME tell(mainFd): %m") ;
+ else
+ {
+ snprintf (offsetString, sizeof(offsetString), "%ld\n",
+ (long)(offset - offsetAdjust) ) ;
+ if ( lseek (mainFd, 0, SEEK_SET) != 0 )
+ syslog (LOG_ERR, "ME seek(mainFd, 0, 0): %m") ;
+ else
+ {
+ writeBytes = strlen (offsetString) ;
+ writePointer = offsetString ;
+ do
+ {
+ writeReturn = write (mainFd, writePointer, writeBytes) ;
+ if (writeReturn < 0)
+ {
+ syslog (LOG_ERR,"ME write input checkpoint: %m") ;
+ break ;
+ }
+ writePointer += writeReturn ;
+ writeBytes -= writeReturn ;
+ } while (writeBytes) ;
+ if ( lseek (mainFd, offset, SEEK_SET) != offset )
+ die ("ME seek(mainFd, %ld, SEEK_SET): %s\n", (long)offset,
+ strerror(errno) ) ;
+ }
+ }
+}
+
+
+void openDroppedArticleFile (void)
+{
+ pid_t myPid = getpid () ;
+ const char *tapeDir = getTapeDirectory() ;
+ size_t len;
+
+ if (dropArtFile != NULL)
+ free (dropArtFile) ;
+
+ len = pathMax(tapeDir) + 1;
+ dropArtFile = xmalloc(len);
+ snprintf (dropArtFile,len,"%s/innfeed-dropped.%c%06d",
+ tapeDir, droppedFileCount + 'A', (int) myPid) ;
+
+ if ((droppedFp = fopen (dropArtFile,"w")) == NULL)
+ {
+ syswarn ("ME cant open %s: loosing articles", dropArtFile) ;
+
+ free (dropArtFile) ;
+ dropArtFile = NULL ;
+
+ if ((droppedFp = fopen ("/dev/null","w")) == NULL)
+ {
+ die ("ME error opening /dev/null") ;
+ }
+ }
+
+
+}
+
+void closeDroppedArticleFile (void)
+{
+ off_t pos ;
+
+ if (droppedFp == NULL)
+ return ;
+
+ fflush (droppedFp) ;
+ pos = ftello (droppedFp) ;
+
+ fclose (droppedFp) ;
+ droppedFp = NULL ;
+
+ if (pos == 0 && dropArtFile != NULL)
+ unlink (dropArtFile) ;
+ else if (pos != 0 && dropArtFile == NULL)
+ warn ("ME lost %ld articles", droppedCount) ;
+ else if (pos != 0)
+ notice ("ME dropped %ld articles", droppedCount) ;
+
+ droppedFileCount = (droppedFileCount + 1) % 26 ;
+ droppedCount = 0 ;
+}
+
+static void dropArticle (const char *peerName, Article article)
+{
+ static bool logged = false ;
+
+ if (!logged)
+ {
+ warn ("ME dropping articles into %s", dropArtFile) ;
+ logged = true ;
+ }
+
+ droppedCount++ ;
+ fprintf (droppedFp,"%s %s %s\n",artFileName (article),
+ artMsgId (article), peerName) ;
+}
+
+
+static void listenerCleanup (void)
+{
+ free (dropArtFile) ;
+ dropArtFile = NULL ;
+}
--- /dev/null
+/* $Id: innlistener.h 6648 2004-01-25 20:07:11Z rra $
+**
+** The public interface to the InnListener class.
+**
+** Written by James Brister <brister@vix.com>
+**
+** The public interface of the things that listens to commands from INN. It
+** receives lines of the form:
+**
+** filename msgid peer1 peer2 peer3
+**
+** and turns them into Article objects and hands those Articles off to the
+** Host objects.
+*/
+
+#if ! defined ( innlistener_h__ )
+#define innlistener_h__
+
+#include <stdio.h>
+
+#include "misc.h"
+
+
+extern InnListener mainListener ;
+
+/* Initialization of the InnListener object. If it fails then returns
+ NULL. ENDPOINT is the endpoint where the article info will come
+ from. A dummy listener exists when processing backlog files and is
+ there just to help drive the process. */
+InnListener newListener (EndPoint endp, bool isDummy, bool dynamicPeers) ;
+
+/* print some useful debugging information about the Listener and all its
+ * Hosts and all their Connecitons/Article/Buffers etc. to the given FILE.
+ */
+void gPrintListenerInfo (FILE *fp, unsigned int indentAmt) ;
+void printListenerInfo (InnListener listener, FILE *fp, unsigned int indentAmt) ;
+
+/* Called by the Host when it is about to delete itself */
+unsigned int listenerHostGone (InnListener listener, Host host) ;
+
+/* Called to hook up the given Host to the Listener. */
+bool listenerAddPeer (InnListener listener, Host hostObj) ;
+
+/* true if the listener is a dummy. */
+bool listenerIsDummy (InnListener listener) ;
+
+/*
+ * This gets called to stop accepting new articles from innd. Typically
+ * called by the signal handler, or when the listener gets EOF on its input
+ * (in channel mode)
+ */
+void shutDown (InnListener cxn) ;
+
+/* Callback fired after config file is loaded */
+int listenerConfigLoadCbk (void *data) ;
+
+ /* stop a specific host. */
+void shutDownHost (InnListener cxn, const char *peerName) ;
+
+ /* Called by the Host when it has nothing to do (so it can be shut down
+ if necessary). */
+void listenerHostIsIdle (InnListener listener, Host host) ;
+
+void openInputFile (void) ;
+
+void openDroppedArticleFile (void) ;
+void closeDroppedArticleFile (void) ;
+
+void listenerLogStatus (FILE *fp) ;
+
+#endif /* innlistener_h__ */
--- /dev/null
+/* $Id: main.c 6956 2004-06-29 22:41:35Z rra $
+**
+** Main routines for the innfeed program.
+**
+** Written by James Brister <brister@vix.com>
+*/
+
+#include "innfeed.h"
+#include "config.h"
+#include "clibrary.h"
+#include "portable/socket.h"
+#include "portable/time.h"
+
+#include <assert.h>
+#include <ctype.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <math.h>
+#include <netdb.h>
+#include <signal.h>
+#include <sys/stat.h>
+#include <syslog.h>
+
+#if defined(HAVE_UNIX_DOMAIN_SOCKETS)
+# include <sys/un.h>
+#endif
+
+#include "inn/innconf.h"
+#include "inn/messages.h"
+#include "libinn.h"
+#include "storage.h"
+
+#include "article.h"
+#include "buffer.h"
+#include "configfile.h"
+#include "connection.h"
+#include "endpoint.h"
+#include "host.h"
+#include "innlistener.h"
+#include "misc.h"
+#include "tape.h"
+
+#define INHERIT 1
+#define NO_INHERIT 0
+
+/* exports */
+bool talkToSelf ;
+extern int debugWrites ;
+bool sigFlag = false ;
+const char *InputFile ;
+char *configFile = NULL ;
+bool RollInputFile = false ;
+char *pidFile = NULL ;
+bool useMMap = false ;
+void (*gPrintInfo) (void) ;
+char *dflTapeDir;
+/* these are used by imapfeed */
+char *deliver_username = NULL;
+char *deliver_authname = NULL;
+char *deliver_password = NULL;
+char *deliver_realm = NULL;
+const char *deliver_rcpt_to = "+%s";
+char *deliver_to_header = NULL;
+
+/* imports */
+extern char *versionInfo ;
+#if defined (sun)
+extern char *optarg ; /* needed for Solaris */
+extern int optind;
+#endif
+extern bool genHtml ;
+
+extern void openInputFile (void);
+
+/* privates */
+static char *logFile ;
+static char *newsspool ;
+
+static void sigemt (int sig) ;
+static void sigalrm (int sig) ;
+static void sigchld (int sig) ;
+static void sigint (int sig) ;
+static void sigquit (int sig) ;
+static void sighup (int sig) ;
+static void sigterm (int sig) ;
+static void sigusr (int sig) ;
+static void usage (int) ;
+static void gprintinfo (void) ;
+static void openLogFile (void) ;
+static void writePidFile (void) ;
+static int mainOptionsProcess (void *data) ;
+static int mainConfigLoadCbk (void *data) ;
+static void mainCleanup (void) ;
+
+static char *bopt = NULL ;
+static char *aopt = NULL ;
+static char *popt = NULL ;
+static bool Mopt = false ;
+static bool Zopt = false ;
+static bool Dopt = false ;
+static int debugLevel = 0 ;
+static unsigned int initialSleep = 2 ;
+static char *sopt = NULL ;
+static char *lopt = NULL ;
+static bool eopt = false ;
+static int elimit = 0 ;
+
+int main (int argc, char **argv)
+{
+ EndPoint ep ;
+ InnListener listener ;
+ int optVal, fd, rval ;
+ const char *subProgram = NULL ;
+ bool seenV = false ;
+ bool dynamicPeers = false ;
+ time_t now = theTime() ;
+ char dateString [30] ;
+ char *copt = NULL ;
+ char *debugFile;
+ bool checkConfig = false ;
+ bool val;
+
+ strlcpy (dateString,ctime(&now),sizeof (dateString)) ;
+
+ message_program_name = strrchr (argv [0],'/');
+ if (message_program_name == NULL)
+ message_program_name = argv [0] ;
+ else
+ message_program_name++;
+
+ gPrintInfo = gprintinfo ;
+
+ openlog (message_program_name,(int)(L_OPENLOG_FLAGS|LOG_PID),LOG_INN_PROG) ;
+ if (!innconf_read(NULL)) {
+ syslog(LOG_ERR, "cant read inn.conf\n");
+ exit(1);
+ }
+
+#if defined (HAVE_MMAP)
+ useMMap = true ;
+#else
+ useMMap = false ;
+#endif
+
+ message_handlers_die (2, error_log_stderr_date, message_log_syslog_err) ;
+ message_handlers_warn (1, message_log_syslog_warning);
+ message_handlers_notice (1, message_log_syslog_notice) ;
+
+#define OPT_STRING "a:b:c:Cd:e:hl:mMo:p:S:s:vxyz"
+
+ while ((optVal = getopt (argc,argv,OPT_STRING)) != EOF)
+ {
+ switch (optVal)
+ {
+ case 'a':
+ aopt = optarg ;
+ break ;
+
+ case 'b':
+ if ( !isDirectory (optarg) )
+ logAndExit (1,"Not a directory: %s\n",optarg) ;
+ bopt = optarg ;
+ break ;
+
+ case 'C':
+ checkConfig = true ;
+ break ;
+
+ case 'c':
+ copt = optarg ;
+ break ;
+
+ case 'd':
+ loggingLevel = atoi (optarg) ;
+ debugLevel = loggingLevel ;
+ Dopt = true ;
+ break ;
+
+ case 'e':
+ eopt = true ;
+ elimit = atoi (optarg) ;
+ if (elimit <= 0)
+ {
+ fprintf (stderr,"Illegal value for -e option\n") ;
+ usage (1) ;
+ }
+ break ;
+
+ case 'h':
+ usage (0) ;
+
+ case 'l':
+ lopt = optarg ;
+ break ;
+
+ case 'M':
+ Mopt = true ;
+ useMMap = false ;
+ break ;
+
+ case 'm':
+ artLogMissingArticles (true) ;
+ break ;
+
+ case 'o':
+ artSetMaxBytesInUse (atoi (optarg)) ;
+ break ;
+
+ case 'p':
+ popt = optarg ;
+ break ;
+
+ case 's':
+ subProgram = optarg ;
+ break ;
+
+ case 'S':
+ sopt = optarg ;
+ break ;
+
+ case 'v':
+ seenV = true ;
+ break ;
+
+ case 'x':
+ talkToSelf = true ;
+ break ;
+
+ case 'y':
+ dynamicPeers = true ;
+ break ;
+
+ case 'z':
+ Zopt = true ;
+ break ;
+
+ default:
+ usage (1) ;
+ }
+ }
+
+ argc -= optind;
+ argv += optind;
+
+ if (argc > 1)
+ usage (1) ;
+ else if (argc == 1)
+ InputFile = *argv;
+
+ if (seenV)
+ {
+ warn ("%s version: %s\n",message_program_name, versionInfo) ;
+ exit (0) ;
+ }
+
+ /* make sure we have valid fds 0, 1 & 2 so it is not taken by
+ something else, probably openlog(). fd 0 will be freopen()ed on the
+ inputFile, the subProgram, or ourself. fd 1 and fd 2 will
+ be freopen()ed on the log file (or will stay pointed at /dev/null).
+
+ without doing this, if the descriptors were closed then the
+ freopen calls on some systems (like BSDI 2.1) will really close
+ whatever has aquired the stdio descriptors, such as the socket
+ to syslogd.
+
+ XXX possible problems: what if fd 0 is closed but no inputFile,
+ XXX subProgram or talkToSelf is true? it will not be freopen()ed, so
+ XXX innfeed won't have any fresh data (besides, fd 0 is only writable
+ XXX here). perhaps a warning should be issued.
+ */
+ do
+ {
+ fd = open("/dev/null", O_WRONLY);
+ switch (fd)
+ {
+ case -1:
+ logAndExit (1,"open(\"/dev/null\", O_WRONLY): %s",
+ strerror (errno));
+ break;
+ case 0:
+ case 1:
+ case 2:
+ /* good, we saved an fd from being trounced */
+ break;
+ default:
+ close(fd);
+ }
+ } while (fd < 2);
+
+ if ( !checkConfig )
+ {
+ notice ("ME starting %s at %s", versionInfo, dateString) ;
+ }
+
+ val = true;
+ if (!SMsetup(SM_PREOPEN, (void *)&val)) {
+ syslog(LOG_ERR, "cant setup the storage subsystem\n");
+ exit(1);
+ }
+ if (!SMinit()) {
+ d_printf(0, "Storage manager initialization failed\n");
+ syslog(LOG_ERR, "Storage manager initialization failed\n");
+ exit(1);
+ }
+
+ if (subProgram == NULL && talkToSelf == false)
+ {
+ struct stat buf ;
+
+ if (fstat (0,&buf) < 0)
+ logAndExit (1,"ME oserr fstat stdin: %s", strerror (errno)) ;
+ else if (S_ISREG (buf.st_mode))
+ InputFile = "";
+ }
+
+ /*
+ * set up the config file name and then read the file in. Order is important.
+ */
+ configAddLoadCallback (mainOptionsProcess,(checkConfig ? stderr : NULL)) ;
+ configAddLoadCallback (tapeConfigLoadCbk,(checkConfig ? stderr : NULL)) ;
+
+ configAddLoadCallback (endpointConfigLoadCbk,(checkConfig ? stderr : NULL));
+ configAddLoadCallback (hostConfigLoadCbk,(checkConfig ? stderr : NULL)) ;
+ configAddLoadCallback (cxnConfigLoadCbk,(checkConfig ? stderr : NULL)) ;
+ configAddLoadCallback (mainConfigLoadCbk,(checkConfig ? stderr : NULL)) ;
+ configAddLoadCallback (listenerConfigLoadCbk,(checkConfig ? stderr : NULL));
+
+ if (copt != NULL && *copt == '\0')
+ {
+ logOrPrint (LOG_CRIT,(checkConfig ? stderr : NULL),
+ "Empty pathname for ``-c'' option") ;
+ exit (1) ;
+ }
+ configFile = buildFilename(innconf->pathetc, copt ? copt : CONFIG_FILE);
+ dflTapeDir = buildFilename(innconf->pathspool, TAPE_DIRECTORY);
+
+ rval = readConfig (configFile,(checkConfig ? stderr : NULL),
+ checkConfig,loggingLevel > 0);
+
+ if (subProgram != NULL && (talkToSelf == true || InputFile))
+ {
+ d_printf (0,"Cannot specify '-s' with '-x' or an input file\n") ;
+ syslog (LOG_ERR,"Incorrect arguments: '-s' with '-x' or an input file\n");
+ usage (1) ;
+ }
+
+ if (checkConfig)
+ {
+ if (!rval)
+ {
+ fprintf (stderr,"config loading failed.\n") ;
+ exit (1) ;
+ }
+ else
+ {
+ fprintf (stderr,"config loading succeeded.\n") ;
+ exit (0) ;
+ }
+ }
+ else if (!rval)
+ exit (1) ;
+
+ debugFile = buildFilename (innconf->pathlog, DEBUG_FILE);
+ if (loggingLevel == 0 && fileExistsP (debugFile))
+ loggingLevel = 1 ;
+
+ if (logFile == NULL && ! isatty (fileno (stderr)))
+ logFile = buildFilename (innconf->pathlog, LOG_FILE) ;
+
+ if (logFile)
+ openLogFile () ;
+
+ openfds = 4 ; /* stdin, stdout, stderr and syslog */
+
+ writePidFile ();
+
+ if (subProgram != NULL)
+ {
+ int fds [2] ;
+ int pid ;
+
+ if (pipe (fds) < 0)
+ sysdie ("ME fatal pipe") ;
+
+ if ((pid = fork ()) < 0)
+ {
+ sysdie ("ME fatal fork") ;
+ }
+ else if (pid == 0)
+ { /* child */
+ close (fds[0]) ;
+ close (0) ;
+ close (1) ;
+ close (2) ;
+ dup2 (fds[1],1) ;
+ dup2 (fds[1],2) ;
+ execlp ("sh", "sh", "-c", subProgram, (char *) 0) ;
+ perror ("execlp") ;
+ exit (1) ;
+ }
+ else
+ { /* parent */
+ close (0) ;
+ dup2 (fds[0],0) ;
+ close (fds[1]) ;
+ xsignal(SIGCHLD,sigchld) ;
+ openfds++ ;
+ }
+ }
+ else if (talkToSelf)
+ {
+ /* We're not really getting information from innd or a subprogram,
+ but are just processing backlog files. We set up a pipe to ourself
+ that we never write to, to simulate an idle innd. */
+ int pipefds [2] ;
+
+ if (pipe (pipefds) != 0)
+ sysdie ("ME fatal pipe") ;
+
+ close (0) ;
+ dup2 (pipefds [0], 0) ;
+
+ openfds++ ;
+ openfds++ ;
+ }
+
+ if (chdir (newsspool) != 0)
+ sysdie ("ME fatal chdir %s", newsspool) ;
+
+ /* hook up the endpoint to the source of new article information (usually
+ innd). */
+ ep = newEndPoint (0) ; /* fd 0, i.e. stdin */
+
+ /* now arrange for this endpoint to always be the first one checked for
+ possible activity. */
+ setMainEndPoint (ep) ;
+
+ listener = newListener (ep, talkToSelf,dynamicPeers) ;
+ mainListener = listener ;
+
+ sleep (initialSleep) ;
+
+ if (innconf->rlimitnofile >= 0)
+ if (setfdlimit (innconf->rlimitnofile) < 0)
+ syswarn ("ME oserr setrlimit(RLIM_NOFILE,%ld)", innconf->rlimitnofile) ;
+
+ if (innconf->timer > 0)
+ TMRinit (TMR_MAX) ;
+
+ configHosts (talkToSelf) ;
+
+ if (InputFile && *InputFile) {
+ openInputFile () ;
+ }
+
+ /* handle signal to shutdown */
+ setSigHandler (SIGTERM,sigterm) ;
+ setSigHandler (SIGQUIT,sigquit) ;
+
+ /* handle signal to reload config */
+ setSigHandler (SIGHUP,sighup) ;
+
+ /* handle signal to print snapshot. */
+ setSigHandler (SIGINT,sigint) ;
+
+ /* handle signal to roll input file */
+ setSigHandler (SIGALRM,sigalrm) ;
+
+ /* handle signal to flush all the backlog files */
+ setSigHandler (SIGCHLD,sigemt) ;
+
+ /* we can increment and decrement logging levels by sending SIGUSR{1,2} */
+ setSigHandler (SIGUSR1,sigusr) ;
+ setSigHandler (SIGUSR2,sigusr) ;
+
+ atexit (mainCleanup) ;
+
+ Run () ;
+
+ exit (0) ;
+}
+
+static void usage (int val)
+{
+ fprintf (stderr,"usage: %s [ options ] [ file ]\n\n",
+ message_program_name) ;
+ fprintf (stderr,"Version: %s\n\n",versionInfo) ;
+ fprintf (stderr,"Config file: %s\n",CONFIG_FILE) ;
+ fprintf (stderr,"Backlog directory: %s/%s\n", innconf->pathspool, TAPE_DIRECTORY) ;
+ fprintf (stderr,"\nLegal options are:\n") ;
+ fprintf (stderr,"\t-a dir Use the given directory as the top of the article spool\n") ;
+
+ fprintf (stderr,"\t-b dir Use the given directory as the the storage\n");
+ fprintf (stderr,"\t place for backlog files and lock files.\n");
+
+ fprintf (stderr,"\t-c file Use the given file as the config file instead of the\n");
+ fprintf (stderr,"\t default of %s\n",CONFIG_FILE);
+
+ fprintf (stderr,"\t-C Check the config file and then exit.\n") ;
+ fprintf (stderr,"\t-d num set the logging level to num (an integer).\n");
+ fprintf (stderr,"\t Larger value means more logging. 0 means no\n");
+ fprintf (stderr,"\t logging. The default is 0\n");
+
+ fprintf (stderr,"\t-e bytes Keep the output backlog files to no bigger\n");
+ fprintf (stderr,"\t than %.2f times this number\n",LIMIT_FUDGE);
+
+ fprintf (stderr,"\t-h print this message\n");
+
+ fprintf (stderr,"\t-l file redirect stderr and stdout to the given file.\n");
+ fprintf (stderr,"\t When run under INN they normally are redirected to\n");
+ fprintf (stderr,"\t /dev/null. This is needed if using '-d'.\n");
+
+ fprintf (stderr,"\t-m Log information on all missing articles\n");
+
+ fprintf (stderr,"\t-M Turn *off* use of mmap\n") ;
+#if ! defined (HAVE_MMAP)
+ fprintf (stderr,"\t (a no-op as this excutable has been built without mmap support\n") ;
+#endif
+
+ fprintf (stderr,"\t-p file Write the process id to the given file\n") ;
+ fprintf (stderr,"\t instead of the default of %s\n",PID_FILE);
+ fprintf (stderr,"\t A relative path is relative to %s\n", innconf->pathrun) ;
+
+ fprintf (stderr,"\t-s command run the given command in a subprocess and use\n");
+ fprintf (stderr,"\t its output as article information instead of\n");
+ fprintf (stderr,"\t running under innd\n");
+
+ fprintf (stderr,"\t-S file Use the give filename instead of innfeed.status\n") ;
+ fprintf (stderr,"\t relative pathnames start from %s\n", innconf->pathlog) ;
+
+ fprintf (stderr,"\t-v print version information\n");
+
+ fprintf (stderr,"\t-x Do not read any article information off stdin,\n");
+ fprintf (stderr,"\t but simply process backlog files and then exit\n");
+ fprintf (stderr,"\t when done\n");
+
+ fprintf (stderr,"\t-y Add peers dynamically. If an unrecognized peername\n");
+ fprintf (stderr,"\t is received from innd, then it is presumed to also\n");
+ fprintf (stderr,"\t be the ip name and a new peer binding is set up\n");
+
+ fprintf (stderr,"\t-z have each of the connections issue their own stats\n");
+ fprintf (stderr,"\t whenever they close, or whenever their controller\n");
+ fprintf (stderr,"\t issues its own stats\n");
+
+ exit (val) ;
+}
+
+static void sigterm (int sig UNUSED)
+{
+ notice ("ME received shutdown signal") ;
+ shutDown (mainListener) ;
+}
+
+static void sigquit (int sig UNUSED)
+{
+ sigterm (0) ;
+}
+
+static void sigint (int sig UNUSED)
+{
+ gprintinfo () ;
+}
+
+static void sighup (int sig UNUSED)
+{
+ notice ("ME reloading config file %s", configFile) ;
+
+ if (!readConfig (configFile,NULL,false,loggingLevel > 0))
+ {
+ die ("ME config aborting, error parsing config file") ;
+ }
+
+ configHosts (talkToSelf) ;
+}
+
+static void sigemt (int sig UNUSED)
+{
+ gFlushTapes () ;
+}
+
+static void sigalrm (int sig UNUSED)
+{
+ if (InputFile == NULL)
+ warn ("ME signal SIGALRM in non-funnel-file mode ignored") ;
+ else
+ {
+ RollInputFile = true;
+ syslog(LOG_NOTICE, "ME preparing to roll %s", InputFile);
+ }
+}
+
+static void sigchld (int sig UNUSED)
+{
+#if 0
+ wait (&status) ; /* we don't care */
+#endif
+
+ xsignal (sig,sigchld) ;
+}
+
+ /* SIGUSR1 increments logging level. SIGUSR2 decrements. */
+static void sigusr (int sig)
+{
+ if (sig == SIGUSR1) {
+ loggingLevel++ ;
+ notice ("ME increasing logging level to %d", loggingLevel) ;
+ } else if (sig == SIGUSR2 && loggingLevel > 0) {
+ loggingLevel-- ;
+ notice ("ME decreasing logging level to %d", loggingLevel) ;
+ }
+}
+
+static void openLogFile (void)
+{
+ FILE *fpr ;
+
+ if (logFile)
+ {
+ fpr = freopen (logFile,"a",stdout) ;
+ if (fpr != stdout)
+ logAndExit (1,"freopen (%s, \"a\", stdout): %s",
+ logFile, strerror (errno)) ;
+
+ fpr = freopen (logFile,"a",stderr) ;
+ if (fpr != stderr)
+ logAndExit (1,"freopen (%s, \"a\", stderr): %s",
+ logFile, strerror (errno)) ;
+
+#if defined (HAVE_SETBUFFER)
+ setbuffer (stdout, NULL, 0) ;
+ setbuffer (stderr, NULL, 0) ;
+#else
+ setbuf (stdout, NULL) ;
+ setbuf (stderr, NULL) ;
+#endif
+ }
+}
+
+static void writePidFile (void)
+{
+ FILE *F;
+ int pid;
+
+ if (pidFile == NULL)
+ logAndExit (1,"NULL pidFile\n") ;
+
+ /* Record our PID. */
+ pid = getpid();
+ if ((F = fopen(pidFile, "w")) == NULL)
+ {
+ syslog(LOG_ERR, "ME cant fopen %s %m", pidFile);
+ }
+ else
+ {
+ if (fprintf(F, "%ld\n", (long)pid) == EOF || ferror(F))
+ {
+ syslog(LOG_ERR, "ME cant fprintf %s %m", pidFile);
+ }
+ if (fclose(F) == EOF)
+ {
+ syslog(LOG_ERR, "ME cant fclose %s %m", pidFile);
+ }
+ if (chmod(pidFile, 0664) < 0)
+ {
+ syslog(LOG_ERR, "ME cant chmod %s %m", pidFile);
+ }
+ }
+}
+
+static void gprintinfo (void)
+{
+ char *snapshotFile;
+ FILE *fp;
+ time_t now = theTime() ;
+
+ snapshotFile = buildFilename(innconf->pathlog, SNAPSHOT_FILE);
+ fp = fopen (snapshotFile,"a") ;
+ if (fp == NULL)
+ {
+ syswarn ("ME fopen %s", snapshotFile) ;
+ free(snapshotFile);
+ return ;
+ }
+ free(snapshotFile);
+
+#if defined (HAVE_SETBUFFER)
+ setbuffer (fp, NULL, 0) ;
+#else
+ setbuf (fp, NULL) ;
+#endif
+
+ fprintf (fp,"----------------------------System snaphot taken at: %s\n",
+ ctime (&now)) ;
+ gPrintListenerInfo (fp,0) ;
+ fprintf (fp,"\n\n\n\n") ;
+ gPrintHostInfo (fp,0) ;
+ fprintf (fp,"\n\n\n\n") ;
+ gPrintCxnInfo (fp,0) ;
+ fprintf (fp,"\n\n\n\n") ;
+ gPrintArticleInfo (fp,0) ;
+ fprintf (fp,"\n\n\n\n") ;
+ gPrintBufferInfo (fp,0) ;
+ fprintf (fp,"\n\n\n\n") ;
+ fclose (fp) ;
+}
+
+/* called after the config file is loaded and after the config data has
+ been updated with command line options. */
+static int mainConfigLoadCbk (void *data)
+{
+ FILE *fp = (FILE *) data ;
+ char *p ;
+ long ival ;
+ int bval ;
+
+ if (getString (topScope,"news-spool", &p,NO_INHERIT))
+ {
+ if ( !isDirectory (p) && isDirectory (innconf->patharticles) )
+ {
+ logOrPrint (LOG_WARNING,fp,
+ "ME config: definition of news-spool (%s) is a"
+ " non-existant directory. Using %s",p,
+ innconf->patharticles) ;
+ p = xstrdup (innconf->patharticles) ;
+ }
+ else if (!isDirectory (p))
+ logAndExit (1,"Bad spool directories: %s, %s\n",p,innconf->patharticles) ;
+ }
+ else if (!isDirectory (innconf->patharticles))
+ logAndExit (1,"ME config: no definition of news-spool, and %s is no good",
+ innconf->patharticles);
+ else
+ p = xstrdup (innconf->patharticles) ;
+ newsspool = p ;
+
+ /***************************************************/
+
+ if (getString (topScope,"input-file",&p,NO_INHERIT))
+ {
+ if (*p != '\0')
+ InputFile = buildFilename (getTapeDirectory(),p) ;
+ else
+ InputFile = "" ;
+ free (p) ;
+ }
+
+ if (getString (topScope,"pid-file",&p,NO_INHERIT))
+ {
+ pidFile = buildFilename (innconf->pathrun,p) ;
+ free (p) ;
+ }
+ else
+ pidFile = buildFilename (innconf->pathrun,PID_FILE) ;
+
+ if (getInteger (topScope,"debug-level",&ival,NO_INHERIT))
+ loggingLevel = (unsigned int) ival ;
+
+
+ if (getInteger (topScope,"initial-sleep",&ival,NO_INHERIT))
+ initialSleep = (unsigned int) ival ;
+
+
+ if (getBool (topScope,"use-mmap",&bval,NO_INHERIT))
+ useMMap = (bval ? true : false) ;
+
+
+ if (getString (topScope,"log-file",&p,NO_INHERIT))
+ {
+ logFile = buildFilename (innconf->pathlog,p) ;
+ free (p) ;
+ }
+
+ /* For imap/lmtp delivering */
+ if (getString (topScope,"deliver-username",&p, NO_INHERIT))
+ {
+ deliver_username = p;
+ /* don't need to free */
+ }
+
+ if (getString (topScope,"deliver-authname",&p, NO_INHERIT))
+ {
+ deliver_authname = p;
+ /* don't need to free */
+ }
+
+ if (getString (topScope,"deliver-password",&p, NO_INHERIT))
+ {
+ deliver_password = p;
+ /* don't need to free */
+ }
+
+ if (getString (topScope,"deliver-realm",&p, NO_INHERIT))
+ {
+ deliver_realm = p;
+ /* don't need to free */
+ }
+
+ if (getString (topScope,"deliver-rcpt-to",&p, NO_INHERIT))
+ {
+ deliver_rcpt_to = p;
+ /* don't need to free */
+ }
+
+ if (getString (topScope,"deliver-to-header",&p, NO_INHERIT))
+ {
+ deliver_to_header = p;
+ /* don't need to free */
+ }
+
+
+
+ return 1 ;
+}
+
+/*
+ * called after config file is loaded but before other callbacks, so we
+ * can adjust config file values from options. They will be validated in the
+ * second callback.
+ */
+static int mainOptionsProcess (void *data UNUSED)
+{
+ value *v ;
+
+ if (bopt != NULL)
+ {
+ if ((v = findValue (topScope,"backlog-directory",NO_INHERIT)) != NULL)
+ {
+ free (v->v.charp_val) ;
+ v->v.charp_val = xstrdup (bopt) ;
+ }
+ else
+ addString (topScope,"backlog-directory",xstrdup (bopt)) ;
+ }
+
+ if (aopt != NULL)
+ {
+ if ((v = findValue (topScope,"news-spool",NO_INHERIT)) != NULL)
+ {
+ free (v->v.charp_val) ;
+ v->v.charp_val = xstrdup (aopt) ;
+ }
+ else
+ addString (topScope,"news-spool",xstrdup (aopt)) ;
+ }
+
+ if (sopt != NULL)
+ {
+ if ((v = findValue (topScope,"status-file",NO_INHERIT)) != NULL)
+ {
+ free (v->v.charp_val) ;
+ v->v.charp_val = xstrdup (sopt) ;
+ }
+ else
+ addString (topScope,"status-file",xstrdup (sopt)) ;
+ }
+
+
+ if (Dopt)
+ {
+ if ((v = findValue (topScope,"debug-level",NO_INHERIT)) != NULL)
+ v->v.int_val = debugLevel ;
+ else
+ addInteger (topScope,"debug-level",debugLevel) ;
+ }
+
+
+ if (eopt || talkToSelf)
+ {
+ if (talkToSelf)
+ elimit = 0 ;
+
+ if ((v = findValue (topScope,"backlog-limit",NO_INHERIT)) != NULL)
+ v->v.int_val = elimit ;
+ else
+ addInteger (topScope,"backlog-limit",elimit) ;
+ }
+
+
+ if (Mopt)
+ {
+ if ((v = findValue (topScope,"use-mmap",NO_INHERIT)) != NULL)
+ v->v.bool_val = 0 ;
+ else
+ addBoolean (topScope,"use-mmap",0) ;
+ }
+
+
+ if (popt != NULL)
+ {
+ if ((v = findValue (topScope,"pid-file",NO_INHERIT)) != NULL)
+ {
+ free (v->v.charp_val) ;
+ v->v.charp_val = xstrdup (popt) ;
+ }
+ else
+ addString (topScope,"pid-file",xstrdup (popt)) ;
+ }
+
+ if (Zopt)
+ {
+ if ((v = findValue (topScope,"connection-stats",NO_INHERIT)) != NULL)
+ v->v.bool_val = 1 ;
+ else
+ addBoolean (topScope,"connection-stats",1) ;
+ }
+
+ if (lopt != NULL)
+ {
+ if ((v = findValue (topScope,"log-file",NO_INHERIT)) != NULL)
+ {
+ free (v->v.charp_val) ;
+ v->v.charp_val = xstrdup (lopt) ;
+ }
+ else
+ addString (topScope,"log-file",xstrdup (lopt)) ;
+ }
+
+ if (InputFile != NULL)
+ {
+ if ((v = findValue (topScope,"input-file",NO_INHERIT)) != NULL)
+ {
+ free (v->v.charp_val) ;
+ v->v.charp_val = xstrdup (InputFile) ;
+ }
+ else
+ addString (topScope,"input-file",xstrdup (InputFile)) ;
+ }
+
+ return 1 ;
+}
+
+
+
+static void mainCleanup (void)
+{
+ free ((void *)configFile) ;
+ free ((void *)pidFile) ;
+ free (logFile) ;
+ free (newsspool) ;
+ configFile = NULL ;
+ pidFile = NULL ;
+ logFile = NULL ;
+ newsspool = NULL ;
+}
+
+
+void mainLogStatus (FILE *fp)
+{
+ fprintf (fp,"%sGlobal configuration parameters:%s\n",
+ genHtml ? "<B>" : "", genHtml ? "</B>" : "") ;
+ fprintf (fp," Mode: ") ;
+ if (InputFile != NULL)
+ fprintf (fp,"Funnel file") ;
+ else if (talkToSelf)
+ fprintf (fp,"Batch") ;
+ else
+ fprintf (fp,"Channel") ;
+ if (InputFile != NULL)
+ fprintf (fp," (%s)",(*InputFile == '\0' ? "stdin" : InputFile)) ;
+ fprintf (fp,"\n") ;
+ fprintf (fp," News spool: %s\n",newsspool) ;
+ fprintf (fp," Pid file: %s\n",pidFile) ;
+ fprintf (fp," Log file: %s\n",(logFile == NULL ? "(none)" : logFile));
+ fprintf (fp," Debug level: %2ld Mmap: %s\n",
+ (long)loggingLevel,boolToString(useMMap)) ;
+ fprintf (fp,"\n") ;
+}
--- /dev/null
+/* $Id: misc.c 7420 2005-10-09 04:40:13Z eagle $
+**
+** Helper routines for the innfeed program.
+**
+** Written by James Brister <brister@vix.com>
+*/
+
+#include "innfeed.h"
+#include "config.h"
+#include "clibrary.h"
+
+#include <assert.h>
+#include <ctype.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <netdb.h>
+#include <netinet/in.h>
+#include <signal.h>
+#include <syslog.h>
+#include <sys/param.h>
+#include <sys/stat.h>
+#include <time.h>
+
+/* FIXME: Default to a max length of 256 characters for path names if the
+ host headers doesn't give better information. Should be replaced by the
+ code from Stevens. */
+#ifndef PATH_MAX
+# define PATH_MAX 256
+#endif
+
+#include "inn/messages.h"
+#include "libinn.h"
+
+#include "endpoint.h"
+#include "misc.h"
+#include "tape.h"
+
+unsigned int openfds ;
+int debuggingOutput ;
+unsigned int loggingLevel ;
+char **PointersFreedOnExit ;
+
+bool debuggingDump = true ;
+extern void (*gPrintInfo) (void) ;
+void (*gCleanUp) (void) = 0 ;
+
+
+/* Log a message to stderr, called from warn or die. Mostly the same as the
+ standard message_log_stderr, but prepends the date to each line. */
+void
+error_log_stderr_date(int len UNUSED, const char *fmt, va_list args, int err)
+{
+ char timebuff[30];
+ time_t now;
+ struct tm *tm;
+
+ now = time(NULL);
+ tm = localtime(&now);
+ strftime(timebuff, sizeof(timebuff), "%Y-%m-%d %H:%M:%S", tm);
+ fprintf(stderr, "%s %s: ", timebuff,
+ (message_program_name ? message_program_name : "UNKNOWN"));
+ vfprintf(stderr, fmt, args);
+ if (err) fprintf(stderr, ": %s", strerror(err));
+ fprintf(stderr, "\n");
+}
+
+/* If desired, print out the state of innfeed, call a cleanup function, and
+ then dump core. Used as an exit handler for die. */
+int
+dump_core(void)
+{
+#if SNAPSHOT_ON_DIE
+ if (debuggingDump && gPrintInfo != NULL)
+ (*gPrintInfo)();
+#endif
+
+ if (gCleanUp != NULL)
+ (*gCleanUp)();
+
+ if (CORE_DIRECTORY != NULL)
+ chdir(CORE_DIRECTORY);
+ else
+ chdir(getTapeDirectory());
+
+ sleep(5);
+ abort();
+
+ /* Not reached. */
+ return 1;
+}
+
+/* An alternate version of die, used when we don't want to dump core. This
+ should somehow eventually be phased out to simplify things; it's
+ basically a copy of die() from lib/error.c that ignores the cleanup
+ handler and has innfeed's handlers hard-coded (ugh). */
+void
+logAndExit(int status, const char *format, ...)
+{
+ va_list args;
+ int length;
+
+ va_start(args, format);
+ length = vsnprintf(NULL, 0, format, args);
+ va_end(args);
+ va_start(args, format);
+ error_log_stderr_date(length, format, args, 0);
+ va_end(args);
+ va_start(args, format);
+ message_log_syslog_err(length, format, args, 0);
+ va_end(args);
+ exit(status);
+}
+
+
+
+void d_printf (unsigned int level, const char *fmt, ...)
+{
+ static pid_t myPid ;
+ char timeString [30] ;
+ time_t now ;
+ va_list ap ;
+
+ if (myPid == 0)
+ myPid = getpid () ;
+
+ if (loggingLevel < level)
+ return ;
+
+ now = theTime() ;
+ /* strip off leading day name */
+ strlcpy (timeString, ctime (&now) + 4, sizeof (timeString)) ;
+ timeString [15] = '\0' ; /* strip off trailing year and newline */
+
+ va_start (ap, fmt) ;
+ fprintf (stderr, "%s %s[%ld]: ",timeString,
+ (message_program_name ? message_program_name : "UNKNOWN"),
+ (long) myPid) ;
+ vfprintf (stderr, fmt, ap) ;
+ va_end (ap) ;
+}
+
+void logOrPrint (int level, FILE *fp, const char *fmt, ...)
+{
+ va_list ap ;
+
+ va_start (ap,fmt) ;
+ if (fp != NULL)
+ {
+ vfprintf (fp,fmt,ap) ;
+ fputc ('\n',fp) ;
+ }
+ else
+ {
+ char buffer [512] ; /* gag me */
+
+ vsnprintf (buffer,sizeof (buffer),fmt,ap) ;
+ syslog (level,"%s",buffer) ;
+ }
+ va_end (ap) ;
+}
+
+
+
+/* return true if the file exists and is a regular file. */
+bool fileExistsP (const char *filename)
+{
+ struct stat buf ;
+
+ if (stat (filename,&buf) < 0)
+ return false ;
+
+ return (S_ISREG (buf.st_mode) ? true : false) ;
+}
+
+
+bool isDirectory (const char *filename)
+{
+ struct stat buf ;
+
+ if (stat (filename,&buf) < 0)
+ return false ;
+
+ return (S_ISDIR (buf.st_mode) ? true : false) ;
+}
+
+
+
+bool getNntpResponse (char *p, int *code, char **rest)
+{
+ bool rval = true ;
+ int cd = 0 ;
+ int digits = 0 ;
+
+ if (rest)
+ *rest = 0 ;
+ *code = 0 ;
+
+ if (p == NULL)
+ return false ;
+
+ while (*p && CTYPE (isspace, *p))
+ p++ ;
+
+ while (*p && CTYPE (isdigit, *p))
+ {
+ digits++ ;
+ cd = (cd * 10) + (*p - '0') ;
+ p++ ;
+ }
+
+ if (digits != 3)
+ return false ;
+
+ if (*p == '-')
+ p++ ;
+
+ while (*p && CTYPE (isspace, *p))
+ p++ ;
+
+ if (rest)
+ *rest = p ;
+
+ *code = cd ;
+
+ return rval ;
+}
+
+
+
+/* Pull out a message id from a response on to a streaming command */
+char *getMsgId (const char *p)
+{
+ const char *q ;
+ char *rval ;
+
+ while (*p && CTYPE (isspace, *p)) p++ ;
+ while (*p && !CTYPE (isspace, *p)) p++ ; /* skip response code */
+ while (*p && CTYPE (isspace, *p)) p++ ;
+
+ if ( *p == '\0' )
+ return NULL ;
+
+ q = p ;
+ while ( *q && !CTYPE (isspace, *q) )
+ q++ ;
+
+ rval = xstrndup (p, q - p) ;
+
+ return rval ;
+}
+
+
+
+
+char *findNonBlankString (char *ptr, char **tail)
+{
+ char *p, *q ;
+
+ for (p = ptr ; *p && CTYPE (isspace, *p) ; p++)
+ /* nada */ ;
+ if ( ! *p )
+ return NULL ;
+
+ for (q = p ; *q && !CTYPE (isspace, *q) ; q++)
+ /* nada */ ;
+
+ *tail = q ;
+
+ return p ;
+}
+
+
+/* strtok can't handle zero length tokens. */
+char *mystrtok (char *line, const char *sep)
+{
+ static char *newPoint ;
+ char *oldline ;
+
+ if (line == NULL && newPoint == NULL)
+ return NULL ;
+
+ if (line != NULL)
+ {
+ oldline = line ;
+ while (*line != '\0' && strchr (sep,*line) == NULL)
+ line++ ;
+
+ if (*line == '\0')
+ newPoint = NULL ;
+ else
+ {
+ newPoint = line + 1 ;
+ *line = '\0' ;
+ }
+ }
+ else
+ {
+ if (newPoint == NULL)
+ return NULL ;
+
+ oldline = newPoint ;
+ line = oldline ;
+
+ while (*line != '\0' && strchr (sep,*line) == NULL)
+ line++ ;
+
+ if (*line == '\0')
+ newPoint = NULL ;
+ else
+ {
+ newPoint = line + 1 ;
+ *line = '\0' ;
+ }
+ }
+
+ return oldline ;
+}
+
+
+
+void trim_ws (char *string)
+{
+ char *p ;
+ unsigned int len ;
+
+ assert (string != NULL) ;
+
+ len = strlen (string) ;
+ if (len == 0)
+ return ;
+
+ for (p = string + len - 1 ; p >= string && CTYPE (isspace, *p) ; p--)
+ /* nada */ ;
+ *++p = '\0' ;
+}
+
+
+#if 0
+/* Scribble on top of memory we're about to free. */
+void deadBeef (void *base, size_t byteCount)
+{
+ unsigned char *b = (unsigned char *) base ;
+ int i ;
+
+#if 0
+
+ memset (base, 0, byteCount) ;
+
+#else
+
+ assert (b != NULL) ;
+
+ for (i = 0 ; i < ((int) byteCount) - 4 ; i += 4)
+ {
+#if 0
+ *((int *) (b + i)) = 0xdeadbeef ;
+#else
+ b [i + 0] = (unsigned char) 0xde ;
+ b [i + 1] = (unsigned char) 0xad ;
+ b [i + 2] = (unsigned char) 0xbe ;
+ b [i + 3] = (unsigned char) 0xef ;
+#endif
+ }
+
+ switch (byteCount % 4)
+ {
+ case 0:
+ *(b + i + 3) = (unsigned char) 0xef ;
+
+ case 3:
+ *(b + i + 2) = (unsigned char) 0xbe ;
+
+ case 2:
+ *(b + i + 1) = (unsigned char) 0xad ;
+
+ case 1:
+ *b = (unsigned char) 0xde ;
+ }
+
+#endif
+}
+#endif
+
+/* Not using plain flock or lockf 'cause I don't want to waste file
+ descriptors. This routine is based on the file shlock.c from INN. */
+bool lockFile (const char *fileName)
+{
+ char buff [20] ;
+ char tmpName [PATH_MAX], realName [PATH_MAX] ;
+ char *p ;
+ int fd, i ;
+ pid_t pid = getpid () ;
+
+ strlcpy (realName,fileName,sizeof (realName)) ;
+ if ((p = strrchr (realName, '/')) != NULL)
+ {
+ *p = '\0' ;
+ snprintf (tmpName, sizeof(tmpName), "%s/lockf%ld", realName,
+ (long) pid) ;
+ *p = '/' ;
+ }
+ else
+ snprintf (tmpName, sizeof(tmpName), "lockf%ld", (long) pid) ;
+
+ /* Create the temporary name for the lock file. */
+ while ((fd = open (tmpName, O_RDWR | O_CREAT | O_EXCL, 0644)) < 0)
+ {
+ switch (errno)
+ {
+ default:
+ unlink (tmpName) ;
+ syswarn ("ME lock file open: %s", tmpName) ;
+ return false ;
+
+ case EEXIST:
+ if (unlink (tmpName) < 0)
+ {
+ syswarn ("ME lock file unlink: %s", tmpName) ;
+ return false ;
+ }
+ break;
+ }
+ }
+
+ /* stick our pid in the temp file. */
+ snprintf (buff,sizeof(buff),"%ld\n",(long) pid) ;
+ if (write (fd,buff,(size_t) strlen (buff)) != (int) strlen (buff))
+ {
+ syswarn ("ME lock file pid-write") ;
+ close (fd) ;
+ unlink (tmpName) ;
+ return false ;
+ }
+ close (fd) ;
+
+ /* now link the real name to the temp file. */
+ while (link (tmpName,realName) < 0)
+ {
+ switch (errno)
+ {
+ default: /* opps. bailing out. */
+ syswarn ("ME lock file link: %s", realName) ;
+ unlink (tmpName) ;
+ return false ;
+
+ case EEXIST:
+ /* the real lock file exists. So pull out the pid in there and
+ see if that process is still alive. */
+ if ((fd = open (realName,O_RDONLY)) < 0)
+ {
+ syswarn ("ME lock file open: %s", realName) ;
+ unlink (tmpName) ;
+ return false ;
+ }
+
+ if ((i = read (fd,buff,sizeof (buff) - 1)) <= 0)
+ {
+ close (fd) ;
+ unlink (tmpName) ;
+ return false ;
+ }
+ close (fd) ;
+
+ buff [i] = '\0' ;
+ pid = (pid_t) atol (buff) ;
+ if (pid <= 0)
+ {
+ warn ("ME lock bad-pid info in %s: %s", realName, buff) ;
+ unlink (tmpName) ;
+ return false ;
+ }
+
+ /* now send a null signal to the process named inside to see if
+ it's still alive. */
+ if (kill (pid,0) == 0)
+ {
+ warn ("ME lock in-use already: %s by pid %ld", realName,
+ (unsigned long) pid);
+ unlink (tmpName) ;
+ return false ; /* process is still alive */
+ }
+
+ /* process that took out the lock is gone */
+ if (unlink (realName) < 0)
+ {
+ syswarn ("ME lock file unlink: %s", realName) ;
+ unlink (tmpName) ;
+ return false ;
+ }
+ }
+ }
+
+ unlink (tmpName) ;
+
+ return true ;
+}
+
+
+void unlockFile (const char *lockfile)
+{
+ unlink (lockfile) ;
+}
+
+
+bool endsIn (const char *string, const char *tail)
+{
+ size_t len = strlen (tail) ;
+ size_t slen = strlen (string) ;
+
+ if (slen < len)
+ return false ;
+ else if (strcmp (string + slen - len, tail) == 0)
+ return true ;
+ else
+ return false ;
+}
+
+
+/* append the contents of src to dest. src is removed if append if
+ successful */
+bool appendFile (const char *dest, const char *src)
+{
+ FILE *inTmp, *outTmp ;
+ char buff [BUFSIZ] ;
+ size_t rval ;
+
+ /* append the outputFilename file to the inputFilename file */
+ if ((outTmp = fopen (dest, "a")) == NULL)
+ die ("fopen (%s): %s",dest, strerror (errno)) ;
+ if ((inTmp = fopen (src, "r")) == NULL)
+ die ("fopen (%s): %s",src, strerror (errno)) ;
+
+ while ((rval = fread (buff,sizeof (char),BUFSIZ,inTmp)) > 0)
+ {
+ if (fwrite (buff,sizeof (char), rval, outTmp) != rval)
+ die ("fwrite: %s", strerror (errno)) ;
+ }
+
+ if (ferror (inTmp))
+ die ("Error on inTmp in newTape") ;
+ if (ferror (outTmp))
+ die ("Error on outTmp in newTape") ;
+
+ if (fclose (inTmp) != 0)
+ die ("fclose (inTmp): appendFile (%s,%s): %s",dest,src,strerror (errno)) ;
+
+ if (fclose (outTmp) != 0)
+ die ("fclose (outTmp): appendFile (%s,%s): %s",dest,src,strerror (errno)) ;
+
+ if (unlink (src) != 0)
+ die ("unlink (%s): %s", src, strerror (errno)) ;
+
+ return true ;
+}
+
+
+/* return true if file1 is older than file2 */
+bool isOlder (const char *file1, const char *file2)
+{
+ struct stat buf1 ;
+ struct stat buf2 ;
+
+ if (stat (file1,&buf1) < 0)
+ return false ;
+
+ if (stat (file2,&buf2) < 0)
+ return false ;
+
+ return ((buf1.st_mtime < buf2.st_mtime) ? true : false) ;
+}
+
+
+void freeCharP (char *charp)
+{
+ free (charp) ;
+}
+
+
+/* return the length of the file reference by the given file descriptor */
+long fileLength (int fd)
+{
+ struct stat buf ;
+
+ if (fstat (fd,&buf) < 0)
+ return false ;
+
+ return ((long) buf.st_size) ;
+}
+
+
+
+const char *boolToString (bool val)
+{
+ return val ? "true" : "false" ;
+}
+
+void addPointerFreedOnExit (char *pointerToFree)
+{
+ static int totalPointers = 0 ;
+ static int nextPointer = 0 ;
+
+ if (nextPointer == 0 || nextPointer == totalPointers - 1)
+ {
+ int i;
+
+ totalPointers += 16 ;
+ if (PointersFreedOnExit == NULL)
+ PointersFreedOnExit = xmalloc (sizeof(char *) * totalPointers) ;
+ else
+ PointersFreedOnExit =
+ xrealloc (PointersFreedOnExit, sizeof(char *) * totalPointers) ;
+
+ for (i = nextPointer; i < totalPointers; i++)
+ PointersFreedOnExit [i] = NULL;
+ }
+ PointersFreedOnExit [nextPointer++] = pointerToFree ;
+}
+
+/* malloc a buffer and build the filename in it. */
+char *buildFilename (const char *directory, const char *fname)
+{
+ int len = 0 ;
+ char *p = NULL ;
+
+ if (fname == NULL)
+ return NULL ;
+
+ if (directory == NULL)
+ directory = "." ;
+
+ len = strlen (directory) + strlen (fname) + 2 + 1 ;
+
+ if (len < pathMax(directory) - 2)
+ {
+ p = xmalloc (len) ;
+ p [0] = '\0' ;
+ if (fname [0] != '/')
+ {
+ strlcat (p,directory,len) ;
+ if (p [strlen(p) - 1] != '/')
+ strlcat (p,"/",len) ;
+ }
+ strlcat (p,fname,len) ;
+ }
+
+ return p ;
+}
+
+
+
+/* borrows heavily from the shrinkfile program by chongo. */
+bool shrinkfile (FILE *fp, long size, char *name, const char *mode)
+{
+ long currlen = ftello (fp) ;
+ char *tmpname ;
+ char buffer [BUFSIZ] ;
+ FILE *tmpFp ;
+ int c ;
+ int i ;
+ int fd ;
+
+ if (currlen <= size)
+ {
+ d_printf (1,"No need to shrink file (%s %ld vs %ld\n",
+ name,size,currlen) ;
+ return true ;
+ }
+
+ /* create a temp file. */
+ tmpname = concat (name,".XXXXXX",(char *)0) ;
+ fd = mkstemp (tmpname) ;
+
+ if (fd < 0)
+ {
+ syswarn ("ME error creating temp shrink file for %s", name) ;
+ free (tmpname) ;
+ return false ;
+ }
+
+ if ((tmpFp = fdopen (fd,"w")) == NULL)
+ {
+ syswarn ("ME error opening temp shrink file %s", tmpname) ;
+ free (tmpname) ;
+ return false ;
+ }
+
+ if (fseeko (fp,currlen - size,SEEK_SET) != 0)
+ {
+ fclose (tmpFp) ;
+ warn ("ME error seeking to point %ld in %s", currlen - size, name) ;
+ free (tmpname) ;
+ return false ;
+ }
+
+ /* find the end of the next line in the shrinking file. */
+ while ((c = fgetc (fp)) != '\n')
+ if (c == EOF)
+ {
+ warn ("ME no newline in shrinking file %s", name) ;
+ fclose (tmpFp) ;
+ fseeko (fp,currlen,SEEK_SET) ;
+ free (tmpname) ;
+ return false ;
+ }
+
+ /* copy the tail of the shrinking file to the temp file. */
+ while ((i = fread (buffer,1,sizeof (buffer),fp)) > 0)
+ {
+ if (fwrite (buffer,1,i,tmpFp) != (size_t) i)
+ {
+ fclose (tmpFp) ;
+ syswarn ("ME fwrite failed to temp shrink file %s", tmpname) ;
+ fseeko (fp,currlen, SEEK_SET) ;
+ free (tmpname) ;
+ return false ;
+ }
+ }
+
+ if (i < 0)
+ logAndExit (1,"ME fread failed on file %s: %s",name, strerror (errno)) ;
+
+ fclose (tmpFp) ;
+
+ if (unlink (name) != 0)
+ logAndExit (1,"ME oserr unlink %s: %s",name, strerror (errno)) ;
+
+ /* we're in the same directory so this is ok. */
+ if (rename (tmpname,name) != 0)
+ logAndExit (1,"ME oserr rename %s, %s: %s", tmpname, name,
+ strerror (errno)) ;
+
+ if (freopen (name,mode,fp) != fp)
+ logAndExit (1,"ME freopen on shrink file failed %s: %s", name,
+ strerror (errno)) ;
+
+ fseeko (fp,0,SEEK_END) ;
+ size = ftello (fp) ;
+
+ notice ("ME file %s shrunk from %ld to %ld", name, currlen, size) ;
+
+ free (tmpname) ;
+
+ return true ;
+}
+
+
+
+long pathMax (const char *pathname UNUSED)
+{
+ static long rval = 0 ;
+
+ if (rval > 0)
+ return rval ;
+
+#if defined (PATH_MAX)
+
+ rval = PATH_MAX ;
+
+#elif defined (_POSIX_PATH_MAX)
+
+ rval = _POSIX_PATH_MAX ;
+
+#elif defined (DO_HAVE_PATHCONF) && defined (_PC_PATH_MAX)
+
+ if (pathname == NULL)
+ pathname = "/tmp" ;
+
+ rval = pathconf (pathname,_PC_PATH_MAX) ;
+
+#else
+
+ rval = 255 ;
+ if (!logged)
+ {
+ syslog (LOG_ERR,NO_PATH_MAX,rval) ;
+ logged = true ;
+ }
+
+#endif
+
+ return rval ;
+}
--- /dev/null
+/* $Id: misc.h 6648 2004-01-25 20:07:11Z rra $
+**
+** Miscellaneous utility functions for innfeed.
+**
+** Written by James Brister <brister@vix.com>
+*/
+
+#if ! defined ( misc_h__ )
+#define misc_h__
+
+#include "config.h"
+
+#include <stdarg.h>
+#include <sys/types.h>
+
+/* These typedefs are all here because C is too stupid to let me multiply
+ define typedefs to the same things (as C++ will). Hence I can't redeclare
+ the typedefs to get around recursive header file includes (like host.h and
+ connection.h would need if they contained their own typedefs). */
+
+typedef struct article_s *Article ; /* see article.h */
+typedef struct buffer_s *Buffer ; /* see buffer.h */
+typedef struct commander_s *Commander ; /* see commander.h */
+typedef struct config_s *Config ; /* see config.h */
+typedef struct connection_s *Connection ; /* see connection.h */
+typedef struct endpoint_s *EndPoint ; /* see endpoint.h */
+typedef struct host_s *Host ; /* see host.h */
+typedef struct innlistener_s *InnListener ; /* see innlistener.h */
+typedef struct tape_s *Tape ; /* see tape.h */
+
+typedef int TimeoutId ; /* see endpoint.h */
+typedef enum { /* see endpoint.h */
+ IoDone, IoIncomplete, IoFailed, IoEOF, IoProgress
+} IoStatus ;
+
+typedef void (*EndpRWCB) (EndPoint e, /* see endpoint.h */
+ IoStatus i, Buffer *b, void *d) ;
+typedef void (*EndpTCB) (TimeoutId tid, void *d) ; /* see endpoint.h */
+typedef void (*EndpWorkCbk) (EndPoint ep, void *data) ;
+
+
+/* debugging information */
+extern unsigned int loggingLevel ; /* if 0 then d_printf is a no-op */
+
+/* the current count of file desccriptors */
+extern unsigned int openfds ;
+
+/* if level <= loggingLevel then print */
+void d_printf (unsigned int level, const char *fmt, ...) __attribute__ ((__format__ (printf, 2, 3)));
+
+/* for the gethostbyname() error code */
+const char *host_err_str (void) ;
+
+/* parse a reponse line into it's code and body. *rest will end up pointing
+ into the middle of p */
+bool getNntpResponse (char *p, int *code, char **rest) ;
+
+/* parse out the first field of a nntp response code as a message-id. Caller
+ must free the return value when done. */
+char *getMsgId (const char *p) ;
+
+/* pick out the next non-blank string inside PTR. TAIL is set to point at
+ the first blank (or NULL) after the string. Returns a pointer into PTR */
+char *findNonBlankString (char *ptr, char **tail) ;
+
+/* if fp is not NULL then print to it, otherwise syslog at the level. */
+void logOrPrint (int level, FILE *fp, const char *fmt, ...)
+ __attribute__ ((__format__ (printf, 3, 4)));
+
+/* Error handling functions for use with warn and die. */
+void error_log_stderr_date(int len, const char *fmt, va_list args, int err);
+
+/* Do cleanup and then abort, for use with die. */
+int dump_core(void);
+
+/* Alternate die that doesn't invoke an error handler. */
+void logAndExit (int exitVal, const char *fmt, ...)
+ __attribute__ ((__format__ (printf, 2, 3)));
+
+/* return true of the file exists and is a regular file */
+bool fileExistsP (const char *filename) ;
+
+/* return true if file exists and is a directory */
+bool isDirectory (const char *filename) ;
+
+char *mystrtok (char *string, const char *sep) ;
+
+/* remove trailing whitespace */
+void trim_ws (char *string) ;
+
+/* locks the peer and returns true or returns false */
+bool lockPeer (const char *peerName) ;
+
+/* return true if the end of string matches tail. */
+bool endsIn (const char *string, const char *tail) ;
+
+/* scribble over then free up the null-terminated string */
+void freeCharP (char *charp) ;
+
+/* append the contents of src to dest. src is removed if append if
+ successful */
+bool appendFile (const char *dest, const char *src) ;
+
+/* return the length of the file reference by the given file descriptor */
+long fileLength (int fd) ;
+
+bool lockFile (const char *fileName) ;
+void unlockFile (const char *lockfile) ;
+
+
+/* return true if file1 is older than file2 */
+bool isOlder (const char *file1, const char *file2) ;
+
+/* converts val into a printable string */
+const char *boolToString (bool val) ;
+
+/* memory leak checker helper. */
+void addPointerFreedOnExit (char *pointerToFree) ;
+
+/* splice direcotory and fname together and return free'able string */
+char *buildFilename (const char *directory, const char *fname) ;
+
+/* string the file opened by FP to the give SIZE. The NEWNAME is the name
+ of the file to have after truncation. FP will be reopened for writing on
+ the new file and will be positioned at the end */
+bool shrinkfile (FILE *fp, long size, char *name, const char *mode) ;
+
+/* max length of pathname */
+long pathMax (const char *pathname) ;
+
+#define ASSERT(x) do{if (!(x))die("assertion -- %s -- failed in file %s line %d",#x,__FILE__,__LINE__);}while(0)
+
+#define INDENT_INCR 5
+#define INDENT_BUFFER_SIZE 80
+#if ! defined (MIN)
+#define MIN(A,B) ((A) < (B) ? (A) : (B))
+#endif
+
+#endif /* misc_h__ */
--- /dev/null
+#! /usr/bin/perl
+# fixscript will replace this line with require innshellvars.pl
+
+# Author: James Brister <brister@vix.com> -- berkeley-unix --
+# Start Date: Thu May 16 10:32:02 1996 +0200
+# Project: INN -- innfeed
+# File: procbatch.pl
+# RCSId: $Id: procbatch.in 6648 2004-01-25 20:07:11Z rra $
+#
+# Description: Take a file of the form generated by innd in the out.going
+# directory ("Wnm*") and separate it into tape files for inn.
+#
+# Thanks to Clayton O'Neill <coneill@premier.net> for serious speed
+# improvments.
+#
+
+#
+# Hmm, perhaps we should try to read "backlog-directory"
+# from innfeed.conf. Oh well.
+#
+$tapeDir = $inn'pathspool . "/innfeed"; #'
+$destDir = $inn'spooltemp ; #'
+$spoolArts = $inn'patharticles ; #'
+$outGoing = $inn'pathoutgoing; #'
+
+##
+## Everything below here should probably be left alone.
+##
+
+$0 =~ s!.*/!! ;
+
+require 'getopts.pl' ;
+
+$usage = "$0 [ -q ][ -v ][ -u ][ -e host ][ -d dir ][ -c [ -s dir ]][ -m [-t dir ]] inn-batchfile\n
+ -e host to process on entries for only that host
+ -d dir to put the output file(s) in that directory ($destDir)
+ -c to check pathnames of articles before storing them
+ -s dir to specify where the news articles are
+ ($spoolArts)
+ -m to have $0 move the new files to the backlog directory.
+ -t dir to specify the backlog directory ($tapeDir)
+ -u to unlink the input files when finished
+ -v for verbosity
+ -q quiet mode; only display error messages. Good for cron jobs.
+
+$0 will take an inn funnel file (normally a file in
+$outGoing), or an innfeed ``dropped'' file,
+which is presumed to be of the format:
+
+ pathname message-id peer1 peer2 peer3 ...
+
+and will break it up into files peer1.tmp peer2.tmp peer3.tmp... Each of
+these files wil be of the format:
+
+ pathname message-id
+
+that is the same as innfeed's backlog file format. Simply rename these files
+to peer1 peer2 peer3 in a running innfeed's backlog directory and they will be
+picked up automatically and processed by innfeed. Use the '-m' flag and
+they'll be moved automatically.
+" ;
+
+$opt_u = $opt_h = ""; # shut up, perl -w
+&Getopts ("he:t:s:d:cvumq") || die $usage ;
+
+die $usage if ( $opt_h ) ;
+die "Cannot specify both -q and -v\n\n" . $usage if ($opt_q && $opt_v);
+
+$spoolArts = $opt_s if $opt_s ;
+$destDir = $opt_d if $opt_d ;
+$tapeDir = $opt_t if $opt_t ;
+$inputFile = shift ;
+
+die $usage if !$inputFile ;
+unless (-f $inputFile) {
+ exit if $opt_q;
+ die "No such file: $inputFile\n\n" . $usage;
+}
+die "No such directory: $spoolArts\n\n" . $usage if ( ! -d $spoolArts && $opt_c ) ;
+die "No such directory: $destDir\n\n" . $usage if ( ! -d $destDir ) ;
+die "No such directory: $tapeDir\n\n" . $usage if ( ! -d $tapeDir && $opt_m ) ;
+
+print "Using $inputFile\n" if $opt_v ;
+open (INPUT,"<$inputFile") || die "$0: open ($inputFile): $!\n" ;
+
+while (<INPUT>) {
+ chop ;
+ @F = split ;
+
+ # Check the format of the line vigorously
+ next unless (m!^\S+/\d+ <.+@.+> \S+! || m!^@[0-9A-F]+@ <.+@.+> \S+!) ;
+
+ if ( $opt_c ) {
+ if ( ! -f "$spoolArts/$F[0]" ) {
+ $missing++ ;
+ print "Dropping file: $spoolArts/$F[0]\n" if $opt_v ;
+ next ;
+ }
+ }
+
+ for ($i = 2 ; $i <= $#F ; $i++) {
+ $host = $F[$i] ;
+ next if ($opt_e && $opt_e ne $host) ;
+
+ # Keep out host names with any funny characters (from
+ # corrupted files)
+ if ($host !~ /^[-\._0-9A-Za-z]+$/) {
+ warn "$0: bad site name ignored: \"$host\"\n";
+ next;
+ }
+
+ if ($hosts{$host}) {
+ print {$hosts{$host}} "$F[0] $F[1]\n";
+ } else {
+ $outputFile = "$destDir/$host.tmp" ;
+ print "Starting $host\n" if ($opt_v);
+ $hosts{$host}=$host;
+ open ($hosts{$host},">>$outputFile") ||
+ die "open >>$outputFile: $!\n" ;
+ print {$hosts{$host}} "$F[0] $F[1]\n";
+ }
+ }
+}
+close (INPUT) ;
+
+foreach $host (keys %hosts) {
+ close($hosts{$host});
+ $outputFile = "$destDir/$host.tmp" ;
+ $tmpTape = "$tapeDir/$host.tmp" ;
+ $tapeFile = "$tapeDir/$host" ;
+ if ( $opt_m ) {
+ if ($outputFile ne $tmpTape) {
+ $cmd = "mv $outputFile $tmpTape" ;
+ system ($cmd) ;
+ die "$0: $cmd: failed\n" unless ($? == 0) ;
+ }
+
+ $cmd = "cat $tmpTape |sort -u >> $tapeFile && rm -f $tmpTape" ;
+ system ($cmd) ;
+ die "$0: $cmd: failed\n" unless ($? == 0) ;
+ }
+}
+
+unlink($inputFile) if ($opt_u);
+
+print "$missing articles dropped\n" if ( $opt_v && $missing > 0 ) ;
--- /dev/null
+/* $Id: startinnfeed.c 7065 2004-12-19 21:49:18Z rra $
+**
+** Raise system limits and exec innfeed.
+**
+** This is a setuid root wrapper around innfeed to increase system limits
+** (file descriptor limits and stack and data sizes). In order to prevent
+** abuse, it uses roughly the same security model as inndstart; only the
+** news user can run this program, and it attempts to drop privileges when
+** doing operations that don't require it.
+*/
+
+#include "config.h"
+#include "clibrary.h"
+#include <syslog.h>
+#include <errno.h>
+#include <grp.h>
+#include <pwd.h>
+
+/* FreeBSD 3.4 RELEASE needs <sys/time.h> before <sys/resource.h>. */
+#if HAVE_SETRLIMIT
+# if HAVE_SYS_TIME_H
+# include <sys/time.h>
+# endif
+# include <sys/resource.h>
+#endif
+
+#include "inn/innconf.h"
+#include "inn/messages.h"
+#include "libinn.h"
+
+/* Options for debugging malloc. */
+#ifdef USE_DMALLOC
+# define DMALLOC_OPTIONS \
+ "DMALLOC_OPTIONS=debug=0x4e405c3,inter=100,log=innfeed-logfile"
+#endif
+
+
+int
+main(int argc, char *argv[])
+{
+ struct passwd * pwd;
+ struct group * grp;
+ uid_t news_uid;
+ gid_t news_gid;
+ char ** innfeed_argv;
+ char * spawn_path;
+ int i;
+
+#if HAVE_SETRLIMIT
+ struct rlimit rl;
+#endif
+
+ openlog("innfeed", L_OPENLOG_FLAGS | LOG_PID, LOG_INN_PROG);
+ message_handlers_warn(1, message_log_syslog_warning);
+ message_handlers_die(1, message_log_syslog_err);
+
+ /* Convert NEWSUSER and NEWSGRP to a UID and GID. getpwnam() and
+ getgrnam() don't set errno normally, so don't print strerror() on
+ failure; it probably contains garbage.*/
+ pwd = getpwnam(NEWSUSER);
+ if (!pwd) die("can't getpwnam(%s)", NEWSUSER);
+ news_uid = pwd->pw_uid;
+ grp = getgrnam(NEWSGRP);
+ if (!grp) die("can't getgrnam(%s)", NEWSGRP);
+ news_gid = grp->gr_gid;
+
+ /* Exit if run by another user. */
+ if (getuid() != news_uid)
+ die("ran by UID %lu, who isn't %s (%lu)", (unsigned long) getuid(),
+ NEWSUSER, (unsigned long) news_uid);
+
+ /* Drop privileges to read inn.conf. */
+ if (seteuid(news_uid) < 0)
+ sysdie("can't seteuid(%lu)", (unsigned long) news_uid);
+ if (!innconf_read(NULL))
+ exit(1);
+
+ /* Regain privileges to increase system limits. */
+ if (seteuid(0) < 0) sysdie("can't seteuid(0)");
+ if (innconf->rlimitnofile >= 0)
+ if (setfdlimit(innconf->rlimitnofile) < 0)
+ syswarn("can't set file descriptor limit to %ld",
+ innconf->rlimitnofile);
+
+ /* These calls will fail on some systems, such as HP-UX 11.00. On those
+ systems, we just blindly assume that the stack and data limits are
+ high enough (they generally are). */
+#if HAVE_SETRLIMIT
+ rl.rlim_cur = RLIM_INFINITY;
+ rl.rlim_max = RLIM_INFINITY;
+# ifdef RLIMIT_DATA
+ setrlimit(RLIMIT_DATA, &rl);
+# endif
+#endif /* HAVE_SETRLIMIT */
+
+ /* Permanently drop privileges. */
+ if (setuid(news_uid) < 0 || getuid() != news_uid)
+ sysdie("can't setuid to %lu", (unsigned long) news_uid);
+
+ /* Check for imapfeed -- continue to use "innfeed" in variable
+ names for historical reasons regardless */
+ if ((argc > 1) && (strcmp(argv[1],"imapfeed") == 0))
+ {
+ argc--;
+ argv++;
+ spawn_path = concat(innconf->pathbin, "/imapfeed", (char *) 0);
+ }
+ else
+ spawn_path = concat(innconf->pathbin, "/innfeed", (char *) 0);
+
+
+ /* Build the argument vector for innfeed. */
+ innfeed_argv = xmalloc((argc + 1) * sizeof(char *));
+ innfeed_argv[0] = spawn_path;
+ for (i = 1; i <= argc; i++)
+ innfeed_argv[i] = argv[i];
+ innfeed_argv[argc] = NULL;
+
+ /* Set debugging malloc options. */
+#ifdef USE_DMALLOC
+ putenv(DMALLOC_OPTIONS);
+#endif
+
+ /* Exec innfeed. */
+ execv(innfeed_argv[0], innfeed_argv);
+ sysdie("can't exec %s", innfeed_argv[0]);
+
+ /* Not reached. */
+ return 1;
+}
--- /dev/null
+/* $Id: tape.c 6716 2004-05-16 20:26:56Z rra $
+**
+** The implementation of the innfeed Tape class.
+**
+** Written by James Brister <brister@vix.com>
+**
+** The implementation of the Tape class. Tapes are read-only or write-only
+** files that are accessed sequentially. Their basic unit of i/o is an
+** Article. Tapes work out of a single directory and manage all file names
+** themselves.
+**
+** Tapes will checkpoint themselves periodically so that when innfeed exits
+** or crashes things can restart close to where they were last. The period
+** checkpointing is handled entirely by the Tape class, but the checkpoint
+** period needs to be set by some external user before the first tape is
+** created.
+*/
+
+#include "innfeed.h"
+#include "config.h"
+#include "clibrary.h"
+
+#include <assert.h>
+#include <ctype.h>
+#include <errno.h>
+#include <sys/param.h>
+#include <sys/stat.h>
+#include <syslog.h>
+
+#if defined (HAVE_DIRENT_H)
+#include <dirent.h>
+typedef struct dirent DIRENTRY ;
+#else
+#include <sys/dir.h>
+typedef struct direct DIRENTRY ;
+#endif
+
+#ifdef TIME_WITH_SYS_TIME
+# include <sys/time.h>
+# include <time.h>
+#else
+# ifdef HAVE_SYS_TIME_H
+# include <sys/time.h>
+# else
+# include <time.h>
+# endif
+#endif
+
+#include "inn/innconf.h"
+#include "inn/messages.h"
+#include "libinn.h"
+
+#include "article.h"
+#include "configfile.h"
+#include "endpoint.h"
+#include "tape.h"
+
+extern char *dflTapeDir;
+extern bool genHtml ;
+
+#if 0
+/* a structure for temporary storage of articles. */
+typedef struct q_e_s
+{
+ Article article ;
+ struct q_e_s *next ;
+} *QueueElem ;
+#endif
+
+/* The Tape class type. */
+struct tape_s
+{
+ /* the pathname of the file the administrator can drop in by hand. */
+ char *handFilename ;
+
+ /* the pathname of the file the Tape will read from */
+ char *inputFilename ;
+
+ /* the pathname of the file the Tape will write to. */
+ char *outputFilename ;
+
+ /* the pathname of the file used in locking */
+ char *lockFilename ;
+
+ /* the peer we're doing this for. */
+ char *peerName ;
+
+ FILE *inFp ; /* input FILE */
+ FILE *outFp ; /* output FILE */
+
+ time_t lastRotated ; /* time files last got switched */
+ bool checkNew ; /* set bool when we need to check for
+ hand-crafted file. */
+
+#if 0
+ /* the tape holds a small output queue in memory to avoid thrashing. */
+ QueueElem head ;
+ QueueElem tail ;
+ unsigned int qLength ; /* amount on the queue */
+#endif
+
+ long outputSize ; /* the current size of the output file. */
+ long lossage ;
+
+ /* the number of bytes we try to keep the output under. We actually
+ wait for the outputSize to get 10% greater than this amount before
+ shrinking the file down again. A value of zero means no limit. */
+ long outputLowLimit ;
+ long outputHighLimit ;
+ double backlogFactor ;
+
+ bool scribbled ; /* have we scribbled a checkpoint value in
+ the file. */
+ long tellpos ; /* for input file checkpointing. */
+ bool changed ; /* true if tape was read since last
+ checkpoint or start. */
+
+ /* true if articles that are output are NOT later input. */
+ bool noRotate ;
+
+ /* true if no articles should ever be spooled */
+ bool noBacklog ;
+};
+
+
+static void checkpointTape (Tape tape) ;
+static void removeTapeGlobally (Tape tape) ;
+static void addTapeGlobally (Tape tape) ;
+static void prepareFiles (Tape tape) ;
+static void tapeCkNewFileCbk (TimeoutId id, void *d) ;
+static void tapeCheckpointCallback (TimeoutId id, void *d) ;
+#if 0
+static void flushTape (Tape tape) ;
+#endif
+static void tapesSetCheckNew (void) ;
+static void initTape (Tape nt) ;
+static void tapeCleanup (void) ;
+
+
+
+/* pathname of directory we store tape files in. */
+static char *tapeDirectory ;
+
+/* the callback ID of the checkpoint timer callback. */
+static TimeoutId checkPtId ;
+static TimeoutId ckNewFileId ;
+
+/* number of seconds between tape checkpoints. */
+static unsigned int tapeCkPtPeriod ;
+static unsigned int tapeCkNewFilePeriod ;
+
+static time_t rotatePeriod = TAPE_ROTATE_PERIOD ;
+
+/* global list of tapes so we can checkpoint them periodically */
+static Tape *activeTapes ;
+
+/* Size of the activeTapes array */
+static size_t activeTapeSize ;
+
+/* index of last element in activeTapes that's being used. */
+static size_t activeTapeIdx ;
+
+#if 0
+/* default limit of the size of output tapes. */
+static long defaultSizeLimit ;
+#endif
+
+unsigned int tapeHighwater ;
+
+bool debugShrinking = false ;
+
+extern bool talkToSelf ; /* main.c */
+
+
+
+
+/* callback when config file is loaded */
+int tapeConfigLoadCbk (void *data)
+{
+ int rval = 1 ;
+ long iv ;
+ int bv ;
+ FILE *fp = (FILE *) data ;
+ char *dir, *p ;
+
+ if (getString (topScope,"backlog-directory",&p,NO_INHERIT))
+ {
+ dir = buildFilename(innconf->pathspool, p);
+ free(p);
+ if (tapeDirectory != NULL && strcmp (tapeDirectory,dir) != 0)
+ {
+ warn ("ME config: cannot change backlog-directory of a running"
+ " process") ;
+ free (dir) ;
+ dir = xstrdup (tapeDirectory) ;
+ }
+
+ if (!isDirectory (dir) && isDirectory (dflTapeDir))
+ {
+ logOrPrint (LOG_ERR,fp,
+ "ME config: definition of backlog-directory (%s) is a"
+ " non-existant directory. Using %s",
+ dir,dflTapeDir) ;
+ free (dir) ;
+ dir = xstrdup (dflTapeDir) ;
+ }
+ else if (!isDirectory (dir))
+ logAndExit (1,"ME config: no usable value for backlog-directory") ;
+ }
+ else if (!isDirectory (dflTapeDir))
+ {
+ logAndExit (1,"ME config: no usable value for backlog-directory") ;
+ return -1; /* not reached */
+ }
+ else
+ dir = xstrdup (dflTapeDir) ;
+
+ if (tapeDirectory != NULL)
+ free (tapeDirectory) ;
+ tapeDirectory = dir ;
+
+
+
+ if (getInteger (topScope,"backlog-highwater",&iv,NO_INHERIT))
+ {
+ if (iv < 0)
+ {
+ rval = 0 ;
+ logOrPrint (LOG_ERR,fp,
+ "ME config: value of %s (%ld) in %s cannot be less"
+ " than 0. Using %ld","backlog-highwater",
+ iv,"global scope",(long)TAPE_HIGHWATER);
+ iv = TAPE_HIGHWATER ;
+ }
+ }
+ else
+ iv = TAPE_HIGHWATER ;
+ tapeHighwater = (unsigned int) iv ;
+
+
+
+ if (getInteger (topScope,"backlog-rotate-period",&iv,NO_INHERIT))
+ {
+ if (iv < 0)
+ {
+ rval = 0 ;
+ logOrPrint (LOG_ERR,fp,
+ "ME config: value of %s (%ld) in %s cannot be less"
+ " than 0. Using %ld",
+ "backlog-rotate-period",
+ iv,"global scope",(long)TAPE_ROTATE_PERIOD);
+ iv = TAPE_ROTATE_PERIOD ;
+ }
+ }
+ else
+ iv = TAPE_ROTATE_PERIOD ;
+ rotatePeriod = (unsigned int) iv ;
+
+
+ if (getInteger (topScope,"backlog-ckpt-period",&iv,NO_INHERIT))
+ {
+ if (iv < 0)
+ {
+ rval = 0 ;
+ logOrPrint (LOG_ERR,fp,
+ "ME config: value of %s (%ld) in %s cannot be less"
+ " than 0. Using %ld",
+ "backlog-ckpt-period",iv,
+ "global scope",(long)TAPE_CHECKPOINT_PERIOD);
+ iv = TAPE_CHECKPOINT_PERIOD ;
+ }
+ }
+ else
+ iv = TAPE_CHECKPOINT_PERIOD ;
+ tapeCkPtPeriod = (unsigned int) iv ;
+
+
+ if (getInteger (topScope,"backlog-newfile-period",&iv,NO_INHERIT))
+ {
+ if (iv < 0)
+ {
+ rval = 0 ;
+ logOrPrint (LOG_ERR,fp,
+ "ME config: value of %s (%ld) in %s cannot be less"
+ " than 0. Using %ld",
+ "backlog-newfile-period",
+ iv,"global scope",(long)TAPE_NEWFILE_PERIOD);
+ iv = TAPE_NEWFILE_PERIOD ;
+ }
+ }
+ else
+ iv = TAPE_NEWFILE_PERIOD ;
+ tapeCkNewFilePeriod = (unsigned int) iv ;
+
+
+ if (getBool (topScope,"debug-shrinking",&bv,NO_INHERIT))
+ debugShrinking = (bv ? true : false) ;
+
+ return rval ;
+}
+
+
+/* Create a new Tape object. There are three potential files involved in
+ I/O. 'peerName' is what the admin may have dropped in by
+ hand. 'peerName.input' is the file that was being used as input the last
+ time things were run. 'peerName.output' is the file that was being used
+ as output. The file 'peerName' is appended to 'peerName.input' (or
+ renamed if 'peerName.input' doesn't exist). Then 'peerName.output' is
+ appeneded (or renamed) to 'peerName.input' */
+
+static bool inited = false ;
+Tape newTape (const char *peerName, bool dontRotate)
+{
+ Tape nt = xmalloc (sizeof(struct tape_s)) ;
+ size_t pLen = strlen (peerName) ;
+ size_t dLen = strlen (tapeDirectory) ;
+
+ if (!inited)
+ {
+ inited = true ;
+ atexit (tapeCleanup) ;
+ }
+
+ ASSERT (nt != NULL) ;
+
+ if (endsIn (peerName,INPUT_TAIL))
+ die ("Sorry, can't have a peer name ending in \"%s\"",INPUT_TAIL) ;
+
+ if (endsIn (peerName,OUTPUT_TAIL))
+ die ("Sorry, can't have a peer name ending in \"%s\"",OUTPUT_TAIL) ;
+
+ if (endsIn (peerName,LOCK_TAIL))
+ die ("Sorry, can't have a peer name ending in \"%s\"",LOCK_TAIL) ;
+
+ nt->peerName = xstrdup (peerName) ;
+
+ nt->handFilename = xmalloc (pLen + dLen + 2) ;
+ sprintf (nt->handFilename,"%s/%s",tapeDirectory,peerName) ;
+
+ nt->lockFilename = xmalloc (pLen + dLen + strlen(LOCK_TAIL) + 2) ;
+ sprintf (nt->lockFilename,"%s/%s%s",tapeDirectory,peerName,LOCK_TAIL) ;
+
+ nt->inputFilename = xmalloc (pLen + dLen + strlen(INPUT_TAIL) + 2) ;
+ sprintf (nt->inputFilename,"%s/%s%s",tapeDirectory,peerName,INPUT_TAIL) ;
+
+ nt->outputFilename = xmalloc (pLen + dLen + strlen(OUTPUT_TAIL) + 2) ;
+ sprintf (nt->outputFilename,"%s/%s%s",tapeDirectory,peerName,OUTPUT_TAIL) ;
+
+ if ( !lockFile (nt->lockFilename) )
+ {
+ warn ("ME lock failed for host: %s", nt->lockFilename) ;
+
+ free (nt->handFilename) ;
+ free (nt->lockFilename) ;
+ free (nt->inputFilename) ;
+ free (nt->outputFilename) ;
+ free (nt) ;
+
+ return NULL ;
+ }
+
+ nt->noRotate = false ; /* for first time prepare */
+ initTape (nt) ;
+ nt->noRotate = dontRotate ;
+
+ addTapeGlobally (nt) ;
+
+ if (checkPtId == 0 && tapeCkPtPeriod > 0) /* only done once. */
+ checkPtId = prepareSleep (tapeCheckpointCallback,tapeCkPtPeriod,NULL);
+
+ if (ckNewFileId == 0 && tapeCkNewFilePeriod > 0)
+ ckNewFileId = prepareSleep (tapeCkNewFileCbk,tapeCkNewFilePeriod,NULL) ;
+
+ return nt ;
+}
+
+static void initTape (Tape nt)
+{
+ value *peerVal = findPeer (nt->peerName) ;
+ scope *s = (peerVal == NULL ? NULL : peerVal->v.scope_val) ;
+
+ nt->inFp = NULL ;
+ nt->outFp = NULL ;
+
+ nt->lastRotated = 0 ;
+ nt->checkNew = false ;
+
+#if 0
+ nt->head = NULL ;
+ nt->tail = NULL ;
+ nt->qLength = 0 ;
+#endif
+
+ nt->scribbled = false ;
+ nt->tellpos = 0 ;
+
+ nt->changed = false ;
+
+ nt->outputSize = 0 ;
+ nt->lossage = 0 ;
+
+ nt->noBacklog = false ;
+ nt->backlogFactor = 0.0 ;
+ nt->outputLowLimit = 0 ;
+ nt->outputHighLimit = 0 ;
+
+ if (!talkToSelf)
+ {
+ int bval ;
+
+ if (getBool (s, "no-backlog", &bval, INHERIT))
+ nt->noBacklog = (bval ? true : false);
+ else
+ nt->noBacklog = TAPE_DISABLE;
+
+ if (getInteger (s,"backlog-limit",&nt->outputLowLimit,INHERIT))
+ {
+ if (!getReal (s,"backlog-factor",&nt->backlogFactor,INHERIT))
+ {
+ if (!getInteger (s,"backlog-limit-highwater",
+ &nt->outputHighLimit,INHERIT))
+ {
+ warn ("%s no backlog-factor or backlog-high-limit",
+ nt->peerName) ;
+ nt->outputLowLimit = 0 ;
+ nt->outputHighLimit = 0 ;
+ nt->backlogFactor = 0.0 ;
+ }
+ }
+ else
+ nt->outputHighLimit = (long)(nt->outputLowLimit * nt->backlogFactor);
+ }
+ else
+ warn ("ME config: no definition for required key backlog-limit") ;
+ }
+
+ d_printf (1, "%s spooling: %s\n", nt->peerName,
+ nt->noBacklog ? "disabled" : "enabled");
+
+ d_printf (1,"%s tape backlog limit: [%ld %ld]\n",nt->peerName,
+ nt->outputLowLimit,
+ nt->outputHighLimit) ;
+
+ prepareFiles (nt) ;
+}
+
+
+void gFlushTapes (void)
+{
+ unsigned int i ;
+
+ notice ("ME flushing tapes") ;
+ for (i = 0 ; i < activeTapeIdx ; i++)
+ tapeFlush (activeTapes [i]) ;
+}
+
+
+
+/* close the input and output tapes and reinitialize everything in the
+ tape. */
+void tapeFlush (Tape tape)
+{
+ if (tape->inFp != NULL)
+ {
+ checkpointTape (tape) ;
+ fclose (tape->inFp) ;
+ }
+
+ if (tape->outFp != NULL)
+ fclose (tape->outFp) ;
+
+ initTape (tape) ;
+}
+
+
+
+void gPrintTapeInfo (FILE *fp, unsigned int indentAmt)
+{
+ char indent [INDENT_BUFFER_SIZE] ;
+ unsigned int i ;
+
+ for (i = 0 ; i < MIN(INDENT_BUFFER_SIZE - 1,indentAmt) ; i++)
+ indent [i] = ' ' ;
+ indent [i] = '\0' ;
+
+ fprintf (fp,"%sGlobal Tape List : (count %lu) {\n",
+ indent,(unsigned long) activeTapeIdx) ;
+
+ for (i = 0 ; i < activeTapeIdx ; i++)
+ printTapeInfo (activeTapes [i],fp,indentAmt + INDENT_INCR) ;
+ fprintf (fp,"%s}\n",indent) ;
+}
+
+
+void tapeLogGlobalStatus (FILE *fp)
+{
+ fprintf (fp,"%sBacklog file global values:%s\n",
+ genHtml ? "<B>" : "", genHtml ? "</B>" : "") ;
+ fprintf (fp," directory: %s\n",tapeDirectory) ;
+ fprintf (fp," rotate period: %-3ld seconds\n",(long) rotatePeriod) ;
+ fprintf (fp,"checkpoint period: %-3ld seconds\n",(long) tapeCkPtPeriod) ;
+ fprintf (fp," newfile period: %-3ld seconds\n",(long) tapeCkNewFilePeriod);
+ fprintf (fp,"\n") ;
+}
+
+
+void tapeLogStatus (Tape tape, FILE *fp)
+{
+ if (tape == NULL)
+ fprintf (fp,"(no tape)\n") ;
+ else if (tape->noBacklog)
+ fprintf (fp," spooling: DISABLED\n");
+ else if (tape->outputLowLimit == 0)
+ fprintf (fp," spooling: UNLIMITED\n");
+ else
+ {
+ fprintf (fp," backlog low limit: %ld\n",
+ tape->outputLowLimit) ;
+ fprintf (fp," backlog upper limit: %ld",
+ tape->outputHighLimit) ;
+ if (tape->backlogFactor > 0.0)
+ fprintf (fp," (factor %1.2f)",tape->backlogFactor) ;
+ fputc ('\n',fp) ;
+ fprintf (fp," backlog shrinkage: ") ;
+ fprintf (fp,"%ld bytes (from current file)\n",tape->lossage) ;
+ }
+}
+
+void printTapeInfo (Tape tape, FILE *fp, unsigned int indentAmt)
+{
+ char indent [INDENT_BUFFER_SIZE] ;
+ unsigned int i ;
+#if 0
+ QueueElem qe ;
+#endif
+
+ for (i = 0 ; i < MIN(INDENT_BUFFER_SIZE - 1,indentAmt) ; i++)
+ indent [i] = ' ' ;
+ indent [i] = '\0' ;
+
+ fprintf (fp,"%sTape : %p {\n",indent,(void *) tape) ;
+
+ if (tape == NULL)
+ {
+ fprintf (fp,"%s}\n",indent) ;
+ return ;
+ }
+
+ fprintf (fp,"%s master-file : %s\n", indent, tape->handFilename) ;
+ fprintf (fp,"%s input-file : %s\n", indent, tape->inputFilename) ;
+ fprintf (fp,"%s output-file : %s\n",indent, tape->outputFilename) ;
+ fprintf (fp,"%s lock-file : %s\n",indent, tape->lockFilename) ;
+ fprintf (fp,"%s peerName : %s\n",indent,tape->peerName) ;
+ fprintf (fp,"%s input-FILE : %p\n",indent, (void *) tape->inFp) ;
+ fprintf (fp,"%s output-FILE : %p\n",indent, (void *) tape->outFp) ;
+ fprintf (fp,"%s output-limit : %ld\n",indent, tape->outputLowLimit) ;
+
+#if 0
+ fprintf (fp,"%s in-memory article queue (length %d) {\n",indent,
+ tape->qLength) ;
+
+ for (qe = tape->head ; qe != NULL ; qe = qe->next)
+ {
+#if 0
+ printArticleInfo (qe->article,fp,indentAmt + INDENT_INCR) ;
+#else
+ fprintf (fp,"%s %p\n",indent,qe->article) ;
+#endif
+ }
+
+ fprintf (fp,"%s }\n",indent) ;
+#endif
+
+ fprintf (fp,"%s tell-position : %ld\n",indent,(long) tape->tellpos) ;
+ fprintf (fp,"%s input-FILE-changed : %s\n",indent,
+ boolToString (tape->changed)) ;
+
+ fprintf (fp,"%s no-rotate : %s\n",indent, boolToString (tape->noRotate));
+
+ fprintf (fp,"%s}\n",indent) ;
+}
+
+
+
+
+/* delete the tape. Spools the in-memory articles to disk. */
+void delTape (Tape tape)
+{
+ struct stat st ;
+
+ if (tape == NULL)
+ return ;
+
+#if 1
+
+ if (tape->outFp != NULL && fclose (tape->outFp) != 0)
+ syswarn ("ME ioerr fclose %s", tape->outputFilename) ;
+
+ if (stat(tape->outputFilename, &st) == 0 && st.st_size == 0)
+ {
+ d_printf (1,"removing empty output tape: %s\n",tape->outputFilename) ;
+ unlink (tape->outputFilename) ;
+ }
+
+ tape->outFp = NULL ;
+ tape->outputSize = 0 ;
+
+#else
+
+ tapeClose (tape) ;
+
+#endif
+
+ if (tape->inFp != NULL)
+ {
+ checkpointTape (tape) ;
+ fclose (tape->inFp) ;
+ }
+
+ unlockFile (tape->lockFilename) ;
+
+ freeCharP (tape->handFilename) ;
+ freeCharP (tape->inputFilename) ;
+ freeCharP (tape->outputFilename) ;
+ freeCharP (tape->lockFilename) ;
+ freeCharP (tape->peerName) ;
+
+ removeTapeGlobally (tape) ;
+
+ free (tape) ;
+}
+
+
+void tapeTakeArticle (Tape tape, Article article)
+{
+#if 0
+ QueueElem elem ;
+#endif
+ int amt ;
+ const char *fname, *msgid ;
+
+ ASSERT (tape != NULL) ;
+
+ /* return immediately if spooling disabled - jgarzik */
+ if (tape->noBacklog)
+ {
+ delArticle (article) ;
+ return;
+ }
+
+ fname = artFileName (article) ;
+ msgid = artMsgId (article) ;
+ amt = fprintf (tape->outFp,"%s %s\n", fname, msgid) ;
+
+ /* I'd rather know where I am each time, and I don't trust all
+ fprintf's to give me character counts. */
+#if defined (TRUST_FPRINTF)
+
+ tape->outputSize += amt ;
+
+#else
+#if defined (NO_TRUST_STRLEN)
+
+ tape->outputSize = ftello (tape->outFp) ;
+
+#else
+
+ tape->outputSize += strlen(fname) + strlen(msgid) + 2 ; /* " " + "\n" */
+
+#endif
+#endif
+
+ delArticle (article) ;
+
+ if (debugShrinking)
+ {
+ struct stat sb ;
+
+ fflush (tape->outFp) ;
+
+ if (fstat (fileno (tape->outFp),&sb) != 0)
+ syswarn ("ME oserr fstat %s",tape->outputFilename) ;
+ else if (sb.st_size != tape->outputSize)
+ syslog (LOG_ERR,"fstat and ftello do not agree: %ld %ld for %s\n",
+ (long)sb.st_size,tape->outputSize,tape->outputFilename) ;
+ }
+
+ if (tape->outputHighLimit > 0 && tape->outputSize >= tape->outputHighLimit)
+ {
+ long oldSize = tape->outputSize ;
+ shrinkfile (tape->outFp,tape->outputLowLimit,tape->outputFilename,"a+");
+ tape->outputSize = ftello (tape->outFp) ;
+ tape->lossage += oldSize - tape->outputSize ;
+ }
+}
+
+
+/* Pick an article off a tape and return it. NULL is returned if there
+ are no more articles. */
+Article getArticle (Tape tape)
+{
+ char line [2048] ; /* ick. 1024 for filename + 1024 for msgid */
+ char *p, *q ;
+ char *msgid, *filename ;
+ Article art = NULL ;
+ time_t now = theTime() ;
+
+ ASSERT (tape != NULL) ;
+
+ if (tape->inFp == NULL && (now - tape->lastRotated) > rotatePeriod)
+ prepareFiles (tape) ; /* will flush queue too. */
+
+ while (tape->inFp != NULL && art == NULL)
+ {
+ tape->changed = true ;
+
+ if (fgets (line,sizeof (line), tape->inFp) == NULL)
+ {
+ if (ferror (tape->inFp))
+ syswarn ("ME ioerr on tape file %s", tape->inputFilename) ;
+ else if ( !feof (tape->inFp) )
+ syswarn ("ME oserr fgets %s", tape->inputFilename) ;
+
+ if (fclose (tape->inFp) != 0)
+ syswarn ("ME ioerr fclose %s", tape->inputFilename) ;
+
+ d_printf (1,"No more articles on tape %s\n",tape->inputFilename) ;
+
+ tape->inFp = NULL ;
+ tape->scribbled = false ;
+
+ unlink (tape->inputFilename) ;
+
+ if ((now - tape->lastRotated) > rotatePeriod)
+ prepareFiles (tape) ; /* rotate files to try next. */
+ }
+ else
+ {
+ msgid = filename = NULL ;
+
+ for (p = line ; *p && CTYPE(isspace, *p) ; p++) /* eat whitespace */
+ /* nada */ ;
+
+ if (*p != '\0')
+ {
+ q = strchr (p,' ') ;
+
+ if (q != NULL)
+ {
+ filename = p ;
+ *q = '\0' ;
+
+ for (q++ ; *q && CTYPE(isspace, *q) ; q++)
+ /* nada */ ;
+
+ if (*q != '\0')
+ {
+ if (((p = strchr (q, ' ')) != NULL) ||
+ ((p = strchr (q, '\n')) != NULL))
+ *p = '\0' ;
+
+ if (p != NULL)
+ msgid = q ;
+ else
+ filename = NULL ; /* no trailing newline or blank */
+ }
+ else
+ filename = NULL ; /* line had one field and some blanks */
+ }
+ else
+ filename = NULL ; /* line only had one field */
+ }
+
+ /* See if message ID looks valid. */
+ if (msgid) {
+ for (p = msgid; *p; p++)
+ ;
+ if (p > msgid) p--;
+ if (*msgid != '<' || *p != '>') {
+ warn ("ME tape invalid messageID in %s: %s",
+ tape->inputFilename,msgid);
+ msgid = NULL;
+ }
+ }
+
+ if (filename != NULL && msgid != NULL)
+ art = newArticle (filename, msgid) ;
+
+ /* art may be NULL here if the file is no longer valid. */
+ }
+ }
+
+#if 0
+ /* now we either have an article or there is no more on disk */
+ if (art == NULL)
+ {
+ if (tape->inFp != NULL && ((c = fgetc (tape->inFp)) != EOF))
+ ungetc (c,tape->inFp) ; /* shouldn't happen */
+ else if (tape->inFp != NULL)
+ {
+ /* last article read was the end of the tape. */
+ if (fclose (tape->inFp) != 0)
+ syswarn ("ME ioerr fclose %s", tape->inputFilename) ;
+
+ tape->inFp = NULL ;
+ tape->scribbled = false ;
+
+ /* toss out the old input file and prepare the new one */
+ unlink (tape->inputFilename) ;
+
+ if (now - tape->lastRotated > rotatePeriod)
+ prepareFiles (tape) ;
+ }
+ }
+#endif
+
+ if (art == NULL)
+ d_printf (2,"%s All out of articles in the backlog\n", tape->peerName) ;
+ else
+ d_printf (2,"%s Peeled article %s from backlog\n",tape->peerName,
+ artMsgId (art)) ;
+
+ return art ;
+}
+
+
+/****************************************************/
+/** CLASS FUNCTIONS **/
+/****************************************************/
+
+/* Cause all the Tapes to checkpoint themselves. */
+void checkPointTapes (void)
+{
+ unsigned int i ;
+
+ for (i = 0 ; i < activeTapeIdx ; i++)
+ checkpointTape (activeTapes [i]) ;
+}
+
+
+/* make all the tapes set their checkNew flag. */
+static void tapesSetCheckNew (void)
+{
+ unsigned int i ;
+
+ for (i = 0 ; i < activeTapeIdx ; i++)
+ activeTapes[i]->checkNew = true ;
+}
+
+
+#if 0
+/* Set the pathname of the directory for storing tapes in. */
+void setTapeDirectory (const char *newDir)
+{
+ /* the activeTape variable gets set when the first Tape object
+ is created */
+ if (activeTapes != NULL)
+ {
+ syslog (LOG_CRIT,"Resetting backlog directory") ;
+ abort() ;
+ }
+
+ if (tapeDirectory != NULL)
+ freeCharP (tapeDirectory) ;
+
+ tapeDirectory = xstrdup (newDir) ;
+
+ addPointerFreedOnExit (tapeDirectory) ;
+}
+#endif
+
+/* Get the pathname of the directory tapes are stored in. */
+const char *getTapeDirectory (void)
+{
+ ASSERT (tapeDirectory != NULL) ;
+
+#if 0
+ if (tapeDirectory == NULL)
+ {
+ tapeDirectory = xstrdup (dflTapeDir) ;
+ addPointerFreedOnExit (tapeDirectory) ;
+ }
+#endif
+
+ return tapeDirectory ;
+}
+
+
+#if 0
+void setOutputSizeLimit (long val)
+{
+ defaultSizeLimit = val ;
+}
+#endif
+
+
+
+
+
+
+/**********************************************************************/
+/* PRIVATE FUNCTIONS */
+/**********************************************************************/
+
+
+/* Add a new tape to the class-level list of active tapes. */
+static void addTapeGlobally (Tape tape)
+{
+ ASSERT (tape != NULL) ;
+
+ if (activeTapeSize == activeTapeIdx)
+ {
+ unsigned int i ;
+
+ activeTapeSize += 10 ;
+ if (activeTapes != NULL)
+ activeTapes = xrealloc (activeTapes, sizeof(Tape) * activeTapeSize) ;
+ else
+ activeTapes = xmalloc (sizeof(Tape) * activeTapeSize) ;
+
+ for (i = activeTapeIdx ; i < activeTapeSize ; i++)
+ activeTapes [i] = NULL ;
+ }
+ activeTapes [activeTapeIdx++] = tape ;
+}
+
+
+/* Remove a tape for the class-level list of active tapes. */
+static void removeTapeGlobally (Tape tape)
+{
+ unsigned int i ;
+
+ if (tape == NULL)
+ return ;
+
+ ASSERT (activeTapeIdx > 0) ;
+
+ for (i = 0 ; i < activeTapeIdx ; i++)
+ if (activeTapes [i] == tape)
+ break ;
+
+ ASSERT (i < activeTapeIdx) ;
+
+ for ( ; i < (activeTapeIdx - 1) ; i++)
+ activeTapes [i] = activeTapes [i + 1] ;
+
+ activeTapes [--activeTapeIdx] = NULL ;
+
+ if (activeTapeIdx == 0)
+ {
+ free (activeTapes) ;
+ activeTapes = NULL ;
+ }
+}
+
+
+/* Have a tape checkpoint itself so that next process can pick up where
+ this one left off. */
+static void checkpointTape (Tape tape)
+{
+ if (tape->inFp == NULL) /* no input file being read. */
+ return ;
+
+ if (!tape->changed) /* haven't read since last checkpoint */
+ {
+ d_printf (1,"Not checkpointing unchanged tape: %s\n", tape->peerName) ;
+ return ;
+ }
+
+ if ((tape->tellpos = ftello (tape->inFp)) < 0)
+ {
+ syswarn ("ME oserr ftello %s", tape->inputFilename) ;
+ return ;
+ }
+
+ /* strlen of "18446744073709551616\n" (2^64) */
+#define BITS64 21
+
+ /* make sure we're not right at the beginning of the file so we can write. */
+ if (tape->tellpos > BITS64)
+ {
+ rewind (tape->inFp) ;
+
+ /* scribble blanks over the first lines characters */
+ if (!tape->scribbled)
+ {
+ int currloc = 0 ;
+ int c ;
+
+ while ((c = fgetc (tape->inFp)) != '\n' || currloc <= BITS64)
+ if (c == EOF)
+ return ;
+ else
+ currloc++ ;
+
+ rewind (tape->inFp) ;
+
+ while (currloc-- > 0)
+ fputc (' ',tape->inFp) ;
+
+ rewind (tape->inFp) ;
+
+ fflush (tape->inFp) ;
+ tape->scribbled = true ;
+ }
+
+ fprintf (tape->inFp,"%ld",tape->tellpos) ;
+
+ if (fseeko (tape->inFp,tape->tellpos,SEEK_SET) != 0)
+ syswarn ("ME oserr fseeko(%s,%ld,SEEK_SET)",tape->inputFilename,
+ tape->tellpos) ;
+ }
+
+ tape->changed = false ;
+}
+
+
+
+/* Prepare the tape file(s) for input and output */
+
+/* For a given Tape there are
+ * three possible files: PEER.input PEER and
+ * PEER.output. PEER.input and PEER.output are private to
+ * innfeed. PEER is where a sysadmin can drop a file that (s)he
+ * wants to inject into the process. If the first line of the input file
+ * contains only an integer (possibly surrounded by spaces), then this is
+ * taken to be the position to seek to before reading.
+ *
+ * prepareFiles will process them in a manner much like the following shell
+ * commands:
+ *
+ * if [ ! -f PEER.input ]; then
+ * if [ -f PEER ]; then mv PEER PEER.input
+ * elif [ -f PEER.output ]; then mv PEER.output PEER; fi
+ * fi
+ *
+ * At this point PEER.input is opened for reading if it exists.
+ *
+ * The checkpoint file is left as-is unless the PEER.input file
+ * happens to be newer that the checkpoint file.
+ */
+static void prepareFiles (Tape tape)
+{
+ bool inpExists ;
+ bool outExists ;
+ bool newExists ;
+
+#if 0
+ /* flush any in memory articles to disk */
+ if (tape->head != NULL && tape->outFp != NULL)
+ flushTape(tape) ;
+#endif
+
+ tape->tellpos = 0 ;
+
+ /* First time through, or something external has set checkNew */
+ if (tape->lastRotated == 0 || tape->checkNew)
+ {
+ newExists = fileExistsP (tape->handFilename) ;
+ if (newExists)
+ notice ("%s new hand-prepared backlog file", tape->peerName) ;
+ }
+ else
+ newExists = false ;
+
+ if (tape->lastRotated == 0) /* first time here */
+ {
+ inpExists = fileExistsP (tape->inputFilename) ;
+ outExists = fileExistsP (tape->outputFilename) ;
+ }
+ else
+ {
+ inpExists = (tape->inFp != NULL) ? true : false ; /* can this ever be true?? */
+ outExists = (tape->outFp != NULL && tape->outputSize > 0) ? true : false ;
+ }
+
+
+ /* move the hand-dropped file to the input file if needed. */
+ if (newExists && !inpExists)
+ {
+ if (rename (tape->handFilename,tape->inputFilename) != 0)
+ syswarn ("ME oserr rename %s, %s", tape->handFilename,
+ tape->inputFilename);
+ else
+ {
+ notice ("%s grabbing external tape file", tape->peerName) ;
+ inpExists = true ;
+ }
+ }
+
+ /* now move the output file to the input file, if needed and only if in
+ not in NOROTATE mode. */
+ if (outExists && !inpExists && !tape->noRotate)
+ {
+ if (tape->outFp != NULL)
+ {
+ fclose (tape->outFp) ;
+ tape->outFp = NULL ;
+ }
+
+ if (rename (tape->outputFilename,tape->inputFilename) != 0)
+ syswarn ("ME oserr rename %s, %s", tape->outputFilename,
+ tape->inputFilename) ;
+ else
+ inpExists = true ;
+
+ outExists = false ;
+ }
+
+ /* now open up the input file and seek to the proper position. */
+ if (inpExists)
+ {
+ int c ;
+ long flength ;
+
+ if ((tape->inFp = fopen (tape->inputFilename,"r+")) == NULL)
+ syswarn ("ME fopen %s", tape->inputFilename) ;
+ else
+ {
+ char buffer [64] ;
+
+ if (fgets (buffer,sizeof (buffer) - 1, tape->inFp) == NULL)
+ {
+ if (feof (tape->inFp))
+ {
+ d_printf (1,"Empty input file: %s\n",tape->inputFilename) ;
+ unlink (tape->inputFilename) ;
+ }
+ else
+ syswarn ("ME oserr fgets %s", tape->inputFilename) ;
+
+ fclose (tape->inFp) ;
+ tape->inFp = NULL ;
+ tape->scribbled = false ;
+ }
+ else
+ {
+ unsigned int len = strlen (buffer) ;
+ long newPos = 0 ;
+
+ if (len > 0 && buffer [len - 1] == '\n')
+ buffer [--len] = '\0' ;
+
+ if (len > 0 && strspn (buffer,"0123456789 \n") == len)
+ {
+ if (sscanf (buffer,"%ld",&newPos) == 1)
+ {
+ tape->scribbled = true ;
+ tape->tellpos = newPos ;
+ }
+ }
+
+ if ((flength = fileLength (fileno (tape->inFp))) < tape->tellpos)
+ {
+ warn ("ME tape short: %s %ld %ld", tape->inputFilename,
+ flength, tape->tellpos) ;
+ tape->tellpos = 0 ;
+ }
+ else if (tape->tellpos == 0)
+ rewind (tape->inFp) ;
+ else if (fseeko (tape->inFp,tape->tellpos - 1,SEEK_SET) != 0)
+ syswarn ("ME oserr fseeko(%s,%ld,SEEK_SET)",
+ tape->inputFilename,tape->tellpos) ;
+ else if ((c = fgetc (tape->inFp)) != '\n')
+ {
+ while (c != EOF && c != '\n')
+ c = fgetc (tape->inFp) ;
+
+ if (c == EOF)
+ {
+ fclose (tape->inFp) ;
+ unlink (tape->inputFilename) ;
+ tape->inFp = NULL ;
+ tape->scribbled = false ;
+ prepareFiles (tape) ;
+ }
+ else
+ {
+ long oldPos = tape->tellpos ;
+
+ tape->changed = true ;
+ checkpointTape (tape) ;
+
+ warn ("ME internal checkpoint line boundary missed:"
+ " %s %ld vs. %ld",tape->inputFilename,
+ tape->tellpos, oldPos) ;
+ }
+ }
+ }
+ }
+ }
+
+ tape->lastRotated = theTime() ;
+ tape->checkNew = false ;
+
+ /* now open up the output file. */
+ if (tape->outFp == NULL)
+ {
+ if ((tape->outFp = fopen (tape->outputFilename,"a+")) == NULL)
+ {
+ syswarn ("ME tape open failed (a+) %s", tape->outputFilename) ;
+ return ;
+ }
+ fseeko (tape->outFp,0,SEEK_END) ;
+ tape->outputSize = ftello (tape->outFp) ;
+ tape->lossage = 0 ;
+ }
+}
+
+
+static void tapeCkNewFileCbk (TimeoutId id, void *d UNUSED)
+{
+ ASSERT (id == ckNewFileId) ;
+ ASSERT (tapeCkNewFilePeriod > 0) ;
+
+ tapesSetCheckNew () ;
+
+ ckNewFileId = prepareSleep (tapeCkNewFileCbk,tapeCkNewFilePeriod,NULL) ;
+}
+
+
+/* The timer callback function that will checkpoint all the active tapes. */
+static void tapeCheckpointCallback (TimeoutId id, void *d UNUSED)
+{
+ ASSERT (id == checkPtId) ;
+ ASSERT (tapeCkPtPeriod > 0) ;
+
+ d_printf (1,"Checkpointing tapes\n") ;
+
+ checkPointTapes () ;
+
+ checkPtId = prepareSleep (tapeCheckpointCallback,tapeCkPtPeriod,NULL) ;
+}
+
+
+
+#if 0
+static void flushTape (Tape tape)
+{
+ QueueElem elem ;
+
+ /* flush out queue to disk. */
+ elem = tape->head ;
+ while (elem != NULL)
+ {
+ tape->head = tape->head->next ;
+ fprintf (tape->outFp,"%s %s\n", artFileName (elem->article),
+ artMsgId (elem->article)) ;
+
+ delArticle (elem->article) ;
+
+ free (elem) ;
+ elem = tape->head ;
+ }
+ tape->tail = NULL ;
+ tape->qLength = 0;
+
+
+ /* I'd rather know where I am each time, and I don't trust all
+ fprintf's to give me character counts. */
+ tape->outputSize = ftello (tape->outFp) ;
+ if (debugShrinking)
+ {
+ struct stat buf ;
+ static bool logged = false ;
+
+ fflush (tape->outFp) ;
+ if (fstat (fileno (tape->outFp),&buf) != 0 && !logged)
+ {
+ syslog (LOG_ERR,FSTAT_FAILURE,tape->outputFilename) ;
+ logged = true ;
+ }
+ else if (buf.st_size != tape->outputSize)
+ {
+ warn ("ME fstat and ftello do not agree for %s",
+ tape->outputFilename) ;
+ logged = true ;
+ }
+ }
+
+ if (tape->outputHighLimit > 0 && tape->outputSize > tape->outputHighLimit)
+ {
+ shrinkfile (tape->outFp,tape->outputLowLimit,tape->outputFilename,"a+");
+ tape->outputSize = ftello (tape->outFp) ;
+ }
+}
+#endif
+
+
+static void tapeCleanup (void)
+{
+ free (tapeDirectory) ;
+ tapeDirectory = NULL ;
+}
--- /dev/null
+/* $Id: tape.h 6648 2004-01-25 20:07:11Z rra $
+**
+** The public interface to the Tape class.
+**
+** Written by James Brister <brister@vix.com>
+**
+** The Tape class simulates a mag tape. It only reads or writes Articles. A
+** tape is either in an Input or Output state. When an Article is given to a
+** Tape it will store the Article in memory until it reaches a highwater mark
+** at which point it dumps all it's articles to disk.
+**
+** Input tapes generate article objects on request if the underlying tape
+** file has info in it. The Tapes take care of cleaning up used-up files as
+** needed.
+*/
+
+#if ! defined ( tape_h__ )
+#define tape_h__
+
+#include <stdio.h>
+
+#include "misc.h"
+
+
+/* If dontRotate is true, then any articles that get written to the tape
+ will never be read back in again. This is for the batch-mode-only case
+ where articles written to tape were done so 'cause the remote
+ temporarily rejected them. */
+Tape newTape (const char *peerName, bool dontRotate) ;
+
+void gPrintTapeInfo (FILE *fp, unsigned int inedntAmt) ;
+void printTapeInfo (Tape tape, FILE *fp, unsigned int indentAmt) ;
+
+/* deletes the tape objects. If it has any articles cached then it dumps
+ them to the disk. */
+void delTape (Tape tape) ;
+
+/* give an article to the Tape for storage */
+void tapeTakeArticle (Tape tape, Article article) ;
+
+/* get a new article from an Input tape. */
+Article getArticle (Tape tape) ;
+
+/* close the input and output files and reopen them. */
+void gFlushTapes (void) ;
+void tapeFlush (Tape tape) ;
+
+
+/**************************************************/
+/* CLASS LEVEL FUNCTIONS */
+/**************************************************/
+
+/* get all the active input tapes to checkpoint their current positions */
+void checkPointTapes (void) ;
+
+/* get the name of the directory tapes are being stored in. */
+const char *getTapeDirectory (void) ;
+
+/* set the size limit of the output tapes. Default is zero which is no
+ limit. */
+void setOutputSizeLimit (long limit) ;
+
+int tapeConfigLoadCbk (void *data) ;
+
+void tapeLogGlobalStatus (FILE *fp) ;
+void tapeLogStatus (Tape tape, FILE *fp) ;
+
+#endif /* tape_h__ */
--- /dev/null
+#!/usr/bin/perl
+#
+# Author: James Brister <brister@vix.com> -- berkeley-unix --
+# Start Date: Wed Jan 3 00:09:01 1996
+# Project: INN -- innfeed
+# File: testListener.pl
+# RCSId: $Id: testListener.pl 6312 2003-05-04 21:40:11Z rra $
+# Description: Generate news files for testing the innfeed feeder.
+#
+# Run like this:
+#
+# testListener.pl -t 30 -d tmp | innfeed
+#
+# or like this:
+#
+# innfeed -s 'perl testListener.pl -t 30 -d tmp'
+#
+
+$0 =~ s!.*/!! ;
+
+require 'getopts.pl' ;
+
+$usage = "$0 [ -a -b name -d directory -c count -t sleep-amt -r -u ] peers\n" .
+ " -a is for duplicate article id's periodically\n" .
+ " -u is for random unlinking of article\n" .
+ " -b add bogus peername periodically\n" .
+ " -d is the directory where articles show be written.\n" .
+ " -c is how many articles to create (0 the default meamns no limit)\n" .
+ " -t is the number of seconds to sleep between each article.\n" .
+ " -r is to have articles be created in NNTP ready format\n" ;
+
+&Getopts ("a:b:c:d:t:rl:h:") || die $usage ;
+
+die $usage if $opt_h ;
+$total = $opt_c ;
+
+$sleepAmt = 1 ;
+$sleepAmt = $opt_t if ($opt_t =~ /^[\d\.]+/) ;
+
+$lineCount = 50 ;
+$lineCount = $opt_l if ($opt_l =~ /^\d+$/) ;
+
+$directory = "." ;
+$directory = $opt_d if $opt_d ;
+
+$bogus = $opt_b ;
+if ( $bogus && $bogus !~ /^[a-zA-Z]+$/ ) {
+ print "The bogus peername must contain only letters\n" ;
+}
+
+$cr = ($opt_r ? "\r" : "") ;
+
+$SIG{'INT'} = 'IGNORE' ;
+$SIG{'TERM'} = 'sigHup' ;
+$SIG{'QUIT'} = 'sigHup' ;
+$SIG{'HUP'} = 'sigHup' ;
+
+sub sigHup {
+ exit (1) ;
+}
+
+
+$monstr = "JanFebMarAprMayJunJulAugSepOctNovDec" ;
+$letstr = "abcdefghijklmnopqrstuvwxyz" ;
+
+sub createArticle {
+ local ($counter) = @_ ;
+ local ($filename,$msgid,$i) ;
+ local ($time) = $^T ;
+ local ($sec,$min,$hour,$mday,$mon,$year,$wday,$yday,$isdst)
+ = gmtime($time);
+ local ($index) = $counter ;
+
+
+ if ($opt_a && ((int (rand (4)) % 2) == 0)) {
+ $index = int ($index / 2) ;
+ }
+
+ $msgid = "<$index.$$.$time\@home.octet.com.au>" ;
+
+ $filename = sprintf ("%s/SampleArticle.%06d",$directory,$index) ;
+
+ open (ARTICLE,">$filename") ||
+ die "open ($filename): $!\n" ;
+ print ARTICLE "Path: home.octet.com.au!not-for-mail$cr\n" ;
+ print ARTICLE "From: brister\@home.octet.com.au$cr\n" ;
+ print ARTICLE "Newsgroups: junk,local.test$cr\n" ;
+ print ARTICLE "Subject: Test$cr\n" ;
+ print ARTICLE "Date: " ;
+
+ printf ARTICLE "%d %s %d %02d:%02d:%02d UTC$cr\n",
+ $mday, substr($monstr,$mon * 3, 3), $year + 1900,
+ $hour, $min, $sec ;
+
+ print ARTICLE "Organization: None that I can think of$cr\n" ;
+ print ARTICLE "Lines: 5$cr\n" ;
+ print ARTICLE "Distribution: world$cr\n" ;
+ print ARTICLE "Message-ID: $msgid$cr\n" ;
+ print ARTICLE "NNTP-Posting-Host: localhost$cr\n" ;
+ print ARTICLE "$cr\n" ;
+
+ for ($i = 0 ; $i < $lineCount ; $i++) {
+ print ARTICLE "x" x ($lineCount + 1), "$cr\n";
+ }
+ print ARTICLE ".This line has a leading dot.$cr\n" ;
+ print ARTICLE "And the next line only has a dot.$cr\n" ;
+ if ($opt_r) {
+ print ARTICLE "..$cr\n" ;
+ } else {
+ print ARTICLE ".$cr\n" ;
+ }
+ print ARTICLE "And the next line has just two dots...$cr\n" ;
+ print ARTICLE "...$cr\n" ;
+ print ARTICLE "foo$cr\n" ;
+ print ARTICLE "And the next line is the last line of the article$cr\n" ;
+ print ARTICLE "and it only has a single dot on it.$cr\n" ;
+ if ($opt_r) {
+ print ARTICLE "..$cr\n" ;
+ } else {
+ print ARTICLE ".$cr\n" ;
+ }
+
+
+ close (ARTICLE) ;
+
+ return ($msgid, $filename) ;
+}
+
+srand ;
+
+
+$| = 1 ;
+
+if ( ! -t STDERR ) {
+ open (STDERR,">>/tmp/TESTLISTENER.LOG") || die ;
+}
+
+srand ;
+$sleepAmt = 1 if ($sleepAmt < 0) ;
+
+foreach $peer ( @ARGV ) {
+ $PEERS{$peer} = 1 ;
+}
+
+die "Must give peernames on command line:\n$usage" if ( ! @ARGV ) ;
+
+for ( $i = 0 ; $total == 0 || $i < $total ; $i++ ) {
+ ($msgid,$filename) = &createArticle ($i) ;
+ if ($opt_a && ((rand (3) % 3) == 0)) {
+ print TTY "Removing file $filename\n" ;
+ unlink ($filename) if $opt_u ;
+ }
+ print "$filename $msgid @ARGV" ;
+ print " $bogus" if ($bogus && (rand (5) % 5) == 0) ;
+ print "\n" ;
+
+ select (undef,undef,undef,(rand ($sleepAmt-1) + 1)) if $sleepAmt ;
+}
+
+sleep 11500 unless -f STDOUT ;
--- /dev/null
+## $Id: Makefile 7727 2008-04-06 07:59:46Z iulius $
+
+include ../Makefile.global
+
+top = ..
+CFLAGS = $(GCFLAGS)
+
+# The base library files that are always compiled and included.
+SOURCES = buffer.c cleanfrom.c clientactive.c clientlib.c concat.c \
+ conffile.c confparse.c daemonize.c date.c dbz.c defdist.c \
+ fdflags.c fdlimit.c genid.c getfqdn.c getmodaddr.c gettime.c \
+ hash.c hashtab.c innconf.c inndcomm.c list.c localopen.c \
+ lockfile.c makedir.c md5.c messages.c mmap.c parsedate.c \
+ qio.c radix32.c readin.c remopen.c reservedfd.c resource.c \
+ sendarticle.c sendpass.c sequence.c sockaddr.c timer.c tst.c \
+ uwildmat.c vector.c version.c wire.c xfopena.c xmalloc.c \
+ xsignal.c xwrite.c
+
+# Sources for additional functions only built to replace missing system ones.
+EXTRA_SOURCES = fseeko.c ftello.c getpagesize.c hstrerror.c inet_aton.c \
+ inet_ntoa.c memcmp.c mkstemp.c pread.c pwrite.c setenv.c \
+ setproctitle.c strcasecmp.c strerror.c strlcat.c strlcpy.c \
+ strspn.c strtok.c
+
+OBJECTS = $(LIBOBJS) $(SOURCES:.c=.o)
+LOBJECTS = $(OBJECTS:.o=.lo)
+
+.SUFFIXES: .lo
+
+all: libinn.$(EXTLIB) perl.o
+
+warnings:
+ $(MAKE) COPT='$(WARNINGS)' all
+
+install: all
+ $(LI_XPUB) libinn.$(EXTLIB) $D$(PATHLIB)/libinn.$(EXTLIB)
+
+clobber clean distclean:
+ rm -f *.o *.lo libinn.la libinn.a parsedate.c parsedate
+ rm -f profiled perl$(PROFSUFFIX).o libinn$(PROFSUFFIX).a
+ rm -f libinn_pure_*.a .pure
+ rm -rf .libs
+
+tags ctags: $(SOURCES)
+ $(CTAGS) $(SOURCES) ../include/*.h
+
+libinn.la: $(OBJECTS) $(LOBJECTS)
+ $(LIBLD) $(LDFLAGS) -o $@ $(LOBJECTS) $(LIBS) \
+ -rpath $(PATHLIB) -version-info 2:0:0
+
+libinn.a: $(OBJECTS)
+ ar r $@ $(OBJECTS)
+ $(RANLIB) libinn.a
+
+.c.o .c.lo:
+ $(LIBCC) $(CFLAGS) -c $*.c
+
+perl.o: perl.c
+ $(CC) $(CFLAGS) $(PERLINC) $(LDFLAGS) -c perl.c
+
+../include/inn/system.h:
+ (cd ../include && $(MAKE))
+
+parsedate.c: parsedate.y
+ @echo Expect 6 shift/reduce conflicts
+ $(YACC) parsedate.y
+ @mv y.tab.c parsedate.c
+
+parsedate: parsedate.c gettime.o
+ $(CC) $(CFLAGS) $(LDFLAGS) -o $@ -DTEST -DYYDEBUG parsedate.c gettime.o
+
+## Profiling. The rules are a bit brute-force, but good enough.
+profiled: libinn$(PROFSUFFIX).a perl$(PROFSUFFIX).o
+ date >$@
+
+libinn$(PROFSUFFIX).a perl$(PROFSUFFIX).o: $(OBJECTS) perl.o
+ rm -f $(OBJECTS)
+ $(MAKEPROFILING) libinn.a
+ $(MAKEPROFILING) perl.o
+ mv libinn.a libinn$(PROFSUFFIX).a
+ mv perl.o perl$(PROFSUFFIX).o
+ $(RANLIB) libinn$(PROFSUFFIX).a
+ rm -f $(OBJECTS)
+
+## Dependencies. Default list, below, is probably good enough.
+
+depend: Makefile $(SOURCES) $(EXTRA_SOURCES) perl.c ../include/inn/system.h
+ $(MAKEDEPEND) '$(CFLAGS) $(PERLINC)' $(SOURCES) $(EXTRA_SOURCES) perl.c
+
+# Special dependency to teach make to build the include directory properly.
+../include/inn/defines.h: ../include/inn/system.h
+
+# DO NOT DELETE THIS LINE -- make depend depends on it.
+buffer.o: buffer.c ../include/config.h ../include/inn/defines.h \
+ ../include/inn/system.h ../include/clibrary.h ../include/config.h \
+ ../include/inn/buffer.h ../include/inn/defines.h ../include/libinn.h
+cleanfrom.o: cleanfrom.c ../include/config.h ../include/inn/defines.h \
+ ../include/inn/system.h ../include/clibrary.h ../include/config.h \
+ ../include/libinn.h
+clientactive.o: clientactive.c ../include/config.h \
+ ../include/inn/defines.h ../include/inn/system.h ../include/clibrary.h \
+ ../include/config.h ../include/inn/innconf.h ../include/inn/defines.h \
+ ../include/libinn.h ../include/nntp.h ../include/paths.h
+clientlib.o: clientlib.c ../include/config.h ../include/inn/defines.h \
+ ../include/inn/system.h ../include/clibrary.h ../include/config.h \
+ ../include/inn/innconf.h ../include/inn/defines.h ../include/libinn.h \
+ ../include/nntp.h
+concat.o: concat.c ../include/config.h ../include/inn/defines.h \
+ ../include/inn/system.h ../include/libinn.h ../include/config.h
+conffile.o: conffile.c ../include/config.h ../include/inn/defines.h \
+ ../include/inn/system.h ../include/clibrary.h ../include/config.h \
+ ../include/conffile.h ../include/libinn.h
+confparse.o: confparse.c ../include/config.h ../include/inn/defines.h \
+ ../include/inn/system.h ../include/clibrary.h ../include/config.h \
+ ../include/inn/confparse.h ../include/inn/defines.h \
+ ../include/inn/hashtab.h ../include/inn/messages.h \
+ ../include/inn/vector.h ../include/libinn.h
+daemonize.o: daemonize.c ../include/config.h ../include/inn/defines.h \
+ ../include/inn/system.h ../include/clibrary.h ../include/config.h \
+ ../include/inn/messages.h ../include/inn/defines.h ../include/libinn.h
+date.o: date.c ../include/config.h ../include/inn/defines.h \
+ ../include/inn/system.h ../include/clibrary.h ../include/config.h \
+ ../include/libinn.h
+dbz.o: dbz.c ../include/config.h ../include/inn/defines.h \
+ ../include/inn/system.h ../include/clibrary.h ../include/config.h \
+ ../include/dbz.h ../include/libinn.h ../include/inn/messages.h \
+ ../include/inn/defines.h ../include/inn/innconf.h ../include/inn/mmap.h \
+ ../include/libinn.h
+defdist.o: defdist.c ../include/config.h ../include/inn/defines.h \
+ ../include/inn/system.h ../include/clibrary.h ../include/config.h \
+ ../include/inn/innconf.h ../include/inn/defines.h ../include/libinn.h \
+ ../include/paths.h
+fdflags.o: fdflags.c ../include/config.h ../include/inn/defines.h \
+ ../include/inn/system.h ../include/clibrary.h ../include/config.h \
+ ../include/libinn.h
+fdlimit.o: fdlimit.c ../include/config.h ../include/inn/defines.h \
+ ../include/inn/system.h ../include/clibrary.h ../include/config.h \
+ ../include/libinn.h
+genid.o: genid.c ../include/config.h ../include/inn/defines.h \
+ ../include/inn/system.h ../include/clibrary.h ../include/config.h \
+ ../include/inn/innconf.h ../include/inn/defines.h ../include/libinn.h
+getfqdn.o: getfqdn.c ../include/config.h ../include/inn/defines.h \
+ ../include/inn/system.h ../include/clibrary.h ../include/config.h \
+ ../include/libinn.h ../include/paths.h
+getmodaddr.o: getmodaddr.c ../include/config.h ../include/inn/defines.h \
+ ../include/inn/system.h ../include/clibrary.h ../include/config.h \
+ ../include/inn/innconf.h ../include/inn/defines.h ../include/libinn.h \
+ ../include/nntp.h ../include/paths.h
+gettime.o: gettime.c ../include/config.h ../include/inn/defines.h \
+ ../include/inn/system.h ../include/libinn.h ../include/config.h
+hash.o: hash.c ../include/config.h ../include/inn/defines.h \
+ ../include/inn/system.h ../include/clibrary.h ../include/config.h \
+ ../include/inn/md5.h ../include/inn/defines.h ../include/libinn.h
+hashtab.o: hashtab.c ../include/config.h ../include/inn/defines.h \
+ ../include/inn/system.h ../include/clibrary.h ../include/config.h \
+ ../include/inn/hashtab.h ../include/inn/defines.h ../include/libinn.h
+innconf.o: innconf.c ../include/config.h ../include/inn/defines.h \
+ ../include/inn/system.h ../include/clibrary.h ../include/config.h \
+ ../include/inn/confparse.h ../include/inn/defines.h \
+ ../include/inn/innconf.h ../include/inn/messages.h \
+ ../include/inn/vector.h ../include/libinn.h ../include/paths.h
+inndcomm.o: inndcomm.c ../include/config.h ../include/inn/defines.h \
+ ../include/inn/system.h ../include/clibrary.h ../include/config.h \
+ ../include/portable/time.h ../include/config.h \
+ ../include/portable/socket.h ../include/inn/innconf.h \
+ ../include/inn/defines.h ../include/inndcomm.h ../include/libinn.h \
+ ../include/paths.h
+list.o: list.c ../include/config.h ../include/inn/defines.h \
+ ../include/inn/system.h ../include/clibrary.h ../include/config.h \
+ ../include/inn/list.h ../include/inn/defines.h
+localopen.o: localopen.c ../include/config.h ../include/inn/defines.h \
+ ../include/inn/system.h ../include/clibrary.h ../include/config.h \
+ ../include/inn/innconf.h ../include/inn/defines.h ../include/libinn.h \
+ ../include/nntp.h ../include/paths.h
+lockfile.o: lockfile.c ../include/config.h ../include/inn/defines.h \
+ ../include/inn/system.h ../include/clibrary.h ../include/config.h \
+ ../include/libinn.h
+makedir.o: makedir.c ../include/config.h ../include/inn/defines.h \
+ ../include/inn/system.h ../include/clibrary.h ../include/config.h \
+ ../include/libinn.h
+md5.o: md5.c ../include/config.h ../include/inn/defines.h \
+ ../include/inn/system.h ../include/clibrary.h ../include/config.h \
+ ../include/inn/md5.h ../include/inn/defines.h
+messages.o: messages.c ../include/config.h ../include/inn/defines.h \
+ ../include/inn/system.h ../include/clibrary.h ../include/config.h \
+ ../include/inn/messages.h ../include/inn/defines.h ../include/libinn.h
+mmap.o: mmap.c ../include/config.h ../include/inn/defines.h \
+ ../include/inn/system.h ../include/clibrary.h ../include/config.h \
+ ../include/portable/mmap.h ../include/config.h \
+ ../include/inn/messages.h ../include/inn/defines.h \
+ ../include/inn/mmap.h
+parsedate.o: parsedate.c ../include/config.h ../include/inn/defines.h \
+ ../include/inn/system.h ../include/clibrary.h ../include/config.h \
+ ../include/libinn.h
+qio.o: qio.c ../include/config.h ../include/inn/defines.h \
+ ../include/inn/system.h ../include/clibrary.h ../include/config.h \
+ ../include/inn/qio.h ../include/inn/defines.h ../include/libinn.h
+radix32.o: radix32.c ../include/config.h ../include/inn/defines.h \
+ ../include/inn/system.h ../include/clibrary.h ../include/config.h \
+ ../include/libinn.h
+readin.o: readin.c ../include/config.h ../include/inn/defines.h \
+ ../include/inn/system.h ../include/clibrary.h ../include/config.h \
+ ../include/libinn.h
+remopen.o: remopen.c ../include/config.h ../include/inn/defines.h \
+ ../include/inn/system.h ../include/clibrary.h ../include/config.h \
+ ../include/portable/socket.h ../include/config.h \
+ ../include/inn/innconf.h ../include/inn/defines.h ../include/libinn.h \
+ ../include/nntp.h ../include/paths.h
+reservedfd.o: reservedfd.c ../include/config.h ../include/inn/defines.h \
+ ../include/inn/system.h ../include/clibrary.h ../include/config.h \
+ ../include/libinn.h
+resource.o: resource.c ../include/config.h ../include/inn/defines.h \
+ ../include/inn/system.h ../include/clibrary.h ../include/config.h \
+ ../include/libinn.h
+sendarticle.o: sendarticle.c ../include/config.h ../include/inn/defines.h \
+ ../include/inn/system.h ../include/clibrary.h ../include/config.h \
+ ../include/libinn.h ../include/nntp.h
+sendpass.o: sendpass.c ../include/config.h ../include/inn/defines.h \
+ ../include/inn/system.h ../include/clibrary.h ../include/config.h \
+ ../include/inn/innconf.h ../include/inn/defines.h ../include/libinn.h \
+ ../include/nntp.h ../include/paths.h
+sequence.o: sequence.c ../include/config.h ../include/inn/defines.h \
+ ../include/inn/system.h ../include/clibrary.h ../include/config.h \
+ ../include/inn/sequence.h ../include/inn/defines.h
+sockaddr.o: sockaddr.c ../include/config.h ../include/inn/defines.h \
+ ../include/inn/system.h ../include/clibrary.h ../include/config.h \
+ ../include/portable/socket.h ../include/config.h ../include/libinn.h
+timer.o: timer.c ../include/config.h ../include/inn/defines.h \
+ ../include/inn/system.h ../include/clibrary.h ../include/config.h \
+ ../include/portable/time.h ../include/config.h \
+ ../include/inn/messages.h ../include/inn/defines.h \
+ ../include/inn/timer.h ../include/libinn.h
+tst.o: tst.c ../include/config.h ../include/inn/defines.h \
+ ../include/inn/system.h ../include/clibrary.h ../include/config.h \
+ ../include/inn/tst.h ../include/inn/defines.h ../include/libinn.h
+uwildmat.o: uwildmat.c ../include/config.h ../include/inn/defines.h \
+ ../include/inn/system.h ../include/clibrary.h ../include/config.h \
+ ../include/libinn.h
+vector.o: vector.c ../include/config.h ../include/inn/defines.h \
+ ../include/inn/system.h ../include/clibrary.h ../include/config.h \
+ ../include/inn/vector.h ../include/inn/defines.h ../include/libinn.h
+version.o: version.c ../include/config.h ../include/inn/defines.h \
+ ../include/inn/system.h ../include/inn/version.h
+wire.o: wire.c ../include/config.h ../include/inn/defines.h \
+ ../include/inn/system.h ../include/clibrary.h ../include/config.h \
+ ../include/inn/wire.h ../include/inn/defines.h ../include/libinn.h
+xfopena.o: xfopena.c ../include/config.h ../include/inn/defines.h \
+ ../include/inn/system.h ../include/clibrary.h ../include/config.h \
+ ../include/libinn.h
+xmalloc.o: xmalloc.c ../include/config.h ../include/inn/defines.h \
+ ../include/inn/system.h ../include/clibrary.h ../include/config.h \
+ ../include/inn/messages.h ../include/inn/defines.h ../include/libinn.h
+xsignal.o: xsignal.c ../include/config.h ../include/inn/defines.h \
+ ../include/inn/system.h ../include/libinn.h ../include/config.h
+xwrite.o: xwrite.c ../include/config.h ../include/inn/defines.h \
+ ../include/inn/system.h ../include/clibrary.h ../include/config.h \
+ ../include/libinn.h
+fseeko.o: fseeko.c ../include/config.h ../include/inn/defines.h \
+ ../include/inn/system.h ../include/clibrary.h ../include/config.h
+ftello.o: ftello.c ../include/config.h ../include/inn/defines.h \
+ ../include/inn/system.h ../include/clibrary.h ../include/config.h
+getpagesize.o: getpagesize.c ../include/config.h ../include/inn/defines.h \
+ ../include/inn/system.h
+hstrerror.o: hstrerror.c ../include/config.h ../include/inn/defines.h \
+ ../include/inn/system.h ../include/clibrary.h ../include/config.h
+inet_aton.o: inet_aton.c ../include/config.h ../include/inn/defines.h \
+ ../include/inn/system.h ../include/clibrary.h ../include/config.h
+inet_ntoa.o: inet_ntoa.c ../include/config.h ../include/inn/defines.h \
+ ../include/inn/system.h ../include/clibrary.h ../include/config.h
+memcmp.o: memcmp.c ../include/config.h ../include/inn/defines.h \
+ ../include/inn/system.h
+mkstemp.o: mkstemp.c ../include/config.h ../include/inn/defines.h \
+ ../include/inn/system.h ../include/clibrary.h ../include/config.h \
+ ../include/portable/time.h ../include/config.h
+pread.o: pread.c ../include/config.h ../include/inn/defines.h \
+ ../include/inn/system.h ../include/clibrary.h ../include/config.h
+pwrite.o: pwrite.c ../include/config.h ../include/inn/defines.h \
+ ../include/inn/system.h ../include/clibrary.h ../include/config.h
+setenv.o: setenv.c ../include/config.h ../include/inn/defines.h \
+ ../include/inn/system.h ../include/clibrary.h ../include/config.h
+setproctitle.o: setproctitle.c ../include/config.h \
+ ../include/inn/defines.h ../include/inn/system.h ../include/clibrary.h \
+ ../include/config.h ../include/portable/setproctitle.h \
+ ../include/config.h ../include/inn/messages.h ../include/inn/defines.h
+strcasecmp.o: strcasecmp.c ../include/config.h ../include/inn/defines.h \
+ ../include/inn/system.h ../include/clibrary.h ../include/config.h
+strerror.o: strerror.c ../include/config.h ../include/inn/defines.h \
+ ../include/inn/system.h
+strlcat.o: strlcat.c ../include/config.h ../include/inn/defines.h \
+ ../include/inn/system.h ../include/clibrary.h ../include/config.h
+strlcpy.o: strlcpy.c ../include/config.h ../include/inn/defines.h \
+ ../include/inn/system.h ../include/clibrary.h ../include/config.h
+strspn.o: strspn.c ../include/config.h ../include/inn/defines.h \
+ ../include/inn/system.h ../include/clibrary.h ../include/config.h
+strtok.o: strtok.c ../include/config.h ../include/inn/defines.h \
+ ../include/inn/system.h ../include/clibrary.h ../include/config.h
+perl.o: perl.c ../include/config.h ../include/inn/defines.h \
+ ../include/inn/system.h
--- /dev/null
+/* $Id: buffer.c 5463 2002-05-06 05:40:46Z rra $
+**
+** Counted, reusable memory buffer.
+**
+** A buffer is an allocated bit of memory with a known size and a separate
+** data length. It's intended to store strings and can be reused repeatedly
+** to minimize the number of memory allocations. Buffers increase in
+** increments of 1K.
+**
+** A buffer contains a notion of the data that's been used and the data
+** that's been left, used when the buffer is an I/O buffer where lots of data
+** is buffered and then slowly processed out of the buffer. The total length
+** of the data is used + left. If a buffer is just used to store some data,
+** used can be set to 0 and left stores the length of the data.
+*/
+
+#include "config.h"
+#include "clibrary.h"
+
+#include "inn/buffer.h"
+#include "libinn.h"
+
+/*
+** Allocate a new struct buffer and initialize it.
+*/
+struct buffer *
+buffer_new(void)
+{
+ struct buffer *buffer;
+
+ buffer = xmalloc(sizeof(struct buffer));
+ buffer->size = 0;
+ buffer->used = 0;
+ buffer->left = 0;
+ buffer->data = NULL;
+ return buffer;
+}
+
+
+/*
+** Resize a buffer to be at least as large as the provided second argument.
+** Resize buffers to multiples of 1KB to keep the number of reallocations to
+** a minimum. Refuse to resize a buffer to make it smaller.
+*/
+void
+buffer_resize(struct buffer *buffer, size_t size)
+{
+ if (size < buffer->size)
+ return;
+ buffer->size = (size + 1023) & ~1023UL;
+ buffer->data = xrealloc(buffer->data, buffer->size);
+}
+
+
+/*
+** Replace whatever data is currently in the buffer with the provided data.
+*/
+void
+buffer_set(struct buffer *buffer, const char *data, size_t length)
+{
+ if (length > 0) {
+ buffer_resize(buffer, length);
+ memmove(buffer->data, data, length);
+ }
+ buffer->left = length;
+ buffer->used = 0;
+}
+
+
+/*
+** Append data to a buffer. The new data shows up as additional unused data
+** at the end of the buffer. Resize the buffer to multiples of 1KB.
+*/
+void
+buffer_append(struct buffer *buffer, const char *data, size_t length)
+{
+ size_t total;
+
+ if (length == 0)
+ return;
+ total = buffer->used + buffer->left;
+ buffer_resize(buffer, total + length);
+ buffer->left += length;
+ memcpy(buffer->data + total, data, length);
+}
+
+
+/*
+** Swap the contents of two buffers.
+*/
+void
+buffer_swap(struct buffer *one, struct buffer *two)
+{
+ struct buffer tmp;
+
+ tmp = *one;
+ *one = *two;
+ *two = tmp;
+}
--- /dev/null
+/* $Id: cleanfrom.c 6135 2003-01-19 01:15:40Z rra $
+**
+*/
+
+#include "config.h"
+#include "clibrary.h"
+#include "libinn.h"
+
+
+#define LPAREN '('
+#define RPAREN ')'
+
+
+/*
+** Clean up a from line, making the following transformations:
+** address address
+** address (stuff) address
+** stuff <address> address
+*/
+void HeaderCleanFrom(char *from)
+{
+ char *p;
+ char *end;
+ int len;
+
+ if ((len = strlen(from)) == 0)
+ return;
+ /* concatenate folded header */
+ for (p = end = from ; p < from + len ;) {
+ if (*p == '\n') {
+ if ((p + 1 < from + len) && ISWHITE(p[1])) {
+ if ((p - 1 >= from) && (p[-1] == '\r')) {
+ end--;
+ *end = p[1];
+ p += 2;
+ } else {
+ *end = p[1];
+ p++;
+ }
+ } else {
+ *end = '\0';
+ break;
+ }
+ } else
+ *end++ = *p++;
+ }
+ if (end != from)
+ *end = '\0';
+
+ /* Do pretty much the equivalent of sed's "s/(.*)//g"; */
+ while ((p = strchr(from, LPAREN)) && (end = strchr(p, RPAREN))) {
+ while (*++end)
+ *p++ = *end;
+ *p = '\0';
+ }
+
+ /* Do pretty much the equivalent of sed's "s/\".*\"//g"; */
+ while ((p = strchr(from, '"')) && (end = strchr(p, '"'))) {
+ while (*++end)
+ *p++ = *end;
+ *p = '\0';
+ }
+
+ /* Do the equivalent of sed's "s/.*<\(.*\)>/\1/" */
+ if ((p = strrchr(from, '<')) && (end = strrchr(p, '>'))) {
+ while (++p < end)
+ *from++ = *p;
+ *from = '\0';
+ }
+
+ /* drop white spaces */
+ if ((len = strlen(from)) == 0)
+ return;
+ for (p = end = from ; p < from + len ;) {
+ if (ISWHITE(*p)) {
+ p++;
+ continue;
+ }
+ *end++ = *p++;
+ }
+ if (end != from)
+ *end = '\0';
+}
--- /dev/null
+/* $Id: clientactive.c 6135 2003-01-19 01:15:40Z rra $
+**
+*/
+
+#include "config.h"
+#include "clibrary.h"
+#include <errno.h>
+
+#include "inn/innconf.h"
+#include "libinn.h"
+#include "nntp.h"
+#include "paths.h"
+
+
+static char *CApathname;
+static FILE *CAfp;
+
+
+/*
+** Get a copy of the active file for a client host to use, locally or
+** remotely.
+*/
+FILE *
+CAopen(FILE *FromServer, FILE *ToServer)
+{
+ char *path;
+
+ /* Use a local (or NFS-mounted) copy if available. Make sure we don't
+ * try to delete it when we close it. */
+ path = concatpath(innconf->pathdb, _PATH_CLIENTACTIVE);
+ CAfp = fopen(path, "r");
+ free(path);
+ if (CAfp != NULL) {
+ CApathname = NULL;
+ return CAfp;
+ }
+
+ /* Use the active file from the server */
+ return CAlistopen(FromServer, ToServer, (char *)NULL);
+}
+
+
+/*
+** Internal library routine.
+*/
+FILE *
+CA_listopen(char *pathname, FILE *FromServer, FILE *ToServer,
+ const char *request)
+{
+ char buff[BUFSIZ];
+ char *p;
+ int oerrno;
+ FILE *F;
+
+ F = fopen(pathname, "w");
+ if (F == NULL)
+ return NULL;
+
+ /* Send a LIST command to and capture the output. */
+ if (request == NULL)
+ fprintf(ToServer, "list\r\n");
+ else
+ fprintf(ToServer, "list %s\r\n", request);
+ fflush(ToServer);
+
+ /* Get the server's reply to our command. */
+ if (fgets(buff, sizeof buff, FromServer) == NULL
+ || strncmp(buff, NNTP_LIST_FOLLOWS, strlen(NNTP_LIST_FOLLOWS)) != 0) {
+ oerrno = errno;
+ /* Only call CAclose() if opened through CAopen() */
+ if (strcmp(CApathname, pathname) == 0)
+ CAclose();
+ errno = oerrno;
+ return NULL;
+ }
+
+ /* Slurp up the rest of the response. */
+ while (fgets(buff, sizeof buff, FromServer) != NULL) {
+ if ((p = strchr(buff, '\r')) != NULL)
+ *p = '\0';
+ if ((p = strchr(buff, '\n')) != NULL)
+ *p = '\0';
+ if (buff[0] == '.' && buff[1] == '\0') {
+ if (ferror(F) || fflush(F) == EOF || fclose(F) == EOF)
+ break;
+ return fopen(pathname, "r");
+ }
+ fprintf(F, "%s\n", buff);
+ }
+
+ /* Ran out of input before finding the terminator; quit. */
+ oerrno = errno;
+ fclose(F);
+ CAclose();
+ errno = oerrno;
+ return NULL;
+}
+
+
+/*
+** Use the NNTP list command to get a file from a server. Default is
+** the active file, otherwise ask for whatever is in the request param.
+*/
+FILE *
+CAlistopen(FILE *FromServer, FILE *ToServer, const char *request)
+{
+ int fd, oerrno;
+
+ /* Gotta talk to the server -- see if we can. */
+ if (FromServer == NULL || ToServer == NULL) {
+ errno = EBADF;
+ return NULL;
+ }
+
+ CApathname = concatpath(innconf->pathtmp, _PATH_TEMPACTIVE);
+ fd = mkstemp(CApathname);
+ if (fd < 0) {
+ oerrno = errno;
+ free(CApathname);
+ CApathname = 0;
+ errno = oerrno;
+ return NULL;
+ }
+ close(fd);
+ return CAfp = CA_listopen(CApathname, FromServer, ToServer, request);
+}
+
+
+
+/*
+** Close the file opened by CAopen or CAlistopen.
+*/
+void
+CAclose(void)
+{
+ if (CAfp) {
+ fclose(CAfp);
+ CAfp = NULL;
+ }
+ if (CApathname != NULL) {
+ unlink(CApathname);
+ CApathname = NULL;
+ }
+}
--- /dev/null
+/* $Id: clientlib.c 6155 2003-01-19 19:58:25Z rra $
+**
+** Routines compatible with the NNTP "clientlib" routines.
+*/
+#include "config.h"
+#include "clibrary.h"
+
+#include "inn/innconf.h"
+#include "libinn.h"
+#include "nntp.h"
+
+
+FILE *ser_rd_fp = NULL;
+FILE *ser_wr_fp = NULL;
+char ser_line[NNTP_STRLEN + 2];
+
+
+/*
+** Get the name of the NNTP server. Ignore the filename; we use
+** our own configuration stuff. Return pointer to static data.
+*/
+char *
+getserverbyfile(char *file UNUSED)
+{
+ static char buff[256];
+
+ strlcpy(buff, innconf->server, sizeof(buff));
+ return buff;
+}
+
+
+/*
+** Get a connection to the remote news server. Return server's reply
+** code or -1 on error.
+*/
+int
+server_init(char *host, int port)
+{
+ char line2[NNTP_STRLEN];
+
+ /* This interface may be used by clients that assume C News behavior and
+ won't read inn.conf themselves. */
+ if (innconf == NULL)
+ if (!innconf_read(NULL))
+ return -1;
+
+ if (NNTPconnect(host, port, &ser_rd_fp, &ser_wr_fp, ser_line) < 0) {
+ if (ser_line[0] == '\0')
+ /* I/O problem. */
+ return -1;
+
+ /* Server rejected connection; return it's reply code. */
+ return atoi(ser_line);
+ }
+
+ /* Send the INN command; if understood, use that reply. */
+ put_server("mode reader");
+ if (get_server(line2, (int)sizeof line2) < 0)
+ return -1;
+ if (atoi(line2) != NNTP_BAD_COMMAND_VAL)
+ strlcpy(ser_line, line2, sizeof(ser_line));
+
+ /* Connected; return server's reply code. */
+ return atoi(ser_line);
+}
+
+
+#define CANTPOST \
+ "NOTE: This machine does not have permission to post articles"
+#define CANTUSE \
+ "This machine does not have permission to use the %s news server.\n"
+/*
+** Print a message based on the the server's initial response.
+** Return -1 if server wants us to go away.
+*/
+int
+handle_server_response(int response, char *host)
+{
+ char *p;
+
+ switch (response) {
+ default:
+ printf("Unknown response code %d from %s.\n", response, host);
+ return -1;
+ case NNTP_GOODBYE_VAL:
+ if (atoi(ser_line) == response) {
+ p = &ser_line[strlen(ser_line) - 1];
+ if (*p == '\n' && *--p == '\r')
+ *p = '\0';
+ if (p > &ser_line[3]) {
+ printf("News server %s unavailable: %s\n", host,
+ &ser_line[4]);
+ return -1;
+ }
+ }
+ printf("News server %s unavailable, try later.\n", host);
+ return -1;
+ case NNTP_ACCESS_VAL:
+ printf(CANTUSE, host);
+ return -1;
+ case NNTP_NOPOSTOK_VAL:
+ printf("%s.\n", CANTPOST);
+ /* FALLTHROUGH */
+ case NNTP_POSTOK_VAL:
+ break;
+ }
+ return 0;
+}
+
+
+/*
+** Send a line of text to the server.
+*/
+void
+put_server(const char *buff)
+{
+ fprintf(ser_wr_fp, "%s\r\n", buff);
+ fflush(ser_wr_fp);
+}
+
+
+/*
+** Get a line of text from the server, strip trailing \r\n.
+** Return -1 on error.
+*/
+int
+get_server(char *buff, int buffsize)
+{
+ char *p;
+
+ if (fgets(buff, buffsize, ser_rd_fp) == NULL)
+ return -1;
+ p = &buff[strlen(buff)];
+ if (p >= &buff[2] && p[-2] == '\r' && p[-1] == '\n')
+ p[-2] = '\0';
+ return 0;
+}
+
+
+/*
+** Send QUIT and close the server.
+*/
+void
+close_server(void)
+{
+ char buff[NNTP_STRLEN];
+
+ if (ser_wr_fp != NULL && ser_rd_fp != NULL) {
+ put_server("QUIT");
+ fclose(ser_wr_fp);
+ ser_wr_fp = NULL;
+
+ get_server(buff, (int)sizeof buff);
+ fclose(ser_rd_fp);
+ ser_rd_fp = NULL;
+ }
+}
--- /dev/null
+/* $Id: concat.c 4234 2000-12-21 03:43:02Z rra $
+**
+** Concatenate strings with dynamic memory allocation.
+**
+** Written by Russ Allbery <rra@stanford.edu>
+** This work is hereby placed in the public domain by its author.
+**
+** Usage:
+**
+** string = concat(string1, string2, ..., (char *) 0);
+** path = concatpath(base, name);
+**
+** Dynamically allocates (using xmalloc) sufficient memory to hold all of
+** the strings given and then concatenates them together into that
+** allocated memory, returning a pointer to it. Caller is responsible for
+** freeing. Assumes xmalloc is available. The last argument must be a
+** null pointer (to a char *, if you actually find a platform where it
+** matters).
+**
+** concatpath is similar, except that it only takes two arguments. If the
+** second argument begins with / or ./, a copy of it is returned;
+** otherwise, the first argument, a slash, and the second argument are
+** concatenated together and returned. This is useful for building file
+** names where names that aren't fully qualified are qualified with some
+** particular directory.
+*/
+
+#include "config.h"
+#include "libinn.h"
+
+#include <stdarg.h>
+#if STDC_HEADERS
+# include <string.h>
+#endif
+
+/* Abbreviation for cleaner code. */
+#define VA_NEXT(var, type) ((var) = (type) va_arg(args, type))
+
+/* ANSI C requires at least one named parameter. */
+char *
+concat(const char *first, ...)
+{
+ va_list args;
+ char *result, *p;
+ const char *string;
+ size_t length = 0;
+
+ /* Find the total memory required. */
+ va_start(args, first);
+ for (string = first; string != NULL; VA_NEXT(string, const char *))
+ length += strlen(string);
+ va_end(args);
+ length++;
+
+ /* Create the string. Doing the copy ourselves avoids useless string
+ traversals of result, if using strcat, or string, if using strlen to
+ increment a pointer into result, at the cost of losing the native
+ optimization of strcat if any. */
+ result = xmalloc(length);
+ p = result;
+ va_start(args, first);
+ for (string = first; string != NULL; VA_NEXT(string, const char *))
+ while (*string != '\0')
+ *p++ = *string++;
+ va_end(args);
+ *p = '\0';
+
+ return result;
+}
+
+
+char *
+concatpath(const char *base, const char *name)
+{
+ if (name[0] == '/' || (name[0] == '.' && name[1] == '/'))
+ return xstrdup(name);
+ else
+ return concat(base, "/", name, (char *) 0);
+}
--- /dev/null
+/* $Id: conffile.c 6733 2004-05-16 23:01:23Z rra $
+**
+** Routines for reading in incoming.conf-style config files.
+*/
+
+#include "config.h"
+#include "clibrary.h"
+#include "conffile.h"
+#include "libinn.h"
+
+static int getconfline(CONFFILE *F, char *buffer, int length) {
+ if (F->f) {
+ fgets(buffer, length, F->f);
+ if (ferror(F->f)) {
+ return 1;
+ }
+ } else if (F->array) {
+ strlcpy(buffer, F->array[F->lineno], F->sbuf);
+ }
+ F->lineno++;
+ if (strlen (F->buf) >= F->sbuf - 1) {
+ return 1; /* Line too long */
+ } else {
+ return 0;
+ }
+}
+
+static int cfeof(CONFFILE *F) {
+ if (F->f) {
+ return feof(F->f);
+ } else if (F->array) {
+ return (F->lineno == F->array_len);
+ } else {
+ return 1;
+ }
+}
+
+static char *CONFgetword(CONFFILE *F)
+{
+ char *p;
+ char *s;
+ char *t;
+ char *word;
+ bool flag, comment;
+
+ if (!F) return (NULL); /* No conf file */
+ if (!F->buf || !F->buf[0]) {
+ if (cfeof (F)) return (NULL);
+ if (!F->buf) {
+ F->sbuf = BIG_BUFFER;
+ F->buf = xmalloc(F->sbuf);
+ }
+ if (getconfline(F, F->buf, F->sbuf) != 0)
+ return (NULL); /* Line too long */
+ }
+ do {
+ /* Ignore blank and comment lines. */
+ if ((p = strchr(F->buf, '\n')) != NULL)
+ *p = '\0';
+ for (p = F->buf; *p == ' ' || *p == '\t' ; p++);
+ flag = true;
+ if ((*p == '\0' || *p == '#') && !cfeof(F)) {
+ flag = false;
+ if (getconfline(F, F->buf, F->sbuf))
+ return (NULL); /* Line too long */
+ continue;
+ }
+ break;
+ } while (!cfeof(F) || !flag);
+
+ comment = false;
+ if (*p == '"') { /* double quoted string ? */
+ p++;
+ do {
+ for (t = p; (*t != '"' || (*t == '"' && *(t - 1) == '\\')) &&
+ *t != '\0'; t++);
+ if (*t == '\0') {
+ if (strlen(F->buf) >= F->sbuf - 2)
+ return (NULL); /* Line too long */
+ *t++ = '\n';
+ *t = '\0';
+ if (getconfline(F, t, F->sbuf - strlen(F->buf)))
+ return (NULL); /* Line too long */
+ if ((s = strchr(t, '\n')) != NULL)
+ *s = '\0';
+ }
+ else
+ break;
+ } while (!cfeof(F));
+ if (*t != '"')
+ return (NULL);
+ *t++ = '\0';
+ }
+ else {
+ for (t = p; *t != ' ' && *t != '\t' && *t != '\0'; t++)
+ if (*t == '#' && (t == p || *(t - 1) != '\\')) {
+ comment = true;
+ break;
+ }
+ if (*t != '\0')
+ *t++ = '\0';
+ }
+ if (*p == '\0' && cfeof(F)) return (NULL);
+ word = xstrdup (p);
+ p = F->buf;
+ if (!comment)
+ for (; *t != '\0'; t++)
+ *p++ = *t;
+ *p = '\0';
+
+ return (word);
+}
+
+CONFFILE *CONFfopen(char *filename)
+{
+ FILE *f;
+ CONFFILE *ret;
+
+ f = fopen(filename, "r");
+ if (!f)
+ return(0);
+ ret = xmalloc(sizeof(CONFFILE));
+ if (!ret) {
+ fclose(f);
+ return(0);
+ }
+ ret->filename = xstrdup(filename);
+ ret->buf = 0;
+ ret->sbuf = 0;
+ ret->lineno = 0;
+ ret->f = f;
+ ret->array = NULL;
+ return(ret);
+}
+
+void CONFfclose(CONFFILE *f)
+{
+ if (!f) return; /* No conf file */
+ fclose(f->f);
+ if (f->buf)
+ free(f->buf);
+ if (f->filename)
+ free(f->filename);
+ free(f);
+}
+
+CONFTOKEN *CONFgettoken(CONFTOKEN *toklist, CONFFILE *file)
+{
+ char *word;
+ static CONFTOKEN ret = {CONFstring, 0};
+ int i;
+
+ if (ret.name) {
+ free(ret.name);
+ ret.name = 0;
+ }
+ word = CONFgetword(file);
+ if (!word)
+ return(0);
+ if (toklist) {
+ for (i = 0; toklist[i].type; i++) {
+ if (strcmp(word, toklist[i].name) == 0) {
+ free(word);
+ return(&toklist[i]);
+ }
+ }
+ }
+ ret.name = word;
+ return(&ret);
+}
--- /dev/null
+/* $Id: confparse.c 6135 2003-01-19 01:15:40Z rra $
+**
+** Parse a standard block-structured configuration file syntax.
+**
+** Herein are all the parsing and access functions for the configuration
+** syntax used by INN. See doc/config-* for additional documentation.
+**
+** All entry point functions begin with config_*. config_parse_file is
+** the entry point for most of the work done in this file; all other
+** functions access the parse tree that config_parse_file generates.
+**
+** Functions are named by the structure or basic task they work on:
+**
+** parameter_* config_parameter structs.
+** group_* config_group structs.
+** file_* config_file structs (including all I/O).
+** token_* The guts of the lexer.
+** parse_* The guts of the parser.
+** error_* Error reporting functions.
+** convert_* Converting raw parameter values.
+**
+** Each currently open file is represented by a config_file struct, which
+** contains the current parse state for that file, including the internal
+** buffer, a pointer to where in the buffer the next token starts, and the
+** current token. Inclusion of additional files is handled by maintaining a
+** stack of config_file structs, so when one file is finished, the top struct
+** popped off the stack and parsing continues where it left off.
+**
+** Since config_file structs contain the parse state, they're passed as an
+** argument to most functions.
+**
+** A config_file struct contains a token struct, representing the current
+** token. The configuration file syntax is specifically designed to never
+** require lookahead to parse; all parse decisions can be made on the basis
+** of the current state and a single token. A token consists of a type and
+** an optional attached string. Note that strings are allocated by the lexer
+** but are never freed by the lexer! Any token with an associated string
+** should have that string copied into permanent storage (like the params
+** hash of a config_group) or freed. error_unexpected_token will do the
+** latter.
+**
+** Errors in the lexer are indicated by setting the token to TOKEN_ERROR.
+** All parsing errors are indicated by setting the error flag in the current
+** config_file struct. Error recovery is *not* implemented by the current
+** algorithm; it would add a lot of complexity to the parsing algorithm and
+** the results still probably shouldn't be used by the calling program, so it
+** would only be useful to catch more than one syntax error per invocation
+** and it isn't expected that syntax errors will be that common. Instead, if
+** something fails to parse, the whole parser unwinds and returns failure.
+**
+** The config_param_* functions are used to retrieve the values of
+** parameters; each use a convert_* function to convert the raw parameter
+** value to the type specified by the user. group_parameter_get can
+** therefore be the same for all parameter types, with all of the variations
+** encapsulated in the convert_* functions.
+*/
+
+#include "config.h"
+#include "clibrary.h"
+#include <errno.h>
+#include <fcntl.h>
+
+#include "inn/confparse.h"
+#include "inn/hashtab.h"
+#include "inn/messages.h"
+#include "inn/vector.h"
+#include "libinn.h"
+
+
+/* The types of tokens seen in configuration files. */
+enum token_type {
+ TOKEN_CRLF,
+ TOKEN_STRING,
+ TOKEN_QSTRING,
+ TOKEN_PARAM,
+ TOKEN_LBRACE,
+ TOKEN_RBRACE,
+ TOKEN_LANGLE,
+ TOKEN_RANGLE,
+ TOKEN_LBRACKET,
+ TOKEN_RBRACKET,
+ TOKEN_SEMICOLON,
+ TOKEN_EOF,
+ TOKEN_ERROR
+};
+
+/* The parse status of a file. Variables marked internal are only used by
+ file_* functions; other functions don't need to look at them. Other
+ variables are marked by what functions are responsible for maintaining
+ them. */
+struct config_file {
+ int fd; /* Internal */
+ char *buffer; /* Internal */
+ size_t bufsize; /* Internal */
+ const char *filename; /* file_open */
+ unsigned int line; /* token_newline and token_quoted_string */
+ bool error; /* Everyone */
+
+ /* Set by file_* and token_*. current == NULL indicates we've not yet
+ read from the file. */
+ char *current;
+
+ /* Normally set by token_*, but file_read and file_read_more may set token
+ to TOKEN_ERROR or TOKEN_EOF when those conditions are encountered. In
+ that situation, they also return false. */
+ struct {
+ enum token_type type;
+ char *string;
+ } token;
+};
+
+/* The types of parameters, used to distinguish the values of the union in the
+ config_parameter_s struct. */
+enum value_type {
+ VALUE_UNKNOWN,
+ VALUE_BOOL,
+ VALUE_INTEGER,
+ VALUE_NUMBER,
+ VALUE_STRING,
+ VALUE_LIST,
+ VALUE_INVALID
+};
+
+/* Each setting is represented by one of these structs, stored in the params
+ hash of a config group. Since all of a config_group must be in the same
+ file (either group->file for regular groups or group->included for groups
+ whose definition is in an included file), we don't have to stash a file
+ name here for error reporting but can instead get that from the enclosing
+ group. */
+struct config_parameter {
+ char *key;
+ char *raw_value;
+ unsigned int line; /* For error reporting. */
+ enum value_type type;
+ union {
+ bool boolean;
+ long integer;
+ double number;
+ char *string;
+ struct vector *list;
+ } value;
+};
+
+/* The type of a function that converts a raw parameter value to some other
+ data type, storing the result in its second argument and returning true on
+ success or false on failure. */
+typedef bool (*convert_func)(struct config_parameter *, const char *, void *);
+
+/* The basic element of configuration data, a group of parameters. This is
+ the only struct that is exposed to callers, and then only as an opaque
+ data structure. */
+struct config_group {
+ char *type;
+ char *tag;
+ char *file; /* File in which the group starts. */
+ unsigned int line; /* Line number where the group starts. */
+ char *included; /* For group <file>, the included file. */
+ struct hash *params;
+
+ struct config_group *parent;
+ struct config_group *child;
+ struct config_group *next;
+};
+
+
+/* Parameter handling, used by the hash table stored in a config_group. */
+static const void *parameter_key(const void *p);
+static bool parameter_equal(const void *k, const void *p);
+static void parameter_free(void *p);
+
+/* Hash traversal function to collect parameters into a vector. */
+static void parameter_collect(void *, void *);
+
+/* Group handling. */
+static struct config_group *group_new(const char *file, unsigned int line,
+ const char *type, const char *tag);
+static void group_free(struct config_group *);
+static bool group_parameter_get(struct config_group *group, const char *key,
+ void *result, convert_func convert);
+
+/* Parameter type conversion functions. All take the parameter, the file, and
+ a pointer to where the result can be placed. */
+static bool convert_boolean(struct config_parameter *, const char *, void *);
+static bool convert_integer(struct config_parameter *, const char *, void *);
+static bool convert_string(struct config_parameter *, const char *, void *);
+
+/* File I/O. Many other functions also manipulate config_file structs; see
+ the struct definition for notes on who's responsible for what. */
+static struct config_file *file_open(const char *filename);
+static bool file_read(struct config_file *);
+static bool file_read_more(struct config_file *, ptrdiff_t offset);
+static void file_close(struct config_file *);
+
+/* The basic lexer function. The token is stashed in file; the return value
+ is just for convenience and duplicates that information. */
+static enum token_type token_next(struct config_file *);
+
+/* Handler functions for specific types of tokens. These should only be
+ called by token_next. */
+static void token_simple(struct config_file *, enum token_type type);
+static void token_newline(struct config_file *);
+static void token_string(struct config_file *);
+static void token_quoted_string(struct config_file *);
+
+/* Handles whitespace for the rest of the lexer. */
+static bool token_skip_whitespace(struct config_file *);
+
+/* Handles comments for the rest of the lexer. */
+static bool token_skip_comment(struct config_file *);
+
+/* Parser functions to parse the named syntactic element. */
+static bool parse_group_contents(struct config_group *, struct config_file *);
+static enum token_type parse_parameter(struct config_group *,
+ struct config_file *, char *key);
+
+/* Error reporting functions. */
+static void error_bad_unquoted_char(struct config_file *, char bad);
+static void error_unexpected_token(struct config_file *,
+ const char *expecting);
+
+
+/*
+** Return the key from a parameter struct, used by the hash table.
+*/
+static const void *
+parameter_key(const void *p)
+{
+ const struct config_parameter *param = p;
+
+ return param->key;
+}
+
+
+/*
+** Check to see if a provided key matches the key of a parameter struct,
+** used by the hash table.
+*/
+static bool
+parameter_equal(const void *k, const void *p)
+{
+ const char *key = k;
+ const struct config_parameter *param = p;
+
+ return strcmp(key, param->key) == 0;
+}
+
+
+/*
+** Free a parameter, used by the hash table.
+*/
+static void
+parameter_free(void *p)
+{
+ struct config_parameter *param = p;
+
+ free(param->key);
+ free(param->raw_value);
+ if (param->type == VALUE_STRING) {
+ free(param->value.string);
+ } else if (param->type == VALUE_LIST) {
+ vector_free(param->value.list);
+ }
+ free(param);
+}
+
+
+/*
+** Report an unexpected character while parsing a regular string and set the
+** current token type to TOKEN_ERROR.
+*/
+static void
+error_bad_unquoted_char(struct config_file *file, char bad)
+{
+ warn("%s:%u: invalid character '%c' in unquoted string", file->filename,
+ file->line, bad);
+ file->token.type = TOKEN_ERROR;
+ file->error = true;
+}
+
+
+/*
+** Report an unexpected token. If the token is TOKEN_ERROR, don't print an
+** additional error message. Takes a string saying what token was expected.
+** Sets the token to TOKEN_ERROR and frees the associated string if the
+** current token type is TOKEN_STRING, TOKEN_QSTRING, or TOKEN_PARAM.
+*/
+static void
+error_unexpected_token(struct config_file *file, const char *expecting)
+{
+ const char *name;
+ bool string = false;
+
+ /* If the bad token type is a string, param, or quoted string, free the
+ string associated with the token to avoid a memory leak. */
+ if (file->token.type != TOKEN_ERROR) {
+ switch (file->token.type) {
+ case TOKEN_STRING: name = "string"; string = true; break;
+ case TOKEN_QSTRING: name = "quoted string"; string = true; break;
+ case TOKEN_PARAM: name = "parameter"; string = true; break;
+ case TOKEN_CRLF: name = "end of line"; break;
+ case TOKEN_LBRACE: name = "'{'"; break;
+ case TOKEN_RBRACE: name = "'}'"; break;
+ case TOKEN_LANGLE: name = "'<'"; break;
+ case TOKEN_RANGLE: name = "'>'"; break;
+ case TOKEN_LBRACKET: name = "'['"; break;
+ case TOKEN_RBRACKET: name = "']'"; break;
+ case TOKEN_SEMICOLON: name = "';'"; break;
+ case TOKEN_EOF: name = "end of file"; break;
+ default: name = "unknown token"; break;
+ }
+ warn("%s:%u: parse error: saw %s, expecting %s", file->filename,
+ file->line, name, expecting);
+ }
+ if (string) {
+ free(file->token.string);
+ file->token.string = NULL;
+ }
+ file->token.type = TOKEN_ERROR;
+ file->error = true;
+}
+
+
+/*
+** Handle a simple token (a single character), advancing the file->current
+** pointer past it and setting file->token as appropriate.
+*/
+static void
+token_simple(struct config_file *file, enum token_type type)
+{
+ file->current++;
+ file->token.type = type;
+ file->token.string = NULL;
+}
+
+
+/*
+** Handle a newline. Skip any number of comments after the newline,
+** including reading more data from the file if necessary, and update
+** file->line as needed.
+*/
+static void
+token_newline(struct config_file *file)
+{
+ /* If we're actually positioned on a newline, update file->line and skip
+ over it. Try to handle CRLF correctly, as a single line terminator
+ that only increments the line count once, while still treating either
+ CR or LF alone as line terminators in their own regard. */
+ if (*file->current == '\n') {
+ file->current++;
+ file->line++;
+ } else if (*file->current == '\r') {
+ if (file->current[1] == '\n')
+ file->current += 2;
+ else if (file->current[1] != '\0')
+ file->current++;
+ else {
+ if (!file_read(file)) {
+ file->current++;
+ return;
+ }
+ if (*file->current == '\n')
+ file->current++;
+ }
+ file->line++;
+ }
+
+ if (!token_skip_whitespace(file))
+ return;
+ while (*file->current == '#') {
+ if (!token_skip_comment(file))
+ return;
+ if (!token_skip_whitespace(file))
+ return;
+ }
+ file->token.type = TOKEN_CRLF;
+ file->token.string = NULL;
+}
+
+
+/*
+** Handle a string. Only some characters are allowed in an unquoted string;
+** check that, since otherwise it could hide syntax errors. Any whitespace
+** ends the token. We have to distinguish between TOKEN_PARAM and
+** TOKEN_STRING; the former ends in a colon, unlike the latter.
+*/
+static void
+token_string(struct config_file *file)
+{
+ int i;
+ bool status;
+ ptrdiff_t offset;
+ bool done = false;
+ bool colon = false;
+
+ /* Use an offset from file->current rather than a pointer that moves
+ through the buffer, since the base of file->current can change during a
+ file_read_more() call and we don't want to have to readjust a
+ pointer. If we have to read more, adjust our counter back one
+ character, since the nul was replaced by a new, valid character. */
+ i = 0;
+ while (!done) {
+ switch (file->current[i]) {
+ case '\t': case '\r': case '\n': case ' ': case ';':
+ done = true;
+ break;
+ case '"': case '<': case '>': case '[':
+ case '\\': case ']': case '{': case '}':
+ error_bad_unquoted_char(file, file->current[i]);
+ return;
+ case ':':
+ if (colon) {
+ error_bad_unquoted_char(file, file->current[i]);
+ return;
+ }
+ colon = true;
+ break;
+ case '\0':
+ offset = file->current - file->buffer;
+ status = file_read_more(file, offset);
+ if (status)
+ i--;
+ else
+ done = true;
+ break;
+ default:
+ if (colon) {
+ error_bad_unquoted_char(file, ':');
+ return;
+ }
+ }
+ if (!done)
+ i++;
+ }
+ file->token.type = colon ? TOKEN_PARAM : TOKEN_STRING;
+ file->token.string = xstrndup(file->current, i - colon);
+ file->current += i;
+}
+
+
+/*
+** Handle a quoted string. This token is unique as the only token that can
+** contain whitespace, even newlines if they're escaped, so we also have to
+** update file->line as we go. Note that the quotes *are* included in the
+** string we stash in file->token, since they should be part of the raw_value
+** of a parameter.
+*/
+static void
+token_quoted_string(struct config_file *file)
+{
+ int i;
+ ptrdiff_t offset;
+ bool status;
+ bool done = false;
+
+ /* Use an offset from file->current rather than a pointer that moves
+ through the buffer, since the base of file->current can change during a
+ file_read_more() call and we don't want to have to readjust a pointer.
+ If we have to read more, adjust our counter back one character, since
+ the nul was replaced by a new, valid character. */
+ for (i = 1; !done; i++) {
+ switch (file->current[i]) {
+ case '"':
+ done = true;
+ break;
+ case '\r':
+ case '\n':
+ warn("%s:%u: no close quote seen for quoted string",
+ file->filename, file->line);
+ file->token.type = TOKEN_ERROR;
+ file->error = true;
+ return;
+ case '\\':
+ i++;
+ if (file->current[i] == '\n')
+ file->line++;
+
+ /* CRLF should count as one line terminator. Handle most cases of
+ that here, but the case where CR is at the end of one buffer
+ and LF at the beginning of the next has to be handled in the \0
+ case below. */
+ if (file->current[i] == '\r') {
+ file->line++;
+ if (file->current[i + 1] == '\n')
+ i++;
+ }
+ break;
+ case '\0':
+ offset = file->current - file->buffer;
+ status = file_read_more(file, offset);
+ if (status)
+ i--;
+ else {
+ warn("%s:%u: end of file encountered while parsing quoted"
+ " string", file->filename, file->line);
+ file->token.type = TOKEN_ERROR;
+ file->error = true;
+ return;
+ }
+
+ /* If the last character of the previous buffer was CR and the
+ first character that we just read was LF, the CR must have been
+ escaped which means that the LF is part of it, forming a CRLF
+ line terminator. Skip over the LF. */
+ if (file->current[i] == '\r' && file->current[i + 1] == '\n')
+ i++;
+
+ break;
+ default:
+ break;
+ }
+ }
+ file->token.type = TOKEN_QSTRING;
+ file->token.string = xstrndup(file->current, i);
+ file->current += i;
+}
+
+
+/*
+** Skip over a comment line at file->current, reading more data as necessary.
+** Stop when an end of line is encountered, positioning file->current
+** directly after the end of line. Returns false on end of file or a read
+** error, true otherwise.
+*/
+static bool
+token_skip_comment(struct config_file *file)
+{
+ char *p = file->current;
+
+ while (*p != '\0' && *p != '\n' && *p != '\r')
+ p++;
+ while (*p == '\0') {
+ if (!file_read(file))
+ return false;
+ p = file->current;
+ while (*p != '\0' && *p != '\n' && *p != '\r')
+ p++;
+ }
+
+ /* CRLF should count as a single line terminator, but it may be split
+ across a read boundary. Try to handle that case correctly. */
+ if (*p == '\n')
+ p++;
+ else if (*p == '\r') {
+ p++;
+ if (*p == '\n')
+ p++;
+ else if (*p == '\0') {
+ if (!file_read(file))
+ return false;
+ p = file->current;
+ if (*p == '\n')
+ p++;
+ }
+ }
+ file->current = p;
+ file->line++;
+ return true;
+}
+
+/*
+** Skip over all whitespace at file->current, reading more data as
+** necessary. Stop when the first non-whitespace character is encountered or
+** at end of file, leaving file->current pointing appropriately. Returns
+** true if non-whitespace is found and false on end of file or a read error.
+*/
+static bool
+token_skip_whitespace(struct config_file *file)
+{
+ char *p = file->current;
+
+ while (*p == ' ' || *p == '\t')
+ p++;
+ while (*p == '\0') {
+ if (!file_read(file))
+ return false;
+ p = file->current;
+ while (*p == ' ' || *p == '\t')
+ p++;
+ }
+ file->current = p;
+ return true;
+}
+
+
+/*
+** The basic lexer function. Read the next token from a configuration file.
+** Returns the token, which is also stored in file. Lexer failures set the
+** token to TOKEN_ERROR.
+*/
+static enum token_type
+token_next(struct config_file *file)
+{
+ /* If file->current is NULL, we've never read from the file. There is
+ special handling for a comment at the very beginning of a file, since
+ normally we only look for comments after newline tokens.
+
+ If we do see a # at the beginning of the first line, let token_newline
+ deal with it. That function can cope with file->current not pointing
+ at a newline. We then return the newline token as the first token in
+ the file. */
+ if (file->current == NULL) {
+ if (!file_read(file))
+ return file->token.type;
+ if (!token_skip_whitespace(file))
+ return file->token.type;
+ if (*file->current == '#') {
+ token_newline(file);
+ return file->token.type;
+ }
+ } else {
+ if (!token_skip_whitespace(file))
+ return file->token.type;
+ }
+
+ /* Almost all of our tokens can be recognized by the first character; the
+ only exception is telling strings from parameters. token_string
+ handles both of those and sets file->token.type appropriately.
+ Comments are handled by token_newline. */
+ switch (*file->current) {
+ case '{': token_simple(file, TOKEN_LBRACE); break;
+ case '}': token_simple(file, TOKEN_RBRACE); break;
+ case '<': token_simple(file, TOKEN_LANGLE); break;
+ case '>': token_simple(file, TOKEN_RANGLE); break;
+ case '[': token_simple(file, TOKEN_LBRACKET); break;
+ case ']': token_simple(file, TOKEN_RBRACKET); break;
+ case ';': token_simple(file, TOKEN_SEMICOLON); break;
+ case '\r': token_newline(file); break;
+ case '\n': token_newline(file); break;
+ case '"': token_quoted_string(file); break;
+ default: token_string(file); break;
+ }
+
+ return file->token.type;
+}
+
+
+/*
+** Open a new configuration file and return config_file representing the
+** parse state of that file. We assume that we don't have to make a copy of
+** the filename argument. Default to stdio BUFSIZ for our buffer size, since
+** it's generally reasonably chosen with respect to disk block sizes, memory
+** consumption, and the like.
+*/
+static struct config_file *
+file_open(const char *filename)
+{
+ struct config_file *file;
+
+ file = xmalloc(sizeof(*file));
+ file->filename = filename;
+ file->fd = open(filename, O_RDONLY);
+ if (file->fd < 0) {
+ free(file);
+ return NULL;
+ }
+ file->buffer = xmalloc(BUFSIZ);
+ file->bufsize = BUFSIZ;
+ file->current = NULL;
+ file->line = 1;
+ file->token.type = TOKEN_ERROR;
+ file->error = false;
+ return file;
+}
+
+
+/*
+** Read some data from a configuration file, handling errors (by reporting
+** them with warn) and returning true if there's data left and false on EOF
+** or a read error.
+*/
+static bool
+file_read(struct config_file *file)
+{
+ ssize_t status;
+
+ status = read(file->fd, file->buffer, file->bufsize - 1);
+ if (status < 0) {
+ syswarn("%s: read error", file->filename);
+ file->token.type = TOKEN_ERROR;
+ file->error = true;
+ } else if (status == 0) {
+ file->token.type = TOKEN_EOF;
+ }
+ if (status <= 0)
+ return false;
+ file->buffer[status] = '\0';
+ file->current = file->buffer;
+
+ /* Reject nuls, since otherwise they would cause strange problems. */
+ if (strlen(file->buffer) != (size_t) status) {
+ warn("%s: invalid NUL character found in file", file->filename);
+ return false;
+ }
+ return true;
+}
+
+
+/*
+** Read additional data from a configuration file when there's some partial
+** data in the buffer already that we want to save. Takes the config_file
+** struct and an offset from file->buffer specifying the start of the data
+** that we want to preserve. Resizes the buffer if offset is 0. Returns
+** false on EOF or a read error, true otherwise.
+*/
+static bool
+file_read_more(struct config_file *file, ptrdiff_t offset)
+{
+ char *start;
+ size_t amount;
+ ssize_t status;
+
+ if (offset > 0) {
+ size_t left;
+
+ left = file->bufsize - offset - 1;
+ memmove(file->buffer, file->buffer + offset, left);
+ file->current -= offset;
+ start = file->buffer + left;
+ amount = offset;
+ } else {
+ file->buffer = xrealloc(file->buffer, file->bufsize + BUFSIZ);
+ file->current = file->buffer;
+ start = file->buffer + file->bufsize - 1;
+ amount = BUFSIZ;
+ file->bufsize += BUFSIZ;
+ }
+ status = read(file->fd, start, amount);
+ if (status < 0)
+ syswarn("%s: read error", file->filename);
+ if (status <= 0)
+ return false;
+ start[status] = '\0';
+
+ /* Reject nuls, since otherwise they would cause strange problems. */
+ if (strlen(start) != (size_t) status) {
+ warn("%s: invalid NUL character found in file", file->filename);
+ return false;
+ }
+ return true;
+}
+
+
+/*
+** Close a file and free the resources associated with it.
+*/
+static void
+file_close(struct config_file *file)
+{
+ close(file->fd);
+ free(file->buffer);
+ free(file);
+}
+
+
+/*
+** Given a config_group with the type and tag already filled in and a
+** config_file with the buffer positioned after the opening brace of the
+** group, read and add parameters to the group until encountering a close
+** brace. Returns true on a successful parse, false on an error that
+** indicates the group should be discarded.
+*/
+static bool
+parse_group_contents(struct config_group *group, struct config_file *file)
+{
+ enum token_type token;
+
+ token = token_next(file);
+ while (!file->error) {
+ switch (token) {
+ case TOKEN_PARAM:
+ token = parse_parameter(group, file, file->token.string);
+ while (token == TOKEN_CRLF || token == TOKEN_SEMICOLON)
+ token = token_next(file);
+ break;
+ case TOKEN_CRLF:
+ token = token_next(file);
+ break;
+ case TOKEN_EOF:
+ return true;
+ default:
+ error_unexpected_token(file, "parameter");
+ break;
+ }
+ }
+ return false;
+}
+
+
+/*
+** Parse a parameter. Takes the group we're currently inside, the
+** config_file parse state, and the key of the parameter. Returns the next
+** token after the parameter, and also checks to make sure that it's
+** something legal (end of line, end of file, or a semicolon).
+*/
+static enum token_type
+parse_parameter(struct config_group *group, struct config_file *file,
+ char *key)
+{
+ enum token_type token;
+
+ token = token_next(file);
+ if (token == TOKEN_STRING || token == TOKEN_QSTRING) {
+ struct config_parameter *param;
+ unsigned int line;
+ char *value;
+
+ /* Before storing the parameter, check to make sure that the next
+ token is valid. If it isn't, chances are high that the user has
+ tried to set a parameter to a value containing spaces without
+ quoting the value. */
+ value = file->token.string;
+ line = file->line;
+ token = token_next(file);
+ switch (token) {
+ default:
+ error_unexpected_token(file, "semicolon or newline");
+ free(value);
+ break;
+ case TOKEN_CRLF:
+ case TOKEN_SEMICOLON:
+ case TOKEN_EOF:
+ param = xmalloc(sizeof(*param));
+ param->key = key;
+ param->raw_value = value;
+ param->type = VALUE_UNKNOWN;
+ param->line = line;
+ if (!hash_insert(group->params, key, param)) {
+ warn("%s:%u: duplicate parameter %s", file->filename, line,
+ key);
+ free(param->raw_value);
+ free(param->key);
+ free(param);
+ }
+ return token;
+ }
+ } else {
+ error_unexpected_token(file, "parameter value");
+ }
+
+ /* If we fell through, we encountered some sort of error. Free allocated
+ memory and return an error token. */
+ free(key);
+ return TOKEN_ERROR;
+}
+
+
+/*
+** Allocate a new config_group and set the initial values of all of the
+** struct members.
+*/
+static struct config_group *
+group_new(const char *file, unsigned int line, const char *type,
+ const char *tag)
+{
+ struct config_group *group;
+
+ group = xmalloc(sizeof(*group));
+ group->type = xstrdup(type);
+ group->tag = (tag == NULL) ? NULL : xstrdup(tag);
+ group->file = xstrdup(file);
+ group->included = NULL;
+ group->line = line;
+ group->params = hash_create(4, hash_string, parameter_key,
+ parameter_equal, parameter_free);
+ group->parent = NULL;
+ group->child = NULL;
+ group->next = NULL;
+ return group;
+}
+
+
+/*
+** Free a config_group and all associated storage.
+*/
+static void
+group_free(struct config_group *group)
+{
+ free(group->type);
+ if (group->tag != NULL)
+ free(group->tag);
+ free(group->file);
+ if (group->included != NULL)
+ free(group->included);
+ hash_free(group->params);
+ free(group);
+}
+
+
+/*
+** Accessor function for the group type.
+*/
+const char *
+config_group_type(struct config_group *group)
+{
+ return group->type;
+}
+
+
+/*
+** Accessor function for the group tag.
+*/
+const char *
+config_group_tag(struct config_group *group)
+{
+ return group->tag;
+}
+
+
+/*
+** Parse a configuration file, returning the config_group that's the root of
+** the tree represented by that file (and any other files that it includes).
+** Returns NULL on a parse failure.
+*/
+struct config_group *
+config_parse_file(const char *filename, ...)
+{
+ struct config_group *group;
+ struct config_file *file;
+ bool success;
+
+ file = file_open(filename);
+ if (file == NULL) {
+ syswarn("open of %s failed", filename);
+ return NULL;
+ }
+ group = group_new(filename, 1, "GLOBAL", NULL);
+ success = parse_group_contents(group, file);
+ file_close(file);
+ return success ? group : NULL;
+}
+
+
+/*
+** Given a config_group representing the root of a configuration structure,
+** recursively free the entire structure.
+*/
+void
+config_free(struct config_group *group)
+{
+ group_free(group);
+}
+
+
+/*
+** Convert a given parameter value to a boolean, returning true if successful
+** and false otherwise.
+*/
+static bool
+convert_boolean(struct config_parameter *param, const char *file,
+ void *result)
+{
+ static const char *const truevals[] = { "yes", "on", "true", NULL };
+ static const char *const falsevals[] = { "no", "off", "false", NULL };
+ bool *value = result;
+ int i;
+
+ if (param->type == VALUE_BOOL) {
+ *value = param->value.boolean;
+ return true;
+ } else if (param->type != VALUE_UNKNOWN) {
+ warn("%s:%u: %s is not a boolean", file, param->line, param->key);
+ return false;
+ }
+ param->type = VALUE_BOOL;
+ for (i = 0; truevals[i] != NULL; i++)
+ if (strcmp(param->raw_value, truevals[i]) == 0) {
+ param->value.boolean = true;
+ *value = true;
+ return true;
+ }
+ for (i = 0; falsevals[i] != NULL; i++)
+ if (strcmp(param->raw_value, falsevals[i]) == 0) {
+ param->value.boolean = false;
+ *value = false;
+ return true;
+ }
+ param->type = VALUE_INVALID;
+ warn("%s:%u: %s is not a boolean", file, param->line, param->key);
+ return false;
+}
+
+
+/*
+** Convert a given parameter value to an integer, returning true if
+** successful and false otherwise.
+*/
+static bool
+convert_integer(struct config_parameter *param, const char *file,
+ void *result)
+{
+ long *value = result;
+ char *p;
+
+ if (param->type == VALUE_INTEGER) {
+ *value = param->value.integer;
+ return true;
+ } else if (param->type != VALUE_UNKNOWN) {
+ warn("%s:%u: %s is not an integer", file, param->line, param->key);
+ return false;
+ }
+
+ /* Do a syntax check even though strtol would do some of this for us,
+ since otherwise some syntax errors may go silently undetected. */
+ p = param->raw_value;
+ if (*p == '-')
+ p++;
+ for (; *p != '\0'; p++)
+ if (*p < '0' || *p > '9')
+ break;
+ if (*p != '\0') {
+ warn("%s:%u: %s is not an integer", file, param->line, param->key);
+ return false;
+ }
+
+ /* Do the actual conversion with strtol. */
+ errno = 0;
+ param->value.integer = strtol(param->raw_value, NULL, 10);
+ if (errno != 0) {
+ warn("%s:%u: %s doesn't convert to an integer", file, param->line,
+ param->key);
+ return false;
+ }
+ *value = param->value.integer;
+ param->type = VALUE_INTEGER;
+ return true;
+}
+
+
+/*
+** Convert a parameter value to a string, interpreting it as a quoted string,
+** and returning true if successful and false otherwise. Does none of the
+** initial type checking, since convert_string should have already done that.
+*/
+static bool
+convert_string_quoted(struct config_parameter *param, const char *file,
+ void *result)
+{
+ const char **value = result;
+ size_t length;
+ char *src, *dest;
+
+ length = strlen(param->raw_value) - 2;
+ param->value.string = xmalloc(length + 1);
+ src = param->raw_value + 1;
+ dest = param->value.string;
+ for (; *src != '"' && *src != '\0'; src++) {
+ if (*src != '\\') {
+ *dest++ = *src;
+ } else {
+ src++;
+
+ /* This should implement precisely the semantics of backslash
+ escapes in quoted strings in C. */
+ switch (*src) {
+ case 'a': *dest++ = '\a'; break;
+ case 'b': *dest++ = '\b'; break;
+ case 'f': *dest++ = '\f'; break;
+ case 'n': *dest++ = '\n'; break;
+ case 'r': *dest++ = '\r'; break;
+ case 't': *dest++ = '\t'; break;
+ case 'v': *dest++ = '\v'; break;
+
+ case '\n': break; /* Escaped newlines disappear. */
+
+ case '\\':
+ case '\'':
+ case '"':
+ case '?':
+ *dest++ = *src;
+ break;
+
+ case '\0':
+ /* Should never happen; the tokenizer should catch this. */
+ warn("%s:%u: unterminated string", file, param->line);
+ goto fail;
+
+ default:
+ /* FIXME: \<octal>, \x, \u, and \U not yet implemented; the
+ last three could use the same basic code. Think about
+ whether the escape should generate a single 8-bit character
+ or a UTF-8 encoded character; maybe the first two generate
+ the former and \u and \U generate the latter? */
+ warn("%s:%u: unrecognized escape '\\%c'", file, param->line,
+ *src);
+ goto fail;
+ }
+ }
+ }
+ *dest = '\0';
+
+ /* The tokenizer already checked this for most cases but could miss the
+ case where the final quote mark is escaped with a backslash. */
+ if (*src != '"') {
+ warn("%s:%u: unterminated string (no closing quote)", file,
+ param->line);
+ goto fail;
+ }
+
+ param->type = VALUE_STRING;
+ *value = param->value.string;
+ return true;
+
+ fail:
+ free(param->value.string);
+ return false;
+}
+
+
+/*
+** Convert a given parameter value to a string, returning true if successful
+** and false otherwise.
+*/
+static bool
+convert_string(struct config_parameter *param, const char *file, void *result)
+{
+ const char **value = result;
+
+ if (param->type == VALUE_STRING) {
+ *value = param->value.string;
+ return true;
+ } else if (param->type != VALUE_UNKNOWN) {
+ warn("%s:%u: %s is not an string", file, param->line, param->key);
+ return false;
+ }
+
+ if (*param->raw_value == '"') {
+ return convert_string_quoted(param, file, result);
+ } else {
+ param->value.string = xstrdup(param->raw_value);
+ param->type = VALUE_STRING;
+ *value = param->value.string;
+ return true;
+ }
+}
+
+
+/*
+** Given a group, query it for the given parameter and then when the
+** parameter is found, check to see if it's already marked invalid. If so,
+** fail quietly; otherwise, hand it off to the conversion function to do
+** type-specific work, returning the result. Returns true if the parameter
+** is found in the group or one of its parents and convert can successfully
+** convert the raw value and put it in result, false otherwise (either for
+** the parameter not being found or for it being the wrong type).
+*/
+static bool
+group_parameter_get(struct config_group *group, const char *key, void *result,
+ convert_func convert)
+{
+ struct config_group *current = group;
+
+ while (current != NULL) {
+ struct config_parameter *param;
+
+ param = hash_lookup(group->params, key);
+ if (param != NULL) {
+ if (param->type == VALUE_INVALID)
+ return false;
+ else
+ return (*convert)(param, group->file, result);
+ }
+ current = group->parent;
+ }
+ return false;
+}
+
+
+/*
+** All of the config_param_* functions do the following:
+**
+** Given a group, query it for the given parameter, interpreting its value as
+** the appropriate type and returning it in the third argument. Returns true
+** on success, false on failure (such as the parameter not being set or an
+** error), and report errors via warn.
+*/
+bool
+config_param_boolean(struct config_group *group, const char *key,
+ bool *result)
+{
+ return group_parameter_get(group, key, result, convert_boolean);
+}
+
+bool
+config_param_integer(struct config_group *group, const char *key,
+ long *result)
+{
+ return group_parameter_get(group, key, result, convert_integer);
+}
+
+bool
+config_param_string(struct config_group *group, const char *key,
+ const char **result)
+{
+ return group_parameter_get(group, key, result, convert_string);
+}
+
+
+/*
+** A hash traversal function to add all parameter keys to the vector provided
+** as the second argument.
+*/
+static void
+parameter_collect(void *element, void *cookie)
+{
+ struct config_parameter *param = element;
+ struct vector *params = cookie;
+
+ vector_add(params, param->key);
+}
+
+
+/*
+** Returns a newly allocated vector of all of the config parameters in a
+** group, including the inherited ones (not implemented yet).
+*/
+struct vector *
+config_params(struct config_group *group)
+{
+ struct vector *params;
+ size_t size;
+
+ /* Size the vector, which we can do accurately for now. */
+ params = vector_new();
+ size = hash_count(group->params);
+ vector_resize(params, size);
+
+ /* Now, walk the hash to build the vector of params. */
+ hash_traverse(group->params, parameter_collect, params);
+ return params;
+}
+
+
+/*
+** Report an error in a given parameter. Used so that the file and line
+** number can be included in the error message.
+*/
+void
+config_error_param(struct config_group *group, const char *key,
+ const char *fmt, ...)
+{
+ va_list args;
+ ssize_t length;
+ char *message, *file;
+ struct config_parameter *param;
+
+ va_start(args, fmt);
+ length = vsnprintf(NULL, 0, fmt, args);
+ va_end(args);
+ if (length < 0)
+ return;
+ message = xmalloc(length + 1);
+ va_start(args, fmt);
+ vsnprintf(message, length + 1, fmt, args);
+ va_end(args);
+
+ param = hash_lookup(group->params, key);
+ if (param == NULL)
+ warn("%s", message);
+ else {
+ file = (group->included != NULL ? group->included : group->file);
+ warn("%s:%u: %s", file, param->line, message);
+ }
+
+ free(message);
+}
+
+
+/*
+** Stubs for functions not yet implemented.
+*/
+struct config_group *
+config_find_group(struct config_group *group UNUSED, const char *type UNUSED)
+{
+ return NULL;
+}
+
+struct config_group *
+config_next_group(struct config_group *group UNUSED)
+{
+ return NULL;
+}
+
+bool
+config_param_real(struct config_group *group UNUSED, const char *key UNUSED,
+ double *result UNUSED)
+{
+ return false;
+}
+
+bool
+config_param_list(struct config_group *group UNUSED, const char *key UNUSED,
+ struct vector *result UNUSED)
+{
+ return false;
+}
+
+void
+config_error_group(struct config_group *group UNUSED, const char *fmt UNUSED,
+ ...)
+{
+}
--- /dev/null
+/* $Id: daemonize.c 6395 2003-07-12 19:13:49Z rra $
+**
+** Become a long-running daemon.
+**
+** Usage:
+**
+** daemonize(path);
+**
+** Performs all of the various system-specific stuff required to become a
+** long-running daemon. Also chdir to the provided path (which is where
+** core dumps will go on most systems).
+*/
+
+#include "config.h"
+#include "clibrary.h"
+#include <fcntl.h>
+#include <sys/ioctl.h>
+#include <sys/stat.h>
+
+#include "inn/messages.h"
+#include "libinn.h"
+
+void
+daemonize(const char *path)
+{
+ int status;
+ int fd;
+
+ /* Fork and exit in the parent to disassociate from the current process
+ group and become the leader of a new process group. */
+ status = fork();
+ if (status < 0)
+ sysdie("cant fork");
+ else if (status > 0)
+ _exit(0);
+
+ /* setsid() should take care of disassociating from the controlling
+ terminal, and FreeBSD at least doesn't like TIOCNOTTY if you don't
+ already have a controlling terminal. So only use the older TIOCNOTTY
+ method if setsid() isn't available. */
+#if HAVE_SETSID
+ if (setsid() < 0)
+ syswarn("cant become session leader");
+#elif defined(TIOCNOTTY)
+ fd = open("/dev/tty", O_RDWR);
+ if (fd >= 0) {
+ if (ioctl(fd, TIOCNOTTY, NULL) < 0)
+ syswarn("cant disassociate from the terminal");
+ close(fd);
+ }
+#endif /* defined(TIOCNOTTY) */
+
+ if (chdir(path) < 0)
+ syswarn("cant chdir to %s", path);
+
+ fd = open("/dev/null", O_RDWR, 0);
+ if (fd != -1) {
+ dup2(fd, STDIN_FILENO);
+ dup2(fd, STDOUT_FILENO);
+ dup2(fd, STDERR_FILENO);
+ if (fd > 2)
+ close(fd);
+ }
+}
--- /dev/null
+/* $Id: date.c 7136 2005-03-11 19:18:27Z rra $
+**
+** Date parsing and conversion routines.
+**
+** Provides various date parsing and conversion routines, including
+** generating Date headers for posted articles. Note that the parsedate
+** parser is separate from this file.
+*/
+
+#include "config.h"
+#include "clibrary.h"
+#include <ctype.h>
+#include <time.h>
+
+#include "libinn.h"
+
+/*
+** Time constants.
+**
+** Do not translate these names. RFC 822 by way of RFC 1036 requires that
+** weekday and month names *not* be translated. This is why we use static
+** tables rather than strftime for building dates, to avoid locale
+** interference.
+*/
+
+static const char WEEKDAY[7][4] = {
+ "Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"
+};
+
+static const char MONTH[12][4] = {
+ "Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct",
+ "Nov", "Dec"
+};
+
+/* Number of days in a month. */
+static const int MONTHDAYS[] = {
+ 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31
+};
+
+/* Non-numeric time zones. Supporting these is required to support the
+ obsolete date format of RFC 2822. The military time zones are handled
+ separately. */
+static const struct {
+ const char name[4];
+ long offset;
+} ZONE_OFFSET[] = {
+ { "UT", 0 }, { "GMT", 0 },
+ { "EDT", -4 * 60 * 60 }, { "EST", -5 * 60 * 60 },
+ { "CDT", -5 * 60 * 60 }, { "CST", -6 * 60 * 60 },
+ { "MDT", -6 * 60 * 60 }, { "MST", -7 * 60 * 60 },
+ { "PDT", -7 * 60 * 60 }, { "PST", -8 * 60 * 60 },
+};
+
+
+/*
+** Time parsing macros.
+*/
+
+/* Whether a given year is a leap year. */
+#define ISLEAP(year) \
+ (((year) % 4) == 0 && (((year) % 100) != 0 || ((year) % 400) == 0))
+
+
+/*
+** RFC 2822 date parsing rules.
+*/
+
+/* The data structure to store a rule. The interpretation of the other fields
+ is based on the value of type. For NUMBER, read between min and max
+ characters and convert to a number. For LOOKUP, look for max characters
+ and find that string in the provided table (with size elements). For
+ DELIM, just make sure that we see the character stored in delimiter. */
+struct rule {
+ enum {
+ TYPE_NUMBER,
+ TYPE_LOOKUP,
+ TYPE_DELIM
+ } type;
+ char delimiter;
+ const char (*table)[4];
+ size_t size;
+ int min;
+ int max;
+};
+
+
+/*
+** Given a time as a time_t, return the offset in seconds of the local time
+** zone from UTC at that time (adding the offset to UTC time yields local
+** time). If the second argument is true, the time represents the current
+** time and in that circumstance we can assume that timezone/altzone are
+** correct. (We can't for arbitrary times in the past.)
+*/
+static long
+local_tz_offset(time_t date, bool current UNUSED)
+{
+ struct tm *tm;
+#if !HAVE_TM_GMTOFF
+ struct tm local, gmt;
+ long offset;
+#endif
+
+ tm = localtime(&date);
+
+#if !HAVE_TM_GMTOFF && HAVE_VAR_TIMEZONE
+ if (current)
+ return (tm->tm_isdst > 0) ? -altzone : -timezone;
+#endif
+
+#if HAVE_TM_GMTOFF
+ return tm->tm_gmtoff;
+#else
+ /* We don't have any easy returnable value, so we call both localtime
+ and gmtime and calculate the difference. Assume that local time is
+ never more than 24 hours away from UTC and ignore seconds. */
+ local = *tm;
+ tm = gmtime(&date);
+ gmt = *tm;
+ offset = local.tm_yday - gmt.tm_yday;
+ if (offset < -1) {
+ /* Local time is in the next year. */
+ offset = 24;
+ } else if (offset > 1) {
+ /* Local time is in the previous year. */
+ offset = -24;
+ } else {
+ offset *= 24;
+ }
+ offset += local.tm_hour - gmt.tm_hour;
+ offset *= 60;
+ offset += local.tm_min - gmt.tm_min;
+ return offset * 60;
+#endif /* !HAVE_TM_GMTOFF */
+}
+
+
+/*
+** Given a time_t, a flag saying whether to use local time, a buffer, and
+** the length of the buffer, write the contents of a valid RFC 2822 / RFC
+** 1036 Date header into the buffer (provided it's long enough). Returns
+** true on success, false if the buffer is too long. Use snprintf rather
+** than strftime to be absolutely certain that locales don't result in the
+** wrong output. If the time is -1, obtain and use the current time.
+*/
+bool
+makedate(time_t date, bool local, char *buff, size_t buflen)
+{
+ time_t realdate;
+ struct tm *tmp_tm;
+ struct tm tm;
+ long tz_offset;
+ int tz_hour_offset, tz_min_offset, tz_sign;
+ size_t date_length;
+ const char *tz_name;
+
+ /* Make sure the buffer is large enough. A complete RFC 2822 date with
+ spaces wherever FWS is required and the optional weekday takes:
+
+ 1 2 3
+ 1234567890123456789012345678901
+ Sat, 31 Aug 2002 23:45:18 +0000
+
+ 31 characters, plus another character for the trailing nul. The buffer
+ will need to have another six characters of space to get the optional
+ trailing time zone comment. */
+ if (buflen < 32)
+ return false;
+
+ /* Get the current time if the provided time is -1. */
+ realdate = (date == (time_t) -1) ? time(NULL) : date;
+
+ /* RFC 2822 says the timezone offset is given as [+-]HHMM, so we have to
+ separate the offset into a sign, hours, and minutes. Dividing the
+ offset by 36 looks like it works, but will fail for any offset that
+ isn't an even number of hours, and there are half-hour timezones. */
+ if (local) {
+ tmp_tm = localtime(&realdate);
+ tm = *tmp_tm;
+ tz_offset = local_tz_offset(realdate, date == (time_t) -1);
+ tz_sign = (tz_offset < 0) ? -1 : 1;
+ tz_offset *= tz_sign;
+ tz_hour_offset = tz_offset / 3600;
+ tz_min_offset = (tz_offset % 3600) / 60;
+ } else {
+ tmp_tm = gmtime(&realdate);
+ tm = *tmp_tm;
+ tz_sign = 1;
+ tz_hour_offset = 0;
+ tz_min_offset = 0;
+ }
+
+ /* tz_min_offset cannot be larger than 60 (by basic mathematics). If
+ through some insane circumtances, tz_hour_offset would be larger,
+ reject the time as invalid rather than generate an invalid date. */
+ if (tz_hour_offset > 24)
+ return false;
+
+ /* Generate the actual date string, sans the trailing time zone comment
+ but with the day of the week and the seconds (both of which are
+ optional in the standard). */
+ snprintf(buff, buflen, "%3.3s, %d %3.3s %d %02d:%02d:%02d %c%02d%02d",
+ &WEEKDAY[tm.tm_wday][0], tm.tm_mday, &MONTH[tm.tm_mon][0],
+ 1900 + tm.tm_year, tm.tm_hour, tm.tm_min, tm.tm_sec,
+ (tz_sign > 0) ? '+' : '-', tz_hour_offset, tz_min_offset);
+ date_length = strlen(buff);
+
+ /* Now, get a pointer to the time zone abbreviation, and if there is
+ enough room in the buffer, add it to the end of the date string as a
+ comment. */
+ if (!local) {
+ tz_name = "UTC";
+ } else {
+#if HAVE_TM_ZONE
+ tz_name = tm.tm_zone;
+#elif HAVE_VAR_TZNAME
+ tz_name = tzname[(tm.tm_isdst > 0) ? 1 : 0];
+#else
+ tz_name = NULL;
+#endif
+ }
+ if (tz_name != NULL && date_length + 4 + strlen(tz_name) <= buflen) {
+ snprintf(buff + date_length, buflen - date_length, " (%s)", tz_name);
+ }
+ return true;
+}
+
+
+/*
+** Given a struct tm representing a calendar time in UTC, convert it to
+** seconds since epoch. Returns (time_t) -1 if the time is not
+** convertable. Note that this function does not canonicalize the provided
+** struct tm, nor does it allow out of range values or years before 1970.
+*/
+static time_t
+mktime_utc(const struct tm *tm)
+{
+ time_t result = 0;
+ int i;
+
+ /* We do allow some ill-formed dates, but we don't do anything special
+ with them and our callers really shouldn't pass them to us. Do
+ explicitly disallow the ones that would cause invalid array accesses
+ or other algorithm problems. */
+ if (tm->tm_mon < 0 || tm->tm_mon > 11 || tm->tm_year < 70)
+ return (time_t) -1;
+
+ /* Convert to a time_t. */
+ for (i = 1970; i < tm->tm_year + 1900; i++)
+ result += 365 + ISLEAP(i);
+ for (i = 0; i < tm->tm_mon; i++)
+ result += MONTHDAYS[i];
+ if (tm->tm_mon > 1 && ISLEAP(tm->tm_year + 1900))
+ result++;
+ result = 24 * (result + tm->tm_mday - 1) + tm->tm_hour;
+ result = 60 * result + tm->tm_min;
+ result = 60 * result + tm->tm_sec;
+ return result;
+}
+
+
+/*
+** Check the ranges of values in a struct tm to make sure that the date was
+** well-formed. Assumes that the year has already been correctly set to
+** something (but may be before 1970).
+*/
+static bool
+valid_tm(const struct tm *tm)
+{
+ if (tm->tm_sec > 60 || tm->tm_min > 59 || tm->tm_hour > 23)
+ return false;
+ if (tm->tm_mday < 1 || tm->tm_mon < 0 || tm->tm_mon > 11)
+ return false;
+
+ /* Make sure that the day isn't past the end of the month, allowing for
+ leap years. */
+ if (tm->tm_mday > MONTHDAYS[tm->tm_mon]
+ && (tm->tm_mon != 1 || tm->tm_mday > 29
+ || !ISLEAP(tm->tm_year + 1900)))
+ return false;
+
+ /* We can't handle years before 1970. */
+ if (tm->tm_year < 70)
+ return false;
+
+ return true;
+}
+
+
+/*
+** Parse a date in the format used in NNTP commands such as NEWGROUPS and
+** NEWNEWS. The first argument is a string of the form YYYYMMDD and the
+** second a string of the form HHMMSS. The third argument is a boolean
+** flag saying whether the date is specified in local time; if false, the
+** date is assumed to be in UTC. Returns the time_t corresponding to the
+** given date and time or (time_t) -1 in the event of an error.
+*/
+time_t
+parsedate_nntp(const char *date, const char *hour, bool local)
+{
+ const char *p;
+ size_t datelen;
+ time_t now, result;
+ struct tm tm;
+ struct tm *current;
+ int century;
+
+ /* Accept YYMMDD and YYYYMMDD. The first is what RFC 977 requires. The
+ second is what the revision of RFC 977 will require. */
+ datelen = strlen(date);
+ if ((datelen != 6 && datelen != 8) || strlen(hour) != 6)
+ return (time_t) -1;
+ for (p = date; *p; p++)
+ if (!CTYPE(isdigit, *p))
+ return (time_t) -1;
+ for (p = hour; *p; p++)
+ if (!CTYPE(isdigit, *p))
+ return (time_t) -1;
+
+ /* Parse the date into a struct tm, skipping over the century part of
+ the year, if any. We'll deal with it in a moment. */
+ tm.tm_isdst = -1;
+ p = date + datelen - 6;
+ tm.tm_year = (p[0] - '0') * 10 + p[1] - '0';
+ tm.tm_mon = (p[2] - '0') * 10 + p[3] - '0' - 1;
+ tm.tm_mday = (p[4] - '0') * 10 + p[5] - '0';
+ p = hour;
+ tm.tm_hour = (p[0] - '0') * 10 + p[1] - '0';
+ tm.tm_min = (p[2] - '0') * 10 + p[3] - '0';
+ tm.tm_sec = (p[4] - '0') * 10 + p[5] - '0';
+
+ /* Four-digit years are the easy case.
+
+ For two-digit years, RFC 977 says "The closest century is assumed as
+ part of the year (i.e., 86 specifies 1986, 30 specifies 2030, 99 is
+ 1999, 00 is 2000)." draft-ietf-nntpext-base-10.txt simplifies this
+ considerably and is what we implement:
+
+ If the first two digits of the year are not specified, the year is
+ to be taken from the current century if YY is smaller than or equal
+ to the current year, otherwise the year is from the previous
+ century.
+
+ This implementation assumes "current year" means the last two digits
+ of the current year. Note that this algorithm interacts poorly with
+ clients with a slightly fast clock around the turn of a century, as
+ it may send 00 for the year when the year on the server is still xx99
+ and have it taken to be 99 years in the past. But 2000 has come and
+ gone, and by 2100 news clients *really* should have started using UTC
+ for everything like the new draft recommends. */
+ if (datelen == 8) {
+ tm.tm_year += (date[0] - '0') * 1000 + (date[1] - '0') * 100;
+ tm.tm_year -= 1900;
+ } else {
+ now = time(NULL);
+ current = local ? localtime(&now) : gmtime(&now);
+ century = current->tm_year / 100;
+ if (tm.tm_year > current->tm_year % 100)
+ century--;
+ tm.tm_year += century * 100;
+ }
+
+ /* Ensure that all of the date components are within valid ranges. */
+ if (!valid_tm(&tm))
+ return (time_t) -1;
+
+ /* tm contains the broken-down date; convert it to a time_t. mktime
+ assumes the supplied struct tm is in the local time zone; if given a
+ time in UTC, use our own routine instead. */
+ result = local ? mktime(&tm) : mktime_utc(&tm);
+ return result;
+}
+
+
+/*
+** Skip any amount of CFWS (comments and folding whitespace), the RFC 2822
+** grammar term for whitespace, CRLF pairs, and possibly nested comments that
+** may contain escaped parens. We also allow simple newlines since we don't
+** always deal with wire-format messages. Note that we do not attempt to
+** ensure that CRLF or a newline is followed by whitespace. Returns the new
+** position of the pointer.
+*/
+static const char *
+skip_cfws(const char *p)
+{
+ int nesting = 0;
+
+ for (; *p != '\0'; p++) {
+ switch (*p) {
+ case ' ':
+ case '\t':
+ case '\n':
+ break;
+ case '\r':
+ if (p[1] != '\n')
+ return p;
+ p++;
+ break;
+ case '(':
+ nesting++;
+ break;
+ case ')':
+ if (nesting == 0)
+ return p;
+ nesting--;
+ break;
+ case '\\':
+ if (nesting == 0 || p[1] == '\0')
+ return p;
+ p++;
+ break;
+ default:
+ if (nesting == 0)
+ return p;
+ break;
+ }
+ }
+ return p;
+}
+
+
+/*
+** Parse a single number. Takes the parsing rule that we're applying and
+** returns a pointer to the new position of the parse stream. If there
+** aren't enough digits, return NULL.
+*/
+static const char *
+parse_number(const char *p, const struct rule *rule, int *value)
+{
+ int count;
+
+ *value = 0;
+ for (count = 0; *p != '\0' && count < rule->max; p++, count++) {
+ if (*p < '0' || *p > '9')
+ break;
+ *value = *value * 10 + (*p - '0');
+ }
+ if (count < rule->min || count > rule->max)
+ return NULL;
+ return p;
+}
+
+
+/*
+** Parse a single string value that has to be done via table lookup. Takes
+** the parsing rule that we're applying. Puts the index number of the string
+** if found into the value pointerand returns the new position of the string,
+** or NULL if the string could not be found in the table.
+*/
+static const char *
+parse_lookup(const char *p, const struct rule *rule, int *value)
+{
+ size_t i;
+
+ for (i = 0; i < rule->size; i++)
+ if (strncasecmp(rule->table[i], p, rule->max) == 0) {
+ p += rule->max;
+ *value = i;
+ return p;
+ }
+ return NULL;
+}
+
+
+/*
+** Apply a set of date parsing rules to a string. Returns the new position
+** in the parse string if this succeeds and NULL if it fails. As part of the
+** parse, stores values into the value pointer in the array of rules that was
+** passed in. Takes an array of rules and a count of rules in that array.
+*/
+static const char *
+parse_by_rule(const char *p, const struct rule rules[], size_t count,
+ int *values)
+{
+ size_t i;
+ const struct rule *rule;
+
+ for (i = 0; i < count; i++) {
+ rule = &rules[i];
+
+ switch (rule->type) {
+ case TYPE_DELIM:
+ if (*p != rule->delimiter)
+ return NULL;
+ p++;
+ break;
+ case TYPE_LOOKUP:
+ p = parse_lookup(p, rule, &values[i]);
+ if (p == NULL)
+ return NULL;
+ break;
+ case TYPE_NUMBER:
+ p = parse_number(p, rule, &values[i]);
+ if (p == NULL)
+ return NULL;
+ break;
+ }
+
+ p = skip_cfws(p);
+ }
+ return p;
+}
+
+
+/*
+** Parse a legacy time zone. This uses the parsing rules in RFC 2822,
+** including assigning an offset of 0 to all single-character military time
+** zones due to their ambiguity in practice. Returns the new position in the
+** parse stream or NULL if we failed to parse the zone.
+*/
+static const char *
+parse_legacy_timezone(const char *p, long *offset)
+{
+ const char *end;
+ size_t max, i;
+
+ for (end = p; *end != '\0' && !CTYPE(isspace, *end); end++)
+ ;
+ if (end == p)
+ return NULL;
+ max = end - p;
+ for (i = 0; i < ARRAY_SIZE(ZONE_OFFSET); i++)
+ if (strncasecmp(ZONE_OFFSET[i].name, p, max) == 0) {
+ p += strlen(ZONE_OFFSET[i].name);
+ *offset = ZONE_OFFSET[i].offset;
+ return p;
+ }
+ if (max == 1 && CTYPE(isalpha, *p) && *p != 'J' && *p != 'j') {
+ *offset = 0;
+ return p + 1;
+ }
+ return NULL;
+}
+
+
+/*
+** Parse an RFC 2822 date, accepting the normal and obsolete syntax. Takes a
+** pointer to the beginning of the date and the length. Returns the
+** translated time in seconds since epoch, or (time_t) -1 on error.
+*/
+time_t
+parsedate_rfc2822(const char *date)
+{
+ const char *p;
+ int zone_sign;
+ long zone_offset;
+ struct tm tm;
+ int values[8];
+ time_t result;
+
+ /* The basic rules. Note that we don't bother to check whether the day of
+ the week is accurate or not. */
+ static const struct rule base_rule[] = {
+ { TYPE_LOOKUP, 0, WEEKDAY, 7, 3, 3 },
+ { TYPE_DELIM, ',', NULL, 0, 1, 1 },
+ { TYPE_NUMBER, 0, NULL, 0, 1, 2 },
+ { TYPE_LOOKUP, 0, MONTH, 12, 3, 3 },
+ { TYPE_NUMBER, 0, NULL, 0, 2, 4 },
+ { TYPE_NUMBER, 0, NULL, 0, 2, 2 },
+ { TYPE_DELIM, ':', NULL, 0, 1, 1 },
+ { TYPE_NUMBER, 0, NULL, 0, 2, 2 }
+ };
+
+ /* Optional seconds at the end of the time. */
+ static const struct rule seconds_rule[] = {
+ { TYPE_DELIM, ':', NULL, 0, 1, 1 },
+ { TYPE_NUMBER, 0, NULL, 0, 2, 2 }
+ };
+
+ /* Numeric time zone. */
+ static const struct rule zone_rule[] = {
+ { TYPE_NUMBER, 0, NULL, 0, 4, 4 }
+ };
+
+ /* Start with a clean slate. */
+ memset(&tm, 0, sizeof(struct tm));
+ memset(values, 0, sizeof(values));
+
+ /* Parse the base part of the date. The initial day of the week is
+ optional. */
+ p = skip_cfws(date);
+ if (CTYPE(isalpha, *p))
+ p = parse_by_rule(p, base_rule, ARRAY_SIZE(base_rule), values);
+ else
+ p = parse_by_rule(p, base_rule + 2, ARRAY_SIZE(base_rule) - 2,
+ values + 2);
+ if (p == NULL)
+ return (time_t) -1;
+
+ /* Stash the results into a struct tm. Values are associated with the
+ rule number of the same index. */
+ tm.tm_mday = values[2];
+ tm.tm_mon = values[3];
+ tm.tm_year = values[4];
+ tm.tm_hour = values[5];
+ tm.tm_min = values[7];
+
+ /* Parse seconds if they're present. */
+ if (*p == ':') {
+ p = parse_by_rule(p, seconds_rule, ARRAY_SIZE(seconds_rule), values);
+ if (p == NULL)
+ return (time_t) -1;
+ tm.tm_sec = values[1];
+ }
+
+ /* Time zone. Unfortunately this is weird enough that we can't use nice
+ parsing rules for it. */
+ if (*p == '-' || *p == '+') {
+ zone_sign = (*p == '+') ? 1 : -1;
+ p = parse_by_rule(p + 1, zone_rule, ARRAY_SIZE(zone_rule), values);
+ if (p == NULL)
+ return (time_t) -1;
+ zone_offset = ((values[0] / 100) * 60 + values[0] % 100) * 60;
+ zone_offset *= zone_sign;
+ } else {
+ p = parse_legacy_timezone(p, &zone_offset);
+ if (p == NULL)
+ return (time_t) -1;
+ }
+
+ /* Fix up the year, using the RFC 2822 rules. Remember that tm_year
+ stores the year - 1900. */
+ if (tm.tm_year < 50)
+ tm.tm_year += 100;
+ else if (tm.tm_year >= 1000)
+ tm.tm_year -= 1900;
+
+ /* Done parsing. Make sure there's nothing left but CFWS and range-check
+ our results and then convert the struct tm to seconds since epoch and
+ then apply the time zone offset. */
+ p = skip_cfws(p);
+ if (*p != '\0')
+ return (time_t) -1;
+ if (!valid_tm(&tm))
+ return (time_t) -1;
+ result = mktime_utc(&tm);
+ return (result == (time_t) -1) ? result : result - zone_offset;
+}
--- /dev/null
+/*
+ dbz.c V6.1.1
+
+Copyright 1988 Jon Zeeff (zeeff@b-tech.ann-arbor.mi.us)
+You can use this code in any manner, as long as you leave my name on it
+and don't hold me responsible for any problems with it.
+
+Hacked on by gdb@ninja.UUCP (David Butler); Sun Jun 5 00:27:08 CDT 1988
+
+Various improvments + INCORE by moraes@ai.toronto.edu (Mark Moraes)
+
+Major reworking by Henry Spencer as part of the C News project.
+
+Minor lint and CodeCenter (Saber) fluff removal by Rich $alz (March, 1991).
+Non-portable CloseOnExec() calls added by Rich $alz (September, 1991).
+Added "writethrough" and tagmask calculation code from
+<rob@violet.berkeley.edu> and <leres@ee.lbl.gov> by Rich $alz (December, 1992).
+Merged in MMAP code by David Robinson, formerly <david@elroy.jpl.nasa.gov>
+now <david.robinson@sun.com> (January, 1993).
+
+Major reworking by Clayton O'Neill (coneill@oneill.net). Removed all the
+C News and backwards compatible cruft. Ripped out all the tagmask stuff
+and replaced it with hashed .pag entries. This removes the need for
+base file access. Primary bottleneck now appears to be the hash
+algorithm and search(). You can change DBZ_INTERNAL_HASH_SIZE in
+dbz.h to increase the size of the stored hash.
+
+These routines replace dbm as used by the usenet news software
+(it's not a full dbm replacement by any means). It's fast and
+simple. It contains no AT&T code.
+
+The dbz database exploits the fact that when news stores a <key,value>
+tuple, the `value' part is a seek offset into a text file, pointing to
+a copy of the `key' part. This avoids the need to store a copy of
+the key in the dbz files.
+
+The basic format of the database is two hash tables, each in it's own
+file. One contains the offsets into the history text file , and the
+other contains a hash of the message id. A value is stored by
+indexing into the tables using a hash value computed from the key;
+collisions are resolved by linear probing (just search forward for an
+empty slot, wrapping around to the beginning of the table if
+necessary). Linear probing is a performance disaster when the table
+starts to get full, so a complication is introduced. Each file actually
+contains one *or more* tables, stored sequentially in the files, and
+the length of the linear-probe sequences is limited. The search (for
+an existing item or an empy slot always starts in the first table of
+the hash file, and whenever MAXRUN probes have been done in table N,
+probing continues in table N+1. It is best not to overflow into more
+than 1-2 tables or else massive performance degradation may occur.
+Choosing the size of the database is extremely important because of this.
+
+The table size is fixed for any particular database, but is determined
+dynamically when a database is rebuilt. The strategy is to try to pick
+the size so the first table will be no more than 2/3 full, that being
+slightly before the point where performance starts to degrade. (It is
+desirable to be a bit conservative because the overflow strategy tends
+to produce files with holes in them, which is a nuisance.)
+
+Tagged hash + offset fuzzy technique merged by Sang-yong Suh (Nov, 1997)
+
+Fixed a bug handling larger than 1Gb history offset by Sang-yong Suh(1998)
+Similar fix was suggested by Mike Hucka <hucka@umich.edu> (Jan, 1998) for
+dbz-3.3.2.
+
+Limited can't tag warnings once per dbzinit() by Sang-yong Suh (May, 1998)
+
+*/
+
+#include "config.h"
+#include "clibrary.h"
+#include <ctype.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <netinet/in.h>
+#include <sys/mman.h>
+#include <sys/stat.h>
+#include <sys/time.h>
+
+#include "dbz.h"
+#include "inn/messages.h"
+#include "inn/innconf.h"
+#include "inn/mmap.h"
+#include "libinn.h"
+
+/* Needed on AIX 4.1 to get fd_set and friends. */
+#ifdef HAVE_SYS_SELECT_H
+# include <sys/select.h>
+#endif
+
+/*
+ * "LIA" = "leave it alone unless you know what you're doing".
+ *
+ * DBZTEST Generate a standalone program for testing and benchmarking
+ * DEFSIZE default table size (not as critical as in old dbz)
+ * NMEMORY number of days of memory for use in sizing new table (LIA)
+ * MAXRUN length of run which shifts to next table (see below) (LIA)
+ */
+
+static int dbzversion = 6; /* for validating .dir file format */
+
+#ifdef DO_TAGGED_HASH
+/* assume that for tagged hash, we don't want more than 4byte of_t even if
+ * off_t is 8 bytes -- people who use tagged-hash are usually short on
+ * RAM.
+ */
+#define of_t long
+#else
+#define of_t off_t
+#endif
+#define SOF (sizeof(of_t))
+#define NOTFOUND ((of_t)-1)
+#ifdef DO_TAGGED_HASH
+
+#define OVERFLOW
+#ifdef OVERFLOW
+#include <limits.h>
+#endif
+
+/* MAXDROPBITS is the maximum number of bits dropped from the offset value.
+ The least significant bits are dropped. The space is used to
+ store hash additional bits, thereby increasing the possibility of the
+ hash detection */
+#define MAXDROPBITS 4 /* max # of bits to drop from the offset */
+
+/* MAXFUZZYLENGTH is the maximum in the offset value due to the MAXDROPBITS */
+#define MAXFUZZYLENGTH ((1 << MAXDROPBITS) - 1)
+
+/*
+ * We assume that unused areas of a binary file are zeros, and that the
+ * bit pattern of `(of_t)0' is all zeros. The alternative is rather
+ * painful file initialization. Note that okayvalue(), if OVERFLOW is
+ * defined, knows what value of an offset would cause overflow.
+ */
+#define VACANT ((of_t)0)
+#define BIAS(o) ((o)+1) /* make any valid of_t non-VACANT */
+#define UNBIAS(o) ((o)-1) /* reverse BIAS() effect */
+
+#define HASTAG(o) ((o)&taghere)
+#define TAG(o) ((o)&tagbits)
+#define NOTAG(o) ((o)&~tagboth)
+#define CANTAG(o) (((o)&tagboth) == 0)
+#define MKTAG(v) (((v)<<conf.tagshift)&tagbits)
+
+#ifndef NOTAGS
+#define TAGENB 0x80 /* tag enable is top bit, tag is next 7 */
+#define TAGMASK 0x7f
+#define TAGSHIFT 24
+#else
+#define TAGENB 0 /* no tags */
+#define TAGMASK 0
+#define TAGSHIFT 0
+#endif
+
+/*
+ * Stdio buffer for base-file reads. Message-IDs (all news ever needs to
+ * read) are essentially never longer than 64 bytes, and the typical stdio
+ * buffer is so much larger that it is much more expensive to fill.
+ */
+
+static of_t tagbits; /* pre-shifted tag mask */
+static of_t taghere; /* pre-shifted tag-enable bit */
+static of_t tagboth; /* tagbits|taghere */
+static int canttag_warned; /* flag to control can't tag warning */
+
+#endif /* DO_TAGGED_HASH */
+
+/* Old dbz used a long as the record type for dbz entries, which became
+ * really gross in places because of mixed references. We define these to
+ * make it a bit easier if we want to store more in here.
+ */
+
+/* A new, from-scratch database, not built as a rebuild of an old one, needs
+ * to know table size. Normally the user supplies this info, but there have
+ * to be defaults. Making this too small can have devestating effects on
+ * history speed for the current history implementation whereas making it too
+ * big just wastes disk space, so err on the side of caution. This may still
+ * be a bit too small. Assume people using tagged hash are running somewhat
+ * smaller servers.
+ */
+#ifndef DEFSIZE
+
+#ifdef DO_TAGGED_HASH
+#define DEFSIZE 1000003 /* I need a prime number */
+#else
+#define DEFSIZE 10000000
+#endif
+
+#endif /* DEFSIZE */
+/*
+ * We read configuration info from the .dir file into this structure,
+ * so we can avoid wired-in assumptions for an existing database.
+ *
+ * Among the info is a record of recent peak usages, so that a new table
+ * size can be chosen intelligently when rebuilding. 10 is a good
+ * number of usages to keep, since news displays marked fluctuations
+ * in volume on a 7-day cycle.
+ */
+#ifndef NMEMORY
+#define NMEMORY 10 /* # days of use info to remember */
+#endif
+#define NUSEDS (1+NMEMORY)
+
+typedef struct {
+ long tsize; /* table size */
+ long used[NUSEDS]; /* entries used today, yesterday, ... */
+ long vused[NUSEDS]; /* ditto for text size */
+ int valuesize; /* size of table values, == sizeof(dbzrec) */
+ int fillpercent; /* fillpercent/100 is the percent full we'll
+ try to keep the .pag file */
+ of_t tagenb; /* unshifted tag-enable bit */
+ of_t tagmask; /* unshifted tag mask */
+ int tagshift; /* shift count for tagmask and tagenb */
+ int dropbits; /* number of bits to discard from offset */
+ int lenfuzzy; /* num of fuzzy characters in offset */
+} dbzconfig;
+static dbzconfig conf;
+
+/*
+ * Default dbzoptions to
+ */
+static dbzoptions options = {
+ false, /* write through off */
+ INCORE_NO, /* index/pag from disk */
+#ifdef HAVE_MMAP
+ INCORE_MMAP, /* exists mmap'ed. ignored in tagged hash mode */
+#else
+ INCORE_NO, /* exists from disk. ignored in tagged hash mode */
+#endif
+ true /* non-blocking writes */
+};
+
+/*
+ * Data structure for recording info about searches.
+ */
+typedef struct {
+ of_t place; /* current location in file */
+ int tabno; /* which table we're in */
+ int run; /* how long we'll stay in this table */
+# ifndef MAXRUN
+# define MAXRUN 100
+# endif
+ HASH hash; /* the key's hash code */
+ unsigned long shorthash; /* integer version of the hash, used for
+ determining the entries location.
+ Tagged_hash stores the 31-bit hash here */
+ of_t tag; /* tag we are looking for */
+ int aborted; /* has i/o error aborted search? */
+} searcher;
+#define FRESH ((searcher *)NULL)
+
+/*
+ * Arguably the searcher struct for a given routine ought to be local to
+ * it, but a fetch() is very often immediately followed by a store(), and
+ * in some circumstances it is a useful performance win to remember where
+ * the fetch() completed. So we use a global struct and remember whether
+ * it is current.
+ */
+static searcher srch;
+static searcher *prevp; /* &srch or FRESH */
+
+/* Structure for hash tables */
+typedef struct {
+ int fd; /* Non-blocking descriptor for writes */
+ off_t pos; /* Current offset into the table */
+ int reclen; /* Length of records in the table */
+ dbz_incore_val incore; /* What we're using core for */
+ void *core; /* Pointer to in-core table */
+} hash_table;
+
+/* central data structures */
+static bool opendb = false; /* Indicates if a database is currently open */
+static FILE *dirf; /* descriptor for .dir file */
+static bool readonly; /* database open read-only? */
+#ifdef DO_TAGGED_HASH
+static FILE *basef; /* descriptor for base file */
+static char *basefname; /* name for not-yet-opened base file */
+static hash_table pagtab; /* pag hash table, stores hash + offset */
+#else
+static hash_table idxtab; /* index hash table, used for data retrieval */
+static hash_table etab; /* existance hash table, used for existance checks */
+#endif
+static bool dirty; /* has a store() been done? */
+static erec empty_rec; /* empty rec to compare against
+ initalized in dbzinit */
+
+/* misc. forwards */
+static bool getcore(hash_table *tab);
+static bool putcore(hash_table *tab);
+static bool getconf(FILE *df, dbzconfig *cp);
+static int putconf(FILE *f, dbzconfig *cp);
+static void start(searcher *sp, const HASH hash, searcher *osp);
+#ifdef DO_TAGGED_HASH
+static of_t search(searcher *sp);
+static bool set_pag(searcher *sp, of_t value);
+#else
+static bool search(searcher *sp);
+#endif
+static bool set(searcher *sp, hash_table *tab, void *value);
+
+/* file-naming stuff */
+static char dir[] = ".dir";
+#ifdef DO_TAGGED_HASH
+static char pag[] = ".pag";
+#else
+static char idx[] = ".index";
+static char exists[] = ".hash";
+#endif
+
+int dbzneedfilecount(void) {
+#ifdef DO_TAGGED_HASH
+ return 2; /* basef and dirf are fopen()'ed and kept */
+#else
+ return 1; /* dirf is fopen()'ed and kept */
+#endif
+}
+
+#ifdef DO_TAGGED_HASH
+/*
+ - dbzconfbase - reconfigure dbzconf from base file size.
+ */
+static void
+config_by_text_size(dbzconfig *c, of_t basesize)
+{
+ int i;
+ unsigned long m;
+
+ /* if no tag requested, just return. */
+ if ((c->tagmask | c->tagenb) == 0)
+ return;
+
+ /* Use 10 % larger base file size. Sometimes the offset overflows */
+ basesize += basesize / 10;
+
+ /* calculate tagging from old file */
+ for (m = 1, i = 0; m < (unsigned long)basesize; i++, m <<= 1)
+ continue;
+
+ /* if we had more tags than the default, use the new data */
+ c->dropbits = 0;
+ while (m > (1 << TAGSHIFT)) {
+ if (c->dropbits >= MAXDROPBITS)
+ break;
+ c->dropbits++;
+ m >>= 1;
+ i--;
+ }
+ c->tagenb = TAGENB;
+ c->tagmask = TAGMASK;
+ c->tagshift = TAGSHIFT;
+ if ((c->tagmask | c->tagenb) && m > (1 << TAGSHIFT)) {
+ c->tagshift = i;
+ c->tagmask = (~(unsigned long)0) >> (i + 1);
+ c->tagenb = (c->tagmask << 1) & ~c->tagmask;
+ }
+ c->lenfuzzy = (int)(1 << c->dropbits) - 1;
+
+ m = (c->tagmask | c->tagenb) << c->tagshift;
+ if (m & (basesize >> c->dropbits)) {
+ fprintf(stderr, "m 0x%lx size 0x%lx\n", m, basesize);
+ exit(1);
+ }
+}
+#endif /* DO_TAGGED_HASH */
+
+/*
+ - create and truncate .pag, .idx, or .hash files
+ - return false on error
+ */
+static bool
+create_truncate(const char *name, const char *pag1)
+{
+ char *fn;
+ FILE *f;
+
+ fn = concat(name, pag1, (char *) 0);
+ f = Fopen(fn, "w", TEMPORARYOPEN);
+ free(fn);
+ if (f == NULL) {
+ syswarn("unable to create/truncate %s", pag1);
+ return false;
+ } else
+ Fclose(f);
+ return true;
+}
+
+/* dbzfresh - set up a new database, no historical info
+ * Return true for success, false for failure
+ * name - base name; .dir and .pag must exist
+ * size - table size (0 means default)
+ */
+bool
+dbzfresh(const char *name, off_t size)
+{
+ char *fn;
+ dbzconfig c;
+ FILE *f;
+#ifdef DO_TAGGED_HASH
+ struct stat sb;
+ of_t m;
+#endif
+
+ if (opendb) {
+ warn("dbzfresh: database already open");
+ return false;
+ }
+ if (size != 0 && size < 2) {
+ warn("dbzfresh: preposterous size (%ld)", (long) size);
+ return false;
+ }
+
+ /* get default configuration */
+ if (!getconf(NULL, &c))
+ return false; /* "can't happen" */
+
+#ifdef DO_TAGGED_HASH
+ /* and mess with it as specified */
+ if (size != 0)
+ c.tsize = size;
+ m = c.tagmask;
+ c.tagshift = 0;
+ while (!(m & 1)) {
+ m >>= 1;
+ c.tagshift++;
+ }
+ c.tagmask = m;
+ c.tagenb = (m << 1) & ~m;
+ c.dropbits = 0;
+ c.lenfuzzy = 0;
+
+ /* if big enough basb file exists, update config */
+ if (stat(name, &sb) != -1)
+ config_by_text_size(&c, sb.st_size);
+#else
+ /* set the size as specified, make sure we get at least 2 bytes
+ of implicit hash */
+ if (size != 0)
+ c.tsize = size > (64 * 1024) ? size : 64 * 1024;
+#endif
+
+ /* write it out */
+ fn = concat(name, dir, (char *) 0);
+ f = Fopen(fn, "w", TEMPORARYOPEN);
+ free(fn);
+ if (f == NULL) {
+ syswarn("dbzfresh: unable to write config");
+ return false;
+ }
+ if (putconf(f, &c) < 0) {
+ Fclose(f);
+ return false;
+ }
+ if (Fclose(f) == EOF) {
+ syswarn("dbzfresh: fclose failure");
+ return false;
+ }
+
+ /* create and truncate .pag, or .index/.hash files */
+#ifdef DO_TAGGED_HASH
+ if (!create_truncate(name, pag))
+ return false;
+#else
+ if (!create_truncate(name, idx))
+ return false;
+ if (!create_truncate(name, exists))
+ return false;
+#endif /* DO_TAGGED_HASH */
+
+ /* and punt to dbzinit for the hard work */
+ return dbzinit(name);
+}
+
+#ifdef DO_TAGGED_HASH
+/*
+ - isprime - is a number prime?
+ */
+static bool
+isprime(long x)
+{
+ static int quick[] = {2, 3, 5, 7, 11, 13, 17, 19, 23, 29, 31, 37, 0 };
+ int *ip;
+ long div1, stop;
+
+ /* hit the first few primes quickly to eliminate easy ones */
+ /* this incidentally prevents ridiculously small tables */
+ for (ip = quick; (div1 = *ip) != 0; ip++)
+ if (x % div1 == 0) {
+ debug("isprime: quick result on %ld", x);
+ return false;
+ }
+
+ /* approximate square root of x */
+ for (stop = x; x/stop < stop; stop >>= 1)
+ continue;
+ stop <<= 1;
+
+ /* try odd numbers up to stop */
+ for (div1 = *--ip; div1 < stop; div1 += 2)
+ if (x%div1 == 0)
+ return false;
+
+ return true;
+}
+#endif
+
+/*
+ * dbzsize - what's a good table size to hold this many entries?
+ * contents - size of table (0 means return the default)
+ */
+long
+dbzsize(off_t contents)
+{
+ of_t n;
+
+ if (contents <= 0) { /* foulup or default inquiry */
+ debug("dbzsize: preposterous input (%ld)", (long) contents);
+ return DEFSIZE;
+ }
+
+ if ((conf.fillpercent > 0) && (conf.fillpercent < 100))
+ n = (contents / conf.fillpercent) * 100;
+ else
+ n = (contents * 3) / 2; /* try to keep table at most 2/3's full */
+
+ /* Make sure that we get at least 2 bytes of implicit hash */
+ if (n < (64 * 1024))
+ n = 64 * 1024;
+
+#ifdef DO_TAGGED_HASH
+ if (!(n & 1))
+ n += 1; /* make it odd */
+ debug("dbzsize: tentative size %ld", n);
+ while (!isprime(n)) /* look for a prime */
+ n += 2;
+#endif
+
+ debug("dbzsize: final size %ld", (long) n);
+ return n;
+}
+
+/* dbzagain - set up a new database to be a rebuild of an old one
+ * Returns true on success, false on failure
+ * name - base name; .dir and .pag must exist
+ * oldname - basename, all must exist
+ */
+bool
+dbzagain(const char *name, const char *oldname)
+{
+ char *fn;
+ dbzconfig c;
+ bool result;
+ int i;
+ long top;
+ FILE *f;
+ int newtable;
+ of_t newsize;
+#ifdef DO_TAGGED_HASH
+ long vtop;
+ struct stat sb;
+#endif
+
+ if (opendb) {
+ warn("dbzagain: database already open");
+ return false;
+ }
+
+ /* pick up the old configuration */
+ fn = concat(oldname, dir, (char *) 0);
+ f = Fopen(fn, "r", TEMPORARYOPEN);
+ free(fn);
+ if (f == NULL) {
+ syswarn("dbzagain: cannot open old .dir file");
+ return false;
+ }
+ result = getconf(f, &c);
+ Fclose(f);
+ if (!result) {
+ syswarn("dbzagain: getconf failed");
+ return false;
+ }
+
+ /* tinker with it */
+ top = 0;
+ newtable = 0;
+ for (i = 0; i < NUSEDS; i++) {
+ if (top < c.used[i])
+ top = c.used[i];
+ if (c.used[i] == 0)
+ newtable = 1; /* hasn't got full usage history yet */
+ }
+ if (top == 0) {
+ debug("dbzagain: old table has no contents!");
+ newtable = 1;
+ }
+ for (i = NUSEDS-1; i > 0; i--)
+ c.used[i] = c.used[i-1];
+ c.used[0] = 0;
+
+#ifdef DO_TAGGED_HASH
+ vtop = 0;
+ for (i = 0; i < NUSEDS; i++) {
+ if (vtop < c.vused[i])
+ vtop = c.vused[i];
+ if (c.vused[i] == 0)
+ newtable = 1; /* hasn't got full usage history yet */
+ }
+ if (top != 0 && vtop == 0) {
+ debug("dbzagain: old table has no contents!");
+ newtable = 1;
+ }
+ for (i = NUSEDS-1; i > 0; i--)
+ c.vused[i] = c.vused[i-1];
+ c.vused[0] = 0;
+
+ /* calculate tagging from old file */
+ if (stat(oldname, &sb) != -1 && vtop < sb.st_size)
+ vtop = sb.st_size;
+ config_by_text_size(&c, vtop);
+#endif
+
+ newsize = dbzsize(top);
+ if (!newtable || newsize > c.tsize) /* don't shrink new table */
+ c.tsize = newsize;
+
+ /* write it out */
+ fn = concat(name, dir, (char *) 0);
+ f = Fopen(fn, "w", TEMPORARYOPEN);
+ free(fn);
+ if (f == NULL) {
+ syswarn("dbzagain: unable to write new .dir");
+ return false;
+ }
+ i = putconf(f, &c);
+ Fclose(f);
+ if (i < 0) {
+ warn("dbzagain: putconf failed");
+ return false;
+ }
+
+ /* create and truncate .pag, or .index/.hash files */
+#ifdef DO_TAGGED_HASH
+ if (!create_truncate(name, pag))
+ return false;
+#else
+ if (!create_truncate(name, idx))
+ return false;
+ if (!create_truncate(name, exists))
+ return false;
+#endif
+
+ /* and let dbzinit do the work */
+ return dbzinit(name);
+}
+
+static bool
+openhashtable(const char *base, const char *ext, hash_table *tab,
+ const size_t reclen, const dbz_incore_val incore)
+{
+ char *name;
+ int oerrno;
+
+ name = concat(base, ext, (char *) 0);
+ if ((tab->fd = open(name, readonly ? O_RDONLY : O_RDWR)) < 0) {
+ syswarn("openhashtable: could not open raw");
+ oerrno = errno;
+ free(name);
+ errno = oerrno;
+ return false;
+ }
+ free(name);
+
+ tab->reclen = reclen;
+ close_on_exec(tab->fd, true);
+ tab->pos = -1;
+
+ /* get first table into core, if it looks desirable and feasible */
+ tab->incore = incore;
+ if (tab->incore != INCORE_NO) {
+ if (!getcore(tab)) {
+ syswarn("openhashtable: getcore failure");
+ oerrno = errno;
+ close(tab->fd);
+ errno = oerrno;
+ return false;
+ }
+ }
+
+ if (options.nonblock && nonblocking(tab->fd, true) < 0) {
+ syswarn("fcntl: could not set nonblock");
+ oerrno = errno;
+ close(tab->fd);
+ errno = oerrno;
+ return false;
+ }
+ return true;
+}
+
+static void closehashtable(hash_table *tab) {
+ close(tab->fd);
+ if (tab->incore == INCORE_MEM)
+ free(tab->core);
+ if (tab->incore == INCORE_MMAP) {
+#if defined(HAVE_MMAP)
+ if (munmap(tab->core, conf.tsize * tab->reclen) == -1) {
+ syswarn("closehashtable: munmap failed");
+ }
+#else
+ warn("closehashtable: can't mmap files");
+#endif
+ }
+}
+
+#ifdef DO_TAGGED_HASH
+static bool
+openbasefile(const char *name)
+{
+ basef = Fopen(name, "r", DBZ_BASE);
+ if (basef == NULL) {
+ syswarn("dbzinit: basefile open failed");
+ basefname = xstrdup(name);
+ } else
+ basefname = NULL;
+ if (basef != NULL)
+ close_on_exec(fileno(basef), true);
+ if (basef != NULL)
+ setvbuf(basef, NULL, _IOFBF, 64);
+ return true;
+}
+#endif /* DO_TAGGED_HASH */
+
+/*
+ * dbzinit - open a database, creating it (using defaults) if necessary
+ *
+ * We try to leave errno set plausibly, to the extent that underlying
+ * functions permit this, since many people consult it if dbzinit() fails.
+ * return true for success, false for failure
+ */
+bool
+dbzinit(const char *name)
+{
+ char *fname;
+
+ if (opendb) {
+ warn("dbzinit: dbzinit already called once");
+ errno = 0;
+ return false;
+ }
+
+ /* open the .dir file */
+ fname = concat(name, dir, (char *) 0);
+ if ((dirf = Fopen(fname, "r+", DBZ_DIR)) == NULL) {
+ dirf = Fopen(fname, "r", DBZ_DIR);
+ readonly = true;
+ } else
+ readonly = false;
+ free(fname);
+ if (dirf == NULL) {
+ syswarn("dbzinit: can't open .dir file");
+ return false;
+ }
+ close_on_exec(fileno(dirf), true);
+
+ /* pick up configuration */
+ if (!getconf(dirf, &conf)) {
+ warn("dbzinit: getconf failure");
+ Fclose(dirf);
+ errno = EDOM; /* kind of a kludge, but very portable */
+ return false;
+ }
+
+ /* open pag or idx/exists file */
+#ifdef DO_TAGGED_HASH
+ if (!openhashtable(name, pag, &pagtab, SOF, options.pag_incore)) {
+ Fclose(dirf);
+ return false;
+ }
+ if (!openbasefile(name)) {
+ close(pagtab.fd);
+ Fclose(dirf);
+ return false;
+ }
+ tagbits = conf.tagmask << conf.tagshift;
+ taghere = conf.tagenb << conf.tagshift;
+ tagboth = tagbits | taghere;
+ canttag_warned = 0;
+#else
+ if (!openhashtable(name, idx, &idxtab, sizeof(of_t), options.pag_incore)) {
+ Fclose(dirf);
+ return false;
+ }
+ if (!openhashtable(name, exists, &etab, sizeof(erec),
+ options.exists_incore)) {
+ Fclose(dirf);
+ return false;
+ }
+#endif
+
+ /* misc. setup */
+ dirty = false;
+ opendb = true;
+ prevp = FRESH;
+ memset(&empty_rec, '\0', sizeof(empty_rec));
+ debug("dbzinit: succeeded");
+ return true;
+}
+
+/* dbzclose - close a database
+ */
+bool
+dbzclose(void)
+{
+ bool ret = true;
+
+ if (!opendb) {
+ warn("dbzclose: not opened!");
+ return false;
+ }
+
+ if (!dbzsync())
+ ret = false;
+
+#ifdef DO_TAGGED_HASH
+ closehashtable(&pagtab);
+ if (Fclose(basef) == EOF) {
+ syswarn("dbzclose: fclose(basef) failed");
+ ret = false;
+ }
+ if (basefname != NULL)
+ free(basefname);
+ basef = NULL;
+#else
+ closehashtable(&idxtab);
+ closehashtable(&etab);
+#endif
+
+ if (Fclose(dirf) == EOF) {
+ syswarn("dbzclose: fclose(dirf) failed");
+ ret = false;
+ }
+
+ debug("dbzclose: %s", (ret == true) ? "succeeded" : "failed");
+ if (ret)
+ opendb = false;
+ return ret;
+}
+
+/* dbzsync - push all in-core data out to disk
+ */
+bool
+dbzsync(void)
+{
+ bool ret = true;
+
+ if (!opendb) {
+ warn("dbzsync: not opened!");
+ return false;
+ }
+
+ if (!dirty)
+ return true;;
+
+#ifdef DO_TAGGED_HASH
+ if (!putcore(&pagtab)) {
+#else
+ if (!putcore(&idxtab) || !putcore(&etab)) {
+#endif
+ warn("dbzsync: putcore failed");
+ ret = false;
+ }
+
+ if (putconf(dirf, &conf) < 0)
+ ret = false;
+
+ debug("dbzsync: %s", ret ? "succeeded" : "failed");
+ return ret;
+}
+
+#ifdef DO_TAGGED_HASH
+/*
+ - okayvalue - check that a value can be stored
+ */
+static int
+okayvalue(of_t value)
+{
+ if (HASTAG(value))
+ return(0);
+#ifdef OVERFLOW
+ if (value == LONG_MAX) /* BIAS() and UNBIAS() will overflow */
+ return(0);
+#endif
+ return(1);
+}
+#endif
+
+/* dbzexists - check if the given message-id is in the database */
+bool
+dbzexists(const HASH key)
+{
+#ifdef DO_TAGGED_HASH
+ off_t value;
+
+ return (dbzfetch(key, &value) != 0);
+#else
+
+ if (!opendb) {
+ warn("dbzexists: database not open!");
+ return false;
+ }
+
+ prevp = FRESH;
+ start(&srch, key, FRESH);
+ return search(&srch);
+#endif
+}
+
+/*
+ * dbzfetch - get offset of an entry from the database
+ *
+ * Returns the offset of the text file for input key,
+ * or -1 if NOTFOUND or error occurs.
+ */
+bool
+dbzfetch(const HASH key, off_t *value)
+{
+#ifdef DO_TAGGED_HASH
+#define MAX_NB2RD (DBZMAXKEY + MAXFUZZYLENGTH + 2)
+#define MIN_KEY_LENGTH 6 /* strlen("<1@a>") + strlen("\t") */
+ char *bp, buffer[MAX_NB2RD];
+ int keylen, j, nb2r;
+ HASH hishash;
+ char *keytext = NULL;
+ of_t offset = NOTFOUND;
+#endif
+
+ prevp = FRESH;
+
+ if (!opendb) {
+ warn("dbzfetch: database not open!");
+ return false;
+ }
+
+ start(&srch, key, FRESH);
+#ifdef DO_TAGGED_HASH
+ /*
+ * nb2r: number of bytes to read from history file.
+ * It can be reduced if history text is written as hashes only.
+ */
+ nb2r = sizeof(buffer) - 1;
+
+ while ((offset = search(&srch)) != NOTFOUND) {
+ debug("got 0x%lx", key_ptr);
+
+ /* fetch the key */
+ offset <<= conf.dropbits;
+ if (offset) /* backspace 1 character to read '\n' */
+ offset--;
+ if (fseeko(basef, offset, SEEK_SET) != 0) {
+ syswarn("dbzfetch: seek failed");
+ return false;
+ }
+ keylen = fread(buffer, 1, nb2r, basef);
+ if (keylen < MIN_KEY_LENGTH) {
+ syswarn("dbzfetch: read failed");
+ return false;
+ }
+ buffer[keylen] = '\0'; /* terminate the string */
+
+ if (offset) { /* find the '\n', the previous EOL */
+ if (keylen > conf.lenfuzzy)
+ keylen = conf.lenfuzzy; /* keylen is fuzzy distance now */
+ for (j=0,bp=buffer; j<keylen; j++,bp++)
+ if (*bp == '\n')
+ break;
+ if (*bp != '\n') {
+ debug("dbzfetch: can't locate EOL");
+ /* pag entry should be deleted, but I'm lazy... */
+ continue;
+ }
+ offset++;
+ bp++; /* now *bp is the start of key */
+ } else {
+ j = 0; /* We are looking for the first history line. */
+ bp = buffer;
+ }
+
+ /* Does this history line really have same key? */
+ if (*bp == '[') {
+ if (!keytext)
+ keytext = HashToText(key);
+ if (memcmp(keytext, bp+1, sizeof(HASH)*2) != 0)
+ continue;
+ } else if (*bp == '<') {
+ char *p = strchr(bp+1, '\t');
+ if (!p) /* history has a corrupted line */
+ continue;
+ *p = '\0';
+ hishash = HashMessageID(bp);
+ if (memcmp(&key, &hishash, sizeof(HASH)) != 0)
+ continue;
+ } else
+ continue;
+
+ /* we found it */
+ offset += j;
+ debug("fetch: successful");
+ *value = offset;
+ return true;
+ }
+
+ /* we didn't find it */
+ debug("fetch: failed");
+ prevp = &srch; /* remember where we stopped */
+ return false;
+#else /* DO_TAGGED_HASH */
+ if (search(&srch) == true) {
+ /* Actually get the data now */
+ if ((options.pag_incore != INCORE_NO) && (srch.place < conf.tsize)) {
+ memcpy(value, &((of_t *)idxtab.core)[srch.place], sizeof(of_t));
+ } else {
+ if (pread(idxtab.fd, value, sizeof(of_t), srch.place * idxtab.reclen) != sizeof(of_t)) {
+ syswarn("fetch: read failed");
+ idxtab.pos = -1;
+ srch.aborted = 1;
+ return false;
+ }
+ }
+ debug("fetch: successful");
+ return true;
+ }
+
+ /* we didn't find it */
+ debug("fetch: failed");
+ prevp = &srch; /* remember where we stopped */
+ return false;
+#endif
+}
+
+/*
+ * dbzstore - add an entry to the database
+ * returns true for success and false for failure
+ */
+DBZSTORE_RESULT
+dbzstore(const HASH key, off_t data)
+{
+#ifdef DO_TAGGED_HASH
+ of_t value;
+#else
+ erec evalue;
+#endif
+
+ if (!opendb) {
+ warn("dbzstore: database not open!");
+ return DBZSTORE_ERROR;
+ }
+ if (readonly) {
+ warn("dbzstore: database open read-only");
+ return DBZSTORE_ERROR;
+ }
+
+#ifdef DO_TAGGED_HASH
+ /* copy the value in to ensure alignment */
+ memcpy(&value, &data, SOF);
+
+ /* update maximum offset value if necessary */
+ if (value > conf.vused[0])
+ conf.vused[0] = value;
+
+ /* now value is in fuzzy format */
+ value >>= conf.dropbits;
+ debug("dbzstore: (%ld)", (long) value);
+
+ if (!okayvalue(value)) {
+ warn("dbzstore: reserved bit or overflow in 0x%lx", value);
+ return DBZSTORE_ERROR;
+ }
+
+ /* find the place, exploiting previous search if possible */
+ start(&srch, key, prevp);
+ while (search(&srch) != NOTFOUND)
+ continue;
+
+ prevp = FRESH;
+ conf.used[0]++;
+ debug("store: used count %ld", conf.used[0]);
+ dirty = 1;
+ if (!set_pag(&srch, value))
+ return DBZSTORE_ERROR;
+ return DBZSTORE_OK;
+#else /* DO_TAGGED_HASH */
+
+ /* find the place, exploiting previous search if possible */
+ start(&srch, key, prevp);
+ if (search(&srch) == true)
+ return DBZSTORE_EXISTS;
+
+ prevp = FRESH;
+ conf.used[0]++;
+ debug("store: used count %ld", conf.used[0]);
+ dirty = true;
+
+ memcpy(&evalue.hash, &srch.hash,
+ sizeof(evalue.hash) < sizeof(srch.hash) ? sizeof(evalue.hash) : sizeof(srch.hash));
+
+ /* Set the value in the index first since we don't care if it's out of date */
+ if (!set(&srch, &idxtab, (void *)&data))
+ return DBZSTORE_ERROR;
+ if (!set(&srch, &etab, &evalue))
+ return DBZSTORE_ERROR;
+ return DBZSTORE_OK;
+#endif /* DO_TAGGED_HASH */
+}
+
+/*
+ * getconf - get configuration from .dir file
+ * df - NULL means just give me the default
+ * pf - NULL means don't care about .pag
+ * returns true for success, false for failure
+ */
+static bool
+getconf(FILE *df, dbzconfig *cp)
+{
+ int i;
+
+ /* empty file, no configuration known */
+#ifdef DO_TAGGED_HASH
+ if (df == NULL) {
+ cp->tsize = DEFSIZE;
+ for (i = 0; i < NUSEDS; i++)
+ cp->used[i] = 0;
+ for (i = 0; i < NUSEDS; i++)
+ cp->vused[i] = 0;
+ cp->valuesize = sizeof(of_t);
+ cp->fillpercent = 50;
+ cp->tagenb = TAGENB;
+ cp->tagmask = TAGMASK;
+ cp->tagshift = TAGSHIFT;
+ cp->dropbits = 0;
+ cp->lenfuzzy = 0;
+ debug("getconf: defaults (%ld, %c, (0x%lx/0x%lx<<%d %d))",
+ cp->tsize, cp->casemap, cp->tagenb,
+ cp->tagmask, cp->tagshift, cp->dropbits);
+ return true;
+ }
+
+ i = fscanf(df, "dbz 6 %ld %d %d %ld %ld %d %d\n", &cp->tsize,
+ &cp->valuesize, &cp->fillpercent, &cp->tagenb,
+ &cp->tagmask, &cp->tagshift, &cp->dropbits);
+ if (i != 7) {
+ warn("dbz: bad first line in config");
+ return false;
+ }
+ if (cp->valuesize != sizeof(of_t)) {
+ warn("dbz: wrong of_t size (%d)", cp->valuesize);
+ return false;
+ }
+ cp->lenfuzzy = (int)(1 << cp->dropbits) - 1;
+#else /* DO_TAGGED_HASH */
+ if (df == NULL) {
+ cp->tsize = DEFSIZE;
+ for (i = 0; i < NUSEDS; i++)
+ cp->used[i] = 0;
+ cp->valuesize = sizeof(of_t) + sizeof(erec);
+ cp->fillpercent = 66;
+ debug("getconf: defaults (%ld)", cp->tsize);
+ return true;
+ }
+
+ i = fscanf(df, "dbz 6 %ld %d %d\n", &cp->tsize,
+ &cp->valuesize, &cp->fillpercent);
+ if (i != 3) {
+ warn("dbz: bad first line in config");
+ return false;
+ }
+ if (cp->valuesize != (sizeof(of_t) + sizeof(erec))) {
+ warn("dbz: wrong of_t size (%d)", cp->valuesize);
+ return false;
+ }
+#endif /* DO_TAGGED_HASH */
+ debug("size %ld", cp->tsize);
+
+ /* second line, the usages */
+ for (i = 0; i < NUSEDS; i++)
+ if (!fscanf(df, "%ld", &cp->used[i])) {
+ warn("dbz: bad usage value in config");
+ return false;
+ }
+ debug("used %ld %ld %ld...", cp->used[0], cp->used[1], cp->used[2]);
+
+#ifdef DO_TAGGED_HASH
+ /* third line, the text usages */
+ for (i = 0; i < NUSEDS; i++)
+ if (!fscanf(df, "%ld", &cp->vused[i])) {
+ warn("dbz: bad text usage value in config");
+ return false;
+ }
+ debug("vused %ld %ld %ld...", cp->vused[0], cp->vused[1], cp->vused[2]);
+#endif /* DO_TAGGED_HASH */
+
+ return true;
+}
+
+/* putconf - write configuration to .dir file
+ * Returns: 0 for success, -1 for failure
+ */
+static int
+putconf(FILE *f, dbzconfig *cp)
+{
+ int i;
+ int ret = 0;
+
+ if (fseeko(f, 0, SEEK_SET) != 0) {
+ syswarn("dbz: fseeko failure in putconf");
+ ret = -1;
+ }
+
+#ifdef DO_TAGGED_HASH
+ fprintf(f, "dbz %d %ld %d %d %ld %ld %d %d\n", dbzversion, cp->tsize,
+ cp->valuesize, cp->fillpercent, cp->tagenb,
+ cp->tagmask, cp->tagshift, cp->dropbits);
+#else /* DO_TAGGED_HASH */
+ fprintf(f, "dbz %d %ld %d %d\n", dbzversion, cp->tsize,
+ cp->valuesize, cp->fillpercent);
+#endif /* DO_TAGGED_HASH */
+
+ for (i = 0; i < NUSEDS; i++)
+ fprintf(f, "%ld%c", cp->used[i], (i < NUSEDS-1) ? ' ' : '\n');
+
+#ifdef DO_TAGGED_HASH
+ for (i = 0; i < NUSEDS; i++)
+ fprintf(f, "%ld%c", cp->vused[i], (i < NUSEDS-1) ? ' ' : '\n');
+#endif
+
+ fflush(f);
+ if (ferror(f))
+ ret = -1;
+
+ debug("putconf status %d", ret);
+ return ret;
+}
+
+/* getcore - try to set up an in-core copy of file
+ *
+ * Returns: pointer to copy of file or NULL on errror
+ */
+static bool
+getcore(hash_table *tab)
+{
+ char *it;
+ ssize_t nread;
+ size_t i;
+ struct stat st;
+ size_t length = conf.tsize * tab->reclen;
+
+ if (tab->incore == INCORE_MMAP) {
+#if defined(HAVE_MMAP)
+ if (fstat(tab->fd, &st) == -1) {
+ syswarn("dbz: getcore: fstat failed");
+ return false;
+ }
+ if (length > st.st_size) {
+ /* file too small; extend it */
+ if (ftruncate(tab->fd, length) == -1) {
+ syswarn("dbz: getcore: ftruncate failed");
+ return false;
+ }
+ }
+ it = mmap(NULL, length, readonly ? PROT_READ : PROT_WRITE | PROT_READ,
+ MAP_SHARED, tab->fd, 0);
+ if (it == (char *)-1) {
+ syswarn("dbz: getcore: mmap failed");
+ return false;
+ }
+#if defined (MADV_RANDOM) && defined(HAVE_MADVISE)
+ /* not present in all versions of mmap() */
+ madvise(it, length, MADV_RANDOM);
+#endif
+#else
+ warn("dbz: getcore: can't mmap files");
+ return false;
+#endif
+ } else {
+ it = xmalloc(length);
+
+ nread = read(tab->fd, it, length);
+ if (nread < 0) {
+ syswarn("dbz: getcore: read failed");
+ free(it);
+ return false;
+ }
+
+ i = length - nread;
+ memset(it + nread, '\0', i);
+ }
+
+ tab->core = it;
+ return true;
+}
+
+/* putcore - try to rewrite an in-core table
+ *
+ * Returns true on success, false on failure
+ */
+static bool
+putcore(hash_table *tab)
+{
+ size_t size;
+ ssize_t result;
+
+ if (tab->incore == INCORE_MEM) {
+ if(options.writethrough)
+ return true;
+ nonblocking(tab->fd, false);
+ size = tab->reclen * conf.tsize;
+ result = xpwrite(tab->fd, tab->core, size, 0);
+ if (result < 0 || (size_t) result != size) {
+ nonblocking(tab->fd, options.nonblock);
+ return false;
+ }
+ nonblocking(tab->fd, options.nonblock);
+ }
+#ifdef HAVE_MMAP
+ if(tab->incore == INCORE_MMAP) {
+ msync(tab->core, conf.tsize * tab->reclen, MS_ASYNC);
+ }
+#endif
+ return true;
+}
+
+#ifdef DO_TAGGED_HASH
+/*
+ - makehash31 : make 31-bit hash from HASH
+ */
+static unsigned int
+makehash31(const HASH *hash)
+{
+ unsigned int h;
+ memcpy(&h, hash, sizeof(h));
+ return (h >> 1);
+}
+#endif
+
+/* start - set up to start or restart a search
+ * osp == NULL is acceptable
+ */
+static void
+start(searcher *sp, const HASH hash, searcher *osp)
+{
+#ifdef DO_TAGGED_HASH
+ unsigned int h;
+
+ h = makehash31(&hash);
+ if (osp != FRESH && osp->shorthash == h) {
+ if (sp != osp)
+ *sp = *osp;
+ sp->run--;
+ debug("search restarted");
+ } else {
+ sp->shorthash = h;
+ sp->tag = MKTAG(h / conf.tsize);
+ sp->place = h % conf.tsize;
+ debug("hash %8.8lx tag %8.8lx place %ld",
+ sp->shorthash, sp->tag, sp->place);
+ sp->tabno = 0;
+ sp->run = -1;
+ sp->aborted = 0;
+ }
+
+#else /* DO_TAGGED_HASH */
+ int tocopy;
+
+ if (osp != FRESH && !memcmp(&osp->hash, &hash, sizeof(hash))) {
+ if (sp != osp)
+ *sp = *osp;
+ sp->run--;
+ debug("search restarted");
+ } else {
+ sp->hash = hash;
+ tocopy = sizeof(hash) < sizeof(sp->shorthash) ? sizeof(hash) : sizeof(sp->shorthash);
+ /* Copy the bottom half of thhe hash into sp->shorthash */
+ memcpy(&sp->shorthash, (const char *)&hash + (sizeof(hash) - tocopy),
+ tocopy);
+ sp->shorthash >>= 1;
+ sp->tabno = 0;
+ sp->run = -1;
+ sp->aborted = 0;
+ }
+#endif /* DO_TAGGED_HASH */
+}
+
+#ifdef DO_TAGGED_HASH
+/*
+ - search - conduct part of a search
+ */
+static of_t /* NOTFOUND if we hit VACANT or error */
+search(searcher *sp)
+{
+ of_t value;
+ unsigned long taboffset = sp->tabno * conf.tsize;
+
+ if (sp->aborted)
+ return(NOTFOUND);
+
+ for (;;) {
+ /* go to next location */
+ if (sp->run++ == MAXRUN) {
+ sp->tabno++;
+ sp->run = 0;
+ taboffset = sp->tabno * conf.tsize;
+ }
+ sp->place = ((sp->shorthash + sp->run) % conf.tsize) + taboffset;
+ debug("search @ %ld", place);
+
+ /* get the tagged value */
+ if ((options.pag_incore != INCORE_NO) && (sp->place < conf.tsize)) {
+ debug("search: in core");
+ value = ((of_t *)pagtab.core)[sp->place];
+ } else {
+ off_t dest;
+ dest = sp->place * SOF;
+
+ /* read it */
+ errno = 0;
+ if (pread(pagtab.fd, &value, sizeof(value), dest) != sizeof(value)) {
+ if (errno != 0) {
+ syswarn("dbz: search: read failed");
+ pagtab.pos = -1;
+ sp->aborted = 1;
+ return(NOTFOUND);
+ } else
+ value = VACANT;
+ pagtab.pos = -1;
+ } else
+ pagtab.pos += sizeof(value);
+ }
+
+ /* vacant slot is always cause to return */
+ if (value == VACANT) {
+ debug("search: empty slot");
+ return(NOTFOUND);
+ };
+
+ /* check the tag */
+ value = UNBIAS(value);
+ debug("got 0x%lx", value);
+ if (!HASTAG(value)) {
+ debug("tagless");
+ return(value);
+ } else if (TAG(value) == sp->tag) {
+ debug("match");
+ return(NOTAG(value));
+ } else {
+ debug("mismatch 0x%lx", TAG(value));
+ }
+ }
+ /* NOTREACHED */
+}
+
+#else /* DO_TAGGED_HASH */
+
+/* search - conduct part of a search
+ *
+ * return false if we hit vacant rec's or error
+ */
+static bool
+search(searcher *sp)
+{
+ erec value;
+ unsigned long taboffset = 0;
+
+ if (sp->aborted)
+ return false;
+
+ for (;;) {
+ /* go to next location */
+ if (sp->run++ == MAXRUN) {
+ sp->tabno++;
+ sp->run = 0;
+ taboffset = sp->tabno * conf.tsize;
+ }
+
+ sp->place = ((sp->shorthash + sp->run) % conf.tsize) + taboffset;
+ debug("search @ %ld", (long) sp->place);
+
+ /* get the value */
+ if ((options.exists_incore != INCORE_NO) && (sp->place < conf.tsize)) {
+ debug("search: in core");
+ memcpy(&value, &((erec *)etab.core)[sp->place], sizeof(erec));
+ } else {
+ off_t dest;
+ dest = sp->place * sizeof(erec);
+
+ /* read it */
+ errno = 0;
+ if (pread(etab.fd, &value, sizeof(erec), dest) != sizeof(erec)) {
+ if (errno != 0) {
+ debug("search: read failed");
+ etab.pos = -1;
+ sp->aborted = 1;
+ return false;
+ } else {
+ memset(&value, '\0', sizeof(erec));
+ }
+ }
+
+ /* and finish up */
+ etab.pos += sizeof(erec);
+ }
+
+ /* Check for an empty record */
+ if (!memcmp(&value, &empty_rec, sizeof(erec))) {
+ debug("search: empty slot");
+ return false;
+ }
+
+ /* check the value */
+ debug("got 0x%.*s", DBZ_INTERNAL_HASH_SIZE, value.hash);
+ if (!memcmp(&value.hash, &sp->hash, DBZ_INTERNAL_HASH_SIZE)) {
+ return true;
+ }
+ }
+ /* NOTREACHED */
+}
+#endif /* DO_TAGGED_HASH */
+
+/* set - store a value into a location previously found by search
+ *
+ * Returns: true success, false failure
+ */
+static bool
+set(searcher *sp, hash_table *tab, void *value)
+{
+ off_t offset;
+
+ if (sp->aborted)
+ return false;
+
+ /* If we have the index file in memory, use it */
+ if ((tab->incore != INCORE_NO) && (sp->place < conf.tsize)) {
+ void *where = (char *)tab->core + (sp->place * tab->reclen);
+
+ memcpy(where, value, tab->reclen);
+ debug("set: incore");
+ if (tab->incore == INCORE_MMAP) {
+ if (innconf->nfswriter) {
+ inn_mapcntl(where, tab->reclen, MS_ASYNC);
+ }
+ return true;
+ }
+ if (!options.writethrough)
+ return true;
+ }
+
+ /* seek to spot */
+ tab->pos = -1; /* invalidate position memory */
+ offset = sp->place * tab->reclen;
+
+ /* write in data */
+ while (pwrite(tab->fd, value, tab->reclen, offset) != tab->reclen) {
+ if (errno == EAGAIN) {
+ fd_set writeset;
+
+ FD_ZERO(&writeset);
+ FD_SET(tab->fd, &writeset);
+ if (select(tab->fd + 1, NULL, &writeset, NULL, NULL) < 1) {
+ syswarn("dbz: set: select failed");
+ sp->aborted = 1;
+ return false;
+ }
+ continue;
+ }
+ syswarn("dbz: set: write failed");
+ sp->aborted = 1;
+ return false;
+ }
+
+ debug("set: succeeded");
+ return true;
+}
+
+#ifdef DO_TAGGED_HASH
+/*
+ - set_pag - store a value into a location previously found by search
+ - on the pag table.
+ - Returns: true success, false failure
+ */
+static bool
+set_pag(searcher *sp, of_t value)
+{
+ of_t v = value;
+
+ if (CANTAG(v)) {
+ v |= sp->tag | taghere;
+ if (v != UNBIAS(VACANT)) /* BIAS(v) won't look VACANT */
+#ifdef OVERFLOW
+ if (v != LONG_MAX) /* and it won't overflow */
+#endif
+ value = v;
+ } else if (canttag_warned == 0) {
+ fprintf(stderr, "dbz.c(set): can't tag value 0x%lx", v);
+ fprintf(stderr, " tagboth = 0x%lx\n", tagboth);
+ canttag_warned = 1;
+ }
+ debug("tagged value is 0x%lx", value);
+ value = BIAS(value);
+
+ return set(sp, &pagtab, &value);
+}
+#endif /* DO_TAGGED_HASH */
+
+/* dbzsetoptions - set runtime options for the database.
+ */
+void
+dbzsetoptions(const dbzoptions o)
+{
+ options = o;
+#ifndef HAVE_MMAP
+ /* Without a working mmap on files, we should avoid it. */
+ if (options.pag_incore == INCORE_MMAP) options.pag_incore = INCORE_NO;
+ if (options.exists_incore == INCORE_MMAP) options.exists_incore = INCORE_NO;
+#endif
+}
+
+/* dbzgetoptions - get runtime options for the database.
+ */
+void
+dbzgetoptions(dbzoptions *o)
+{
+ *o = options;
+}
+
+
+#ifdef DBZTEST
+
+int
+timediffms(struct timeval start, struct timeval end)
+{
+ return (((end.tv_sec - start.tv_sec) * 1000) +
+ ((end.tv_usec - start.tv_usec)) / 1000);
+}
+
+void
+RemoveDBZ(char *filename)
+{
+ char *fn;
+
+#ifdef DO_TAGGED_HASH
+ fn = concat(filename, pag, (char *) 0);
+ unlink(fn);
+ free(fn);
+#else
+ fn = concat(filename, exists, (char *) 0);
+ unlink(fn);
+ free(fn);
+ fn = concat(filename, idx, (char *) 0);
+ unlink(fn);
+ free(fn);
+#endif
+ fn = concat(filename, dir, (char *) 0);
+ unlink(fn);
+ free(fn);
+}
+
+static void
+usage(void)
+{
+ fprintf(stderr, "usage: dbztest [-i] [-n|m] [-N] [-s size] <history>\n");
+#ifdef DO_TAGGED_HASH
+ fprintf(stderr, " -i initialize history. deletes .pag files\n");
+#else
+ fprintf(stderr, " -i initialize history. deletes .exists and .index files\n");
+#endif
+ fprintf(stderr, " -n or m use INCORE_NO, INCORE_MMAP. default = INCORE_MEM\n");
+ fprintf(stderr, " -N using nfswriter mode\n");
+ fprintf(stderr, " -s size number of history lines[2500000]\n");
+ fprintf(stderr, " history history text file\n");
+ exit(1);
+}
+
+int
+main(int argc, char *argv[])
+{
+ int i, line;
+ FILE *fpi;
+ char ibuf[2048], *p;
+ HASH key;
+ off_t where;
+ int initialize = 0, size = 2500000;
+ char *history = NULL;
+ dbzoptions opt;
+ dbz_incore_val incore = INCORE_MEM;
+ struct timeval start, end;
+ off_t ivalue;
+
+ innconf = xcalloc(1, sizeof(struct innconf));
+
+ for (i=1; i<argc; i++)
+ if (strcmp(argv[i], "-i") == 0)
+ initialize = 1;
+ else if (strcmp(argv[i], "-n") == 0)
+ incore = INCORE_NO;
+ else if (strcmp(argv[i], "-N") == 0)
+ innconf->nfswriter = true;
+ else if (strcmp(argv[i], "-m") == 0)
+#if defined(HAVE_MMAP)
+ incore = INCORE_MMAP;
+#else
+ fprintf (stderr, "can't mmap files\n");
+#endif
+ else if (strcmp(argv[i], "-s") == 0)
+ size = atoi(argv[++i]);
+ else if (*argv[i] != '-' && history == NULL)
+ history = argv[i];
+ else
+ usage();
+
+ if (history == NULL)
+ usage();
+ if ((fpi = fopen(history, "r")) == NULL) {
+ fprintf(stderr, "can't open %s\n", history);
+ usage();
+ }
+
+ dbzgetoptions(&opt);
+ opt.pag_incore = incore;
+ dbzsetoptions(opt);
+
+ if (initialize) {
+ RemoveDBZ(history);
+ gettimeofday(&start, NULL);
+ if (dbzfresh(history, dbzsize(size)) < 0) {
+ fprintf(stderr, "cant dbzfresh %s\n", history);
+ exit(1);
+ }
+ gettimeofday(&end, NULL);
+ printf("dbzfresh: %d msec\n", timediffms(start, end));
+ } else {
+ gettimeofday(&start, NULL);
+ if (dbzinit(history) < 0) {
+ fprintf(stderr, "cant dbzinit %s\n", history);
+ exit(1);
+ }
+ gettimeofday(&end, NULL);
+ printf("dbzinit: %d msec\n", timediffms(start, end));
+ }
+
+ gettimeofday(&start, NULL);
+ where = ftello(fpi);
+ for (line=1; fgets(ibuf, sizeof(ibuf), fpi); line++, where=ftello(fpi)) {
+ if (*ibuf == '<') {
+ if ((p = strchr(ibuf, '\t')) == NULL) {
+ fprintf(stderr, "ignoreing bad line: %s\n", ibuf);
+ continue;
+ }
+ *p = '\0';
+ key = HashMessageID(ibuf);
+ } else if (*ibuf == '[')
+ key = TextToHash(ibuf+1);
+ else
+ continue;
+ if (initialize) {
+ if (dbzstore(key, where) == DBZSTORE_ERROR) {
+ fprintf(stderr, "cant store %s\n", ibuf);
+ exit(1);
+ }
+ } else {
+ if (!dbzfetch(key, &ivalue)) {
+ fprintf(stderr, "line %d can't fetch %s\n", line, ibuf);
+ exit(1);
+ }
+ }
+ }
+ line--;
+ gettimeofday(&end, NULL);
+ i = timediffms(start, end);
+ printf("%s: %d lines %.3f msec/id\n",
+ (initialize) ? "dbzstore" : "dbzfetch",
+ line, (double)i / (double)line);
+
+ gettimeofday(&end, NULL);
+ dbzclose();
+ gettimeofday(&end, NULL);
+ printf("dbzclose: %d msec\n", timediffms(start, end));
+ return(0);
+}
+#endif /* DBZTEST */
--- /dev/null
+/* $Id: defdist.c 6135 2003-01-19 01:15:40Z rra $
+**
+*/
+
+#include "config.h"
+#include "clibrary.h"
+#include <ctype.h>
+#include <errno.h>
+
+#include "inn/innconf.h"
+#include "libinn.h"
+#include "paths.h"
+
+
+typedef struct _DDENTRY {
+ char *Pattern;
+ char *Value;
+ int Weight;
+} DDENTRY;
+
+struct _DDHANDLE {
+ int Count;
+ DDENTRY *Entries;
+ DDENTRY *Current;
+};
+typedef struct _DDHANDLE DDHANDLE;
+
+struct _DDHANDLE *
+DDstart(FILE *FromServer, FILE *ToServer)
+{
+ DDHANDLE *h;
+ DDENTRY *ep;
+ FILE *F;
+ char buff[BUFSIZ];
+ char *p;
+ char *q;
+ char *path;
+ int i;
+ int fd;
+ char *name = NULL;
+
+ /* Open the file. */
+ path = concatpath(innconf->pathetc, _PATH_DISTPATS);
+ F = fopen(path, "r");
+ free(path);
+ if (F == NULL) {
+ /* Not available locally; try remotely. */
+ if (FromServer == NULL || ToServer == NULL)
+ /* We're probably nnrpd running on the server and the
+ * file isn't installed. Oh well. */
+ return NULL;
+ name = concatpath(innconf->pathtmp, _PATH_TEMPACTIVE);
+ fd = mkstemp(name);
+ if (fd < 0)
+ return NULL;
+ close(fd);
+ if ((F = CA_listopen(name, FromServer, ToServer,
+ "distrib.pats")) == NULL)
+ return NULL;
+ }
+
+ /* Count lines. */
+ for (i = 0; fgets(buff, sizeof buff, F) != NULL; i++)
+ continue;
+
+ /* Allocate space for the handle. */
+ if ((h = xmalloc(sizeof(DDHANDLE))) == NULL) {
+ i = errno;
+ fclose(F);
+ if (name != NULL)
+ unlink(name);
+ errno = i;
+ return NULL;
+ }
+ h->Count = 0;
+ h->Current = NULL;
+ if (i == 0) {
+ return NULL ;
+ } else if ((h->Entries = xmalloc(sizeof(DDENTRY) * i)) == NULL) {
+ i = errno;
+ free(h);
+ fclose(F);
+ if (name != NULL)
+ unlink(name);
+ errno = i;
+ return NULL;
+ }
+
+ fseeko(F, 0, SEEK_SET);
+ for (ep = h->Entries; fgets(buff, sizeof buff, F) != NULL; ) {
+ if ((p = strchr(buff, '\n')) != NULL)
+ *p = '\0';
+ if (buff[0] == '\0' || buff[0] == '#')
+ continue;
+ if ((p = strchr(buff, ':')) == NULL
+ || (q = strchr(p + 1, ':')) == NULL)
+ continue;
+ *p++ = '\0';
+ ep->Weight = atoi(buff);
+ ep->Pattern = xstrdup(p);
+ q = strchr(ep->Pattern, ':');
+ *q++ = '\0';
+ ep->Value = q;
+ ep++;
+ }
+ h->Count = ep - h->Entries;
+
+ fclose(F);
+ if (name != NULL)
+ unlink(name);
+ return h;
+}
+
+
+void
+DDcheck(DDHANDLE *h, char *group)
+{
+ DDENTRY *ep;
+ int i;
+ int w;
+
+ if (h == NULL || group == NULL)
+ return;
+
+ w = h->Current ? h->Current->Weight : -1;
+ for (ep = h->Entries, i = h->Count; --i >= 0; ep++)
+ if (ep->Weight > w && uwildmat(group, ep->Pattern)) {
+ h->Current = ep;
+ w = ep->Weight;
+ }
+}
+
+
+char *
+DDend(DDHANDLE *h)
+{
+ static char NIL[] = "";
+ char *p;
+ int i;
+ DDENTRY *ep;
+
+ if (h == NULL) {
+ p = NIL;
+ return xstrdup(p);
+ }
+
+ if (h->Current == NULL)
+ p = NIL;
+ else
+ p = h->Current->Value;
+ p = xstrdup(p);
+
+ for (ep = h->Entries, i = h->Count; --i >= 0; ep++)
+ free(ep->Pattern);
+ free(h->Entries);
+ free(h);
+ return p;
+}
+
+#if defined(TEST)
+int
+main(int ac, char *av[])
+{
+ struct _DDHANDLE *h;
+ char *p;
+ FILE *FromServer;
+ FILE *ToServer;
+ char buff[SMBUF];
+
+ if (NNTPremoteopen(NNTP_PORT, &FromServer, &ToServer, buff) < 0) {
+ if ((p = strchr(buff, '\n')) != NULL)
+ *p = '\0';
+ if ((p = strchr(buff, '\r')) != NULL)
+ *p = '\0';
+ if (buff[0])
+ fprintf(stderr, "%s\n", buff);
+ else
+ perror("Can't connect");
+ exit(1);
+ }
+
+ if ((h = DDstart(FromServer, ToServer)) == NULL)
+ perror("Init failed, proceeding anyway");
+ while ((p = *++av) != NULL)
+ DDcheck(h, p);
+ p = DDend(h);
+ printf(">%s<\n", p);
+ exit(0);
+ /* NOTREACHED */
+}
+#endif /* defined(TEST) */
--- /dev/null
+/* $Id: fdflags.c 4740 2001-05-14 03:52:34Z rra $
+**
+** Set or clear file descriptor flags.
+**
+** Simple functions (wrappers around fcntl) to set or clear file descriptor
+** flags like close on exec or nonblocking I/O.
+*/
+
+#include "config.h"
+#include "clibrary.h"
+#include "libinn.h"
+#include <errno.h>
+#include <fcntl.h>
+
+/*
+** Set a file to close on exec.
+**
+** One is supposed to retrieve the flags, add FD_CLOEXEC, and then set
+** them, although I've never seen a system with any flags other than
+** close-on-exec. Do it right anyway; it's not that expensive. Avoid
+** changing errno. Errors are ignored, since it generally doesn't cause
+** significant harm to fail.
+*/
+void
+close_on_exec(int fd, bool flag)
+{
+ int oerrno;
+ int oflag;
+
+ oerrno = errno;
+ oflag = fcntl(fd, F_GETFD, 0);
+ if (oflag < 0) {
+ errno = oerrno;
+ return;
+ }
+ fcntl(fd, F_SETFD, flag ? (oflag | FD_CLOEXEC) : (oflag & ~FD_CLOEXEC));
+ errno = oerrno;
+}
+
+
+/*
+** Set a file descriptor to nonblocking (or clear the nonblocking flag if
+** flag is false).
+**
+** Always use O_NONBLOCK; O_NDELAY is *not* the same thing historically.
+** The semantics of O_NDELAY are that if the read would block, it returns 0
+** instead. This is indistinguishable from an end of file condition.
+** POSIX added O_NONBLOCK, which requires read to return -1 and set errno
+** to EAGAIN, which is what we want.
+**
+** FNDELAY (4.3BSD) originally did the correct thing, although it has a
+** different incompatibility (affecting all users of a socket rather than
+** just a file descriptor and returning EWOULDBLOCK instead of EAGAIN) that
+** we don't care about in INN. Using it is *probably* safe, but BSD should
+** also have the ioctl, and at least on Solaris FNDELAY does the same thing
+** as O_NDELAY, not O_NONBLOCK. So if we don't have O_NONBLOCK, fall back
+** to the ioctl instead.
+**
+** Reference: Stevens, Advanced Unix Programming, pg. 364.
+**
+** Note that O_NONBLOCK is known not to work on earlier versions of ULTRIX,
+** SunOS, and AIX, possibly not setting the socket nonblocking at all,
+** despite the fact that they do define it. It works in later SunOS and,
+** current AIX, however, and a 1999-10-25 survey of current operating
+** systems failed to turn up any that didn't handle it correctly (as
+** required by POSIX), while HP-UX 11.00 did use the broken return-zero
+** semantics of O_NDELAY (most other operating systems surveyed treated
+** O_NDELAY as synonymous with O_NONBLOCK). Accordingly, we currently
+** unconditionally use O_NONBLOCK. If this causes too many problems, an
+** autoconf test may be required.
+*/
+
+#ifdef O_NONBLOCK
+
+int
+nonblocking(int fd, bool flag)
+{
+ int mode;
+
+ mode = fcntl(fd, F_GETFL, 0);
+ if (mode < 0)
+ return -1;
+ mode = (flag ? (mode | O_NONBLOCK) : (mode & ~O_NONBLOCK));
+ return fcntl(fd, F_SETFL, mode);
+}
+
+#else /* !O_NONBLOCK */
+
+#include <sys/ioctl.h>
+#if HAVE_SYS_FILIO_H
+# include <sys/filio.h>
+#endif
+
+int
+nonblocking(int fd, bool flag)
+{
+ int state;
+
+ state = flag ? 1 : 0;
+ return ioctl(fd, FIONBIO, &state);
+}
+
+#endif /* !O_NONBLOCK */
--- /dev/null
+/* $Id: fdlimit.c 4511 2001-02-07 23:41:08Z rra $
+**
+** Portably determine or set the limit on open file descriptors.
+**
+** Pretty much all platforms these days have getrlimit and setrlimit, so
+** prefer those, but for determining the current limit preserve the old
+** portability (if we don't have getrlimit, try sysconf, then
+** getdtablesize, then ulimit, and then try to find a hard-coded constant
+** in <sys/param.h> and failing that fall back to the POSIX-guaranteed
+** minimum of 20.
+**
+** For setting the limit, only setrlimit is supported; if it isn't
+** available, return -1 always. We also refuse to set the limit to
+** something higher than select can handle, checking against FD_SETSIZE.
+**
+** Note that on some versions of Linux (2.2.x reported), sysconf may return
+** the wrong value for the maximum file descriptors. getrlimit is correct,
+** so always prefer it.
+*/
+
+#include "config.h"
+#include "clibrary.h"
+#include <errno.h>
+#if HAVE_SYS_SELECT_H
+# include <sys/select.h>
+#endif
+
+/* FreeBSD 3.4 RELEASE needs <sys/time.h> before <sys/resource.h>. */
+#if HAVE_GETRLIMIT || HAVE_SETRLIMIT
+# if HAVE_SYS_TIME_H
+# include <sys/time.h>
+# endif
+# include <sys/resource.h>
+#endif
+
+#include "libinn.h"
+
+#if HAVE_SETRLIMIT && defined(RLIMIT_NOFILE)
+
+int
+setfdlimit(unsigned int limit)
+{
+ struct rlimit rl;
+
+#ifdef FD_SETSIZE
+ if (limit > FD_SETSIZE) {
+ errno = EINVAL;
+ return -1;
+ }
+#endif
+
+ rl.rlim_cur = 0;
+ rl.rlim_max = 0;
+
+#if HAVE_GETRLIMIT
+ if (getrlimit(RLIMIT_NOFILE, &rl) < 0) {
+ rl.rlim_cur = 0;
+ rl.rlim_max = 0;
+ }
+#endif
+
+ rl.rlim_cur = limit;
+ if (limit > rl.rlim_max)
+ rl.rlim_max = limit;
+ return setrlimit(RLIMIT_NOFILE, &rl);
+}
+
+#else /* !(HAVE_SETRLIMIT && RLIMIT_NOFILE) */
+
+int
+setfdlimit(unsigned int limit UNUSED)
+{
+ /* Unimplemented system call is close enough. */
+ errno = ENOSYS;
+ return -1;
+}
+
+#endif /* !(HAVE_SETRLIMIT && RLIMIT_NOFILE) */
+
+#if HAVE_GETRLIMIT && defined(RLIMIT_NOFILE)
+
+int
+getfdlimit(void)
+{
+ struct rlimit rl;
+
+ if (getrlimit(RLIMIT_NOFILE, &rl) < 0)
+ return -1;
+ return rl.rlim_cur;
+}
+
+#elif HAVE_SYSCONF
+
+int
+getfdlimit(void)
+{
+ return sysconf(_SC_OPEN_MAX);
+}
+
+#elif HAVE_GETDTABLESIZE
+
+int
+getfdlimit(void)
+{
+ return getdtablesize();
+}
+
+#elif HAVE_ULIMIT
+
+int
+getfdlimit(void)
+{
+# ifdef UL_GDESLIM
+ return ulimit(UL_GDESLIM, 0);
+# else
+ return ulimit(4, 0);
+# endif
+}
+
+#else /* no function mechanism available */
+# if HAVE_LIMITS_H
+# include <limits.h>
+# endif
+# include <sys/param.h>
+
+int
+getfdcount(void)
+{
+# ifdef NOFILE
+ return NOFILE;
+# else
+ return 20;
+# endif
+}
+
+#endif
--- /dev/null
+/* $Id: fseeko.c 3680 2000-07-29 22:54:52Z rra $
+**
+** Replacement for a missing fseeko.
+**
+** fseeko is a version of fseek that takes an off_t instead of a long. For
+** large file support (and because it's a more logical interface), INN uses
+** fseeko unconditionally; if fseeko isn't provided by a platform but
+** fpos_t is compatible with off_t (as in BSDI), define it in terms of
+** fsetpos. Otherwise, just call fseek (which won't work for files over
+** 2GB).
+*/
+
+#include "config.h"
+#include "clibrary.h"
+#include <errno.h>
+
+#if HAVE_LARGE_FPOS_T
+
+int
+fseeko(FILE *stream, off_t pos, int whence)
+{
+ fpos_t fpos;
+
+ switch (whence) {
+ case SEEK_SET:
+ fpos = pos;
+ return fsetpos(stream, &fpos);
+
+ case SEEK_END:
+ if (fseek(stream, 0, SEEK_END) < 0)
+ return -1;
+ if (pos == 0)
+ return 0;
+ /* Fall through. */
+
+ case SEEK_CUR:
+ if (fgetpos(stream, &fpos) < 0)
+ return -1;
+ fpos += pos;
+ return fsetpos(stream, &fpos);
+
+ default:
+ errno = EINVAL;
+ return -1;
+ }
+}
+
+#else /* !HAVE_LARGE_FPOS_T */
+
+int
+fseeko(FILE *stream, off_t pos, int whence)
+{
+ return fseek(stream, (long) pos, whence);
+}
+
+#endif /* !HAVE_LARGE_FPOS_T */
--- /dev/null
+/* $Id: ftello.c 3681 2000-07-29 22:55:18Z rra $
+**
+** Replacement for a missing ftello.
+**
+** ftello is a version of ftell that returns an off_t instead of a long.
+** For large file support (and because it's a more logical interface), INN
+** uses ftello unconditionally; if ftello isn't provided by a platform but
+** fpos_t is compatible with off_t (as in BSDI), define it in terms of
+** fgetpos. Otherwise, just call ftell (which won't work for files over
+** 2GB).
+*/
+
+#include "config.h"
+#include "clibrary.h"
+
+#if HAVE_LARGE_FPOS_T
+
+off_t
+ftello(FILE *stream)
+{
+ fpos_t fpos;
+
+ if (fgetpos(stream, &fpos) < 0) {
+ return -1;
+ } else {
+ return (off_t) fpos;
+ }
+}
+
+#else /* !HAVE_LARGE_FPOS_T */
+
+off_t
+ftello(FILE *stream)
+{
+ return (off_t) ftell(stream);
+}
+
+#endif /* !HAVE_LARGE_FPOS_T */
--- /dev/null
+/* $Id: genid.c 6135 2003-01-19 01:15:40Z rra $
+**
+** Generate a message ID.
+*/
+
+#include "config.h"
+#include "clibrary.h"
+
+#include "inn/innconf.h"
+#include "libinn.h"
+
+/* Scale time back a bit, for shorter Message-ID's. */
+#define OFFSET 673416000L
+
+char *
+GenerateMessageID(char *domain)
+{
+ static char buff[SMBUF];
+ static int count;
+ char *p;
+ char sec32[10];
+ char pid32[10];
+ TIMEINFO Now;
+
+ if (GetTimeInfo(&Now) < 0)
+ return NULL;
+ Radix32(Now.time - OFFSET, sec32);
+ Radix32(getpid(), pid32);
+ if ((domain != NULL && innconf->domain == NULL) ||
+ (domain != NULL && innconf->domain != NULL
+ && strcmp(domain, innconf->domain) != 0)) {
+ p = domain;
+ } else {
+ if ((p = GetFQDN(domain)) == NULL)
+ return NULL;
+ }
+ snprintf(buff, sizeof(buff), "<%s$%s$%d@%s>", sec32, pid32, ++count, p);
+ return buff;
+}
--- /dev/null
+/* $Id: getfqdn.c 6155 2003-01-19 19:58:25Z rra $
+**
+*/
+
+#include "config.h"
+#include "clibrary.h"
+#include <netdb.h>
+
+#include "libinn.h"
+#include "paths.h"
+
+
+/*
+** Get the fully-qualified domain name for this host.
+*/
+char *GetFQDN(char *domain)
+{
+ static char buff[SMBUF];
+ struct hostent *hp;
+ char *p;
+ char **ap;
+#if 0
+ /* See comments below. */
+ char temp[SMBUF + 2];
+#endif /* 0 */
+
+ /* Return any old results. */
+ if (buff[0])
+ return buff;
+
+ /* Try gethostname. */
+ if (gethostname(buff, (int)sizeof buff) < 0)
+ return NULL;
+ if (strchr(buff, '.') != NULL)
+ return buff;
+
+ /* See if DNS (or /etc/hosts) gives us a full domain name. */
+ if ((hp = gethostbyname(buff)) == NULL)
+ return NULL;
+#if 0
+ /* This code is a "feature" that allows multiple domains (NIS or
+ * DNS, I'm not sure) to work with a single INN server. However,
+ * it turns out to cause more problems for people, and they have to
+ * use hacks like __switch_gethostbyname, etc. So if you need this,
+ * turn it on, but don't complain to me. */
+ if (strchr(hp->h_name, '.') == NULL) {
+ /* Try to force DNS lookup if NIS/whatever gets in the way. */
+ strlcpy(temp, buff, sizeof(temp));
+ strlcat(temp, ".", sizeof(temp));
+ hp = gethostbyname(temp);
+ }
+#endif /* 0 */
+
+ /* First, see if the main name is a FQDN. It should be. */
+ if (hp != NULL && strchr(hp->h_name, '.') != NULL) {
+ if (strlen(hp->h_name) < sizeof buff - 1) {
+ strlcpy(buff, hp->h_name, sizeof(buff));
+ return buff;
+ }
+ /* Doesn't fit; make sure we don't return bad data next time. */
+ buff[0] = '\0';
+ return hp->h_name;
+ }
+
+ /* Second, see if any aliases are. */
+ if ((ap = hp->h_aliases) != NULL)
+ while ((p = *ap++) != NULL)
+ if (strchr(p, '.') != NULL) {
+ /* Deja-vous all over again. */
+ if (strlen(p) < sizeof buff - 1) {
+ strlcpy(buff, p, sizeof(buff));
+ return buff;
+ }
+ buff[0] = '\0';
+ return p ;
+ }
+
+ /* Give up: Get the domain config param and append it. */
+ if ((p = domain) == NULL || *p == '\0')
+ return NULL;
+ if (strlen(buff) + 1 + strlen(p) > sizeof buff - 1)
+ /* Doesn't fit. */
+ return NULL;
+ strlcat(buff, ".", sizeof(buff));
+ strlcat(buff, p, sizeof(buff));
+ return buff;
+}
--- /dev/null
+/* $Id: getmodaddr.c 6155 2003-01-19 19:58:25Z rra $
+**
+*/
+
+#include "config.h"
+#include "clibrary.h"
+#include <errno.h>
+
+#include "inn/innconf.h"
+#include "libinn.h"
+#include "nntp.h"
+#include "paths.h"
+
+
+static char *GMApathname = NULL;
+static FILE *GMAfp = NULL;
+
+
+/*
+** Close the file opened by GMAlistopen.
+*/
+static void
+GMAclose(void)
+{
+ if (GMAfp) {
+ fclose(GMAfp);
+ GMAfp = NULL;
+ }
+ if (GMApathname != NULL) {
+ unlink(GMApathname);
+ free(GMApathname);
+ GMApathname = NULL;
+ }
+}
+
+/*
+** Internal library routine.
+*/
+static FILE *
+GMA_listopen(int fd, FILE *FromServer, FILE *ToServer, const char *request)
+{
+ char buff[BUFSIZ];
+ char *p;
+ int oerrno;
+ FILE *F;
+
+ F = fdopen(fd, "r+");
+ if (F == NULL)
+ return NULL;
+
+ /* Send a LIST command to and capture the output. */
+ if (request == NULL)
+ fprintf(ToServer, "list moderators\r\n");
+ else
+ fprintf(ToServer, "list %s\r\n", request);
+ fflush(ToServer);
+
+ /* Get the server's reply to our command. */
+ if (fgets(buff, sizeof buff, FromServer) == NULL
+ || strncmp(buff, NNTP_LIST_FOLLOWS, strlen(NNTP_LIST_FOLLOWS)) != 0) {
+ oerrno = errno;
+ fclose(F);
+ GMAclose();
+ errno = oerrno;
+ return NULL;
+ }
+
+ /* Slurp up the rest of the response. */
+ while (fgets(buff, sizeof buff, FromServer) != NULL) {
+ if ((p = strchr(buff, '\r')) != NULL)
+ *p = '\0';
+ if ((p = strchr(buff, '\n')) != NULL)
+ *p = '\0';
+ if (buff[0] == '.' && buff[1] == '\0') {
+ if (ferror(F) || fflush(F) == EOF || fseeko(F, 0, SEEK_SET) != 0)
+ break;
+ return F;
+ }
+ fprintf(F, "%s\n", buff);
+ }
+
+ /* Ran out of input before finding the terminator; quit. */
+ oerrno = errno;
+ fclose(F);
+ GMAclose();
+ errno = oerrno;
+ return NULL;
+}
+
+/*
+** Read the moderators file, looking for a moderator.
+*/
+char *
+GetModeratorAddress(FILE *FromServer, FILE *ToServer, char *group,
+ char *moderatormailer)
+{
+ static char address[SMBUF];
+ char *p;
+ char *save;
+ char *path;
+ char buff[BUFSIZ];
+ char name[SMBUF];
+ int fd;
+
+ strlcpy(name, group, sizeof(name));
+ address[0] = '\0';
+
+ if (FromServer==NULL || ToServer==NULL){
+
+ /*
+ * This should be part of nnrpd or the like running on the server.
+ * Open the server copy of the moderators file.
+ */
+ path = concatpath(innconf->pathetc, _PATH_MODERATORS);
+ GMAfp = fopen(path, "r");
+ free(path);
+ }else{
+ /*
+ * Get a local copy of the moderators file from the server.
+ */
+ GMApathname = concatpath(innconf->pathtmp, _PATH_TEMPMODERATORS);
+ fd = mkstemp(GMApathname);
+ if (fd >= 0)
+ GMAfp = GMA_listopen(fd, FromServer, ToServer, "moderators");
+ else
+ GMAfp = NULL;
+
+ /* Fallback to the local copy if the server doesn't have it */
+ if (GMAfp == NULL) {
+ path = concatpath(innconf->pathetc, _PATH_MODERATORS);
+ GMAfp = fopen(path, "r");
+ free(path);
+ }
+ }
+
+ if (GMAfp != NULL) {
+ while (fgets(buff, sizeof buff, GMAfp) != NULL) {
+ /* Skip blank and comment lines. */
+ if ((p = strchr(buff, '\n')) != NULL)
+ *p = '\0';
+ if (buff[0] == '\0' || buff[0] == '#')
+ continue;
+
+ /* Snip off the first word. */
+ if ((p = strchr(buff, ':')) == NULL)
+ /* Malformed line... */
+ continue;
+ *p++ = '\0';
+
+ /* If it pattern-matches the newsgroup, the second field is a
+ * format for mailing, with periods changed to dashes. */
+ if (uwildmat(name, buff)) {
+ for (save = p; ISWHITE(*save); save++)
+ continue;
+ for (p = name; *p; p++)
+ if (*p == '.')
+ *p = '-';
+ snprintf(address, sizeof(address), save, name);
+ break;
+ }
+ }
+
+ GMAclose();
+ if (address[0])
+ return address;
+ }
+
+ /* If we don't have an address, see if the config file has a default. */
+ if ((save = moderatormailer) == NULL)
+ return NULL;
+
+ for (p = name; *p; p++)
+ if (*p == '.')
+ *p = '-';
+ snprintf(address, sizeof(address), save, name);
+ return address;
+}
--- /dev/null
+/* $Id: getpagesize.c 5596 2002-08-17 21:29:35Z rra $
+**
+** Replacement for a missing getpagesize.
+**
+** Provides getpagesize implemented in terms of sysconf for those systems
+** that don't have the getpagesize function. Defaults to a page size of 16KB
+** if sysconf isn't available either.
+*/
+
+#include "config.h"
+#include <unistd.h>
+
+int
+getpagesize(void)
+{
+ int pagesize;
+
+#ifdef _SC_PAGESIZE
+ pagesize = sysconf(_SC_PAGESIZE);
+#else
+ pagesize = 16 * 1024;
+#endif
+ return pagesize;
+}
--- /dev/null
+/* $Id: gettime.c 4135 2000-10-19 16:38:13Z kondou $
+**
+** Find and return time information portably.
+*/
+#include "config.h"
+#include "libinn.h"
+
+#ifdef HAVE_UNISTD_H
+# include <unistd.h>
+#endif
+
+#ifdef TIME_WITH_SYS_TIME
+# include <sys/time.h>
+# include <time.h>
+#else
+# ifdef HAVE_SYS_TIME_H
+# include <sys/time.h>
+# else
+# include <time.h>
+# endif
+#endif
+
+
+int
+GetTimeInfo(TIMEINFO *Now)
+{
+ static time_t NextHour;
+ static long LastTzone;
+ struct tm *tm;
+ int secondsUntilNextHour;
+
+ struct timeval tv;
+
+#ifndef HAVE_TM_GMTOFF
+ struct tm local;
+ struct tm gmt;
+#endif
+
+ /* Get the basic time. */
+ if (gettimeofday(&tv, (struct timezone *) 0) == -1)
+ return -1;
+ Now->time = tv.tv_sec;
+ Now->usec = tv.tv_usec;
+
+ /* Now get the timezone if the last time < HH:00:00 <= now for some HH. */
+ if (NextHour <= Now->time) {
+ tm = localtime(&Now->time);
+ if (tm == NULL)
+ return -1;
+ secondsUntilNextHour = 60 * (60 - tm->tm_min) - tm->tm_sec;
+
+#ifdef HAVE_TM_GMTOFF
+ LastTzone = (0 - tm->tm_gmtoff) / 60;
+#else
+ /* To get the timezone, compare localtime with GMT. */
+ local = *tm;
+ if ((tm = gmtime(&Now->time)) == NULL)
+ return -1;
+ gmt = *tm;
+
+ /* Assume we are never more than 24 hours away. */
+ LastTzone = gmt.tm_yday - local.tm_yday;
+ if (LastTzone > 1)
+ LastTzone = -24;
+ else if (LastTzone < -1)
+ LastTzone = 24;
+ else
+ LastTzone *= 24;
+
+ /* Scale in the hours and minutes; ignore seconds. */
+ LastTzone += gmt.tm_hour - local.tm_hour;
+ LastTzone *= 60;
+ LastTzone += gmt.tm_min - local.tm_min;
+#endif /* defined(HAVE_TM_GMTOFF) */
+
+ NextHour = Now->time + secondsUntilNextHour;
+ }
+ Now->tzone = LastTzone;
+ return 0;
+}
--- /dev/null
+/* This provides a generic hash function for use w/INN. Currently
+ is implemented using MD5, but should probably have a mechanism for
+ choosing the hash algorithm and tagging the hash with the algorithm
+ used */
+#include "config.h"
+#include "clibrary.h"
+#include <ctype.h>
+
+#include "inn/md5.h"
+#include "libinn.h"
+
+static HASH empty= { { 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0 }};
+
+/* cipoint - where in this message-ID does it become case-insensitive?
+ *
+ * The RFC822 code is not quite complete. Absolute, total, full RFC822
+ * compliance requires a horrible parsing job, because of the arcane
+ * quoting conventions -- abc"def"ghi is not equivalent to abc"DEF"ghi,
+ * for example. There are three or four things that might occur in the
+ * domain part of a message-id that are case-sensitive. They don't seem
+ * to ever occur in real news, thank Cthulhu. (What? You were expecting
+ * a merciful and forgiving deity to be invoked in connection with RFC822?
+ * Forget it; none of them would come near it.)
+ *
+ * Returns: pointer into s, or NULL for "nowhere"
+ */
+static const char *
+cipoint(const char *s, size_t size)
+{
+ char *p;
+ static const char post[] = "postmaster";
+ static int plen = sizeof(post) - 1;
+
+ if ((p = memchr(s, '@', size))== NULL) /* no local/domain split */
+ return NULL; /* assume all local */
+ if ((p - (s + 1) == plen) && !strncasecmp(post, s+1, plen)) {
+ /* crazy -- "postmaster" is case-insensitive */
+ return s;
+ }
+ return p;
+}
+
+HASH
+Hash(const void *value, const size_t len)
+{
+ struct md5_context context;
+ HASH hash;
+
+ md5_init(&context);
+ md5_update(&context, value, len);
+ md5_final(&context);
+ memcpy(&hash,
+ &context.digest,
+ (sizeof(hash) < sizeof(context.digest)) ? sizeof(hash) : sizeof(context.digest));
+ return hash;
+}
+
+HASH
+HashMessageID(const char *MessageID)
+{
+ char *new = NULL;
+ const char *cip, *p = NULL;
+ char *q;
+ int len;
+ HASH hash;
+
+ len = strlen(MessageID);
+ cip = cipoint(MessageID, len);
+ if (cip != NULL) {
+ for (p = cip + 1; *p != '\0'; p++) {
+ if (!CTYPE(islower, *p)) {
+ new = xstrdup(MessageID);
+ break;
+ }
+ }
+ }
+ if (new != NULL)
+ for (q = new + (p - MessageID); *q != '\0'; q++)
+ *q = tolower(*q);
+ hash = Hash(new ? new : MessageID, len);
+ if (new != NULL)
+ free(new);
+ return hash;
+}
+
+/*
+** Check if the hash is all zeros, and subseqently empty, see HashClear
+** for more info on this.
+*/
+bool
+HashEmpty(const HASH h)
+{
+ return (memcmp(&empty, &h, sizeof(HASH)) == 0);
+}
+
+/*
+** Set the hash to all zeros. Using all zeros as the value for empty
+** introduces the possibility of colliding w/a value that actually hashes
+** to all zeros, but that's fairly unlikely.
+*/
+void
+HashClear(HASH *hash)
+{
+ memset(hash, '\0', sizeof(HASH));
+}
+
+/*
+** Convert the binary form of the hash to a form that we can use in error
+** messages and logs.
+*/
+char *
+HashToText(const HASH hash)
+{
+ static const char hex[] = "0123456789ABCDEF";
+ const char *p;
+ unsigned int i;
+ static char hashstr[(sizeof(HASH)*2) + 1];
+
+ for (p = hash.hash, i = 0; i < sizeof(HASH); i++, p++) {
+ hashstr[i * 2] = hex[(*p & 0xF0) >> 4];
+ hashstr[(i * 2) + 1] = hex[*p & 0x0F];
+ }
+ hashstr[(sizeof(HASH) * 2)] = '\0';
+ return hashstr;
+}
+
+/*
+** Converts a hex digit and converts it to a int
+*/
+static
+int hextodec(const int c)
+{
+ return isdigit(c) ? (c - '0') : ((c - 'A') + 10);
+}
+
+/*
+** Convert the ASCII representation of the hash back to the canonical form
+*/
+HASH
+TextToHash(const char *text)
+{
+ char *q;
+ int i;
+ HASH hash;
+
+ for (q = (char *)&hash, i = 0; i != sizeof(HASH); i++) {
+ q[i] = (hextodec(text[i * 2]) << 4) + hextodec(text[(i * 2) + 1]);
+ }
+ return hash;
+}
+
+/* This is rather gross, we compare like the last 4 bytes of the
+ hash are at the beginning because dbz considers them to be the
+ most significant bytes */
+int HashCompare(const HASH *h1, const HASH *h2) {
+ int i;
+ int tocomp = sizeof(HASH) - sizeof(unsigned long);
+
+ if ((i = memcmp(&h1->hash[tocomp], &h1->hash[tocomp], sizeof(unsigned long))))
+ return i;
+ return memcmp(h1, h2, sizeof(HASH));
+}
--- /dev/null
+/* $Id: hashtab.c 6135 2003-01-19 01:15:40Z rra $
+**
+** Generic hash table implementation.
+**
+** Written by Russ Allbery <rra@stanford.edu>
+** This work is hereby placed in the public domain by its author.
+**
+** This is a generic hash table implementation with linear probing. It
+** takes a comparison function and a hashing function and stores void *.
+**
+** Included for the use of callers is the hash function LOOKUP2 by Bob
+** Jenkins, taken from <http://burtleburtle.net/bob/hash/>; see that web
+** page for analysis and performance comparisons. The performance of this
+** hash is slightly worse than the standard sum and modulus hash function
+** seen in many places but it produces fewer collisions.
+*/
+
+#include "config.h"
+#include "clibrary.h"
+#include "inn/hashtab.h"
+#include "libinn.h"
+
+/* Magic values for empty and deleted hash table slots. */
+#define HASH_EMPTY ((void *) 0)
+#define HASH_DELETED ((void *) 1)
+
+struct hash {
+ size_t size; /* Allocated size. */
+ size_t mask; /* Used to resolve a hash to an index. */
+ size_t nelements; /* Total elements, including deleted. */
+ size_t ndeleted; /* Number of deleted elements. */
+
+ unsigned long searches; /* Count of lookups (for debugging). */
+ unsigned long collisions; /* Count of collisions (for debugging). */
+ unsigned long expansions; /* Count of hash resizes needed. */
+
+ hash_func hash; /* Return hash of a key. */
+ hash_key_func key; /* Given an element, returns its key. */
+ hash_equal_func equal; /* Whether a key matches an element. */
+ hash_delete_func delete; /* Called when a hash element is deleted. */
+
+ void **table; /* The actual elements. */
+};
+
+
+/*
+** Given a target table size, return the nearest power of two that's
+** greater than or equal to that size, with a minimum size of four. The
+** minimum must be at least four to ensure that there is always at least
+** one empty slot in the table given hash_find_slot's resizing of the table
+** if it as least 75% full. Otherwise, it would be possible for
+** hash_find_slot to go into an infinite loop.
+*/
+static size_t
+hash_size(size_t target)
+{
+ int n;
+ size_t size;
+
+ size = target - 1;
+ for (n = 0; size > 0; n++)
+ size >>= 1;
+ size = 1 << n;
+ return (size < 4) ? 4 : size;
+}
+
+
+/*
+** Create a new hash table. The given size is rounded up to the nearest
+** power of two for speed reasons (it greatly simplifies the use of the
+** hash function).
+*/
+struct hash *
+hash_create(size_t size, hash_func hash_f, hash_key_func key_f,
+ hash_equal_func equal_f, hash_delete_func delete_f)
+{
+ struct hash *hash;
+
+ hash = xcalloc(1, sizeof(struct hash));
+ hash->hash = hash_f;
+ hash->key = key_f;
+ hash->equal = equal_f;
+ hash->delete = delete_f;
+ hash->size = hash_size(size);
+ hash->mask = hash->size - 1;
+ hash->table = xcalloc(hash->size, sizeof(void *));
+ return hash;
+}
+
+
+/*
+** Free a hash and all resources used by it, and call the delete function
+** on every element.
+*/
+void
+hash_free(struct hash *hash)
+{
+ size_t i;
+ void *entry;
+
+ for (i = 0; i < hash->size; i++) {
+ entry = hash->table[i];
+ if (entry != HASH_EMPTY && entry != HASH_DELETED)
+ (*hash->delete)(entry);
+ }
+ free(hash->table);
+ free(hash);
+}
+
+
+/*
+** Internal search function used by hash_expand. This is an optimized
+** version of hash_find_slot that returns a pointer to the first empty
+** slot, not trying to call the equality function on non-empty slots and
+** assuming there are no HASH_DELETED slots.
+*/
+static void **
+hash_find_empty(struct hash *hash, const void *key)
+{
+ size_t slot;
+
+ slot = (*hash->hash)(key) & hash->mask;
+ while (1) {
+ if (hash->table[slot] == HASH_EMPTY)
+ return &hash->table[slot];
+
+ slot++;
+ if (slot >= hash->size)
+ slot -= hash->size;
+ }
+}
+
+
+/*
+** Expand the hash table to be approximately 50% empty based on the number
+** of elements in the hash. This is done by allocating a new table and
+** then calling hash_find_empty for each element in the previous table,
+** recovering the key by calling hash->key on the element.
+*/
+static void
+hash_expand(struct hash *hash)
+{
+ void **old, **slot;
+ size_t i, size;
+
+ old = hash->table;
+ size = hash->size;
+ hash->size = hash_size((hash->nelements - hash->ndeleted) * 2);
+ hash->mask = hash->size - 1;
+ hash->table = xcalloc(hash->size, sizeof(void *));
+
+ hash->nelements = 0;
+ hash->ndeleted = 0;
+ for (i = 0; i < size; i++)
+ if (old[i] != HASH_EMPTY && old[i] != HASH_DELETED) {
+ slot = hash_find_empty(hash, (*hash->key)(old[i]));
+ *slot = old[i];
+ hash->nelements++;
+ }
+
+ hash->expansions++;
+ free(old);
+}
+
+
+/*
+** Find a slot in the hash for a given key. This is used both for
+** inserting and deleting elements from the hash, as well as looking up
+** entries. Returns a pointer to the slot. If insert is true, return the
+** first empty or deleted slot. If insert is false, return NULL if the
+** element could not be found.
+**
+** This function assumes that there is at least one empty slot in the
+** hash; otherwise, it can loop infinitely. It attempts to ensure this by
+** always expanding the hash if it is at least 75% full; this will ensure
+** that property for any hash size of 4 or higher.
+*/
+static void **
+hash_find_slot(struct hash *hash, const void *key, bool insert)
+{
+ void **deleted_slot = NULL;
+ void *entry;
+ size_t slot;
+
+ if (insert && hash->nelements * 4 >= hash->size * 3)
+ hash_expand(hash);
+
+ hash->searches++;
+
+ slot = (*hash->hash)(key) & hash->mask;
+ while (1) {
+ entry = hash->table[slot];
+ if (entry == HASH_EMPTY) {
+ if (!insert)
+ return NULL;
+
+ if (deleted_slot != NULL) {
+ *deleted_slot = HASH_EMPTY;
+ hash->ndeleted--;
+ return deleted_slot;
+ }
+ hash->nelements++;
+ return &hash->table[slot];
+ } else if (entry == HASH_DELETED) {
+ if (insert)
+ deleted_slot = &hash->table[slot];
+ } else if ((*hash->equal)(key, entry)) {
+ return &hash->table[slot];
+ }
+
+ hash->collisions++;
+ slot++;
+ if (slot >= hash->size)
+ slot -= hash->size;
+ }
+}
+
+
+/*
+** Given a key, return the entry corresponding to that key or NULL if that
+** key isn't present in the hash table.
+*/
+void *
+hash_lookup(struct hash *hash, const void *key)
+{
+ void **slot;
+
+ slot = hash_find_slot(hash, key, false);
+ return (slot == NULL) ? NULL : *slot;
+}
+
+
+/*
+** Insert a new key/value pair into the hash, returning true if the
+** insertion was successful and false if there is already a value in the
+** hash with that key.
+*/
+bool
+hash_insert(struct hash *hash, const void *key, void *datum)
+{
+ void **slot;
+
+ slot = hash_find_slot(hash, key, true);
+ if (*slot != HASH_EMPTY)
+ return false;
+ *slot = datum;
+ return true;
+}
+
+
+/*
+** Replace an existing hash value with a new data value, calling the delete
+** function for the old data. Returns true if the replacement was
+** successful or false (without changing the hash) if the key whose value
+** should be replaced was not found in the hash.
+*/
+bool
+hash_replace(struct hash *hash, const void *key, void *datum)
+{
+ void **slot;
+
+ slot = hash_find_slot(hash, key, false);
+ if (slot == NULL)
+ return false;
+ (*hash->delete)(*slot);
+ *slot = datum;
+ return true;
+}
+
+
+/*
+** Delete a key out of the hash. Returns true if the deletion was
+** successful, false if the key could not be found in the hash.
+*/
+bool
+hash_delete(struct hash *hash, const void *key)
+{
+ bool result;
+
+ result = hash_replace(hash, key, HASH_DELETED);
+ if (result)
+ hash->ndeleted++;
+ return result;
+}
+
+
+/*
+** For each element in the hash table, call the provided function, passing
+** it the element and the opaque token that's passed to this function.
+*/
+void
+hash_traverse(struct hash *hash, hash_traverse_func callback, void *data)
+{
+ size_t i;
+ void *entry;
+
+ for (i = 0; i < hash->size; i++) {
+ entry = hash->table[i];
+ if (entry != HASH_EMPTY && entry != HASH_DELETED)
+ (*callback)(entry, data);
+ }
+}
+
+
+/*
+** Returns a count of undeleted elements in the hash.
+*/
+unsigned long
+hash_count(struct hash *hash)
+{
+ return hash->nelements - hash->ndeleted;
+}
+
+
+/*
+** Accessor functions for the debugging statistics.
+*/
+unsigned long
+hash_searches(struct hash *hash)
+{
+ return hash->searches;
+}
+
+unsigned long
+hash_collisions(struct hash *hash)
+{
+ return hash->collisions;
+}
+
+unsigned long
+hash_expansions(struct hash *hash)
+{
+ return hash->expansions;
+}
+
+
+/*
+** Mix three 32-bit values reversibly. This is the internal mixing
+** function for the hash function.
+**
+** For every delta with one or two bit set, and the deltas of all three
+** high bits or all three low bits, whether the original value of a,b,c
+** is almost all zero or is uniformly distributed,
+**
+** * If mix() is run forward or backward, at least 32 bits in a,b,c
+** have at least 1/4 probability of changing.
+**
+** * If mix() is run forward, every bit of c will change between 1/3 and
+** 2/3 of the time. (Well, 22/100 and 78/100 for some 2-bit deltas.)
+**
+** mix() takes 36 machine instructions, but only 18 cycles on a superscalar
+** machine (like a Pentium or a Sparc). No faster mixer seems to work,
+** that's the result of my brute-force search. There were about 2^68
+** hashes to choose from. I (Bob Jenkins) only tested about a billion of
+** those.
+*/
+#define MIX(a, b, c) \
+ { \
+ (a) -= (b); (a) -= (c); (a) ^= ((c) >> 13); \
+ (b) -= (c); (b) -= (a); (b) ^= ((a) << 8); \
+ (c) -= (a); (c) -= (b); (c) ^= ((b) >> 13); \
+ (a) -= (b); (a) -= (c); (a) ^= ((c) >> 12); \
+ (b) -= (c); (b) -= (a); (b) ^= ((a) << 16); \
+ (c) -= (a); (c) -= (b); (c) ^= ((b) >> 5); \
+ (a) -= (b); (a) -= (c); (a) ^= ((c) >> 3); \
+ (b) -= (c); (b) -= (a); (b) ^= ((a) << 10); \
+ (c) -= (a); (c) -= (b); (c) ^= ((b) >> 15); \
+ }
+
+
+/*
+** Hash a variable-length key into a 32-bit value.
+**
+** Takes byte sequence to hash and returns a 32-bit value. A partial
+** result can be passed as the third parameter so that large amounts of
+** data can be hashed by subsequent calls, passing in the result of the
+** previous call each time. Every bit of the key affects every bit of the
+** return value. Every 1-bit and 2-bit delta achieves avalanche. About
+** (36 + 6n) instructions.
+**
+** The best hash table sizes are powers of 2. There is no need to mod with
+** a prime (mod is sooo slow!). If you need less than 32 bits, use a
+** bitmask. For example, if you need only 10 bits, do:
+**
+** h = h & ((1 << 10) - 1);
+**
+** In which case, the hash table should have 2^10 elements.
+**
+** Based on code by Bob Jenkins <bob_jenkins@burtleburtle.net>, originally
+** written in 1996. The original license was:
+**
+** By Bob Jenkins, 1996. bob_jenkins@burtleburtle.net. You may use
+** this code any way you wish, private, educational, or commercial.
+** It's free.
+**
+** See <http://burlteburtle.net/bob/hash/evahash.html> for discussion of
+** this hash function. Use for hash table lookup, or anything where one
+** collision in 2^32 is acceptable. Do NOT use for cryptographic purposes.
+*/
+unsigned long
+hash_lookup2(const char *key, size_t length, unsigned long partial)
+{
+ uint32_t a, b, c, len;
+
+ /* Set up the internal state. a and b are initialized to a golden
+ ratio, an arbitrary value intended to avoid mapping all zeroes to all
+ zeroes. */
+ len = length;
+ a = b = 0x9e3779b9;
+ c = partial;
+
+#define S0(c) ((uint32_t)(c))
+#define S1(c) ((uint32_t)(c) << 8)
+#define S2(c) ((uint32_t)(c) << 16)
+#define S3(c) ((uint32_t)(c) << 24)
+
+ /* Handle most of the key. */
+ while (len >= 12) {
+ a += S0(key[0]) + S1(key[1]) + S2(key[2]) + S3(key[3]);
+ b += S0(key[4]) + S1(key[5]) + S2(key[6]) + S3(key[7]);
+ c += S0(key[8]) + S1(key[9]) + S2(key[10]) + S3(key[11]);
+ MIX(a, b, c);
+ key += 12;
+ len -= 12;
+ }
+
+ /* Handle the last 11 bytes. All of the cases fall through. */
+ c += length;
+ switch (len) {
+ case 11: c += S3(key[10]);
+ case 10: c += S2(key[9]);
+ case 9: c += S1(key[8]);
+ /* The first byte of c is reserved for the length. */
+ case 8: b += S3(key[7]);
+ case 7: b += S2(key[6]);
+ case 6: b += S1(key[5]);
+ case 5: b += S0(key[4]);
+ case 4: a += S3(key[3]);
+ case 3: a += S2(key[2]);
+ case 2: a += S1(key[1]);
+ case 1: a += S0(key[0]);
+ /* case 0: nothing left to add. */
+ }
+ MIX(a, b, c);
+ return c;
+}
+
+
+/*
+** A hash function for nul-terminated strings using hash_lookup2, suitable
+** for passing to hash_create.
+*/
+unsigned long
+hash_string(const void *key)
+{
+ return hash_lookup2(key, strlen(key), 0);
+}
--- /dev/null
+/* $Id: hstrerror.c 5556 2002-08-11 22:23:14Z rra $
+**
+** Replacement for a missing hstrerror.
+**
+** Written by Russ Allbery <rra@stanford.edu>
+** This work is hereby placed in the public domain by its author.
+**
+** Provides hstrerror (strerror, but for h_errno from the resolver
+** libraries) on those platforms that don't have it (most non-BSD). This
+** function is thread-safe unless called with an unknown h_errno.
+*/
+
+#include "config.h"
+#include "clibrary.h"
+#include <netdb.h>
+
+static const char * const errors[] = {
+ "No resolver error", /* 0 NETDB_SUCCESS */
+ "Unknown host", /* 1 HOST_NOT_FOUND */
+ "Host name lookup failure", /* 2 TRY_AGAIN */
+ "Unknown server error", /* 3 NO_RECOVERY */
+ "No address associated with name", /* 4 NO_ADDRESS / NO_DATA */
+};
+static int nerrors = (sizeof errors / sizeof errors[0]);
+
+/* If we're running the test suite, rename hstrerror to avoid conflicts with
+ the system version. */
+#if TESTING
+# define hstrerror test_hstrerror
+const char *test_hstrerror(int);
+#endif
+
+const char *
+hstrerror(int error)
+{
+ static char buf[32];
+
+ if (error == -1)
+ return "Internal resolver error";
+ if (error >= 0 && error < nerrors)
+ return errors[error];
+ snprintf(buf, sizeof(buf), "Resolver error %d", error);
+ return buf;
+}
--- /dev/null
+/* $Id: inet_aton.c 5049 2001-12-12 09:06:00Z rra $
+**
+** Replacement for a missing inet_aton.
+**
+** Written by Russ Allbery <rra@stanford.edu>
+** This work is hereby placed in the public domain by its author.
+**
+** Provides the same functionality as the standard library routine
+** inet_aton for those platforms that don't have it. inet_aton is
+** thread-safe.
+*/
+
+#include "config.h"
+#include "clibrary.h"
+#include <netinet/in.h>
+
+/* If we're running the test suite, rename inet_ntoa to avoid conflicts with
+ the system version. */
+#if TESTING
+# define inet_aton test_inet_aton
+int test_inet_aton(const char *, struct in_addr *);
+#endif
+
+int
+inet_aton(const char *s, struct in_addr *addr)
+{
+ unsigned long octet[4], address;
+ const char *p;
+ int base, i;
+ int part = 0;
+
+ if (s == NULL) return 0;
+
+ /* Step through each period-separated part of the address. If we see
+ more than four parts, the address is invalid. */
+ for (p = s; *p != 0; part++) {
+ if (part > 3) return 0;
+
+ /* Determine the base of the section we're looking at. Numbers are
+ represented the same as in C; octal starts with 0, hex starts
+ with 0x, and anything else is decimal. */
+ if (*p == '0') {
+ p++;
+ if (*p == 'x') {
+ p++;
+ base = 16;
+ } else {
+ base = 8;
+ }
+ } else {
+ base = 10;
+ }
+
+ /* Make sure there's actually a number. (A section of just "0"
+ would set base to 8 and leave us pointing at a period; allow
+ that.) */
+ if (*p == '.' && base != 8) return 0;
+ octet[part] = 0;
+
+ /* Now, parse this segment of the address. For each digit, multiply
+ the result so far by the base and then add the value of the
+ digit. Be careful of arithmetic overflow in cases where an
+ unsigned long is 32 bits; we need to detect it *before* we
+ multiply by the base since otherwise we could overflow and wrap
+ and then not detect the error. */
+ for (; *p != 0 && *p != '.'; p++) {
+ if (octet[part] > 0xffffffffUL / base) return 0;
+
+ /* Use a switch statement to parse each digit rather than
+ assuming ASCII. Probably pointless portability.... */
+ switch (*p) {
+ case '0': i = 0; break;
+ case '1': i = 1; break;
+ case '2': i = 2; break;
+ case '3': i = 3; break;
+ case '4': i = 4; break;
+ case '5': i = 5; break;
+ case '6': i = 6; break;
+ case '7': i = 7; break;
+ case '8': i = 8; break;
+ case '9': i = 9; break;
+ case 'A': case 'a': i = 10; break;
+ case 'B': case 'b': i = 11; break;
+ case 'C': case 'c': i = 12; break;
+ case 'D': case 'd': i = 13; break;
+ case 'E': case 'e': i = 14; break;
+ case 'F': case 'f': i = 15; break;
+ default: return 0;
+ }
+ if (i >= base) return 0;
+ octet[part] = (octet[part] * base) + i;
+ }
+
+ /* Advance over periods; the top of the loop will increment the
+ count of parts we've seen. We need a check here to detect an
+ illegal trailing period. */
+ if (*p == '.') {
+ p++;
+ if (*p == 0) return 0;
+ }
+ }
+ if (part == 0) return 0;
+
+ /* IPv4 allows three types of address specification:
+
+ a.b
+ a.b.c
+ a.b.c.d
+
+ If there are fewer than four segments, the final segment accounts for
+ all of the remaining portion of the address. For example, in the a.b
+ form, b is the final 24 bits of the address. We also allow a simple
+ number, which is interpreted as the 32-bit number corresponding to
+ the full IPv4 address.
+
+ The first for loop below ensures that any initial segments represent
+ only 8 bits of the address and builds the upper portion of the IPv4
+ address. Then, the remaining segment is checked to make sure it's no
+ bigger than the remaining space in the address and then is added into
+ the result. */
+ address = 0;
+ for (i = 0; i < part - 1; i++) {
+ if (octet[i] > 0xff) return 0;
+ address |= octet[i] << (8 * (3 - i));
+ }
+ if (octet[i] > (0xffffffffUL >> (i * 8))) return 0;
+ address |= octet[i];
+ if (addr != NULL) addr->s_addr = htonl(address);
+ return 1;
+}
--- /dev/null
+/* $Id: inet_ntoa.c 5049 2001-12-12 09:06:00Z rra $
+**
+** Replacement for a missing or broken inet_ntoa.
+**
+** Written by Russ Allbery <rra@stanford.edu>
+** This work is hereby placed in the public domain by its author.
+**
+** Provides the same functionality as the standard library routine
+** inet_ntoa for those platforms that don't have it or where it doesn't
+** work right (such as on IRIX when using gcc to compile). inet_ntoa is
+** not thread-safe since it uses static storage (inet_ntop should be used
+** instead when available).
+*/
+
+#include "config.h"
+#include "clibrary.h"
+#include <netinet/in.h>
+
+/* If we're running the test suite, rename inet_ntoa to avoid conflicts with
+ the system version. */
+#if TESTING
+# define inet_ntoa test_inet_ntoa
+const char *test_inet_ntoa(const struct in_addr);
+#endif
+
+const char *
+inet_ntoa(const struct in_addr in)
+{
+ static char buf[16];
+ const unsigned char *p;
+
+ p = (const unsigned char *) &in.s_addr;
+ sprintf(buf, "%u.%u.%u.%u",
+ (unsigned int) (p[0] & 0xff), (unsigned int) (p[1] & 0xff),
+ (unsigned int) (p[2] & 0xff), (unsigned int) (p[3] & 0xff));
+ return buf;
+}
--- /dev/null
+/* $Id: innconf.c 7751 2008-04-06 14:35:40Z iulius $
+**
+** Manage the global innconf struct.
+**
+** The functions in this file collapse the parse tree for inn.conf into the
+** innconf struct that's used throughout INN. The code to collapse a
+** configuration parse tree into a struct is fairly generic and should
+** probably be moved into a separate library.
+**
+** When adding new inn.conf parameters, make sure to add them in all of the
+** following places:
+**
+** * The table in this file.
+** * include/inn/innconf.h
+** * doc/pod/inn.conf.pod (and regenerate doc/man/inn.conf.5)
+** * Add the default value to samples/inn.conf.in
+**
+** Please maintain the current organization of parameters. There are two
+** different orders, one of which is a logical order used by the
+** documentation, the include file, and the sample file, and the other of
+** which is used in this file. The order in this file is documentation of
+** where each parameter is used, for later work at breaking up this mess
+** of parameters into separate configuration groups for each INN subsystem.
+*/
+
+#include "config.h"
+#include "clibrary.h"
+#include <ctype.h>
+
+#include "inn/confparse.h"
+#include "inn/innconf.h"
+#include "inn/messages.h"
+#include "inn/vector.h"
+#include "libinn.h"
+#include "paths.h"
+
+/* Instantiation of the global innconf variable. */
+struct innconf *innconf = NULL;
+
+/* Data types used to express the mappings from the configuration parse into
+ the innconf struct. */
+
+enum type {
+ TYPE_BOOLEAN,
+ TYPE_NUMBER,
+ TYPE_STRING
+};
+
+struct config {
+ const char *name;
+ size_t location;
+ enum type type;
+ struct {
+ bool boolean;
+ long integer;
+ const char *string;
+ } defaults;
+};
+
+/* The following macros are helpers to make it easier to define the table that
+ specifies how to convert the configuration file into a struct. */
+
+#define K(name) (#name), offsetof(struct innconf, name)
+
+#define BOOL(def) TYPE_BOOLEAN, { (def), 0, NULL }
+#define NUMBER(def) TYPE_NUMBER, { 0, (def), NULL }
+#define STRING(def) TYPE_STRING, { 0, 0, (def) }
+
+/* Accessor macros to get a pointer to a value inside a struct. */
+#define CONF_BOOL(conf, offset) (bool *) (void *)((char *) (conf) + (offset))
+#define CONF_LONG(conf, offset) (long *) (void *)((char *) (conf) + (offset))
+#define CONF_STRING(conf, offset) (char **)(void *)((char *) (conf) + (offset))
+
+/* Special notes:
+
+ checkincludedtext and localmaxartisize are used by both nnrpd and inews,
+ but inews should probably just let nnrpd do that checking.
+
+ organization is used by both nnrpd and inews. Perhaps inews should just
+ let nnrpd set it.
+
+ mergetogroups is currently used by nnrpd for permission checking on
+ posting. I think the check should always be performed based on the
+ newsgroup to which the user is actually posting, and nnrpd should let
+ innd do the merging.
+
+ useoverchan is only used in innd and overchan. It should probably be
+ something the storage system knows. Ideally, the storage system would
+ handle overchan itself, but that would require a lot of infrastructure;
+ in the interim, it could be something that programs could ask the
+ overview subsystem about.
+
+ doinnwatch and docnfsstat are used by rc.news currently, but really
+ should be grouped with the appropriate subsystem.
+
+ newsrequeue also uses nntplinklog, but the parameter should go away
+ completely anyway.
+
+ timer is currently used in various places, but it may be best to replace
+ it with individual settings for innd and innfeed, and any other code that
+ wants to use it.
+
+ maxforks is used by rnews and nnrpd. I'm not sure this is that useful of
+ a setting to have.
+*/
+
+const struct config config_table[] = {
+ { K(domain), STRING (NULL) },
+ { K(enableoverview), BOOL (true) },
+ { K(fromhost), STRING (NULL) },
+ { K(groupbaseexpiry), BOOL (true) },
+ { K(mailcmd), STRING (NULL) },
+ { K(maxforks), NUMBER (10) },
+ { K(mta), STRING (NULL) },
+ { K(nicekids), NUMBER (4) },
+ { K(ovmethod), STRING (NULL) },
+ { K(pathhost), STRING (NULL) },
+ { K(rlimitnofile), NUMBER (-1) },
+ { K(server), STRING (NULL) },
+ { K(sourceaddress), STRING (NULL) },
+ { K(sourceaddress6), STRING (NULL) },
+ { K(timer), NUMBER (0) },
+
+ { K(patharchive), STRING (NULL) },
+ { K(patharticles), STRING (NULL) },
+ { K(pathbin), STRING (NULL) },
+ { K(pathcontrol), STRING (NULL) },
+ { K(pathdb), STRING (NULL) },
+ { K(pathetc), STRING (NULL) },
+ { K(pathfilter), STRING (NULL) },
+ { K(pathhttp), STRING (NULL) },
+ { K(pathincoming), STRING (NULL) },
+ { K(pathlog), STRING (NULL) },
+ { K(pathnews), STRING (NULL) },
+ { K(pathoutgoing), STRING (NULL) },
+ { K(pathoverview), STRING (NULL) },
+ { K(pathrun), STRING (NULL) },
+ { K(pathspool), STRING (NULL) },
+ { K(pathtmp), STRING (NULL) },
+
+ /* The following settings are specific to innd. */
+ { K(artcutoff), NUMBER (10) },
+ { K(badiocount), NUMBER (5) },
+ { K(bindaddress), STRING (NULL) },
+ { K(bindaddress6), STRING (NULL) },
+ { K(blockbackoff), NUMBER (120) },
+ { K(chaninacttime), NUMBER (600) },
+ { K(chanretrytime), NUMBER (300) },
+ { K(datamovethreshold), NUMBER (8192) },
+ { K(dontrejectfiltered), BOOL (false) },
+ { K(hiscachesize), NUMBER (0) },
+ { K(icdsynccount), NUMBER (10) },
+ { K(ignorenewsgroups), BOOL (false) },
+ { K(linecountfuzz), NUMBER (0) },
+ { K(logartsize), BOOL (true) },
+ { K(logcancelcomm), BOOL (false) },
+ { K(logipaddr), BOOL (true) },
+ { K(logsitename), BOOL (true) },
+ { K(maxartsize), NUMBER (1000000) },
+ { K(maxconnections), NUMBER (50) },
+ { K(mergetogroups), BOOL (false) },
+ { K(nntpactsync), NUMBER (200) },
+ { K(nntplinklog), BOOL (false) },
+ { K(noreader), BOOL (false) },
+ { K(pathalias), STRING (NULL) },
+ { K(pathcluster), STRING (NULL) },
+ { K(pauseretrytime), NUMBER (300) },
+ { K(peertimeout), NUMBER (3600) },
+ { K(port), NUMBER (119) },
+ { K(readerswhenstopped), BOOL (false) },
+ { K(refusecybercancels), BOOL (false) },
+ { K(remembertrash), BOOL (true) },
+ { K(stathist), STRING (NULL) },
+ { K(status), NUMBER (0) },
+ { K(verifycancels), BOOL (false) },
+ { K(wanttrash), BOOL (false) },
+ { K(wipcheck), NUMBER (5) },
+ { K(wipexpire), NUMBER (10) },
+ { K(xrefslave), BOOL (false) },
+
+ /* The following settings are specific to nnrpd. */
+ { K(addnntppostingdate), BOOL (true) },
+ { K(addnntppostinghost), BOOL (true) },
+ { K(allownewnews), BOOL (true) },
+ { K(backoffauth), BOOL (false) },
+ { K(backoffdb), STRING (NULL) },
+ { K(backoffk), NUMBER (1) },
+ { K(backoffpostfast), NUMBER (0) },
+ { K(backoffpostslow), NUMBER (1) },
+ { K(backofftrigger), NUMBER (10000) },
+ { K(checkincludedtext), BOOL (false) },
+ { K(clienttimeout), NUMBER (600) },
+ { K(complaints), STRING (NULL) },
+ { K(initialtimeout), NUMBER (10) },
+ { K(keyartlimit), NUMBER (100000) },
+ { K(keylimit), NUMBER (512) },
+ { K(keymaxwords), NUMBER (250) },
+ { K(keywords), BOOL (false) },
+ { K(localmaxartsize), NUMBER (1000000) },
+ { K(maxcmdreadsize), NUMBER (BUFSIZ) },
+ { K(msgidcachesize), NUMBER (10000) },
+ { K(moderatormailer), STRING (NULL) },
+ { K(nfsreader), BOOL (false) },
+ { K(nfsreaderdelay), NUMBER (60) },
+ { K(nicenewnews), NUMBER (0) },
+ { K(nicennrpd), NUMBER (0) },
+ { K(nnrpdflags), STRING ("") },
+ { K(nnrpdauthsender), BOOL (false) },
+ { K(nnrpdloadlimit), NUMBER (16) },
+ { K(nnrpdoverstats), BOOL (false) },
+ { K(organization), STRING (NULL) },
+ { K(readertrack), BOOL (false) },
+ { K(spoolfirst), BOOL (false) },
+ { K(strippostcc), BOOL (false) },
+
+ /* The following settings are used by nnrpd and rnews. */
+ { K(nnrpdposthost), STRING (NULL) },
+ { K(nnrpdpostport), NUMBER (119) },
+
+ /* The following settings are specific to the storage subsystem. */
+ { K(articlemmap), BOOL (false) },
+ { K(cnfscheckfudgesize), NUMBER (0) },
+ { K(immediatecancel), BOOL (false) },
+ { K(keepmmappedthreshold), NUMBER (1024) },
+ { K(nfswriter), BOOL (false) },
+ { K(nnrpdcheckart), BOOL (true) },
+ { K(overcachesize), NUMBER (15) },
+ { K(ovgrouppat), STRING (NULL) },
+ { K(storeonxref), BOOL (true) },
+ { K(tradindexedmmap), BOOL (true) },
+ { K(useoverchan), BOOL (false) },
+ { K(wireformat), BOOL (false) },
+
+ /* The following settings are specific to the history subsystem. */
+ { K(hismethod), STRING (NULL) },
+
+ /* The following settings are specific to rc.news. */
+ { K(docnfsstat), BOOL (false) },
+ { K(innflags), STRING (NULL) },
+ { K(pgpverify), BOOL (false) },
+
+ /* The following settings are specific to innwatch. */
+ { K(doinnwatch), BOOL (true) },
+ { K(innwatchbatchspace), NUMBER (800) },
+ { K(innwatchlibspace), NUMBER (25000) },
+ { K(innwatchloload), NUMBER (1000) },
+ { K(innwatchhiload), NUMBER (2000) },
+ { K(innwatchpauseload), NUMBER (1500) },
+ { K(innwatchsleeptime), NUMBER (600) },
+ { K(innwatchspoolnodes), NUMBER (200) },
+ { K(innwatchspoolspace), NUMBER (8000) },
+
+ /* The following settings are specific to scanlogs. */
+ { K(logcycles), NUMBER (3) },
+};
+
+
+/*
+** Set some defaults that cannot be included in the table because they depend
+** on other elements or require function calls to set. Called after the
+** configuration file is read, so any that have to override what's read have
+** to free whatever values are set by the file.
+*/
+static void
+innconf_set_defaults(void)
+{
+ char *value;
+
+ /* Some environment variables override settings in inn.conf. */
+ value = getenv("FROMHOST");
+ if (value != NULL) {
+ if (innconf->fromhost != NULL)
+ free(innconf->fromhost);
+ innconf->fromhost = xstrdup(value);
+ }
+ value = getenv("NNTPSERVER");
+ if (value != NULL) {
+ if (innconf->server != NULL)
+ free(innconf->server);
+ innconf->server = xstrdup(value);
+ }
+ value = getenv("ORGANIZATION");
+ if (value != NULL) {
+ if (innconf->organization != NULL)
+ free(innconf->organization);
+ innconf->organization = xstrdup(value);
+ }
+ value = getenv("INND_BIND_ADDRESS");
+ if (value != NULL) {
+ if (innconf->bindaddress != NULL)
+ free(innconf->bindaddress);
+ innconf->bindaddress = xstrdup(value);
+ }
+ value = getenv("INND_BIND_ADDRESS6");
+ if (value != NULL) {
+ if (innconf->bindaddress6 != NULL)
+ free(innconf->bindaddress6);
+ innconf->bindaddress6 = xstrdup(value);
+ }
+
+ /* Some parameters have defaults that depend on other parameters. */
+ if (innconf->fromhost == NULL)
+ innconf->fromhost = xstrdup(GetFQDN(innconf->domain));
+ if (innconf->pathhost == NULL)
+ innconf->pathhost = xstrdup(GetFQDN(innconf->domain));
+ if (innconf->pathtmp == NULL)
+ innconf->pathtmp = xstrdup(_PATH_TMP);
+
+ /* All of the paths are relative to other paths if not set except for
+ pathnews, which is required to be set by innconf_validate. */
+ if (innconf->pathbin == NULL)
+ innconf->pathbin = concatpath(innconf->pathnews, "bin");
+ if (innconf->pathfilter == NULL)
+ innconf->pathfilter = concatpath(innconf->pathbin, "filter");
+ if (innconf->pathdb == NULL)
+ innconf->pathdb = concatpath(innconf->pathnews, "db");
+ if (innconf->pathetc == NULL)
+ innconf->pathetc = concatpath(innconf->pathnews, "etc");
+ if (innconf->pathrun == NULL)
+ innconf->pathrun = concatpath(innconf->pathnews, "run");
+ if (innconf->pathlog == NULL)
+ innconf->pathlog = concatpath(innconf->pathnews, "log");
+ if (innconf->pathhttp == NULL)
+ innconf->pathhttp = xstrdup(innconf->pathlog);
+ if (innconf->pathspool == NULL)
+ innconf->pathspool = concatpath(innconf->pathnews, "spool");
+ if (innconf->patharticles == NULL)
+ innconf->patharticles = concatpath(innconf->pathspool, "articles");
+ if (innconf->pathoverview == NULL)
+ innconf->pathoverview = concatpath(innconf->pathspool, "overview");
+ if (innconf->pathoutgoing == NULL)
+ innconf->pathoutgoing = concatpath(innconf->pathspool, "outgoing");
+ if (innconf->pathincoming == NULL)
+ innconf->pathincoming = concatpath(innconf->pathspool, "incoming");
+ if (innconf->patharchive == NULL)
+ innconf->patharchive = concatpath(innconf->pathspool, "archive");
+
+ /* One other parameter depends on pathbin. */
+ if (innconf->mailcmd == NULL)
+ innconf->mailcmd = concatpath(innconf->pathbin, "innmail");
+}
+
+
+/*
+** Given a config_group struct representing the inn.conf file, parse that
+** into an innconf struct and return the newly allocated struct. This
+** routine should be pulled out into a library for smashing configuration
+** file parse results into structs.
+*/
+static struct innconf *
+innconf_parse(struct config_group *group)
+{
+ unsigned int i;
+ bool *bool_ptr;
+ long *long_ptr;
+ const char *char_ptr;
+ char **string;
+ struct innconf *config;
+
+ config = xmalloc(sizeof(struct innconf));
+ for (i = 0; i < ARRAY_SIZE(config_table); i++)
+ switch (config_table[i].type) {
+ case TYPE_BOOLEAN:
+ bool_ptr = CONF_BOOL(config, config_table[i].location);
+ if (!config_param_boolean(group, config_table[i].name, bool_ptr))
+ *bool_ptr = config_table[i].defaults.boolean;
+ break;
+ case TYPE_NUMBER:
+ long_ptr = CONF_LONG(config, config_table[i].location);
+ if (!config_param_integer(group, config_table[i].name, long_ptr))
+ *long_ptr = config_table[i].defaults.integer;
+ break;
+ case TYPE_STRING:
+ if (!config_param_string(group, config_table[i].name, &char_ptr))
+ char_ptr = config_table[i].defaults.string;
+ string = CONF_STRING(config, config_table[i].location);
+ *string = (char_ptr == NULL) ? NULL : xstrdup(char_ptr);
+ break;
+ default:
+ die("internal error: invalid type in row %u of config table", i);
+ break;
+ }
+ return config;
+}
+
+
+/*
+** Check the configuration file for consistency and ensure that mandatory
+** settings are present. Returns true if the file is okay and false
+** otherwise.
+*/
+static bool
+innconf_validate(struct config_group *group)
+{
+ bool okay = true;
+ long threshold;
+
+ if (GetFQDN(innconf->domain) == NULL) {
+ warn("hostname does not resolve or domain not set in inn.conf");
+ okay = false;
+ }
+ if (innconf->mta == NULL) {
+ warn("must set mta in inn.conf");
+ okay = false;
+ }
+ if (innconf->pathnews == NULL) {
+ warn("must set pathnews in inn.conf");
+ okay = false;
+ }
+ if (innconf->hismethod == NULL) {
+ warn("must set hismethod in inn.conf");
+ okay = false;
+ }
+ if (innconf->enableoverview && innconf->ovmethod == NULL) {
+ warn("ovmethod must be set in inn.conf if enableoverview is true");
+ okay = false;
+ }
+ threshold = innconf->datamovethreshold;
+ if (threshold <= 0 || threshold > 1024 * 1024) {
+ config_error_param(group, "datamovethreshold",
+ "maximum value for datamovethreshold is 1MB");
+ innconf->datamovethreshold = 1024 * 1024;
+ }
+ return okay;
+}
+
+
+/*
+** Read in inn.conf. Takes a single argument, which is either NULL to read
+** the default configuration file or a path to an alternate configuration
+** file to read. Returns true if the file was read successfully and false
+** otherwise.
+*/
+bool
+innconf_read(const char *path)
+{
+ struct config_group *group;
+ char *tmpdir;
+
+ if (innconf != NULL)
+ innconf_free(innconf);
+ if (path == NULL)
+ path = getenv("INNCONF");
+ group = config_parse_file(path == NULL ? _PATH_CONFIG : path);
+ if (group == NULL)
+ return false;
+
+ innconf = innconf_parse(group);
+ if (!innconf_validate(group))
+ return false;
+ config_free(group);
+ innconf_set_defaults();
+
+ /* It's not clear that this belongs here, but it was done by the old
+ configuration parser, so this is a convenient place to do it. */
+ tmpdir = getenv("TMPDIR");
+ if (tmpdir == NULL || strcmp(tmpdir, innconf->pathtmp) != 0)
+ if (setenv("TMPDIR", innconf->pathtmp, true) != 0) {
+ warn("cannot set TMPDIR in the environment");
+ return false;
+ }
+
+ return true;
+}
+
+
+/*
+** Check an inn.conf file. This involves reading it in and then additionally
+** making sure that there are no keys defined in the inn.conf file that
+** aren't recognized. This doesn't have to be very fast (and isn't).
+** Returns true if everything checks out successfully, and false otherwise.
+**
+** A lot of code is duplicated with innconf_read here and should be
+** refactored.
+*/
+bool
+innconf_check(const char *path)
+{
+ struct config_group *group;
+ struct vector *params;
+ size_t set, known;
+ bool found;
+ bool okay = true;
+
+ if (innconf != NULL)
+ innconf_free(innconf);
+ if (path == NULL)
+ path = getenv("INNCONF");
+ group = config_parse_file(path == NULL ? _PATH_CONFIG : path);
+ if (group == NULL)
+ return false;
+
+ innconf = innconf_parse(group);
+ if (!innconf_validate(group))
+ return false;
+
+ /* Now, do the work that innconf_read doesn't do. Get a list of
+ parameters defined in innconf and then walk our list of valid
+ parameters and see if there are any set that we don't recognize. */
+ params = config_params(group);
+ for (set = 0; set < params->count; set++) {
+ found = false;
+ for (known = 0; known < ARRAY_SIZE(config_table); known++)
+ if (strcmp(params->strings[set], config_table[known].name) == 0)
+ found = true;
+ if (!found) {
+ config_error_param(group, params->strings[set],
+ "unknown parameter %s", params->strings[set]);
+ okay = false;
+ }
+ }
+
+ /* Check and warn about a few other parameters. */
+ if (innconf->peertimeout < 3 * 60)
+ config_error_param(group, "peertimeout",
+ "warning: NNTP draft (15) states inactivity"
+ " timeouts MUST be at least three minutes");
+ if (innconf->clienttimeout < 3 * 60)
+ config_error_param(group, "clienttimeout",
+ "warning: NNTP draft (15) states inactivity"
+ " timeouts MUST be at least three minutes");
+
+ /* All done. Free the parse tree and return. */
+ config_free(group);
+ return okay;
+}
+
+
+/*
+** Free innconf, requiring some complexity since all strings stored in the
+** innconf struct are allocated memory. This routine is mostly generic to
+** any struct smashed down from a configuration file parse.
+*/
+void
+innconf_free(struct innconf *config)
+{
+ unsigned int i;
+ char *p;
+
+ for (i = 0; i < ARRAY_SIZE(config_table); i++)
+ if (config_table[i].type == TYPE_STRING) {
+ p = *CONF_STRING(config, config_table[i].location);
+ if (p != NULL)
+ free(p);
+ }
+ free(config);
+}
+
+
+/*
+** Print a single boolean value with appropriate quoting.
+*/
+static void
+print_boolean(FILE *file, const char *key, bool value,
+ enum innconf_quoting quoting)
+{
+ char *upper, *p;
+
+ switch (quoting) {
+ case INNCONF_QUOTE_NONE:
+ fprintf(file, "%s\n", value ? "true" : "false");
+ break;
+ case INNCONF_QUOTE_SHELL:
+ upper = xstrdup(key);
+ for (p = upper; *p != '\0'; p++)
+ *p = toupper(*p);
+ fprintf(file, "%s=%s; export %s;\n", upper, value ? "true" : "false",
+ upper);
+ free(upper);
+ break;
+ case INNCONF_QUOTE_PERL:
+ fprintf(file, "$%s = '%s';\n", key, value ? "true" : "false");
+ break;
+ case INNCONF_QUOTE_TCL:
+ fprintf(file, "set inn_%s \"%s\"\n", key, value ? "true" : "false");
+ break;
+ }
+}
+
+
+/*
+** Print a single integer value with appropriate quoting.
+*/
+static void
+print_number(FILE *file, const char *key, long value,
+ enum innconf_quoting quoting)
+{
+ char *upper, *p;
+
+ switch (quoting) {
+ case INNCONF_QUOTE_NONE:
+ fprintf(file, "%ld\n", value);
+ break;
+ case INNCONF_QUOTE_SHELL:
+ upper = xstrdup(key);
+ for (p = upper; *p != '\0'; p++)
+ *p = toupper(*p);
+ fprintf(file, "%s=%ld; export %s;\n", upper, value, upper);
+ free(upper);
+ break;
+ case INNCONF_QUOTE_PERL:
+ fprintf(file, "$%s = %ld;\n", key, value);
+ break;
+ case INNCONF_QUOTE_TCL:
+ fprintf(file, "set inn_%s %ld\n", key, value);
+ break;
+ }
+}
+
+
+/*
+** Print a single string value with appropriate quoting.
+*/
+static void
+print_string(FILE *file, const char *key, const char *value,
+ enum innconf_quoting quoting)
+{
+ char *upper, *p;
+ const char *letter;
+ static const char tcl_unsafe[] = "$[]{}\"\\";
+
+ switch (quoting) {
+ case INNCONF_QUOTE_NONE:
+ fprintf(file, "%s\n", value != NULL ? value : "");
+ break;
+ case INNCONF_QUOTE_SHELL:
+ upper = xstrdup(key);
+ for (p = upper; *p != '\0'; p++)
+ *p = toupper(*p);
+ fprintf(file, "%s='", upper);
+ for (letter = value; letter != NULL && *letter != '\0'; letter++) {
+ if (*letter == '\'')
+ fputs("'\\''", file);
+ else if (*letter == '\\')
+ fputs("\\\\", file);
+ else
+ fputc(*letter, file);
+ }
+ fprintf(file, "'; export %s;\n", upper);
+ free(upper);
+ break;
+ case INNCONF_QUOTE_PERL:
+ fprintf(file, "$%s = '", key);
+ for (letter = value; letter != NULL && *letter != '\0'; letter++) {
+ if (*letter == '\'' || *letter == '\\')
+ fputc('\\', file);
+ fputc(*letter, file);
+ }
+ fputs("';\n", file);
+ break;
+ case INNCONF_QUOTE_TCL:
+ fprintf(file, "set inn_%s \"", key);
+ for (letter = value; letter != NULL && *letter != '\0'; letter++) {
+ if (strchr(tcl_unsafe, *letter) != NULL)
+ fputc('\\', file);
+ fputc(*letter, file);
+ }
+ fputs("\"\n", file);
+ break;
+ }
+}
+
+
+/*
+** Print a single paramter to the given file. Takes an index into the table
+** specifying the attribute to print and the quoting.
+*/
+static void
+print_parameter(FILE *file, size_t i, enum innconf_quoting quoting)
+{
+ bool bool_val;
+ long long_val;
+ const char *string_val;
+
+ switch (config_table[i].type) {
+ case TYPE_BOOLEAN:
+ bool_val = *CONF_BOOL(innconf, config_table[i].location);
+ print_boolean(file, config_table[i].name, bool_val, quoting);
+ break;
+ case TYPE_NUMBER:
+ long_val = *CONF_LONG(innconf, config_table[i].location);
+ print_number(file, config_table[i].name, long_val, quoting);
+ break;
+ case TYPE_STRING:
+ string_val = *CONF_STRING(innconf, config_table[i].location);
+ print_string(file, config_table[i].name, string_val, quoting);
+ break;
+ default:
+ die("internal error: invalid type in row %d of config table", i);
+ break;
+ }
+}
+
+
+/*
+** Given a single parameter, find it in the table and print out its value.
+*/
+bool
+innconf_print_value(FILE *file, const char *key, enum innconf_quoting quoting)
+{
+ size_t i;
+
+ for (i = 0; i < ARRAY_SIZE(config_table); i++)
+ if (strcmp(key, config_table[i].name) == 0) {
+ print_parameter(file, i, quoting);
+ return true;
+ }
+ return false;
+}
+
+
+/*
+** Dump the entire inn.conf configuration with appropriate quoting.
+*/
+void
+innconf_dump(FILE *file, enum innconf_quoting quoting)
+{
+ size_t i;
+
+ for (i = 0; i < ARRAY_SIZE(config_table); i++)
+ print_parameter(file, i, quoting);
+}
+
+
+/*
+** Compare two innconf structs to see if they represent identical
+** configurations. This routine is mostly used for testing. Prints warnings
+** about where the two configurations differ and return false if they differ,
+** true if they match. This too should be moved into a config smashing
+** library.
+*/
+bool
+innconf_compare(struct innconf *conf1, struct innconf *conf2)
+{
+ unsigned int i;
+ bool bool1, bool2;
+ long long1, long2;
+ const char *string1, *string2;
+ bool okay = true;
+
+ for (i = 0; i < ARRAY_SIZE(config_table); i++)
+ switch (config_table[i].type) {
+ case TYPE_BOOLEAN:
+ bool1 = *CONF_BOOL(conf1, config_table[i].location);
+ bool2 = *CONF_BOOL(conf2, config_table[i].location);
+ if (bool1 != bool2) {
+ warn("boolean variable %s differs: %d != %d",
+ config_table[i].name, bool1, bool2);
+ okay = false;
+ }
+ break;
+ case TYPE_NUMBER:
+ long1 = *CONF_LONG(conf1, config_table[i].location);
+ long2 = *CONF_LONG(conf2, config_table[i].location);
+ if (long1 != long2) {
+ warn("integer variable %s differs: %ld != %ld",
+ config_table[i].name, long1, long2);
+ okay = false;
+ }
+ break;
+ case TYPE_STRING:
+ string1 = *CONF_STRING(conf1, config_table[i].location);
+ string2 = *CONF_STRING(conf2, config_table[i].location);
+ if (string1 == NULL && string2 != NULL) {
+ warn("string variable %s differs: NULL != %s",
+ config_table[i].name, string2);
+ okay = false;
+ } else if (string1 != NULL && string2 == NULL) {
+ warn("string variable %s differs: %s != NULL",
+ config_table[i].name, string1);
+ okay = false;
+ } else if (string1 != NULL && string2 != NULL) {
+ if (strcmp(string1, string2) != 0) {
+ warn("string variable %s differs: %s != %s",
+ config_table[i].name, string1, string2);
+ okay = false;
+ }
+ }
+ break;
+ default:
+ die("internal error: invalid type in row %d of config table", i);
+ break;
+ }
+ return okay;
+}
--- /dev/null
+/* $Id: inndcomm.c 7597 2007-01-16 23:11:05Z eagle $
+**
+** Library routines to let other programs control innd.
+*/
+
+#include "config.h"
+#include "clibrary.h"
+#include "portable/time.h"
+#include "portable/socket.h"
+#include <ctype.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <signal.h>
+#include <sys/stat.h>
+
+/* Needed on AIX 4.1 to get fd_set and friends. */
+#ifdef HAVE_SYS_SELECT_H
+# include <sys/select.h>
+#endif
+
+#ifdef HAVE_UNIX_DOMAIN_SOCKETS
+# include <sys/un.h>
+#endif
+
+#include "inn/innconf.h"
+#include "inndcomm.h"
+#include "libinn.h"
+#include "paths.h"
+
+static char *ICCsockname = NULL;
+#ifdef HAVE_UNIX_DOMAIN_SOCKETS
+static struct sockaddr_un ICCserv;
+static struct sockaddr_un ICCclient;
+#endif
+static int ICCfd;
+static int ICCtimeout;
+const char *ICCfailure;
+
+
+/*
+** Set the timeout.
+*/
+void
+ICCsettimeout(int i)
+{
+ ICCtimeout = i;
+}
+
+
+/*
+** Get ready to talk to the server.
+*/
+int
+ICCopen(void)
+{
+ int mask, oerrno, fd;
+ int size = 65535;
+
+ if (innconf == NULL) {
+ if (!innconf_read(NULL)) {
+ ICCfailure = "innconf";
+ return -1;
+ }
+ }
+ /* Create a temporary name. mkstemp is complete overkill here and is used
+ only because it's convenient. We don't use it properly, since we
+ actually need to create a socket or named pipe, so there is a race
+ condition here. It doesn't matter, since pathrun isn't world-writable
+ (conceivably two processes could end up using the same temporary name
+ at the same time, but the worst that will happen is that one process
+ will delete the other's temporary socket). */
+ if (ICCsockname == NULL)
+ ICCsockname = concatpath(innconf->pathrun, _PATH_TEMPSOCK);
+ fd = mkstemp(ICCsockname);
+ if (fd < 0) {
+ ICCfailure = "mkstemp";
+ return -1;
+ }
+ close(fd);
+ if (unlink(ICCsockname) < 0 && errno != ENOENT) {
+ ICCfailure = "unlink";
+ return -1;
+ }
+
+#ifdef HAVE_UNIX_DOMAIN_SOCKETS
+
+ /* Make a socket and give it the name. */
+ if ((ICCfd = socket(AF_UNIX, SOCK_DGRAM, 0)) < 0) {
+ ICCfailure = "socket";
+ return -1;
+ }
+
+ /* Adjust the socket buffer size to accomodate large responses. Ignore
+ failure; the message may fit anyway, and if not, we'll fail below. */
+ setsockopt(ICCfd, SOL_SOCKET, SO_RCVBUF, &size, sizeof(size));
+
+ memset(&ICCclient, 0, sizeof ICCclient);
+ ICCclient.sun_family = AF_UNIX;
+ strlcpy(ICCclient.sun_path, ICCsockname, sizeof(ICCclient.sun_path));
+ mask = umask(0);
+ if (bind(ICCfd, (struct sockaddr *) &ICCclient, SUN_LEN(&ICCclient)) < 0) {
+ oerrno = errno;
+ umask(mask);
+ errno = oerrno;
+ ICCfailure = "bind";
+ return -1;
+ }
+ umask(mask);
+
+ /* Name the server's socket. */
+ memset(&ICCserv, 0, sizeof ICCserv);
+ ICCserv.sun_family = AF_UNIX;
+ strlcpy(ICCserv.sun_path, innconf->pathrun, sizeof(ICCserv.sun_path));
+ strlcat(ICCserv.sun_path, "/", sizeof(ICCserv.sun_path));
+ strlcat(ICCserv.sun_path, _PATH_NEWSCONTROL, sizeof(ICCserv.sun_path));
+
+#else /* !HAVE_UNIX_DOMAIN_SOCKETS */
+
+ /* Make a named pipe and open it. */
+ mask = umask(0);
+ if (mkfifo(ICCsockname, 0666) < 0) {
+ oerrno = errno;
+ umask(mask);
+ errno = oerrno;
+ ICCfailure = "mkfifo";
+ return -1;
+ }
+ umask(mask);
+ if ((ICCfd = open(ICCsockname, O_RDWR)) < 0) {
+ ICCfailure = "open";
+ return -1;
+ }
+#endif /* !HAVE_UNIX_DOMAIN_SOCKETS */
+
+ ICCfailure = NULL;
+ return 0;
+}
+
+
+/*
+** Close down.
+*/
+int
+ICCclose(void)
+{
+ int i;
+
+ ICCfailure = NULL;
+ i = 0;
+ if (close(ICCfd) < 0) {
+ ICCfailure = "close";
+ i = -1;
+ }
+ if (unlink(ICCsockname) < 0 && errno != ENOENT) {
+ ICCfailure = "unlink";
+ i = -1;
+ }
+ return i;
+}
+
+
+/*
+** Get the server's pid.
+*/
+static pid_t
+ICCserverpid(void)
+{
+ pid_t pid;
+ FILE *F;
+ char *path;
+ char buff[SMBUF];
+
+ pid = 1;
+ path = concatpath(innconf->pathrun, _PATH_SERVERPID);
+ F = fopen(path, "r");
+ free(path);
+ if (F != NULL) {
+ if (fgets(buff, sizeof buff, F) != NULL)
+ pid = atol(buff);
+ fclose(F);
+ }
+ return pid;
+}
+
+
+/*
+** See if the server is still there. When in doubt, assume yes. Cache the
+** PID since a rebooted server won't know about our pending message.
+*/
+static bool
+ICCserveralive(pid_t pid)
+{
+ if (kill(pid, 0) > 0 || errno != ESRCH)
+ return true;
+ return false;
+}
+
+
+/*
+** Send an arbitrary command to the server.
+**
+** There is a protocol version (one-byte) on the front of the message,
+** followed by a two byte length count. The length includes the protocol
+** byte and the length itself. This differs from the protocol in much
+** earlier versions of INN.
+*/
+int
+ICCcommand(char cmd, const char *argv[], char **replyp)
+{
+ char *buff;
+ char *p;
+ const char *q;
+ char save;
+ int i ;
+#ifndef HAVE_UNIX_DOMAIN_SOCKETS
+ int fd;
+ char *path;
+#endif
+ int len;
+ fd_set Rmask;
+ struct timeval T;
+ pid_t pid;
+ ICC_MSGLENTYPE rlen;
+ ICC_PROTOCOLTYPE protocol;
+ size_t bufsiz = 64 * 1024 - 1;
+
+ /* Is server there? */
+ pid = ICCserverpid();
+ if (!ICCserveralive(pid)) {
+ ICCfailure = "dead server";
+ return -1;
+ }
+
+ /* Get the length of the buffer. */
+ buff = xmalloc(bufsiz);
+ if (replyp)
+ *replyp = NULL;
+
+ /* Advance to leave space for length + protocol version info. */
+ buff += HEADER_SIZE;
+ bufsiz -= HEADER_SIZE;
+
+ /* Format the message. */
+ snprintf(buff, bufsiz, "%s%c%c", ICCsockname, SC_SEP, cmd);
+ for (p = buff + strlen(buff), i = 0; (q = argv[i]) != NULL; i++) {
+ *p++ = SC_SEP;
+ *p = '\0';
+ strlcat(buff, q, bufsiz);
+ p += strlen(q);
+ }
+
+ /* Send message. */
+ ICCfailure = NULL;
+ len = p - buff + HEADER_SIZE;
+ rlen = htons(len);
+
+ /* now stick in the protocol version and the length. */
+ buff -= HEADER_SIZE;
+ bufsiz += HEADER_SIZE;
+ protocol = ICC_PROTOCOL_1;
+ memcpy(buff, &protocol, sizeof(protocol));
+ memcpy(buff + sizeof(protocol), &rlen, sizeof(rlen));
+
+#ifdef HAVE_UNIX_DOMAIN_SOCKETS
+ if (sendto(ICCfd, buff, len, 0,(struct sockaddr *) &ICCserv,
+ SUN_LEN(&ICCserv)) < 0) {
+ free(buff);
+ ICCfailure = "sendto";
+ return -1;
+ }
+#else /* !HAVE_UNIX_DOMAIN_SOCKETS */
+ path = concatpath(innconf->pathrun, _PATH_NEWSCONTROL);
+ fd = open(path, O_WRONLY);
+ free(path);
+ if (fd < 0) {
+ free(buff);
+ ICCfailure = "open";
+ return -1;
+ }
+ if (write(fd, buff, len) != len) {
+ i = errno;
+ free(buff);
+ close(fd);
+ errno = i;
+ ICCfailure = "write";
+ return -1;
+ }
+ close(fd);
+#endif /* !HAVE_UNIX_DOMAIN_SOCKETS */
+
+ /* Possibly get a reply. */
+ switch (cmd) {
+ default:
+ if (ICCtimeout >= 0)
+ break;
+ /* FALLTHROUGH */
+ case SC_SHUTDOWN:
+ case SC_XABORT:
+ case SC_XEXEC:
+ free(buff);
+ return 0;
+ }
+
+ /* Wait for the reply. */
+ for ( ; ; ) {
+ FD_ZERO(&Rmask);
+ FD_SET(ICCfd, &Rmask);
+ T.tv_sec = ICCtimeout ? ICCtimeout : 120;
+ T.tv_usec = 0;
+ i = select(ICCfd + 1, &Rmask, NULL, NULL, &T);
+ if (i < 0) {
+ free(buff);
+ ICCfailure = "select";
+ return -1;
+ }
+ if (i > 0 && FD_ISSET(ICCfd, &Rmask))
+ /* Server reply is there; go handle it. */
+ break;
+
+ /* No data -- if we timed out, return. */
+ if (ICCtimeout) {
+ free(buff);
+ errno = ETIMEDOUT;
+ ICCfailure = "timeout";
+ return -1;
+ }
+
+ if (!ICCserveralive(pid)) {
+ free(buff);
+ ICCfailure = "dead server";
+ return -1;
+ }
+ }
+
+#ifdef HAVE_UNIX_DOMAIN_SOCKETS
+
+ /* Read the reply. */
+ i = RECVorREAD(ICCfd, buff, bufsiz);
+ if ((unsigned int) i < HEADER_SIZE) {
+ free(buff);
+ ICCfailure = "read";
+ return -1;
+ }
+ memcpy(&protocol, buff, sizeof(protocol));
+ memcpy(&rlen, buff + sizeof(protocol), sizeof(rlen));
+ rlen = ntohs(rlen);
+
+ if (i != rlen) {
+ free(buff);
+ ICCfailure = "short read";
+ return -1;
+ }
+
+ if (protocol != ICC_PROTOCOL_1) {
+ free(buff);
+ ICCfailure = "protocol mismatch";
+ return -1;
+ }
+
+ memmove(buff, buff + HEADER_SIZE, rlen - HEADER_SIZE);
+ i -= HEADER_SIZE;
+
+ buff[i] = '\0';
+
+#else /* !HAVE_UNIX_DOMAIN_SOCKETS */
+
+ i = RECVorREAD(ICCfd, buff, HEADER_SIZE);
+ if (i != HEADER_SIZE)
+ return -1;
+
+ memcpy(&protocol, buff, sizeof(protocol));
+ memcpy(&rlen, buff + sizeof(protocol), sizeof(rlen));
+ rlen = ntohs(rlen) - HEADER_SIZE;
+ if (rlen > bufsiz) {
+ ICCfailure = "bad length";
+ return -1;
+ }
+
+ i = RECVorREAD(ICCfd, buff, rlen);
+ if (i != rlen) {
+ ICCfailure = "short read";
+ return -1;
+ }
+
+ buff[i] = '\0';
+
+ if (protocol != ICC_PROTOCOL_1) {
+ ICCfailure = "protocol mismatch";
+ return -1;
+ }
+
+#endif /* !HAVE_UNIX_DOMAIN_SOCKETS */
+
+ /* Parse the rest of the reply; expected to be like
+ <exitcode><space><text>" */
+ i = 0;
+ if (CTYPE(isdigit, buff[0])) {
+ for (p = buff; *p && CTYPE(isdigit, *p); p++)
+ continue;
+ if (*p) {
+ save = *p;
+ *p = '\0';
+ i = atoi(buff);
+ *p = save;
+ }
+ }
+ if (replyp)
+ *replyp = buff;
+ else
+ free(buff);
+
+ return i;
+}
+
+
+/*
+** Send a "cancel" command.
+*/
+int
+ICCcancel(const char *msgid)
+{
+ const char *args[2];
+
+ args[0] = msgid;
+ args[1] = NULL;
+ return ICCcommand(SC_CANCEL, args, NULL);
+}
+
+
+/*
+** Send a "go" command.
+*/
+int
+ICCgo(const char *why)
+{
+ const char *args[2];
+
+ args[0] = why;
+ args[1] = NULL;
+ return ICCcommand(SC_GO, args, NULL);
+}
+
+
+/*
+** Send a "pause" command.
+*/
+int
+ICCpause(const char *why)
+{
+ const char *args[2];
+
+ args[0] = why;
+ args[1] = NULL;
+ return ICCcommand(SC_PAUSE, args, NULL);
+}
+
+
+/*
+** Send a "reserve" command.
+*/
+int
+ICCreserve(const char *why)
+{
+ const char *args[2];
+
+ args[0] = why;
+ args[1] = NULL;
+ return ICCcommand(SC_RESERVE, args, NULL);
+}
--- /dev/null
+/* $Id: list.c 6168 2003-01-21 06:27:32Z alexk $
+**
+*/
+
+#include "config.h"
+#include "clibrary.h"
+
+#include "inn/list.h"
+
+void
+list_new(struct list *list)
+{
+ list->head = (struct node *)&list->tail;
+ list->tailpred = (struct node *)&list->head;
+ list->tail = NULL;
+}
+
+struct node *
+list_addhead(struct list *list, struct node *node)
+{
+ node->succ = list->head;
+ node->pred = (struct node *)&list->head;
+ list->head->pred = node;
+ list->head = node;
+ return node;
+}
+
+struct node *
+list_addtail(struct list *list, struct node *node)
+{
+ node->succ = (struct node *)&list->tail;
+ node->pred = list->tailpred;
+ list->tailpred->succ = node;
+ list->tailpred = node;
+ return node;
+}
+
+struct node *
+list_remhead(struct list *list)
+{
+ struct node *node;
+
+ node = list->head->succ;
+ if (node) {
+ node->pred = (struct node *)&list->head;
+ node = list->head;
+ list->head = node->succ;
+ }
+ return node;
+}
+
+struct node *
+list_head(struct list *list)
+{
+ if (list->head->succ)
+ return list->head;
+ return NULL;
+}
+
+struct node *
+list_tail(struct list *list)
+{
+ if (list->tailpred->pred)
+ return list->tailpred;
+ return NULL;
+}
+
+struct node *
+list_succ(struct node *node)
+{
+ if (node->succ->succ)
+ return node->succ;
+ return NULL;
+}
+
+struct node *
+list_pred(struct node *node)
+{
+ if (node->pred->pred)
+ return node->pred;
+ return NULL;
+}
+
+struct node *
+list_remove(struct node *node)
+{
+ node->pred->succ = node->succ;
+ node->succ->pred = node->pred;
+ return node;
+}
+
+struct node *
+list_remtail(struct list *list)
+{
+ struct node *node;
+
+ node = list_tail(list);
+ if (node)
+ list_remove(node);
+ return node;
+}
+
+bool
+list_isempty(struct list *list)
+{
+ return list->tailpred == (struct node *)list;
+}
+
+struct node *
+list_insert(struct list *list, struct node *node, struct node *pred)
+{
+ if (pred) {
+ if (pred->succ) {
+ node->succ = pred->succ;
+ node->pred = pred;
+ pred->succ->pred = node;
+ pred->succ = node;
+ } else {
+ list_addtail(list, node);
+ }
+ } else {
+ list_addhead(list, node);
+ }
+ return node;
+}
--- /dev/null
+/* $Id: localopen.c 6155 2003-01-19 19:58:25Z rra $
+**
+*/
+
+#include "config.h"
+#include "clibrary.h"
+#include <errno.h>
+#include <sys/socket.h>
+
+#include "inn/innconf.h"
+#include "libinn.h"
+#include "nntp.h"
+#include "paths.h"
+
+#if HAVE_UNIX_DOMAIN_SOCKETS
+# include <sys/un.h>
+#endif
+
+
+/*
+** Open a connection to the local InterNetNews NNTP server and optionally
+** create stdio FILE's for talking to it. Return -1 on error.
+*/
+int
+NNTPlocalopen(FILE **FromServerp, FILE **ToServerp, char *errbuff)
+{
+#if defined(HAVE_UNIX_DOMAIN_SOCKETS)
+ int i;
+ int j;
+ int oerrno;
+ struct sockaddr_un server;
+ FILE *F;
+ char mybuff[NNTP_STRLEN + 2];
+ char *buff;
+
+ buff = errbuff ? errbuff : mybuff;
+ *buff = '\0';
+
+ /* Create a socket. */
+ if ((i = socket(AF_UNIX, SOCK_STREAM, 0)) < 0)
+ return -1;
+
+ /* Connect to the server. */
+ memset(&server, 0, sizeof server);
+ server.sun_family = AF_UNIX;
+ strlcpy(server.sun_path, innconf->pathrun, sizeof(server.sun_path));
+ strlcat(server.sun_path, "/", sizeof(server.sun_path));
+ strlcat(server.sun_path, _PATH_NNTPCONNECT, sizeof(server.sun_path));
+ if (connect(i, (struct sockaddr *)&server, SUN_LEN(&server)) < 0) {
+ oerrno = errno;
+ close(i);
+ errno = oerrno;
+ return -1;
+ }
+
+ /* Connected -- now make sure we can post. */
+ if ((F = fdopen(i, "r")) == NULL) {
+ oerrno = errno;
+ close(i);
+ errno = oerrno;
+ return -1;
+ }
+ if (fgets(buff, sizeof mybuff, F) == NULL) {
+ oerrno = errno;
+ fclose(F);
+ errno = oerrno;
+ return -1;
+ }
+ j = atoi(buff);
+ if (j != NNTP_POSTOK_VAL && j != NNTP_NOPOSTOK_VAL) {
+ fclose(F);
+ /* This seems like a reasonable error code to use... */
+ errno = EPERM;
+ return -1;
+ }
+
+ *FromServerp = F;
+ if ((*ToServerp = fdopen(dup(i), "w")) == NULL) {
+ oerrno = errno;
+ fclose(F);
+ errno = oerrno;
+ return -1;
+ }
+ return 0;
+#else
+ return NNTPconnect("127.0.0.1", innconf->port, FromServerp, ToServerp,
+ errbuff);
+#endif /* defined(HAVE_UNIX_DOMAIN_SOCKETS) */
+}
--- /dev/null
+/* $Id: lockfile.c 6020 2002-12-20 00:19:58Z rra $
+**
+** Lock a file or a range in a file.
+**
+** Provides inn_lock_file and inn_lock_range functions to lock or unlock a
+** file or ranges within a file with a more convenient syntax than fcntl.
+** Assume that fcntl is available.
+*/
+
+#include "config.h"
+#include "clibrary.h"
+#include <errno.h>
+#include <fcntl.h>
+
+#include "libinn.h"
+
+bool
+inn_lock_file(int fd, enum inn_locktype type, bool block)
+{
+ return inn_lock_range(fd, type, block, 0, 0);
+}
+
+bool
+inn_lock_range(int fd, enum inn_locktype type, bool block, off_t offset,
+ off_t size)
+{
+ struct flock fl;
+ int status;
+
+ switch (type) {
+ case INN_LOCK_READ: fl.l_type = F_RDLCK; break;
+ case INN_LOCK_WRITE: fl.l_type = F_WRLCK; break;
+ default:
+ case INN_LOCK_UNLOCK: fl.l_type = F_UNLCK; break;
+ }
+
+ do {
+ fl.l_whence = SEEK_SET;
+ fl.l_start = offset;
+ fl.l_len = size;
+
+ status = fcntl(fd, block ? F_SETLKW : F_SETLK, &fl);
+ } while (status == -1 && errno == EINTR);
+ return (status != -1);
+}
--- /dev/null
+#include "config.h"
+#include "clibrary.h"
+#include <errno.h>
+#include <sys/stat.h>
+
+#include "libinn.h"
+
+/*
+** Try to make one directory. Return false on error.
+*/
+static bool MakeDir(char *Name)
+{
+ struct stat Sb;
+
+ if (mkdir(Name, GROUPDIR_MODE) >= 0) {
+ return true;
+ }
+
+ /* See if it failed because it already exists. */
+ if (stat(Name, &Sb) >= 0 && S_ISDIR(Sb.st_mode)) {
+ errno = 0;
+ return true;
+ }
+ return false;
+}
+
+
+/*
+** Given a directory, comp/foo/bar, create that directory and all
+** intermediate directories needed. Return true if ok, else false.
+*/
+bool MakeDirectory(char *Name, bool Recurse)
+{
+ char *p;
+ bool made;
+
+ /* Optimize common case -- parent almost always exists. */
+ if (MakeDir(Name))
+ return true;
+
+ if (!Recurse)
+ return false;
+
+ /* Try to make each of comp and comp/foo in turn. */
+ for (p = (Name[0] == '/') ? &Name[1] : Name; *p; p++)
+ if (*p == '/') {
+ *p = '\0';
+ made = MakeDir(Name);
+ *p = '/';
+ if (!made)
+ return false;
+ }
+
+ return MakeDir(Name);
+}
--- /dev/null
+/* $Id: md5.c 4154 2000-10-31 16:28:53Z kondou $
+**
+** RSA Data Security, Inc. MD5 Message-Digest Algorithm
+**
+** The standard MD5 message digest routines, from RSA Data Security, Inc.
+** by way of Landon Curt Noll, modified and integrated into INN by Clayton
+** O'Neill and then simplified somewhat, converted to INN coding style,
+** and commented by Russ Allbery.
+**
+** To form the message digest for a message M:
+** (1) Initialize a context buffer md5_context using md5_init
+** (2) Call md5_update on md5_context and M
+** (3) Call md5_final on md5_context
+** The message digest is now in md5_context->digest[0...15].
+**
+** Alternately, just call md5_hash on M, passing it a buffer of at least
+** 16 bytes into which to put the digest. md5_hash does the above
+** internally for you and is the most convenient interface; the interface
+** described above, however, is better when all of the data to hash isn't
+** available neatly in a single buffer (such as hashing data aquired a
+** block at a time).
+**
+** For information about MD5, see RFC 1321.
+**
+** LANDON CURT NOLL DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE,
+** INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO
+** EVENT SHALL LANDON CURT NOLL BE LIABLE FOR ANY SPECIAL, INDIRECT OR
+** CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF
+** USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR
+** OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+** PERFORMANCE OF THIS SOFTWARE.
+**
+** Copyright (C) 1990, RSA Data Security, Inc. All rights reserved.
+**
+** License to copy and use this software is granted provided that it is
+** identified as the "RSA Data Security, Inc. MD5 Message-Digest Algorithm"
+** in all material mentioning or referencing this software or this
+** function.
+**
+** License is also granted to make and use derivative works provided that
+** such works are identified as "derived from the RSA Data Security,
+** Inc. MD5 Message-Digest Algorithm" in all material mentioning or
+** referencing the derived work.
+**
+** RSA Data Security, Inc. makes no representations concerning either the
+** merchantability of this software or the suitability of this software for
+** any particular purpose. It is provided "as is" without express or
+** implied warranty of any kind.
+**
+** These notices must be retained in any copies of any part of this
+** documentation and/or software.
+*/
+
+/*
+** The actual mathematics and cryptography are at the bottom of this file,
+** as those parts should be fully debugged and very infrequently changed.
+** The core of actual work is done by md5_transform. The beginning of this
+** file contains the infrastructure around the mathematics.
+*/
+
+#include "config.h"
+#include "clibrary.h"
+#include "inn/md5.h"
+
+/* Rotate a 32-bit value left, used by both the MD5 mathematics and by the
+ routine below to byteswap data on big-endian machines. */
+#define ROT(X, n) (((X) << (n)) | ((X) >> (32 - (n))))
+
+/* Almost zero fill padding, used by md5_final. The 0x80 is part of the MD5
+ hash algorithm, from RFC 1321 section 3.1:
+
+ Padding is performed as follows: a single "1" bit is appended to the
+ message, and then "0" bits are appended so that the length in bits of
+ the padded message becomes congruent to 448, modulo 512. In all, at
+ least one bit and at most 512 bits are appended.
+
+ Let the compiler zero the remainder of the array for us, guaranteed by
+ ISO C99 6.7.8 paragraph 21. */
+static const unsigned char padding[MD5_CHUNKSIZE] = { 0x80, 0 /* 0, ... */ };
+
+/* Internal prototypes. */
+static void md5_transform(uint32_t *, const uint32_t *);
+static void md5_update_block(struct md5_context *, const unsigned char *,
+ size_t);
+
+/* MD5 requires that input data be treated as words in little-endian byte
+ order. From RFC 1321 section 2:
+
+ Similarly, a sequence of bytes can be interpreted as a sequence of
+ 32-bit words, where each consecutive group of four bytes is interpreted
+ as a word with the low-order (least significant) byte given first.
+
+ Input data must therefore be byteswapped on big-endian machines, as must
+ the 16-byte result digest. Since we have to make a copy of the incoming
+ data anyway to ensure alignment for 4-byte access, we can byteswap it in
+ place. */
+#if !WORDS_BIGENDIAN
+# define decode(data) /* empty */
+# define encode(data, out) memcpy((out), (data), MD5_DIGESTSIZE)
+#else
+
+/* The obvious way to do this is to pull single bytes at a time out of the
+ input array and build words from them; this requires three shifts and
+ three Boolean or operations, but it requires four memory reads per word
+ unless the compiler is really smart. Since we can assume four-byte
+ alignment for the input data, use this optimized routine from J. Touch,
+ USC/ISI. This requires four shifts, two ands, and two ors, but only one
+ memory read per word. */
+#define swap(word) \
+ do { \
+ htmp = ROT((word), 16); \
+ ltmp = htmp >> 8; \
+ htmp &= 0x00ff00ff; \
+ ltmp &= 0x00ff00ff; \
+ htmp <<= 8; \
+ (word) = htmp | ltmp; \
+ } while (0)
+
+/* We process 16 words of data (one MD5 block) of data at a time, completely
+ unrolling the loop manually since it should allow the compiler to take
+ advantage of pipelining and parallel arithmetic units. */
+static void
+decode(uint32_t *data)
+{
+ uint32_t ltmp, htmp;
+
+ swap(data[ 0]); swap(data[ 1]); swap(data[ 2]); swap(data[ 3]);
+ swap(data[ 4]); swap(data[ 5]); swap(data[ 6]); swap(data[ 7]);
+ swap(data[ 8]); swap(data[ 9]); swap(data[10]); swap(data[11]);
+ swap(data[12]); swap(data[13]); swap(data[14]); swap(data[15]);
+}
+
+/* Used by md5_final to generate the final digest. The digest is not
+ guaranteed to be aligned for 4-byte access but we are allowed to be
+ destructive, so byteswap first and then copy the result over. */
+static void
+encode(uint32_t *data, unsigned char *out)
+{
+ uint32_t ltmp, htmp;
+
+ swap(data[0]);
+ swap(data[1]);
+ swap(data[2]);
+ swap(data[3]);
+ memcpy(out, data, MD5_DIGESTSIZE);
+}
+
+#endif /* WORDS_BIGENDIAN */
+
+
+/*
+** That takes care of the preliminaries; now the real fun begins. The
+** initialization function for a struct md5_context; set lengths to zero
+** and initialize our working buffer with the magic constants (c.f. RFC
+** 1321 section 3.3).
+*/
+void
+md5_init(struct md5_context *context)
+{
+ context->buf[0] = 0x67452301U;
+ context->buf[1] = 0xefcdab89U;
+ context->buf[2] = 0x98badcfeU;
+ context->buf[3] = 0x10325476U;
+
+ context->count[0] = 0;
+ context->count[1] = 0;
+ context->datalen = 0;
+}
+
+
+/*
+** The workhorse function, called for each chunk of data. Update the
+** message-digest context to account for the presence of each of the
+** characters data[0 .. count - 1] in the message whose digest is being
+** computed. Accepts any length of data; all whole blocks are processed
+** and the remaining data is buffered for later processing by either
+** another call to md5_update or by md5_final.
+*/
+void
+md5_update(struct md5_context *context, const unsigned char *data,
+ size_t count)
+{
+ unsigned int datalen = context->datalen;
+ unsigned int left;
+ size_t high_count, low_count, used;
+
+ /* Update the count of hashed bytes. The machinations here are to do
+ the right thing on a platform where size_t > 32 bits without causing
+ compiler warnings on platforms where it's 32 bits. RFC 1321 section
+ 3.2 says:
+
+ A 64-bit representation of b (the length of the message before the
+ padding bits were added) is appended to the result of the previous
+ step. In the unlikely event that b is greater than 2^64, then only
+ the low-order 64 bits of b are used.
+
+ so we can just ignore the higher bits of count if size_t is larger
+ than 64 bits. (Think ahead!) If size_t is only 32 bits, the
+ compiler should kill the whole if statement as dead code. */
+ if (sizeof(count) > 4) {
+ high_count = count >> 31;
+ context->count[1] += (high_count >> 1) & 0xffffffffU;
+ }
+
+ /* Now deal with count[0]. Add in the low 32 bits of count and
+ increment count[1] if count[0] wraps. Isn't unsigned arithmetic
+ cool? */
+ low_count = count & 0xffffffffU;
+ context->count[0] += low_count;
+ if (context->count[0] < low_count)
+ context->count[1]++;
+
+ /* See if we already have some data queued. If so, try to complete a
+ block and then deal with it first. If the new data still doesn't
+ fill the buffer, add it to the buffer and return. Otherwise,
+ transform that block and update data and count to account for the
+ data we've already used. */
+ if (datalen > 0) {
+ left = MD5_CHUNKSIZE - datalen;
+ if (left > count) {
+ memcpy(context->in.byte + datalen, data, count);
+ context->datalen += count;
+ return;
+ } else {
+ memcpy(context->in.byte + datalen, data, left);
+ decode(context->in.word);
+ md5_transform(context->buf, context->in.word);
+ data += left;
+ count -= left;
+ context->datalen = 0;
+ }
+ }
+
+ /* If we have a block of data or more left, pass the rest off to
+ md5_update_block to deal with all of the full blocks available. */
+ if (count >= MD5_CHUNKSIZE) {
+ md5_update_block(context, data, count);
+ used = (count / MD5_CHUNKSIZE) * MD5_CHUNKSIZE;
+ data += used;
+ count -= used;
+ }
+
+ /* If there's anything left, buffer it until we can complete a block or
+ for md5_final to deal with. */
+ if (count > 0) {
+ memcpy(context->in.byte, data, count);
+ context->datalen = count;
+ }
+}
+
+
+/*
+** Update the message-digest context to account for the presence of each of
+** the characters data[0 .. count - 1] in the message whose digest is being
+** computed, except that we only deal with full blocks of data. The data
+** is processed one block at a time, and partial blocks at the end are
+** ignored (they're dealt with by md5_update, which calls this routine.
+**
+** Note that we always make a copy of the input data into an array of
+** 4-byte values. If our input data were guaranteed to be aligned for
+** 4-byte access, we could just use the input buffer directly on
+** little-endian machines, and in fact this implementation used to do that.
+** However, a requirement to align isn't always easily detectable, even by
+** configure (an Alpha running Tru64 will appear to allow unaligned
+** accesses, but will spew errors to the terminal if you do it). On top of
+** that, even when it's allowed, unaligned access is quite frequently
+** slower; we're about to do four reads of each word of the input data for
+** the calculations, so doing one additional copy of the data up-front is
+** probably worth it.
+*/
+static void
+md5_update_block(struct md5_context *context, const unsigned char *data,
+ size_t count)
+{
+ uint32_t in[MD5_CHUNKWORDS];
+
+ /* Process data in MD5_CHUNKSIZE blocks. */
+ while (count >= MD5_CHUNKSIZE) {
+ memcpy(in, data, MD5_CHUNKSIZE);
+ decode(in);
+ md5_transform(context->buf, in);
+ data += MD5_CHUNKSIZE;
+ count -= MD5_CHUNKSIZE;
+ }
+}
+
+
+/*
+** Terminates the message-digest computation, accounting for any final
+** trailing data and adding the message length to the hashed data, and ends
+** with the desired message digest in context->digest[0...15].
+*/
+void
+md5_final(struct md5_context *context)
+{
+ unsigned int pad_needed, left;
+ uint32_t count[2];
+ uint32_t *countloc;
+
+ /* Save the count before appending the padding. */
+ count[0] = context->count[0];
+ count[1] = context->count[1];
+
+ /* Pad the final block of data. RFC 1321 section 3.1:
+
+ The message is "padded" (extended) so that its length (in bits) is
+ congruent to 448, modulo 512. That is, the message is extended so
+ that it is just 64 bits shy of being a multiple of 512 bits long.
+ Padding is always performed, even if the length of the message is
+ already congruent to 448, modulo 512.
+
+ The 64 bits (two words) left are for the 64-bit count of bits
+ hashed. We'll need at most 64 bytes of padding; lucky that the
+ padding array is exactly that size! */
+ left = context->datalen;
+ pad_needed = (left < 64 - 8) ? (64 - 8 - left) : (128 - 8 - left);
+ md5_update(context, padding, pad_needed);
+
+ /* Append length in bits and transform. Note that we cheat slightly
+ here to save a few operations on big-endian machines; the algorithm
+ says that we should add the length, in little-endian byte order, to
+ the last block of data. We'd then transform it into big-endian order
+ on a big-endian machine. But the count is *already* in big-endian
+ order on a big-endian machine, so effectively we'd be byteswapping it
+ twice. Instead, add it to the block after doing byte swapping on the
+ rest.
+
+ Note that we've been counting *bytes*, but the algorithm demands the
+ length in *bits*, so shift things around a bit. */
+ decode(context->in.word);
+ countloc = &context->in.word[MD5_CHUNKWORDS - 2];
+ countloc[0] = count[0] << 3;
+ countloc[1] = (count[1] << 3) | (count[0] >> 29);
+ md5_transform(context->buf, context->in.word);
+
+ /* Recover the final digest. Whoo-hoo, we're done! */
+ encode(context->buf, context->digest);
+}
+
+
+/*
+** A convenience wrapper around md5_init, md5_update, and md5_final. Takes
+** a pointer to a buffer of data, the length of the data, and a pointer to
+** a buffer of at least 16 bytes into which to write the message digest.
+*/
+void
+md5_hash(const unsigned char *data, size_t length, unsigned char *digest)
+{
+ struct md5_context context;
+
+ md5_init(&context);
+ md5_update(&context, data, length);
+ md5_final(&context);
+ memcpy(digest, context.digest, MD5_DIGESTSIZE);
+}
+
+
+/*
+** Look out, here comes the math.
+**
+** There are no user-serviceable parts below this point unless you know
+** quite a bit about MD5 or optimization of integer math. The only
+** function remaining, down below, is md5_transform; the rest of this is
+** setting up macros to make that function more readable.
+**
+** The F, G, H and I are basic MD5 functions. The following identity saves
+** one boolean operation.
+**
+** F: (((x) & (y)) | (~(x) & (z))) == ((z) ^ ((x) & ((y) ^ (z))))
+** G: (((x) & (z)) | ((y) & ~(z))) == ((y) ^ ((z) & ((x) ^ (y))))
+*/
+#define F(x, y, z) ((z) ^ ((x) & ((y) ^ (z))))
+#define G(x, y, z) ((y) ^ ((z) & ((x) ^ (y))))
+#define H(x, y, z) ((x) ^ (y) ^ (z))
+#define I(x, y, z) ((y) ^ ((x) | (~z)))
+
+/*
+** FF, GG, HH, and II transformations for rounds 1, 2, 3, and 4. Rotation
+** is separate from addition to prevent recomputation. S?? are the shift
+** values for each round.
+*/
+#define S11 7
+#define S12 12
+#define S13 17
+#define S14 22
+#define FF(a, b, c, d, x, s, ac) \
+ { \
+ (a) += F((b), (c), (d)) + (x) + (uint32_t) (ac); \
+ (a) = ROT((a), (s)); \
+ (a) += (b); \
+ }
+
+#define S21 5
+#define S22 9
+#define S23 14
+#define S24 20
+#define GG(a, b, c, d, x, s, ac) \
+ { \
+ (a) += G((b), (c), (d)) + (x) + (uint32_t) (ac); \
+ (a) = ROT((a), (s)); \
+ (a) += (b); \
+ }
+
+#define S31 4
+#define S32 11
+#define S33 16
+#define S34 23
+#define HH(a, b, c, d, x, s, ac) \
+ { \
+ (a) += H((b), (c), (d)) + (x) + (uint32_t) (ac); \
+ (a) = ROT((a), (s)); \
+ (a) += (b); \
+ }
+
+#define S41 6
+#define S42 10
+#define S43 15
+#define S44 21
+#define II(a, b, c, d, x, s, ac) \
+ { \
+ (a) += I((b), (c), (d)) + (x) + (uint32_t) (ac); \
+ (a) = ROT((a), (s)); \
+ (a) += (b); \
+ }
+
+/*
+** Basic MD5 step. Transforms buf based on in.
+*/
+static void
+md5_transform(uint32_t *buf, const uint32_t *in)
+{
+ uint32_t a = buf[0];
+ uint32_t b = buf[1];
+ uint32_t c = buf[2];
+ uint32_t d = buf[3];
+
+ /* Round 1 */
+ FF(a, b, c, d, in[ 0], S11, 3614090360UL); /* 1 */
+ FF(d, a, b, c, in[ 1], S12, 3905402710UL); /* 2 */
+ FF(c, d, a, b, in[ 2], S13, 606105819UL); /* 3 */
+ FF(b, c, d, a, in[ 3], S14, 3250441966UL); /* 4 */
+ FF(a, b, c, d, in[ 4], S11, 4118548399UL); /* 5 */
+ FF(d, a, b, c, in[ 5], S12, 1200080426UL); /* 6 */
+ FF(c, d, a, b, in[ 6], S13, 2821735955UL); /* 7 */
+ FF(b, c, d, a, in[ 7], S14, 4249261313UL); /* 8 */
+ FF(a, b, c, d, in[ 8], S11, 1770035416UL); /* 9 */
+ FF(d, a, b, c, in[ 9], S12, 2336552879UL); /* 10 */
+ FF(c, d, a, b, in[10], S13, 4294925233UL); /* 11 */
+ FF(b, c, d, a, in[11], S14, 2304563134UL); /* 12 */
+ FF(a, b, c, d, in[12], S11, 1804603682UL); /* 13 */
+ FF(d, a, b, c, in[13], S12, 4254626195UL); /* 14 */
+ FF(c, d, a, b, in[14], S13, 2792965006UL); /* 15 */
+ FF(b, c, d, a, in[15], S14, 1236535329UL); /* 16 */
+
+ /* Round 2 */
+ GG(a, b, c, d, in[ 1], S21, 4129170786UL); /* 17 */
+ GG(d, a, b, c, in[ 6], S22, 3225465664UL); /* 18 */
+ GG(c, d, a, b, in[11], S23, 643717713UL); /* 19 */
+ GG(b, c, d, a, in[ 0], S24, 3921069994UL); /* 20 */
+ GG(a, b, c, d, in[ 5], S21, 3593408605UL); /* 21 */
+ GG(d, a, b, c, in[10], S22, 38016083UL); /* 22 */
+ GG(c, d, a, b, in[15], S23, 3634488961UL); /* 23 */
+ GG(b, c, d, a, in[ 4], S24, 3889429448UL); /* 24 */
+ GG(a, b, c, d, in[ 9], S21, 568446438UL); /* 25 */
+ GG(d, a, b, c, in[14], S22, 3275163606UL); /* 26 */
+ GG(c, d, a, b, in[ 3], S23, 4107603335UL); /* 27 */
+ GG(b, c, d, a, in[ 8], S24, 1163531501UL); /* 28 */
+ GG(a, b, c, d, in[13], S21, 2850285829UL); /* 29 */
+ GG(d, a, b, c, in[ 2], S22, 4243563512UL); /* 30 */
+ GG(c, d, a, b, in[ 7], S23, 1735328473UL); /* 31 */
+ GG(b, c, d, a, in[12], S24, 2368359562UL); /* 32 */
+
+ /* Round 3 */
+ HH(a, b, c, d, in[ 5], S31, 4294588738UL); /* 33 */
+ HH(d, a, b, c, in[ 8], S32, 2272392833UL); /* 34 */
+ HH(c, d, a, b, in[11], S33, 1839030562UL); /* 35 */
+ HH(b, c, d, a, in[14], S34, 4259657740UL); /* 36 */
+ HH(a, b, c, d, in[ 1], S31, 2763975236UL); /* 37 */
+ HH(d, a, b, c, in[ 4], S32, 1272893353UL); /* 38 */
+ HH(c, d, a, b, in[ 7], S33, 4139469664UL); /* 39 */
+ HH(b, c, d, a, in[10], S34, 3200236656UL); /* 40 */
+ HH(a, b, c, d, in[13], S31, 681279174UL); /* 41 */
+ HH(d, a, b, c, in[ 0], S32, 3936430074UL); /* 42 */
+ HH(c, d, a, b, in[ 3], S33, 3572445317UL); /* 43 */
+ HH(b, c, d, a, in[ 6], S34, 76029189UL); /* 44 */
+ HH(a, b, c, d, in[ 9], S31, 3654602809UL); /* 45 */
+ HH(d, a, b, c, in[12], S32, 3873151461UL); /* 46 */
+ HH(c, d, a, b, in[15], S33, 530742520UL); /* 47 */
+ HH(b, c, d, a, in[ 2], S34, 3299628645UL); /* 48 */
+
+ /* Round 4 */
+ II(a, b, c, d, in[ 0], S41, 4096336452UL); /* 49 */
+ II(d, a, b, c, in[ 7], S42, 1126891415UL); /* 50 */
+ II(c, d, a, b, in[14], S43, 2878612391UL); /* 51 */
+ II(b, c, d, a, in[ 5], S44, 4237533241UL); /* 52 */
+ II(a, b, c, d, in[12], S41, 1700485571UL); /* 53 */
+ II(d, a, b, c, in[ 3], S42, 2399980690UL); /* 54 */
+ II(c, d, a, b, in[10], S43, 4293915773UL); /* 55 */
+ II(b, c, d, a, in[ 1], S44, 2240044497UL); /* 56 */
+ II(a, b, c, d, in[ 8], S41, 1873313359UL); /* 57 */
+ II(d, a, b, c, in[15], S42, 4264355552UL); /* 58 */
+ II(c, d, a, b, in[ 6], S43, 2734768916UL); /* 59 */
+ II(b, c, d, a, in[13], S44, 1309151649UL); /* 60 */
+ II(a, b, c, d, in[ 4], S41, 4149444226UL); /* 61 */
+ II(d, a, b, c, in[11], S42, 3174756917UL); /* 62 */
+ II(c, d, a, b, in[ 2], S43, 718787259UL); /* 63 */
+ II(b, c, d, a, in[ 9], S44, 3951481745UL); /* 64 */
+
+ buf[0] += a;
+ buf[1] += b;
+ buf[2] += c;
+ buf[3] += d;
+}
--- /dev/null
+/* $Id: memcmp.c 5049 2001-12-12 09:06:00Z rra $
+**
+** Replacement for a missing or broken memcmp.
+**
+** Written by Russ Allbery <rra@stanford.edu>
+** This work is hereby placed in the public domain by its author.
+**
+** Provides the same functionality as the standard library routine memcmp
+** for those platforms that don't have it or where it doesn't work right
+** (such as on SunOS where it can't deal with eight-bit characters).
+*/
+
+#include "config.h"
+#include <sys/types.h>
+
+/* If we're running the test suite, rename memcmp to avoid conflicts with
+ the system version. */
+#if TESTING
+# define memcmp test_memcmp
+int test_memcmp(const void *, const void *, size_t);
+#endif
+
+int
+memcmp(const void *s1, const void *s2, size_t n)
+{
+ size_t i;
+ const unsigned char *p1, *p2;
+
+ /* It's technically illegal to call memcmp with NULL pointers, but we
+ may as well check anyway. */
+ if (!s1)
+ return !s2 ? 0 : -1;
+ if (!s2)
+ return 1;
+
+ p1 = (const unsigned char *) s1;
+ p2 = (const unsigned char *) s2;
+ for (i = 0; i < n; i++, p1++, p2++)
+ if (*p1 != *p2)
+ return (int) *p1 - (int) *p2;
+ return 0;
+}
--- /dev/null
+/* $Id: messages.c 5496 2002-06-07 13:59:06Z alexk $
+**
+** Message and error reporting (possibly fatal).
+**
+** Usage:
+**
+** extern int cleanup(void);
+** extern void log(int, const char *, va_list, int);
+**
+** message_fatal_cleanup = cleanup;
+** message_program_name = argv[0];
+**
+** warn("Something horrible happened at %lu", time);
+** syswarn("Couldn't unlink temporary file %s", tmpfile);
+**
+** die("Something fatal happened at %lu", time);
+** sysdie("open of %s failed", filename);
+**
+** debug("Some debugging message about %s", string);
+** trace(TRACE_PROGRAM, "Program trace output");
+** notice("Informational notices");
+**
+** message_handlers_warn(1, log);
+** warn("This now goes through our log function");
+**
+** These functions implement message reporting through user-configurable
+** handler functions. debug() only does something if DEBUG is defined,
+** trace() supports sending trace messages in one of a number of configurable
+** classes of traces so that they can be turned on or off independently, and
+** notice() and warn() just output messages as configured. die() similarly
+** outputs a message but then exits, normally with a status of 1.
+**
+** The sys* versions do the same, but append a colon, a space, and the
+** results of strerror(errno) to the end of the message. All functions
+** accept printf-style formatting strings and arguments.
+**
+** If message_fatal_cleanup is non-NULL, it is called before exit by die and
+** sysdie and its return value is used as the argument to exit. It is a
+** pointer to a function taking no arguments and returning an int, and can be
+** used to call cleanup functions or to exit in some alternate fashion (such
+** as by calling _exit).
+**
+** If message_program_name is non-NULL, the string it points to, followed by
+** a colon and a space, is prepended to all error messages logged through the
+** message_log_stdout and message_log_stderr message handlers (the former is
+** the default for notice, and the latter is the default for warn and die).
+**
+** Honoring error_program_name and printing to stderr is just the default
+** handler; with message_handlers_* the handlers for any message function can
+** be changed. By default, notice prints to stdout, warn and die print to
+** stderr, and the others don't do anything at all. These functions take a
+** count of handlers and then that many function pointers, each one to a
+** function that takes a message length (the number of characters snprintf
+** generates given the format and arguments), a format, an argument list as a
+** va_list, and the applicable errno value (if any).
+*/
+
+#include "config.h"
+#include "clibrary.h"
+#include <errno.h>
+#include <syslog.h>
+
+#include "inn/messages.h"
+#include "libinn.h"
+
+/* The default handler lists. */
+static message_handler_func stdout_handlers[2] = {
+ message_log_stdout, NULL
+};
+static message_handler_func stderr_handlers[2] = {
+ message_log_stderr, NULL
+};
+
+/* The list of logging functions currently in effect. */
+static message_handler_func *debug_handlers = NULL;
+static message_handler_func *trace_handlers = NULL;
+static message_handler_func *notice_handlers = stdout_handlers;
+static message_handler_func *warn_handlers = stderr_handlers;
+static message_handler_func *die_handlers = stderr_handlers;
+
+/* If non-NULL, called before exit and its return value passed to exit. */
+int (*message_fatal_cleanup)(void) = NULL;
+
+/* If non-NULL, prepended (followed by ": ") to messages. */
+const char *message_program_name = NULL;
+
+/* Whether or not we're currently outputting a particular type of trace. */
+static bool tracing[TRACE_ALL] = { false /* false, ... */ };
+
+
+/*
+** Set the handlers for a particular message function. Takes a pointer to
+** the handler list, the count of handlers, and the argument list.
+*/
+static void
+message_handlers(message_handler_func **list, int count, va_list args)
+{
+ int i;
+
+ if (*list != stdout_handlers && *list != stderr_handlers)
+ free(*list);
+ *list = xmalloc(sizeof(message_handler_func) * (count + 1));
+ for (i = 0; i < count; i++)
+ (*list)[i] = (message_handler_func) va_arg(args, message_handler_func);
+ (*list)[count] = NULL;
+}
+
+
+/*
+** There's no good way of writing these handlers without a bunch of code
+** duplication since we can't assume variadic macros, but I can at least make
+** it easier to write and keep them consistent.
+*/
+#define HANDLER_FUNCTION(type) \
+ void \
+ message_handlers_ ## type(int count, ...) \
+ { \
+ va_list args; \
+ \
+ va_start(args, count); \
+ message_handlers(& type ## _handlers, count, args); \
+ va_end(args); \
+ }
+HANDLER_FUNCTION(debug)
+HANDLER_FUNCTION(trace)
+HANDLER_FUNCTION(notice)
+HANDLER_FUNCTION(warn)
+HANDLER_FUNCTION(die)
+
+
+/*
+** Print a message to stdout, supporting message_program_name.
+*/
+void
+message_log_stdout(int len UNUSED, const char *fmt, va_list args, int err)
+{
+ if (message_program_name != NULL)
+ fprintf(stdout, "%s: ", message_program_name);
+ vfprintf(stdout, fmt, args);
+ if (err)
+ fprintf(stdout, ": %s", strerror(err));
+ fprintf(stdout, "\n");
+}
+
+
+/*
+** Print a message to stderr, supporting message_program_name. Also flush
+** stdout so that errors and regular output occur in the right order.
+*/
+void
+message_log_stderr(int len UNUSED, const char *fmt, va_list args, int err)
+{
+ fflush(stdout);
+ if (message_program_name != NULL)
+ fprintf(stderr, "%s: ", message_program_name);
+ vfprintf(stderr, fmt, args);
+ if (err)
+ fprintf(stderr, ": %s", strerror(err));
+ fprintf(stderr, "\n");
+}
+
+
+/*
+** Log a message to syslog. This is a helper function used to implement all
+** of the syslog message log handlers. It takes the same arguments as a
+** regular message handler function but with an additional priority
+** argument.
+*/
+static void
+message_log_syslog(int pri, int len, const char *fmt, va_list args, int err)
+{
+ char *buffer;
+
+ buffer = malloc(len + 1);
+ if (buffer == NULL) {
+ fprintf(stderr, "failed to malloc %u bytes at %s line %d: %s",
+ len + 1, __FILE__, __LINE__, strerror(errno));
+ exit(message_fatal_cleanup ? (*message_fatal_cleanup)() : 1);
+ }
+ vsnprintf(buffer, len + 1, fmt, args);
+ syslog(pri, err ? "%s: %m" : "%s", buffer);
+ free(buffer);
+}
+
+
+/*
+** Do the same sort of wrapper to generate all of the separate syslog logging
+** functions.
+*/
+#define SYSLOG_FUNCTION(name, type) \
+ void \
+ message_log_syslog_ ## name(int l, const char *f, va_list a, int e) \
+ { \
+ message_log_syslog(LOG_ ## type, l, f, a, e); \
+ }
+SYSLOG_FUNCTION(debug, DEBUG)
+SYSLOG_FUNCTION(info, INFO)
+SYSLOG_FUNCTION(notice, NOTICE)
+SYSLOG_FUNCTION(warning, WARNING)
+SYSLOG_FUNCTION(err, ERR)
+SYSLOG_FUNCTION(crit, CRIT)
+
+
+/*
+** Enable or disable tracing for particular classes of messages.
+*/
+void
+message_trace_enable(enum message_trace type, bool enable)
+{
+ if (type > TRACE_ALL)
+ return;
+ if (type == TRACE_ALL) {
+ int i;
+
+ for (i = 0; i < TRACE_ALL; i++)
+ tracing[i] = enable;
+ } else {
+ tracing[type] = enable;
+ }
+}
+
+
+/*
+** All of the message handlers. There's a lot of code duplication here too,
+** but each one is still *slightly* different and va_start has to be called
+** multiple times, so it's hard to get rid of the duplication.
+*/
+
+#ifdef DEBUG
+void
+debug(const char *format, ...)
+{
+ va_list args;
+ message_handler_func *log;
+ int length;
+
+ if (debug_handlers == NULL)
+ return;
+ va_start(args, format);
+ length = vsnprintf(NULL, 0, format, args);
+ va_end(args);
+ if (length < 0)
+ return;
+ for (log = debug_handlers; *log != NULL; log++) {
+ va_start(args, format);
+ (**log)(length, format, args, 0);
+ va_end(args);
+ }
+}
+#elif !INN_HAVE_C99_VAMACROS && !INN_HAVE_GNU_VAMACROS
+void debug(const char *format UNUSED, ...) { }
+#endif
+
+void
+trace(enum message_trace type, const char *format, ...)
+{
+ va_list args;
+ message_handler_func *log;
+ int length;
+
+ if (trace_handlers == NULL || !tracing[type])
+ return;
+ va_start(args, format);
+ length = vsnprintf(NULL, 0, format, args);
+ va_end(args);
+ if (length < 0)
+ return;
+ for (log = trace_handlers; *log != NULL; log++) {
+ va_start(args, format);
+ (**log)(length, format, args, 0);
+ va_end(args);
+ }
+}
+
+void
+notice(const char *format, ...)
+{
+ va_list args;
+ message_handler_func *log;
+ int length;
+
+ va_start(args, format);
+ length = vsnprintf(NULL, 0, format, args);
+ va_end(args);
+ if (length < 0)
+ return;
+ for (log = notice_handlers; *log != NULL; log++) {
+ va_start(args, format);
+ (**log)(length, format, args, 0);
+ va_end(args);
+ }
+}
+
+void
+sysnotice(const char *format, ...)
+{
+ va_list args;
+ message_handler_func *log;
+ int length;
+ int error = errno;
+
+ va_start(args, format);
+ length = vsnprintf(NULL, 0, format, args);
+ va_end(args);
+ if (length < 0)
+ return;
+ for (log = notice_handlers; *log != NULL; log++) {
+ va_start(args, format);
+ (**log)(length, format, args, error);
+ va_end(args);
+ }
+}
+
+void
+warn(const char *format, ...)
+{
+ va_list args;
+ message_handler_func *log;
+ int length;
+
+ va_start(args, format);
+ length = vsnprintf(NULL, 0, format, args);
+ va_end(args);
+ if (length < 0)
+ return;
+ for (log = warn_handlers; *log != NULL; log++) {
+ va_start(args, format);
+ (**log)(length, format, args, 0);
+ va_end(args);
+ }
+}
+
+void
+syswarn(const char *format, ...)
+{
+ va_list args;
+ message_handler_func *log;
+ int length;
+ int error = errno;
+
+ va_start(args, format);
+ length = vsnprintf(NULL, 0, format, args);
+ va_end(args);
+ if (length < 0)
+ return;
+ for (log = warn_handlers; *log != NULL; log++) {
+ va_start(args, format);
+ (**log)(length, format, args, error);
+ va_end(args);
+ }
+}
+
+void
+die(const char *format, ...)
+{
+ va_list args;
+ message_handler_func *log;
+ int length;
+
+ va_start(args, format);
+ length = vsnprintf(NULL, 0, format, args);
+ va_end(args);
+ if (length >= 0)
+ for (log = die_handlers; *log != NULL; log++) {
+ va_start(args, format);
+ (**log)(length, format, args, 0);
+ va_end(args);
+ }
+ exit(message_fatal_cleanup ? (*message_fatal_cleanup)() : 1);
+}
+
+void
+sysdie(const char *format, ...)
+{
+ va_list args;
+ message_handler_func *log;
+ int length;
+ int error = errno;
+
+ va_start(args, format);
+ length = vsnprintf(NULL, 0, format, args);
+ va_end(args);
+ if (length >= 0)
+ for (log = die_handlers; *log != NULL; log++) {
+ va_start(args, format);
+ (**log)(length, format, args, error);
+ va_end(args);
+ }
+ exit(message_fatal_cleanup ? (*message_fatal_cleanup)() : 1);
+}
--- /dev/null
+/* $Id: mkstemp.c 5329 2002-03-17 07:39:14Z rra $
+**
+** Replacement for a missing mkstemp.
+**
+** Written by Russ Allbery <rra@stanford.edu>
+** This work is hereby placed in the public domain by its author.
+**
+** Provides the same functionality as the library function mkstemp for those
+** systems that don't have it.
+*/
+
+#include "config.h"
+#include "clibrary.h"
+#include "portable/time.h"
+#include <errno.h>
+#include <fcntl.h>
+
+/* If we're running the test suite, rename mkstemp to avoid conflicts with the
+ system version. #undef it first because some systems may define it to
+ another name. */
+#if TESTING
+# undef mkstemp
+# define mkstemp test_mkstemp
+int test_mkstemp(char *);
+#endif
+
+/* Pick the longest available integer type. */
+#if HAVE_LONG_LONG
+typedef unsigned long long long_int_type;
+#else
+typedef unsigned long long_int_type;
+#endif
+
+int
+mkstemp(char *template)
+{
+ static const char letters[] =
+ "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";
+ size_t length;
+ char *XXXXXX;
+ struct timeval tv;
+ long_int_type randnum, working;
+ int i, tries, fd;
+
+ /* Make sure we have a valid template and initialize p to point at the
+ beginning of the template portion of the string. */
+ length = strlen(template);
+ if (length < 6) {
+ errno = EINVAL;
+ return -1;
+ }
+ XXXXXX = template + length - 6;
+ if (strcmp(XXXXXX, "XXXXXX") != 0) {
+ errno = EINVAL;
+ return -1;
+ }
+
+ /* Get some more-or-less random information. */
+ gettimeofday(&tv, NULL);
+ randnum = ((long_int_type) tv.tv_usec << 16) ^ tv.tv_sec ^ getpid();
+
+ /* Now, try to find a working file name. We try no more than TMP_MAX file
+ names. */
+ for (tries = 0; tries < TMP_MAX; tries++) {
+ for (working = randnum, i = 0; i < 6; i++) {
+ XXXXXX[i] = letters[working % 62];
+ working /= 62;
+ }
+ fd = open(template, O_RDWR | O_CREAT | O_EXCL, 0600);
+ if (fd >= 0 || errno != EEXIST)
+ return fd;
+
+ /* This is a relatively random increment. Cut off the tail end of
+ tv_usec since it's often predictable. */
+ randnum += (tv.tv_usec >> 10) & 0xfff;
+ }
+ errno = EEXIST;
+ return -1;
+}
--- /dev/null
+/* $Id: mmap.c 7598 2007-02-09 02:40:51Z eagle $
+**
+** MMap manipulation routines
+**
+** Written by Alex Kiernan (alex.kiernan@thus.net)
+**
+** These routines work with mmap()ed memory
+*/
+
+#include "config.h"
+#include "clibrary.h"
+#include "portable/mmap.h"
+
+#include "inn/messages.h"
+#include "inn/mmap.h"
+
+/*
+** Figure out what page an address is in and flush those pages
+*/
+void
+inn__mapcntl(void *p, size_t length, int flags)
+{
+ int pagesize;
+
+ pagesize = getpagesize();
+ if (pagesize == -1)
+ syswarn("getpagesize failed");
+ else {
+ char *start, *end;
+
+ start = (char *)((size_t)p & ~(size_t)(pagesize - 1));
+ end = (char *)((size_t)((char *)p + length + pagesize) &
+ ~(size_t)(pagesize - 1));
+ msync(start, end - start, flags);
+ }
+}
--- /dev/null
+%{
+/* $Id: parsedate.y 6372 2003-05-31 19:48:28Z rra $
+**
+** Originally written by Steven M. Bellovin <smb@research.att.com> while
+** at the University of North Carolina at Chapel Hill. Later tweaked by
+** a couple of people on Usenet. Completely overhauled by Rich $alz
+** <rsalz@osf.org> and Jim Berets <jberets@bbn.com> in August, 1990.
+** Further revised (removed obsolete constructs and cleaned up timezone
+** names) in August, 1991, by Rich. Paul Eggert <eggert@twinsun.com>
+** helped in September, 1992.
+**
+** This grammar has six shift/reduce conflicts.
+**
+** This code is in the public domain and has no copyright.
+*/
+
+#include "config.h"
+#include "clibrary.h"
+#include <ctype.h>
+
+#if defined(_HPUX_SOURCE)
+# include <alloca.h>
+#endif
+
+#ifdef TM_IN_SYS_TIME
+# include <sys/time.h>
+#else
+# include <time.h>
+#endif
+
+#include "libinn.h"
+
+
+#define yylhs date_yylhs
+#define yylen date_yylen
+#define yydefred date_yydefred
+#define yydgoto date_yydgoto
+#define yysindex date_yysindex
+#define yyrindex date_yyrindex
+#define yygindex date_yygindex
+#define yytable date_yytable
+#define yycheck date_yycheck
+#define yyparse date_parse
+#define yylex date_lex
+#define yyerror date_error
+#define yymaxdepth date_yymaxdepth
+
+
+static int date_lex(void);
+
+int yyparse(void);
+
+ /* See the LeapYears table in Convert. */
+#define EPOCH 1970
+#define END_OF_TIME 2038
+ /* Constants for general time calculations. */
+#define DST_OFFSET 1
+#define SECSPERDAY (24L * 60L * 60L)
+ /* Readability for TABLE stuff. */
+#define HOUR(x) (x * 60)
+
+#define LPAREN '('
+#define RPAREN ')'
+#define IS7BIT(x) ((unsigned int)(x) < 0200)
+
+
+/*
+** An entry in the lexical lookup table.
+*/
+typedef struct _TABLE {
+ const char * name;
+ int type;
+ time_t value;
+} TABLE;
+
+/*
+** Daylight-savings mode: on, off, or not yet known.
+*/
+typedef enum _DSTMODE {
+ DSTon, DSToff, DSTmaybe
+} DSTMODE;
+
+/*
+** Meridian: am, pm, or 24-hour style.
+*/
+typedef enum _MERIDIAN {
+ MERam, MERpm, MER24
+} MERIDIAN;
+
+
+/*
+** Global variables. We could get rid of most of them by using a yacc
+** union, but this is more efficient. (This routine predates the
+** yacc %union construct.)
+*/
+static char *yyInput;
+static DSTMODE yyDSTmode;
+static int yyHaveDate;
+static int yyHaveRel;
+static int yyHaveTime;
+static time_t yyTimezone;
+static time_t yyDay;
+static time_t yyHour;
+static time_t yyMinutes;
+static time_t yyMonth;
+static time_t yySeconds;
+static time_t yyYear;
+static MERIDIAN yyMeridian;
+static time_t yyRelMonth;
+static time_t yyRelSeconds;
+
+
+/* extern struct tm *localtime(); */
+
+static void date_error(const char *s);
+%}
+
+%union {
+ time_t Number;
+ enum _MERIDIAN Meridian;
+}
+
+%token tDAY tDAYZONE tMERIDIAN tMONTH tMONTH_UNIT tSEC_UNIT tSNUMBER
+%token tUNUMBER tZONE
+
+%type <Number> tDAYZONE tMONTH tMONTH_UNIT tSEC_UNIT
+%type <Number> tSNUMBER tUNUMBER tZONE numzone zone
+%type <Meridian> tMERIDIAN o_merid
+
+%%
+
+spec : /* NULL */
+ | spec item
+ ;
+
+item : time {
+ yyHaveTime++;
+#if defined(lint)
+ /* I am compulsive about lint natterings... */
+ if (yyHaveTime == -1) {
+ YYERROR;
+ }
+#endif /* defined(lint) */
+ }
+ | time zone {
+ yyHaveTime++;
+ yyTimezone = $2;
+ }
+ | date {
+ yyHaveDate++;
+ }
+ | rel {
+ yyHaveRel = 1;
+ }
+ ;
+
+time : tUNUMBER o_merid {
+ if ($1 < 100) {
+ yyHour = $1;
+ yyMinutes = 0;
+ }
+ else {
+ yyHour = $1 / 100;
+ yyMinutes = $1 % 100;
+ }
+ yySeconds = 0;
+ yyMeridian = $2;
+ }
+ | tUNUMBER ':' tUNUMBER o_merid {
+ yyHour = $1;
+ yyMinutes = $3;
+ yySeconds = 0;
+ yyMeridian = $4;
+ }
+ | tUNUMBER ':' tUNUMBER numzone {
+ yyHour = $1;
+ yyMinutes = $3;
+ yyTimezone = $4;
+ yyMeridian = MER24;
+ yyDSTmode = DSToff;
+ }
+ | tUNUMBER ':' tUNUMBER ':' tUNUMBER o_merid {
+ yyHour = $1;
+ yyMinutes = $3;
+ yySeconds = $5;
+ yyMeridian = $6;
+ }
+ | tUNUMBER ':' tUNUMBER ':' tUNUMBER numzone {
+ yyHour = $1;
+ yyMinutes = $3;
+ yySeconds = $5;
+ yyTimezone = $6;
+ yyMeridian = MER24;
+ yyDSTmode = DSToff;
+ }
+ ;
+
+zone : tZONE {
+ $$ = $1;
+ yyDSTmode = DSToff;
+ }
+ | tDAYZONE {
+ $$ = $1;
+ yyDSTmode = DSTon;
+ }
+ | tZONE numzone {
+ /* Only allow "GMT+300" and "GMT-0800" */
+ if ($1 != 0) {
+ YYABORT;
+ }
+ $$ = $2;
+ yyDSTmode = DSToff;
+ }
+ | numzone {
+ $$ = $1;
+ yyDSTmode = DSToff;
+ }
+ ;
+
+numzone : tSNUMBER {
+ int i;
+
+ /* Unix and GMT and numeric timezones -- a little confusing. */
+ if ($1 < 0) {
+ /* Don't work with negative modulus. */
+ $1 = -$1;
+ if ($1 > 9999 || (i = $1 % 100) >= 60) {
+ YYABORT;
+ }
+ $$ = ($1 / 100) * 60 + i;
+ }
+ else {
+ if ($1 > 9999 || (i = $1 % 100) >= 60) {
+ YYABORT;
+ }
+ $$ = -(($1 / 100) * 60 + i);
+ }
+ }
+ ;
+
+date : tUNUMBER '/' tUNUMBER {
+ yyMonth = $1;
+ yyDay = $3;
+ }
+ | tUNUMBER '/' tUNUMBER '/' tUNUMBER {
+ if ($1 > 100) {
+ /* assume YYYY/MM/DD format, so need not to add 1900 */
+ if ($1 > 999) {
+ yyYear = $1;
+ } else {
+ yyYear = 1900 + $1;
+ }
+ yyMonth = $3;
+ yyDay = $5;
+ }
+ else {
+ /* assume MM/DD/YY* format */
+ yyMonth = $1;
+ yyDay = $3;
+ if ($5 > 999) {
+ /* assume year is YYYY format, so need not to add 1900 */
+ yyYear = $5;
+ } else if ($5 < 100) {
+ /* assume year is YY format, so need to add 1900 */
+ yyYear = $5 + (yyYear / 100 + (yyYear % 100 - $5) / 50) * 100;
+ } else {
+ yyYear = 1900 + $5;
+ }
+ }
+ }
+ | tMONTH tUNUMBER {
+ yyMonth = $1;
+ yyDay = $2;
+ }
+ | tMONTH tUNUMBER ',' tUNUMBER {
+ yyMonth = $1;
+ yyDay = $2;
+ if ($4 > 999) {
+ /* assume year is YYYY format, so need not to add 1900 */
+ yyYear = $4;
+ } else if ($4 < 100) {
+ /* assume year is YY format, so need to add 1900 */
+ yyYear = $4 + (yyYear / 100 + (yyYear % 100 - $4) / 50) * 100;
+ } else {
+ yyYear = 1900 + $4;
+ }
+ }
+ | tUNUMBER tMONTH {
+ yyDay = $1;
+ yyMonth = $2;
+ }
+ | tUNUMBER tMONTH tUNUMBER {
+ yyDay = $1;
+ yyMonth = $2;
+ if ($3 > 999) {
+ /* assume year is YYYY format, so need not to add 1900 */
+ yyYear = $3;
+ } else if ($3 < 100) {
+ /* assume year is YY format, so need to add 1900 */
+ yyYear = $3 + (yyYear / 100 + (yyYear % 100 - $3) / 50) * 100;
+ } else {
+ yyYear = 1900 + $3;
+ }
+ }
+ | tDAY ',' tUNUMBER tMONTH tUNUMBER {
+ yyDay = $3;
+ yyMonth = $4;
+ if ($5 > 999) {
+ /* assume year is YYYY format, so need not to add 1900 */
+ yyYear = $5;
+ } else if ($5 < 100) {
+ /* assume year is YY format, so need to add 1900 */
+ yyYear = $5 + (yyYear / 100 + (yyYear % 100 - $5) / 50) * 100;
+ } else {
+ yyYear = 1900 + $5;
+ }
+ }
+ ;
+
+rel : tSNUMBER tSEC_UNIT {
+ yyRelSeconds += $1 * $2;
+ }
+ | tUNUMBER tSEC_UNIT {
+ yyRelSeconds += $1 * $2;
+ }
+ | tSNUMBER tMONTH_UNIT {
+ yyRelMonth += $1 * $2;
+ }
+ | tUNUMBER tMONTH_UNIT {
+ yyRelMonth += $1 * $2;
+ }
+ ;
+
+o_merid : /* NULL */ {
+ $$ = MER24;
+ }
+ | tMERIDIAN {
+ $$ = $1;
+ }
+ ;
+
+%%
+
+/* Month and day table. */
+static TABLE MonthDayTable[] = {
+ { "january", tMONTH, 1 },
+ { "february", tMONTH, 2 },
+ { "march", tMONTH, 3 },
+ { "april", tMONTH, 4 },
+ { "may", tMONTH, 5 },
+ { "june", tMONTH, 6 },
+ { "july", tMONTH, 7 },
+ { "august", tMONTH, 8 },
+ { "september", tMONTH, 9 },
+ { "october", tMONTH, 10 },
+ { "november", tMONTH, 11 },
+ { "december", tMONTH, 12 },
+ /* The value of the day isn't used... */
+ { "sunday", tDAY, 0 },
+ { "monday", tDAY, 0 },
+ { "tuesday", tDAY, 0 },
+ { "wednesday", tDAY, 0 },
+ { "thursday", tDAY, 0 },
+ { "friday", tDAY, 0 },
+ { "saturday", tDAY, 0 },
+};
+
+/* Time units table. */
+static TABLE UnitsTable[] = {
+ { "year", tMONTH_UNIT, 12 },
+ { "month", tMONTH_UNIT, 1 },
+ { "week", tSEC_UNIT, 7 * 24 * 60 * 60 },
+ { "day", tSEC_UNIT, 1 * 24 * 60 * 60 },
+ { "hour", tSEC_UNIT, 60 * 60 },
+ { "minute", tSEC_UNIT, 60 },
+ { "min", tSEC_UNIT, 60 },
+ { "second", tSEC_UNIT, 1 },
+ { "sec", tSEC_UNIT, 1 },
+};
+
+/* Timezone table. */
+static TABLE TimezoneTable[] = {
+ { "gmt", tZONE, HOUR( 0) }, /* Greenwich Mean */
+ { "ut", tZONE, HOUR( 0) }, /* Universal */
+ { "utc", tZONE, HOUR( 0) }, /* Universal Coordinated */
+ { "cut", tZONE, HOUR( 0) }, /* Coordinated Universal */
+ { "z", tZONE, HOUR( 0) }, /* Greenwich Mean */
+ { "wet", tZONE, HOUR( 0) }, /* Western European */
+ { "bst", tDAYZONE, HOUR( 0) }, /* British Summer */
+ { "nst", tZONE, HOUR(3)+30 }, /* Newfoundland Standard */
+ { "ndt", tDAYZONE, HOUR(3)+30 }, /* Newfoundland Daylight */
+ { "ast", tZONE, HOUR( 4) }, /* Atlantic Standard */
+ { "adt", tDAYZONE, HOUR( 4) }, /* Atlantic Daylight */
+ { "est", tZONE, HOUR( 5) }, /* Eastern Standard */
+ { "edt", tDAYZONE, HOUR( 5) }, /* Eastern Daylight */
+ { "cst", tZONE, HOUR( 6) }, /* Central Standard */
+ { "cdt", tDAYZONE, HOUR( 6) }, /* Central Daylight */
+ { "mst", tZONE, HOUR( 7) }, /* Mountain Standard */
+ { "mdt", tDAYZONE, HOUR( 7) }, /* Mountain Daylight */
+ { "pst", tZONE, HOUR( 8) }, /* Pacific Standard */
+ { "pdt", tDAYZONE, HOUR( 8) }, /* Pacific Daylight */
+ { "yst", tZONE, HOUR( 9) }, /* Yukon Standard */
+ { "ydt", tDAYZONE, HOUR( 9) }, /* Yukon Daylight */
+ { "akst", tZONE, HOUR( 9) }, /* Alaska Standard */
+ { "akdt", tDAYZONE, HOUR( 9) }, /* Alaska Daylight */
+ { "hst", tZONE, HOUR(10) }, /* Hawaii Standard */
+ { "hast", tZONE, HOUR(10) }, /* Hawaii-Aleutian Standard */
+ { "hadt", tDAYZONE, HOUR(10) }, /* Hawaii-Aleutian Daylight */
+ { "ces", tDAYZONE, -HOUR(1) }, /* Central European Summer */
+ { "cest", tDAYZONE, -HOUR(1) }, /* Central European Summer */
+ { "mez", tZONE, -HOUR(1) }, /* Middle European */
+ { "mezt", tDAYZONE, -HOUR(1) }, /* Middle European Summer */
+ { "cet", tZONE, -HOUR(1) }, /* Central European */
+ { "met", tZONE, -HOUR(1) }, /* Middle European */
+ { "eet", tZONE, -HOUR(2) }, /* Eastern Europe */
+ { "msk", tZONE, -HOUR(3) }, /* Moscow Winter */
+ { "msd", tDAYZONE, -HOUR(3) }, /* Moscow Summer */
+ { "wast", tZONE, -HOUR(8) }, /* West Australian Standard */
+ { "wadt", tDAYZONE, -HOUR(8) }, /* West Australian Daylight */
+ { "hkt", tZONE, -HOUR(8) }, /* Hong Kong */
+ { "cct", tZONE, -HOUR(8) }, /* China Coast */
+ { "jst", tZONE, -HOUR(9) }, /* Japan Standard */
+ { "kst", tZONE, -HOUR(9) }, /* Korean Standard */
+ { "kdt", tZONE, -HOUR(9) }, /* Korean Daylight */
+ { "cast", tZONE, -(HOUR(9)+30) }, /* Central Australian Standard */
+ { "cadt", tDAYZONE, -(HOUR(9)+30) }, /* Central Australian Daylight */
+ { "east", tZONE, -HOUR(10) }, /* Eastern Australian Standard */
+ { "eadt", tDAYZONE, -HOUR(10) }, /* Eastern Australian Daylight */
+ { "nzst", tZONE, -HOUR(12) }, /* New Zealand Standard */
+ { "nzdt", tDAYZONE, -HOUR(12) }, /* New Zealand Daylight */
+
+ /* For completeness we include the following entries. */
+#if 0
+
+ /* Duplicate names. Either they conflict with a zone listed above
+ * (which is either more likely to be seen or just been in circulation
+ * longer), or they conflict with another zone in this section and
+ * we could not reasonably choose one over the other. */
+ { "fst", tZONE, HOUR( 2) }, /* Fernando De Noronha Standard */
+ { "fdt", tDAYZONE, HOUR( 2) }, /* Fernando De Noronha Daylight */
+ { "bst", tZONE, HOUR( 3) }, /* Brazil Standard */
+ { "est", tZONE, HOUR( 3) }, /* Eastern Standard (Brazil) */
+ { "edt", tDAYZONE, HOUR( 3) }, /* Eastern Daylight (Brazil) */
+ { "wst", tZONE, HOUR( 4) }, /* Western Standard (Brazil) */
+ { "wdt", tDAYZONE, HOUR( 4) }, /* Western Daylight (Brazil) */
+ { "cst", tZONE, HOUR( 5) }, /* Chile Standard */
+ { "cdt", tDAYZONE, HOUR( 5) }, /* Chile Daylight */
+ { "ast", tZONE, HOUR( 5) }, /* Acre Standard */
+ { "adt", tDAYZONE, HOUR( 5) }, /* Acre Daylight */
+ { "cst", tZONE, HOUR( 5) }, /* Cuba Standard */
+ { "cdt", tDAYZONE, HOUR( 5) }, /* Cuba Daylight */
+ { "est", tZONE, HOUR( 6) }, /* Easter Island Standard */
+ { "edt", tDAYZONE, HOUR( 6) }, /* Easter Island Daylight */
+ { "sst", tZONE, HOUR(11) }, /* Samoa Standard */
+ { "ist", tZONE, -HOUR(2) }, /* Israel Standard */
+ { "idt", tDAYZONE, -HOUR(2) }, /* Israel Daylight */
+ { "idt", tDAYZONE, -(HOUR(3)+30) }, /* Iran Daylight */
+ { "ist", tZONE, -(HOUR(3)+30) }, /* Iran Standard */
+ { "cst", tZONE, -HOUR(8) }, /* China Standard */
+ { "cdt", tDAYZONE, -HOUR(8) }, /* China Daylight */
+ { "sst", tZONE, -HOUR(8) }, /* Singapore Standard */
+
+ /* Dubious (e.g., not in Olson's TIMEZONE package) or obsolete. */
+ { "gst", tZONE, HOUR( 3) }, /* Greenland Standard */
+ { "wat", tZONE, -HOUR(1) }, /* West Africa */
+ { "at", tZONE, HOUR( 2) }, /* Azores */
+ { "gst", tZONE, -HOUR(10) }, /* Guam Standard */
+ { "nft", tZONE, HOUR(3)+30 }, /* Newfoundland */
+ { "idlw", tZONE, HOUR(12) }, /* International Date Line West */
+ { "mewt", tZONE, -HOUR(1) }, /* Middle European Winter */
+ { "mest", tDAYZONE, -HOUR(1) }, /* Middle European Summer */
+ { "swt", tZONE, -HOUR(1) }, /* Swedish Winter */
+ { "sst", tDAYZONE, -HOUR(1) }, /* Swedish Summer */
+ { "fwt", tZONE, -HOUR(1) }, /* French Winter */
+ { "fst", tDAYZONE, -HOUR(1) }, /* French Summer */
+ { "bt", tZONE, -HOUR(3) }, /* Baghdad */
+ { "it", tZONE, -(HOUR(3)+30) }, /* Iran */
+ { "zp4", tZONE, -HOUR(4) }, /* USSR Zone 3 */
+ { "zp5", tZONE, -HOUR(5) }, /* USSR Zone 4 */
+ { "ist", tZONE, -(HOUR(5)+30) }, /* Indian Standard */
+ { "zp6", tZONE, -HOUR(6) }, /* USSR Zone 5 */
+ { "nst", tZONE, -HOUR(7) }, /* North Sumatra */
+ { "sst", tZONE, -HOUR(7) }, /* South Sumatra */
+ { "jt", tZONE, -(HOUR(7)+30) }, /* Java (3pm in Cronusland!) */
+ { "nzt", tZONE, -HOUR(12) }, /* New Zealand */
+ { "idle", tZONE, -HOUR(12) }, /* International Date Line East */
+ { "cat", tZONE, HOUR(10) }, /* -- expired 1967 */
+ { "nt", tZONE, HOUR(11) }, /* -- expired 1967 */
+ { "ahst", tZONE, HOUR(10) }, /* -- expired 1983 */
+ { "hdt", tDAYZONE, HOUR(10) }, /* -- expired 1986 */
+#endif /* 0 */
+};
+
+\f
+
+static void
+date_error(const char *s)
+{
+ s = s; /* ARGSUSED */
+ /* NOTREACHED */
+}
+
+
+static time_t
+ToSeconds(time_t Hours, time_t Minutes, time_t Seconds, MERIDIAN Meridian)
+{
+ if (Minutes < 0 || Minutes > 59 || Seconds < 0 || Seconds > 61)
+ return -1;
+ if (Meridian == MER24) {
+ if (Hours < 0 || Hours > 23)
+ return -1;
+ }
+ else {
+ if (Hours < 1 || Hours > 12)
+ return -1;
+ if (Hours == 12)
+ Hours = 0;
+ if (Meridian == MERpm)
+ Hours += 12;
+ }
+ return (Hours * 60L + Minutes) * 60L + Seconds;
+}
+
+
+static time_t
+Convert(time_t Month, time_t Day, time_t Year, time_t Hours, time_t Minutes,
+ time_t Seconds, MERIDIAN Meridian, DSTMODE dst)
+{
+ static int DaysNormal[13] = {
+ 0, 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31
+ };
+ static int DaysLeap[13] = {
+ 0, 31, 29, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31
+ };
+ static int LeapYears[] = {
+ 1972, 1976, 1980, 1984, 1988, 1992, 1996,
+ 2000, 2004, 2008, 2012, 2016, 2020, 2024, 2028, 2032, 2036
+ };
+ int *yp;
+ int *mp;
+ time_t Julian;
+ int i;
+ time_t tod;
+
+ /* Year should not be passed as a relative value, but absolute one.
+ so this should not happen, but just ensure it */
+ if (Year < 0)
+ Year = -Year;
+ if (Year < 100) {
+ Year += 1900;
+ if (Year < EPOCH)
+ Year += 100;
+ }
+ for (mp = DaysNormal, yp = LeapYears; yp < ARRAY_END(LeapYears); yp++)
+ if (Year == *yp) {
+ mp = DaysLeap;
+ break;
+ }
+ if (Year < EPOCH || Year > END_OF_TIME
+ || Month < 1 || Month > 12
+ /* NOSTRICT *//* conversion from long may lose accuracy */
+ || Day < 1 || Day > mp[(int)Month])
+ return -1;
+
+ Julian = Day - 1 + (Year - EPOCH) * 365;
+ for (yp = LeapYears; yp < ARRAY_END(LeapYears); yp++, Julian++)
+ if (Year <= *yp)
+ break;
+ for (i = 1; i < Month; i++)
+ Julian += *++mp;
+ Julian *= SECSPERDAY;
+ Julian += yyTimezone * 60L;
+ if ((tod = ToSeconds(Hours, Minutes, Seconds, Meridian)) < 0)
+ return -1;
+ Julian += tod;
+ tod = Julian;
+ if (dst == DSTon || (dst == DSTmaybe && localtime(&tod)->tm_isdst))
+ Julian -= DST_OFFSET * 60 * 60;
+ return Julian;
+}
+
+
+static time_t
+DSTcorrect(time_t Start, time_t Future)
+{
+ time_t StartDay;
+ time_t FutureDay;
+
+ StartDay = (localtime(&Start)->tm_hour + 1) % 24;
+ FutureDay = (localtime(&Future)->tm_hour + 1) % 24;
+ return (Future - Start) + (StartDay - FutureDay) * DST_OFFSET * 60 * 60;
+}
+
+
+static time_t
+RelativeMonth(time_t Start, time_t RelMonth)
+{
+ struct tm *tm;
+ time_t Month;
+ time_t Year;
+
+ tm = localtime(&Start);
+ Month = 12 * tm->tm_year + tm->tm_mon + RelMonth;
+ Year = Month / 12;
+ Year += 1900;
+ Month = Month % 12 + 1;
+ return DSTcorrect(Start,
+ Convert(Month, (time_t)tm->tm_mday, Year,
+ (time_t)tm->tm_hour, (time_t)tm->tm_min, (time_t)tm->tm_sec,
+ MER24, DSTmaybe));
+}
+
+
+static int
+LookupWord(char *buff, int length)
+{
+ char *p;
+ const char *q;
+ TABLE *tp;
+ int c;
+
+ p = buff;
+ c = p[0];
+
+ /* See if we have an abbreviation for a month. */
+ if (length == 3 || (length == 4 && p[3] == '.'))
+ for (tp = MonthDayTable; tp < ARRAY_END(MonthDayTable); tp++) {
+ q = tp->name;
+ if (c == q[0] && p[1] == q[1] && p[2] == q[2]) {
+ yylval.Number = tp->value;
+ return tp->type;
+ }
+ }
+ else
+ for (tp = MonthDayTable; tp < ARRAY_END(MonthDayTable); tp++)
+ if (c == tp->name[0] && strcmp(p, tp->name) == 0) {
+ yylval.Number = tp->value;
+ return tp->type;
+ }
+
+ /* Try for a timezone. */
+ for (tp = TimezoneTable; tp < ARRAY_END(TimezoneTable); tp++)
+ if (c == tp->name[0] && p[1] == tp->name[1]
+ && strcmp(p, tp->name) == 0) {
+ yylval.Number = tp->value;
+ return tp->type;
+ }
+
+ /* Try the units table. */
+ for (tp = UnitsTable; tp < ARRAY_END(UnitsTable); tp++)
+ if (c == tp->name[0] && strcmp(p, tp->name) == 0) {
+ yylval.Number = tp->value;
+ return tp->type;
+ }
+
+ /* Strip off any plural and try the units table again. */
+ if (--length > 0 && p[length] == 's') {
+ p[length] = '\0';
+ for (tp = UnitsTable; tp < ARRAY_END(UnitsTable); tp++)
+ if (c == tp->name[0] && strcmp(p, tp->name) == 0) {
+ p[length] = 's';
+ yylval.Number = tp->value;
+ return tp->type;
+ }
+ p[length] = 's';
+ }
+ length++;
+
+ /* Drop out any periods. */
+ for (p = buff, q = buff; *q; q++)
+ if (*q != '.')
+ *p++ = *q;
+ *p = '\0';
+
+ /* Try the meridians. */
+ if (buff[1] == 'm' && buff[2] == '\0') {
+ if (buff[0] == 'a') {
+ yylval.Meridian = MERam;
+ return tMERIDIAN;
+ }
+ if (buff[0] == 'p') {
+ yylval.Meridian = MERpm;
+ return tMERIDIAN;
+ }
+ }
+
+ /* If we saw any periods, try the timezones again. */
+ if (p - buff != length) {
+ c = buff[0];
+ for (p = buff, tp = TimezoneTable; tp < ARRAY_END(TimezoneTable); tp++)
+ if (c == tp->name[0] && p[1] == tp->name[1]
+ && strcmp(p, tp->name) == 0) {
+ yylval.Number = tp->value;
+ return tp->type;
+ }
+ }
+
+ /* Unknown word -- assume GMT timezone. */
+ yylval.Number = 0;
+ return tZONE;
+}
+
+
+static int
+date_lex(void)
+{
+ char c;
+ char *p;
+ char buff[20];
+ int sign;
+ int i;
+ int nesting;
+
+ for ( ; ; ) {
+ /* Get first character after the whitespace. */
+ for ( ; ; ) {
+ while (CTYPE(isspace, (int)*yyInput))
+ yyInput++;
+ c = *yyInput;
+
+ /* Ignore RFC 822 comments, typically time zone names. */
+ if (c != LPAREN)
+ break;
+ for (nesting = 1; (c = *++yyInput) != RPAREN || --nesting; )
+ if (c == LPAREN)
+ nesting++;
+ else if (!IS7BIT(c) || c == '\0' || c == '\r'
+ || (c == '\\' && ((c = *++yyInput) == '\0' || !IS7BIT(c))))
+ /* Lexical error: bad comment. */
+ return '?';
+ yyInput++;
+ }
+
+ /* A number? */
+ if (CTYPE(isdigit, (int)c) || c == '-' || c == '+') {
+ if (c == '-' || c == '+') {
+ sign = c == '-' ? -1 : 1;
+ yyInput++;
+ if (!CTYPE(isdigit, (int)*yyInput))
+ /* Skip the plus or minus sign. */
+ continue;
+ }
+ else
+ sign = 0;
+ for (i = 0; (c = *yyInput++) != '\0' && CTYPE(isdigit, (int)c); )
+ i = 10 * i + c - '0';
+ yyInput--;
+ yylval.Number = sign < 0 ? -i : i;
+ return sign ? tSNUMBER : tUNUMBER;
+ }
+
+ /* A word? */
+ if (CTYPE(isalpha, (int)c)) {
+ for (p = buff; (c = *yyInput++) == '.' || CTYPE(isalpha, (int)c); )
+ if (p < &buff[sizeof buff - 1])
+ *p++ = CTYPE(isupper, (int)c) ? tolower(c) : c;
+ *p = '\0';
+ yyInput--;
+ return LookupWord(buff, p - buff);
+ }
+
+ return *yyInput++;
+ }
+}
+
+
+time_t
+parsedate(char *p, TIMEINFO *now)
+{
+ struct tm *tm;
+ TIMEINFO ti;
+ time_t Start;
+
+ yyInput = p;
+ if (now == NULL) {
+ now = &ti;
+ GetTimeInfo(&ti);
+ }
+
+ tm = localtime(&now->time);
+ yyYear = tm->tm_year + 1900;
+ yyMonth = tm->tm_mon + 1;
+ yyDay = tm->tm_mday;
+ yyTimezone = now->tzone;
+ yyDSTmode = DSTmaybe;
+ yyHour = 0;
+ yyMinutes = 0;
+ yySeconds = 0;
+ yyMeridian = MER24;
+ yyRelSeconds = 0;
+ yyRelMonth = 0;
+ yyHaveDate = 0;
+ yyHaveRel = 0;
+ yyHaveTime = 0;
+
+ if (date_parse() || yyHaveTime > 1 || yyHaveDate > 1)
+ return -1;
+
+ if (yyHaveDate || yyHaveTime) {
+ Start = Convert(yyMonth, yyDay, yyYear, yyHour, yyMinutes, yySeconds,
+ yyMeridian, yyDSTmode);
+ if (Start < 0)
+ return -1;
+ }
+ else {
+ Start = now->time;
+ if (!yyHaveRel)
+ Start -= (tm->tm_hour * 60L + tm->tm_min) * 60L + tm->tm_sec;
+ }
+
+ Start += yyRelSeconds;
+ if (yyRelMonth)
+ Start += RelativeMonth(Start, yyRelMonth);
+
+ /* Have to do *something* with a legitimate -1 so it's distinguishable
+ * from the error return value. (Alternately could set errno on error.) */
+ return Start == -1 ? 0 : Start;
+}
+
+
+#if defined(TEST)
+
+#if YYDEBUG
+extern int yydebug;
+#endif /* YYDEBUG */
+
+/* ARGSUSED */
+int
+main(int ac, char *av[])
+{
+ char buff[128];
+ time_t d;
+
+#if YYDEBUG
+ yydebug = 1;
+#endif /* YYDEBUG */
+
+ printf("Enter date, or blank line to exit.\n\t> ");
+ for ( ; ; ) {
+ printf("\t> ");
+ fflush(stdout);
+ if (gets(buff) == NULL || buff[0] == '\n')
+ break;
+#if YYDEBUG
+ if (strcmp(buff, "yydebug") == 0) {
+ yydebug = !yydebug;
+ printf("yydebug = %s\n", yydebug ? "on" : "off");
+ continue;
+ }
+#endif /* YYDEBUG */
+ d = parsedate(buff, (TIMEINFO *)NULL);
+ if (d == -1)
+ printf("Bad format - couldn't convert.\n");
+ else
+ printf("%s", ctime(&d));
+ }
+
+ exit(0);
+ /* NOTREACHED */
+}
+#endif /* defined(TEST) */
--- /dev/null
+/* $Id: perl.c 7929 2008-06-29 17:55:04Z iulius $
+**
+** Embedded Perl support for INN.
+**
+** Originally written by Christophe Wolfhugel <wolf@pasteur.fr> (although
+** he wouldn't recongize it any more, so don't blame him) and modified,
+** expanded, and tweaked by James Brister, Dave Hayes, and Russ Allbery
+** among others.
+**
+** This file contains the Perl linkage shared by both nnrpd and innd. It
+** assumes Perl 5.004 or later.
+*/
+
+#include "config.h"
+
+/* Skip this entire file if DO_PERL (./configure --with-perl) isn't set. */
+#if DO_PERL
+
+#include "clibrary.h"
+#include <fcntl.h>
+#include <syslog.h>
+
+#include "libinn.h"
+
+#include <EXTERN.h>
+#include <perl.h>
+#include <XSUB.h>
+#include "ppport.h"
+
+#include "innperl.h"
+
+/* Provided by DynaLoader but not declared in Perl's header files. */
+extern void boot_DynaLoader(CV *cv);
+
+/* Forward declarations. */
+void PerlSilence(void);
+void PerlUnSilence(void);
+void xs_init(void);
+
+/* Whether Perl filtering is currently active. */
+bool PerlFilterActive = false;
+
+/* The filter sub called (filter_art or filter_post). */
+CV *perl_filter_cv;
+
+/* The embedded Perl interpretor. */
+static PerlInterpreter *PerlCode;
+
+
+static void LogPerl(void)
+{
+ syslog(L_NOTICE, "SERVER perl filtering %s", PerlFilterActive ? "enabled" : "disabled");
+}
+
+
+/*
+** Enable or disable the Perl filter. Takes the desired state of the filter
+** as an argument and returns success or failure. Failure to enable
+** indicates that the filter is not defined.
+*/
+bool
+PerlFilter(bool value)
+{
+ dSP;
+ char *argv[] = { NULL };
+
+ if (value == PerlFilterActive)
+ return true;
+
+ if (!value) {
+ /* Execute an end function, if one is defined. */
+ if (perl_get_cv("filter_end", false) != NULL) {
+ ENTER;
+ SAVETMPS;
+ perl_call_argv("filter_end", G_EVAL | G_DISCARD | G_NOARGS, argv);
+ if (SvTRUE(ERRSV)) {
+ syslog (L_ERROR, "SERVER perl function filter_end died: %s",
+ SvPV(ERRSV, PL_na));
+ (void) POPs;
+ }
+ FREETMPS;
+ LEAVE;
+ }
+ PerlFilterActive = value;
+ LogPerl();
+ return true;
+ } else {
+ if (perl_filter_cv == NULL) {
+ syslog (L_ERROR, "SERVER perl filter not defined");
+ return false;
+ } else {
+ PerlFilterActive = value;
+ LogPerl();
+ return true;
+ }
+ }
+}
+
+
+
+/*
+** Loads a setup Perl module. startupfile is the name of the file loaded
+** one-time at startup. filterfile is the file containing the filter
+** functions which is loaded at startup and at each reload. function is a
+** function name that must be defined after the filterfile file is loaded for
+** filtering to be turned on to start with.
+*/
+void PERLsetup (char *startupfile, char *filterfile, const char *function)
+{
+ if (PerlCode == NULL) {
+ /* Perl waits on standard input if not called with '-e'. */
+ int argc = 3;
+ const char *argv[] = { "innd", "-e", "0", NULL };
+ char *env[] = { NULL };
+#ifdef PERL_SYS_INIT3
+ PERL_SYS_INIT3(&argc, &argv, &env);
+#endif
+ PerlCode = perl_alloc();
+ perl_construct(PerlCode);
+ perl_parse(PerlCode, xs_init, argc, (char **)argv, env) ;
+ }
+
+ if (startupfile != NULL && filterfile != NULL) {
+ char *evalfile = NULL;
+ size_t length;
+ dSP;
+
+ ENTER ;
+ SAVETMPS ;
+
+ /* The Perl expression which will be evaluated. */
+ length = strlen("do '%s'") + strlen(startupfile);
+ evalfile = xmalloc(length);
+ snprintf(evalfile, length, "do '%s'", startupfile);
+
+ PerlSilence();
+ perl_eval_pv(evalfile, TRUE);
+ PerlUnSilence();
+
+ SPAGAIN ;
+
+ if (SvTRUE(ERRSV)) /* check $@ */ {
+ syslog(L_ERROR,"SERVER perl loading %s failed: %s",
+ startupfile, SvPV(ERRSV, PL_na)) ;
+ PerlFilter (false) ;
+
+ } else {
+ PERLreadfilter (filterfile,function) ;
+ }
+
+ FREETMPS ;
+ LEAVE ;
+ } else {
+ PERLreadfilter (filterfile,function) ;
+ }
+}
+
+
+/* Load the perl file FILTERFILE. After it is load check that the give
+ function is defined. If yes filtering is turned on. If not it is turned
+ off. We remember whether the filter function was defined properly so
+ that we can catch when the use tries to turn filtering on without the
+ the funciton there. */
+int PERLreadfilter(char *filterfile, const char *function)
+{
+ dSP ;
+ char *argv[] = { NULL };
+ char *evalfile = NULL;
+ size_t length;
+
+ ENTER ;
+ SAVETMPS ;
+
+ if (perl_get_cv("filter_before_reload", false) != NULL) {
+ perl_call_argv("filter_before_reload", G_EVAL|G_DISCARD|G_NOARGS, argv);
+ if (SvTRUE(ERRSV)) /* check $@ */ {
+ syslog (L_ERROR,"SERVER perl function filter_before_reload died: %s",
+ SvPV(ERRSV, PL_na)) ;
+ (void)POPs ;
+ PerlFilter (false) ;
+ }
+ }
+
+ /* The Perl expression which will be evaluated. */
+ length = strlen("do '%s'") + strlen(filterfile);
+ evalfile = xmalloc(length);
+ snprintf(evalfile, length, "do '%s'", filterfile);
+
+ PerlSilence();
+ perl_eval_pv(evalfile, TRUE);
+ PerlUnSilence();
+
+ free(evalfile);
+ evalfile = NULL;
+
+ if (SvTRUE(ERRSV)) /* check $@ */ {
+ syslog (L_ERROR,"SERVER perl loading %s failed: %s",
+ filterfile, SvPV(ERRSV, PL_na)) ;
+ PerlFilter (false) ;
+
+ /* If the reload failed we don't want the old definition hanging
+ around. */
+ length = strlen("undef &%s") + strlen(function);
+ evalfile = xmalloc(length);
+ snprintf(evalfile, length, "undef &%s", function);
+ perl_eval_pv(evalfile, TRUE);
+
+ if (SvTRUE(ERRSV)) /* check $@ */ {
+ syslog (L_ERROR,"SERVER perl undef &%s failed: %s",
+ function, SvPV(ERRSV, PL_na)) ;
+ }
+ } else if ((perl_filter_cv = perl_get_cv(function, false)) == NULL) {
+ PerlFilter (false) ;
+ }
+
+ if (perl_get_cv("filter_after_reload", false) != NULL) {
+ perl_call_argv("filter_after_reload", G_EVAL|G_DISCARD|G_NOARGS, argv);
+ if (SvTRUE(ERRSV)) /* check $@ */ {
+ syslog (L_ERROR,"SERVER perl function filter_after_reload died: %s",
+ SvPV(ERRSV, PL_na)) ;
+ (void)POPs ;
+ PerlFilter (false) ;
+ }
+ }
+
+ FREETMPS ;
+ LEAVE ;
+
+ return (perl_filter_cv != NULL) ;
+}
+
+
+/*
+** Stops using the Perl filter
+*/
+void PerlClose(void)
+{
+ perl_destruct(PerlCode);
+ perl_free(PerlCode);
+#ifdef PERL_SYS_TERM
+ PERL_SYS_TERM();
+#endif
+ PerlFilterActive = false;
+}
+
+/*
+** Redirects STDOUT/STDERR briefly (otherwise PERL complains to the net
+** connection for NNRPD and that just won't do) -- dave@jetcafe.org
+*/
+static int savestdout = 0;
+static int savestderr = 0;
+void PerlSilence(void)
+{
+ int newfd;
+
+ /* Save the descriptors */
+ if ( (savestdout = dup(1)) < 0) {
+ syslog(L_ERROR,"SERVER perl silence cant redirect stdout: %m");
+ savestdout = 0;
+ return;
+ }
+ if ( (savestderr = dup(2)) < 0) {
+ syslog(L_ERROR,"SERVER perl silence cant redirect stderr: %m");
+ savestdout = 0;
+ savestderr = 0;
+ return;
+ }
+
+ /* Open /dev/null */
+ if ((newfd = open("/dev/null",O_WRONLY)) < 0) {
+ syslog(L_ERROR,"SERVER perl silence cant open /dev/null: %m");
+ savestdout = 0;
+ savestderr = 0;
+ return;
+ }
+
+ /* Redirect descriptors */
+ if (dup2(newfd,1) < 0) {
+ syslog(L_ERROR,"SERVER perl silence cant redirect stdout: %m");
+ savestdout = 0;
+ return;
+ }
+
+ if (dup2(newfd,2) < 0) {
+ syslog(L_ERROR,"SERVER perl silence cant redirect stderr: %m");
+ savestderr = 0;
+ return;
+ }
+ close(newfd);
+}
+
+void PerlUnSilence(void) {
+ if (savestdout != 0) {
+ if (dup2(savestdout,1) < 0) {
+ syslog(L_ERROR,"SERVER perl silence cant restore stdout: %m");
+ }
+ close(savestdout);
+ savestdout = 0;
+ }
+
+ if (savestderr != 0) {
+ if (dup2(savestderr,2) < 0) {
+ syslog(L_ERROR,"SERVER perl silence cant restore stderr: %m");
+ }
+ close(savestderr);
+ savestderr = 0;
+ }
+}
+
+/*
+** The remainder of this file consists of XS callbacks usable by either
+** innd or nnrpd and initialized automatically when the Perl filter is
+** initialized, as well as the function that initializes them.
+*/
+
+/*
+** Log a message via syslog. Only the first letter of the priority
+** matters, and this function assumes that the controlling program has
+** already done an openlog(). The argument must be a complete message, not
+** a printf-style format.
+*/
+XS(XS_INN_syslog)
+{
+ dXSARGS;
+ const char *loglevel;
+ const char *logmsg;
+ int priority;
+
+ if (items != 2)
+ croak("Usage: INN::syslog(level, message)");
+
+ loglevel = (const char *) SvPV(ST(0), PL_na);
+ logmsg = (const char *) SvPV(ST(1), PL_na);
+
+ switch (*loglevel) {
+ default: priority = LOG_NOTICE;
+ case 'a': case 'A': priority = LOG_ALERT; break;
+ case 'c': case 'C': priority = LOG_CRIT; break;
+ case 'e': case 'E': priority = LOG_ERR; break;
+ case 'w': case 'W': priority = LOG_WARNING; break;
+ case 'n': case 'N': priority = LOG_NOTICE; break;
+ case 'i': case 'I': priority = LOG_INFO; break;
+ case 'd': case 'D': priority = LOG_DEBUG; break;
+ }
+ syslog(priority, "filter: %s", logmsg);
+ XSRETURN_UNDEF;
+}
+
+extern void
+xs_init()
+{
+ dXSUB_SYS;
+ newXS("DynaLoader::boot_DynaLoader", boot_DynaLoader, "perl.c");
+ newXS("INN::syslog", XS_INN_syslog, "perl.c");
+}
+
+#endif /* defined(DO_PERL) */
--- /dev/null
+/* $Id: pread.c 5049 2001-12-12 09:06:00Z rra $
+**
+** Replacement for a missing pread.
+**
+** Written by Russ Allbery <rra@stanford.edu>
+** This work is hereby placed in the public domain by its author.
+**
+** Provides the same functionality as the standard library routine pread
+** for those platforms that don't have it. Note that pread requires that
+** the file pointer not move and without the library function, we can't
+** copy that behavior; instead, we approximate it by moving the file
+** pointer and then moving it back. This may break threaded programs.
+*/
+
+#include "config.h"
+#include "clibrary.h"
+#include <errno.h>
+
+/* If we're running the test suite, rename pread to avoid conflicts with the
+ system version. #undef first because large file support may define a
+ macro pread (pointing to pread64) on some platforms (e.g. Solaris). */
+#if TESTING
+# undef pread
+# define pread test_pread
+ssize_t test_pread(int, void *, size_t, off_t);
+#endif
+
+ssize_t
+pread(int fd, void *buf, size_t nbyte, off_t offset)
+{
+ off_t current;
+ ssize_t nread;
+ int oerrno;
+
+ current = lseek(fd, 0, SEEK_CUR);
+ if (current == (off_t) -1 || lseek(fd, offset, SEEK_SET) == (off_t) -1)
+ return -1;
+
+ nread = read(fd, buf, nbyte);
+
+ /* Ignore errors in restoring the file position; this isn't ideal, but
+ reporting a failed read when the read succeeded is worse. Make sure
+ that errno, if set, is set by read and not lseek. */
+ oerrno = errno;
+ lseek(fd, current, SEEK_SET);
+ errno = oerrno;
+ return nread;
+}
--- /dev/null
+/* $Id: pwrite.c 5049 2001-12-12 09:06:00Z rra $
+**
+** Replacement for a missing pwrite.
+**
+** Written by Russ Allbery <rra@stanford.edu>
+** This work is hereby placed in the public domain by its author.
+**
+** Provides the same functionality as the standard library routine pwrite
+** for those platforms that don't have it. Note that pwrite requires that
+** the file pointer not move and without the library function, we can't
+** copy that behavior; instead, we approximate it by moving the file
+** pointer and then moving it back. This may break threaded programs.
+*/
+
+#include "config.h"
+#include "clibrary.h"
+#include <errno.h>
+
+/* If we're running the test suite, rename pread to avoid conflicts with the
+ system version. #undef first because large file support may define a
+ macro pwrite (pointing to pwrite64) on some platforms (e.g. Solaris). */
+#if TESTING
+# undef pwrite
+# define pwrite test_pwrite
+ssize_t test_pwrite(int, const void *, size_t, off_t);
+#endif
+
+ssize_t
+pwrite(int fd, const void *buf, size_t nbyte, off_t offset)
+{
+ off_t current;
+ ssize_t nwritten;
+ int oerrno;
+
+ current = lseek(fd, 0, SEEK_CUR);
+ if (current == (off_t) -1 || lseek(fd, offset, SEEK_SET) == (off_t) -1)
+ return -1;
+
+ nwritten = write(fd, buf, nbyte);
+
+ /* Ignore errors in restoring the file position; this isn't ideal, but
+ reporting a failed write when the write succeeded is worse. Make
+ sure that errno, if set, is set by write and not lseek. */
+ oerrno = errno;
+ lseek(fd, current, SEEK_SET);
+ errno = oerrno;
+ return nwritten;
+}
--- /dev/null
+/* $Id: qio.c 6943 2004-06-10 22:20:24Z hkehoe $
+**
+** Quick I/O package.
+**
+** A set of routines optimized for reading through files line by line.
+** This package uses internal buffering like stdio, but is even more
+** aggressive about its buffering. The basic read call reads a single line
+** and returns the whole line, provided that it can fit in the buffer.
+*/
+
+#include "config.h"
+#include "clibrary.h"
+#include <errno.h>
+#include <fcntl.h>
+#include <sys/stat.h>
+
+#include "inn/qio.h"
+#include "libinn.h"
+
+/* A reasonable default buffer size to use. */
+#define QIO_BUFFERSIZE 8192
+
+/*
+** Given a file descriptor, return a reasonable buffer size to use for that
+** file. Uses st_blksize if available and reasonable, QIO_BUFFERSIZE
+** otherwise.
+*/
+static size_t
+buffer_size(int fd)
+{
+ size_t size = QIO_BUFFERSIZE;
+
+#if HAVE_ST_BLKSIZE
+ struct stat st;
+
+ /* The Solaris 2.6 man page says that st_blksize is not defined for
+ block or character special devices (and could contain garbage), so
+ only use this value for regular files. */
+ if (fstat(fd, &st) == 0 && S_ISREG(st.st_mode)) {
+ size = st.st_blksize;
+ if (size > (4 * QIO_BUFFERSIZE) || size == 0)
+ size = QIO_BUFFERSIZE;
+ else
+ while(size < QIO_BUFFERSIZE)
+ size += st.st_blksize;
+ }
+#endif /* HAVE_ST_BLKSIZE */
+
+ return size;
+}
+
+
+/*
+** Open a quick file from a descriptor.
+*/
+QIOSTATE *
+QIOfdopen(const int fd)
+{
+ QIOSTATE *qp;
+
+ qp = xmalloc(sizeof(*qp));
+ qp->_fd = fd;
+ qp->_length = 0;
+ qp->_size = buffer_size(fd);
+ qp->_buffer = xmalloc(qp->_size);
+ qp->_start = qp->_buffer;
+ qp->_end = qp->_buffer;
+ qp->_count = 0;
+ qp->_flag = QIO_ok;
+
+ return qp;
+}
+
+
+/*
+** Open a quick file from a file name.
+*/
+QIOSTATE *
+QIOopen(const char *name)
+{
+ int fd;
+
+ fd = open(name, O_RDONLY);
+ if (fd < 0)
+ return NULL;
+ return QIOfdopen(fd);
+}
+
+
+/*
+** Close an open quick file.
+*/
+void
+QIOclose(QIOSTATE *qp)
+{
+ close(qp->_fd);
+ free(qp->_buffer);
+ free(qp);
+}
+
+
+/*
+** Rewind a quick file. Reads the first buffer full of data automatically,
+** anticipating the first read from the file. Returns -1 on error, 0 on
+** success.
+*/
+int
+QIOrewind(QIOSTATE *qp)
+{
+ ssize_t nread;
+
+ if (lseek(qp->_fd, 0, SEEK_SET) < 0)
+ return -1;
+ nread = read(qp->_fd, qp->_buffer, qp->_size);
+ if (nread < 0)
+ return nread;
+ qp->_count = nread;
+ qp->_start = qp->_buffer;
+ qp->_end = qp->_buffer + nread;
+ return 0;
+}
+
+
+/*
+** Get the next newline-terminated line from a quick file, replacing the
+** newline with a nul. Returns a pointer to that line on success and NULL
+** on failure or end of file, with _flag set appropriately.
+*/
+char *
+QIOread(QIOSTATE *qp)
+{
+ char *p, *line;
+ ssize_t nread;
+ size_t nleft;
+
+ /* Loop until we get a result or fill the buffer. */
+ qp->_flag = QIO_ok;
+ while (1) {
+ nleft = qp->_end - qp->_start;
+
+ /* If nleft <= 0, the buffer currently contains no data that hasn't
+ previously been returned by QIOread, so we can overwrite the
+ buffer with new data. Otherwise, first check the existing data
+ to see if we have a full line. */
+ if (nleft <= 0) {
+ qp->_start = qp->_buffer;
+ qp->_end = qp->_buffer;
+ } else {
+ p = memchr(qp->_start, '\n', nleft);
+ if (p != NULL) {
+ *p = '\0';
+ qp->_length = p - qp->_start;
+ line = qp->_start;
+ qp->_start = p + 1;
+ return (qp->_flag == QIO_long) ? NULL : line;
+ }
+
+ /* Not there. See if our buffer is full. If so, tag as having
+ seen too long of a line. This will cause us to keep reading
+ as normal until we finally see the end of a line and then
+ return NULL. */
+ if (nleft >= qp->_size) {
+ qp->_flag = QIO_long;
+ qp->_start = qp->_end;
+ nleft = 0;
+ }
+
+ /* We need to read more data. If there's read data in buffer,
+ then move the unread data down to the beginning of the buffer
+ first. */
+ if (qp->_start > qp->_buffer) {
+ if (nleft > 0)
+ memmove(qp->_buffer, qp->_start, nleft);
+ qp->_start = qp->_buffer;
+ qp->_end = qp->_buffer + nleft;
+ }
+ }
+
+ /* Read in some more data, and then let the loop try to find the
+ newline again or discover that the line is too long. */
+ do {
+ nread = read(qp->_fd, qp->_end, qp->_size - nleft);
+ } while (nread == -1 && errno == EINTR);
+ if (nread <= 0) {
+ if (nread < 0)
+ qp->_flag = QIO_error;
+ return NULL;
+ }
+ qp->_count += nread;
+ qp->_end += nread;
+ }
+}
--- /dev/null
+/* $Id: radix32.c 6118 2003-01-13 06:44:24Z rra $
+**
+** Radix-32 strings divide a number into five-bit nibbles and use the
+** alphabet 0..9a..v to represent 0..32.
+*/
+
+#include "config.h"
+#include "clibrary.h"
+#include <time.h>
+
+#include "libinn.h"
+
+
+static char ALPHABET[] =
+ "0123456789abcdefghijklmnopqrstuv";
+
+
+/*
+** Turn a number into a Radix-32 string. Assume the number fits into
+** 32 bits.
+*/
+void Radix32(unsigned long l, char *buff)
+{
+ char *p;
+ int i;
+ char temp[10];
+
+ /* Simple sanity checks. */
+ if ((l &= 0xFFFFFFFFL) == 0) {
+ *buff++ = ALPHABET[0];
+ *buff = '\0';
+ return;
+ }
+
+ /* Format the string, in reverse. */
+ for (p = temp; l; l >>= 5)
+ *p++ = ALPHABET[(int)(l & 037)];
+
+ /* Reverse it. */
+ for (i = p - temp; --i >= 0; )
+ *buff++ = *--p;
+ *buff = '\0';
+}
+
+
+#if 0
+/*
+** Return a Radix-32 string as a number, or ~0 on error.
+*/
+unsigned long
+Decode32(p)
+ char *p;
+{
+ unsigned long l;
+ char *cp;
+
+ for (l = 0; *p; p++) {
+ if ((cp = strchr(ALPHABET, *p)) == NULL)
+ return ~0;
+ l = (l << 6) + cp - ALPHABET;
+ }
+ return l;
+}
+#endif /* 0 */
--- /dev/null
+/* $Id: readin.c 6394 2003-07-12 19:13:14Z rra $
+**
+*/
+
+#include "config.h"
+#include "clibrary.h"
+#include <errno.h>
+#include <fcntl.h>
+#include <sys/stat.h>
+
+#include "libinn.h"
+
+
+/*
+** Read a big amount, looping until it is all done. Return true if
+** successful.
+*/
+int xread(int fd, char *p, off_t i)
+{
+ int count;
+
+ for ( ; i; p += count, i -= count) {
+ do {
+ count = read(fd, p, i);
+ } while (count == -1 && errno == EINTR);
+ if (count <= 0)
+ return -1;
+ }
+ return 0;
+}
+
+
+/*
+** Read an already-open file into memory.
+*/
+char *ReadInDescriptor(int fd, struct stat *Sbp)
+{
+ struct stat mystat;
+ char *p;
+ int oerrno;
+
+ if (Sbp == NULL)
+ Sbp = &mystat;
+
+ /* Get the size, and enough memory. */
+ if (fstat(fd, Sbp) < 0) {
+ oerrno = errno;
+ close(fd);
+ errno = oerrno;
+ return NULL;
+ }
+ p = xmalloc(Sbp->st_size + 1);
+
+ /* Slurp, slurp. */
+ if (xread(fd, p, Sbp->st_size) < 0) {
+ oerrno = errno;
+ free(p);
+ close(fd);
+ errno = oerrno;
+ return NULL;
+ }
+
+ /* Terminate the string; terminate the routine. */
+ p[Sbp->st_size] = '\0';
+ return p;
+}
+
+
+/*
+** Read a file into allocated memory. Optionally fill in the stat(2) data.
+** Return a pointer to the file contents, or NULL on error.
+*/
+char *ReadInFile(const char *name, struct stat *Sbp)
+{
+ char *p;
+ int fd;
+
+ if ((fd = open(name, O_RDONLY)) < 0)
+ return NULL;
+
+ p = ReadInDescriptor(fd, Sbp);
+ close(fd);
+ return p;
+}
--- /dev/null
+/* $Revision: 6119 $
+**
+** Open a connection to a remote NNTP server.
+*/
+
+#include "config.h"
+#include "clibrary.h"
+#include "portable/socket.h"
+#include <errno.h>
+#include <netdb.h>
+
+#ifdef HAVE_UNIX_DOMAIN_SOCKETS
+# include <sys/un.h>
+#endif
+
+#include "inn/innconf.h"
+#include "libinn.h"
+#include "nntp.h"
+#include "paths.h"
+
+/*
+** Open a connection to an NNTP server and create stdio FILE's for talking
+** to it. Return -1 on error.
+*/
+int NNTPconnect(char *host, int port, FILE **FromServerp, FILE **ToServerp, char *errbuff)
+{
+ char mybuff[NNTP_STRLEN + 2];
+ char *buff;
+ int i = -1;
+ int j;
+ int oerrno;
+ FILE *F;
+#ifdef HAVE_INET6
+ struct addrinfo hints, *ressave, *addr;
+ char portbuf[16];
+ struct sockaddr_storage client;
+#else
+ char **ap;
+ char *dest;
+ char *fakelist[2];
+ char *p;
+ struct hostent *hp;
+ struct hostent fakehp;
+ struct in_addr quadaddr;
+ struct sockaddr_in server, client;
+#endif
+
+ buff = errbuff ? errbuff : mybuff;
+ *buff = '\0';
+
+#ifdef HAVE_INET6
+ memset(&hints, 0, sizeof(struct addrinfo));
+ hints.ai_family = PF_UNSPEC;
+ hints.ai_socktype = SOCK_STREAM;
+ sprintf(portbuf, "%d", port);
+ if (getaddrinfo(host, portbuf, &hints, &addr) != 0)
+ return -1;
+
+ for (ressave = addr; addr; addr = addr->ai_next) {
+ if ((i = socket(addr->ai_family, addr->ai_socktype,
+ addr->ai_protocol)) < 0)
+ continue; /* ignore */
+ /* bind the local (source) address, if requested */
+ memset(&client, 0, sizeof client);
+ if (addr->ai_family == AF_INET && innconf->sourceaddress) {
+ if (inet_pton(AF_INET, innconf->sourceaddress,
+ &((struct sockaddr_in *)&client)->sin_addr) < 1) {
+ addr = NULL;
+ break;
+ }
+ ((struct sockaddr_in *)&client)->sin_family = AF_INET;
+#ifdef HAVE_SOCKADDR_LEN
+ ((struct sockaddr_in *)&client)->sin_len = sizeof( struct sockaddr_in );
+#endif
+ }
+ if (addr->ai_family == AF_INET6 && innconf->sourceaddress6) {
+ if (inet_pton(AF_INET6, innconf->sourceaddress6,
+ &((struct sockaddr_in6 *)&client)->sin6_addr) < 1) {
+ addr = NULL;
+ break;
+ }
+ ((struct sockaddr_in6 *)&client)->sin6_family = AF_INET6;
+#ifdef HAVE_SOCKADDR_LEN
+ ((struct sockaddr_in6 *)&client)->sin6_len = sizeof( struct sockaddr_in6 );
+#endif
+ }
+ if (client.ss_family != 0) {
+ if (bind(i, (struct sockaddr *)&client, addr->ai_addrlen) < 0) {
+ addr = NULL;
+ break;
+ }
+ }
+ /* we are ready, try to connect */
+ if (connect(i, addr->ai_addr, addr->ai_addrlen) == 0)
+ break; /* success */
+ oerrno = errno;
+ close(i);
+ errno = oerrno;
+ }
+ freeaddrinfo(ressave);
+
+ if (addr == NULL) {
+ /* all connect(2) calls failed or some other error has occurred */
+ oerrno = errno;
+ close(i);
+ errno = oerrno;
+ return -1;
+ }
+ {
+#else /* HAVE_INET6 */
+ if (inet_aton(host, &quadaddr)) {
+ /* Host was specified as a dotted-quad internet address. Fill in
+ * the parts of the hostent struct that we need. */
+ fakehp.h_length = sizeof quadaddr;
+ fakehp.h_addrtype = AF_INET;
+ hp = &fakehp;
+ fakelist[0] = (char *)&quadaddr;
+ fakelist[1] = NULL;
+ ap = fakelist;
+ }
+ else if ((hp = gethostbyname(host)) != NULL)
+ ap = hp->h_addr_list;
+ else
+ /* Not a host name. */
+ return -1;
+
+ /* Set up the socket address. */
+ memset(&server, 0, sizeof server);
+ server.sin_family = hp->h_addrtype;
+#ifdef HAVE_SOCKADDR_LEN
+ server.sin_len = sizeof( struct sockaddr_in );
+#endif
+ server.sin_port = htons(port);
+
+ /* Source IP address to which we bind. */
+ memset(&client, 0, sizeof client);
+ client.sin_family = AF_INET;
+#ifdef HAVE_SOCKADDR_LEN
+ client.sin_len = sizeof( struct sockaddr_in );
+#endif
+ if (innconf->sourceaddress) {
+ if (!inet_aton(innconf->sourceaddress, &client.sin_addr))
+ return -1;
+ } else
+ client.sin_addr.s_addr = htonl(INADDR_ANY);
+
+ /* Loop through the address list, trying to connect. */
+ for (; ap && *ap; ap++) {
+ /* Make a socket and try to connect. */
+ if ((i = socket(hp->h_addrtype, SOCK_STREAM, 0)) < 0)
+ break;
+ /* Bind to the source address we want. */
+ if (bind(i, (struct sockaddr *)&client, sizeof client) < 0) {
+ oerrno = errno;
+ close(i);
+ errno = oerrno;
+ continue;
+ }
+ /* Copy the address via inline memcpy:
+ * memcpy(&server.sin_addr, *ap, hp->h_length); */
+ p = (char *)*ap;
+ for (dest = (char *)&server.sin_addr, j = hp->h_length; --j >= 0; )
+ *dest++ = *p++;
+ if (connect(i, (struct sockaddr *)&server, sizeof server) < 0) {
+ oerrno = errno;
+ close(i);
+ errno = oerrno;
+ continue;
+ }
+#endif /* HAVE_INET6 */
+
+ /* Connected -- now make sure we can post. */
+ if ((F = fdopen(i, "r")) == NULL) {
+ oerrno = errno;
+ close(i);
+ errno = oerrno;
+ return -1;
+ }
+ if (fgets(buff, sizeof mybuff, F) == NULL) {
+ oerrno = errno;
+ fclose(F);
+ errno = oerrno;
+ return -1;
+ }
+ j = atoi(buff);
+ if (j != NNTP_POSTOK_VAL && j != NNTP_NOPOSTOK_VAL) {
+ fclose(F);
+ /* This seems like a reasonable error code to use... */
+ errno = EPERM;
+ return -1;
+ }
+
+ *FromServerp = F;
+ if ((*ToServerp = fdopen(dup(i), "w")) == NULL) {
+ oerrno = errno;
+ fclose(F);
+ errno = oerrno;
+ return -1;
+ }
+ return 0;
+ }
+
+ return -1;
+}
+
+int NNTPremoteopen(int port, FILE **FromServerp, FILE **ToServerp, char *errbuff)
+{
+ char *p;
+
+ if ((p = innconf->server) == NULL) {
+ if (errbuff)
+ strcpy(errbuff, "What server?");
+ return -1;
+ }
+ return NNTPconnect(p, port, FromServerp, ToServerp, errbuff);
+}
--- /dev/null
+/* $Id: reservedfd.c 6135 2003-01-19 01:15:40Z rra $
+**
+*/
+
+#include "config.h"
+#include "clibrary.h"
+#include <fcntl.h>
+
+#include "libinn.h"
+
+
+static FILE **Reserved_fd = NULL;
+static int Maxfd = -1;
+
+bool
+fdreserve(int fdnum)
+{
+ static int allocated = 0;
+ int i, start = allocated;
+
+ if (fdnum <= 0) {
+ if (Reserved_fd != NULL) {
+ for (i = 0 ; i < Maxfd ; i++) {
+ fclose(Reserved_fd[i]);
+ }
+ free(Reserved_fd);
+ Reserved_fd = NULL;
+ }
+ Maxfd = -1;
+ allocated = 0;
+ return true;
+ }
+ if (Reserved_fd == NULL) {
+ Reserved_fd = xmalloc(fdnum * sizeof(FILE *));
+ allocated = fdnum;
+ } else {
+ if (allocated < fdnum) {
+ Reserved_fd = xrealloc(Reserved_fd, fdnum * sizeof(FILE *));
+ allocated = fdnum;
+ } else if (Maxfd > fdnum) {
+ for (i = fdnum ; i < Maxfd ; i++) {
+ fclose(Reserved_fd[i]);
+ }
+ }
+ }
+ for (i = start ; i < fdnum ; i++) {
+ if (((Reserved_fd[i] = fopen("/dev/null", "r")) == NULL)){
+ for (--i ; i >= 0 ; i--)
+ fclose(Reserved_fd[i]);
+ free(Reserved_fd);
+ Reserved_fd = NULL;
+ allocated = 0;
+ Maxfd = -1;
+ return false;
+ }
+ }
+ Maxfd = fdnum;
+ return true;
+}
+
+FILE *
+Fopen(const char *p, const char *type, int xindex)
+{
+ FILE *nfp;
+ if (p == NULL || *p == '\0')
+ return NULL;
+ if (xindex < 0 || xindex > Maxfd || Reserved_fd[xindex] == NULL)
+ return fopen(p, type);
+ if ((nfp = freopen(p, type, Reserved_fd[xindex])) == NULL) {
+ Reserved_fd[xindex] = freopen("/dev/null", "r", Reserved_fd[xindex]);
+ return NULL;
+ }
+ return (Reserved_fd[xindex] = nfp);
+}
+
+int
+Fclose(FILE *fp)
+{
+ int i;
+
+ if (fp == NULL)
+ return 0;
+ for (i = 0 ; i < Maxfd ; i++) {
+ if (Reserved_fd[i] == fp)
+ break;
+ }
+ if (i >= Maxfd)
+ return fclose(fp);
+ Reserved_fd[i] = freopen("/dev/null", "r", Reserved_fd[i]);
+ return 0;
+}
--- /dev/null
+/* $Id: resource.c 6135 2003-01-19 01:15:40Z rra $
+**
+*/
+
+#include "config.h"
+#include "clibrary.h"
+#include "libinn.h"
+
+#ifdef HAVE_GETRUSAGE
+
+#include <sys/time.h>
+#include <sys/resource.h>
+
+#define TIMEVALasDOUBLE(t) \
+ ((double)(t).tv_sec + ((double)(t).tv_usec) / 1000000.0)
+
+int getrusage(int who, struct rusage *rusage);
+
+int GetResourceUsage(double *usertime, double *systime)
+{
+ struct rusage R;
+
+ if (getrusage(RUSAGE_SELF, &R) < 0)
+ return -1;
+ *usertime = TIMEVALasDOUBLE(R.ru_utime);
+ *systime = TIMEVALasDOUBLE(R.ru_stime);
+ return 0;
+}
+
+#else /* HAVE_GETRUSAGE */
+
+#include <sys/param.h>
+#include <sys/times.h>
+
+#if !defined(HZ)
+#define HZ 60
+#endif /* !defined(HZ) */
+
+#define CPUTIMEasDOUBLE(t1, t2) ((double)(t1 + t2) / (double)HZ)
+
+int GetResourceUsage(double *usertime, double *systime)
+{
+ struct tms T;
+
+ if (times(&T) == -1)
+ return -1;
+ *usertime = CPUTIMEasDOUBLE(T.tms_utime, T.tms_cutime);
+ *systime = CPUTIMEasDOUBLE(T.tms_stime, T.tms_cstime);
+ return 0;
+}
+
+#endif /* !HAVE_GETRUSAGE */
--- /dev/null
+/* $Id: sendarticle.c 4076 2000-10-05 00:36:52Z rra $
+**
+*/
+
+#include "config.h"
+#include "clibrary.h"
+#include "libinn.h"
+#include "nntp.h"
+
+
+/*
+** Send a string of one or more lines down a stdio FILE using RFC977
+** conventions. Return -1 on error.
+*/
+int NNTPsendarticle(char *p, FILE *F, bool Terminate)
+{
+ char *next;
+
+ for (; p && *p; next[-1] = '\n', p = next) {
+ /* Get pointer to next line. Truncate long lines. */
+ if ((next = strchr(p, '\n')) != NULL)
+ *next++ = '\0';
+
+ /* Write line. */
+ if (*p == '.' && putc('.', F) == EOF)
+ return -1;
+ if (fprintf(F, "%s\r\n", p) == EOF)
+ return -1;
+
+ /* Done? */
+ if (next == NULL)
+ break;
+ }
+
+ if (Terminate && fprintf(F, ".\r\n") == EOF)
+ return -1;
+
+ return fflush(F) == EOF || ferror(F) ? -1 : 0;
+}
--- /dev/null
+/* $Id: sendpass.c 7145 2005-04-10 03:28:01Z rra $
+**
+*/
+
+#include "config.h"
+#include "clibrary.h"
+#include <ctype.h>
+#include <errno.h>
+
+#include "inn/innconf.h"
+#include "libinn.h"
+#include "nntp.h"
+#include "paths.h"
+
+
+/*
+** Send authentication information to an NNTP server.
+*/
+int NNTPsendpassword(char *server, FILE *FromServer, FILE *ToServer)
+{
+ FILE *F;
+ char *p;
+ char *path;
+ char buff[SMBUF];
+ char input[SMBUF];
+ char *user;
+ char *pass;
+ char *style;
+ int oerrno;
+
+ /* Default to innconf->server. If that's not set either, error out. Fake
+ errno since some of our callers rely on it. */
+ if (server == NULL)
+ server = innconf->server;
+ if (server == NULL) {
+ errno = EINVAL;
+ return -1;
+ }
+
+ /* Open the password file; coarse check on errno, but good enough. */
+ path = concatpath(innconf->pathetc, _PATH_NNTPPASS);
+ F = fopen(path, "r");
+ free(path);
+ if (F == NULL)
+ return errno == EPERM ? -1 : 0;
+
+ /* Scan the file, skipping blank and comment lines. */
+ while (fgets(buff, sizeof buff, F) != NULL) {
+ if ((p = strchr(buff, '\n')) != NULL)
+ *p = '\0';
+ if (buff[0] == '\0' || buff[0] == '#')
+ continue;
+
+ /* Parse the line. */
+ if ((user = strchr(buff, ':')) == NULL)
+ continue;
+ *user++ = '\0';
+ if ((pass = strchr(user, ':')) == NULL)
+ continue;
+ *pass++ = '\0';
+ if ((style = strchr(pass, ':')) != NULL) {
+ *style++ = '\0';
+ if (strcmp(style, "authinfo") != 0) {
+ errno = EDOM;
+ break;
+ }
+ }
+
+ if (strcasecmp(server, buff) != 0)
+ continue;
+
+ if (*user) {
+ /* Send the first part of the command, get a reply. */
+ fprintf(ToServer, "authinfo user %s\r\n", user);
+ if (fflush(ToServer) == EOF || ferror(ToServer))
+ break;
+ if (fgets(input, sizeof input, FromServer) == NULL
+ || atoi(input) != NNTP_AUTH_NEXT_VAL)
+ break;
+ }
+
+ if (*pass) {
+ /* Send the second part of the command, get a reply. */
+ fprintf(ToServer, "authinfo pass %s\r\n", pass);
+ if (fflush(ToServer) == EOF || ferror(ToServer))
+ break;
+ if (fgets(input, sizeof input, FromServer) == NULL
+ || atoi(input) != NNTP_AUTH_OK_VAL)
+ break;
+ }
+
+ /* Authenticated. */
+ fclose(F);
+ return 0;
+ }
+
+ /* End of file without finding a password, that's okay. */
+ if (feof(F)) {
+ fclose(F);
+ return 0;
+ }
+
+ /* Save errno, close the file, fail. */
+ oerrno = errno;
+ fclose(F);
+ errno = oerrno;
+ return -1;
+}
--- /dev/null
+/* $Id: sequence.c 4871 2001-07-09 08:09:58Z alexk $
+**
+** Sequence space arithmetic routines.
+**
+** This is a set of routines for implementing so called sequence
+** space arithmetic (typically used for DNS serial numbers). The
+** implementation here is taken from RFC 1982.
+*/
+
+#include "config.h"
+#include "clibrary.h"
+#include <limits.h>
+#include "inn/sequence.h"
+
+
+/*
+** compare two unsigned long numbers using sequence space arithmetic
+**
+** returns:
+** 0 - i1 = i2
+** -1 - i1 < i2
+** 1 - i1 > i2
+** INT_MAX - undefined
+*/
+int
+seq_lcompare(unsigned long i1, unsigned long i2)
+{
+ if (i1 == i2)
+ return 0;
+ else if ((i1 < i2 && i2 - i1 < (1 + ULONG_MAX / 2)) ||
+ (i1 > i2 && i1 - i2 > (1 + ULONG_MAX / 2)))
+ return -1;
+ else if ((i1 < i2 && i2 - i1 > (1 + ULONG_MAX / 2)) ||
+ (i1 > i2 && i1 - i2 < (1 + ULONG_MAX / 2)))
+ return 1;
+ return INT_MAX;
+}
--- /dev/null
+/* $Id: setenv.c 5713 2002-09-01 03:04:10Z rra $
+**
+** Replacement for a missing setenv.
+**
+** Written by Russ Allbery <rra@stanford.edu>
+** This work is hereby placed in the public domain by its author.
+**
+** Provides the same functionality as the standard library routine setenv
+** for those platforms that don't have it.
+*/
+
+#include "config.h"
+#include "clibrary.h"
+
+/* If we're running the test suite, rename setenv to avoid conflicts with
+ the system version. */
+#if TESTING
+# define setenv test_setenv
+int test_setenv(const char *, const char *, int);
+#endif
+
+int
+setenv(const char *name, const char *value, int overwrite)
+{
+ char *envstring;
+
+ if (!overwrite && getenv(name) != NULL)
+ return 0;
+
+ /* Allocate memory for the environment string. We intentionally don't
+ use concat here, or the xmalloc family of allocation routines, since
+ the intention is to provide a replacement for the standard library
+ function which sets errno and returns in the event of a memory
+ allocation failure. */
+ envstring = malloc(strlen(name) + 1 + strlen(value) + 1);
+ if (envstring == NULL)
+ return -1;
+
+ /* Build the environment string and add it to the environment using
+ putenv. Systems without putenv lose, but XPG4 requires it. */
+ strcpy(envstring, name);
+ strcat(envstring, "=");
+ strcat(envstring, value);
+ return putenv(envstring);
+
+ /* Note that the memory allocated is not freed. This is intentional;
+ many implementations of putenv assume that the string passed to
+ putenv will never be freed and don't make a copy of it. Repeated use
+ of this function will therefore leak memory, since most
+ implementations of putenv also don't free strings removed from the
+ environment (due to being overwritten). */
+}
--- /dev/null
+/* $Id: seteuid.c 3839 2000-08-29 04:50:18Z rra $
+**
+** Replacement for a missing seteuid.
+**
+** Written by Russ Allbery <rra@stanford.edu>
+** This work is hereby placed in the public domain by its author.
+**
+** Some systems don't have seteuid but do have setreuid. setreuid with
+** -1 given for the real UID is equivalent to seteuid on systems with
+** POSIX saved UIDs. On systems without POSIX saved UIDs, we'd lose our
+** ability to regain privileges if we just set the effective UID, so
+** instead fake a saved UID by setting the real UID to the current
+** effective UID, using the real UID as the saved UID.
+**
+** Note that swapping UIDs doesn't work on AIX, but AIX has saved UIDs.
+** Note also that systems without setreuid lose, and that we assume that
+** any system with seteuid has saved UIDs.
+*/
+
+#include "config.h"
+#include "clibrary.h"
+
+int
+seteuid(uid_t euid)
+{
+ int ruid;
+
+#ifdef _POSIX_SAVED_IDS
+ ruid = -1;
+#else
+ ruid = geteuid();
+#endif
+ return setreuid(ruid, euid);
+}
--- /dev/null
+/* $Id: setproctitle.c 5943 2002-12-08 02:28:06Z rra $
+**
+** Replacement for a missing setproctitle.
+**
+** Provides the same functionality as the BSD function setproctitle on hosts
+** where modifying argv will produce those results, or on HP-UX (which has
+** its own peculiar way of doing this). This may be ineffective on some
+** platforms.
+**
+** Before calling setproctitle, it is *required* that setproctitle_init be
+** called, passing it argc and argv as arguments. setproctitle_init will be
+** stubbed out on those platforms that don't need it.
+*/
+
+#include "config.h"
+#include "clibrary.h"
+#include "portable/setproctitle.h"
+
+#include "inn/messages.h"
+
+#if HAVE_PSTAT
+
+#include <sys/param.h>
+#include <sys/pstat.h>
+
+void
+setproctitle(const char *format, ...)
+{
+ va_list args;
+ char title[BUFSIZ];
+ union pstun un;
+ ssize_t delta = 0;
+
+ if (message_program_name != NULL) {
+ delta = snprintf(title, sizeof(title), "%s: ", message_program_name);
+ if (delta < 0)
+ delta = 0;
+ }
+ va_start(args, format);
+ vsnprintf(title + delta, sizeof(title) - delta, format, args);
+ va_end(args);
+ un.pst_command = title;
+ pstat(PSTAT_SETCMD, un, strlen(title), 0, 0);
+}
+
+#else
+
+static char *title_start = NULL;
+static char *title_end = NULL;
+
+void
+setproctitle_init(int argc, char *argv[])
+{
+ title_start = argv[0];
+ title_end = argv[argc - 1] + strlen(argv[argc - 1]) - 1;
+}
+
+void
+setproctitle(const char *format, ...)
+{
+ va_list args;
+ size_t length;
+ ssize_t delta;
+ char *title;
+
+ if (title_start == NULL || title_end == NULL) {
+ warn("setproctitle called without setproctitle_init");
+ return;
+ }
+
+ /* setproctitle prepends the program name to its arguments. Our emulation
+ should therefore do the same thing. However, some operating systems
+ seem to do that automatically even when we completely overwrite argv,
+ so start our title with a - so that they'll instead put (nnrpd) at the
+ end, thinking we're swapped out. */
+ title = title_start;
+ *title++ = '-';
+ *title++ = ' ';
+ length = title_end - title_start - 2;
+
+ /* Now, put in the actual content. Get the program name from
+ message_program_name if it's set. */
+ if (message_program_name != NULL) {
+ delta = snprintf(title, length, "%s: ", message_program_name);
+ if (delta < 0 || (size_t) delta > length)
+ return;
+ if (delta > 0) {
+ title += delta;
+ length -= delta;
+ }
+ }
+ va_start(args, format);
+ delta = vsnprintf(title, length, format, args);
+ va_end(args);
+ if (delta < 0 || (size_t) delta > length)
+ return;
+ if (delta > 0) {
+ title += delta;
+ length -= delta;
+ }
+ for (; length > 1; length--, title++)
+ *title = ' ';
+ *title = '\0';
+}
+
+#endif /* !HAVE_PSTAT */
--- /dev/null
+/* $Id: snprintf.c 7230 2005-04-16 23:33:17Z rra $
+**
+** Replacement for a missing snprintf or vsnprintf.
+**
+** The following implementation of snprintf was taken mostly verbatim from
+** <http://www.fiction.net/~blong/programs/>; it is the version of snprintf
+** used in Mutt.
+**
+** Please do not reformat or otherwise change this file more than
+** necessary so that later merges with the original source are easy.
+** Bug fixes and improvements should be sent back to the original author.
+*/
+
+/* If we're running the test suite, rename snprintf and vsnprintf to avoid
+ conflicts with the system version. */
+#if TESTING
+# define snprintf test_snprintf
+# define vsnprintf test_vsnprintf
+#endif
+
+/*
+ * Copyright Patrick Powell 1995
+ * This code is based on code written by Patrick Powell (papowell@astart.com)
+ * It may be used for any purpose as long as this notice remains intact
+ * on all source code distributions
+ */
+
+/**************************************************************
+ * Original:
+ * Patrick Powell Tue Apr 11 09:48:21 PDT 1995
+ * A bombproof version of doprnt (dopr) included.
+ * Sigh. This sort of thing is always nasty do deal with. Note that
+ * the version here does not include floating point...
+ *
+ * snprintf() is used instead of sprintf() as it does limit checks
+ * for string length. This covers a nasty loophole.
+ *
+ * The other functions are there to prevent NULL pointers from
+ * causing nast effects.
+ *
+ * More Recently:
+ * Brandon Long <blong@fiction.net> 9/15/96 for mutt 0.43
+ * This was ugly. It is still ugly. I opted out of floating point
+ * numbers, but the formatter understands just about everything
+ * from the normal C string format, at least as far as I can tell from
+ * the Solaris 2.5 printf(3S) man page.
+ *
+ * Brandon Long <blong@fiction.net> 10/22/97 for mutt 0.87.1
+ * Ok, added some minimal floating point support, which means this
+ * probably requires libm on most operating systems. Don't yet
+ * support the exponent (e,E) and sigfig (g,G). Also, fmtint()
+ * was pretty badly broken, it just wasn't being exercised in ways
+ * which showed it, so that's been fixed. Also, formated the code
+ * to mutt conventions, and removed dead code left over from the
+ * original. Also, there is now a builtin-test, just compile with:
+ * gcc -DTEST_SNPRINTF -o snprintf snprintf.c -lm
+ * and run snprintf for results.
+ *
+ * Thomas Roessler <roessler@guug.de> 01/27/98 for mutt 0.89i
+ * The PGP code was using unsigned hexadecimal formats.
+ * Unfortunately, unsigned formats simply didn't work.
+ *
+ * Michael Elkins <me@cs.hmc.edu> 03/05/98 for mutt 0.90.8
+ * The original code assumed that both snprintf() and vsnprintf() were
+ * missing. Some systems only have snprintf() but not vsnprintf(), so
+ * the code is now broken down under HAVE_SNPRINTF and HAVE_VSNPRINTF.
+ *
+ * Andrew Tridgell (tridge@samba.org) Oct 1998
+ * fixed handling of %.0f
+ * added test for HAVE_LONG_DOUBLE
+ *
+ * Russ Allbery <rra@stanford.edu> 2000-08-26
+ * fixed return value to comply with C99
+ * fixed handling of snprintf(NULL, ...)
+ *
+ * Hrvoje Niksic <hniksic@arsdigita.com> 2000-11-04
+ * include <stdio.h> for NULL.
+ * added support for long long.
+ * don't declare argument types to (v)snprintf if stdarg is not used.
+ *
+ **************************************************************/
+
+#include "config.h"
+#include <string.h>
+#include <ctype.h>
+#include <sys/types.h>
+
+#ifndef NULL
+# define NULL 0
+#endif
+
+/* varargs declarations: */
+
+#include <stdarg.h>
+#define HAVE_STDARGS /* let's hope that works everywhere (mj) */
+#define VA_LOCAL_DECL va_list ap
+#define VA_START(f) va_start(ap, f)
+#define VA_SHIFT(v,t) ; /* no-op for ANSI */
+#define VA_END va_end(ap)
+
+#ifdef HAVE_LONG_DOUBLE
+#define LDOUBLE long double
+#else
+#define LDOUBLE double
+#endif
+
+#ifdef HAVE_LONG_LONG
+# define LLONG long long
+#else
+# define LLONG long
+#endif
+
+int snprintf (char *str, size_t count, const char *fmt, ...);
+int vsnprintf (char *str, size_t count, const char *fmt, va_list arg);
+
+static int dopr (char *buffer, size_t maxlen, const char *format,
+ va_list args);
+static int fmtstr (char *buffer, size_t *currlen, size_t maxlen,
+ const char *value, int flags, int min, int max);
+static int fmtint (char *buffer, size_t *currlen, size_t maxlen,
+ LLONG value, int base, int min, int max, int flags);
+static int fmtfp (char *buffer, size_t *currlen, size_t maxlen,
+ LDOUBLE fvalue, int min, int max, int flags);
+static int dopr_outch (char *buffer, size_t *currlen, size_t maxlen, char c );
+
+/*
+ * dopr(): poor man's version of doprintf
+ */
+
+/* format read states */
+#define DP_S_DEFAULT 0
+#define DP_S_FLAGS 1
+#define DP_S_MIN 2
+#define DP_S_DOT 3
+#define DP_S_MAX 4
+#define DP_S_MOD 5
+#define DP_S_MOD_L 6
+#define DP_S_CONV 7
+#define DP_S_DONE 8
+
+/* format flags - Bits */
+#define DP_F_MINUS (1 << 0)
+#define DP_F_PLUS (1 << 1)
+#define DP_F_SPACE (1 << 2)
+#define DP_F_NUM (1 << 3)
+#define DP_F_ZERO (1 << 4)
+#define DP_F_UP (1 << 5)
+#define DP_F_UNSIGNED (1 << 6)
+
+/* Conversion Flags */
+#define DP_C_SHORT 1
+#define DP_C_LONG 2
+#define DP_C_LLONG 3
+#define DP_C_LDOUBLE 4
+
+#define char_to_int(p) (p - '0')
+#define MAX(p,q) ((p >= q) ? p : q)
+#define MIN(p,q) ((p <= q) ? p : q)
+
+static int dopr (char *buffer, size_t maxlen, const char *format, va_list args)
+{
+ char ch;
+ LLONG value;
+ LDOUBLE fvalue;
+ char *strvalue;
+ int min;
+ int max;
+ int state;
+ int flags;
+ int cflags;
+ int total;
+ size_t currlen;
+
+ state = DP_S_DEFAULT;
+ currlen = flags = cflags = min = 0;
+ max = -1;
+ ch = *format++;
+ total = 0;
+
+ while (state != DP_S_DONE)
+ {
+ if (ch == '\0')
+ state = DP_S_DONE;
+
+ switch(state)
+ {
+ case DP_S_DEFAULT:
+ if (ch == '%')
+ state = DP_S_FLAGS;
+ else
+ total += dopr_outch (buffer, &currlen, maxlen, ch);
+ ch = *format++;
+ break;
+ case DP_S_FLAGS:
+ switch (ch)
+ {
+ case '-':
+ flags |= DP_F_MINUS;
+ ch = *format++;
+ break;
+ case '+':
+ flags |= DP_F_PLUS;
+ ch = *format++;
+ break;
+ case ' ':
+ flags |= DP_F_SPACE;
+ ch = *format++;
+ break;
+ case '#':
+ flags |= DP_F_NUM;
+ ch = *format++;
+ break;
+ case '0':
+ flags |= DP_F_ZERO;
+ ch = *format++;
+ break;
+ default:
+ state = DP_S_MIN;
+ break;
+ }
+ break;
+ case DP_S_MIN:
+ if ('0' <= ch && ch <= '9')
+ {
+ min = 10*min + char_to_int (ch);
+ ch = *format++;
+ }
+ else if (ch == '*')
+ {
+ min = va_arg (args, int);
+ ch = *format++;
+ state = DP_S_DOT;
+ }
+ else
+ state = DP_S_DOT;
+ break;
+ case DP_S_DOT:
+ if (ch == '.')
+ {
+ state = DP_S_MAX;
+ ch = *format++;
+ }
+ else
+ state = DP_S_MOD;
+ break;
+ case DP_S_MAX:
+ if ('0' <= ch && ch <= '9')
+ {
+ if (max < 0)
+ max = 0;
+ max = 10*max + char_to_int (ch);
+ ch = *format++;
+ }
+ else if (ch == '*')
+ {
+ max = va_arg (args, int);
+ ch = *format++;
+ state = DP_S_MOD;
+ }
+ else
+ state = DP_S_MOD;
+ break;
+ case DP_S_MOD:
+ switch (ch)
+ {
+ case 'h':
+ cflags = DP_C_SHORT;
+ ch = *format++;
+ break;
+ case 'l':
+ cflags = DP_C_LONG;
+ ch = *format++;
+ break;
+ case 'L':
+ cflags = DP_C_LDOUBLE;
+ ch = *format++;
+ break;
+ default:
+ break;
+ }
+ if (cflags != DP_C_LONG)
+ state = DP_S_CONV;
+ else
+ state = DP_S_MOD_L;
+ break;
+ case DP_S_MOD_L:
+ switch (ch)
+ {
+ case 'l':
+ cflags = DP_C_LLONG;
+ ch = *format++;
+ break;
+ default:
+ break;
+ }
+ state = DP_S_CONV;
+ break;
+ case DP_S_CONV:
+ switch (ch)
+ {
+ case 'd':
+ case 'i':
+ if (cflags == DP_C_SHORT)
+ value = (short int) va_arg (args, int);
+ else if (cflags == DP_C_LONG)
+ value = va_arg (args, long int);
+ else if (cflags == DP_C_LLONG)
+ value = va_arg (args, LLONG);
+ else
+ value = va_arg (args, int);
+ total += fmtint (buffer, &currlen, maxlen, value, 10, min, max, flags);
+ break;
+ case 'o':
+ flags |= DP_F_UNSIGNED;
+ if (cflags == DP_C_SHORT)
+ value = (unsigned short int) va_arg (args, unsigned int);
+ else if (cflags == DP_C_LONG)
+ value = va_arg (args, unsigned long int);
+ else if (cflags == DP_C_LLONG)
+ value = va_arg (args, unsigned LLONG);
+ else
+ value = va_arg (args, unsigned int);
+ total += fmtint (buffer, &currlen, maxlen, value, 8, min, max, flags);
+ break;
+ case 'u':
+ flags |= DP_F_UNSIGNED;
+ if (cflags == DP_C_SHORT)
+ value = (unsigned short int) va_arg (args, unsigned int);
+ else if (cflags == DP_C_LONG)
+ value = va_arg (args, unsigned long int);
+ else if (cflags == DP_C_LLONG)
+ value = va_arg (args, unsigned LLONG);
+ else
+ value = va_arg (args, unsigned int);
+ total += fmtint (buffer, &currlen, maxlen, value, 10, min, max, flags);
+ break;
+ case 'X':
+ flags |= DP_F_UP;
+ case 'x':
+ flags |= DP_F_UNSIGNED;
+ if (cflags == DP_C_SHORT)
+ value = (unsigned short int) va_arg (args, unsigned int);
+ else if (cflags == DP_C_LONG)
+ value = va_arg (args, unsigned long int);
+ else if (cflags == DP_C_LLONG)
+ value = va_arg (args, unsigned LLONG);
+ else
+ value = va_arg (args, unsigned int);
+ total += fmtint (buffer, &currlen, maxlen, value, 16, min, max, flags);
+ break;
+ case 'f':
+ if (cflags == DP_C_LDOUBLE)
+ fvalue = va_arg (args, LDOUBLE);
+ else
+ fvalue = va_arg (args, double);
+ /* um, floating point? */
+ total += fmtfp (buffer, &currlen, maxlen, fvalue, min, max, flags);
+ break;
+ case 'E':
+ flags |= DP_F_UP;
+ case 'e':
+ if (cflags == DP_C_LDOUBLE)
+ fvalue = va_arg (args, LDOUBLE);
+ else
+ fvalue = va_arg (args, double);
+ break;
+ case 'G':
+ flags |= DP_F_UP;
+ case 'g':
+ if (cflags == DP_C_LDOUBLE)
+ fvalue = va_arg (args, LDOUBLE);
+ else
+ fvalue = va_arg (args, double);
+ break;
+ case 'c':
+ total += dopr_outch (buffer, &currlen, maxlen, va_arg (args, int));
+ break;
+ case 's':
+ strvalue = va_arg (args, char *);
+ total += fmtstr (buffer, &currlen, maxlen, strvalue, flags, min, max);
+ break;
+ case 'p':
+ strvalue = va_arg (args, void *);
+ total += fmtint (buffer, &currlen, maxlen, (long) strvalue, 16, min,
+ max, flags);
+ break;
+ case 'n':
+ if (cflags == DP_C_SHORT)
+ {
+ short int *num;
+ num = va_arg (args, short int *);
+ *num = currlen;
+ }
+ else if (cflags == DP_C_LONG)
+ {
+ long int *num;
+ num = va_arg (args, long int *);
+ *num = currlen;
+ }
+ else if (cflags == DP_C_LLONG)
+ {
+ LLONG *num;
+ num = va_arg (args, LLONG *);
+ *num = currlen;
+ }
+ else
+ {
+ int *num;
+ num = va_arg (args, int *);
+ *num = currlen;
+ }
+ break;
+ case '%':
+ total += dopr_outch (buffer, &currlen, maxlen, ch);
+ break;
+ case 'w':
+ /* not supported yet, treat as next char */
+ ch = *format++;
+ break;
+ default:
+ /* Unknown, skip */
+ break;
+ }
+ ch = *format++;
+ state = DP_S_DEFAULT;
+ flags = cflags = min = 0;
+ max = -1;
+ break;
+ case DP_S_DONE:
+ break;
+ default:
+ /* hmm? */
+ break; /* some picky compilers need this */
+ }
+ }
+ if (buffer != NULL)
+ {
+ if (currlen < maxlen - 1)
+ buffer[currlen] = '\0';
+ else
+ buffer[maxlen - 1] = '\0';
+ }
+ return total;
+}
+
+static int fmtstr (char *buffer, size_t *currlen, size_t maxlen,
+ const char *value, int flags, int min, int max)
+{
+ int padlen, strln; /* amount to pad */
+ int cnt = 0;
+ int total = 0;
+
+ if (value == 0)
+ {
+ value = "<NULL>";
+ }
+
+ for (strln = 0; value[strln]; ++strln); /* strlen */
+ if (max >= 0 && max < strln)
+ strln = max;
+ padlen = min - strln;
+ if (padlen < 0)
+ padlen = 0;
+ if (flags & DP_F_MINUS)
+ padlen = -padlen; /* Left Justify */
+
+ while (padlen > 0)
+ {
+ total += dopr_outch (buffer, currlen, maxlen, ' ');
+ --padlen;
+ }
+ while (*value && ((max < 0) || (cnt < max)))
+ {
+ total += dopr_outch (buffer, currlen, maxlen, *value++);
+ ++cnt;
+ }
+ while (padlen < 0)
+ {
+ total += dopr_outch (buffer, currlen, maxlen, ' ');
+ ++padlen;
+ }
+ return total;
+}
+
+/* Have to handle DP_F_NUM (ie 0x and 0 alternates) */
+
+static int fmtint (char *buffer, size_t *currlen, size_t maxlen,
+ LLONG value, int base, int min, int max, int flags)
+{
+ int signvalue = 0;
+ unsigned LLONG uvalue;
+ char convert[24];
+ unsigned int place = 0;
+ int spadlen = 0; /* amount to space pad */
+ int zpadlen = 0; /* amount to zero pad */
+ const char *digits;
+ int total = 0;
+
+ if (max < 0)
+ max = 0;
+
+ uvalue = value;
+
+ if(!(flags & DP_F_UNSIGNED))
+ {
+ if( value < 0 ) {
+ signvalue = '-';
+ uvalue = -value;
+ }
+ else
+ if (flags & DP_F_PLUS) /* Do a sign (+/i) */
+ signvalue = '+';
+ else
+ if (flags & DP_F_SPACE)
+ signvalue = ' ';
+ }
+
+ if (flags & DP_F_UP)
+ /* Should characters be upper case? */
+ digits = "0123456789ABCDEF";
+ else
+ digits = "0123456789abcdef";
+
+ do {
+ convert[place++] = digits[uvalue % (unsigned)base];
+ uvalue = (uvalue / (unsigned)base );
+ } while(uvalue && (place < sizeof (convert)));
+ if (place == sizeof (convert)) place--;
+ convert[place] = 0;
+
+ zpadlen = max - place;
+ spadlen = min - MAX ((unsigned int)max, place) - (signvalue ? 1 : 0);
+ if (zpadlen < 0) zpadlen = 0;
+ if (spadlen < 0) spadlen = 0;
+ if (flags & DP_F_ZERO)
+ {
+ zpadlen = MAX(zpadlen, spadlen);
+ spadlen = 0;
+ }
+ if (flags & DP_F_MINUS)
+ spadlen = -spadlen; /* Left Justifty */
+
+#ifdef DEBUG_SNPRINTF
+ dprint (1, (debugfile, "zpad: %d, spad: %d, min: %d, max: %d, place: %d\n",
+ zpadlen, spadlen, min, max, place));
+#endif
+
+ /* Spaces */
+ while (spadlen > 0)
+ {
+ total += dopr_outch (buffer, currlen, maxlen, ' ');
+ --spadlen;
+ }
+
+ /* Sign */
+ if (signvalue)
+ total += dopr_outch (buffer, currlen, maxlen, signvalue);
+
+ /* Zeros */
+ if (zpadlen > 0)
+ {
+ while (zpadlen > 0)
+ {
+ total += dopr_outch (buffer, currlen, maxlen, '0');
+ --zpadlen;
+ }
+ }
+
+ /* Digits */
+ while (place > 0)
+ total += dopr_outch (buffer, currlen, maxlen, convert[--place]);
+
+ /* Left Justified spaces */
+ while (spadlen < 0) {
+ total += dopr_outch (buffer, currlen, maxlen, ' ');
+ ++spadlen;
+ }
+
+ return total;
+}
+
+static LDOUBLE abs_val (LDOUBLE value)
+{
+ LDOUBLE result = value;
+
+ if (value < 0)
+ result = -value;
+
+ return result;
+}
+
+static LDOUBLE pow10 (int exp)
+{
+ LDOUBLE result = 1;
+
+ while (exp)
+ {
+ result *= 10;
+ exp--;
+ }
+
+ return result;
+}
+
+static LLONG round (LDOUBLE value)
+{
+ LLONG intpart;
+
+ intpart = value;
+ value = value - intpart;
+ if (value >= 0.5)
+ intpart++;
+
+ return intpart;
+}
+
+static int fmtfp (char *buffer, size_t *currlen, size_t maxlen,
+ LDOUBLE fvalue, int min, int max, int flags)
+{
+ int signvalue = 0;
+ LDOUBLE ufvalue;
+ char iconvert[20];
+ char fconvert[20];
+ int iplace = 0;
+ int fplace = 0;
+ int padlen = 0; /* amount to pad */
+ int zpadlen = 0;
+ int caps = 0;
+ int total = 0;
+ LLONG intpart;
+ LLONG fracpart;
+
+ /*
+ * AIX manpage says the default is 0, but Solaris says the default
+ * is 6, and sprintf on AIX defaults to 6
+ */
+ if (max < 0)
+ max = 6;
+
+ ufvalue = abs_val (fvalue);
+
+ if (fvalue < 0)
+ signvalue = '-';
+ else
+ if (flags & DP_F_PLUS) /* Do a sign (+/i) */
+ signvalue = '+';
+ else
+ if (flags & DP_F_SPACE)
+ signvalue = ' ';
+
+#if 0
+ if (flags & DP_F_UP) caps = 1; /* Should characters be upper case? */
+#endif
+
+ intpart = ufvalue;
+
+ /*
+ * Sorry, we only support 9 digits past the decimal because of our
+ * conversion method
+ */
+ if (max > 9)
+ max = 9;
+
+ /* We "cheat" by converting the fractional part to integer by
+ * multiplying by a factor of 10
+ */
+ fracpart = round ((pow10 (max)) * (ufvalue - intpart));
+
+ if (fracpart >= pow10 (max))
+ {
+ intpart++;
+ fracpart -= pow10 (max);
+ }
+
+#ifdef DEBUG_SNPRINTF
+ dprint (1, (debugfile, "fmtfp: %f =? %d.%d\n", fvalue, intpart, fracpart));
+#endif
+
+ /* Convert integer part */
+ do {
+ iconvert[iplace++] =
+ (caps? "0123456789ABCDEF":"0123456789abcdef")[intpart % 10];
+ intpart = (intpart / 10);
+ } while(intpart && (iplace < 20));
+ if (iplace == 20) iplace--;
+ iconvert[iplace] = 0;
+
+ /* Convert fractional part */
+ do {
+ fconvert[fplace++] =
+ (caps? "0123456789ABCDEF":"0123456789abcdef")[fracpart % 10];
+ fracpart = (fracpart / 10);
+ } while(fracpart && (fplace < 20));
+ if (fplace == 20) fplace--;
+ fconvert[fplace] = 0;
+
+ /* -1 for decimal point, another -1 if we are printing a sign */
+ padlen = min - iplace - max - 1 - ((signvalue) ? 1 : 0);
+ zpadlen = max - fplace;
+ if (zpadlen < 0)
+ zpadlen = 0;
+ if (padlen < 0)
+ padlen = 0;
+ if (flags & DP_F_MINUS)
+ padlen = -padlen; /* Left Justifty */
+
+ if ((flags & DP_F_ZERO) && (padlen > 0))
+ {
+ if (signvalue)
+ {
+ total += dopr_outch (buffer, currlen, maxlen, signvalue);
+ --padlen;
+ signvalue = 0;
+ }
+ while (padlen > 0)
+ {
+ total += dopr_outch (buffer, currlen, maxlen, '0');
+ --padlen;
+ }
+ }
+ while (padlen > 0)
+ {
+ total += dopr_outch (buffer, currlen, maxlen, ' ');
+ --padlen;
+ }
+ if (signvalue)
+ total += dopr_outch (buffer, currlen, maxlen, signvalue);
+
+ while (iplace > 0)
+ total += dopr_outch (buffer, currlen, maxlen, iconvert[--iplace]);
+
+ /*
+ * Decimal point. This should probably use locale to find the correct
+ * char to print out.
+ */
+ if (max > 0)
+ {
+ total += dopr_outch (buffer, currlen, maxlen, '.');
+
+ while (fplace > 0)
+ total += dopr_outch (buffer, currlen, maxlen, fconvert[--fplace]);
+ }
+
+ while (zpadlen > 0)
+ {
+ total += dopr_outch (buffer, currlen, maxlen, '0');
+ --zpadlen;
+ }
+
+ while (padlen < 0)
+ {
+ total += dopr_outch (buffer, currlen, maxlen, ' ');
+ ++padlen;
+ }
+
+ return total;
+}
+
+static int dopr_outch (char *buffer, size_t *currlen, size_t maxlen, char c)
+{
+ if (*currlen + 1 < maxlen)
+ buffer[(*currlen)++] = c;
+ return 1;
+}
+
+int vsnprintf (char *str, size_t count, const char *fmt, va_list args)
+{
+ if (str != NULL)
+ str[0] = 0;
+ return dopr(str, count, fmt, args);
+}
+
+/* VARARGS3 */
+#ifdef HAVE_STDARGS
+int snprintf (char *str,size_t count,const char *fmt,...)
+#else
+int snprintf (va_alist) va_dcl
+#endif
+{
+#ifndef HAVE_STDARGS
+ char *str;
+ size_t count;
+ char *fmt;
+#endif
+ VA_LOCAL_DECL;
+ int total;
+
+ VA_START (fmt);
+ VA_SHIFT (str, char *);
+ VA_SHIFT (count, size_t );
+ VA_SHIFT (fmt, char *);
+ total = vsnprintf(str, count, fmt, ap);
+ VA_END;
+ return total;
+}
+
+#ifdef TEST_SNPRINTF
+#ifndef LONG_STRING
+#define LONG_STRING 1024
+#endif
+int main (void)
+{
+ char buf1[LONG_STRING];
+ char buf2[LONG_STRING];
+ char *fp_fmt[] = {
+ "%-1.5f",
+ "%1.5f",
+ "%123.9f",
+ "%10.5f",
+ "% 10.5f",
+ "%+22.9f",
+ "%+4.9f",
+ "%01.3f",
+ "%4f",
+ "%3.1f",
+ "%3.2f",
+ "%.0f",
+ "%.1f",
+ NULL
+ };
+ double fp_nums[] = { -1.5, 134.21, 91340.2, 341.1234, 0203.9, 0.96, 0.996,
+ 0.9996, 1.996, 4.136, 0};
+ char *int_fmt[] = {
+ "%-1.5d",
+ "%1.5d",
+ "%123.9d",
+ "%5.5d",
+ "%10.5d",
+ "% 10.5d",
+ "%+22.33d",
+ "%01.3d",
+ "%4d",
+ NULL
+ };
+ long int_nums[] = { -1, 134, 91340, 341, 0203, 0};
+ int x, y;
+ int fail = 0;
+ int num = 0;
+
+ printf ("Testing snprintf format codes against system sprintf...\n");
+
+ for (x = 0; fp_fmt[x] != NULL ; x++)
+ for (y = 0; fp_nums[y] != 0 ; y++)
+ {
+ snprintf (buf1, sizeof (buf1), fp_fmt[x], fp_nums[y]);
+ sprintf (buf2, fp_fmt[x], fp_nums[y]);
+ if (strcmp (buf1, buf2))
+ {
+ printf("snprintf doesn't match Format: %s\n\tsnprintf = %s\n\tsprintf = %s\n",
+ fp_fmt[x], buf1, buf2);
+ fail++;
+ }
+ num++;
+ }
+
+ for (x = 0; int_fmt[x] != NULL ; x++)
+ for (y = 0; int_nums[y] != 0 ; y++)
+ {
+ snprintf (buf1, sizeof (buf1), int_fmt[x], int_nums[y]);
+ sprintf (buf2, int_fmt[x], int_nums[y]);
+ if (strcmp (buf1, buf2))
+ {
+ printf("snprintf doesn't match Format: %s\n\tsnprintf = %s\n\tsprintf = %s\n",
+ int_fmt[x], buf1, buf2);
+ fail++;
+ }
+ num++;
+ }
+ printf ("%d tests failed out of %d.\n", fail, num);
+}
+#endif /* SNPRINTF_TEST */
--- /dev/null
+/* $Id: sockaddr.c 5381 2002-03-31 22:35:47Z rra $
+**
+** Routines for manipulating sockaddr structs
+*/
+
+#include "config.h"
+#include "clibrary.h"
+#include "portable/socket.h"
+#include <netdb.h>
+
+#include "libinn.h"
+
+char *sprint_sockaddr(const struct sockaddr *sa)
+{
+#ifdef HAVE_INET6
+ static char buff[256];
+ const struct sockaddr_in6 *sin6 = (const struct sockaddr_in6 *) sa;
+
+ *buff = '\0';
+ if (sa->sa_family == AF_INET6 && IN6_IS_ADDR_V4MAPPED(&sin6->sin6_addr)) {
+ struct sockaddr_in sin;
+ memcpy(&sin.sin_addr, sin6->sin6_addr.s6_addr + 12,
+ sizeof sin.sin_addr);
+ sin.sin_port = sin6->sin6_port;
+ sin.sin_family = AF_INET;
+#ifdef HAVE_SOCKADDR_LEN
+ sin.sin_len = sizeof(struct sockaddr_in);
+#endif
+ return inet_ntoa(sin.sin_addr);
+ }
+ getnameinfo(sa, SA_LEN(sa), buff, sizeof buff, NULL, 0, NI_NUMERICHOST);
+
+ return buff;
+#else
+ return inet_ntoa(((const struct sockaddr_in *)sa)->sin_addr);
+#endif
+}
+
+void make_sin(struct sockaddr_in *s, const struct in_addr *src)
+{
+ memset(s, 0, sizeof( struct sockaddr_in ));
+ s->sin_family = AF_INET;
+#ifdef HAVE_SOCKADDR_LEN
+ s->sin_len = sizeof( struct sockaddr_in );
+#endif
+ s->sin_addr = *src;
+}
--- /dev/null
+#include "config.h"
+#include "clibrary.h"
+
+
+/* $Revision: 6118 $
+ *
+ * Copyright (c) 1987 Regents of the University of California.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms are permitted
+ * provided that: (1) source distributions retain this entire copyright
+ * notice and comment, and (2) distributions including binaries display
+ * the following acknowledgement: ``This product includes software
+ * developed by the University of California, Berkeley and its contributors''
+ * in the documentation or other materials provided with the distribution
+ * and in all advertising materials mentioning features or use of this
+ * software. Neither the name of the University nor the names of its
+ * contributors may be used to endorse or promote products derived
+ * from this software without specific prior written permission.
+ * THIS SOFTWARE IS PROVIDED ``AS IS'' AND WITHOUT ANY EXPRESS OR
+ * IMPLIED WARRANTIES, INCLUDING, WITHOUT LIMITATION, THE IMPLIED
+ * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE.
+ */
+
+#if defined(LIBC_SCCS) && !defined(lint)
+static const char sccsid[] = "@(#)strcasecmp.c 5.9 (Berkeley) 6/1/90";
+#endif /* LIBC_SCCS and not lint */
+
+typedef unsigned char u_char;
+
+/*
+ * This array is designed for mapping upper and lower case letter
+ * together for a case independent comparison. The mappings are
+ * based upon ascii character sequences.
+ */
+static const u_char charmap[] = {
+ '\000', '\001', '\002', '\003', '\004', '\005', '\006', '\007',
+ '\010', '\011', '\012', '\013', '\014', '\015', '\016', '\017',
+ '\020', '\021', '\022', '\023', '\024', '\025', '\026', '\027',
+ '\030', '\031', '\032', '\033', '\034', '\035', '\036', '\037',
+ '\040', '\041', '\042', '\043', '\044', '\045', '\046', '\047',
+ '\050', '\051', '\052', '\053', '\054', '\055', '\056', '\057',
+ '\060', '\061', '\062', '\063', '\064', '\065', '\066', '\067',
+ '\070', '\071', '\072', '\073', '\074', '\075', '\076', '\077',
+ '\100', '\141', '\142', '\143', '\144', '\145', '\146', '\147',
+ '\150', '\151', '\152', '\153', '\154', '\155', '\156', '\157',
+ '\160', '\161', '\162', '\163', '\164', '\165', '\166', '\167',
+ '\170', '\171', '\172', '\133', '\134', '\135', '\136', '\137',
+ '\140', '\141', '\142', '\143', '\144', '\145', '\146', '\147',
+ '\150', '\151', '\152', '\153', '\154', '\155', '\156', '\157',
+ '\160', '\161', '\162', '\163', '\164', '\165', '\166', '\167',
+ '\170', '\171', '\172', '\173', '\174', '\175', '\176', '\177',
+ '\200', '\201', '\202', '\203', '\204', '\205', '\206', '\207',
+ '\210', '\211', '\212', '\213', '\214', '\215', '\216', '\217',
+ '\220', '\221', '\222', '\223', '\224', '\225', '\226', '\227',
+ '\230', '\231', '\232', '\233', '\234', '\235', '\236', '\237',
+ '\240', '\241', '\242', '\243', '\244', '\245', '\246', '\247',
+ '\250', '\251', '\252', '\253', '\254', '\255', '\256', '\257',
+ '\260', '\261', '\262', '\263', '\264', '\265', '\266', '\267',
+ '\270', '\271', '\272', '\273', '\274', '\275', '\276', '\277',
+ '\300', '\301', '\302', '\303', '\304', '\305', '\306', '\307',
+ '\310', '\311', '\312', '\313', '\314', '\315', '\316', '\317',
+ '\320', '\321', '\322', '\323', '\324', '\325', '\326', '\327',
+ '\330', '\331', '\332', '\333', '\334', '\335', '\336', '\337',
+ '\340', '\341', '\342', '\343', '\344', '\345', '\346', '\347',
+ '\350', '\351', '\352', '\353', '\354', '\355', '\356', '\357',
+ '\360', '\361', '\362', '\363', '\364', '\365', '\366', '\367',
+ '\370', '\371', '\372', '\373', '\374', '\375', '\376', '\377',
+};
+
+int
+strcasecmp(s1, s2)
+ const char *s1, *s2;
+{
+ const u_char *cm = charmap,
+ *us1 = (const u_char *)s1,
+ *us2 = (const u_char *)s2;
+
+ while (cm[*us1] == cm[*us2++])
+ if (*us1++ == '\0')
+ return (0);
+ return (cm[*us1] - cm[*--us2]);
+}
+
+int
+strncasecmp(s1, s2, n)
+ const char *s1, *s2;
+ size_t n;
+{
+ if (n != 0) {
+ const u_char *cm = charmap,
+ *us1 = (const u_char *)s1,
+ *us2 = (const u_char *)s2;
+
+ do {
+ if (cm[*us1] != cm[*us2++])
+ return (cm[*us1] - cm[*--us2]);
+ if (*us1++ == '\0')
+ break;
+ } while (--n != 0);
+ }
+ return (0);
+}
--- /dev/null
+/* $Id: strerror.c 6127 2003-01-18 22:25:37Z rra $
+**
+** Replacement for a missing strerror.
+**
+** Written by Russ Allbery <rra@stanford.edu>
+** This work is hereby placed in the public domain by its author.
+**
+** Provides the same functionality as the standard library routine strerror
+** for those platforms that don't have it (e.g. Ultrix). Assume that we
+** have sys_nerr and sys_errlist available to use instead. Calling
+** strerror should be thread-safe unless it is called for an unknown errno.
+*/
+
+#include "config.h"
+
+/* Our declarations of sys_nerr and sys_errlist may conflict with the ones
+ provided by stdio.h from glibc. This trick hides the declarations in the
+ system header from the compiler while we test. (The conflicts are just
+ whether or not to const, so there are no negative effects from using our
+ declarations.) */
+#if TESTING
+# define sys_nerr hidden_sys_nerr
+# define sys_errlist hidden_sys_errlist
+#endif
+
+#include <errno.h>
+#include <stdio.h>
+
+#if TESTING
+# undef sys_nerr
+# undef sys_errlist
+#endif
+
+extern const int sys_nerr;
+extern const char *sys_errlist[];
+
+/* If we're running the test suite, rename strerror to avoid conflicts with
+ the system version. */
+#if TESTING
+# define strerror test_strerror
+const char *test_strerror(int);
+int snprintf(char *, size_t, const char *, ...);
+#endif
+
+const char *
+strerror(int error)
+{
+ static char buff[32];
+ int oerrno;
+
+ if (error >= 0 && error < sys_nerr)
+ return sys_errlist[error];
+ oerrno = errno;
+ snprintf(buff, sizeof(buff), "Error code %d", error);
+ errno = oerrno;
+ return buff;
+}
--- /dev/null
+/* $Id: strlcat.c 5681 2002-08-29 04:07:50Z rra $
+**
+** Replacement for a missing strlcat.
+**
+** Written by Russ Allbery <rra@stanford.edu>
+** This work is hereby placed in the public domain by its author.
+**
+** Provides the same functionality as the *BSD function strlcat, originally
+** developed by Todd Miller and Theo de Raadt. strlcat works similarly to
+** strncat, except simpler. The result is always nul-terminated even if the
+** source string is longer than the space remaining in the destination
+** string, and the total space required is returned. The third argument is
+** the total space available in the destination buffer, not just the amount
+** of space remaining.
+*/
+
+#include "config.h"
+#include "clibrary.h"
+
+/* If we're running the test suite, rename strlcat to avoid conflicts with
+ the system version. */
+#if TESTING
+# define strlcat test_strlcat
+size_t test_strlcat(char *, const char *, size_t);
+#endif
+
+size_t
+strlcat(char *dst, const char *src, size_t size)
+{
+ size_t used, length, copy;
+
+ used = strlen(dst);
+ length = strlen(src);
+ if (size > 0 && used < size - 1) {
+ copy = (length >= size - used) ? size - used - 1 : length;
+ memcpy(dst + used, src, copy);
+ dst[used + copy] = '\0';
+ }
+ return used + length;
+}
--- /dev/null
+/* $Id: strlcpy.c 5681 2002-08-29 04:07:50Z rra $
+**
+** Replacement for a missing strlcpy.
+**
+** Written by Russ Allbery <rra@stanford.edu>
+** This work is hereby placed in the public domain by its author.
+**
+** Provides the same functionality as the *BSD function strlcpy, originally
+** developed by Todd Miller and Theo de Raadt. strlcpy works similarly to
+** strncpy, except saner and simpler. The result is always nul-terminated
+** even if the source string is longer than the destination string, and the
+** total space required is returned. The destination string is not
+** nul-filled like strncpy does, just nul-terminated.
+*/
+
+#include "config.h"
+#include "clibrary.h"
+
+/* If we're running the test suite, rename strlcpy to avoid conflicts with
+ the system version. */
+#if TESTING
+# define strlcpy test_strlcpy
+size_t test_strlcpy(char *, const char *, size_t);
+#endif
+
+size_t
+strlcpy(char *dst, const char *src, size_t size)
+{
+ size_t length, copy;
+
+ length = strlen(src);
+ if (size > 0) {
+ copy = (length >= size) ? size - 1 : length;
+ memcpy(dst, src, copy);
+ dst[copy] = '\0';
+ }
+ return length;
+}
--- /dev/null
+/* $Id: strspn.c 6118 2003-01-13 06:44:24Z rra $
+**
+** This file has been modified to get it to compile more easily
+** on pre-4.4BSD systems. Rich $alz, June 1991.
+*/
+
+#include "config.h"
+#include "clibrary.h"
+
+
+/*
+ * Copyright (c) 1989 The Regents of the University of California.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms are permitted
+ * provided that: (1) source distributions retain this entire copyright
+ * notice and comment, and (2) distributions including binaries display
+ * the following acknowledgement: ``This product includes software
+ * developed by the University of California, Berkeley and its contributors''
+ * in the documentation or other materials provided with the distribution
+ * and in all advertising materials mentioning features or use of this
+ * software. Neither the name of the University nor the names of its
+ * contributors may be used to endorse or promote products derived
+ * from this software without specific prior written permission.
+ * THIS SOFTWARE IS PROVIDED ``AS IS'' AND WITHOUT ANY EXPRESS OR
+ * IMPLIED WARRANTIES, INCLUDING, WITHOUT LIMITATION, THE IMPLIED
+ * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE.
+ */
+
+#if 0
+#if defined(LIBC_SCCS) && !defined(lint)
+static char sccsid[] = "@(#)strspn.c 5.7 (Berkeley) 6/1/90";
+#endif /* LIBC_SCCS and not lint */
+
+#include <sys/stdc.h>
+#include <string.h>
+#endif
+
+/*
+ * Span the string s2 (skip characters that are in s2).
+ */
+size_t
+strspn(s1, s2)
+ const char *s1;
+ const char *s2;
+{
+ const char *p = s1, *spanp;
+ char c, sc;
+
+ /*
+ * Skip any characters in s2, excluding the terminating \0.
+ */
+cont:
+ c = *p++;
+ for (spanp = s2; (sc = *spanp++) != 0;)
+ if (sc == c)
+ goto cont;
+ return (p - 1 - s1);
+}
--- /dev/null
+/* $Id: strtok.c 6118 2003-01-13 06:44:24Z rra $
+**
+** This file has been modified to get it to compile more easily
+** on pre-4.4BSD systems. Rich $alz, June 1991.
+*/
+
+#include "config.h"
+#include "clibrary.h"
+
+
+/*
+ * Copyright (c) 1988 Regents of the University of California.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms are permitted
+ * provided that: (1) source distributions retain this entire copyright
+ * notice and comment, and (2) distributions including binaries display
+ * the following acknowledgement: ``This product includes software
+ * developed by the University of California, Berkeley and its contributors''
+ * in the documentation or other materials provided with the distribution
+ * and in all advertising materials mentioning features or use of this
+ * software. Neither the name of the University nor the names of its
+ * contributors may be used to endorse or promote products derived
+ * from this software without specific prior written permission.
+ * THIS SOFTWARE IS PROVIDED ``AS IS'' AND WITHOUT ANY EXPRESS OR
+ * IMPLIED WARRANTIES, INCLUDING, WITHOUT LIMITATION, THE IMPLIED
+ * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE.
+ */
+
+#if 0
+#if defined(LIBC_SCCS) && !defined(lint)
+static char sccsid[] = "@(#)strtok.c 5.7 (Berkeley) 6/1/90";
+#endif /* LIBC_SCCS and not lint */
+
+#include <stddef.h>
+#include <string.h>
+#endif
+
+char *
+strtok(s, delim)
+ char *s, *delim;
+{
+ char *spanp;
+ int c, sc;
+ char *tok;
+ static char *last;
+
+
+ if (s == NULL && (s = last) == NULL)
+ return (NULL);
+
+ /*
+ * Skip (span) leading delimiters (s += strspn(s, delim), sort of).
+ */
+cont:
+ c = *s++;
+ for (spanp = delim; (sc = *spanp++) != 0;) {
+ if (c == sc)
+ goto cont;
+ }
+
+ if (c == 0) { /* no non-delimiter characters */
+ last = NULL;
+ return (NULL);
+ }
+ tok = s - 1;
+
+ /*
+ * Scan token (scan for delimiters: s += strcspn(s, delim), sort of).
+ * Note that delim must have one NUL; we stop if we see that, too.
+ */
+ for (;;) {
+ c = *s++;
+ spanp = delim;
+ do {
+ if ((sc = *spanp++) == c) {
+ if (c == 0)
+ s = NULL;
+ else
+ s[-1] = 0;
+ last = s;
+ return (tok);
+ }
+ } while (sc != 0);
+ }
+ /* NOTREACHED */
+}
--- /dev/null
+/* $Id: timer.c 6129 2003-01-19 00:39:49Z rra $
+**
+** Timer functions, to gather profiling data.
+**
+** These functions log profiling information about where the server spends
+** its time. While this doesn't provide as detailed of information as a
+** profiling build would, it's much faster and simpler, and since it's fast
+** enough to always leave on even on production servers, it can gather
+** information *before* it's needed and show long-term trends.
+**
+** Functions that should have their time monitored need to call TMRstart(n)
+** at the beginning of the segment of code and TMRstop(n) at the end. The
+** time spent will be accumulated and added to the total for the counter n,
+** where n should be one of the constants in timer.h or defined in your
+** application. If you add new timers in the library code, add them to
+** timer.h and also add a description to TMRsummary; if you add them in
+** your application add them to your own description array. Also add them
+** to innreport.
+**
+** Calls are sanity-checked to some degree and errors reported via
+** warn/die, so all callers should have the proper warn and die handlers
+** set up, if appropriate.
+**
+** Recursion is not allowed on a given timer. Setting multiple timers
+** at once is fine (i.e., you may have a timer for the total time to write
+** an article, how long the disk write takes, how long the history update
+** takes, etc. which are components of the total article write time). If a
+** timer is started while another timer is running, the new timer is
+** considered to be a sub-timer of the running timer, and must be stopped
+** before the parent timer is stopped. Note that the same timer number can
+** be a sub-timer of more than one timer or a timer without a parent, and
+** each of those counts will be reported separately.
+**
+** Note that this code is not thread-safe and in fact would need to be
+** completely overhauled for a threaded server (since the idea of global
+** timing statistics doesn't make as much sense when different tasks are
+** done in different threads).
+*/
+
+#include "config.h"
+#include "clibrary.h"
+#include "portable/time.h"
+#include <syslog.h>
+
+#include "inn/messages.h"
+#include "inn/timer.h"
+#include "libinn.h"
+
+/* Timer values are stored in a series of trees. This allows use to use
+ nested timers. Each nested timer node is linked to three of its
+ neighbours to make lookups easy and fast. The current position in the
+ graph is given by timer_current.
+
+ As an optimization, since most timers aren't nested, timer_list holds an
+ array of pointers to non-nested timers that's filled in as TMRstart is
+ called so that the non-nested case remains O(1). That array is stored in
+ timers. This is the "top level" of the timer trees; if timer_current is
+ NULL, any timer that's started is found in this array. If timer_current
+ isn't NULL, there's a running timer, and starting a new timer adds to
+ that tree.
+
+ Note that without the parent pointer, this is a tree. id is the
+ identifier of the timer. start stores the time (relative to the last
+ summary) at which TMRstart was last called for each timer. total is
+ the total time accrued by that timer since the last summary. count is
+ the number of times the timer has been stopped since the last summary. */
+struct timer {
+ unsigned int id;
+ unsigned long start;
+ unsigned long total;
+ unsigned long count;
+
+ struct timer *parent;
+ struct timer *brother;
+ struct timer *child;
+};
+static struct timer **timers = NULL;
+static struct timer *timer_current = NULL;
+unsigned int timer_count = 0;
+
+/* Names for all of the timers. These must be given in the same order
+ as the definition of the enum in timer.h. */
+static const char *const timer_name[TMR_APPLICATION] = {
+ "hishave", "hisgrep", "hiswrite", "hissync",
+};
+
+
+/*
+** Returns the current time as a double. This is not used by any of the
+** other timer code, but is used by various programs right now to keep track
+** of elapsed time.
+*/
+double
+TMRnow_double(void)
+{
+ struct timeval tv;
+
+ gettimeofday(&tv, NULL);
+ return (tv.tv_sec + tv.tv_usec * 1.0e-6);
+}
+
+
+/*
+** Returns the number of milliseconds since the base time. This gives
+** better resolution than time, but the return value is a lot easier to
+** work with than a struct timeval. If the argument is true, also reset
+** the base time.
+*/
+static unsigned long
+TMRgettime(bool reset)
+{
+ unsigned long now;
+ struct timeval tv;
+
+ /* The time of the last summary, used as a base for times returned by
+ TMRnow. Formerly, times were relative to the last call to TMRinit,
+ which was only called once when innd was starting up; with that
+ approach, times may overflow a 32-bit unsigned long about 50 days
+ after the server starts up. While this may still work due to unsigned
+ arithmetic, this approach is less confusing to follow. */
+ static struct timeval base;
+
+ gettimeofday(&tv, NULL);
+ now = (tv.tv_sec - base.tv_sec) * 1000;
+ now += (tv.tv_usec - base.tv_usec) / 1000;
+ if (reset)
+ base = tv;
+ return now;
+}
+
+
+/*
+** Initialize the timer. Zero out even variables that would initially be
+** zero so that this function can be called multiple times if wanted.
+*/
+void
+TMRinit(unsigned int count)
+{
+ unsigned int i;
+
+ /* TMRinit(0) disables all timers. */
+ TMRfree();
+ if (count != 0) {
+ timers = xmalloc(count * sizeof(struct timer *));
+ for (i = 0; i < count; i++)
+ timers[i] = NULL;
+ TMRgettime(true);
+ }
+ timer_count = count;
+}
+
+
+/*
+** Recursively destroy a timer node.
+*/
+static void
+TMRfreeone(struct timer *timer)
+{
+ if (timer == NULL)
+ return;
+ if (timer->child != NULL)
+ TMRfreeone(timer->child);
+ if (timer->brother != NULL)
+ TMRfreeone(timer->brother);
+ free(timer);
+}
+
+
+/*
+** Free all timers and the resources devoted to them.
+*/
+void
+TMRfree(void)
+{
+ unsigned int i;
+
+ if (timers != NULL)
+ for (i = 0; i < timer_count; i++)
+ TMRfreeone(timers[i]);
+ free(timers);
+ timers = NULL;
+ timer_count = 0;
+}
+
+
+/*
+** Allocate a new timer node. Takes the id and the parent pointer.
+*/
+static struct timer *
+TMRnew(unsigned int id, struct timer *parent)
+{
+ struct timer *timer;
+
+ timer = xmalloc(sizeof(struct timer));
+ timer->parent = parent;
+ timer->brother = NULL;
+ timer->child = NULL;
+ timer->id = id;
+ timer->start = 0;
+ timer->total = 0;
+ timer->count = 0;
+ return timer;
+}
+
+
+/*
+** Start a particular timer. If no timer is currently running, start one
+** of the top-level timers in the timers array (creating a new one if
+** needed). Otherwise, search for the timer among the children of the
+** currently running timer, again creating a new timer if necessary.
+*/
+void
+TMRstart(unsigned int timer)
+{
+ struct timer *search;
+
+ if (timer_count == 0) {
+ /* this should happen if innconf->timer == 0 */
+ return;
+ }
+ if (timer >= timer_count) {
+ warn("timer %u is larger than the maximum timer %u, ignored",
+ timer, timer_count - 1);
+ return;
+ }
+
+ /* timers will be non-NULL if timer_count > 0. */
+ if (timer_current == NULL) {
+ if (timers[timer] == NULL)
+ timers[timer] = TMRnew(timer, NULL);
+ timer_current = timers[timer];
+ } else {
+ search = timer_current;
+
+ /* Go to the "child" level and look for the good "brother"; the
+ "brothers" are a simple linked list. */
+ if (search->child == NULL) {
+ search->child = TMRnew(timer, search);
+ timer_current = search->child;
+ } else {
+ search = search->child;
+ while (search->id != timer && search->brother != NULL)
+ search = search->brother;
+ if (search->id != timer) {
+ search->brother = TMRnew(timer, search->parent);
+ timer_current = search->brother;
+ } else {
+ timer_current = search;
+ }
+ }
+ }
+ timer_current->start = TMRgettime(false);
+}
+
+
+/*
+** Stop a particular timer, adding the total time to total and incrementing
+** the count of times that timer has been invoked.
+*/
+void
+TMRstop(unsigned int timer)
+{
+ if (timer_count == 0) {
+ /* this should happen if innconf->timer == 0 */
+ return;
+ }
+ if (timer_current == NULL)
+ warn("timer %u stopped when no timer was running", timer);
+ else if (timer != timer_current->id)
+ warn("timer %u stopped doesn't match running timer %u", timer,
+ timer_current->id);
+ else {
+ timer_current->total += TMRgettime(false) - timer_current->start;
+ timer_current->count++;
+ timer_current = timer_current->parent;
+ }
+}
+
+
+/*
+** Return the current time in milliseconds since the last summary or the
+** initialization of the timer. This is intended for use by the caller to
+** determine when next to call TMRsummary.
+*/
+unsigned long
+TMRnow(void)
+{
+ return TMRgettime(false);
+}
+
+
+/*
+** Return the label associated with timer number id. Used internally
+** to do the right thing when fetching from the timer_name or labels
+** arrays
+*/
+static const char *
+TMRlabel(const char *const *labels, unsigned int id)
+{
+ if (id >= TMR_APPLICATION)
+ return labels[id - TMR_APPLICATION];
+ else
+ return timer_name[id];
+}
+
+
+
+/*
+** Recursively summarize a single timer tree into the supplied buffer,
+** returning the number of characters added to the buffer.
+*/
+static size_t
+TMRsumone(const char *const *labels, struct timer *timer, char *buf,
+ size_t len)
+{
+ struct timer *node;
+ size_t off = 0;
+
+ /* This results in "child/parent nn(nn)" instead of the arguably more
+ intuitive "parent/child" but it's easy. Since we ensure sane snprintf
+ semantics, it's safe to defer checking for overflow until after
+ formatting all of the timer data. */
+ for (node = timer; node != NULL; node = node->parent)
+ off += snprintf(buf + off, len - off, "%s/",
+ TMRlabel(labels, node->id));
+ off--;
+ off += snprintf(buf + off, len - off, " %lu(%lu) ", timer->total,
+ timer->count);
+ if (off == len) {
+ warn("timer log too long while processing %s",
+ TMRlabel(labels, timer->id));
+ return 0;
+ }
+
+ timer->total = 0;
+ timer->count = 0;
+ if (timer->child != NULL)
+ off += TMRsumone(labels, timer->child, buf + off, len - off);
+ if (timer->brother != NULL)
+ off += TMRsumone(labels, timer->brother, buf + off, len - off);
+ return off;
+}
+
+
+/*
+** Summarize the current timer statistics, report them to syslog, and then
+** reset them for the next polling interval.
+*/
+void
+TMRsummary(const char *prefix, const char *const *labels)
+{
+ char *buf;
+ unsigned int i;
+ size_t len, off;
+
+ /* To find the needed buffer size, note that a 64-bit unsigned number can
+ be up to 20 digits long, so each timer can be 52 characters. We also
+ allow another 27 characters for the introductory timestamp, plus some
+ for the prefix. We may have timers recurring at multiple points in
+ the structure, so this may not be long enough, but this is over-sized
+ enough that it shouldn't be a problem. We use snprintf, so if the
+ buffer isn't large enough it will just result in logged errors. */
+ len = 52 * timer_count + 27 + (prefix == NULL ? 0 : strlen(prefix)) + 1;
+ buf = xmalloc(len);
+ if (prefix == NULL)
+ off = 0;
+ else
+ off = snprintf(buf, len, "%s ", prefix);
+ off += snprintf(buf + off, len - off, "time %ld ", TMRgettime(true));
+ for (i = 0; i < timer_count; i++)
+ if (timers[i] != NULL)
+ off += TMRsumone(labels, timers[i], buf + off, len - off);
+ syslog(LOG_NOTICE, "%s", buf);
+ free(buf);
+}
--- /dev/null
+/* $Id: tst.c 6083 2002-12-27 07:24:36Z rra $
+**
+** Ternary search trie implementation.
+**
+** A ternary search trie stores key/value pairs where the key is a
+** nul-terminated string and the value is an arbitrary pointer. It uses a
+** data structure designed for fast lookups, where each level of the trie
+** represents a character in the string being searched for.
+**
+** This implementation is based on the implementation by Peter A. Friend
+** (version 1.3), but has been assimilated into INN and modified to use INN
+** formatting conventions. If new versions are released, examine the
+** differences between that version and version 1.3 (which was checked into
+** INN as individual files in case it's no longer available) and then apply
+** the changes to this file.
+**
+** Copyright (c) 2002, Peter A. Friend
+** All rights reserved.
+**
+** Redistribution and use in source and binary forms, with or without
+** modification, are permitted provided that the following conditions are
+** met:
+**
+** Redistributions of source code must retain the above copyright notice,
+** this list of conditions and the following disclaimer.
+**
+** 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.
+**
+** Neither the name of Peter A. Friend nor the names of his contributors may
+** be used to endorse or promote products derived from this software without
+** specific prior written permission.
+**
+** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS 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 THE COPYRIGHT OWNER 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 "config.h"
+#include "clibrary.h"
+
+#include "inn/tst.h"
+#include "libinn.h"
+
+
+/* A single node in the ternary search trie. Stores a character, which is
+ part of the string formed by walking the tree from its root down to the
+ node, and left, right, and middle pointers to child nodes. If value is
+ non-zero (not a nul), middle is the pointer to follow if the desired
+ string's character matches value. left is used if it's less than value and
+ right is used if it's greater than value. If value is zero, this is a
+ terminal node, and middle holds a pointer to the data associated with the
+ string that ends at this point. */
+struct node {
+ unsigned char value;
+ struct node *left;
+ struct node *middle;
+ struct node *right;
+};
+
+/* The search trie structure. node_line_width is the number of nodes that are
+ allocated at a time, and node_lines holds a linked list of groups of nodes.
+ The free_list is a linked list (through the middle pointers) of available
+ nodes to use, and head holds pointers to the first nodes for each possible
+ first letter of the string. */
+struct tst {
+ int node_line_width;
+ struct node_lines *node_lines;
+ struct node *free_list;
+ struct node *head[256];
+};
+
+/* A simple linked list structure used to hold all the groups of nodes. */
+struct node_lines {
+ struct node *node_line;
+ struct node_lines *next;
+};
+
+
+/*
+** Given a node and a character, decide whether a new node for that character
+** should be placed as the left child of that node. (If false, it should be
+** placed as the right child.)
+*/
+#define LEFTP(n, c) (((n)->value == 0) ? ((c) < 64) : ((c) < (n)->value))
+
+
+/*
+** Allocate new nodes for the free list, called when the free list is empty.
+*/
+static void
+tst_grow_node_free_list(struct tst *tst)
+{
+ struct node *current_node;
+ struct node_lines *new_line;
+ int i;
+
+ new_line = xmalloc(sizeof(struct node_lines));
+ new_line->node_line = xcalloc(tst->node_line_width, sizeof(struct node));
+ new_line->next = tst->node_lines;
+ tst->node_lines = new_line;
+
+ current_node = tst->node_lines->node_line;
+ tst->free_list = current_node;
+ for (i = 1; i < tst->node_line_width; i++) {
+ current_node->middle = &(tst->node_lines->node_line[i]);
+ current_node = current_node->middle;
+ }
+ current_node->middle = NULL;
+}
+
+
+/*
+** Grab a node from the free list and initialize it with the given value.
+*/
+static struct node *
+tst_get_free_node(struct tst *tst, unsigned char value)
+{
+ struct node *free_node;
+
+ if (tst->free_list == NULL)
+ tst_grow_node_free_list(tst);
+ free_node = tst->free_list;
+ tst->free_list = tst->free_list->middle;
+ free_node->middle = NULL;
+ free_node->value = value;
+ return free_node;
+}
+
+
+/*
+** tst_init allocates memory for members of struct tst, and allocates the
+** first node_line_width nodes. The value for width must be chosen very
+** carefully. One node is required for every character in the tree. If you
+** choose a value that is too small, your application will spend too much
+** time calling malloc and your node space will be too spread out. Too large
+** a value is just a waste of space.
+*/
+struct tst *
+tst_init(int width)
+{
+ struct tst *tst;
+
+ tst = xcalloc(1, sizeof(struct tst));
+ tst->node_lines = NULL;
+ tst->node_line_width = width;
+ tst_grow_node_free_list(tst);
+ return tst;
+}
+
+
+/*
+** tst_insert inserts the string key into the tree. Behavior when a
+** duplicate key is inserted is controlled by option. If key is already in
+** the tree then TST_DUPLICATE_KEY is returned, and the data pointer for the
+** existing key is placed in exist_ptr. If option is set to TST_REPLACE then
+** the existing data pointer for the existing key is replaced by data. The
+** old data pointer will still be placed in exist_ptr.
+**
+** If a duplicate key is encountered and option is not set to TST_REPLACE
+** then TST_DUPLICATE_KEY is returned. If key is zero-length, then
+** TST_NULL_KEY is returned. A successful insert or replace returns TST_OK.
+**
+** The data argument may not be NULL; if it is, TST_NULL_DATA is returned.
+** If you just want a simple existence tree, use the tst pointer as the data
+** pointer.
+*/
+int
+tst_insert(struct tst *tst, const unsigned char *key, void *data, int option,
+ void **exist_ptr)
+{
+ struct node *current_node = NULL;
+ struct node **root_node = NULL;
+ int key_index;
+
+ if (data == NULL)
+ return TST_NULL_DATA;
+
+ if (key == NULL || *key == '\0')
+ return TST_NULL_KEY;
+
+ key_index = 1;
+ if (tst->head[*key] == NULL)
+ root_node = &tst->head[*key];
+ else
+ current_node = tst->head[*key];
+
+ while (root_node == NULL) {
+ if (key[key_index] == current_node->value) {
+ if (key[key_index] == '\0') {
+ if (exist_ptr != NULL)
+ *exist_ptr = current_node->middle;
+ if (option == TST_REPLACE) {
+ current_node->middle = data;
+ return TST_OK;
+ } else
+ return TST_DUPLICATE_KEY;
+ }
+ if (current_node->middle == NULL)
+ root_node = ¤t_node->middle;
+ else {
+ current_node = current_node->middle;
+ key_index++;
+ }
+ } else if (LEFTP(current_node, key[key_index])) {
+ if (current_node->left == NULL)
+ root_node = ¤t_node->left;
+ else
+ current_node = current_node->left;
+ } else {
+ if (current_node->right == NULL)
+ root_node = ¤t_node->right;
+ else
+ current_node = current_node->right;
+ }
+
+ }
+
+ *root_node = tst_get_free_node(tst, key[key_index]);
+ current_node = *root_node;
+
+ while (key[key_index] != '\0') {
+ key_index++;
+ current_node->middle = tst_get_free_node(tst, key[key_index]);
+ current_node = current_node->middle;
+ }
+
+ current_node->middle = data;
+ return TST_OK;
+}
+
+
+/*
+** tst_search finds the string key in the tree if it exists and returns the
+** data pointer associated with that key or NULL if it's not found.
+*/
+void *
+tst_search(struct tst *tst, const unsigned char *key)
+{
+ struct node *current_node;
+ int key_index;
+
+ if (key == NULL || *key == '\0')
+ return NULL;
+
+ if (tst->head[*key] == NULL)
+ return NULL;
+
+ current_node = tst->head[*key];
+ key_index = 1;
+ while (current_node != NULL) {
+ if (key[key_index] == current_node->value) {
+ if (current_node->value == '\0')
+ return current_node->middle;
+ else {
+ current_node = current_node->middle;
+ key_index++;
+ continue;
+ }
+ } else if (LEFTP(current_node, key[key_index]))
+ current_node = current_node->left;
+ else
+ current_node = current_node->right;
+ }
+ return NULL;
+}
+
+
+/*
+** tst_delete deletes the string key from the tree if it exists and returns
+** the data pointer assocaited with that key, or NULL if it wasn't found.
+*/
+void *
+tst_delete(struct tst *tst, const unsigned char *key)
+{
+ struct node *current_node;
+ struct node *current_node_parent;
+ struct node *last_branch;
+ struct node *last_branch_parent;
+ struct node *next_node;
+ struct node *last_branch_replacement;
+ struct node *last_branch_dangling_child;
+ int key_index;
+
+ if (key == NULL || *key == '\0')
+ return NULL;
+
+ if (tst->head[*key] == NULL)
+ return NULL;
+
+ last_branch = NULL;
+ last_branch_parent = NULL;
+ current_node = tst->head[*key];
+ current_node_parent = NULL;
+ key_index = 1;
+ while (current_node != NULL) {
+ if (key[key_index] == current_node->value) {
+ if (current_node->left != NULL || current_node->right != NULL) {
+ last_branch = current_node;
+ last_branch_parent = current_node_parent;
+ }
+ if (key[key_index] == '\0')
+ break;
+ else {
+ current_node_parent = current_node;
+ current_node = current_node->middle;
+ key_index++;
+ }
+ } else if (LEFTP(current_node, key[key_index])) {
+ last_branch_parent = current_node;
+ current_node_parent = current_node;
+ current_node = current_node->left;
+ last_branch = current_node;
+ } else {
+ last_branch_parent = current_node;
+ current_node_parent = current_node;
+ current_node = current_node->right;
+ last_branch = current_node;
+ }
+ }
+ if (current_node == NULL)
+ return NULL;
+
+ if (last_branch == NULL) {
+ next_node = tst->head[*key];
+ tst->head[*key] = NULL;
+ } else if (last_branch->left == NULL && last_branch->right == NULL) {
+ if (last_branch_parent->left == last_branch)
+ last_branch_parent->left = NULL;
+ else
+ last_branch_parent->right = NULL;
+ next_node = last_branch;
+ } else {
+ if (last_branch->left != NULL && last_branch->right != NULL) {
+ last_branch_replacement = last_branch->right;
+ last_branch_dangling_child = last_branch->left;
+ } else if (last_branch->right != NULL) {
+ last_branch_replacement = last_branch->right;
+ last_branch_dangling_child = NULL;
+ } else {
+ last_branch_replacement = last_branch->left;
+ last_branch_dangling_child = NULL;
+ }
+
+ if (last_branch_parent == NULL)
+ tst->head[*key] = last_branch_replacement;
+ else {
+ if (last_branch_parent->left == last_branch)
+ last_branch_parent->left = last_branch_replacement;
+ else if (last_branch_parent->right == last_branch)
+ last_branch_parent->right = last_branch_replacement;
+ else
+ last_branch_parent->middle = last_branch_replacement;
+ }
+
+ if (last_branch_dangling_child != NULL) {
+ current_node = last_branch_replacement;
+ while (current_node->left != NULL)
+ current_node = current_node->left;
+ current_node->left = last_branch_dangling_child;
+ }
+
+ next_node = last_branch;
+ }
+
+ do {
+ current_node = next_node;
+ next_node = current_node->middle;
+
+ current_node->left = NULL;
+ current_node->right = NULL;
+ current_node->middle = tst->free_list;
+ tst->free_list = current_node;
+ } while (current_node->value != 0);
+
+ return next_node;
+}
+
+
+/*
+** tst_cleanup frees all memory allocated to nodes, internal structures,
+** as well as tst itself.
+*/
+void
+tst_cleanup(struct tst *tst)
+{
+ struct node_lines *current_line;
+ struct node_lines *next_line;
+
+ next_line = tst->node_lines;
+ do {
+ current_line = next_line;
+ next_line = current_line->next;
+ free(current_line->node_line);
+ free(current_line);
+ } while (next_line != NULL);
+
+ free(tst);
+}
--- /dev/null
+/* $Id: uwildmat.c 6779 2004-05-17 07:25:28Z rra $
+**
+** wildmat pattern matching with Unicode UTF-8 extensions.
+**
+** Do shell-style pattern matching for ?, \, [], and * characters. Might not
+** be robust in face of malformed patterns; e.g., "foo[a-" could cause a
+** segmentation violation. It is 8-bit clean. (Robustness hopefully fixed
+** July 2000; all malformed patterns should now just fail to match anything.)
+**
+** Original by Rich $alz, mirror!rs, Wed Nov 26 19:03:17 EST 1986.
+** Rich $alz is now <rsalz@osf.org>.
+**
+** April, 1991: Replaced mutually-recursive calls with in-line code for the
+** star character.
+**
+** Special thanks to Lars Mathiesen <thorinn@diku.dk> for the ABORT code.
+** This can greatly speed up failing wildcard patterns. For example:
+**
+** pattern: -*-*-*-*-*-*-12-*-*-*-m-*-*-*
+** text 1: -adobe-courier-bold-o-normal--12-120-75-75-m-70-iso8859-1
+** text 2: -adobe-courier-bold-o-normal--12-120-75-75-X-70-iso8859-1
+**
+** Text 1 matches with 51 calls, while text 2 fails with 54 calls. Without
+** the ABORT code, it takes 22310 calls to fail. Ugh. The following
+** explanation is from Lars:
+**
+** The precondition that must be fulfilled is that DoMatch will consume at
+** least one character in text. This is true if *p is neither '*' nor '\0'.)
+** The last return has ABORT instead of false to avoid quadratic behaviour in
+** cases like pattern "*a*b*c*d" with text "abcxxxxx". With false, each
+** star-loop has to run to the end of the text; with ABORT only the last one
+** does.
+**
+** Once the control of one instance of DoMatch enters the star-loop, that
+** instance will return either true or ABORT, and any calling instance will
+** therefore return immediately after (without calling recursively again).
+** In effect, only one star-loop is ever active. It would be possible to
+** modify the code to maintain this context explicitly, eliminating all
+** recursive calls at the cost of some complication and loss of clarity (and
+** the ABORT stuff seems to be unclear enough by itself). I think it would
+** be unwise to try to get this into a released version unless you have a
+** good test data base to try it out on.
+**
+** June, 1991: Robert Elz <kre@munnari.oz.au> added minus and close bracket
+** handling for character sets.
+**
+** July, 2000: Largely rewritten by Russ Allbery <rra@stanford.edu> to add
+** support for ',', '!', and optionally '@' to the core wildmat routine.
+** Broke the character class matching into a separate function for clarity
+** since it's infrequently used in practice, and added some simple lookahead
+** to significantly decrease the recursive calls in the '*' matching code.
+** Added support for UTF-8 as the default character set for any high-bit
+** characters.
+**
+** For more information on UTF-8, see RFC 2279.
+**
+** Please note that this file is intentionally written so that conditionally
+** executed expressions are on separate lines from the condition to
+** facilitate analysis of the coverage of the test suite using purecov.
+** Please preserve this. As of March 11, 2001, purecov reports that the
+** accompanying test suite achieves 100% coverage of this file.
+*/
+
+#include "config.h"
+#include "clibrary.h"
+#include "libinn.h"
+
+#define ABORT -1
+
+/* Whether or not an octet looks like the start of a UTF-8 character. */
+#define ISUTF8(c) (((c) & 0xc0) == 0xc0)
+
+
+/*
+** Determine the length of a non-ASCII character in octets (for advancing
+** pointers when skipping over characters). Takes a pointer to the start of
+** the character and to the last octet of the string. If end is NULL, expect
+** the string pointed to by start to be nul-terminated. If the character is
+** malformed UTF-8, return 1 to treat it like an eight-bit local character.
+*/
+static int
+utf8_length(const unsigned char *start, const unsigned char *end)
+{
+ unsigned char mask = 0x80;
+ const unsigned char *p;
+ int length = 0;
+ int left;
+
+ for (; mask > 0 && (*start & mask) == mask; mask >>= 1)
+ length++;
+ if (length < 2 || length > 6)
+ return 1;
+ if (end != NULL && (end - start + 1) < length)
+ return 1;
+ left = length - 1;
+ p = start + 1;
+ for (p = start + 1; left > 0 && (*p & 0xc0) == 0x80; p++)
+ left--;
+ return (left == 0) ? length : 1;
+}
+
+
+/*
+** Convert a UTF-8 character to UCS-4. Takes a pointer to the start of the
+** character and to the last octet of the string, and to a uint32_t into
+** which to put the decoded UCS-4 value. If end is NULL, expect the string
+** pointed to by start to be nul-terminated. Returns the number of octets in
+** the UTF-8 encoding. If the UTF-8 character is malformed, set result to
+** the decimal value of the first octet; this is wrong, but it will generally
+** cause the rest of the wildmat matching to do the right thing for non-UTF-8
+** input.
+*/
+static int
+utf8_decode(const unsigned char *start, const unsigned char *end,
+ uint32_t *result)
+{
+ uint32_t value = 0;
+ int length, i;
+ const unsigned char *p = start;
+ unsigned char mask;
+
+ length = utf8_length(start, end);
+ if (length < 2) {
+ *result = *start;
+ return 1;
+ }
+ mask = (1 << (7 - length)) - 1;
+ value = *p & mask;
+ p++;
+ for (i = length - 1; i > 0; i--) {
+ value = (value << 6) | (*p & 0x3f);
+ p++;
+ }
+ *result = value;
+ return length;
+}
+
+
+/*
+** Match a character class against text, a UCS-4 character. start is a
+** pointer to the first character of the character class, end a pointer to
+** the last. Returns whether the class matches that character.
+*/
+static bool
+match_class(uint32_t text, const unsigned char *start,
+ const unsigned char *end)
+{
+ bool reversed, allowrange;
+ const unsigned char *p = start;
+ uint32_t first, last;
+
+ /* Check for an inverted character class (starting with ^). If the
+ character matches the character class, we return !reversed; that way,
+ we return true if it's a regular character class and false if it's a
+ reversed one. If the character doesn't match, we return reversed. */
+ reversed = (*p == '^');
+ if (reversed)
+ p++;
+
+ /* Walk through the character class until we reach the end or find a
+ match, handling character ranges as we go. Only permit a range to
+ start when allowrange is true; this allows - to be treated like a
+ normal character as the first character of the class and catches
+ malformed ranges like a-e-n. We treat the character at the beginning
+ of a range as both a regular member of the class and the beginning of
+ the range; this is harmless (although it means that malformed ranges
+ like m-a will match m and nothing else). */
+ allowrange = false;
+ while (p <= end) {
+ if (allowrange && *p == '-' && p < end) {
+ p++;
+ p += utf8_decode(p, end, &last);
+ if (text >= first && text <= last)
+ return !reversed;
+ allowrange = false;
+ } else {
+ p += utf8_decode(p, end, &first);
+ if (text == first)
+ return !reversed;
+ allowrange = true;
+ }
+ }
+ return reversed;
+}
+
+
+/*
+** Match the text against the pattern between start and end. This is a
+** single pattern; a leading ! or @ must already be taken care of, and
+** commas must be dealt with outside of this routine.
+*/
+static int
+match_pattern(const unsigned char *text, const unsigned char *start,
+ const unsigned char *end)
+{
+ const unsigned char *q, *endclass;
+ const unsigned char *p = start;
+ bool ismeta;
+ int matched, width;
+ uint32_t c;
+
+ for (; p <= end; p++) {
+ if (!*text && *p != '*')
+ return ABORT;
+
+ switch (*p) {
+ case '\\':
+ if (!*++p)
+ return ABORT;
+ /* Fall through. */
+
+ default:
+ if (*text++ != *p)
+ return false;
+ break;
+
+ case '?':
+ text += ISUTF8(*text) ? utf8_length(text, NULL) : 1;
+ break;
+
+ case '*':
+ /* Consecutive stars are equivalent to one. Advance pattern to
+ the character after the star. */
+ for (++p; *p == '*'; p++)
+ ;
+
+ /* A trailing star will match anything. */
+ if (p > end)
+ return true;
+
+ /* Basic algorithm: Recurse at each point where the * could
+ possibly match. If the match succeeds or aborts, return
+ immediately; otherwise, try the next position.
+
+ Optimization: If the character after the * in the pattern
+ isn't a metacharacter (the common case), then the * has to
+ consume characters at least up to the next occurance of that
+ character in the text. Scan forward for those points rather
+ than recursing at every possible point to save the extra
+ function call overhead. */
+ ismeta = (*p == '[' || *p == '?' || *p == '\\');
+ while (*text) {
+ width = ISUTF8(*text) ? utf8_length(text, NULL) : 1;
+ if (ismeta) {
+ matched = match_pattern(text, p, end);
+ text += width;
+ } else {
+ while (*text && *text != *p) {
+ text += width;
+ width = ISUTF8(*text) ? utf8_length(text, NULL) : 1;
+ }
+ if (!*text)
+ return ABORT;
+ matched = match_pattern(++text, p + 1, end);
+ }
+ if (matched != false)
+ return matched;
+ }
+ return ABORT;
+
+ case '[':
+ /* Find the end of the character class, making sure not to pick
+ up a close bracket at the beginning of the class. */
+ p++;
+ q = p + (*p == '^') + 1;
+ if (q > end)
+ return ABORT;
+ endclass = memchr(q, ']', (size_t) (end - q + 1));
+ if (!endclass)
+ return ABORT;
+
+ /* Do the heavy lifting in another function for clarity, since
+ character classes are an uncommon case. */
+ text += utf8_decode(text, NULL, &c);
+ if (!match_class(c, p, endclass - 1))
+ return false;
+ p = endclass;
+ break;
+ }
+ }
+
+ return (*text == '\0');
+}
+
+
+/*
+** Takes text and a wildmat expression; a wildmat expression is a
+** comma-separated list of wildmat patterns, optionally preceeded by ! to
+** invert the sense of the expression. Returns WILDMAT_MATCH if that
+** expression matches the text, WILDMAT_FAIL otherwise. If allowpoison is
+** set, allow @ to introduce a poison expression (the same as !, but if it
+** triggers the failed match the routine returns WILDMAT_POISON instead).
+*/
+static enum uwildmat
+match_expression(const unsigned char *text, const unsigned char *start,
+ bool allowpoison)
+{
+ const unsigned char *end, *split;
+ const unsigned char *p = start;
+ bool reverse, escaped;
+ bool match = false;
+ bool poison = false;
+ bool poisoned = false;
+
+ /* Handle the empty expression separately, since otherwise end will be
+ set to an invalid pointer. */
+ if (!*p)
+ return !*text ? UWILDMAT_MATCH : UWILDMAT_FAIL;
+ end = start + strlen((const char *) start) - 1;
+
+ /* Main match loop. Find each comma that separates patterns, and attempt
+ to match the text with each pattern in order. The last matching
+ pattern determines whether the whole expression matches. */
+ for (; p <= end + 1; p = split + 1) {
+ if (allowpoison)
+ poison = (*p == '@');
+ reverse = (*p == '!') || poison;
+ if (reverse)
+ p++;
+
+ /* Find the first unescaped comma, if any. If there is none, split
+ will be one greater than end and point at the nul at the end of
+ the string. */
+ for (escaped = false, split = p; split <= end; split++) {
+ if (*split == '[') {
+ split++;
+ if (*split == ']')
+ split++;
+ while (split <= end && *split != ']')
+ split++;
+ }
+ if (*split == ',' && !escaped)
+ break;
+ escaped = (*split == '\\') ? !escaped : false;
+ }
+
+ /* Optimization: If match == !reverse and poison == poisoned, this
+ pattern can't change the result, so don't do any work. */
+ if (match == !reverse && poison == poisoned)
+ continue;
+ if (match_pattern(text, p, split - 1) == true) {
+ poisoned = poison;
+ match = !reverse;
+ }
+ }
+ if (poisoned)
+ return UWILDMAT_POISON;
+ return match ? UWILDMAT_MATCH : UWILDMAT_FAIL;
+}
+
+
+/*
+** User-level routine used for wildmats where @ should be treated as a
+** regular character.
+*/
+bool
+uwildmat(const char *text, const char *pat)
+{
+ const unsigned char *utext = (const unsigned char *) text;
+ const unsigned char *upat = (const unsigned char *) pat;
+
+ if (upat[0] == '*' && upat[1] == '\0')
+ return true;
+ else
+ return (match_expression(utext, upat, false) == UWILDMAT_MATCH);
+}
+
+
+/*
+** User-level routine used for wildmats that support poison matches.
+*/
+enum uwildmat
+uwildmat_poison(const char *text, const char *pat)
+{
+ const unsigned char *utext = (const unsigned char *) text;
+ const unsigned char *upat = (const unsigned char *) pat;
+
+ if (upat[0] == '*' && upat[1] == '\0')
+ return UWILDMAT_MATCH;
+ else
+ return match_expression(utext, upat, true);
+}
+
+
+/*
+** User-level routine for simple expressions (neither , nor ! are special).
+*/
+bool
+uwildmat_simple(const char *text, const char *pat)
+{
+ const unsigned char *utext = (const unsigned char *) text;
+ const unsigned char *upat = (const unsigned char *) pat;
+ size_t length;
+
+ if (upat[0] == '*' && upat[1] == '\0')
+ return true;
+ else {
+ length = strlen(pat);
+ return (match_pattern(utext, upat, upat + length - 1) == true);
+ }
+}
--- /dev/null
+/* $Id: vector.c 6699 2004-04-07 06:47:44Z rra $
+**
+** Vector handling (counted lists of char *'s).
+**
+** Written by Russ Allbery <rra@stanford.edu>
+** This work is hereby placed in the public domain by its author.
+**
+** A vector is a table for handling a list of strings with less overhead than
+** linked list. The intention is for vectors, once allocated, to be reused;
+** this saves on memory allocations once the array of char *'s reaches a
+** stable size.
+**
+** There are two types of vectors. Standard vectors copy strings when
+** they're inserted into the vector, whereas cvectors just accept pointers
+** to external strings to store. There are therefore two entry points for
+** every vector function, one for vectors and one for cvectors.
+**
+** There's a whole bunch of code duplication here. This would be a lot
+** cleaner with C++ features (either inheritance or templates would
+** probably help). One could probably in some places just cast a cvector
+** to a vector and perform the same operations, but I'm leery of doing that
+** as I'm not sure if it's a violation of the C type aliasing rules.
+*/
+
+#include "config.h"
+#include "clibrary.h"
+#include <ctype.h>
+
+#include "inn/vector.h"
+#include "libinn.h"
+
+/*
+** Allocate a new, empty vector.
+*/
+struct vector *
+vector_new(void)
+{
+ struct vector *vector;
+
+ vector = xmalloc(sizeof(struct vector));
+ vector->count = 0;
+ vector->allocated = 0;
+ vector->strings = NULL;
+ return vector;
+}
+
+struct cvector *
+cvector_new(void)
+{
+ struct cvector *vector;
+
+ vector = xmalloc(sizeof(struct cvector));
+ vector->count = 0;
+ vector->allocated = 0;
+ vector->strings = NULL;
+ return vector;
+}
+
+
+/*
+** Resize a vector (using realloc to resize the table).
+*/
+void
+vector_resize(struct vector *vector, size_t size)
+{
+ size_t i;
+
+ if (vector->count > size) {
+ for (i = size; i < vector->count; i++)
+ free(vector->strings[i]);
+ vector->count = size;
+ }
+ if (size == 0) {
+ free(vector->strings);
+ vector->strings = NULL;
+ } else {
+ vector->strings = xrealloc(vector->strings, size * sizeof(char *));
+ }
+ vector->allocated = size;
+}
+
+void
+cvector_resize(struct cvector *vector, size_t size)
+{
+ if (vector->count > size)
+ vector->count = size;
+ if (size == 0) {
+ free(vector->strings);
+ vector->strings = NULL;
+ } else {
+ vector->strings =
+ xrealloc(vector->strings, size * sizeof(const char *));
+ }
+ vector->allocated = size;
+}
+
+
+/*
+** Add a new string to the vector, resizing the vector as necessary. The
+** vector is resized an element at a time; if a lot of resizes are expected,
+** vector_resize should be called explicitly with a more suitable size.
+*/
+void
+vector_add(struct vector *vector, const char *string)
+{
+ size_t next = vector->count;
+
+ if (vector->count == vector->allocated)
+ vector_resize(vector, vector->allocated + 1);
+ vector->strings[next] = xstrdup(string);
+ vector->count++;
+}
+
+void
+cvector_add(struct cvector *vector, const char *string)
+{
+ size_t next = vector->count;
+
+ if (vector->count == vector->allocated)
+ cvector_resize(vector, vector->allocated + 1);
+ vector->strings[next] = string;
+ vector->count++;
+}
+
+
+/*
+** Empty a vector but keep the allocated memory for the pointer table.
+*/
+void
+vector_clear(struct vector *vector)
+{
+ size_t i;
+
+ for (i = 0; i < vector->count; i++)
+ free(vector->strings[i]);
+ vector->count = 0;
+}
+
+void
+cvector_clear(struct cvector *vector)
+{
+ vector->count = 0;
+}
+
+
+/*
+** Free a vector completely.
+*/
+void
+vector_free(struct vector *vector)
+{
+ vector_clear(vector);
+ free(vector->strings);
+ free(vector);
+}
+
+void
+cvector_free(struct cvector *vector)
+{
+ cvector_clear(vector);
+ free(vector->strings);
+ free(vector);
+}
+
+
+/*
+** Given a vector that we may be reusing, clear it out. If the first
+** argument is NULL, allocate a new vector. Used by vector_split*.
+*/
+static struct vector *
+vector_reuse(struct vector *vector)
+{
+ if (vector == NULL)
+ return vector_new();
+ else {
+ vector_clear(vector);
+ return vector;
+ }
+}
+
+static struct cvector *
+cvector_reuse(struct cvector *vector)
+{
+ if (vector == NULL)
+ return cvector_new();
+ else {
+ cvector_clear(vector);
+ return vector;
+ }
+}
+
+
+/*
+** Given a string and a separator character, count the number of strings
+** that it will split into.
+*/
+static size_t
+split_count(const char *string, char separator)
+{
+ const char *p;
+ size_t count;
+
+ if (*string == '\0')
+ return 1;
+ for (count = 1, p = string; *p; p++)
+ if (*p == separator)
+ count++;
+ return count;
+}
+
+
+/*
+** Given a string and a separator character, form a vector by splitting the
+** string at those separators. Do a first pass to size the vector, and if
+** the third argument isn't NULL, reuse it. Otherwise, allocate a new one.
+*/
+struct vector *
+vector_split(const char *string, char separator, struct vector *vector)
+{
+ const char *p, *start;
+ size_t i, count;
+
+ vector = vector_reuse(vector);
+
+ count = split_count(string, separator);
+ if (vector->allocated < count)
+ vector_resize(vector, count);
+
+ for (start = string, p = string, i = 0; *p; p++)
+ if (*p == separator) {
+ vector->strings[i++] = xstrndup(start, p - start);
+ start = p + 1;
+ }
+ vector->strings[i++] = xstrndup(start, p - start);
+ vector->count = i;
+
+ return vector;
+}
+
+
+/*
+** Given a modifiable string and a separator character, form a cvector by
+** modifying the string in-place to add nuls at the separators and then
+** building a vector of pointers into the string. Do a first pass to size
+** the vector, and if the third argument isn't NULL, reuse it. Otherwise,
+** allocate a new one.
+*/
+struct cvector *
+cvector_split(char *string, char separator, struct cvector *vector)
+{
+ char *p, *start;
+ size_t i, count;
+
+ vector = cvector_reuse(vector);
+
+ count = split_count(string, separator);
+ if (vector->allocated < count)
+ cvector_resize(vector, count);
+
+ for (start = string, p = string, i = 0; *p; p++)
+ if (*p == separator) {
+ *p = '\0';
+ vector->strings[i++] = start;
+ start = p + 1;
+ }
+ vector->strings[i++] = start;
+ vector->count = i;
+
+ return vector;
+}
+
+
+/*
+** Given a string, count the number of strings that it will split into when
+** splitting on whitespace.
+*/
+static size_t
+split_space_count(const char *string)
+{
+ const char *p;
+ size_t count;
+
+ if (*string == '\0')
+ return 0;
+ for (count = 1, p = string + 1; *p != '\0'; p++)
+ if ((*p == ' ' || *p == '\t') && !(p[-1] == ' ' || p[-1] == '\t'))
+ count++;
+
+ /* If the string ends in whitespace, we've overestimated the number of
+ strings by one. */
+ if (p[-1] == ' ' || p[-1] == '\t')
+ count--;
+ return count;
+}
+
+
+/*
+** Given a string, split it at whitespace to form a vector, copying each
+** string segment. If the fourth argument isn't NULL, reuse that vector;
+** otherwise, allocate a new one. Any number of consecutive whitespace
+** characters is considered a single separator.
+*/
+struct vector *
+vector_split_space(const char *string, struct vector *vector)
+{
+ const char *p, *start;
+ size_t i, count;
+
+ vector = vector_reuse(vector);
+
+ count = split_space_count(string);
+ if (vector->allocated < count)
+ vector_resize(vector, count);
+
+ for (start = string, p = string, i = 0; *p; p++)
+ if (*p == ' ' || *p == '\t') {
+ if (start != p)
+ vector->strings[i++] = xstrndup(start, p - start);
+ start = p + 1;
+ }
+ if (start != p)
+ vector->strings[i++] = xstrndup(start, p - start);
+ vector->count = i;
+
+ return vector;
+}
+
+
+/*
+** Given a string, split it at whitespace to form a vector, destructively
+** modifying the string to nul-terminate each segment. If the fourth
+** argument isn't NULL, reuse that vector; otherwise, allocate a new one.
+** Any number of consecutive whitespace characters is considered a single
+** separator.
+*/
+struct cvector *
+cvector_split_space(char *string, struct cvector *vector)
+{
+ char *p, *start;
+ size_t i, count;
+
+ vector = cvector_reuse(vector);
+
+ count = split_space_count(string);
+ if (vector->allocated < count)
+ cvector_resize(vector, count);
+
+ for (start = string, p = string, i = 0; *p; p++)
+ if (*p == ' ' || *p == '\t') {
+ if (start != p) {
+ *p = '\0';
+ vector->strings[i++] = start;
+ }
+ start = p + 1;
+ }
+ if (start != p)
+ vector->strings[i++] = start;
+ vector->count = i;
+
+ return vector;
+}
+
+
+/*
+** Given a vector and a separator string, allocate and build a new string
+** composed of all the strings in the vector separated from each other by the
+** seperator string. Caller is responsible for freeing.
+*/
+char *
+vector_join(const struct vector *vector, const char *seperator)
+{
+ char *string;
+ size_t i, size, seplen;
+
+ seplen = strlen(seperator);
+ for (size = 0, i = 0; i < vector->count; i++)
+ size += strlen(vector->strings[i]);
+ size += (vector->count - 1) * seplen + 1;
+
+ string = xmalloc(size);
+ strlcpy(string, vector->strings[0], size);
+ for (i = 1; i < vector->count; i++) {
+ strlcat(string, seperator, size);
+ strlcat(string, vector->strings[i], size);
+ }
+
+ return string;
+}
+
+char *
+cvector_join(const struct cvector *vector, const char *seperator)
+{
+ char *string;
+ size_t i, size, seplen;
+
+ seplen = strlen(seperator);
+ for (size = 0, i = 0; i < vector->count; i++)
+ size += strlen(vector->strings[i]);
+ size += (vector->count - 1) * seplen + 1;
+
+ string = xmalloc(size);
+ strlcpy(string, vector->strings[0], size);
+ for (i = 1; i < vector->count; i++) {
+ strlcat(string, seperator, size);
+ strlcat(string, vector->strings[i], size);
+ }
+
+ return string;
+}
--- /dev/null
+/* $Id: version.c 3989 2000-10-01 01:59:45Z rra $
+**
+** INN compile-time version information.
+*/
+
+#include "config.h"
+#include "inn/version.h"
+
+const int inn_version[3] = {
+ INN_VERSION_MAJOR, INN_VERSION_MINOR, INN_VERSION_PATCH
+};
+const char inn_version_extra[] = INN_VERSION_EXTRA;
+const char inn_version_string[] = INN_VERSION_STRING;
--- /dev/null
+/* $Id: wire.c 7258 2005-06-06 03:14:45Z eagle $
+**
+** Wire format article utilities.
+**
+** Originally written by Alex Kiernan (alex.kiernan@thus.net)
+**
+** These routines manipulate wire format articles; in particular, they should
+** be safe in the presence of embedded NULs. They assume wire format
+** conventions (\r\n as a line ending, in particular) and will not work with
+** articles in native format.
+**
+** The functions in this file take const char * pointers and return char *
+** pointers so that they can work on both const char * and char * article
+** bodies without changing the const sense. This unfortunately means that
+** the routines in this file will produce warnings about const being cast
+** away. To avoid those, one would need to duplicate all the code in this
+** file or use C++.
+*/
+
+#include "config.h"
+#include "clibrary.h"
+#include <assert.h>
+
+#include "inn/wire.h"
+#include "libinn.h"
+
+/*
+** Given a pointer to the start of an article, locate the first octet of the
+** body (which may be the octet beyond the end of the buffer if your article
+** is bodiless).
+*/
+char *
+wire_findbody(const char *article, size_t length)
+{
+ char *p;
+ const char *end;
+
+ /* Handle the degenerate case of an article with no headers. */
+ if (length > 5 && article[0] == '\r' && article[1] == '\n')
+ return (char *) article + 2;
+
+ /* Jump from \r to \r and give up if we're too close to the end. */
+ end = article + length;
+ for (p = (char *) article; (p + 4) <= end; ++p) {
+ p = memchr(p, '\r', end - p - 3);
+ if (p == NULL)
+ break;
+ if (memcmp(p, "\r\n\r\n", 4) == 0) {
+ p += 4;
+ return p;
+ }
+ }
+ return NULL;
+}
+
+
+/*
+** Given a pointer into an article and a pointer to the last octet of the
+** article, find the next line ending and return a pointer to the first
+** character after that line ending. If no line ending is found in the
+** article or if it is at the end of the article, return NULL.
+*/
+char *
+wire_nextline(const char *article, const char *end)
+{
+ char *p;
+
+ for (p = (char *) article; (p + 2) <= end; ++p) {
+ p = memchr(p, '\r', end - p - 2);
+ if (p == NULL)
+ break;
+ if (p[1] == '\n') {
+ p += 2;
+ return p;
+ }
+ }
+ return NULL;
+}
+
+
+/*
+** Returns true if line is the beginning of a valid header for header, also
+** taking the length of the header name as a third argument. Assumes that
+** there is at least length + 2 bytes of data at line, and that the header
+** name doesn't contain nul.
+*/
+static bool
+isheader(const char *line, const char *header, size_t length)
+{
+ if (line[length] != ':' || !ISWHITE(line[length + 1]))
+ return false;
+ return strncasecmp(line, header, length) == 0;
+}
+
+
+/*
+** Skip over folding whitespace, as defined by RFC 2822. Takes a pointer to
+** where to start skipping and a pointer to the end of the data, and will not
+** return a pointer past the end pointer. If skipping folding whitespace
+** takes us past the end of data, return NULL.
+*/
+static char *
+skip_fws(char *text, const char *end)
+{
+ char *p;
+
+ for (p = text; p <= end; p++) {
+ if (p < end + 1 && p[0] == '\r' && p[1] == '\n' && ISWHITE(p[2]))
+ p += 2;
+ if (!ISWHITE(*p))
+ return p;
+ }
+ return NULL;
+}
+
+
+/*
+** Given a pointer to the start of the article, the article length, and the
+** header to look for, find the first occurance of that header in the
+** article. Skip over headers with no content, but allow for headers that
+** are folded before the first text in the header. If no matching headers
+** with content other than spaces and tabs are found, return NULL.
+*/
+char *
+wire_findheader(const char *article, size_t length, const char *header)
+{
+ char *p;
+ const char *end;
+ ptrdiff_t headerlen;
+
+ headerlen = strlen(header);
+ end = article + length - 1;
+
+ /* There has to be enough space left in the article for at least the
+ header, the colon, whitespace, and one non-whitespace character, hence
+ 3, minus 1 since the character pointed to by end is part of the
+ article. */
+ p = (char *) article;
+ while (p != NULL && end - p > headerlen + 2) {
+ if (p[0] == '\r' && p[1] == '\n')
+ return NULL;
+ else if (isheader(p, header, headerlen)) {
+ p = skip_fws(p + headerlen + 2, end);
+ if (p == NULL)
+ return NULL;
+ if (p >= end || p[0] != '\r' || p[1] != '\n')
+ return p;
+ }
+ p = wire_nextline(p, end);
+ }
+ return NULL;
+}
+
+
+/*
+** Given a pointer to a header and a pointer to the last octet of the
+** article, find the end of the header (a pointer to the final \n of the
+** header value). If the header contents don't end in \r\n, return NULL.
+*/
+char *
+wire_endheader(const char *header, const char *end)
+{
+ char *p;
+
+ p = wire_nextline(header, end);
+ while (p != NULL) {
+ if (!ISWHITE(*p))
+ return p - 1;
+ p = wire_nextline(p, end);
+ }
+ if (end - header >= 1 && *end == '\n' && *(end - 1) == '\r')
+ return (char *) end;
+ return NULL;
+}
--- /dev/null
+/* $Id: xfopena.c 5381 2002-03-31 22:35:47Z rra $
+**
+*/
+
+#include "config.h"
+#include "clibrary.h"
+#include <fcntl.h>
+
+#include "libinn.h"
+
+/*
+** Open a file in append mode. Since not all fopen's set the O_APPEND
+** flag, we do it by hand.
+*/
+FILE *xfopena(const char *p)
+{
+ int fd;
+
+ /* We can't trust stdio to really use O_APPEND, so open, then fdopen. */
+ fd = open(p, O_WRONLY | O_APPEND | O_CREAT, 0666);
+ return fd >= 0 ? fdopen(fd, "a") : NULL;
+}
--- /dev/null
+/* $Id: xmalloc.c 5381 2002-03-31 22:35:47Z rra $
+**
+** malloc routines with failure handling.
+**
+** Usage:
+**
+** extern xmalloc_handler_t memory_error;
+** extern const char *string;
+** char *buffer;
+**
+** xmalloc_error_handler = memory_error;
+** buffer = xmalloc(1024);
+** xrealloc(buffer, 2048);
+** free(buffer);
+** buffer = xcalloc(1024);
+** free(buffer);
+** buffer = xstrdup(string);
+** free(buffer);
+** buffer = xstrndup(string, 25);
+**
+** xmalloc, xcalloc, xrealloc, and xstrdup behave exactly like their C
+** library counterparts without the leading x except that they will never
+** return NULL. Instead, on error, they call xmalloc_error_handler,
+** passing it the name of the function whose memory allocation failed, the
+** amount of the allocation, and the file and line number where the
+** allocation function was invoked (from __FILE__ and __LINE__). This
+** function may do whatever it wishes, such as some action to free up
+** memory or a call to sleep to hope that system resources return. If the
+** handler returns, the interrupted memory allocation function will try its
+** allocation again (calling the handler again if it still fails).
+**
+** xstrndup behaves like xstrdup but only copies the given number of
+** characters. It allocates an additional byte over its second argument and
+** always nul-terminates the string.
+**
+** The default error handler, if none is set by the caller, prints an error
+** message to stderr and exits with exit status 1. An error handler must
+** take a const char * (function name), size_t (bytes allocated), const
+** char * (file), and int (line).
+**
+** xmalloc will return a pointer to a valid memory region on an xmalloc of 0
+** bytes, ensuring this by allocating space for one character instead of 0
+** bytes.
+**
+** The functions defined here are actually x_malloc, x_realloc, etc. The
+** header file defines macros named xmalloc, etc. that pass the file name
+** and line number to these functions.
+*/
+
+#include "config.h"
+#include "clibrary.h"
+
+#include "inn/messages.h"
+#include "libinn.h"
+
+/* The default error handler. */
+void
+xmalloc_fail(const char *function, size_t size, const char *file, int line)
+{
+ sysdie("failed to %s %lu bytes at %s line %d", function,
+ (unsigned long) size, file, line);
+}
+
+/* Assign to this variable to choose a handler other than the default. */
+xmalloc_handler_t xmalloc_error_handler = xmalloc_fail;
+
+void *
+x_malloc(size_t size, const char *file, int line)
+{
+ void *p;
+ size_t real_size;
+
+ real_size = (size > 0) ? size : 1;
+ p = malloc(real_size);
+ while (p == NULL) {
+ (*xmalloc_error_handler)("malloc", size, file, line);
+ p = malloc(real_size);
+ }
+ return p;
+}
+
+void *
+x_calloc(size_t n, size_t size, const char *file, int line)
+{
+ void *p;
+
+ n = (n > 0) ? n : 1;
+ size = (size > 0) ? size : 1;
+ p = calloc(n, size);
+ while (p == NULL) {
+ (*xmalloc_error_handler)("calloc", n * size, file, line);
+ p = calloc(n, size);
+ }
+ return p;
+}
+
+void *
+x_realloc(void *p, size_t size, const char *file, int line)
+{
+ void *newp;
+
+ newp = realloc(p, size);
+ while (newp == NULL && size > 0) {
+ (*xmalloc_error_handler)("realloc", size, file, line);
+ newp = realloc(p, size);
+ }
+ return newp;
+}
+
+char *
+x_strdup(const char *s, const char *file, int line)
+{
+ char *p;
+ size_t len;
+
+ len = strlen(s) + 1;
+ p = malloc(len);
+ while (p == NULL) {
+ (*xmalloc_error_handler)("strdup", len, file, line);
+ p = malloc(len);
+ }
+ memcpy(p, s, len);
+ return p;
+}
+
+char *
+x_strndup(const char *s, size_t size, const char *file, int line)
+{
+ char *p;
+
+ p = malloc(size + 1);
+ while (p == NULL) {
+ (*xmalloc_error_handler)("strndup", size + 1, file, line);
+ p = malloc(size + 1);
+ }
+ memcpy(p, s, size);
+ p[size] = '\0';
+ return p;
+}
--- /dev/null
+/* $Id: xsignal.c 5610 2002-08-18 22:22:52Z rra $
+**
+** A reliable implementation of signal for System V systems.
+**
+** Two functions are provided, xsignal and xsignal_norestart. The former
+** attempts to set system calls to be restarted and the latter does not.
+**
+** Be aware that there's weird declaration stuff going on here; a signal
+** handler is a pointer to a function taking an int and returning void.
+** We typedef this as sig_handler_type for clearer code.
+*/
+
+#include "config.h"
+#include "libinn.h"
+#include <signal.h>
+
+typedef void (*sig_handler_type)(int);
+
+#ifdef HAVE_SIGACTION
+
+sig_handler_type
+xsignal(int signum, sig_handler_type sigfunc)
+{
+ struct sigaction act, oact;
+
+ act.sa_handler = sigfunc;
+ sigemptyset(&act.sa_mask);
+
+ /* Try to restart system calls if possible. */
+#ifdef SA_RESTART
+ act.sa_flags = SA_RESTART;
+#else
+ act.sa_flags = 0;
+#endif
+
+ if (sigaction(signum, &act, &oact) < 0)
+ return SIG_ERR;
+ return oact.sa_handler;
+}
+
+sig_handler_type
+xsignal_norestart(int signum, sig_handler_type sigfunc)
+{
+ struct sigaction act, oact;
+
+ act.sa_handler = sigfunc;
+ sigemptyset(&act.sa_mask);
+
+ /* Try not to restart system calls. */
+#ifdef SA_INTERRUPT
+ act.sa_flags = SA_INTERRUPT;
+#else
+ act.sa_flags = 0;
+#endif
+
+ if (sigaction(signum, &act, &oact) < 0)
+ return SIG_ERR;
+ return oact.sa_handler;
+}
+
+#else /* !HAVE_SIGACTION */
+
+sig_handler_type
+xsignal(int signum, sig_handler_type sigfunc)
+{
+ return signal(signum, sigfunc);
+}
+
+sig_handler_type
+xsignal_norestart(int signum, sig_handler_type sigfunc)
+{
+ return signal(signum, sigfunc);
+}
+
+#endif /* !HAVE_SIGACTION */
--- /dev/null
+/* $Id: xwrite.c 5771 2002-09-17 17:00:05Z alexk $
+**
+** write and writev replacements to handle partial writes.
+**
+** Usage:
+**
+** ssize_t xwrite(int fildes, const void *buf, size_t nbyte);
+** ssize_t xpwrite(int fildes, const void *buf, size_t nbyte,
+** off_t offset);
+** ssize_t xwritev(int fildes, const struct iovec *iov, int iovcnt);
+**
+** xwrite, xpwrite, and xwritev behave exactly like their C library
+** counterparts except that, if write or writev succeeds but returns a number
+** of bytes written less than the total bytes, the write is repeated picking
+** up where it left off until the full amount of the data is written. The
+** write is also repeated if it failed with EINTR. The write will be aborted
+** after 10 successive writes with no forward progress.
+**
+** Both functions return the number of bytes written on success or -1 on an
+** error, and will leave errno set to whatever the underlying system call
+** set it to. Note that it is possible for a write to fail after some data
+** was written, on the subsequent additional write; in that case, these
+** functions will return -1 and the number of bytes actually written will
+** be lost.
+*/
+
+#include "config.h"
+#include "clibrary.h"
+#include <errno.h>
+#include <sys/uio.h>
+
+#include "libinn.h"
+
+/* If we're running the test suite, call testing versions of the write
+ functions. #undef pwrite first because large file support may define a
+ macro pwrite (pointing to pwrite64) on some platforms (e.g. Solaris). */
+#if TESTING
+# undef pwrite
+# define pwrite fake_pwrite
+# define write fake_write
+# define writev fake_writev
+ssize_t fake_pwrite(int, const void *, size_t, off_t);
+ssize_t fake_write(int, const void *, size_t);
+ssize_t fake_writev(int, const struct iovec *, int);
+#endif
+
+ssize_t
+xwrite(int fd, const void *buffer, size_t size)
+{
+ size_t total;
+ ssize_t status;
+ int count = 0;
+
+ if (size == 0)
+ return 0;
+
+ /* Abort the write if we try ten times with no forward progress. */
+ for (total = 0; total < size; total += status) {
+ if (++count > 10)
+ break;
+ status = write(fd, (const char *) buffer + total, size - total);
+ if (status > 0)
+ count = 0;
+ if (status < 0) {
+ if (errno != EINTR)
+ break;
+ status = 0;
+ }
+ }
+ return (total < size) ? -1 : (ssize_t) total;
+}
+
+ssize_t
+xpwrite(int fd, const void *buffer, size_t size, off_t offset)
+{
+ size_t total;
+ ssize_t status;
+ int count = 0;
+
+ if (size == 0)
+ return 0;
+
+ /* Abort the write if we try ten times with no forward progress. */
+ for (total = 0; total < size; total += status) {
+ if (++count > 10)
+ break;
+ status = pwrite(fd, (const char *) buffer + total, size - total,
+ offset + total);
+ if (status > 0)
+ count = 0;
+ if (status < 0) {
+ if (errno != EINTR)
+ break;
+ status = 0;
+ }
+ }
+ return (total < size) ? -1 : (ssize_t) total;
+}
+
+ssize_t
+xwritev(int fd, const struct iovec iov[], int iovcnt)
+{
+ ssize_t total, status = 0;
+ size_t left, offset;
+ int iovleft, i, count;
+ struct iovec *tmpiov;
+
+ if (iovcnt == 0)
+ return 0;
+
+ /* Get a count of the total number of bytes in the iov array. */
+ for (total = 0, i = 0; i < iovcnt; i++)
+ total += iov[i].iov_len;
+
+ if (total == 0)
+ return 0;
+
+ /* First, try just writing it all out. Most of the time this will
+ succeed and save us lots of work. Abort the write if we try ten times
+ with no forward progress. */
+ count = 0;
+ do {
+ if (++count > 10)
+ break;
+ status = writev(fd, iov, iovcnt);
+ if (status > 0)
+ count = 0;
+ } while (status < 0 && errno == EINTR);
+ if (status < 0)
+ return -1;
+ if (status == total)
+ return total;
+
+ /* If we fell through to here, the first write partially succeeded.
+ Figure out how far through the iov array we got, and then duplicate
+ the rest of it so that we can modify it to reflect how much we manage
+ to write on successive tries. */
+ offset = status;
+ left = total - offset;
+ for (i = 0; offset >= (size_t) iov[i].iov_len; i++)
+ offset -= iov[i].iov_len;
+ iovleft = iovcnt - i;
+ tmpiov = xmalloc(iovleft * sizeof(struct iovec));
+ memcpy(tmpiov, iov + i, iovleft * sizeof(struct iovec));
+
+ /* status now contains the offset into the first iovec struct in tmpiov.
+ Go into the write loop, trying to write out everything remaining at
+ each point. At the top of the loop, status will contain a count of
+ bytes written out at the beginning of the set of iovec structs. */
+ i = 0;
+ do {
+ if (++count > 10)
+ break;
+
+ /* Skip any leading data that has been written out. */
+ for (; offset >= (size_t) tmpiov[i].iov_len && iovleft > 0; i++) {
+ offset -= tmpiov[i].iov_len;
+ iovleft--;
+ }
+ tmpiov[i].iov_base = (char *) tmpiov[i].iov_base + offset;
+ tmpiov[i].iov_len -= offset;
+
+ /* Write out what's left and return success if it's all written. */
+ status = writev(fd, tmpiov + i, iovleft);
+ if (status <= 0)
+ offset = 0;
+ else {
+ offset = status;
+ left -= offset;
+ count = 0;
+ }
+ } while (left > 0 && (status >= 0 || errno == EINTR));
+
+ /* We're either done or got an error; if we're done, left is now 0. */
+ free(tmpiov);
+ return (left == 0) ? total : -1;
+}
--- /dev/null
+## $Id: Makefile 7727 2008-04-06 07:59:46Z iulius $
+
+include ../Makefile.global
+
+top = ..
+CFLAGS = $(GCFLAGS) $(SSLINC)
+
+ALL = nnrpd
+
+SOURCES = article.c cache.c group.c commands.c line.c list.c misc.c \
+ newnews.c nnrpd.c perl.c perm.c post.c python.c \
+ sasl_config.c tls.c track.c
+
+INCLUDES = cache.h nnrpd.h post.h sasl_config.h tls.h
+
+OBJECTS = $(SOURCES:.c=.o)
+
+INSTALLED = $(D)$(PATHBIN)/nnrpd
+
+all: $(ALL)
+
+warnings:
+ $(MAKE) COPT='$(WARNINGS)' all
+
+install: all
+ $(LI_XPUB) nnrpd $D$(PATHBIN)/nnrpd
+
+clean:
+ rm -f *.o $(ALL) nnrpdp profiled
+ rm -rf .libs
+
+clobber distclean: clean
+ rm -f tags
+
+tags ctags: $(SOURCES) $(INCLUDES)
+ $(CTAGS) $(SOURCES) $(INCLUDES) ../lib/*.c ../include/*.h
+
+
+## Compilation rules.
+
+NNRPDLIBS = $(LIBHIST) $(LIBSTORAGE) $(LIBINN) $(EXTSTORAGELIBS) \
+ $(PERLLIB) $(PYTHONLIB) $(SSLLIB) $(LIBS)
+
+perl.o: perl.c ; $(CC) $(CFLAGS) $(PERLINC) -c perl.c
+python.o: python.c ; $(CC) $(CFLAGS) $(PYTHONINC) -c python.c
+
+nnrpd: $(OBJECTS) $(LIBHIST) $(LIBSTORAGE) $(LIBINN)
+ $(LIBLD) $(LDFLAGS) -o $@ $(OBJECTS) $(NNRPDLIBS)
+
+$(LIBINN): ; (cd ../lib ; $(MAKE))
+$(LIBSTORAGE): ; (cd ../storage ; $(MAKE))
+$(LIBHIST): ; (cd ../history ; $(MAKE))
+
+
+## Profiling. These rules have not been checked for a while and may need
+## some work.
+
+profiled: nnrpdp
+ date >$@
+
+nnrpdp: $(SOURCES)
+ rm -f $(OBJECTS)
+ $(MAKEPROFILING) nnrpd
+ mv nnrpd nnrpdp
+ rm -f $(OBJECTS)
+
+
+## Dependencies. Default list, below, is probably good enough.
+
+depend: $(SOURCES)
+ $(MAKEDEPEND) '$(CFLAGS) $(PERLINC) $(PYTHONINC) $(TCLINC)' $(SOURCES)
+
+# DO NOT DELETE THIS LINE -- make depend depends on it.
+article.o: article.c ../include/config.h ../include/inn/defines.h \
+ ../include/inn/system.h ../include/clibrary.h ../include/config.h \
+ ../include/inn/innconf.h ../include/inn/defines.h \
+ ../include/inn/messages.h ../include/inn/wire.h nnrpd.h \
+ ../include/portable/socket.h ../include/config.h \
+ ../include/portable/time.h ../include/inn/qio.h ../include/libinn.h \
+ ../include/nntp.h ../include/paths.h ../include/storage.h \
+ ../include/inn/vector.h ../include/inn/timer.h ../include/ov.h \
+ ../include/storage.h ../include/inn/history.h tls.h cache.h
+cache.o: cache.c ../include/config.h ../include/inn/defines.h \
+ ../include/inn/system.h ../include/clibrary.h ../include/config.h \
+ ../include/inn/innconf.h ../include/inn/defines.h ../include/inn/tst.h \
+ ../include/inn/list.h ../include/libinn.h ../include/storage.h cache.h
+group.o: group.c ../include/config.h ../include/inn/defines.h \
+ ../include/inn/system.h ../include/clibrary.h ../include/config.h \
+ ../include/inn/innconf.h ../include/inn/defines.h nnrpd.h \
+ ../include/portable/socket.h ../include/config.h \
+ ../include/portable/time.h ../include/inn/qio.h ../include/libinn.h \
+ ../include/nntp.h ../include/paths.h ../include/storage.h \
+ ../include/inn/vector.h ../include/inn/timer.h ../include/ov.h \
+ ../include/storage.h ../include/inn/history.h
+commands.o: commands.c ../include/config.h ../include/inn/defines.h \
+ ../include/inn/system.h ../include/clibrary.h ../include/config.h \
+ ../include/portable/wait.h ../include/config.h nnrpd.h \
+ ../include/portable/socket.h ../include/portable/time.h \
+ ../include/inn/qio.h ../include/inn/defines.h ../include/libinn.h \
+ ../include/nntp.h ../include/paths.h ../include/storage.h \
+ ../include/inn/vector.h ../include/inn/timer.h ../include/ov.h \
+ ../include/storage.h ../include/inn/history.h ../include/inn/innconf.h \
+ ../include/inn/messages.h
+line.o: line.c ../include/config.h ../include/inn/defines.h \
+ ../include/inn/system.h ../include/clibrary.h ../include/config.h \
+ ../include/inn/messages.h ../include/inn/defines.h nnrpd.h \
+ ../include/portable/socket.h ../include/config.h \
+ ../include/portable/time.h ../include/inn/qio.h ../include/libinn.h \
+ ../include/nntp.h ../include/paths.h ../include/storage.h \
+ ../include/inn/vector.h ../include/inn/timer.h
+list.o: list.c ../include/config.h ../include/inn/defines.h \
+ ../include/inn/system.h ../include/clibrary.h ../include/config.h \
+ nnrpd.h ../include/portable/socket.h ../include/config.h \
+ ../include/portable/time.h ../include/inn/qio.h \
+ ../include/inn/defines.h ../include/libinn.h ../include/nntp.h \
+ ../include/paths.h ../include/storage.h ../include/inn/vector.h \
+ ../include/inn/timer.h ../include/ov.h ../include/storage.h \
+ ../include/inn/history.h ../include/inn/innconf.h \
+ ../include/inn/messages.h
+misc.o: misc.c ../include/config.h ../include/inn/defines.h \
+ ../include/inn/system.h ../include/clibrary.h ../include/config.h \
+ ../include/inn/innconf.h ../include/inn/defines.h nnrpd.h \
+ ../include/portable/socket.h ../include/config.h \
+ ../include/portable/time.h ../include/inn/qio.h ../include/libinn.h \
+ ../include/nntp.h ../include/paths.h ../include/storage.h \
+ ../include/inn/vector.h ../include/inn/timer.h tls.h sasl_config.h
+newnews.o: newnews.c ../include/config.h ../include/inn/defines.h \
+ ../include/inn/system.h ../include/clibrary.h ../include/config.h \
+ ../include/inn/innconf.h ../include/inn/defines.h \
+ ../include/inn/messages.h ../include/inn/wire.h nnrpd.h \
+ ../include/portable/socket.h ../include/config.h \
+ ../include/portable/time.h ../include/inn/qio.h ../include/libinn.h \
+ ../include/nntp.h ../include/paths.h ../include/storage.h \
+ ../include/inn/vector.h ../include/inn/timer.h ../include/ov.h \
+ ../include/storage.h ../include/inn/history.h cache.h
+nnrpd.o: nnrpd.c ../include/config.h ../include/inn/defines.h \
+ ../include/inn/system.h ../include/clibrary.h ../include/config.h \
+ ../include/portable/setproctitle.h ../include/config.h \
+ ../include/portable/wait.h ../include/inn/innconf.h \
+ ../include/inn/defines.h ../include/inn/messages.h ../include/libinn.h \
+ ../include/ov.h ../include/storage.h ../include/inn/history.h nnrpd.h \
+ ../include/portable/socket.h ../include/portable/time.h \
+ ../include/inn/qio.h ../include/nntp.h ../include/storage.h \
+ ../include/inn/vector.h ../include/inn/timer.h tls.h sasl_config.h
+perl.o: perl.c ../include/config.h ../include/inn/defines.h \
+ ../include/inn/system.h ../include/clibrary.h ../include/config.h \
+ ../include/inn/innconf.h ../include/inn/defines.h nnrpd.h \
+ ../include/portable/socket.h ../include/config.h \
+ ../include/portable/time.h ../include/inn/qio.h ../include/libinn.h \
+ ../include/nntp.h ../include/paths.h ../include/storage.h \
+ ../include/inn/vector.h ../include/inn/timer.h post.h
+perm.o: perm.c ../include/config.h ../include/inn/defines.h \
+ ../include/inn/system.h ../include/clibrary.h ../include/config.h \
+ ../include/portable/wait.h ../include/config.h ../include/conffile.h \
+ ../include/inn/innconf.h ../include/inn/defines.h ../include/innperl.h \
+ nnrpd.h ../include/portable/socket.h ../include/portable/time.h \
+ ../include/inn/qio.h ../include/libinn.h ../include/nntp.h \
+ ../include/paths.h ../include/storage.h ../include/inn/vector.h \
+ ../include/inn/timer.h
+post.o: post.c ../include/config.h ../include/inn/defines.h \
+ ../include/inn/system.h ../include/clibrary.h ../include/config.h \
+ ../include/inn/innconf.h ../include/inn/defines.h nnrpd.h \
+ ../include/portable/socket.h ../include/config.h \
+ ../include/portable/time.h ../include/inn/qio.h ../include/libinn.h \
+ ../include/nntp.h ../include/paths.h ../include/storage.h \
+ ../include/inn/vector.h ../include/inn/timer.h ../include/ov.h \
+ ../include/storage.h ../include/inn/history.h post.h
+python.o: python.c ../include/config.h ../include/inn/defines.h \
+ ../include/inn/system.h ../include/clibrary.h ../include/config.h \
+ ../include/inn/innconf.h ../include/inn/defines.h nnrpd.h \
+ ../include/portable/socket.h ../include/config.h \
+ ../include/portable/time.h ../include/inn/qio.h ../include/libinn.h \
+ ../include/nntp.h ../include/paths.h ../include/storage.h \
+ ../include/inn/vector.h ../include/inn/timer.h ../include/inn/hashtab.h
+sasl_config.o: sasl_config.c ../include/config.h ../include/inn/defines.h \
+ ../include/inn/system.h ../include/clibrary.h ../include/config.h \
+ ../include/inn/innconf.h ../include/inn/defines.h nnrpd.h \
+ ../include/portable/socket.h ../include/config.h \
+ ../include/portable/time.h ../include/inn/qio.h ../include/libinn.h \
+ ../include/nntp.h ../include/paths.h ../include/storage.h \
+ ../include/inn/vector.h ../include/inn/timer.h sasl_config.h
+tls.o: tls.c ../include/config.h ../include/inn/defines.h \
+ ../include/inn/system.h nnrpd.h ../include/portable/socket.h \
+ ../include/config.h ../include/portable/time.h ../include/inn/qio.h \
+ ../include/inn/defines.h ../include/libinn.h ../include/config.h \
+ ../include/nntp.h ../include/paths.h ../include/storage.h \
+ ../include/inn/vector.h ../include/inn/timer.h tls.h sasl_config.h
+track.o: track.c ../include/config.h ../include/inn/defines.h \
+ ../include/inn/system.h ../include/clibrary.h ../include/config.h \
+ ../include/inn/innconf.h ../include/inn/defines.h nnrpd.h \
+ ../include/portable/socket.h ../include/config.h \
+ ../include/portable/time.h ../include/inn/qio.h ../include/libinn.h \
+ ../include/nntp.h ../include/paths.h ../include/storage.h \
+ ../include/inn/vector.h ../include/inn/timer.h
--- /dev/null
+/* $Id: article.c 7538 2006-08-26 05:44:06Z eagle $
+**
+** Article-related routines.
+*/
+
+#include "config.h"
+#include "clibrary.h"
+#include <assert.h>
+#if HAVE_LIMITS_H
+# include <limits.h>
+#endif
+#include <sys/uio.h>
+
+#include "inn/innconf.h"
+#include "inn/messages.h"
+#include "inn/wire.h"
+#include "nnrpd.h"
+#include "ov.h"
+#include "tls.h"
+#include "cache.h"
+
+#ifdef HAVE_SSL
+extern SSL *tls_conn;
+#endif
+
+/*
+** Data structures for use in ARTICLE/HEAD/BODY/STAT common code.
+*/
+typedef enum _SENDTYPE {
+ STarticle,
+ SThead,
+ STbody,
+ STstat
+} SENDTYPE;
+
+typedef struct _SENDDATA {
+ SENDTYPE Type;
+ int ReplyCode;
+ const char *Item;
+} SENDDATA;
+
+static char ARTnotingroup[] = NNTP_NOTINGROUP;
+static char ARTnoartingroup[] = NNTP_NOARTINGRP;
+static char ARTnocurrart[] = NNTP_NOCURRART;
+static ARTHANDLE *ARThandle = NULL;
+static SENDDATA SENDbody = {
+ STbody, NNTP_BODY_FOLLOWS_VAL, "body"
+};
+static SENDDATA SENDarticle = {
+ STarticle, NNTP_ARTICLE_FOLLOWS_VAL, "article"
+};
+static SENDDATA SENDstat = {
+ STstat, NNTP_NOTHING_FOLLOWS_VAL, "status"
+};
+static SENDDATA SENDhead = {
+ SThead, NNTP_HEAD_FOLLOWS_VAL, "head"
+};
+
+
+static struct iovec iov[IOV_MAX > 1024 ? 1024 : IOV_MAX];
+static int queued_iov = 0;
+
+static void PushIOvHelper(struct iovec* vec, int* countp) {
+ int result;
+ TMRstart(TMR_NNTPWRITE);
+#ifdef HAVE_SSL
+ if (tls_conn) {
+Again:
+ result = SSL_writev(tls_conn, vec, *countp);
+ switch (SSL_get_error(tls_conn, result)) {
+ case SSL_ERROR_NONE:
+ case SSL_ERROR_SYSCALL:
+ break;
+ case SSL_ERROR_WANT_WRITE:
+ goto Again;
+ break;
+ case SSL_ERROR_SSL:
+ SSL_shutdown(tls_conn);
+ tls_conn = NULL;
+ errno = ECONNRESET;
+ break;
+ case SSL_ERROR_ZERO_RETURN:
+ break;
+ }
+ } else {
+ result = xwritev(STDOUT_FILENO, vec, *countp);
+ }
+#else
+ result = xwritev(STDOUT_FILENO, vec, *countp);
+#endif
+ TMRstop(TMR_NNTPWRITE);
+ if (result == -1) {
+ /* we can't recover, since we can't resynchronise with our
+ * peer */
+ ExitWithStats(1, true);
+ }
+ *countp = 0;
+}
+
+static void
+PushIOvRateLimited(void) {
+ double start, end, elapsed, target;
+ struct iovec newiov[IOV_MAX > 1024 ? 1024 : IOV_MAX];
+ int newiov_len;
+ int sentiov;
+ int i;
+ int bytesfound;
+ int chunkbittenoff;
+ struct timeval waittime;
+
+ while (queued_iov) {
+ bytesfound = newiov_len = 0;
+ sentiov = 0;
+ for (i = 0; (i < queued_iov) && (bytesfound < MaxBytesPerSecond); i++) {
+ if ((signed)iov[i].iov_len + bytesfound > MaxBytesPerSecond) {
+ chunkbittenoff = MaxBytesPerSecond - bytesfound;
+ newiov[newiov_len].iov_base = iov[i].iov_base;
+ newiov[newiov_len++].iov_len = chunkbittenoff;
+ iov[i].iov_base = (char *)iov[i].iov_base + chunkbittenoff;
+ iov[i].iov_len -= chunkbittenoff;
+ bytesfound += chunkbittenoff;
+ } else {
+ newiov[newiov_len++] = iov[i];
+ sentiov++;
+ bytesfound += iov[i].iov_len;
+ }
+ }
+ assert(sentiov <= queued_iov);
+ start = TMRnow_double();
+ PushIOvHelper(newiov, &newiov_len);
+ end = TMRnow_double();
+ target = (double) bytesfound / MaxBytesPerSecond;
+ elapsed = end - start;
+ if (elapsed < 1 && elapsed < target) {
+ waittime.tv_sec = 0;
+ waittime.tv_usec = (target - elapsed) * 1e6;
+ start = TMRnow_double();
+ if (select(0, NULL, NULL, NULL, &waittime) != 0)
+ syswarn("%s: select in PushIOvRateLimit failed", ClientHost);
+ end = TMRnow_double();
+ IDLEtime += end - start;
+ }
+ memmove(iov, &iov[sentiov], (queued_iov - sentiov) * sizeof(struct iovec));
+ queued_iov -= sentiov;
+ }
+}
+
+static void
+PushIOv(void) {
+ TMRstart(TMR_NNTPWRITE);
+ fflush(stdout);
+ TMRstop(TMR_NNTPWRITE);
+ if (MaxBytesPerSecond != 0)
+ PushIOvRateLimited();
+ else
+ PushIOvHelper(iov, &queued_iov);
+}
+
+static void
+SendIOv(const char *p, int len) {
+ char *q;
+
+ if (queued_iov) {
+ q = (char *)iov[queued_iov - 1].iov_base + iov[queued_iov - 1].iov_len;
+ if (p == q) {
+ iov[queued_iov - 1].iov_len += len;
+ return;
+ }
+ }
+ iov[queued_iov].iov_base = (char*)p;
+ iov[queued_iov++].iov_len = len;
+ if (queued_iov == IOV_MAX)
+ PushIOv();
+}
+
+static char *_IO_buffer_ = NULL;
+static int highwater = 0;
+
+static void
+PushIOb(void) {
+ TMRstart(TMR_NNTPWRITE);
+ fflush(stdout);
+#ifdef HAVE_SSL
+ if (tls_conn) {
+ int r;
+Again:
+ r = SSL_write(tls_conn, _IO_buffer_, highwater);
+ switch (SSL_get_error(tls_conn, r)) {
+ case SSL_ERROR_NONE:
+ case SSL_ERROR_SYSCALL:
+ break;
+ case SSL_ERROR_WANT_WRITE:
+ goto Again;
+ break;
+ case SSL_ERROR_SSL:
+ SSL_shutdown(tls_conn);
+ tls_conn = NULL;
+ errno = ECONNRESET;
+ break;
+ case SSL_ERROR_ZERO_RETURN:
+ break;
+ }
+ if (r != highwater) {
+ TMRstop(TMR_NNTPWRITE);
+ highwater = 0;
+ return;
+ }
+ } else {
+ if (xwrite(STDOUT_FILENO, _IO_buffer_, highwater) != highwater) {
+ TMRstop(TMR_NNTPWRITE);
+ highwater = 0;
+ return;
+ }
+ }
+#else
+ if (xwrite(STDOUT_FILENO, _IO_buffer_, highwater) != highwater) {
+ TMRstop(TMR_NNTPWRITE);
+ highwater = 0;
+ return;
+ }
+#endif
+ TMRstop(TMR_NNTPWRITE);
+ highwater = 0;
+}
+
+static void
+SendIOb(const char *p, int len) {
+ int tocopy;
+
+ if (_IO_buffer_ == NULL)
+ _IO_buffer_ = xmalloc(BIG_BUFFER);
+
+ while (len > 0) {
+ tocopy = (len > (BIG_BUFFER - highwater)) ? (BIG_BUFFER - highwater) : len;
+ memcpy(&_IO_buffer_[highwater], p, tocopy);
+ p += tocopy;
+ highwater += tocopy;
+ len -= tocopy;
+ if (highwater == BIG_BUFFER)
+ PushIOb();
+ }
+}
+
+
+/*
+** If we have an article open, close it.
+*/
+void ARTclose(void)
+{
+ if (ARThandle) {
+ SMfreearticle(ARThandle);
+ ARThandle = NULL;
+ }
+}
+
+bool ARTinstorebytoken(TOKEN token)
+{
+ ARTHANDLE *art;
+ struct timeval stv, etv;
+
+ if (PERMaccessconf->nnrpdoverstats) {
+ gettimeofday(&stv, NULL);
+ }
+ art = SMretrieve(token, RETR_STAT); /* XXX This isn't really overstats, is it? */
+ if (PERMaccessconf->nnrpdoverstats) {
+ gettimeofday(&etv, NULL);
+ OVERartcheck+=(etv.tv_sec - stv.tv_sec) * 1000;
+ OVERartcheck+=(etv.tv_usec - stv.tv_usec) / 1000;
+ }
+ if (art) {
+ SMfreearticle(art);
+ return true;
+ }
+ return false;
+}
+
+/*
+** If the article name is valid, open it and stuff in the ID.
+*/
+static bool ARTopen(ARTNUM artnum)
+{
+ static ARTNUM save_artnum;
+ TOKEN token;
+
+ /* Re-use article if it's the same one. */
+ if (save_artnum == artnum) {
+ if (ARThandle)
+ return true;
+ }
+ ARTclose();
+
+ if (!OVgetartinfo(GRPcur, artnum, &token))
+ return false;
+
+ TMRstart(TMR_READART);
+ ARThandle = SMretrieve(token, RETR_ALL);
+ TMRstop(TMR_READART);
+ if (ARThandle == NULL) {
+ return false;
+ }
+
+ save_artnum = artnum;
+ return true;
+}
+
+
+/*
+** Open the article for a given Message-ID.
+*/
+static bool
+ARTopenbyid(char *msg_id, ARTNUM *ap, bool final)
+{
+ TOKEN token;
+
+ *ap = 0;
+ token = cache_get(HashMessageID(msg_id), final);
+ if (token.type == TOKEN_EMPTY) {
+ if (History == NULL) {
+ time_t statinterval;
+
+ /* Do lazy opens of the history file - lots of clients
+ * will never ask for anything by message id, so put off
+ * doing the work until we have to */
+ History = HISopen(HISTORY, innconf->hismethod, HIS_RDONLY);
+ if (!History) {
+ syslog(L_NOTICE, "cant initialize history");
+ Reply("%d NNTP server unavailable. Try later.\r\n",
+ NNTP_TEMPERR_VAL);
+ ExitWithStats(1, true);
+ }
+ statinterval = 30;
+ HISctl(History, HISCTLS_STATINTERVAL, &statinterval);
+ }
+ if (!HISlookup(History, msg_id, NULL, NULL, NULL, &token))
+ return false;
+ }
+ if (token.type == TOKEN_EMPTY)
+ return false;
+ TMRstart(TMR_READART);
+ ARThandle = SMretrieve(token, RETR_ALL);
+ TMRstop(TMR_READART);
+ if (ARThandle == NULL) {
+ return false;
+ }
+
+ return true;
+}
+
+/*
+** Send a (part of) a file to stdout, doing newline and dot conversion.
+*/
+static void ARTsendmmap(SENDTYPE what)
+{
+ const char *p, *q, *r;
+ const char *s, *path, *xref, *endofpath;
+ long bytecount;
+ char lastchar;
+
+ ARTcount++;
+ GRParticles++;
+ bytecount = 0;
+ lastchar = -1;
+
+ /* Get the headers and detect if wire format. */
+ if (what == STarticle) {
+ q = ARThandle->data;
+ p = ARThandle->data + ARThandle->len;
+ } else {
+ for (q = p = ARThandle->data; p < (ARThandle->data + ARThandle->len); p++) {
+ if (*p == '\r')
+ continue;
+ if (*p == '\n') {
+ if (lastchar == '\n') {
+ if (what == SThead) {
+ if (*(p-1) == '\r')
+ p--;
+ break;
+ } else {
+ q = p + 1;
+ p = ARThandle->data + ARThandle->len;
+ break;
+ }
+ }
+ }
+ lastchar = *p;
+ }
+ }
+
+ /* q points to the start of the article buffer, p to the end of it */
+ if (VirtualPathlen > 0 && (what != STbody)) {
+ path = wire_findheader(ARThandle->data, ARThandle->len, "Path");
+ if (path == NULL) {
+ SendIOv(".\r\n", 3);
+ ARTgetsize += 3;
+ PushIOv();
+ ARTget++;
+ return;
+ } else {
+ xref = wire_findheader(ARThandle->data, ARThandle->len, "Xref");
+ if (xref == NULL) {
+ SendIOv(".\r\n", 3);
+ ARTgetsize += 3;
+ PushIOv();
+ ARTget++;
+ return;
+ }
+ }
+ endofpath = wire_endheader(path, ARThandle->data + ARThandle->len - 1);
+ if (endofpath == NULL) {
+ SendIOv(".\r\n", 3);
+ ARTgetsize += 3;
+ PushIOv();
+ ARTget++;
+ return;
+ }
+ if ((r = memchr(xref, ' ', p - xref)) == NULL || r == p) {
+ SendIOv(".\r\n", 3);
+ ARTgetsize += 3;
+ PushIOv();
+ ARTget++;
+ return;
+ }
+ /* r points to the first space in the Xref header */
+ for (s = path, lastchar = '\0';
+ s + VirtualPathlen + 1 < endofpath;
+ lastchar = *s++) {
+ if ((lastchar != '\0' && lastchar != '!') || *s != *VirtualPath ||
+ strncmp(s, VirtualPath, VirtualPathlen - 1) != 0)
+ continue;
+ if (*(s + VirtualPathlen - 1) != '\0' &&
+ *(s + VirtualPathlen - 1) != '!')
+ continue;
+ break;
+ }
+ if (s + VirtualPathlen + 1 < endofpath) {
+ if (xref > path) {
+ SendIOv(q, path - q);
+ SendIOv(s, xref - s);
+ SendIOv(VirtualPath, VirtualPathlen - 1);
+ SendIOv(r, p - r);
+ } else {
+ SendIOv(q, xref - q);
+ SendIOv(VirtualPath, VirtualPathlen - 1);
+ SendIOv(r, path - r);
+ SendIOv(s, p - s);
+ }
+ } else {
+ if (xref > path) {
+ SendIOv(q, path - q);
+ SendIOv(VirtualPath, VirtualPathlen);
+ SendIOv(path, xref - path);
+ SendIOv(VirtualPath, VirtualPathlen - 1);
+ SendIOv(r, p - r);
+ } else {
+ SendIOv(q, xref - q);
+ SendIOv(VirtualPath, VirtualPathlen - 1);
+ SendIOv(r, path - r);
+ SendIOv(VirtualPath, VirtualPathlen);
+ SendIOv(path, p - path);
+ }
+ }
+ } else
+ SendIOv(q, p - q);
+ ARTgetsize += p - q;
+ if (what == SThead) {
+ SendIOv(".\r\n", 3);
+ ARTgetsize += 3;
+ } else if (memcmp((ARThandle->data + ARThandle->len - 5), "\r\n.\r\n", 5)) {
+ if (memcmp((ARThandle->data + ARThandle->len - 2), "\r\n", 2)) {
+ SendIOv("\r\n.\r\n", 5);
+ ARTgetsize += 5;
+ } else {
+ SendIOv(".\r\n", 3);
+ ARTgetsize += 3;
+ }
+ }
+ PushIOv();
+
+ ARTget++;
+}
+
+/*
+** Return the header from the specified file, or NULL if not found.
+*/
+char *GetHeader(const char *header)
+{
+ const char *p, *q, *r, *s, *t;
+ char *w, prevchar;
+ /* Bogus value here to make sure that it isn't initialized to \n */
+ char lastchar = ' ';
+ const char *limit;
+ const char *cmplimit;
+ static char *retval = NULL;
+ static int retlen = 0;
+ int headerlen;
+ bool pathheader = false;
+ bool xrefheader = false;
+
+ limit = ARThandle->data + ARThandle->len;
+ cmplimit = ARThandle->data + ARThandle->len - strlen(header) - 1;
+ for (p = ARThandle->data; p < cmplimit; p++) {
+ if (*p == '\r')
+ continue;
+ if ((lastchar == '\n') && (*p == '\n')) {
+ return NULL;
+ }
+ if ((lastchar == '\n') || (p == ARThandle->data)) {
+ headerlen = strlen(header);
+ if (strncasecmp(p, header, headerlen) == 0 && p[headerlen] == ':') {
+ for (; (p < limit) && !isspace((int)*p) ; p++);
+ for (; (p < limit) && isspace((int)*p) ; p++);
+ for (q = p; q < limit; q++)
+ if ((*q == '\r') || (*q == '\n')) {
+ /* Check for continuation header lines */
+ t = q + 1;
+ if (t < limit) {
+ if ((*q == '\r' && *t == '\n')) {
+ t++;
+ if (t == limit)
+ break;
+ }
+ if ((*t == '\t' || *t == ' ')) {
+ for (; (t < limit) && isspace((int)*t) ; t++);
+ q = t;
+ } else {
+ break;
+ }
+ } else {
+ break;
+ }
+ }
+ if (q == limit)
+ return NULL;
+ if (strncasecmp("Path", header, headerlen) == 0)
+ pathheader = true;
+ else if (strncasecmp("Xref", header, headerlen) == 0)
+ xrefheader = true;
+ if (retval == NULL) {
+ retlen = q - p + VirtualPathlen + 1;
+ retval = xmalloc(retlen);
+ } else {
+ if ((q - p + VirtualPathlen + 1) > retlen) {
+ retlen = q - p + VirtualPathlen + 1;
+ retval = xrealloc(retval, retlen);
+ }
+ }
+ if (pathheader && (VirtualPathlen > 0)) {
+ const char *endofpath;
+ const char *endofarticle;
+
+ endofarticle = ARThandle->data + ARThandle->len - 1;
+ endofpath = wire_endheader(p, endofarticle);
+ if (endofpath == NULL)
+ return NULL;
+ for (s = p, prevchar = '\0';
+ s + VirtualPathlen + 1 < endofpath;
+ prevchar = *s++) {
+ if ((prevchar != '\0' && prevchar != '!') ||
+ *s != *VirtualPath ||
+ strncmp(s, VirtualPath, VirtualPathlen - 1) != 0)
+ continue;
+ if (*(s + VirtualPathlen - 1) != '\0' &&
+ *(s + VirtualPathlen - 1) != '!')
+ continue;
+ break;
+ }
+ if (s + VirtualPathlen + 1 < endofpath) {
+ memcpy(retval, s, q - s);
+ *(retval + (int)(q - s)) = '\0';
+ } else {
+ memcpy(retval, VirtualPath, VirtualPathlen);
+ memcpy(retval + VirtualPathlen, p, q - p);
+ *(retval + (int)(q - p) + VirtualPathlen) = '\0';
+ }
+ } else if (xrefheader && (VirtualPathlen > 0)) {
+ if ((r = memchr(p, ' ', q - p)) == NULL)
+ return NULL;
+ for (; (r < q) && isspace((int)*r) ; r++);
+ if (r == q)
+ return NULL;
+ memcpy(retval, VirtualPath, VirtualPathlen - 1);
+ memcpy(retval + VirtualPathlen - 1, r - 1, q - r + 1);
+ *(retval + (int)(q - r) + VirtualPathlen) = '\0';
+ } else {
+ memcpy(retval, p, q - p);
+ *(retval + (int)(q - p)) = '\0';
+ }
+ for (w = retval; *w; w++)
+ if (*w == '\n' || *w == '\r')
+ *w = ' ';
+ return retval;
+ }
+ }
+ lastchar = *p;
+ }
+ return NULL;
+}
+
+/*
+** Fetch part or all of an article and send it to the client.
+*/
+void CMDfetch(int ac, char *av[])
+{
+ char buff[SMBUF];
+ SENDDATA *what;
+ bool ok;
+ ARTNUM art;
+ char *msgid;
+ ARTNUM tart;
+ bool final = false;
+
+ /* Find what to send; get permissions. */
+ ok = PERMcanread;
+ switch (*av[0]) {
+ default:
+ what = &SENDbody;
+ final = true;
+ break;
+ case 'a': case 'A':
+ what = &SENDarticle;
+ final = true;
+ break;
+ case 's': case 'S':
+ what = &SENDstat;
+ break;
+ case 'h': case 'H':
+ what = &SENDhead;
+ /* Poster might do a "head" command to verify the article. */
+ ok = PERMcanread || PERMcanpost;
+ break;
+ }
+
+ if (!ok) {
+ Reply("%s\r\n", NOACCESS);
+ return;
+ }
+
+ /* Requesting by Message-ID? */
+ if (ac == 2 && av[1][0] == '<') {
+ if (!ARTopenbyid(av[1], &art, final)) {
+ Reply("%d No such article\r\n", NNTP_DONTHAVEIT_VAL);
+ return;
+ }
+ if (!PERMartok()) {
+ ARTclose();
+ Reply("%s\r\n", NOACCESS);
+ return;
+ }
+ tart=art;
+ Reply("%d %lu %s %s\r\n", what->ReplyCode, (unsigned long) art,
+ av[1], what->Item);
+ if (what->Type != STstat) {
+ ARTsendmmap(what->Type);
+ }
+ ARTclose();
+ return;
+ }
+
+ /* Trying to read. */
+ if (GRPcount == 0) {
+ Reply("%s\r\n", ARTnotingroup);
+ return;
+ }
+
+ /* Default is to get current article, or specified article. */
+ if (ac == 1) {
+ if (ARTnumber < ARTlow || ARTnumber > ARThigh) {
+ Reply("%s\r\n", ARTnocurrart);
+ return;
+ }
+ snprintf(buff, sizeof(buff), "%d", ARTnumber);
+ tart=ARTnumber;
+ }
+ else {
+ if (strspn(av[1], "0123456789") != strlen(av[1])) {
+ Reply("%s\r\n", ARTnoartingroup);
+ return;
+ }
+ strlcpy(buff, av[1], sizeof(buff));
+ tart=(ARTNUM)atol(buff);
+ }
+
+ /* Open the article and send the reply. */
+ if (!ARTopen(atol(buff))) {
+ Reply("%s\r\n", ARTnoartingroup);
+ return;
+ }
+ if (ac > 1)
+ ARTnumber = tart;
+ if ((msgid = GetHeader("Message-ID")) == NULL) {
+ Reply("%s\r\n", ARTnoartingroup);
+ return;
+ }
+ Reply("%d %s %.512s %s\r\n", what->ReplyCode, buff, msgid, what->Item);
+ if (what->Type != STstat)
+ ARTsendmmap(what->Type);
+ ARTclose();
+}
+
+
+/*
+** Go to the next or last (really previous) article in the group.
+*/
+void CMDnextlast(int ac UNUSED, char *av[])
+{
+ char *msgid;
+ int save, delta, errcode;
+ bool next;
+ const char *message;
+
+ if (!PERMcanread) {
+ Reply("%s\r\n", NOACCESS);
+ return;
+ }
+ if (GRPcount == 0) {
+ Reply("%s\r\n", ARTnotingroup);
+ return;
+ }
+ if (ARTnumber < ARTlow || ARTnumber > ARThigh) {
+ Reply("%s\r\n", ARTnocurrart);
+ return;
+ }
+
+ next = (av[0][0] == 'n' || av[0][0] == 'N');
+ if (next) {
+ delta = 1;
+ errcode = NNTP_NONEXT_VAL;
+ message = "next";
+ }
+ else {
+ delta = -1;
+ errcode = NNTP_NOPREV_VAL;
+ message = "previous";
+ }
+
+ save = ARTnumber;
+ msgid = NULL;
+ do {
+ ARTnumber += delta;
+ if (ARTnumber < ARTlow || ARTnumber > ARThigh) {
+ Reply("%d No %s to retrieve.\r\n", errcode, message);
+ ARTnumber = save;
+ return;
+ }
+ if (!ARTopen(ARTnumber))
+ continue;
+ msgid = GetHeader("Message-ID");
+ } while (msgid == NULL);
+
+ ARTclose();
+ Reply("%d %d %s Article retrieved; request text separately.\r\n",
+ NNTP_NOTHING_FOLLOWS_VAL, ARTnumber, msgid);
+}
+
+
+static bool CMDgetrange(int ac, char *av[], ARTRANGE *rp, bool *DidReply)
+{
+ char *p;
+
+ *DidReply = false;
+ if (GRPcount == 0) {
+ Reply("%s\r\n", ARTnotingroup);
+ *DidReply = true;
+ return false;
+ }
+
+ if (ac == 1) {
+ /* No argument, do only current article. */
+ if (ARTnumber < ARTlow || ARTnumber > ARThigh) {
+ Reply("%s\r\n", ARTnocurrart);
+ *DidReply = true;
+ return false;
+ }
+ rp->High = rp->Low = ARTnumber;
+ return true;
+ }
+
+ /* Got just a single number? */
+ if ((p = strchr(av[1], '-')) == NULL) {
+ rp->Low = rp->High = atol(av[1]);
+ return true;
+ }
+
+ /* Parse range. */
+ *p++ = '\0';
+ rp->Low = atol(av[1]);
+ if (*p == '\0' || (rp->High = atol(p)) < rp->Low)
+ /* "XHDR 234-0 header" gives everything to the end. */
+ rp->High = ARThigh;
+ else if (rp->High > ARThigh)
+ rp->High = ARThigh;
+ if (rp->Low < ARTlow)
+ rp->Low = ARTlow;
+ p--;
+ *p = '-';
+
+ return true;
+}
+
+
+/*
+** Apply virtual hosting to an Xref field.
+*/
+static char *
+vhost_xref(char *p)
+{
+ char *space;
+ size_t offset;
+ char *field = NULL;
+
+ space = strchr(p, ' ');
+ if (space == NULL) {
+ warn("malformed Xref `%s'", field);
+ goto fail;
+ }
+ offset = space + 1 - p;
+ space = strchr(p + offset, ' ');
+ if (space == NULL) {
+ warn("malformed Xref `%s'", field);
+ goto fail;
+ }
+ field = concat(PERMaccessconf->domain, space, NULL);
+ fail:
+ free(p);
+ return field;
+}
+
+/*
+** XOVER another extension. Dump parts of the overview database.
+*/
+void CMDxover(int ac, char *av[])
+{
+ bool DidReply;
+ ARTRANGE range;
+ struct timeval stv, etv;
+ ARTNUM artnum;
+ void *handle;
+ char *data, *r;
+ const char *p, *q;
+ int len, useIOb = 0;
+ TOKEN token;
+ struct cvector *vector = NULL;
+
+ if (!PERMcanread) {
+ Printf("%s\r\n", NOACCESS);
+ return;
+ }
+
+ /* Trying to read. */
+ if (GRPcount == 0) {
+ Reply("%s\r\n", ARTnotingroup);
+ return;
+ }
+
+ /* Parse range. */
+ if (!CMDgetrange(ac, av, &range, &DidReply)) {
+ if (!DidReply) {
+ Reply("%d data follows\r\n", NNTP_OVERVIEW_FOLLOWS_VAL);
+ Printf(".\r\n");
+ return;
+ }
+ }
+
+ OVERcount++;
+ gettimeofday(&stv, NULL);
+ if ((handle = (void *)OVopensearch(GRPcur, range.Low, range.High)) == NULL) {
+ if (av[1] != NULL)
+ Reply("%d %s fields follow\r\n.\r\n", NNTP_OVERVIEW_FOLLOWS_VAL, av[1]);
+ else
+ Reply("%d %d fields follow\r\n.\r\n", NNTP_OVERVIEW_FOLLOWS_VAL, ARTnumber);
+ return;
+ }
+ if (PERMaccessconf->nnrpdoverstats) {
+ gettimeofday(&etv, NULL);
+ OVERtime+=(etv.tv_sec - stv.tv_sec) * 1000;
+ OVERtime+=(etv.tv_usec - stv.tv_usec) / 1000;
+ }
+
+ if (av[1] != NULL)
+ Reply("%d %s fields follow\r\n", NNTP_OVERVIEW_FOLLOWS_VAL, av[1]);
+ else
+ Reply("%d %d fields follow\r\n", NNTP_OVERVIEW_FOLLOWS_VAL, ARTnumber);
+ fflush(stdout);
+ if (PERMaccessconf->nnrpdoverstats)
+ gettimeofday(&stv, NULL);
+
+ /* If OVSTATICSEARCH is true, then the data returned by OVsearch is only
+ valid until the next call to OVsearch. In this case, we must use
+ SendIOb because it copies the data. */
+ OVctl(OVSTATICSEARCH, &useIOb);
+
+ while (OVsearch(handle, &artnum, &data, &len, &token, NULL)) {
+ if (PERMaccessconf->nnrpdoverstats) {
+ gettimeofday(&etv, NULL);
+ OVERtime+=(etv.tv_sec - stv.tv_sec) * 1000;
+ OVERtime+=(etv.tv_usec - stv.tv_usec) / 1000;
+ }
+ if (len == 0 || (PERMaccessconf->nnrpdcheckart && !ARTinstorebytoken(token))) {
+ if (PERMaccessconf->nnrpdoverstats) {
+ OVERmiss++;
+ gettimeofday(&stv, NULL);
+ }
+ continue;
+ }
+ if (PERMaccessconf->nnrpdoverstats) {
+ OVERhit++;
+ OVERsize += len;
+ }
+ vector = overview_split(data, len, NULL, vector);
+ r = overview_getheader(vector, OVERVIEW_MESSAGE_ID, OVextra);
+ cache_add(HashMessageID(r), token);
+ free(r);
+ if (VirtualPathlen > 0 && overhdr_xref != -1) {
+ if ((overhdr_xref + 1) >= vector->count)
+ continue;
+ p = vector->strings[overhdr_xref] + sizeof("Xref: ") - 1;
+ while ((p < data + len) && *p == ' ')
+ ++p;
+ q = memchr(p, ' ', data + len - p);
+ if (q == NULL)
+ continue;
+ if(useIOb) {
+ SendIOb(data, p - data);
+ SendIOb(VirtualPath, VirtualPathlen - 1);
+ SendIOb(q, len - (q - data));
+ } else {
+ SendIOv(data, p - data);
+ SendIOv(VirtualPath, VirtualPathlen - 1);
+ SendIOv(q, len - (q - data));
+ }
+ } else {
+ if(useIOb)
+ SendIOb(data, len);
+ else
+ SendIOv(data, len);
+ }
+ if (PERMaccessconf->nnrpdoverstats)
+ gettimeofday(&stv, NULL);
+ }
+
+ if (vector)
+ cvector_free(vector);
+
+ if (PERMaccessconf->nnrpdoverstats) {
+ gettimeofday(&etv, NULL);
+ OVERtime+=(etv.tv_sec - stv.tv_sec) * 1000;
+ OVERtime+=(etv.tv_usec - stv.tv_usec) / 1000;
+ }
+ if(useIOb) {
+ SendIOb(".\r\n", 3);
+ PushIOb();
+ } else {
+ SendIOv(".\r\n", 3);
+ PushIOv();
+ }
+ if (PERMaccessconf->nnrpdoverstats)
+ gettimeofday(&stv, NULL);
+ OVclosesearch(handle);
+ if (PERMaccessconf->nnrpdoverstats) {
+ gettimeofday(&etv, NULL);
+ OVERtime+=(etv.tv_sec - stv.tv_sec) * 1000;
+ OVERtime+=(etv.tv_usec - stv.tv_usec) / 1000;
+ }
+
+}
+
+/*
+** XHDR and XPAT extensions. Note that HDR as specified in the new NNTP
+** draft works differently than XHDR has historically, so don't just use this
+** function to implement it without reviewing the differences.
+*/
+/* ARGSUSED */
+void CMDpat(int ac, char *av[])
+{
+ char *p;
+ int i;
+ ARTRANGE range;
+ bool IsLines;
+ bool DidReply;
+ char *header;
+ char *pattern;
+ char *text;
+ int Overview;
+ ARTNUM artnum;
+ char buff[SPOOLNAMEBUFF];
+ void *handle;
+ char *data;
+ int len;
+ TOKEN token;
+ struct cvector *vector = NULL;
+
+ if (!PERMcanread) {
+ Printf("%s\r\n", NOACCESS);
+ return;
+ }
+
+ header = av[1];
+ IsLines = (strcasecmp(header, "lines") == 0);
+
+ if (ac > 3) /* XPAT */
+ pattern = Glom(&av[3]);
+ else
+ pattern = NULL;
+
+ do {
+ /* Message-ID specified? */
+ if (ac > 2 && av[2][0] == '<') {
+ p = av[2];
+ if (!ARTopenbyid(p, &artnum, false)) {
+ Printf("%d No such article.\r\n", NNTP_DONTHAVEIT_VAL);
+ break;
+ }
+ Printf("%d %s matches follow (ID)\r\n", NNTP_HEAD_FOLLOWS_VAL,
+ header);
+ if ((text = GetHeader(header)) != NULL
+ && (!pattern || uwildmat_simple(text, pattern)))
+ Printf("%s %s\r\n", p, text);
+
+ ARTclose();
+ Printf(".\r\n");
+ break;
+ }
+
+ if (GRPcount == 0) {
+ Reply("%s\r\n", ARTnotingroup);
+ break;
+ }
+
+ /* Range specified. */
+ if (!CMDgetrange(ac - 1, av + 1, &range, &DidReply)) {
+ if (!DidReply) {
+ Reply("%d %s no matches follow (range)\r\n",
+ NNTP_HEAD_FOLLOWS_VAL, header ? header : "\"\"");
+ Printf(".\r\n");
+ break;
+ }
+ }
+
+ /* In overview? */
+ Overview = overview_index(header, OVextra);
+
+ /* Not in overview, we have to fish headers out from the articles */
+ if (Overview < 0 ) {
+ Reply("%d %s matches follow (art)\r\n", NNTP_HEAD_FOLLOWS_VAL,
+ header);
+ for (i = range.Low; i <= range.High && range.High > 0; i++) {
+ if (!ARTopen(i))
+ continue;
+ p = GetHeader(header);
+ if (p && (!pattern || uwildmat_simple(p, pattern))) {
+ snprintf(buff, sizeof(buff), "%u ", i);
+ SendIOb(buff, strlen(buff));
+ SendIOb(p, strlen(p));
+ SendIOb("\r\n", 2);
+ ARTclose();
+ }
+ }
+ SendIOb(".\r\n", 3);
+ PushIOb();
+ break;
+ }
+
+ /* Okay then, we can grab values from overview. */
+ handle = (void *)OVopensearch(GRPcur, range.Low, range.High);
+ if (handle == NULL) {
+ Reply("%d %s no matches follow (NOV)\r\n.\r\n",
+ NNTP_HEAD_FOLLOWS_VAL, header);
+ break;
+ }
+
+ Printf("%d %s matches follow (NOV)\r\n", NNTP_HEAD_FOLLOWS_VAL,
+ header);
+ while (OVsearch(handle, &artnum, &data, &len, &token, NULL)) {
+ if (len == 0 || (PERMaccessconf->nnrpdcheckart
+ && !ARTinstorebytoken(token)))
+ continue;
+ vector = overview_split(data, len, NULL, vector);
+ p = overview_getheader(vector, Overview, OVextra);
+ if (p != NULL) {
+ if (PERMaccessconf->virtualhost &&
+ Overview == overhdr_xref) {
+ p = vhost_xref(p);
+ if (p == NULL)
+ continue;
+ }
+ if (!pattern || uwildmat_simple(p, pattern)) {
+ snprintf(buff, sizeof(buff), "%lu ", artnum);
+ SendIOb(buff, strlen(buff));
+ SendIOb(p, strlen(p));
+ SendIOb("\r\n", 2);
+ }
+ free(p);
+ }
+ }
+ SendIOb(".\r\n", 3);
+ PushIOb();
+ OVclosesearch(handle);
+ } while (0);
+
+ if (vector)
+ cvector_free(vector);
+
+ if (pattern)
+ free(pattern);
+}
--- /dev/null
+/* $Id: cache.c 6169 2003-01-21 06:31:40Z alexk $
+**
+** MessageID to storage token cache
+**
+** Written by Alex Kiernan (alex.kiernan@thus.net)
+**
+** Implementation of a message ID to storage token cache which can be
+** built during XOVER/XHDR/NEWNEWS. If we hit in the cache when
+** retrieving articles the (relatively) expensive cost of a trip
+** through the history database is saved.
+*/
+
+#include "config.h"
+#include "clibrary.h"
+
+#include "inn/innconf.h"
+#include "inn/tst.h"
+#include "inn/list.h"
+#include "libinn.h"
+#include "storage.h"
+
+#include "cache.h"
+
+/*
+** Pointer to the message ID to storage token ternary search tree
+*/
+static struct tst *msgidcache;
+
+/*
+** Count of message IDs in the cache so that someone doing GROUP,
+** XOVER, GROUP, XOVER etc. for example doesn't blow up with out of
+** memory
+*/
+static int msgcachecount;
+
+struct cache_entry {
+ struct node node;
+ HASH hash;
+ TOKEN token;
+};
+
+static struct list unused, used;
+
+/*
+** Add a translation from HASH, h, to TOKEN, t, to the message ID
+** cache
+*/
+void
+cache_add(const HASH h, const TOKEN t)
+{
+ if (innconf->msgidcachesize != 0) {
+ struct cache_entry *entry;
+ const unsigned char *p;
+ struct cache_entry *exist;
+
+ if (!msgidcache) {
+ msgidcache = tst_init((innconf->msgidcachesize + 9) / 10);
+ list_new(&unused);
+ list_new(&used);
+ }
+
+ entry = xmalloc(sizeof *entry);
+ entry->hash = h;
+ entry->token = t;
+ p = (unsigned char *) HashToText(h);
+ if (tst_insert(msgidcache, p, entry,
+ 0, (void **)&exist) == TST_DUPLICATE_KEY) {
+ free(entry);
+ list_remove(&exist->node);
+ list_addtail(&unused, &exist->node);
+ } else {
+ list_addtail(&unused, &entry->node);
+ ++msgcachecount;
+ }
+ if (msgcachecount >= innconf->msgidcachesize) {
+ /* need to throw away a node */
+ entry = (struct cache_entry *)list_remhead(&used);
+ if (entry == NULL)
+ entry = (struct cache_entry *)list_remhead(&unused);
+ if (entry != NULL) {
+ tst_delete(msgidcache,
+ (unsigned char *) HashToText(entry->hash));
+ free(entry);
+ }
+ }
+ }
+}
+
+
+/*
+** Lookup (and remove if found) a MessageID to TOKEN mapping. If this
+** is a final lookup (ARTICLE or BODY) we remove it if we find it
+** since this matches the observed behaviour of most clients, but
+** cache it just in case we can reuse it if they issue multiple
+** commands against the same message ID (e.g. HEAD, BODY).
+*/
+TOKEN
+cache_get(const HASH h, bool final)
+{
+ static HASH last_hash;
+ static TOKEN last_token;
+ static const TOKEN empty_token = { TOKEN_EMPTY, 0, "" };
+
+ if (HashCompare(&h, &last_hash) == 0 && !HashEmpty(last_hash))
+ return last_token;
+
+ if (msgidcache) {
+ struct cache_entry *entry;
+
+ entry = tst_search(msgidcache, (unsigned char *) HashToText(h));
+ if (entry != NULL) {
+ list_remove(&entry->node);
+ if (!final)
+ list_addtail(&unused, &entry->node);
+ else
+ list_addtail(&used, &entry->node);
+ last_hash = entry->hash;
+ last_token = entry->token;
+ return last_token;
+ }
+ }
+ return empty_token;
+}
--- /dev/null
+#ifndef CACHE_H
+#define CACHE_H
+
+#include "libinn.h"
+#include "storage.h"
+
+BEGIN_DECLS
+
+void cache_add(const HASH, const TOKEN);
+TOKEN cache_get(const HASH, bool final);
+
+END_DECLS
+
+#endif /* CACHE_H */
--- /dev/null
+/* $Id: commands.c 7542 2006-08-26 05:57:11Z eagle $
+**
+** Miscellaneous commands.
+*/
+#include "config.h"
+#include "clibrary.h"
+#include "portable/wait.h"
+
+#include "nnrpd.h"
+#include "ov.h"
+#include "inn/innconf.h"
+#include "inn/messages.h"
+
+typedef struct {
+ char *name;
+ int high;
+ int low;
+ int count;
+} GROUPDATA;
+
+
+extern const char *NNRPinstance;
+
+/* returns:
+ -1 for problem (such as no such authenticator etc.)
+ 0 for authentication succeeded
+ 1 for authentication failed
+ */
+
+static char *PERMauthstring;
+
+static int
+PERMgeneric(char *av[], char *accesslist)
+{
+ char path[BIG_BUFFER], *fields[6], *p;
+ int i, pan[2], status;
+ pid_t pid;
+ struct stat stb;
+
+ av += 2;
+
+ PERMcanread = false;
+ PERMcanpost = false;
+ PERMaccessconf->locpost = false;
+ PERMaccessconf->allowapproved = false;
+
+ if (!*av) {
+ Reply("%d no authenticator\r\n", NNTP_SYNTAX_VAL);
+ return(-1);
+ }
+
+ /* check for ../. I'd use strstr, but there doesn't appear to
+ be any other references for it, and I don't want to break
+ portability */
+ for (p = av[0]; *p; p++)
+ if (strncmp(p, "../", 3) == 0) {
+ Reply("%d ../ in authenticator %s\r\n", NNTP_SYNTAX_VAL, av[0]);
+ return(-1);
+ }
+
+ if (strchr(_PATH_AUTHDIR,'/') == NULL)
+ snprintf(path, sizeof(path), "%s/%s/%s/%s", innconf->pathbin,
+ _PATH_AUTHDIR, _PATH_AUTHDIR_GENERIC, av[0]);
+ else
+ snprintf(path, sizeof(path), "%s/%s/%s", _PATH_AUTHDIR,
+ _PATH_AUTHDIR_GENERIC, av[0]);
+
+#if !defined(S_IXUSR) && defined(_S_IXUSR)
+#define S_IXUSR _S_IXUSR
+#endif /* !defined(S_IXUSR) && defined(_S_IXUSR) */
+
+#if !defined(S_IXUSR) && defined(S_IEXEC)
+#define S_IXUSR S_IEXEC
+#endif /* !defined(S_IXUSR) && defined(S_IEXEC) */
+
+ if (stat(path, &stb) || !(stb.st_mode&S_IXUSR)) {
+ Reply("%d No such authenticator %s\r\n", NNTP_TEMPERR_VAL, av[0]);
+ return -1;
+ }
+
+
+ /* Create a pipe. */
+ if (pipe(pan) < 0) {
+ syslog(L_FATAL, "cant pipe for %s %m", av[0]);
+ return -1;
+ }
+
+ for (i = 0; (pid = fork()) < 0; i++) {
+ if (i == innconf->maxforks) {
+ Reply("%d Can't fork %s\r\n", NNTP_TEMPERR_VAL,
+ strerror(errno));
+ syslog(L_FATAL, "cant fork %s %m", av[0]);
+ return -1;
+ }
+ syslog(L_NOTICE, "cant fork %s -- waiting", av[0]);
+ sleep(5);
+ }
+
+ /* Run the child, with redirection. */
+ if (pid == 0) {
+ close(STDERR_FILENO); /* Close existing stderr */
+ close(pan[PIPE_READ]);
+
+ /* stderr goes down the pipe. */
+ if (pan[PIPE_WRITE] != STDERR_FILENO) {
+ if ((i = dup2(pan[PIPE_WRITE], STDERR_FILENO)) != STDERR_FILENO) {
+ syslog(L_FATAL, "cant dup2 %d to %d got %d %m",
+ pan[PIPE_WRITE], STDERR_FILENO, i);
+ _exit(1);
+ }
+ close(pan[PIPE_WRITE]);
+ }
+
+ close_on_exec(STDIN_FILENO, false);
+ close_on_exec(STDOUT_FILENO, false);
+ close_on_exec(STDERR_FILENO, false);
+
+ execv(path, av);
+ Reply("%s\r\n", NNTP_BAD_COMMAND);
+
+ syslog(L_FATAL, "cant execv %s %m", path);
+ _exit(1);
+ }
+
+ close(pan[PIPE_WRITE]);
+ i = read(pan[PIPE_READ], path, sizeof(path));
+
+ waitpid(pid, &status, 0);
+ if (!WIFEXITED(status) || WEXITSTATUS(status) != 0)
+ return 1;
+
+ if ((p = strchr(path, '\n')) != NULL)
+ *p = '\0';
+
+ if (PERMauthstring)
+ free(PERMauthstring);
+
+ PERMauthstring = xstrdup(path);
+
+ /*syslog(L_NOTICE, "%s (%ld) returned: %d %s %d\n", av[0], (long) pid, i, path, status);*/
+ /* Split "host:permissions:user:pass:groups" into fields. */
+ for (fields[0] = path, i = 0, p = path; *p; p++)
+ if (*p == ':') {
+ *p = '\0';
+ fields[++i] = p + 1;
+ }
+
+ PERMcanread = strchr(fields[1], 'R') != NULL;
+ PERMcanpost = strchr(fields[1], 'P') != NULL;
+ PERMaccessconf->allowapproved = strchr(fields[1], 'A') != NULL;
+ PERMaccessconf->locpost = strchr(fields[1], 'L') != NULL;
+ PERMaccessconf->allowihave = strchr(fields[1], 'I') != NULL;
+ if (strchr(fields[1], 'N') != NULL) PERMaccessconf->allownewnews = true;
+ snprintf(PERMuser, sizeof(PERMuser), "%s@%s", fields[2], fields[0]);
+ strlcpy(PERMpass, fields[3], sizeof(PERMpass));
+ strcpy(accesslist, fields[4]);
+ /*strcpy(writeaccess, fields[5]); future work? */
+
+ /*for (i = 0; fields[i] && i < 6; i++)
+ printf("fields[%d] = %s\n", i, fields[i]);*/
+
+ return 0;
+}
+
+/* ARGSUSED */
+void
+CMDauthinfo(ac, av)
+ int ac;
+ char *av[];
+{
+ static char User[SMBUF];
+ static char Password[SMBUF];
+ char accesslist[BIG_BUFFER];
+ char errorstr[BIG_BUFFER];
+
+ if (strcasecmp(av[1], "generic") == 0) {
+ char *logrec = Glom(av);
+
+ strlcpy(PERMuser, "<none>", sizeof(PERMuser));
+
+ switch (PERMgeneric(av, accesslist)) {
+ case 1:
+ PERMspecified = NGgetlist(&PERMreadlist, accesslist);
+ PERMpostlist = PERMreadlist;
+ syslog(L_NOTICE, "%s auth %s (%s -> %s)", ClientHost, PERMuser,
+ logrec, PERMauthstring? PERMauthstring: "" );
+ Reply("%d Authentication succeeded\r\n", NNTP_AUTH_OK_VAL);
+ PERMneedauth = false;
+ PERMauthorized = true;
+ free(logrec);
+ return;
+ case 0:
+ syslog(L_NOTICE, "%s bad_auth %s (%s)", ClientHost, PERMuser,
+ logrec);
+ Reply("%d Authentication failed\r\n", NNTP_ACCESS_VAL);
+ free(logrec);
+ ExitWithStats(1, false);
+ default:
+ /* lower level has issued Reply */
+ return;
+ }
+
+ } else {
+
+ if (strcasecmp(av[1], "simple") == 0) {
+ if (ac != 4) {
+ Reply("%d AUTHINFO SIMPLE <USER> <PASS>\r\n", NNTP_BAD_COMMAND_VAL);
+ return;
+ }
+ strlcpy(User, av[2], sizeof(User));
+ strlcpy(Password, av[3], sizeof(Password));
+ } else {
+ if (strcasecmp(av[1], "user") == 0) {
+ strlcpy(User, av[2], sizeof(User));
+ Reply("%d PASS required\r\n", NNTP_AUTH_NEXT_VAL);
+ return;
+ }
+
+ if (strcasecmp(av[1], "pass") != 0) {
+ Reply("%d bad authinfo param\r\n", NNTP_BAD_COMMAND_VAL);
+ return;
+ }
+ if (User[0] == '\0') {
+ Reply("%d USER required\r\n", NNTP_AUTH_REJECT_VAL);
+ return;
+ }
+
+ strlcpy(Password, av[2], sizeof(Password));
+ }
+
+ if (strcmp(User, PERMuser) == 0 && strcmp(Password, PERMpass) == 0) {
+ syslog(L_NOTICE, "%s user %s", ClientHost, PERMuser);
+ if (LLOGenable) {
+ fprintf(locallog, "%s user (%s):%s\n", ClientHost, Username, PERMuser);
+ fflush(locallog);
+ }
+ Reply("%d Ok\r\n", NNTP_AUTH_OK_VAL);
+ PERMneedauth = false;
+ PERMauthorized = true;
+ return;
+ }
+
+ errorstr[0] = '\0';
+
+ PERMlogin(User, Password, errorstr);
+ PERMgetpermissions();
+ if (!PERMneedauth) {
+ syslog(L_NOTICE, "%s user %s", ClientHost, PERMuser);
+ if (LLOGenable) {
+ fprintf(locallog, "%s user (%s):%s\n", ClientHost, Username, PERMuser);
+ fflush(locallog);
+ }
+ Reply("%d Ok\r\n", NNTP_AUTH_OK_VAL);
+ PERMneedauth = false;
+ PERMauthorized = true;
+ return;
+ }
+
+ syslog(L_NOTICE, "%s bad_auth", ClientHost);
+ if (errorstr[0] != '\0') {
+ syslog(L_NOTICE, "%s script error str: %s", ClientHost, errorstr);
+ Reply("%d %s\r\n", NNTP_ACCESS_VAL, errorstr);
+ } else {
+ Reply("%d Authentication error\r\n", NNTP_ACCESS_VAL);
+ }
+ ExitWithStats(1, false);
+ }
+
+}
+
+
+/*
+** The "DATE" command. Part of NNTPv2.
+*/
+/* ARGSUSED0 */
+void
+CMDdate(ac, av)
+ int ac UNUSED;
+ char *av[] UNUSED;
+{
+ TIMEINFO t;
+ struct tm *gmt;
+
+ if (GetTimeInfo(&t) < 0 || (gmt = gmtime(&t.time)) == NULL) {
+ Reply("%d Can't get time, %s\r\n", NNTP_TEMPERR_VAL, strerror(errno));
+ return;
+ }
+ Reply("%d %04.4d%02.2d%02.2d%02.2d%02.2d%02.2d\r\n",
+ NNTP_DATE_FOLLOWS_VAL,
+ gmt->tm_year + 1900, gmt->tm_mon + 1, gmt->tm_mday,
+ gmt->tm_hour, gmt->tm_min, gmt->tm_sec);
+}
+
+
+/*
+** Handle the "mode" command.
+*/
+/* ARGSUSED */
+void
+CMDmode(ac, av)
+ int ac UNUSED;
+ char *av[];
+{
+ if (strcasecmp(av[1], "reader") == 0)
+ Reply("%d %s InterNetNews NNRP server %s ready (%s).\r\n",
+ PERMcanpost ? NNTP_POSTOK_VAL : NNTP_NOPOSTOK_VAL,
+ PERMaccessconf->pathhost, inn_version_string,
+ PERMcanpost ? "posting ok" : "no posting");
+ else
+ Reply("%d What?\r\n", NNTP_SYNTAX_VAL);
+}
+
+static int GroupCompare(const void *a1, const void* b1) {
+ const GROUPDATA *a = a1;
+ const GROUPDATA *b = b1;
+
+ return strcmp(a->name, b->name);
+}
+
+/*
+** Display new newsgroups since a given date and time for specified
+** <distributions>.
+*/
+void CMDnewgroups(int ac, char *av[])
+{
+ char *p;
+ char *q;
+ QIOSTATE *qp;
+ time_t date;
+ char *grplist[2];
+ int hi, lo, count, flag;
+ GROUPDATA *grouplist = NULL;
+ GROUPDATA key;
+ GROUPDATA *gd;
+ int listsize = 0;
+ int numgroups = 0;
+ int numfound = 0;
+ int i;
+ bool local;
+
+ /* Parse the date. */
+ local = !(ac > 3 && strcasecmp(av[3], "GMT") == 0);
+ date = parsedate_nntp(av[1], av[2], local);
+ if (date == (time_t) -1) {
+ Reply("%d Bad date\r\n", NNTP_SYNTAX_VAL);
+ return;
+ }
+
+ /* Log an error if active.times doesn't exist, but don't return an error
+ to the client. The most likely cause of this is a new server
+ installation that's yet to have any new groups created, and returning
+ an error was causing needless confusion. Just return the empty list
+ of groups. */
+ if ((qp = QIOopen(ACTIVETIMES)) == NULL) {
+ syslog(L_ERROR, "%s cant fopen %s %m", ClientHost, ACTIVETIMES);
+ Reply("%d New newsgroups follow.\r\n", NNTP_NEWGROUPS_FOLLOWS_VAL);
+ Printf(".\r\n");
+ return;
+ }
+
+ /* Read the file, ignoring long lines. */
+ while ((p = QIOread(qp)) != NULL) {
+ if ((q = strchr(p, ' ')) == NULL)
+ continue;
+ *q++ = '\0';
+ if ((time_t) atol(q) < date)
+ continue;
+ if (!OVgroupstats(p, &lo, &hi, &count, &flag))
+ continue;
+
+ if (PERMspecified) {
+ grplist[0] = p;
+ grplist[1] = NULL;
+ if (!PERMmatch(PERMreadlist, grplist))
+ continue;
+ }
+ else
+ continue;
+
+ if (grouplist == NULL) {
+ grouplist = xmalloc(1000 * sizeof(GROUPDATA));
+ listsize = 1000;
+ }
+ if (listsize <= numgroups) {
+ listsize += 1000;
+ grouplist = xrealloc(grouplist, listsize * sizeof(GROUPDATA));
+ }
+
+ grouplist[numgroups].high = hi;
+ grouplist[numgroups].low = lo;
+ grouplist[numgroups].count = count;
+ grouplist[numgroups].name = xstrdup(p);
+ numgroups++;
+ }
+ QIOclose(qp);
+
+ if ((qp = QIOopen(ACTIVE)) == NULL) {
+ syslog(L_ERROR, "%s cant fopen %s %m", ClientHost, ACTIVE);
+ Reply("%d Cannot open active file.\r\n", NNTP_TEMPERR_VAL);
+ return;
+ }
+ qsort(grouplist, numgroups, sizeof(GROUPDATA), GroupCompare);
+ Reply("%d New newsgroups follow.\r\n", NNTP_NEWGROUPS_FOLLOWS_VAL);
+ for (numfound = numgroups; (p = QIOread(qp)) && numfound;) {
+ if ((q = strchr(p, ' ')) == NULL)
+ continue;
+ *q++ = '\0';
+ if ((q = strchr(q, ' ')) == NULL)
+ continue;
+ q++;
+ if ((q = strchr(q, ' ')) == NULL)
+ continue;
+ q++;
+ key.name = p;
+ if ((gd = bsearch(&key, grouplist, numgroups, sizeof(GROUPDATA), GroupCompare)) == NULL)
+ continue;
+ Printf("%s %u %u %s\r\n", p, gd->high, gd->low, q);
+ numfound--;
+ }
+ for (i = 0; i < numgroups; i++) {
+ free(grouplist[i].name);
+ }
+ free(grouplist);
+ QIOclose(qp);
+ Printf(".\r\n");
+}
+
+
+/*
+** Post an article.
+*/
+/* ARGSUSED */
+void
+CMDpost(int ac UNUSED, char *av[] UNUSED)
+{
+ static char *article;
+ static int size;
+ char *p, *q;
+ char *end;
+ int longline;
+ READTYPE r;
+ int i;
+ long l;
+ long sleeptime;
+ char *path;
+ const char *response;
+ char idbuff[SMBUF];
+ static int backoff_inited = false;
+ bool ihave, permanent;
+
+ ihave = (strcasecmp(av[0], "ihave") == 0);
+ if (ihave && (!PERMaccessconf->allowihave || !PERMcanpost)) {
+ syslog(L_NOTICE, "%s noperm ihave without permission", ClientHost);
+ Reply("%s\r\n", NNTP_ACCESS);
+ return;
+ }
+ if (!ihave && !PERMcanpost) {
+ syslog(L_NOTICE, "%s noperm post without permission", ClientHost);
+ Reply("%s\r\n", NNTP_CANTPOST);
+ return;
+ }
+
+ if (!backoff_inited) {
+ /* Exponential posting backoff */
+ InitBackoffConstants();
+ backoff_inited = true;
+ }
+
+ /* Dave's posting limiter - Limit postings to a certain rate
+ * And now we support multiprocess rate limits. Questions?
+ * Email dave@jetcafe.org.
+ */
+ if (BACKOFFenabled) {
+
+ /* Acquire lock (this could be in RateLimit but that would
+ * invoke the spaghetti factor).
+ */
+ if ((path = (char *) PostRecFilename(ClientIpString,PERMuser)) == NULL) {
+ Reply("%s\r\n", NNTP_CANTPOST);
+ return;
+ }
+
+ if (LockPostRec(path) == 0) {
+ syslog(L_ERROR, "%s Error write locking '%s'",
+ ClientHost, path);
+ Reply("%s\r\n", NNTP_CANTPOST);
+ return;
+ }
+
+ if (!RateLimit(&sleeptime,path)) {
+ syslog(L_ERROR, "%s can't check rate limit info", ClientHost);
+ Reply("%s\r\n", NNTP_CANTPOST);
+ UnlockPostRec(path);
+ return;
+ } else if (sleeptime != 0L) {
+ syslog(L_NOTICE,"%s post sleep time is now %ld", ClientHost, sleeptime);
+ sleep(sleeptime);
+ }
+
+ /* Remove the lock here so that only one nnrpd process does the
+ * backoff sleep at once. Other procs are sleeping for the lock.
+ */
+ UnlockPostRec(path);
+
+ } /* end backoff code */
+
+ /* Start at beginning of buffer. */
+ if (article == NULL) {
+ size = 4096;
+ article = xmalloc(size);
+ }
+ idbuff[0] = 0;
+ if (ihave) {
+ Reply(NNTP_SENDIT "\r\n");
+ } else {
+ if ((p = GenerateMessageID(PERMaccessconf->domain)) != NULL) {
+ if (VirtualPathlen > 0) {
+ q = p;
+ if ((p = strchr(p, '@')) != NULL) {
+ *p = '\0';
+ snprintf(idbuff, sizeof(idbuff), "%s%s@%s>", q,
+ NNRPinstance, PERMaccessconf->domain);
+ }
+ } else {
+ strlcpy(idbuff, p, sizeof(idbuff));
+ }
+ }
+ Reply("%d Ok, recommended ID %s\r\n", NNTP_START_POST_VAL, idbuff);
+ }
+ fflush(stdout);
+
+ p = article;
+ end = &article[size];
+
+ longline = 0;
+ for (l = 1; ; l++) {
+ size_t len;
+ const char *line;
+
+ r = line_read(&NNTPline, PERMaccessconf->clienttimeout, &line, &len);
+ switch (r) {
+ default:
+ warn("%s internal %d in post", ClientHost, r);
+ /* FALLTHROUGH */
+ case RTtimeout:
+ warn("%s timeout in post", ClientHost);
+ ExitWithStats(1, false);
+ /* NOTREACHED */
+ case RTeof:
+ warn("%s eof in post", ClientHost);
+ ExitWithStats(1, false);
+ /* NOTREACHED */
+ case RTlong:
+ if (longline == 0)
+ longline = l;
+ continue;
+ case RTok:
+ break;
+ }
+
+ /* if its the terminator, break out */
+ if (strcmp(line, ".") == 0) {
+ break;
+ }
+
+ /* if they broke our line length limit, there's little point
+ * in processing any more of their input */
+ if (longline != 0) {
+ continue;
+ }
+
+ /* +2 because of the \n\0 we append; note we don't add the 2
+ * when increasing the size of the buffer as ART_LINE_MALLOC
+ * will always be larger than 2 bytes */
+ if ((len + 2) > (size_t)(end - p)) {
+ i = p - article;
+ size += len + ART_LINE_MALLOC;
+ article = xrealloc(article, size);
+ end = &article[size];
+ p = i + article;
+ }
+
+ /* reverse any byte-stuffing */
+ if (*line == '.') {
+ ++line;
+ --len;
+ }
+ memcpy(p, line, len);
+ p += len;
+ *p++ = '\n';
+ *p = '\0';
+ }
+
+ if (longline) {
+ warn("%s toolong in post", ClientHost);
+ Printf("%d Line %d too long\r\n",
+ ihave ? NNTP_REJECTIT_VAL : NNTP_POSTFAIL_VAL, longline);
+ POSTrejected++;
+ return;
+ }
+
+ /* Send the article to the server. */
+ response = ARTpost(article, idbuff, ihave, &permanent);
+ if (response == NULL) {
+ notice("%s post ok %s", ClientHost, idbuff);
+ Reply("%s %s\r\n", ihave ? NNTP_TOOKIT : NNTP_POSTEDOK, idbuff);
+ POSTreceived++;
+ }
+ else {
+ if ((p = strchr(response, '\r')) != NULL)
+ *p = '\0';
+ if ((p = strchr(response, '\n')) != NULL)
+ *p = '\0';
+ notice("%s post failed %s", ClientHost, response);
+ if (!ihave || permanent) {
+ /* for permanent errors reject the message */
+ Reply("%d %s\r\n", ihave ? NNTP_REJECTIT_VAL : NNTP_POSTFAIL_VAL,
+ response);
+ } else {
+ /* non-permanent errors only have relevance to ihave, for
+ * these we have the error status from the upstream
+ * server to report */
+ Reply("%s\r\n", response);
+ }
+ POSTrejected++;
+ }
+}
+
+/*
+** The "xpath" command. An uncommon extension.
+*/
+/* ARGSUSED */
+void
+CMDxpath(ac, av)
+ int ac UNUSED;
+ char *av[] UNUSED;
+{
+ Reply("%d Syntax error or bad command\r\n", NNTP_BAD_COMMAND_VAL);
+}
--- /dev/null
+/* $Id: group.c 7538 2006-08-26 05:44:06Z eagle $
+**
+** Newsgroups and the active file.
+*/
+
+#include "config.h"
+#include "clibrary.h"
+
+#include "inn/innconf.h"
+#include "nnrpd.h"
+#include "ov.h"
+
+/*
+** Change to or list the specified newsgroup. If invalid, stay in the old
+** group.
+*/
+void CMDgroup(int ac, char *av[])
+{
+ static char NOSUCHGROUP[] = NNTP_NOSUCHGROUP;
+ ARTNUM i;
+ char *grplist[2];
+ char *group;
+ void *handle;
+ TOKEN token;
+ int count;
+ bool boolval;
+ bool hookpresent = false;
+
+#ifdef DO_PYTHON
+ hookpresent = PY_use_dynamic;
+#endif /* DO_PYTHON */
+
+ if (!hookpresent && !PERMcanread) {
+ if (PERMspecified)
+ Reply("%d Permission denied\r\n", NNTP_ACCESS_VAL);
+ else
+ Reply("%d Authentication required\r\n", NNTP_AUTH_NEEDED_VAL);
+ return;
+ }
+
+ /* Parse arguments. */
+ if (ac == 1) {
+ if (GRPcur == NULL) {
+ Printf("%d No group specified\r\n", NNTP_XGTITLE_BAD);
+ return;
+ } else {
+ group = xstrdup(GRPcur);
+ }
+ } else {
+ group = xstrdup(av[1]);
+ }
+
+ if (!OVgroupstats(group, &ARTlow, &ARThigh, &count, NULL)) {
+ Reply("%s %s\r\n", NOSUCHGROUP, group);
+ free(group);
+ return;
+ }
+
+#ifdef DO_PYTHON
+ if (PY_use_dynamic) {
+ char *reply;
+
+ /* Authorize user using Python module method dynamic*/
+ if (PY_dynamic(PERMuser, group, false, &reply) < 0) {
+ syslog(L_NOTICE, "PY_dynamic(): authorization skipped due to no Python dynamic method defined.");
+ } else {
+ if (reply != NULL) {
+ syslog(L_TRACE, "PY_dynamic() returned a refuse string for user %s at %s who wants to read %s: %s", PERMuser, ClientHost, group, reply);
+ Reply("%d %s\r\n", NNTP_ACCESS_VAL, reply);
+ free(group);
+ free(reply);
+ return;
+ }
+ }
+ }
+#endif /* DO_PYTHON */
+
+ if (!hookpresent) {
+ if (PERMspecified) {
+ grplist[0] = group;
+ grplist[1] = NULL;
+ if (!PERMmatch(PERMreadlist, grplist)) {
+ Reply("%d Permission denied\r\n", NNTP_ACCESS_VAL);
+ free(group);
+ return;
+ }
+ } else {
+ Reply("%d Authentication required\r\n", NNTP_AUTH_NEEDED_VAL);
+ free(group);
+ return;
+ }
+ }
+
+ /* Close out any existing article, report group stats. */
+ ARTclose();
+ GRPreport();
+
+ /* Doing a "group" command? */
+ if (strcasecmp(av[0], "group") == 0) {
+ if (count == 0)
+ Reply("%d 0 0 0 %s\r\n", NNTP_GROUPOK_VAL, group);
+ else {
+ /* if we're an NFS reader, check the last nfsreaderdelay
+ * articles in the group to see if they arrived in the
+ * last nfsreaderdelay (default 60) seconds. If they did,
+ * don't report them as we don't want them to appear too
+ * soon */
+ if (innconf->nfsreader) {
+ ARTNUM low, prev;
+ time_t now, arrived;
+
+ time(&now);
+ if (ARTlow + innconf->nfsreaderdelay > ARThigh)
+ low = ARTlow;
+ else
+ low = ARThigh - innconf->nfsreaderdelay;
+ handle = OVopensearch(group, low, ARThigh);
+ if (!handle) {
+ Reply("%d group disappeared\r\n", NNTP_TEMPERR_VAL);
+ free(group);
+ return;
+ }
+ prev = low;
+ while (OVsearch(handle, &i, NULL, NULL, NULL, &arrived)) {
+ if (arrived + innconf->nfsreaderdelay > now) {
+ ARThigh = prev;
+ break;
+ }
+ prev = i;
+ }
+ OVclosesearch(handle);
+ }
+ Reply("%d %d %lu %lu %s\r\n", NNTP_GROUPOK_VAL, count,
+ (unsigned long) ARTlow, (unsigned long) ARThigh, group);
+ }
+ GRPcount++;
+ ARTnumber = ARTlow;
+ if (GRPcur) {
+ if (strcmp(GRPcur, group) != 0) {
+ OVctl(OVCACHEFREE, &boolval);
+ free(GRPcur);
+ GRPcur = xstrdup(group);
+ }
+ } else
+ GRPcur = xstrdup(group);
+ } else {
+ /* Must be doing a "listgroup" command. We used to just return
+ something bland here ("Article list follows"), but reference NNTP
+ returns the same data as GROUP does and since we have it all
+ available it shouldn't hurt to return the same thing. */
+ if (count == 0) {
+ Reply("%d 0 0 0 %s\r\n", NNTP_GROUPOK_VAL, group);
+ Printf(".\r\n");
+ } else if ((handle = OVopensearch(group, ARTlow, ARThigh)) != NULL) {
+ Reply("%d %d %lu %lu %s\r\n", NNTP_GROUPOK_VAL, count,
+ (unsigned long) ARTlow, (unsigned long) ARThigh, group);
+ while (OVsearch(handle, &i, NULL, NULL, &token, NULL)) {
+ if (PERMaccessconf->nnrpdcheckart && !ARTinstorebytoken(token))
+ continue;
+ Printf("%lu\r\n", (unsigned long) i);
+ }
+ OVclosesearch(handle);
+ Printf(".\r\n");
+ GRPcount++;
+ ARTnumber = ARTlow;
+ if (GRPcur) {
+ if (strcmp(GRPcur, group) != 0) {
+ OVctl(OVCACHEFREE, &boolval);
+ free(GRPcur);
+ GRPcur = xstrdup(group);
+ }
+ } else
+ GRPcur = xstrdup(group);
+ } else {
+ Reply("%s %s\r\n", NOSUCHGROUP, group);
+ }
+ }
+ free(group);
+}
+
+
+/*
+** Report on the number of articles read in the group, and clear the count.
+*/
+void
+GRPreport()
+{
+ char buff[SPOOLNAMEBUFF];
+ char repbuff[1024];
+
+ if (GRPcur) {
+ strlcpy(buff, GRPcur, sizeof(buff));
+ syslog(L_NOTICE, "%s group %s %lu", ClientHost, buff,
+ (unsigned long) GRParticles);
+ GRParticles = 0;
+ repbuff[0]='\0';
+ }
+}
+
+
+/*
+** Used by ANU-News clients.
+*/
+void
+CMDxgtitle(ac, av)
+ int ac;
+ char *av[];
+{
+ QIOSTATE *qp;
+ char *line;
+ char *p;
+ char *q;
+ char *grplist[2];
+ char save;
+
+ /* Parse the arguments. */
+ if (ac == 1) {
+ if (GRPcount == 0) {
+ Printf("%d No group specified\r\n", NNTP_XGTITLE_BAD);
+ return;
+ }
+ p = GRPcur;
+ }
+ else
+ p = av[1];
+
+ if (!PERMspecified) {
+ Printf("%d list follows\r\n", NNTP_XGTITLE_OK);
+ Printf(".\r\n");
+ return;
+ }
+
+ /* Open the file, get ready to scan. */
+ if ((qp = QIOopen(NEWSGROUPS)) == NULL) {
+ syslog(L_ERROR, "%s cant open %s %m", ClientHost, NEWSGROUPS);
+ Printf("%d Can't open %s\r\n", NNTP_XGTITLE_BAD, NEWSGROUPS);
+ return;
+ }
+ Printf("%d list follows\r\n", NNTP_XGTITLE_OK);
+
+ /* Print all lines with matching newsgroup name. */
+ while ((line = QIOread(qp)) != NULL) {
+ for (q = line; *q && !ISWHITE(*q); q++)
+ continue;
+ save = *q;
+ *q = '\0';
+ if (uwildmat(line, p)) {
+ if (PERMspecified) {
+ grplist[0] = line;
+ grplist[1] = NULL;
+ if (!PERMmatch(PERMreadlist, grplist))
+ continue;
+ }
+ *q = save;
+ Printf("%s\r\n", line);
+ }
+ }
+
+ /* Done. */
+ QIOclose(qp);
+ Printf(".\r\n");
+}
--- /dev/null
+/* $Id: line.c 7837 2008-05-19 17:14:15Z iulius $
+**
+** Line by line reading support from sockets/pipes
+**
+** Written by Alex Kiernan (alex.kiernan@thus.net)
+**
+** This code implements a infinitely (well size_t) long single line
+** read routine, to protect against eating all available memory it
+** actually starts discarding characters if you try to send more than
+** the maximum article size in a single line.
+**
+*/
+
+#include "config.h"
+#include "clibrary.h"
+#include <assert.h>
+#ifdef HAVE_SYS_SELECT_H
+# include <sys/select.h>
+#endif
+
+#include "inn/messages.h"
+#include "nnrpd.h"
+
+#ifdef HAVE_SSL
+#include <openssl/ssl.h>
+#include <signal.h>
+extern SSL *tls_conn;
+#endif
+
+/*
+** free a previously allocated line structure
+*/
+void
+line_free(struct line *line)
+{
+ static const struct line nullline = {0, 0, 0, 0};
+
+ if (line && line->start) {
+ free(line->start);
+ *line = nullline;
+ }
+}
+
+#ifdef HAVE_SSL
+/*
+** Alarm signal handler for client timeout.
+*/
+static void
+alarmHandler(int s)
+{
+ SSL_shutdown(tls_conn);
+ tls_conn = NULL;
+ errno = ECONNRESET;
+}
+#endif
+
+/*
+** initialise a new line structure
+*/
+void
+line_init(struct line *line)
+{
+ assert(line);
+ line->allocated = NNTP_STRLEN;
+ line->where = line->start = xmalloc(line->allocated);
+ line->remaining = 0;
+}
+
+static ssize_t
+line_doread(void *p, size_t len, int timeout)
+{
+ ssize_t n;
+
+#ifdef HAVE_SSL
+ if (tls_conn) {
+ int err;
+ xsignal(SIGALRM, alarmHandler);
+ do {
+ alarm(timeout);
+ n = SSL_read(tls_conn, p, len);
+ alarm(0);
+ if (tls_conn == NULL) {
+ break;
+ }
+ err = SSL_get_error(tls_conn, n);
+ switch (err) {
+ case SSL_ERROR_SYSCALL:
+ break;
+
+ case SSL_ERROR_SSL:
+ SSL_shutdown(tls_conn);
+ tls_conn = NULL;
+ errno = ECONNRESET;
+ break;
+ }
+ } while (err == SSL_ERROR_WANT_READ);
+ xsignal(SIGALRM, SIG_DFL);
+ } else {
+#endif
+ do {
+ n = read(STDIN_FILENO, p, len);
+ } while (n == -1 && errno == EINTR);
+#ifdef HAVE_SSL
+ }
+#endif
+ return n;
+}
+
+READTYPE
+line_read(struct line *line, int timeout, const char **p, size_t *len)
+{
+ char *where;
+ char *lf = NULL;
+ READTYPE r = RTok;
+
+ assert(line != NULL);
+ assert(line->start != NULL);
+ /* shuffle any trailing portion not yet processed to the start of
+ * the buffer */
+ if (line->remaining != 0) {
+ if (line->start != line->where) {
+ memmove(line->start, line->where, line->remaining);
+ }
+ lf = memchr(line->start, '\n', line->remaining);
+ }
+ where = line->start + line->remaining;
+
+ /* if we found a line terminator in the data we have we don't need
+ * to ask for any more */
+ if (lf == NULL) {
+ do {
+ fd_set rmask;
+ int i;
+ ssize_t count;
+
+ /* if we've filled the line buffer, double the size,
+ * reallocate the buffer and try again */
+ if (where == line->start + line->allocated) {
+ size_t newsize = line->allocated * 2;
+
+ /* don't grow the buffer bigger than the maximum
+ * article size we'll accept */
+ if (PERMaccessconf->localmaxartsize > NNTP_STRLEN)
+ if (newsize > (unsigned)PERMaccessconf->localmaxartsize)
+ newsize = PERMaccessconf->localmaxartsize;
+
+ /* if we're trying to grow from the same size, to the
+ * same size, we must have hit the localmaxartsize
+ * buffer for a second (or subsequent) time - the user
+ * is likely trying to DOS us, so don't double the
+ * size any more, just overwrite characters until they
+ * stop, then discard the whole thing */
+ if (newsize == line->allocated) {
+ warn("%s overflowed our line buffer (%ld), "
+ "discarding further input", ClientHost,
+ PERMaccessconf->localmaxartsize);
+ where = line->start;
+ r = RTlong;
+ } else {
+ line->start = xrealloc(line->start, newsize);
+ where = line->start + line->allocated;
+ line->allocated = newsize;
+ }
+ }
+
+#ifdef HAVE_SSL
+ /* It seems that the SSL_read cannot be mixed with select()
+ * as in the current code. SSL communicates in its own data
+ * blocks and hand shaking. The do_readline using SSL_read
+ * could return, but still with a partial line in the SSL_read
+ * buffer. Then the server SSL routine would sit there waiting
+ * for completion of that data block while nnrpd sat at the
+ * select() routine waiting for more data from the server.
+ *
+ * Here, we decide to just bypass the select() wait. Unlike
+ * innd with multiple threads, the select on nnrpd is just
+ * waiting on a single file descriptor, so it is not really
+ * essential with blocked read like SSL_read. Using an alarm
+ * signal around SSL_read for non active timeout, SSL works
+ * without dead locks. However, without the select() wait,
+ * the IDLE timer stat won't be collected...
+ */
+ if (tls_conn == NULL) {
+#endif
+ /* Wait for activity on stdin, updating timer stats as we
+ * go. */
+ do {
+ struct timeval t;
+
+ FD_ZERO(&rmask);
+ FD_SET(STDIN_FILENO, &rmask);
+ t.tv_sec = timeout;
+ t.tv_usec = 0;
+ TMRstart(TMR_IDLE);
+ i = select(STDIN_FILENO + 1, &rmask, NULL, NULL, &t);
+ TMRstop(TMR_IDLE);
+ if (i == -1 && errno != EINTR) {
+ syswarn("%s can't select", ClientHost);
+ return RTtimeout;
+ }
+ } while (i == -1);
+
+ /* If stdin didn't select, we must have timed out. */
+ if (i == 0 || !FD_ISSET(STDIN_FILENO, &rmask))
+ return RTtimeout;
+#ifdef HAVE_SSL
+ }
+#endif
+ count = line_doread(where,
+ line->allocated - (where - line->start),
+ timeout);
+
+ /* give timeout for read errors */
+ if (count < 0) {
+ sysnotice("%s can't read", ClientHost);
+ return RTtimeout;
+ }
+ /* if we hit EOF, terminate the string and send it back */
+ if (count == 0) {
+ assert((where + count) < (line->start + line->allocated));
+ where[count] = '\0';
+ return RTeof;
+ }
+ /* search for `\n' in what we just read, if we find it we'll
+ * drop out and return the line for processing */
+ lf = memchr(where, '\n', count);
+ where += count;
+ } while (lf == NULL);
+ }
+
+ /* remember where we've processed up to so we can start off there
+ * next time */
+ line->where = lf + 1;
+ line->remaining = where - line->where;
+
+ if (r == RTok) {
+ /* if we see a full CRLF pair strip them both off before
+ * returning the line to our caller, if we just get an LF
+ * we'll accept that too */
+ if (lf > line->start && lf[-1] == '\r') {
+ --lf;
+ }
+ *lf = '\0';
+ *len = lf - line->start;
+ *p = line->start;
+ }
+ return r;
+}
--- /dev/null
+/* $Id: list.c 7731 2008-04-06 08:40:29Z iulius $
+**
+** List commands.
+*/
+
+#include "config.h"
+#include "clibrary.h"
+
+#include "nnrpd.h"
+#include "ov.h"
+#include "inn/innconf.h"
+#include "inn/messages.h"
+
+typedef struct _LISTINFO {
+ const char *method;
+ const char * File;
+ void (*impl)(struct _LISTINFO *);
+ bool Required;
+ const char * Items;
+ const char * Format;
+} LISTINFO;
+
+static void cmd_list_schema(LISTINFO *lp);
+static void cmd_list_extensions(LISTINFO *lp);
+
+static LISTINFO INFOactive = {
+ "active", _PATH_ACTIVE, NULL, true, "active newsgroups",
+ "Newsgroups in form \"group high low flags\""
+};
+static LISTINFO INFOactivetimes = {
+ "active.times", _PATH_ACTIVETIMES, NULL, false, "creation times",
+ "Group creations in form \"name time who\""
+};
+static LISTINFO INFOdistribs = {
+ "distributions", _PATH_NNRPDIST, NULL, false, "newsgroup distributions",
+ "Distributions in form \"area description\""
+};
+static LISTINFO INFOsubs = {
+ "subscriptions", _PATH_NNRPSUBS, NULL, false, "automatic group subscriptions",
+ "Subscriptions in form \"group\""
+};
+static LISTINFO INFOdistribpats = {
+ "distrib.pats", _PATH_DISTPATS, NULL, false, "distribution patterns",
+ "Default distributions in form \"weight:pattern:value\""
+};
+static LISTINFO INFOextensions = {
+ "extensions", NULL, cmd_list_extensions, false, "supported extensions",
+ "Supported NNTP extensions"
+};
+static LISTINFO INFOgroups = {
+ "newsgroups", _PATH_NEWSGROUPS, NULL, false, "newsgroup descriptions",
+ "Descriptions in form \"group description\""
+};
+static LISTINFO INFOmoderators = {
+ "moderators", _PATH_MODERATORS, NULL, false, "moderator patterns",
+ "Newsgroup moderators in form \"group-pattern:mail-address-pattern\""
+};
+static LISTINFO INFOschema = {
+ "overview.fmt", NULL, cmd_list_schema, true, "overview format",
+ "Order of fields in overview database"
+};
+static LISTINFO INFOmotd = {
+ "motd", _PATH_MOTD, NULL, false, "motd",
+ "Message of the day text"
+};
+
+static LISTINFO *info[] = {
+ &INFOactive,
+ &INFOactivetimes,
+ &INFOdistribs,
+ &INFOsubs,
+ &INFOdistribpats,
+ &INFOextensions,
+ &INFOgroups,
+ &INFOmoderators,
+ &INFOschema,
+ &INFOmotd,
+};
+
+
+/*
+** List the overview schema
+*/
+static void
+cmd_list_schema(LISTINFO *lp)
+{
+ const struct cvector *standard;
+ unsigned int i;
+
+ Reply("%d %s.\r\n", NNTP_LIST_FOLLOWS_VAL, lp->Format);
+ standard = overview_fields();
+ for (i = 0; i < standard->count; ++i) {
+ Printf("%s:\r\n", standard->strings[i]);
+ }
+ for (i = 0; i < OVextra->count; ++i) {
+ Printf("%s:full\r\n", OVextra->strings[i]);
+ }
+ Printf(".\r\n");
+}
+
+
+/*
+** List supported extensions
+*/
+static void
+cmd_list_extensions(LISTINFO *lp)
+{
+ Reply("%d %s.\r\n", NNTP_SLAVEOK_VAL, lp->Format);
+ if (PERMauthorized != true)
+ Printf("AUTHINFO USER\r\n");
+ Printf("LISTGROUP\r\n");
+ Printf(".\r\n");
+}
+
+
+/*
+** List a single newsgroup. Called by LIST ACTIVE with a single argument.
+** This is quicker than parsing the whole active file, but only works with
+** single groups. It also doesn't work for aliased groups, since overview
+** doesn't know what group the group is aliased to (yet). Returns whether we
+** were able to answer the command.
+*/
+static bool
+CMD_list_single(char *group)
+{
+ char *grplist[2] = { NULL, NULL };
+ int lo, hi, flag;
+
+ if (PERMspecified) {
+ grplist[0] = group;
+ if (!PERMmatch(PERMreadlist, grplist))
+ return false;
+ }
+ if (OVgroupstats(group, &lo, &hi, NULL, &flag) && flag != '=') {
+ Reply("%d %s.\r\n", NNTP_LIST_FOLLOWS_VAL, INFOactive.Format);
+ Printf("%s %010u %010u %c\r\n.\r\n", group, hi, lo, flag);
+ return true;
+ }
+ return false;
+}
+
+
+/*
+** List active newsgroups, newsgroup descriptions, and distributions.
+*/
+void
+CMDlist(int ac, char *av[])
+{
+ QIOSTATE *qp;
+ char *p;
+ char *save;
+ char *path;
+ char *q;
+ char *grplist[2];
+ LISTINFO *lp;
+ char *wildarg = NULL;
+ char savec;
+ unsigned int i;
+
+ p = av[1];
+ if (p == NULL) {
+ lp = &INFOactive;
+ } else {
+ lp = NULL;
+ for (i = 0; i < ARRAY_SIZE(info); ++i) {
+ if (strcasecmp(p, info[i]->method) == 0) {
+ lp = info[i];
+ break;
+ }
+ }
+ }
+ if (lp == NULL) {
+ Reply("%s\r\n", NNTP_SYNTAX_USE);
+ return;
+ }
+ if (lp == &INFOactive) {
+ if (ac == 3) {
+ wildarg = av[2];
+ if (CMD_list_single(wildarg))
+ return;
+ }
+ } else if (lp == &INFOgroups || lp == &INFOactivetimes) {
+ if (ac == 3)
+ wildarg = av[2];
+ }
+
+ if (ac > 2 && !wildarg) {
+ Reply("%s\r\n", NNTP_SYNTAX_USE);
+ return;
+ }
+
+ if (lp->impl != NULL) {
+ lp->impl(lp);
+ return;
+ }
+
+ path = innconf->pathetc;
+ if ((strstr(lp->File, "active") != NULL) ||
+ (strstr(lp->File, "newsgroups") != NULL))
+ path = innconf->pathdb;
+ if (strchr(lp->File, '/') != NULL)
+ path = "";
+ path = concatpath(path, lp->File);
+ qp = QIOopen(path);
+ free(path);
+ if (qp == NULL) {
+ Reply("%d No list of %s available.\r\n",
+ NNTP_TEMPERR_VAL, lp->Items);
+ if (lp->Required || errno != ENOENT) {
+ syslog(L_ERROR, "%s cant fopen %s %m", ClientHost, lp->File);
+ }
+ return;
+ }
+
+ Reply("%d %s.\r\n", NNTP_LIST_FOLLOWS_VAL, lp->Format);
+ if (!PERMspecified) {
+ /* Optmize for unlikely case of no permissions and false default. */
+ QIOclose(qp);
+ Printf(".\r\n");
+ return;
+ }
+
+ /* Set up group list terminator. */
+ grplist[1] = NULL;
+
+ /* Read lines, ignore long ones. */
+ while ((p = QIOread(qp)) != NULL) {
+ if (lp == &INFOmotd) {
+ Printf("%s\r\n", p);
+ continue;
+ }
+ if (p[0] == '.' && p[1] == '\0') {
+ syslog(L_ERROR, "%s single dot in %s", ClientHost, lp->File);
+ continue;
+ }
+ /* matching patterns against patterns is not that
+ good but it's better than nothing ... */
+ if (lp == &INFOdistribpats) {
+ if (*p == '\0' || *p == '#' || *p == ';' || *p == ' ')
+ continue;
+ if (PERMspecified) {
+ if ((q = strchr(p, ':')) == NULL)
+ continue;
+ q++;
+ if ((save = strchr(q, ':')) == NULL)
+ continue;
+ *save = '\0';
+ grplist[0] = q;
+ if (!PERMmatch(PERMreadlist, grplist))
+ continue;
+ *save = ':';
+ }
+ Printf("%s\r\n", p);
+ continue;
+ }
+ if (lp == &INFOdistribs || lp == &INFOmoderators) {
+ if (*p != '\0' && *p != '#' && *p != ';' && *p != ' ')
+ Printf("%s\r\n", p);
+ continue;
+ }
+ savec = '\0';
+ for (save = p; *save != '\0'; save++) {
+ if (*save == ' ' || *save == '\t') {
+ savec = *save;
+ *save = '\0';
+ break;
+ }
+ }
+
+ if (PERMspecified) {
+ grplist[0] = p;
+ if (!PERMmatch(PERMreadlist, grplist))
+ continue;
+ }
+ if (wildarg && !uwildmat(p, wildarg))
+ continue;
+ if (savec != '\0')
+ *save = savec;
+ Printf("%s\r\n", p);
+ }
+ QIOclose(qp);
+
+ Printf(".\r\n");
+}
--- /dev/null
+/* $Id: misc.c 6535 2003-12-10 09:02:22Z rra $
+**
+** Miscellaneous support routines.
+*/
+
+#include "config.h"
+#include "clibrary.h"
+
+/* Needed on AIX 4.1 to get fd_set and friends. */
+#ifdef HAVE_SYS_SELECT_H
+# include <sys/select.h>
+#endif
+
+#include "inn/innconf.h"
+#include "nnrpd.h"
+#include "tls.h"
+#include "sasl_config.h"
+
+#ifdef HAVE_SSL
+extern SSL *tls_conn;
+extern int nnrpd_starttls_done;
+#endif
+
+
+/*
+** Parse a string into a NULL-terminated array of words; return number
+** of words. If argvp isn't NULL, it and what it points to will be freed.
+*/
+int
+Argify(line, argvp)
+ char *line;
+ char ***argvp;
+{
+ char **argv;
+ char *p;
+
+ if (*argvp != NULL) {
+ free(*argvp[0]);
+ free(*argvp);
+ }
+
+ /* Copy the line, which we will split up. */
+ while (ISWHITE(*line))
+ line++;
+ p = xstrdup(line);
+
+ /* Allocate worst-case amount of space. */
+ for (*argvp = argv = xmalloc((strlen(p) + 2) * sizeof(char *)); *p; ) {
+ /* Mark start of this word, find its end. */
+ for (*argv++ = p; *p && !ISWHITE(*p); )
+ p++;
+ if (*p == '\0')
+ break;
+
+ /* Nip off word, skip whitespace. */
+ for (*p++ = '\0'; ISWHITE(*p); )
+ p++;
+ }
+ *argv = NULL;
+ return argv - *argvp;
+}
+
+
+/*
+** Take a vector which Argify made and glue it back together with
+** spaces between each element. Returns a pointer to dynamic space.
+*/
+char *
+Glom(av)
+ char **av;
+{
+ char **v;
+ int i;
+ char *save;
+
+ /* Get space. */
+ for (i = 0, v = av; *v; v++)
+ i += strlen(*v) + 1;
+ i++;
+
+ save = xmalloc(i);
+ save[0] = '\0';
+ for (v = av; *v; v++) {
+ if (v > av)
+ strlcat(save, " ", i);
+ strlcat(save, *v, i);
+ }
+
+ return save;
+}
+
+
+/*
+** Match a list of newsgroup specifiers against a list of newsgroups.
+** func is called to see if there is a match.
+*/
+bool PERMmatch(char **Pats, char **list)
+{
+ int i;
+ char *p;
+ int match = false;
+
+ if (Pats == NULL || Pats[0] == NULL)
+ return true;
+
+ for ( ; *list; list++) {
+ for (i = 0; (p = Pats[i]) != NULL; i++) {
+ if (p[0] == '!') {
+ if (uwildmat(*list, ++p))
+ match = false;
+ }
+ else if (uwildmat(*list, p))
+ match = true;
+ }
+ if (match)
+ /* If we can read it in one group, we can read it, period. */
+ return true;
+ }
+
+ return false;
+}
+
+
+/*
+** Check to see if user is allowed to see this article by matching
+** Newsgroups line.
+*/
+bool
+PERMartok(void)
+{
+ static char **grplist;
+ char *p, **grp;
+
+ if (!PERMspecified)
+ return false;
+
+ if ((p = GetHeader("Xref")) == NULL) {
+ /* in case article does not include Xref */
+ if ((p = GetHeader("Newsgroups")) != NULL) {
+ if (!NGgetlist(&grplist, p))
+ /* No newgroups or null entry. */
+ return true;
+ } else {
+ return true;
+ }
+ } else {
+ /* skip path element */
+ if ((p = strchr(p, ' ')) == NULL)
+ return true;
+ for (p++ ; *p == ' ' ; p++);
+ if (*p == '\0')
+ return true;
+ if (!NGgetlist(&grplist, p))
+ /* No newgroups or null entry. */
+ return true;
+ /* chop ':' and article number */
+ for (grp = grplist ; *grp != NULL ; grp++) {
+ if ((p = strchr(*grp, ':')) == NULL)
+ return true;
+ *p = '\0';
+ }
+ }
+
+#ifdef DO_PYTHON
+ if (PY_use_dynamic) {
+ char *reply;
+
+ /* Authorize user at a Python authorization module */
+ if (PY_dynamic(PERMuser, p, false, &reply) < 0) {
+ syslog(L_NOTICE, "PY_dynamic(): authorization skipped due to no Python dynamic method defined.");
+ } else {
+ if (reply != NULL) {
+ syslog(L_TRACE, "PY_dynamic() returned a refuse string for user %s at %s who wants to read %s: %s", PERMuser, ClientHost, p, reply);
+ free(reply);
+ return false;
+ }
+ return true;
+ }
+ }
+#endif /* DO_PYTHON */
+
+ return PERMmatch(PERMreadlist, grplist);
+}
+
+
+/*
+** Parse a newsgroups line, return true if there were any.
+*/
+bool
+NGgetlist(argvp, list)
+ char ***argvp;
+ char *list;
+{
+ char *p;
+
+ for (p = list; *p; p++)
+ if (*p == ',')
+ *p = ' ';
+
+ return Argify(list, argvp) != 0;
+}
+
+
+/*********************************************************************
+ * POSTING RATE LIMITS - The following code implements posting rate
+ * limits. News clients are indexed by IP number (or PERMuser, see
+ * config file). After a relatively configurable number of posts, the nnrpd
+ * process will sleep for a period of time before posting anything.
+ *
+ * Each time that IP number posts a message, the time of
+ * posting and the previous sleep time is stored. The new sleep time
+ * is computed based on these values.
+ *
+ * To compute the new sleep time, the previous sleep time is, for most
+ * cases multiplied by a factor (backoff_k).
+ *
+ * See inn.conf(5) for how this code works
+ *
+ *********************************************************************/
+
+/* Defaults are pass through, i.e. not enabled
+ * NEW for INN 1.8 - Use the inn.conf file to specify the following:
+ *
+ * backoff_k: <integer>
+ * backoff_postfast: <integer>
+ * backoff_postslow: <integer>
+ * backoff_trigger: <integer>
+ * backoff_db: <path>
+ * backoff_auth: <on|off>
+ *
+ * You may also specify posting backoffs on a per user basis. To do this
+ * turn on "backoff_auth"
+ *
+ * Now these are runtime constants. <grin>
+ */
+static char postrec_dir[SMBUF]; /* Where is the post record directory? */
+
+void
+InitBackoffConstants()
+{
+ struct stat st;
+
+ /* Default is not to enable this code */
+ BACKOFFenabled = false;
+
+ /* Read the runtime config file to get parameters */
+
+ if ((PERMaccessconf->backoff_db == NULL) ||
+ !(PERMaccessconf->backoff_k >= 0L && PERMaccessconf->backoff_postfast >= 0L && PERMaccessconf->backoff_postslow >= 1L))
+ return;
+
+ /* Need this database for backing off */
+ strlcpy(postrec_dir, PERMaccessconf->backoff_db, sizeof(postrec_dir));
+ if (stat(postrec_dir, &st) < 0) {
+ if (ENOENT == errno) {
+ if (!MakeDirectory(postrec_dir, true)) {
+ syslog(L_ERROR, "%s cannot create backoff_db '%s': %s",ClientHost,postrec_dir,strerror(errno));
+ return;
+ }
+ } else {
+ syslog(L_ERROR, "%s cannot stat backoff_db '%s': %s",ClientHost,postrec_dir,strerror(errno));
+ return;
+ }
+ }
+ if (!S_ISDIR(st.st_mode)) {
+ syslog(L_ERROR, "%s backoff_db '%s' is not a directory",ClientHost,postrec_dir);
+ return;
+ }
+
+ BACKOFFenabled = true;
+
+ return;
+}
+
+/*
+ * PostRecs are stored in individual files. I didn't have a better
+ * way offhand, don't want to touch DBZ, and the number of posters is
+ * small compared to the number of readers. This is the filename corresponding
+ * to an IP number.
+ */
+char
+*PostRecFilename(ip,user)
+ char *ip;
+ char *user;
+{
+ static char buff[SPOOLNAMEBUFF];
+ char dirbuff[SPOOLNAMEBUFF];
+ struct in_addr inaddr;
+ unsigned long int addr;
+ unsigned char quads[4];
+ unsigned int i;
+
+ if (PERMaccessconf->backoff_auth) {
+ snprintf(buff, sizeof(buff), "%s/%s", postrec_dir, user);
+ return(buff);
+ }
+
+ if (inet_aton(ip, &inaddr) < 1) {
+ /* If inet_aton() fails, we'll assume it's an IPv6 address. We'll
+ * also assume for now that we're dealing with a limited number of
+ * IPv6 clients so we'll place their files all in the same
+ * directory for simplicity. Someday we'll need to change this to
+ * something more scalable such as DBZ when IPv6 clients become
+ * more popular. */
+ snprintf(buff, sizeof(buff), "%s/%s", postrec_dir, ip);
+ return(buff);
+ }
+ /* If it's an IPv4 address just fall through. */
+
+ addr = ntohl(inaddr.s_addr);
+ for (i=0; i<4; i++)
+ quads[i] = (unsigned char) (0xff & (addr>>(i*8)));
+
+ snprintf(dirbuff, sizeof(dirbuff), "%s/%03d%03d/%03d",
+ postrec_dir, quads[3], quads[2], quads[1]);
+ if (!MakeDirectory(dirbuff,true)) {
+ syslog(L_ERROR, "%s Unable to create postrec directories '%s': %s",
+ ClientHost, dirbuff, strerror(errno));
+ return NULL;
+ }
+ snprintf(buff, sizeof(buff), "%s/%03d", dirbuff, quads[0]);
+ return(buff);
+}
+
+/*
+ * Lock the post rec file. Return 1 on lock, 0 on error
+ */
+int
+LockPostRec(path)
+ char *path;
+{
+ char lockname[SPOOLNAMEBUFF];
+ char temp[SPOOLNAMEBUFF];
+ int statfailed = 0;
+
+ snprintf(lockname, sizeof(lockname), "%s.lock", path);
+
+ for (;; sleep(5)) {
+ int fd;
+ struct stat st;
+ time_t now;
+
+ fd = open(lockname, O_WRONLY|O_EXCL|O_CREAT, 0600);
+ if (fd >= 0) {
+ /* We got the lock! */
+ snprintf(temp, sizeof(temp), "pid:%ld\n", (unsigned long) getpid());
+ write(fd, temp, strlen(temp));
+ close(fd);
+ return(1);
+ }
+
+ /* No lock. See if the file is there. */
+ if (stat(lockname, &st) < 0) {
+ syslog(L_ERROR, "%s cannot stat lock file %s", ClientHost, strerror(errno));
+ if (statfailed++ > 5) return(0);
+ continue;
+ }
+
+ /* If lockfile is older than the value of
+ PERMaccessconf->backoff_postslow, remove it */
+ statfailed = 0;
+ time(&now);
+ if (now < st.st_ctime + PERMaccessconf->backoff_postslow) continue;
+ syslog(L_ERROR, "%s removing stale lock file %s", ClientHost, lockname);
+ unlink(lockname);
+ }
+}
+
+void
+UnlockPostRec(path)
+ char *path;
+{
+ char lockname[SPOOLNAMEBUFF];
+
+ snprintf(lockname, sizeof(lockname), "%s.lock", path);
+ if (unlink(lockname) < 0) {
+ syslog(L_ERROR, "%s can't unlink lock file: %s", ClientHost,strerror(errno)) ;
+ }
+ return;
+}
+
+/*
+ * Get the stored postrecord for that IP
+ */
+static int
+GetPostRecord(char *path, long *lastpost, long *lastsleep, long *lastn)
+{
+ static char buff[SMBUF];
+ FILE *fp;
+ char *s;
+
+ fp = fopen(path,"r");
+ if (fp == NULL) {
+ if (errno == ENOENT) {
+ return 1;
+ }
+ syslog(L_ERROR, "%s Error opening '%s': %s",
+ ClientHost, path, strerror(errno));
+ return 0;
+ }
+
+ if (fgets(buff,SMBUF,fp) == NULL) {
+ syslog(L_ERROR, "%s Error reading '%s': %s",
+ ClientHost, path, strerror(errno));
+ return 0;
+ }
+ *lastpost = atol(buff);
+
+ if ((s = strchr(buff,',')) == NULL) {
+ syslog(L_ERROR, "%s bad data in postrec file: '%s'",
+ ClientHost, buff);
+ return 0;
+ }
+ s++; *lastsleep = atol(s);
+
+ if ((s = strchr(s,',')) == NULL) {
+ syslog(L_ERROR, "%s bad data in postrec file: '%s'",
+ ClientHost, buff);
+ return 0;
+ }
+ s++; *lastn = atol(s);
+
+ fclose(fp);
+ return 1;
+}
+
+/*
+ * Store the postrecord for that IP
+ */
+static int
+StorePostRecord(char *path, time_t lastpost, long lastsleep, long lastn)
+{
+ FILE *fp;
+
+ fp = fopen(path,"w");
+ if (fp == NULL) {
+ syslog(L_ERROR, "%s Error opening '%s': %s",
+ ClientHost, path, strerror(errno));
+ return 0;
+ }
+
+ fprintf(fp,"%ld,%ld,%ld\n",(long) lastpost,lastsleep,lastn);
+ fclose(fp);
+ return 1;
+}
+
+/*
+ * Return the proper sleeptime. Return false on error.
+ */
+int
+RateLimit(sleeptime,path)
+ long *sleeptime;
+ char *path;
+{
+ TIMEINFO Now;
+ long prevpost,prevsleep,prevn,n;
+
+ if (GetTimeInfo(&Now) < 0)
+ return 0;
+
+ prevpost = 0L; prevsleep = 0L; prevn = 0L; n = 0L;
+ if (!GetPostRecord(path,&prevpost,&prevsleep,&prevn)) {
+ syslog(L_ERROR, "%s can't get post record: %s",
+ ClientHost, strerror(errno));
+ return 0;
+ }
+ /*
+ * Just because yer paranoid doesn't mean they ain't out ta get ya
+ * This is called paranoid clipping
+ */
+ if (prevn < 0L) prevn = 0L;
+ if (prevsleep < 0L) prevsleep = 0L;
+ if (prevsleep > PERMaccessconf->backoff_postfast) prevsleep = PERMaccessconf->backoff_postfast;
+
+ /*
+ * Compute the new sleep time
+ */
+ *sleeptime = 0L;
+ if (prevpost <= 0L) {
+ prevpost = 0L;
+ prevn = 1L;
+ } else {
+ n = Now.time - prevpost;
+ if (n < 0L) {
+ syslog(L_NOTICE,"%s previous post was in the future (%ld sec)",
+ ClientHost,n);
+ n = 0L;
+ }
+ if (n < PERMaccessconf->backoff_postfast) {
+ if (prevn >= PERMaccessconf->backoff_trigger) {
+ *sleeptime = 1 + (prevsleep * PERMaccessconf->backoff_k);
+ }
+ } else if (n < PERMaccessconf->backoff_postslow) {
+ if (prevn >= PERMaccessconf->backoff_trigger) {
+ *sleeptime = prevsleep;
+ }
+ } else {
+ prevn = 0L;
+ }
+ prevn++;
+ }
+
+ *sleeptime = ((*sleeptime) > PERMaccessconf->backoff_postfast) ? PERMaccessconf->backoff_postfast : (*sleeptime);
+ /* This ought to trap this bogon */
+ if ((*sleeptime) < 0L) {
+ syslog(L_ERROR,"%s Negative sleeptime detected: %ld, prevsleep: %ld, N: %ld",ClientHost,*sleeptime,prevsleep,n);
+ *sleeptime = 0L;
+ }
+
+ /* Store the postrecord */
+ if (!StorePostRecord(path,Now.time,*sleeptime,prevn)) {
+ syslog(L_ERROR, "%s can't store post record: %s", ClientHost, strerror(errno));
+ return 0;
+ }
+
+ return 1;
+}
+
+#ifdef HAVE_SSL
+/*
+** The "STARTTLS" command. RFC2595.
+*/
+/* ARGSUSED0 */
+
+void
+CMDstarttls(ac, av)
+ int ac UNUSED;
+ char *av[] UNUSED;
+{
+ int result;
+
+ tls_init();
+ if (nnrpd_starttls_done == 1) {
+ Reply("%d Already successfully executed STARTTLS\r\n",
+ NNTP_STARTTLS_DONE_VAL);
+ return;
+ }
+
+ Reply("%d Begin TLS negotiation now\r\n", NNTP_STARTTLS_NEXT_VAL);
+ fflush(stdout);
+
+ /* must flush our buffers before starting tls */
+
+ result=tls_start_servertls(0, /* read */
+ 1); /* write */
+ if (result==-1) {
+ Reply("%d Starttls failed\r\n", NNTP_STARTTLS_BAD_VAL);
+ return;
+ }
+ nnrpd_starttls_done = 1;
+}
+#endif /* HAVE_SSL */
--- /dev/null
+/* $Revision: 6372 $
+**
+** The newnews command.
+*/
+#include "config.h"
+#include "clibrary.h"
+
+#include "inn/innconf.h"
+#include "inn/messages.h"
+#include "inn/wire.h"
+#include "nnrpd.h"
+#include "ov.h"
+#include "cache.h"
+
+#define GROUP_LIST_DELTA 10
+
+static bool FindHeader(ARTHANDLE *art, const char **pp, const char **qp,
+ const char* hdr, size_t hdrlen)
+{
+ const char *p, *p1, *q;
+ bool Nocr = true;
+
+ p = wire_findheader(art->data, art->len, hdr);
+ if (p == NULL)
+ return false;
+ q = p;
+ for (p1 = NULL; p < art->data + art->len; p++) {
+ if (p1 != NULL && *p1 == '\r' && *p == '\n') {
+ Nocr = false;
+ break;
+ }
+ if (*p == '\n') {
+ Nocr = true;
+ break;
+ }
+ p1 = p;
+ }
+ if (p >= art->data + art->len)
+ return false;
+ if (!Nocr)
+ p = p1;
+
+ *pp = p;
+ *qp = q;
+ return true;
+}
+
+/*
+** get Xref header
+*/
+static char *GetXref(ARTHANDLE *art) {
+ const char *p, *q;
+
+ if (!FindHeader(art, &p, &q, "xref", sizeof("xref")))
+ return NULL;
+ return xstrndup(q, p - q);
+}
+
+/*
+** Split newsgroup list into array of newsgroups. Return static pointer,
+** or NULL if there are no newsgroup.
+*/
+static char **GetGroups(char *p) {
+ static int size;
+ static char **list;
+ int i;
+ char *q;
+ static char *Xrefbuf = NULL;
+ char *Xref = p;
+
+ if (size == 0) {
+ size = GROUP_LIST_DELTA;
+ list = xmalloc((size + 1) * sizeof(char *));
+ }
+ Xref = p;
+ for (Xref++; *Xref == ' '; Xref++);
+ if ((Xref = strchr(Xref, ' ')) == NULL)
+ return NULL;
+ for (Xref++; *Xref == ' '; Xref++);
+ if (!Xrefbuf)
+ Xrefbuf = xmalloc(BIG_BUFFER);
+ strlcpy(Xrefbuf, Xref, BIG_BUFFER);
+ if ((q = strchr(Xrefbuf, '\t')) != NULL)
+ *q = '\0';
+ p = Xrefbuf;
+
+ for (i = 0 ; ;i++) {
+ while (ISWHITE(*p))
+ p++;
+ if (*p == '\0' || *p == '\n')
+ break;
+
+ if (i >= size - 1) {
+ size += GROUP_LIST_DELTA;
+ list = xrealloc(list, (size + 1) * sizeof(char *));
+ }
+ for (list[i] = p; *p && *p != '\n' && !ISWHITE(*p); p++) {
+ if (*p == ':')
+ *p = '\0';
+ }
+ if (*p) *p++ = '\0';
+ }
+ list[i] = NULL;
+ return i ? list : NULL;
+}
+
+static bool HaveSeen(bool AllGroups, char *group, char **groups, char **xrefs) {
+ char *list[2];
+
+ list[1] = NULL;
+ for ( ; *xrefs; xrefs++) {
+ list[0] = *xrefs;
+ if ((!AllGroups && PERMmatch(groups, list)) && (!PERMspecified || (PERMspecified && PERMmatch(PERMreadlist, list)))) {
+ if (!strcmp(*xrefs, group))
+ return false;
+ else
+ return true;
+ }
+ }
+ return false;
+}
+
+static char **groups;
+
+static void
+process_newnews(char *group, bool AllGroups, time_t date)
+{
+ char **xrefs;
+ int count;
+ void *handle;
+ char *p;
+ time_t arrived;
+ ARTHANDLE *art = NULL;
+ TOKEN token;
+ char *data;
+ int len;
+ char *grplist[2];
+ time_t now;
+
+ grplist[0] = group;
+ grplist[1] = NULL;
+ if (PERMspecified && !PERMmatch(PERMreadlist, grplist))
+ return;
+ if (!AllGroups && !PERMmatch(groups, grplist))
+ return;
+ if (!OVgroupstats(group, &ARTlow, &ARThigh, &count, NULL))
+ return;
+ if ((handle = OVopensearch(group, ARTlow, ARThigh)) != NULL) {
+ ARTNUM artnum;
+ unsigned long artcount = 0;
+ struct cvector *vector = NULL;
+
+ if (innconf->nfsreader) {
+ time(&now);
+ /* move the start time back nfsreaderdelay seconds */
+ if (date >= innconf->nfsreaderdelay)
+ date -= innconf->nfsreaderdelay;
+ }
+ while (OVsearch(handle, &artnum, &data, &len, &token, &arrived)) {
+ if (innconf->nfsreader && arrived + innconf->nfsreaderdelay > now)
+ continue;
+ if (len == 0 || date > arrived)
+ continue;
+
+ vector = overview_split(data, len, NULL, vector);
+ if (overhdr_xref == -1) {
+ if ((art = SMretrieve(token, RETR_HEAD)) == NULL)
+ continue;
+ p = GetXref(art);
+ SMfreearticle(art);
+ } else {
+ if (PERMaccessconf->nnrpdcheckart &&
+ !ARTinstorebytoken(token))
+ continue;
+ /* We only care about the newsgroup list here, virtual
+ * hosting isn't relevant */
+ p = overview_getheader(vector, overhdr_xref, OVextra);
+ }
+ if (p == NULL)
+ continue;
+ xrefs = GetGroups(p);
+ free(p);
+ if (xrefs == NULL)
+ continue;
+ if (HaveSeen(AllGroups, group, groups, xrefs))
+ continue;
+ p = overview_getheader(vector, OVERVIEW_MESSAGE_ID, OVextra);
+ if (p == NULL)
+ continue;
+
+ ++artcount;
+ cache_add(HashMessageID(p), token);
+ Printf("%s\r\n", p);
+ free(p);
+ }
+ OVclosesearch(handle);
+ notice("%s newnews %s %lu", ClientHost, group, artcount);
+ if (vector)
+ cvector_free(vector);
+ }
+}
+
+/*
+** NEWNEWS newsgroups date time ["GMT"]
+** Return the Message-ID of any articles after the specified date
+*/
+void CMDnewnews(int ac, char *av[]) {
+ char *p, *q;
+ char *path;
+ bool AllGroups;
+ char line[BIG_BUFFER];
+ time_t date;
+ QIOSTATE *qp;
+ int i;
+ bool local;
+
+ if (!PERMaccessconf->allownewnews) {
+ Reply("%d NEWNEWS command disabled by administrator\r\n", NNTP_ACCESS_VAL);
+ return;
+ }
+
+ if (!PERMcanread) {
+ Reply("%s\r\n", NNTP_ACCESS);
+ return;
+ }
+
+ /* Make other processes happier if someone uses NEWNEWS */
+ if (innconf->nicenewnews > 0) {
+ nice(innconf->nicenewnews);
+ innconf->nicenewnews = 0;
+ }
+
+ snprintf(line, sizeof(line), "%s %s %s %s", av[1], av[2], av[3],
+ (ac >= 5 && (*av[4] == 'G' || *av[4] == 'U')) ? "GMT" : "local");
+ notice("%s newnews %s", ClientHost, line);
+
+ TMRstart(TMR_NEWNEWS);
+ /* Optimization in case client asks for !* (no groups) */
+ if (strcmp(av[1], "!*") == 0) {
+ Reply("%s\r\n", NNTP_NEWNEWSOK);
+ Printf(".\r\n");
+ TMRstop(TMR_NEWNEWS);
+ return;
+ }
+
+ /* Parse the newsgroups. */
+ AllGroups = (strcmp(av[1], "*") == 0);
+ if (!AllGroups && !NGgetlist(&groups, av[1])) {
+ Reply("%d Bad newsgroup specifier %s\r\n", NNTP_SYNTAX_VAL, av[1]);
+ TMRstop(TMR_NEWNEWS);
+ return;
+ }
+
+ /* Parse the date. */
+ local = !(ac > 4 && strcasecmp(av[4], "GMT") == 0);
+ date = parsedate_nntp(av[2], av[3], local);
+ if (date == (time_t) -1) {
+ Reply("%d Bad date\r\n", NNTP_SYNTAX_VAL);
+ TMRstop(TMR_NEWNEWS);
+ return;
+ }
+
+ if (strcspn(av[1], "\\!*[?]") == strlen(av[1])) {
+ /* optimise case - don't need to scan the active file pattern
+ * matching */
+ Reply("%s\r\n", NNTP_NEWNEWSOK);
+ for (i = 0; groups[i]; ++i) {
+ process_newnews(groups[i], AllGroups, date);
+ }
+ } else {
+ path = concatpath(innconf->pathdb, _PATH_ACTIVE);
+ qp = QIOopen(path);
+ if (qp == NULL) {
+ if (errno == ENOENT) {
+ Reply("%d Can't open active\r\n", NNTP_TEMPERR_VAL);
+ } else {
+ syswarn("%s cant fopen %s", ClientHost, path);
+ Reply("%d Can't open active\r\n", NNTP_TEMPERR_VAL);
+ }
+ free(path);
+ TMRstop(TMR_NEWNEWS);
+ return;
+ }
+ free(path);
+
+ Reply("%s\r\n", NNTP_NEWNEWSOK);
+
+ while ((p = QIOread(qp)) != NULL) {
+ for (q = p; *q != '\0'; q++) {
+ if (*q == ' ' || *q == '\t') {
+ *q = '\0';
+ break;
+ }
+ }
+ process_newnews(p, AllGroups, date);
+ }
+ QIOclose(qp);
+ }
+ Printf(".\r\n");
+ TMRstop(TMR_NEWNEWS);
+}
--- /dev/null
+/* $Id: nnrpd.c 7731 2008-04-06 08:40:29Z iulius $
+**
+** NNTP server for readers (NNRP) for InterNetNews.
+**
+** This server doesn't do any real load-limiting, except for what has
+** proven empirically necesary (i.e., look at GRPscandir).
+*/
+
+#include "config.h"
+#include "clibrary.h"
+#include "portable/setproctitle.h"
+#include "portable/wait.h"
+#include <grp.h>
+#include <netdb.h>
+#include <pwd.h>
+#include <signal.h>
+
+#if HAVE_GETSPNAM
+# include <shadow.h>
+#endif
+
+#include "inn/innconf.h"
+#include "inn/messages.h"
+#include "libinn.h"
+#include "ov.h"
+#define MAINLINE
+#include "nnrpd.h"
+
+#include "tls.h"
+#include "sasl_config.h"
+
+#ifdef HAVE_SSL
+extern SSL *tls_conn;
+int nnrpd_starttls_done = 0;
+#endif
+
+#if NEED_HERRNO_DECLARATION
+extern int h_errno;
+#endif
+
+/* If we have getloadavg, include the appropriate header file. Otherwise,
+ just assume that we always have a load of 0. */
+#if HAVE_GETLOADAVG
+# if HAVE_SYS_LOADAVG_H
+# include <sys/loadavg.h>
+# endif
+#else
+static int
+getloadavg(double loadavg[], int nelem)
+{
+ int i;
+
+ for (i = 0; i < nelem && i < 3; i++)
+ loadavg[i] = 0;
+ return i;
+}
+#endif
+
+
+#define MAXPATTERNDEFINE 10
+
+#define CMDany -1
+
+
+typedef struct _CMDENT {
+ const char * Name;
+ void (*Function)(int, char **);
+ bool Needauth;
+ int Minac;
+ int Maxac;
+ const char * Help;
+} CMDENT;
+
+
+char NOACCESS[] = NNTP_ACCESS;
+char *ACTIVE = NULL;
+char *ACTIVETIMES = NULL;
+char *HISTORY = NULL;
+char *NEWSGROUPS = NULL;
+char *NNRPACCESS = NULL;
+
+static char *LocalLogFileName = NULL;
+static char *LocalLogDirName;
+
+struct history *History;
+static double STATstart;
+static double STATfinish;
+static char *PushedBack;
+static sig_atomic_t ChangeTrace;
+bool DaemonMode = false;
+bool ForeGroundMode = false;
+#if HAVE_GETSPNAM
+static const char *ShadowGroup;
+#endif
+static const char *HostErrorStr;
+bool GetHostByAddr = true; /* formerly DO_NNRP_GETHOSTBYADDR */
+const char *NNRPinstance = "";
+
+#ifdef DO_PERL
+bool PerlLoaded = false;
+#endif /* DO_PERL */
+
+#ifdef DO_PYTHON
+bool PY_use_dynamic = false;
+#endif /* DO_PYTHON */
+
+static char CMDfetchhelp[] = "[MessageID|Number]";
+
+static CMDENT CMDtable[] = {
+ { "authinfo", CMDauthinfo, false, 3, CMDany,
+ "user Name|pass Password|generic <prog> <args>" },
+#ifdef HAVE_SSL
+ { "starttls", CMDstarttls, false, 1, 1,
+ NULL },
+#endif
+ { "article", CMDfetch, true, 1, 2,
+ CMDfetchhelp },
+ { "body", CMDfetch, true, 1, 2,
+ CMDfetchhelp },
+ { "date", CMDdate, false, 1, 1,
+ NULL },
+ { "group", CMDgroup, true, 2, 2,
+ "newsgroup" },
+ { "head", CMDfetch, true, 1, 2,
+ CMDfetchhelp },
+ { "help", CMDhelp, false, 1, CMDany,
+ NULL },
+ { "ihave", CMDpost, true, 2, 2,
+ "MessageID" },
+ { "last", CMDnextlast, true, 1, 1,
+ NULL },
+ { "list", CMDlist, true, 1, 3,
+ "[active|active.times|distrib.pats|distributions|extensions|moderators|motd|newsgroups|overview.fmt|subscriptions]" },
+ { "listgroup", CMDgroup, true, 1, 2,
+ "newsgroup" },
+ { "mode", CMDmode, false, 2, 2,
+ "reader" },
+ { "newgroups", CMDnewgroups, true, 3, 5,
+ "[YY]yymmdd hhmmss [\"GMT\"]" },
+ { "newnews", CMDnewnews, true, 4, 5,
+ "newsgroups [YY]yymmdd hhmmss [\"GMT\"]" },
+ { "next", CMDnextlast, true, 1, 1,
+ NULL },
+ { "post", CMDpost, true, 1, 1,
+ NULL },
+ { "slave", CMD_unimp, false, 1, 1,
+ NULL },
+ { "stat", CMDfetch, true, 1, 2,
+ CMDfetchhelp },
+ { "xgtitle", CMDxgtitle, true, 1, 2,
+ "[group_pattern]" },
+ { "xhdr", CMDpat, true, 2, 3,
+ "header [range|MessageID]" },
+ { "xover", CMDxover, true, 1, 2,
+ "[range]" },
+ { "xpat", CMDpat, true, 4, CMDany,
+ "header range|MessageID pat [morepat...]" },
+ { "xpath", CMDxpath, true, 2, 2,
+ "MessageID" },
+ { NULL, CMD_unimp, false, 0, 0,
+ NULL }
+};
+
+
+static const char *const timer_name[] = {
+ "idle",
+ "newnews",
+ "readart",
+ "checkart",
+ "nntpread",
+ "nntpwrite",
+};
+
+/*
+** Log a summary status message and exit.
+*/
+void
+ExitWithStats(int x, bool readconf)
+{
+ double usertime;
+ double systime;
+
+ line_free(&NNTPline);
+ fflush(stdout);
+ STATfinish = TMRnow_double();
+ if (GetResourceUsage(&usertime, &systime) < 0) {
+ usertime = 0;
+ systime = 0;
+ }
+
+ GRPreport();
+ if (ARTcount)
+ syslog(L_NOTICE, "%s exit articles %ld groups %ld",
+ ClientHost, ARTcount, GRPcount);
+ if (POSTreceived || POSTrejected)
+ syslog(L_NOTICE, "%s posts received %ld rejected %ld",
+ ClientHost, POSTreceived, POSTrejected);
+ syslog(L_NOTICE, "%s times user %.3f system %.3f idle %.3f elapsed %.3f",
+ ClientHost, usertime, systime, IDLEtime, STATfinish - STATstart);
+ /* Tracking code - Make entries in the logfile(s) to show that we have
+ finished with this session */
+ if (!readconf && PERMaccessconf && PERMaccessconf->readertrack) {
+ syslog(L_NOTICE, "%s Tracking Disabled (%s)", ClientHost, Username);
+ if (LLOGenable) {
+ fprintf(locallog, "%s Tracking Disabled (%s)\n", ClientHost, Username);
+ fclose(locallog);
+ syslog(L_NOTICE,"%s Local Logging ends (%s) %s",ClientHost, Username, LocalLogFileName);
+ }
+ }
+ if (ARTget)
+ syslog(L_NOTICE, "%s artstats get %ld time %ld size %ld", ClientHost,
+ ARTget, ARTgettime, ARTgetsize);
+ if (!readconf && PERMaccessconf && PERMaccessconf->nnrpdoverstats && OVERcount)
+ syslog(L_NOTICE, "%s overstats count %ld hit %ld miss %ld time %ld size %ld dbz %ld seek %ld get %ld artcheck %ld", ClientHost,
+ OVERcount, OVERhit, OVERmiss, OVERtime, OVERsize, OVERdbz, OVERseek, OVERget, OVERartcheck);
+
+#ifdef HAVE_SSL
+ if (tls_conn) {
+ SSL_shutdown(tls_conn);
+ SSL_free(tls_conn);
+ tls_conn = NULL;
+ }
+#endif
+
+ if (DaemonMode) {
+ shutdown(STDIN_FILENO, 2);
+ shutdown(STDOUT_FILENO, 2);
+ shutdown(STDERR_FILENO, 2);
+ close(STDIN_FILENO);
+ close(STDOUT_FILENO);
+ close(STDERR_FILENO);
+ }
+
+ OVclose();
+ SMshutdown();
+
+#ifdef DO_PYTHON
+ PY_close_python();
+#endif /* DO_PYTHON */
+
+ if (History)
+ HISclose(History);
+
+ if (innconf->timer != 0) {
+ TMRsummary(ClientHost, timer_name);
+ TMRfree();
+ }
+
+ if (LocalLogFileName != NULL)
+ free(LocalLogFileName);
+ closelog();
+ exit(x);
+}
+
+
+/*
+** The "help" command.
+*/
+/* ARGSUSED0 */
+void
+CMDhelp(int ac UNUSED, char *av[] UNUSED)
+{
+ CMDENT *cp;
+ char *p, *q;
+ static const char *newsmaster = NEWSMASTER;
+
+ Reply("%s\r\n", NNTP_HELP_FOLLOWS);
+ for (cp = CMDtable; cp->Name; cp++)
+ if (cp->Help == NULL)
+ Printf(" %s\r\n", cp->Name);
+ else
+ Printf(" %s %s\r\n", cp->Name, cp->Help);
+ if (PERMaccessconf && (VirtualPathlen > 0)) {
+ if (PERMaccessconf->newsmaster) {
+ if (strchr(PERMaccessconf->newsmaster, '@') == NULL) {
+ Printf("Report problems to <%s@%s>\r\n",
+ PERMaccessconf->newsmaster, PERMaccessconf->domain);
+ } else {
+ Printf("Report problems to <%s>\r\n",
+ PERMaccessconf->newsmaster);
+ }
+ } else {
+ /* sigh, pickup from newsmaster anyway */
+ if ((p = strchr(newsmaster, '@')) == NULL)
+ Printf("Report problems to <%s@%s>\r\n",
+ newsmaster, PERMaccessconf->domain);
+ else {
+ q = xstrndup(newsmaster, p - newsmaster);
+ Printf("Report problems to <%s@%s>\r\n",
+ q, PERMaccessconf->domain);
+ free(q);
+ }
+ }
+ } else {
+ if (strchr(newsmaster, '@') == NULL)
+ Printf("Report problems to <%s@%s>\r\n",
+ newsmaster, innconf->fromhost);
+ else
+ Printf("Report problems to <%s>\r\n",
+ newsmaster);
+ }
+ Reply(".\r\n");
+}
+
+
+/*
+** Unimplemented catch-all.
+*/
+/* ARGSUSED0 */
+void
+CMD_unimp(ac, av)
+ int ac UNUSED;
+ char *av[];
+{
+ if (strcasecmp(av[0], "slave") == 0)
+ /* Somebody sends us this? I don't believe it! */
+ Reply("%d Unsupported\r\n", NNTP_SLAVEOK_VAL);
+ else
+ Reply("%d %s not implemented; try help\r\n",
+ NNTP_BAD_COMMAND_VAL, av[0]);
+}
+
+
+#ifndef INADDR_LOOPBACK
+#define INADDR_LOOPBACK 0x7f000001
+#endif /* INADDR_LOOPBACK */
+/*
+** Convert an IP address to a hostname. Don't trust the reverse lookup,
+** since anyone can fake .in-addr.arpa entries.
+*/
+static bool
+Address2Name(INADDR *ap, char *hostname, int i)
+{
+ char *p;
+ struct hostent *hp;
+ static char mismatch_error[] = "reverse lookup validation failed";
+ char **pp;
+
+ /* Get the official hostname, store it away. */
+ if ((hp = gethostbyaddr((char *)ap, sizeof *ap, AF_INET)) == NULL) {
+ HostErrorStr = hstrerror(h_errno);
+ return false;
+ }
+ strlcpy(hostname, hp->h_name, i);
+
+ /* Get addresses for this host. */
+ if ((hp = gethostbyname(hostname)) == NULL) {
+ HostErrorStr = hstrerror(h_errno);
+ return false;
+ }
+
+ /* Make sure one of those addresses is the address we got. */
+ for (pp = hp->h_addr_list; *pp; pp++)
+ if (strncmp((const char *)&ap->s_addr, *pp, hp->h_length) == 0)
+ break;
+ if (*pp == NULL)
+ {
+ HostErrorStr = mismatch_error;
+ return false;
+ }
+
+ /* Only needed for misconfigured YP/NIS systems. */
+ if (ap->s_addr != INADDR_LOOPBACK && strchr(hostname, '.') == NULL
+ && (p = innconf->domain) != NULL) {
+ strlcat(hostname, ".", i);
+ strlcat(hostname, p, i);
+ }
+
+ /* Make all lowercase, for wildmat. */
+ for (p = hostname; *p; p++)
+ if (CTYPE(isupper, (int)*p))
+ *p = tolower(*p);
+ return true;
+}
+
+/*
+** Convert an IPv6 address to a hostname. Don't trust the reverse lookup,
+** since anyone can fake .ip6.arpa entries.
+*/
+#ifdef HAVE_INET6
+static bool
+Address2Name6(struct sockaddr *sa, char *hostname, int i)
+{
+ static char mismatch_error[] = "reverse lookup validation failed";
+ int ret;
+ bool valid = 0;
+ struct addrinfo hints, *res, *res0;
+ char *p;
+
+ /* Get the official hostname, store it away. */
+ ret = getnameinfo( sa, SA_LEN( sa ), hostname, i, NULL, 0, NI_NAMEREQD );
+ if( ret != 0 )
+ {
+ HostErrorStr = gai_strerror( ret );
+ return false;
+ }
+
+ /* Get addresses for this host. */
+ memset( &hints, 0, sizeof( hints ) );
+ hints.ai_socktype = SOCK_STREAM;
+ hints.ai_family = AF_INET6;
+ if( ( ret = getaddrinfo( hostname, NULL, &hints, &res0 ) ) != 0 )
+ {
+ HostErrorStr = gai_strerror( ret );
+ return false;
+ }
+
+ /* Make sure one of those addresses is the address we got. */
+ for( res = res0; res; res = res->ai_next )
+ {
+#ifdef HAVE_BROKEN_IN6_ARE_ADDR_EQUAL
+ if( ! memcmp( &(((struct sockaddr_in6 *)sa)->sin6_addr),
+ &(((struct sockaddr_in6 *)(res->ai_addr))->sin6_addr),
+ sizeof( struct in6_addr ) ) )
+#else
+ if( IN6_ARE_ADDR_EQUAL( &(((struct sockaddr_in6 *)sa)->sin6_addr),
+ &(((struct sockaddr_in6 *)(res->ai_addr))->sin6_addr) ) )
+#endif
+ {
+ valid = 1;
+ break;
+ }
+ }
+
+ freeaddrinfo( res0 );
+
+ if (valid) {
+ /* Make all lowercase for matching. */
+ for (p = hostname; *p != '\0'; p++)
+ if (CTYPE(isupper, *p))
+ *p = tolower(*p);
+ return true;
+ } else {
+ HostErrorStr = mismatch_error;
+ return false;
+ }
+}
+#endif
+
+
+static bool
+Sock2String( struct sockaddr *sa, char *string, int len, bool lookup )
+{
+ struct sockaddr_in *sin4 = (struct sockaddr_in *)sa;
+
+#ifdef HAVE_INET6
+ struct sockaddr_in6 *sin6 = (struct sockaddr_in6 *)sa;
+ struct sockaddr_in temp;
+
+ if( sa->sa_family == AF_INET6 )
+ {
+ if( ! IN6_IS_ADDR_V4MAPPED(&sin6->sin6_addr) )
+ {
+ if( lookup )
+ {
+ return Address2Name6(sa, string, len);
+ } else {
+ strlcpy( string, sprint_sockaddr( sa ), len );
+ return true;
+ }
+ } else {
+ temp.sin_family = AF_INET;
+ memcpy( &temp.sin_addr, sin6->sin6_addr.s6_addr + 12, 4 );
+ temp.sin_port = sin6->sin6_port;
+ sin4 = &temp;
+ /* fall through to AF_INET case */
+ }
+ }
+#endif
+ if( lookup ) {
+ return Address2Name(&sin4->sin_addr, string, len);
+ } else {
+ strlcpy( string, inet_ntoa(sin4->sin_addr), len );
+ return true;
+ }
+}
+
+/*
+** Determine access rights of the client.
+*/
+static void StartConnection(void)
+{
+ struct sockaddr_storage ssc, sss;
+ socklen_t length;
+ const char *default_host_error = "unknown error";
+
+ ClientIpAddr = 0L;
+ ClientHost[0] = '\0';
+ ClientIpString[0] = '\0';
+ ClientPort = 0;
+ ServerHost[0] = '\0';
+ ServerIpString[0] = '\0';
+ ServerPort = 0;
+
+ /* Get the peer's name. */
+ length = sizeof ssc;
+ if (getpeername(STDIN_FILENO, (struct sockaddr *)&ssc, &length) < 0) {
+ if (!isatty(STDIN_FILENO)) {
+ syslog(L_TRACE, "%s cant getpeername %m", "?");
+ /* so stats generation looks correct. */
+ strlcpy(ClientHost, "?", sizeof(ClientHost));
+ Printf("%d I can't get your name. Goodbye.\r\n", NNTP_ACCESS_VAL);
+ ExitWithStats(1, true);
+ }
+ strlcpy(ClientHost, "stdin", sizeof(ClientHost));
+ }
+
+ else {
+#ifdef HAVE_INET6
+ if ( ssc.ss_family != AF_INET && ssc.ss_family != AF_INET6) {
+#else
+ if ( ssc.ss_family != AF_INET ) {
+#endif
+ syslog(L_ERROR, "%s bad_address_family %ld",
+ "?", (long)ssc.ss_family);
+ Printf("%d Bad address family. Goodbye.\r\n", NNTP_ACCESS_VAL);
+ ExitWithStats(1, true);
+ }
+
+ length = sizeof sss;
+ if (getsockname(STDIN_FILENO, (struct sockaddr *)&sss, &length) < 0) {
+ syslog(L_NOTICE, "%s can't getsockname %m", ClientHost);
+ Printf("%d Can't figure out where you connected to. Goodbye\r\n", NNTP_ACCESS_VAL);
+ ExitWithStats(1, true);
+ }
+
+ /* figure out client's IP address/hostname */
+ HostErrorStr = default_host_error;
+ if( ! Sock2String( (struct sockaddr *)&ssc, ClientIpString,
+ sizeof( ClientIpString ), false ) ) {
+ syslog(L_NOTICE, "? cant get client numeric address: %s", HostErrorStr);
+ ExitWithStats(1, true);
+ }
+ if(GetHostByAddr) {
+ HostErrorStr = default_host_error;
+ if( ! Sock2String( (struct sockaddr *)&ssc, ClientHost,
+ sizeof( ClientHost ), true ) ) {
+ syslog(L_NOTICE,
+ "? reverse lookup for %s failed: %s -- using IP address for access",
+ ClientIpString, HostErrorStr);
+ strlcpy(ClientHost, ClientIpString, sizeof(ClientHost));
+ }
+ } else {
+ strlcpy(ClientHost, ClientIpString, sizeof(ClientHost));
+ }
+
+ /* figure out server's IP address/hostname */
+ HostErrorStr = default_host_error;
+ if( ! Sock2String( (struct sockaddr *)&sss, ServerIpString,
+ sizeof( ServerIpString ), false ) ) {
+ syslog(L_NOTICE, "? cant get server numeric address: %s", HostErrorStr);
+ ExitWithStats(1, true);
+ }
+ if(GetHostByAddr) {
+ HostErrorStr = default_host_error;
+ if( ! Sock2String( (struct sockaddr *)&sss, ServerHost,
+ sizeof( ServerHost ), true ) ) {
+ syslog(L_NOTICE,
+ "? reverse lookup for %s failed: %s -- using IP address for access",
+ ServerIpString, HostErrorStr);
+ strlcpy(ServerHost, ServerIpString, sizeof(ServerHost));
+ }
+ } else {
+ strlcpy(ServerHost, ServerIpString, sizeof(ServerHost));
+ }
+
+ /* get port numbers */
+ switch( ssc.ss_family ) {
+ case AF_INET:
+ ClientPort = ntohs( ((struct sockaddr_in *)&ssc)->sin_port );
+ ServerPort = ntohs( ((struct sockaddr_in *)&sss)->sin_port );
+ break;
+#ifdef HAVE_INET6
+ case AF_INET6:
+ ClientPort = ntohs( ((struct sockaddr_in6 *)&ssc)->sin6_port );
+ ServerPort = ntohs( ((struct sockaddr_in6 *)&sss)->sin6_port );
+ break;
+#endif
+ }
+ }
+
+ strlcpy(LogName, ClientHost, sizeof(LogName));
+
+ syslog(L_NOTICE, "%s (%s) connect", ClientHost, ClientIpString);
+
+ PERMgetaccess(NNRPACCESS);
+ PERMgetpermissions();
+}
+
+
+/*
+** Send a reply, possibly with debugging output.
+*/
+void
+Reply(const char *fmt, ...)
+{
+ va_list args;
+ int oerrno;
+ char * p;
+ char buff[2048];
+
+#ifdef HAVE_SSL
+ if (tls_conn) {
+ int r;
+
+ va_start(args, fmt);
+ vsnprintf(buff, sizeof(buff), fmt, args);
+ va_end(args);
+ TMRstart(TMR_NNTPWRITE);
+Again:
+ r = SSL_write(tls_conn, buff, strlen(buff));
+ switch (SSL_get_error(tls_conn, r)) {
+ case SSL_ERROR_NONE:
+ case SSL_ERROR_SYSCALL:
+ break;
+ case SSL_ERROR_WANT_WRITE:
+ goto Again;
+ break;
+ case SSL_ERROR_SSL:
+ SSL_shutdown(tls_conn);
+ tls_conn = NULL;
+ errno = ECONNRESET;
+ break;
+ case SSL_ERROR_ZERO_RETURN:
+ break;
+ }
+ TMRstop(TMR_NNTPWRITE);
+ } else {
+ va_start(args, fmt);
+ TMRstart(TMR_NNTPWRITE);
+ vprintf(fmt, args);
+ TMRstop(TMR_NNTPWRITE);
+ va_end(args);
+ }
+#else
+ va_start(args, fmt);
+ TMRstart(TMR_NNTPWRITE);
+ vprintf(fmt, args);
+ TMRstop(TMR_NNTPWRITE);
+ va_end(args);
+#endif
+ if (Tracing) {
+ oerrno = errno;
+ va_start(args, fmt);
+
+ /* Copy output, but strip trailing CR-LF. Note we're assuming here
+ that no output line can ever be longer than 2045 characters. */
+ vsnprintf(buff, sizeof(buff), fmt, args);
+ va_end(args);
+ p = buff + strlen(buff) - 1;
+ while (p >= buff && (*p == '\n' || *p == '\r'))
+ *p-- = '\0';
+ syslog(L_TRACE, "%s > %s", ClientHost, buff);
+
+ errno = oerrno;
+ }
+}
+
+void
+Printf(const char *fmt, ...)
+{
+ va_list args;
+
+#ifdef HAVE_SSL
+ if (tls_conn) {
+ int r;
+ char buff[2048];
+
+ va_start(args, fmt);
+ vsnprintf(buff, sizeof(buff), fmt, args);
+ va_end(args);
+ TMRstart(TMR_NNTPWRITE);
+Again:
+ r = SSL_write(tls_conn, buff, strlen(buff));
+ switch (SSL_get_error(tls_conn, r)) {
+ case SSL_ERROR_NONE:
+ case SSL_ERROR_SYSCALL:
+ break;
+ case SSL_ERROR_WANT_WRITE:
+ goto Again;
+ break;
+ case SSL_ERROR_SSL:
+ SSL_shutdown(tls_conn);
+ tls_conn = NULL;
+ errno = ECONNRESET;
+ break;
+ case SSL_ERROR_ZERO_RETURN:
+ break;
+ }
+ TMRstop(TMR_NNTPWRITE);
+ } else {
+#endif /* HAVE_SSL */
+ va_start(args, fmt);
+ TMRstart(TMR_NNTPWRITE);
+ vprintf(fmt, args);
+ TMRstop(TMR_NNTPWRITE);
+ va_end(args);
+#ifdef HAVE_SSL
+ }
+#endif /* HAVE_SSL */
+}
+
+
+#ifdef HAVE_SIGACTION
+#define NO_SIGACTION_UNUSED UNUSED
+#else
+#define NO_SIGACTION_UNUSED
+#endif
+/*
+** Got a signal; toggle tracing.
+*/
+static RETSIGTYPE
+ToggleTrace(int s NO_SIGACTION_UNUSED)
+{
+ ChangeTrace = true;
+#ifndef HAVE_SIGACTION
+ xsignal(s, ToggleTrace);
+#endif
+}
+
+/*
+** Got a SIGPIPE; exit cleanly
+*/
+static RETSIGTYPE
+CatchPipe(int s UNUSED)
+{
+ ExitWithStats(0, false);
+}
+
+/*
+** Got a signal; wait for children.
+*/
+static RETSIGTYPE
+WaitChild(int s NO_SIGACTION_UNUSED)
+{
+ int pid;
+
+ for (;;) {
+ pid = waitpid(-1, NULL, WNOHANG);
+ if (pid <= 0)
+ break;
+ }
+#ifndef HAVE_SIGACTION
+ xsignal(s, WaitChild);
+#endif
+}
+
+static void SetupDaemon(void) {
+ bool val;
+
+ val = true;
+ if (SMsetup(SM_PREOPEN, (void *)&val) && !SMinit()) {
+ syslog(L_NOTICE, "cant initialize storage method, %s", SMerrorstr);
+ Reply("%d NNTP server unavailable. Try later.\r\n", NNTP_TEMPERR_VAL);
+ ExitWithStats(1, true);
+ }
+ OVextra = overview_extra_fields();
+ if (OVextra == NULL) {
+ /* overview_extra_fields should already have logged something
+ * useful */
+ Reply("%d NNTP server unavailable. Try later.\r\n", NNTP_TEMPERR_VAL);
+ ExitWithStats(1, true);
+ }
+ overhdr_xref = overview_index("Xref", OVextra);
+ if (!OVopen(OV_READ)) {
+ /* This shouldn't really happen. */
+ syslog(L_NOTICE, "cant open overview %m");
+ Reply("%d NNTP server unavailable. Try later.\r\n", NNTP_TEMPERR_VAL);
+ ExitWithStats(1, true);
+ }
+ if (!OVctl(OVCACHEKEEP, &val)) {
+ syslog(L_NOTICE, "cant enable overview cache %m");
+ Reply("%d NNTP server unavailable. Try later.\r\n", NNTP_TEMPERR_VAL);
+ ExitWithStats(1, true);
+ }
+}
+
+/*
+** Print a usage message and exit.
+*/
+static void
+Usage(void)
+{
+ fprintf(stderr, "Usage error.\n");
+ exit(1);
+}
+
+
+/* ARGSUSED0 */
+int
+main(int argc, char *argv[])
+{
+ const char *name;
+ CMDENT *cp;
+ char buff[NNTP_STRLEN];
+ char **av;
+ int ac;
+ READTYPE r;
+ int i;
+ char *Reject;
+ int timeout;
+ unsigned int vid=0;
+ int count=123456789;
+ struct timeval tv;
+ unsigned short ListenPort = NNTP_PORT;
+#ifdef HAVE_INET6
+ char ListenAddr[INET6_ADDRSTRLEN];
+#else
+ char ListenAddr[16];
+#endif
+ int lfd, fd;
+ socklen_t clen;
+#ifdef HAVE_INET6
+ struct sockaddr_storage ssa, csa;
+ struct sockaddr_in6 *ssa6 = (struct sockaddr_in6 *) &ssa;
+#else
+ struct sockaddr_in ssa, csa;
+#endif
+ struct sockaddr_in *ssa4 = (struct sockaddr_in *) &ssa;
+ struct stat Sb;
+ pid_t pid = -1;
+ gid_t NewsGID;
+ uid_t NewsUID;
+ int one = 1;
+ FILE *pidfile;
+ struct passwd *pwd;
+ int clienttimeout;
+ char *ConfFile = NULL;
+ char *path;
+#if HAVE_GETSPNAM
+ struct group *grp;
+ gid_t shadowgid;
+#endif /* HAVE_GETSPNAM */
+
+ int respawn = 0;
+
+ setproctitle_init(argc, argv);
+
+ /* Parse arguments. Must xstrdup() optarg if used because setproctitle may
+ clobber it! */
+ Reject = NULL;
+ LLOGenable = false;
+ GRPcur = NULL;
+ MaxBytesPerSecond = 0;
+ strlcpy(Username, "unknown", sizeof(Username));
+
+ /* Set up the pathname, first thing, and teach our error handlers about
+ the name of the program. */
+ name = argv[0];
+ if (name == NULL || *name == '\0')
+ name = "nnrpd";
+ else {
+ const char *p;
+
+ p = strrchr(name, '/');
+ if (p != NULL)
+ name = p + 1;
+ }
+ message_program_name = xstrdup(name);
+ openlog(message_program_name, L_OPENLOG_FLAGS | LOG_PID, LOG_INN_PROG);
+ message_handlers_die(1, message_log_syslog_crit);
+ message_handlers_warn(1, message_log_syslog_warning);
+ message_handlers_notice(1, message_log_syslog_notice);
+
+ if (!innconf_read(NULL))
+ exit(1);
+
+#ifdef HAVE_SSL
+ while ((i = getopt(argc, argv, "c:b:Dfi:I:g:nop:P:r:s:tS")) != EOF)
+#else
+ while ((i = getopt(argc, argv, "c:b:Dfi:I:g:nop:P:r:s:t")) != EOF)
+#endif /* HAVE_SSL */
+ switch (i) {
+ default:
+ Usage();
+ /* NOTREACHED */
+ case 'c': /* use alternate readers.conf */
+ ConfFile = concatpath(innconf->pathetc, optarg);
+ break;
+ case 'b': /* bind to a certain address in
+ daemon mode */
+ strlcpy(ListenAddr, optarg, sizeof(ListenAddr));
+ break;
+ case 'D': /* standalone daemon mode */
+ DaemonMode = true;
+ break;
+ case 'P': /* prespawn count in daemon mode */
+ respawn = atoi(optarg);
+ break;
+ case 'f': /* Don't fork on daemon mode */
+ ForeGroundMode = true;
+ break;
+#if HAVE_GETSPNAM
+ case 'g':
+ ShadowGroup = optarg;
+ break;
+#endif /* HAVE_GETSPNAM */
+ case 'i': /* Initial command */
+ PushedBack = xstrdup(optarg);
+ break;
+ case 'I': /* Instance */
+ NNRPinstance = xstrdup(optarg);
+ break;
+ case 'n': /* No DNS lookups */
+ GetHostByAddr = false;
+ break;
+ case 'o':
+ Offlinepost = true; /* Offline posting only */
+ break;
+ case 'p': /* tcp port for daemon mode */
+ ListenPort = atoi(optarg);
+ break;
+ case 'r': /* Reject connection message */
+ Reject = xstrdup(optarg);
+ break;
+ case 's': /* Unused title string */
+ break;
+ case 't': /* Tracing */
+ Tracing = true;
+ break;
+#ifdef HAVE_SSL
+ case 'S': /* SSL negotiation as soon as connected */
+ initialSSL = true;
+ break;
+#endif /* HAVE_SSL */
+ }
+ argc -= optind;
+ if (argc)
+ Usage();
+
+ /*
+ * Make other processes happier if someone is reading
+ * This allows other processes like 'overchan' to keep up when
+ * there are lots of readers. Note that this is cumulative with
+ * 'nicekids'
+ */
+ if (innconf->nicennrpd > 0)
+ nice(innconf->nicennrpd);
+
+ HISTORY = concatpath(innconf->pathdb, _PATH_HISTORY);
+ ACTIVE = concatpath(innconf->pathdb, _PATH_ACTIVE);
+ ACTIVETIMES = concatpath(innconf->pathdb, _PATH_ACTIVETIMES);
+ NEWSGROUPS = concatpath(innconf->pathdb, _PATH_NEWSGROUPS);
+ if(ConfFile)
+ NNRPACCESS = ConfFile;
+ else
+ NNRPACCESS = concatpath(innconf->pathetc,_PATH_NNRPACCESS);
+ SPOOLlen = strlen(innconf->patharticles);
+
+ if (DaemonMode) {
+#ifdef HAVE_INET6
+ memset(&ssa, '\0', sizeof(struct sockaddr_in6));
+ ssa6->sin6_family = AF_INET6;
+ ssa6->sin6_port = htons(ListenPort);
+ if (inet_pton(AF_INET6, ListenAddr, ssa6->sin6_addr.s6_addr) > 0) {
+ if ( (lfd = socket(AF_INET6, SOCK_STREAM, 0)) < 0) {
+ syslog(L_FATAL, "can't open socket (%m)");
+ exit(1);
+ }
+ }
+ else {
+#endif
+ memset(&ssa, '\0', sizeof(struct sockaddr_in));
+ ssa4->sin_family = AF_INET;
+ ssa4->sin_port = htons(ListenPort);
+ if (inet_aton(ListenAddr, &ssa4->sin_addr) <= 0 )
+ ssa4->sin_addr.s_addr = htonl(INADDR_ANY);
+ if ( (lfd = socket(AF_INET, SOCK_STREAM, 0)) < 0) {
+ syslog(L_FATAL, "can't open socket (%m)");
+ exit(1);
+ }
+#ifdef HAVE_INET6
+ }
+#endif
+
+ if (setsockopt(lfd, SOL_SOCKET, SO_REUSEADDR,
+ (char *)&one, sizeof(one)) < 0) {
+ syslog(L_FATAL, "can't setsockopt(SO_REUSEADDR) (%m)");
+ exit(1);
+ }
+
+ if (bind(lfd, (struct sockaddr *) &ssa, sizeof(ssa)) < 0) {
+ fprintf(stderr, "%s: can't bind (%s)\n", argv[0], strerror(errno));
+ syslog(L_FATAL, "can't bind local address (%m)");
+ exit(1);
+ }
+
+ /* If started as root, switch to news uid */
+ if (getuid() == 0) {
+ if (stat(innconf->pathrun, &Sb) < 0 || !S_ISDIR(Sb.st_mode)) {
+ syslog(L_FATAL, "nnrpd cant stat %s %m", innconf->pathrun);
+ exit(1);
+ }
+ if (Sb.st_uid == 0) {
+ syslog(L_FATAL, "nnrpd %s must not be owned by root", innconf->pathrun);
+ exit(1);
+ }
+ pwd = getpwnam(NEWSUSER);
+ if (pwd == (struct passwd *)NULL) {
+ syslog(L_FATAL, "nnrpd getpwnam(%s): %s", NEWSUSER, strerror(errno));
+ exit(1);
+ } else if (pwd->pw_gid != Sb.st_gid) {
+ syslog(L_FATAL, "nnrpd %s must have group %s", innconf->pathrun, NEWSGRP);
+ exit(1);
+ } else if (pwd->pw_uid != Sb.st_uid) {
+ syslog(L_FATAL, "nnrpd %s must be owned by %s", innconf->pathrun, NEWSUSER);
+ exit(1);
+ }
+
+#if HAVE_GETSPNAM
+ shadowgid = (gid_t) -1;
+ /* Find shadowgroup gid if needed */
+ if (ShadowGroup != NULL) {
+ if ((grp = getgrnam(ShadowGroup)) == NULL)
+ syslog(L_ERROR, "nnrpd cannot find group %s",
+ ShadowGroup);
+ else
+ shadowgid = grp->gr_gid;
+ } else if ((grp = getgrnam("shadow")) != NULL) {
+ /* found default group "shadow" */
+ shadowgid = grp->gr_gid;
+ ShadowGroup = "shadow";
+ }
+ /* If we have a shadowgid, try to set it as an extra group. */
+ if (shadowgid != (gid_t) -1) {
+ if (setgroups(1, &shadowgid) < 0)
+ syslog(L_ERROR, "nnrpd cannot set supplementary group %s %m",
+ ShadowGroup);
+ else
+ syslog(L_NOTICE, "nnrpd added supplementary group %s",
+ ShadowGroup);
+ }
+#endif /* HAVE_GETSPNAM */
+
+ NewsUID = Sb.st_uid;
+ NewsGID = Sb.st_gid;
+ setgid(NewsGID);
+ if (getgid() != NewsGID)
+ syslog(L_ERROR, "nnrpd cant setgid to %d %m", NewsGID);
+ setuid(NewsUID);
+ if (getuid() != NewsUID)
+ syslog(L_ERROR, "nnrpd cant setuid to %d %m", NewsUID);
+ }
+
+ /* Detach */
+ if (!ForeGroundMode) {
+ daemonize("/");
+ }
+
+ if (ListenPort == NNTP_PORT)
+ strlcpy(buff, "nnrpd.pid", sizeof(buff));
+ else
+ snprintf(buff, sizeof(buff), "nnrpd-%d.pid", ListenPort);
+ path = concatpath(innconf->pathrun, buff);
+ pidfile = fopen(path, "w");
+ free(path);
+ if (pidfile == NULL) {
+ syslog(L_ERROR, "cannot write %s %m", buff);
+ exit(1);
+ }
+ fprintf(pidfile,"%lu\n", (unsigned long) getpid());
+ fclose(pidfile);
+
+ /* Set signal handle to care for dead children */
+ if (!respawn)
+ xsignal(SIGCHLD, WaitChild);
+
+ /* Arrange to toggle tracing. */
+ xsignal(SIGHUP, ToggleTrace);
+
+ setproctitle("accepting connections");
+
+ listen(lfd, 128);
+
+ if (respawn) {
+ /* pre-forked mode */
+ for (;;) {
+ if (respawn > 0) {
+ --respawn;
+ pid = fork();
+ if (pid == 0) {
+ do {
+ clen = sizeof(csa);
+ fd = accept(lfd, (struct sockaddr *) &csa, &clen);
+ } while (fd < 0);
+ break;
+ }
+ }
+ for (;;) {
+ if (respawn == 0)
+ pid = wait(NULL);
+ else
+ pid = waitpid(-1, NULL, WNOHANG);
+ if (pid <= 0)
+ break;
+ ++respawn;
+ }
+ }
+ } else {
+ /* fork on demand */
+ do {
+ clen = sizeof(csa);
+ fd = accept(lfd, (struct sockaddr *) &csa, &clen);
+ if (fd < 0)
+ continue;
+
+ for (i = 0; i <= innconf->maxforks && (pid = fork()) < 0; i++) {
+ if (i == innconf->maxforks) {
+ syslog(L_FATAL, "cant fork (dropping connection): %m");
+ continue;
+ }
+ syslog(L_NOTICE, "cant fork (waiting): %m");
+ sleep(1);
+ }
+ if (ChangeTrace) {
+ Tracing = Tracing ? false : true;
+ syslog(L_TRACE, "trace %sabled", Tracing ? "en" : "dis");
+ ChangeTrace = false;
+ }
+ if (pid != 0)
+ close(fd);
+ } while (pid != 0);
+ }
+
+ /* child process starts here */
+ setproctitle("connected");
+ close(lfd);
+ dup2(fd, 0);
+ close(fd);
+ dup2(0, 1);
+ dup2(0, 2);
+ if (innconf->timer != 0)
+ TMRinit(TMR_MAX);
+ STATstart = TMRnow_double();
+ SetupDaemon();
+
+ /* if we are a daemon innd didn't make us nice, so be nice kids */
+ if (innconf->nicekids) {
+ if (nice(innconf->nicekids) < 0)
+ syslog(L_ERROR, "Could not nice child to %ld: %m", innconf->nicekids);
+ }
+
+ /* Only automatically reap children in the listening process */
+ xsignal(SIGCHLD, SIG_DFL);
+
+ } else {
+ if (innconf->timer)
+ TMRinit(TMR_MAX);
+ STATstart = TMRnow_double();
+ SetupDaemon();
+ /* Arrange to toggle tracing. */
+ xsignal(SIGHUP, ToggleTrace);
+ }/* DaemonMode */
+
+#ifdef HAVE_SSL
+ ClientSSL = false;
+ if (initialSSL) {
+ tls_init();
+ if (tls_start_servertls(0, 1) == -1) {
+ Reply("%d SSL connection failed\r\n", NNTP_STARTTLS_BAD_VAL);
+ ExitWithStats(1, false);
+ }
+ nnrpd_starttls_done = 1;
+ ClientSSL = true;
+ }
+#endif /* HAVE_SSL */
+
+ /* If requested, check the load average. */
+ if (innconf->nnrpdloadlimit > 0) {
+ double load[1];
+
+ if (getloadavg(load, 1) < 0)
+ warn("cannot obtain system load");
+ else {
+ if ((int)(load[0] + 0.5) > innconf->nnrpdloadlimit) {
+ syslog(L_NOTICE, "load %.2f > %ld", load[0], innconf->nnrpdloadlimit);
+ Reply("%d load at %.2f, try later\r\n", NNTP_GOODBYE_VAL,
+ load[0]);
+ ExitWithStats(1, true);
+ }
+ }
+ }
+
+ strlcpy(LogName, "?", sizeof(LogName));
+
+ /* Catch SIGPIPE so that we can exit out of long write loops */
+ xsignal(SIGPIPE, CatchPipe);
+
+ /* Get permissions and see if we can talk to this client */
+ StartConnection();
+ if (!PERMcanread && !PERMcanpost && !PERMneedauth) {
+ syslog(L_NOTICE, "%s no_permission", ClientHost);
+ Printf("%d You have no permission to talk. Goodbye.\r\n",
+ NNTP_ACCESS_VAL);
+ ExitWithStats(1, false);
+ }
+
+ /* Proceed with initialization. */
+ setproctitle("%s connect", ClientHost);
+
+ /* Were we told to reject connections? */
+ if (Reject) {
+ syslog(L_NOTICE, "%s rejected %s", ClientHost, Reject);
+ Reply("%s %s\r\n", NNTP_GOODBYE, Reject);
+ ExitWithStats(0, false);
+ }
+
+ if (PERMaccessconf) {
+ if (PERMaccessconf->readertrack)
+ PERMaccessconf->readertrack=TrackClient(ClientHost,Username);
+ } else {
+ if (innconf->readertrack)
+ innconf->readertrack=TrackClient(ClientHost,Username);
+ }
+
+ if ((PERMaccessconf && PERMaccessconf->readertrack)
+ || (!PERMaccessconf && innconf->readertrack)) {
+ int len;
+ syslog(L_NOTICE, "%s Tracking Enabled (%s)", ClientHost, Username);
+ pid=getpid();
+ gettimeofday(&tv,NULL);
+ count += pid;
+ vid = tv.tv_sec ^ tv.tv_usec ^ pid ^ count;
+ len = strlen("innconf->pathlog") + strlen("/tracklogs/log-") + BUFSIZ;
+ LocalLogFileName = xmalloc(len);
+ sprintf(LocalLogFileName, "%s/tracklogs/log-%d", innconf->pathlog, vid);
+ if ((locallog = fopen(LocalLogFileName, "w")) == NULL) {
+ LocalLogDirName = concatpath(innconf->pathlog, "tracklogs");
+ MakeDirectory(LocalLogDirName, false);
+ free(LocalLogDirName);
+ }
+ if (locallog == NULL && (locallog = fopen(LocalLogFileName, "w")) == NULL) {
+ syslog(L_ERROR, "%s Local Logging failed (%s) %s: %m", ClientHost, Username, LocalLogFileName);
+ } else {
+ syslog(L_NOTICE, "%s Local Logging begins (%s) %s",ClientHost, Username, LocalLogFileName);
+ fprintf(locallog, "%s Tracking Enabled (%s)\n", ClientHost, Username);
+ fflush(locallog);
+ LLOGenable = true;
+ }
+ }
+
+ if (PERMaccessconf) {
+ Reply("%d %s InterNetNews NNRP server %s ready (%s).\r\n",
+ PERMcanpost ? NNTP_POSTOK_VAL : NNTP_NOPOSTOK_VAL,
+ PERMaccessconf->pathhost, inn_version_string,
+ PERMcanpost ? "posting ok" : "no posting");
+ clienttimeout = PERMaccessconf->clienttimeout;
+ } else {
+ Reply("%d %s InterNetNews NNRP server %s ready (%s).\r\n",
+ PERMcanpost ? NNTP_POSTOK_VAL : NNTP_NOPOSTOK_VAL,
+ innconf->pathhost, inn_version_string,
+ PERMcanpost ? "posting ok" : "no posting");
+ clienttimeout = innconf->clienttimeout;
+ }
+
+ line_init(&NNTPline);
+
+ /* Main dispatch loop. */
+ for (timeout = innconf->initialtimeout, av = NULL, ac = 0; ;
+ timeout = clienttimeout) {
+ TMRstart(TMR_NNTPWRITE);
+ fflush(stdout);
+ TMRstop(TMR_NNTPWRITE);
+ if (ChangeTrace) {
+ Tracing = Tracing ? false : true;
+ syslog(L_TRACE, "trace %sabled", Tracing ? "en" : "dis");
+ ChangeTrace = false;
+ }
+ if (PushedBack) {
+ if (PushedBack[0] == '\0')
+ continue;
+ if (Tracing)
+ syslog(L_TRACE, "%s < %s", ClientHost, PushedBack);
+ ac = Argify(PushedBack, &av);
+ r = RTok;
+ }
+ else {
+ size_t len;
+ const char *p;
+
+ r = line_read(&NNTPline, timeout, &p, &len);
+ switch (r) {
+ default:
+ syslog(L_ERROR, "%s internal %d in main", ClientHost, r);
+ /* FALLTHROUGH */
+ case RTtimeout:
+ if (timeout < clienttimeout)
+ syslog(L_NOTICE, "%s timeout short", ClientHost);
+ else
+ syslog(L_NOTICE, "%s timeout", ClientHost);
+ ExitWithStats(1, false);
+ break;
+ case RTok:
+ if (len < sizeof(buff)) {
+ /* line_read guarantees null termination */
+ memcpy(buff, p, len + 1);
+ /* Do some input processing, check for blank line. */
+ if (Tracing)
+ syslog(L_TRACE, "%s < %s", ClientHost, buff);
+ if (buff[0] == '\0')
+ continue;
+ ac = Argify(buff, &av);
+ break;
+ }
+ /* FALLTHROUGH */
+ case RTlong:
+ Reply("%d Line too long\r\n", NNTP_BAD_COMMAND_VAL);
+ continue;
+ case RTeof:
+ /* Handled below. */
+ break;
+ }
+ }
+ /* Client gone? */
+ if (r == RTeof)
+ break;
+ if (ac == 0 || strcasecmp(av[0], "quit") == 0)
+ break;
+
+ /* Find command. */
+ for (cp = CMDtable; cp->Name; cp++)
+ if (strcasecmp(cp->Name, av[0]) == 0)
+ break;
+ if (cp->Name == NULL) {
+ if ((int)strlen(buff) > 40)
+ syslog(L_NOTICE, "%s unrecognized %.40s...", ClientHost, buff);
+ else
+ syslog(L_NOTICE, "%s unrecognized %s", ClientHost, buff);
+ Reply("%d What?\r\n", NNTP_BAD_COMMAND_VAL);
+ continue;
+ }
+
+ /* Check usage. */
+ if ((cp->Minac != CMDany && ac < cp->Minac)
+ || (cp->Maxac != CMDany && ac > cp->Maxac)) {
+ Reply("%d %s\r\n",
+ NNTP_SYNTAX_VAL, cp->Help ? cp->Help : "Usage error");
+ continue;
+ }
+
+ /* Check permissions and dispatch. */
+ if (cp->Needauth && PERMneedauth) {
+ Reply("%d Authentication required for command\r\n",
+ NNTP_AUTH_NEEDED_VAL);
+ continue;
+ }
+ setproctitle("%s %s", ClientHost, av[0]);
+ (*cp->Function)(ac, av);
+ if (PushedBack)
+ break;
+ if (PERMaccessconf)
+ clienttimeout = PERMaccessconf->clienttimeout;
+ else
+ clienttimeout = innconf->clienttimeout;
+ }
+
+ Reply("%s\r\n", NNTP_GOODBYE_ACK);
+
+ ExitWithStats(0, false);
+ /* NOTREACHED */
+ return 1;
+}
--- /dev/null
+/* $Id: nnrpd.h 7343 2005-06-20 03:23:34Z eagle $
+**
+** Net News Reading Protocol server.
+*/
+
+#include "config.h"
+#include "portable/socket.h"
+#include "portable/time.h"
+
+#include <ctype.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <syslog.h>
+#include <sys/stat.h>
+
+#include "inn/qio.h"
+#include "libinn.h"
+#include "nntp.h"
+#include "paths.h"
+#include "storage.h"
+#include "inn/vector.h"
+#include "inn/timer.h"
+
+/*
+** Maximum input line length, sigh.
+*/
+#define ART_LINE_LENGTH 1000
+#define ART_LINE_MALLOC 1024
+#define ART_MAX 1024
+
+
+/*
+** Some convenient shorthands.
+*/
+typedef struct in_addr INADDR;
+
+
+/*
+** A range of article numbers.
+*/
+typedef struct _ARTRANGE {
+ int Low;
+ int High;
+} ARTRANGE;
+
+/*
+** access configuration for each readers
+ */
+typedef struct _ACCESSGROUP {
+ char *name;
+ char *key;
+ char *read;
+ char *post;
+ char *users;
+ char *rejectwith;
+ int allownewnews;
+ bool allowihave;
+ int locpost;
+ int allowapproved;
+ int used;
+ int localtime;
+ int strippath;
+ int nnrpdperlfilter;
+ int nnrpdpythonfilter;
+ char *fromhost;
+ char *pathhost;
+ char *organization;
+ char *moderatormailer;
+ char *domain;
+ char *complaints;
+ int spoolfirst;
+ int checkincludedtext;
+ int clienttimeout;
+ long localmaxartsize;
+ int readertrack;
+ int strippostcc;
+ int addnntppostinghost;
+ int addnntppostingdate;
+ char *nnrpdposthost;
+ int nnrpdpostport;
+ int nnrpdoverstats;
+ int backoff_auth;
+ char *backoff_db;
+ long backoff_k;
+ long backoff_postfast;
+ long backoff_postslow;
+ long backoff_trigger;
+ int nnrpdcheckart;
+ int nnrpdauthsender;
+ int virtualhost;
+ char *newsmaster;
+ long maxbytespersecond;
+} ACCESSGROUP;
+
+/*
+** What line_read returns.
+*/
+typedef enum _READTYPE {
+ RTeof,
+ RTok,
+ RTlong,
+ RTtimeout
+} READTYPE;
+
+
+/*
+** Structure used by line_read to keep track of what's been read
+*/
+struct line {
+ char *start;
+ char *where;
+ size_t remaining;
+ size_t allocated;
+};
+
+/*
+** Information about the schema of the news overview files.
+*/
+typedef struct _ARTOVERFIELD {
+ char *Header;
+ int Length;
+ bool NeedsHeader;
+} ARTOVERFIELD;
+
+/*
+** Supported timers. If you add new timers to this list, also add them to
+** the list of tags in nnrpd.c.
+*/
+enum timer {
+ TMR_IDLE = TMR_APPLICATION, /* Server is completely idle. */
+ TMR_NEWNEWS, /* Executing NEWNEWS command */
+ TMR_READART, /* Reading an article (SMretrieve) */
+ TMR_CHECKART, /* Checking an article (ARTinstorebytoken) */
+ TMR_NNTPREAD, /* Reading from the peer */
+ TMR_NNTPWRITE, /* Writing to the peer */
+ TMR_MAX
+};
+
+#if defined(MAINLINE)
+#define EXTERN /* NULL */
+#else
+#define EXTERN extern
+#endif /* defined(MAINLINE) */
+
+EXTERN bool PERMauthorized;
+EXTERN bool PERMcanpost;
+EXTERN bool PERMcanread;
+EXTERN bool PERMneedauth;
+EXTERN bool PERMspecified;
+EXTERN ACCESSGROUP *PERMaccessconf;
+EXTERN bool Tracing;
+EXTERN bool Offlinepost;
+EXTERN bool initialSSL;
+EXTERN char **PERMreadlist;
+EXTERN char **PERMpostlist;
+EXTERN char ClientHost[SMBUF];
+EXTERN char ServerHost[SMBUF];
+EXTERN char Username[SMBUF];
+#ifdef HAVE_INET6
+EXTERN char ClientIpString[INET6_ADDRSTRLEN];
+EXTERN char ServerIpString[INET6_ADDRSTRLEN];
+#else
+EXTERN char ClientIpString[20];
+EXTERN char ServerIpString[20];
+#endif
+EXTERN int ClientPort;
+EXTERN int ServerPort;
+EXTERN char LogName[256] ;
+#ifdef HAVE_SSL
+EXTERN bool ClientSSL;
+#endif
+extern char *ACTIVETIMES;
+extern char *HISTORY;
+extern char *ACTIVE;
+extern char *NEWSGROUPS;
+extern char *NNRPACCESS;
+extern char NOACCESS[];
+EXTERN int SPOOLlen;
+EXTERN char PERMpass[SMBUF];
+EXTERN char PERMuser[SMBUF];
+EXTERN FILE *locallog;
+EXTERN int ARTnumber; /* Current article number */
+EXTERN int ARThigh; /* Current high number for group */
+EXTERN int ARTlow; /* Current low number for group */
+EXTERN long ARTcount; /* Current number of articles in group */
+EXTERN long MaxBytesPerSecond; /* maximum bytes per sec a client can use, defaults to 0 */
+EXTERN long ARTget;
+EXTERN long ARTgettime;
+EXTERN long ARTgetsize;
+EXTERN long OVERcount; /* number of XOVER commands */
+EXTERN long OVERhit; /* number of XOVER records found in .overview */
+EXTERN long OVERmiss; /* number of XOVER records found in articles */
+EXTERN long OVERtime; /* number of ms spent sending XOVER data */
+EXTERN long OVERsize; /* number of bytes of XOVER data sent */
+EXTERN long OVERdbz; /* number of ms spent reading dbz data */
+EXTERN long OVERseek; /* number of ms spent seeking history */
+EXTERN long OVERget; /* number of ms spent reading history */
+EXTERN long OVERartcheck; /* number of ms spent article check */
+EXTERN double IDLEtime;
+EXTERN long GRParticles;
+EXTERN long GRPcount;
+EXTERN char *GRPcur;
+EXTERN long POSTreceived;
+EXTERN long POSTrejected;
+
+EXTERN bool BACKOFFenabled;
+EXTERN long ClientIpAddr;
+EXTERN char *VirtualPath;
+EXTERN int VirtualPathlen;
+EXTERN struct history *History;
+EXTERN struct line NNTPline;
+EXTERN struct vector *OVextra;
+EXTERN int overhdr_xref;
+EXTERN bool LLOGenable;
+
+extern const char *ARTpost(char *article, char *idbuff, bool ihave,
+ bool *permanent);
+extern void ARTclose(void);
+extern int TrimSpaces(char *line);
+extern char *Glom(char **av);
+extern int Argify(char *line, char ***argvp);
+extern void InitBackoffConstants(void);
+extern char *PostRecFilename(char *ip, char *user);
+extern int LockPostRec(char *path);
+extern int LockPostRec(char *path);
+extern void UnlockPostRec(char *path);
+extern int RateLimit(long *sleeptime, char *path);
+extern void ExitWithStats(int x, bool readconf);
+extern char *GetHeader(const char *header);
+extern void GRPreport(void);
+extern bool NGgetlist(char ***argvp, char *list);
+extern bool PERMartok(void);
+extern void PERMgetaccess(char *nnrpaccess);
+extern void PERMgetpermissions(void);
+extern void PERMlogin(char *uname, char *pass, char *errorstr);
+extern bool PERMmatch(char **Pats, char **list);
+extern bool ParseDistlist(char ***argvp, char *list);
+extern void SetDefaultAccess(ACCESSGROUP*);
+extern void Reply(const char *fmt, ...);
+extern void Printf(const char *fmt, ...);
+
+extern void CMDauthinfo (int ac, char** av);
+extern void CMDdate (int ac, char** av);
+extern void CMDfetch (int ac, char** av);
+extern void CMDgroup (int ac, char** av);
+extern void CMDhelp (int ac, char** av);
+extern void CMDlist (int ac, char** av);
+extern void CMDmode (int ac, char** av);
+extern void CMDnewgroups (int ac, char** av);
+extern void CMDnewnews (int ac, char** av);
+extern void CMDnextlast (int ac, char** av);
+extern void CMDpost (int ac, char** av);
+extern void CMDxgtitle (int ac, char** av);
+extern void CMDxover (int ac, char** av);
+extern void CMDpat (int ac, char** av);
+extern void CMDxpath (int ac, char** av);
+extern void CMD_unimp (int ac, char** av);
+#ifdef HAVE_SSL
+extern void CMDstarttls (int ac, char** av);
+#endif
+
+
+
+extern char *HandleHeaders(char *article);
+extern bool ARTinstorebytoken(TOKEN token);
+
+extern int TrackClient(char *client, char* user);
+
+#ifdef DO_PERL
+extern void loadPerl(void);
+extern void perlAccess(char *user, struct vector *access_vec);
+extern int perlAuthenticate(char *user, char *passwd, char *errorstring, char*newUser);
+extern void perlAuthInit(void);
+#endif /* DO_PERL */
+
+#ifdef DO_PYTHON
+extern bool PY_use_dynamic;
+
+int PY_authenticate(char *path, char *Username, char *Password, char *errorstring, char *newUser);
+void PY_access(char* path, struct vector *access_vec, char *Username);
+int PY_dynamic(char *Username, char *NewsGroup, int PostFlag, char **reply_message);
+void PY_dynamic_init (char* file);
+#endif /* DO_PYTHON */
+
+void line_free(struct line *);
+void line_init(struct line *);
+READTYPE line_read(struct line *, int, const char **, size_t *);
--- /dev/null
+/* $Id: perl.c 7815 2008-05-05 08:43:58Z iulius $
+**
+** Embedded Perl support for INN.
+**
+** Originally written by Christophe Wolfhugel <wolf@pasteur.fr> (although
+** he wouldn't recongize it any more, so don't blame him) and modified,
+** expanded, and tweaked by James Brister, Dave Hayes, Andrew Gierth, and
+** Russ Allbery among others.
+**
+** This file should contain all innd-specific Perl linkage. Linkage
+** applicable to both innd and nnrpd should go into lib/perl.c instead.
+**
+** We are assuming Perl 5.004 or later.
+*/
+
+#include "config.h"
+#include "clibrary.h"
+
+#include "inn/innconf.h"
+#include "nnrpd.h"
+#include "paths.h"
+#include "post.h"
+
+#include "nntp.h"
+
+/* Skip this entire file if DO_PERL (./configure --with-perl) isn't set. */
+#ifdef DO_PERL
+
+#include <EXTERN.h>
+#include <perl.h>
+#include <XSUB.h>
+#include "ppport.h"
+
+#include "innperl.h"
+
+extern HEADER Table[], *EndOfTable;
+extern char LogName[];
+extern char PERMuser[];
+
+extern char **OtherHeaders;
+extern int OtherCount;
+extern bool HeadersModified;
+
+extern bool PerlLoaded;
+
+/* #define DEBUG_MODIFY only if you want to see verbose outout */
+#ifdef DEBUG_MODIFY
+static FILE *flog;
+void dumpTable(char *msg);
+#endif /* DEBUG_MODIFY */
+
+char *HandleHeaders(char *article)
+{
+ dSP;
+ HEADER *hp;
+ HV *hdr;
+ SV *body;
+ int rc;
+ char *p, *q;
+ static char buf[256];
+ int i;
+ char *s,*t;
+ HE *scan;
+ SV *modswitch;
+ int OtherSize;
+ char *argv[] = { NULL };
+
+ if(!PerlLoaded) {
+ loadPerl();
+ }
+
+ if (!PerlFilterActive)
+ return NULL; /* not really necessary */
+
+#ifdef DEBUG_MODIFY
+ if ((flog = fopen("/var/news/log/nnrpdperlerrror","a+")) == NULL) {
+ syslog(L_ERROR,"Whoops. Can't open error log: %m");
+ }
+#endif /* DEBUG_MODIFY */
+
+ ENTER ;
+ SAVETMPS ;
+
+ /* Create the Perl Hash */
+ hdr = perl_get_hv("hdr", true);
+ for (hp = Table; hp < EndOfTable; hp++) {
+ if (hp->Body)
+ hv_store(hdr, (char *) hp->Name, strlen(hp->Name), newSVpv(hp->Body, 0), 0);
+ }
+
+ /* Also store other headers */
+ OtherSize = OtherCount;
+ for (i = 0; i < OtherCount; i++) {
+ p = OtherHeaders[i];
+ if (p == NULL) {
+ syslog (L_ERROR,"Null header number %d copying headers for Perl",i);
+ continue;
+ }
+ s = strchr(p,':');
+ if (s == NULL) {
+ syslog (L_ERROR,"Bad header copying headers for Perl: '%s'",p);
+ continue;
+ }
+ s++;
+ t = (*s == ' ' ? s + 1 : s);
+ hv_store(hdr, p, (s - p) - 1, newSVpv(t, 0), 0);
+ }
+ /* Store user */
+ sv_setpv(perl_get_sv("user",true), PERMuser);
+
+ /* Store body */
+ body = perl_get_sv("body", true);
+ sv_setpv(body, article);
+
+ /* Call the filtering function */
+ rc = perl_call_argv("filter_post", G_EVAL|G_SCALAR, argv);
+
+ SPAGAIN;
+
+ /* Restore headers */
+ modswitch = perl_get_sv("modify_headers",false);
+ HeadersModified = false;
+ if (SvTRUE(modswitch)) {
+ HeadersModified = true;
+ i = 0;
+
+#ifdef DEBUG_MODIFY
+ dumpTable("Before mod");
+#endif /* DEBUG_MODIFY */
+
+ hv_iterinit(hdr);
+ while ((scan = hv_iternext(hdr)) != NULL) {
+ /* Get the values */
+ p = HePV(scan, PL_na);
+ s = SvPV(HeVAL(scan), PL_na);
+#ifdef DEBUG_MODIFY
+ fprintf(flog,"Hash iter: '%s','%s'\n",p,s);
+#endif /* DEBUG_MODIFY */
+
+ /* See if it's a table header */
+ for (hp = Table; hp < EndOfTable; hp++) {
+ if (strncasecmp(p, hp->Name, hp->Size) == 0) {
+ char *copy = xstrdup(s);
+ HDR_SET(hp - Table, copy);
+ hp->Len = TrimSpaces(hp->Value);
+ for (q = hp->Value ; ISWHITE(*q) || *q == '\n' ; q++)
+ continue;
+ hp->Body = q;
+ if (hp->Len == 0) {
+ free(hp->Value);
+ hp->Value = hp->Body = NULL;
+ }
+ break;
+ }
+ }
+ if (hp != EndOfTable) continue;
+
+ /* Add to other headers */
+ if (i >= OtherSize - 1) {
+ OtherSize += 20;
+ OtherHeaders = xrealloc(OtherHeaders, OtherSize * sizeof(char *));
+ }
+ t = concat(p, ": ", s, (char *) 0);
+ OtherHeaders[i++] = t;
+ }
+ OtherCount = i;
+#ifdef DEBUG_MODIFY
+ dumpTable("After Mod");
+#endif /* DEBUG_MODIFY */
+ }
+
+ hv_undef (hdr);
+ sv_setsv (body, &PL_sv_undef);
+
+ buf [0] = '\0' ;
+
+ if (SvTRUE(ERRSV)) /* check $@ */ {
+ syslog (L_ERROR,"Perl function filter_post died: %s",
+ SvPV(ERRSV, PL_na)) ;
+ (void)POPs ;
+ PerlFilter (false) ;
+ } else if (rc == 1) {
+ p = POPp;
+ if (p != NULL && *p != '\0')
+ strlcpy(buf, p, sizeof(buf));
+ }
+
+ FREETMPS ;
+ LEAVE ;
+
+ if (buf[0] != '\0')
+ return buf ;
+ return NULL;
+}
+
+void loadPerl(void) {
+ char *path;
+
+ path = concatpath(innconf->pathfilter, _PATH_PERL_FILTER_NNRPD);
+ PERLsetup(NULL, path, "filter_post");
+ free(path);
+ PerlFilter(true);
+ PerlLoaded = true;
+}
+
+void perlAccess(char *user, struct vector *access_vec) {
+ dSP;
+ HV *attribs;
+ SV *sv;
+ int rc, i;
+ char *key, *val, *buffer;
+
+ if (!PerlFilterActive)
+ return;
+
+ ENTER;
+ SAVETMPS;
+
+ attribs = perl_get_hv("attributes", true);
+ hv_store(attribs, "hostname", 8, newSVpv(ClientHost, 0), 0);
+ hv_store(attribs, "ipaddress", 9, newSVpv(ClientIpString, 0), 0);
+ hv_store(attribs, "port", 4, newSViv(ClientPort), 0);
+ hv_store(attribs, "interface", 9, newSVpv(ServerHost, 0), 0);
+ hv_store(attribs, "intipaddr", 9, newSVpv(ServerIpString, 0), 0);
+ hv_store(attribs, "intport", 7, newSViv(ServerPort), 0);
+ hv_store(attribs, "username", 8, newSVpv(user, 0), 0);
+
+ PUSHMARK(SP);
+
+ if (perl_get_cv("access", 0) == NULL) {
+ syslog(L_ERROR, "Perl function access not defined");
+ Reply("%d Internal Error (3). Goodbye\r\n", NNTP_ACCESS_VAL);
+ ExitWithStats(1, true);
+ }
+
+ rc = perl_call_pv("access", G_EVAL|G_ARRAY);
+
+ SPAGAIN;
+
+ if (rc == 0 ) { /* Error occured, same as checking $@ */
+ syslog(L_ERROR, "Perl function access died: %s",
+ SvPV(ERRSV, PL_na));
+ Reply("%d Internal Error (1). Goodbye\r\n", NNTP_ACCESS_VAL);
+ ExitWithStats(1, true);
+ }
+
+ if ((rc % 2) != 0) {
+ syslog(L_ERROR, "Perl function access returned an odd number of arguments: %i", rc);
+ Reply("%d Internal Error (2). Goodbye\r\n", NNTP_ACCESS_VAL);
+ ExitWithStats(1, true);
+ }
+
+ vector_resize(access_vec, (rc / 2));
+
+ buffer = xmalloc(BIG_BUFFER);
+
+ for (i = (rc / 2); i >= 1; i--) {
+ sv = POPs;
+ val = SvPV(sv, PL_na);
+ sv = POPs;
+ key = SvPV(sv, PL_na);
+
+ strlcpy(buffer, key, BIG_BUFFER);
+ strlcat(buffer, ": \"", BIG_BUFFER);
+ strlcat(buffer, val, BIG_BUFFER);
+ strlcat(buffer, "\"\n", BIG_BUFFER);
+
+ vector_add(access_vec, xstrdup(buffer));
+ }
+
+ free(buffer);
+
+ PUTBACK;
+ FREETMPS;
+ LEAVE;
+
+}
+
+void perlAuthInit(void) {
+ dSP;
+ int rc;
+
+ if (!PerlFilterActive)
+ return;
+
+ ENTER;
+ SAVETMPS;
+ PUSHMARK(SP);
+
+ if (perl_get_cv("auth_init", 0) == NULL) {
+ syslog(L_ERROR, "Perl function auth_init not defined");
+ Reply("%d Internal Error (3). Goodbye\r\n", NNTP_ACCESS_VAL);
+ ExitWithStats(1, true);
+ }
+
+ rc = perl_call_pv("auth_init", G_EVAL|G_DISCARD);
+
+ SPAGAIN;
+
+
+ if (SvTRUE(ERRSV)) /* check $@ */ {
+ syslog(L_ERROR, "Perl function authenticate died: %s",
+ SvPV(ERRSV, PL_na));
+ Reply("%d Internal Error (1). Goodbye\r\n", NNTP_ACCESS_VAL);
+ ExitWithStats(1, true);
+ }
+
+ while (rc--) {
+ (void)POPs;
+ }
+
+ PUTBACK;
+ FREETMPS;
+ LEAVE;
+
+}
+
+int perlAuthenticate(char *user, char *passwd, char *errorstring, char *newUser) {
+ dSP;
+ HV *attribs;
+ int rc;
+ char *p;
+ int code;
+
+ if (!PerlFilterActive)
+ return NNTP_ACCESS_VAL;
+
+ if (perl_get_cv("authenticate", 0) == NULL) {
+ syslog(L_ERROR, "Perl function authenticate not defined");
+ Reply("%d Internal Error (3). Goodbye\r\n", NNTP_ACCESS_VAL);
+ ExitWithStats(1, true);
+ }
+
+ ENTER;
+ SAVETMPS;
+ attribs = perl_get_hv("attributes", true);
+ hv_store(attribs, "hostname", 8, newSVpv(ClientHost, 0), 0);
+ hv_store(attribs, "ipaddress", 9, newSVpv(ClientIpString, 0), 0);
+ hv_store(attribs, "port", 4, newSViv(ClientPort), 0);
+ hv_store(attribs, "interface", 9, newSVpv(ServerHost, 0), 0);
+ hv_store(attribs, "intipaddr", 9, newSVpv(ServerIpString, 0), 0);
+ hv_store(attribs, "intport", 7, newSViv(ServerPort), 0);
+ hv_store(attribs, "username", 8, newSVpv(user, 0), 0);
+ hv_store(attribs, "password", 8, newSVpv(passwd, 0), 0);
+
+ PUSHMARK(SP);
+ rc = perl_call_pv("authenticate", G_EVAL|G_ARRAY);
+
+ SPAGAIN;
+
+ if (rc == 0 ) { /* Error occured, same as checking $@ */
+ syslog(L_ERROR, "Perl function authenticate died: %s",
+ SvPV(ERRSV, PL_na));
+ Reply("%d Internal Error (1). Goodbye\r\n", NNTP_ACCESS_VAL);
+ ExitWithStats(1, false);
+ }
+
+ if ((rc != 3) && (rc != 2)) {
+ syslog(L_ERROR, "Perl function authenticate returned wrong number of results: %d", rc);
+ Reply("%d Internal Error (2). Goodbye\r\n", NNTP_ACCESS_VAL);
+ ExitWithStats(1, false);
+ }
+
+ if (rc == 3) {
+ p = POPp;
+ strcpy(newUser, p);
+ }
+
+ p = POPp;
+ strcpy(errorstring, p);
+
+ code = POPi;
+
+ if ((code == NNTP_POSTOK_VAL) || (code == NNTP_NOPOSTOK_VAL))
+ code = PERMcanpost ? NNTP_POSTOK_VAL : NNTP_NOPOSTOK_VAL;
+
+ if (code == NNTP_AUTH_NEEDED_VAL)
+ PERMneedauth = true;
+
+ hv_undef(attribs);
+
+ PUTBACK;
+ FREETMPS;
+ LEAVE;
+
+ return code;
+}
+
+#ifdef DEBUG_MODIFY
+void
+dumpTable (msg)
+char *msg;
+{
+ HEADER *hp;
+ int i;
+
+ fprintf(flog,"===BEGIN TABLE DUMP: %s\n",msg);
+
+ for (hp = Table; hp < EndOfTable; hp++) {
+ fprintf(flog," Name: '%s'",hp->Name); fflush(flog);
+ fprintf(flog," Size: '%d'",hp->Size); fflush(flog);
+ fprintf(flog," Value: '%s'\n",((hp->Value == NULL) ? "(NULL)" : hp->Value)); fflush(flog);
+ }
+
+ for (i=0; i<OtherCount; i++) {
+ fprintf(flog,"Extra[%02d]: %s\n",i,OtherHeaders[i]);
+ }
+ fprintf(flog,"===END TABLE DUMP: %s\n",msg);
+}
+#endif /* DEBUG_MODIFY */
+
+#endif /* DO_PERL */
--- /dev/null
+/* $Id: perm.c 7426 2005-12-11 20:37:27Z eagle $
+**
+** How to figure out where a user comes from, and what that user can do once
+** we know who sie is.
+*/
+
+#include "config.h"
+#include "clibrary.h"
+#include "portable/wait.h"
+#include <netdb.h>
+#include <signal.h>
+
+#include "conffile.h"
+#include "inn/innconf.h"
+#include "innperl.h"
+#include "nnrpd.h"
+
+/* Needed on AIX 4.1 to get fd_set and friends. */
+#ifdef HAVE_SYS_SELECT_H
+# include <sys/select.h>
+#endif
+
+/* data types */
+typedef struct _CONFCHAIN {
+ CONFFILE *f;
+ struct _CONFCHAIN *parent;
+} CONFCHAIN;
+
+typedef struct _METHOD {
+ char *name;
+ char *program;
+ int type; /* type of auth (perl, python or external) */
+ char *users; /* only used for auth_methods, not for res_methods. */
+ char **extra_headers;
+ char **extra_logs;
+} METHOD;
+
+typedef struct _AUTHGROUP {
+ char *name;
+ char *key;
+#ifdef HAVE_SSL
+ int require_ssl;
+#endif
+ char *hosts;
+ METHOD **res_methods;
+ METHOD **auth_methods;
+ char *default_user;
+ char *default_domain;
+ char *localaddress;
+ char *access_script;
+ int access_type; /* type of access (perl or python) */
+ char *dynamic_script;
+ int dynamic_type; /* type of dynamic authorization (python only) */
+} AUTHGROUP;
+
+typedef struct _GROUP {
+ char *name;
+ struct _GROUP *above;
+ AUTHGROUP *auth;
+ ACCESSGROUP *access;
+} GROUP;
+
+/* function declarations */
+static void PERMreadfile(char *filename);
+static void authdecl_parse(AUTHGROUP*, CONFFILE*, CONFTOKEN*);
+static void accessdecl_parse(ACCESSGROUP *curaccess, CONFFILE *f, CONFTOKEN *tok);
+static void method_parse(METHOD*, CONFFILE*, CONFTOKEN*, int);
+
+static void add_authgroup(AUTHGROUP*);
+static void add_accessgroup(ACCESSGROUP*);
+static void strip_accessgroups(void);
+
+static METHOD *copy_method(METHOD*);
+static void free_method(METHOD*);
+static AUTHGROUP *copy_authgroup(AUTHGROUP*);
+static void free_authgroup(AUTHGROUP*);
+static ACCESSGROUP *copy_accessgroup(ACCESSGROUP*);
+static void free_accessgroup(ACCESSGROUP*);
+
+static void CompressList(char*);
+static bool MatchHost(char*, char*, char*);
+static int MatchUser(char*, char*);
+static char *ResolveUser(AUTHGROUP*);
+static char *AuthenticateUser(AUTHGROUP*, char*, char*, char*);
+
+static void GrowArray(void***, void*);
+static void PERMvectortoaccess(ACCESSGROUP *acc, const char *name, struct vector *acccess_vec);
+
+/* global variables */
+static AUTHGROUP **auth_realms;
+static AUTHGROUP *success_auth;
+static ACCESSGROUP **access_realms;
+
+static char *ConfigBit;
+static int ConfigBitsize;
+
+extern bool PerlLoaded;
+
+#define PERMlbrace 1
+#define PERMrbrace 2
+#define PERMgroup 3
+#define PERMauth 4
+#define PERMaccess 5
+#define PERMhost 6
+#define PERMauthprog 7
+#define PERMresolv 8
+#define PERMresprog 9
+#define PERMdefuser 10
+#define PERMdefdomain 11
+#define PERMusers 12
+#define PERMnewsgroups 13
+#define PERMread 14
+#define PERMpost 15
+#define PERMaccessrp 16
+#define PERMheader 17
+#define PERMalsolog 18
+#define PERMprogram 19
+#define PERMinclude 20
+#define PERMkey 21
+#define PERMlocaltime 22
+#define PERMstrippath 23
+#define PERMnnrpdperlfilter 24
+#define PERMnnrpdpythonfilter 25
+#define PERMfromhost 26
+#define PERMpathhost 27
+#define PERMorganization 28
+#define PERMmoderatormailer 29
+#define PERMdomain 30
+#define PERMcomplaints 31
+#define PERMspoolfirst 32
+#define PERMcheckincludedtext 33
+#define PERMclienttimeout 34
+#define PERMlocalmaxartsize 35
+#define PERMreadertrack 36
+#define PERMstrippostcc 37
+#define PERMaddnntppostinghost 38
+#define PERMaddnntppostingdate 39
+#define PERMnnrpdposthost 40
+#define PERMnnrpdpostport 41
+#define PERMnnrpdoverstats 42
+#define PERMbackoff_auth 43
+#define PERMbackoff_db 44
+#define PERMbackoff_k 45
+#define PERMbackoff_postfast 46
+#define PERMbackoff_postslow 47
+#define PERMbackoff_trigger 48
+#define PERMnnrpdcheckart 49
+#define PERMnnrpdauthsender 50
+#define PERMvirtualhost 51
+#define PERMnewsmaster 52
+#define PERMlocaladdress 53
+#define PERMrejectwith 54
+#define PERMmaxbytespersecond 55
+#define PERMperl_auth 56
+#define PERMpython_auth 57
+#define PERMperl_access 58
+#define PERMpython_access 59
+#define PERMpython_dynamic 60
+#ifdef HAVE_SSL
+#define PERMrequire_ssl 61
+#define PERMMAX 62
+#else
+#define PERMMAX 61
+#endif
+
+#define TEST_CONFIG(a, b) \
+ { \
+ int byte, offset; \
+ offset = a % 8; \
+ byte = (a - offset) / 8; \
+ b = ((ConfigBit[byte] & (1 << offset)) != 0) ? true : false; \
+ }
+#define SET_CONFIG(a) \
+ { \
+ int byte, offset; \
+ offset = a % 8; \
+ byte = (a - offset) / 8; \
+ ConfigBit[byte] |= (1 << offset); \
+ }
+#define CLEAR_CONFIG(a) \
+ { \
+ int byte, offset; \
+ offset = a % 8; \
+ byte = (a - offset) / 8; \
+ ConfigBit[byte] &= ~(1 << offset); \
+ }
+
+static CONFTOKEN PERMtoks[] = {
+ { PERMlbrace, "{" },
+ { PERMrbrace, "}" },
+ { PERMgroup, "group" },
+ { PERMauth, "auth" },
+ { PERMaccess, "access" },
+ { PERMhost, "hosts:" },
+ { PERMauthprog, "auth:" },
+ { PERMresolv, "res" },
+ { PERMresprog, "res:" },
+ { PERMdefuser, "default:" },
+ { PERMdefdomain, "default-domain:" },
+ { PERMusers, "users:" },
+ { PERMnewsgroups, "newsgroups:" },
+ { PERMread, "read:" },
+ { PERMpost, "post:" },
+ { PERMaccessrp, "access:" },
+ { PERMheader, "header:" },
+ { PERMalsolog, "log:" },
+ { PERMprogram, "program:" },
+ { PERMinclude, "include" },
+ { PERMkey, "key:" },
+ { PERMlocaltime, "localtime:" },
+ { PERMstrippath, "strippath:" },
+ { PERMnnrpdperlfilter, "perlfilter:" },
+ { PERMnnrpdpythonfilter, "pythonfilter:" },
+ { PERMfromhost, "fromhost:" },
+ { PERMpathhost, "pathhost:" },
+ { PERMorganization, "organization:" },
+ { PERMmoderatormailer, "moderatormailer:" },
+ { PERMdomain, "domain:" },
+ { PERMcomplaints, "complaints:" },
+ { PERMspoolfirst, "spoolfirst:" },
+ { PERMcheckincludedtext, "checkincludedtext:" },
+ { PERMclienttimeout, "clienttimeout:" },
+ { PERMlocalmaxartsize, "localmaxartsize:" },
+ { PERMreadertrack, "readertrack:" },
+ { PERMstrippostcc, "strippostcc:" },
+ { PERMaddnntppostinghost, "addnntppostinghost:" },
+ { PERMaddnntppostingdate, "addnntppostingdate:" },
+ { PERMnnrpdposthost, "nnrpdposthost:" },
+ { PERMnnrpdpostport, "nnrpdpostport:" },
+ { PERMnnrpdoverstats, "nnrpdoverstats:" },
+ { PERMbackoff_auth, "backoff_auth:" },
+ { PERMbackoff_db, "backoff_db:" },
+ { PERMbackoff_k, "backoff_k:" },
+ { PERMbackoff_postfast, "backoff_postfast:" },
+ { PERMbackoff_postslow, "backoff_postslow:" },
+ { PERMbackoff_trigger, "backoff_trigger:" },
+ { PERMnnrpdcheckart, "nnrpdcheckart:" },
+ { PERMnnrpdauthsender, "nnrpdauthsender:" },
+ { PERMvirtualhost, "virtualhost:" },
+ { PERMnewsmaster, "newsmaster:" },
+ { PERMlocaladdress, "localaddress:" },
+ { PERMrejectwith, "reject_with:" },
+ { PERMmaxbytespersecond, "max_rate:" },
+ { PERMperl_auth, "perl_auth:" },
+ { PERMpython_auth, "python_auth:" },
+ { PERMperl_access, "perl_access:" },
+ { PERMpython_access, "python_access:" },
+ { PERMpython_dynamic, "python_dynamic:" },
+#ifdef HAVE_SSL
+ { PERMrequire_ssl, "require_ssl:" },
+#endif
+ { 0, 0 }
+};
+
+/* function definitions */
+static void GrowArray(void ***array, void *el)
+{
+ int i;
+
+ if (!*array) {
+ *array = xmalloc(2 * sizeof(void *));
+ i = 0;
+ } else {
+ for (i = 0; (*array)[i]; i++)
+ ;
+ *array = xrealloc(*array, (i + 2) * sizeof(void *));
+ }
+ (*array)[i++] = el;
+ (*array)[i] = 0;
+}
+
+static METHOD *copy_method(METHOD *orig)
+{
+ METHOD *ret;
+ int i;
+
+ ret = xmalloc(sizeof(METHOD));
+ memset(ConfigBit, '\0', ConfigBitsize);
+
+ ret->name = xstrdup(orig->name);
+ ret->program = xstrdup(orig->program);
+ if (orig->users)
+ ret->users = xstrdup(orig->users);
+ else
+ ret->users = 0;
+
+ ret->extra_headers = 0;
+ if (orig->extra_headers) {
+ for (i = 0; orig->extra_headers[i]; i++)
+ GrowArray((void***) &ret->extra_headers,
+ (void*) xstrdup(orig->extra_headers[i]));
+ }
+
+ ret->extra_logs = 0;
+ if (orig->extra_logs) {
+ for (i = 0; orig->extra_logs[i]; i++)
+ GrowArray((void***) &ret->extra_logs,
+ (void*) xstrdup(orig->extra_logs[i]));
+ }
+
+ ret->type = orig->type;
+
+ return(ret);
+}
+
+static void free_method(METHOD *del)
+{
+ int j;
+
+ if (del->extra_headers) {
+ for (j = 0; del->extra_headers[j]; j++)
+ free(del->extra_headers[j]);
+ free(del->extra_headers);
+ }
+ if (del->extra_logs) {
+ for (j = 0; del->extra_logs[j]; j++)
+ free(del->extra_logs[j]);
+ free(del->extra_logs);
+ }
+ if (del->program)
+ free(del->program);
+ if (del->users)
+ free(del->users);
+ free(del->name);
+ free(del);
+}
+
+static AUTHGROUP *copy_authgroup(AUTHGROUP *orig)
+{
+ AUTHGROUP *ret;
+ int i;
+
+ if (!orig)
+ return(0);
+ ret = xmalloc(sizeof(AUTHGROUP));
+ memset(ConfigBit, '\0', ConfigBitsize);
+
+ if (orig->name)
+ ret->name = xstrdup(orig->name);
+ else
+ ret->name = 0;
+
+ if (orig->key)
+ ret->key = xstrdup(orig->key);
+ else
+ ret->key = 0;
+
+ if (orig->hosts)
+ ret->hosts = xstrdup(orig->hosts);
+ else
+ ret->hosts = 0;
+
+#ifdef HAVE_SSL
+ ret->require_ssl = orig->require_ssl;
+#endif
+
+ ret->res_methods = 0;
+ if (orig->res_methods) {
+ for (i = 0; orig->res_methods[i]; i++)
+ GrowArray((void***) &ret->res_methods,
+ (void*) copy_method(orig->res_methods[i]));;
+ }
+
+ ret->auth_methods = 0;
+ if (orig->auth_methods) {
+ for (i = 0; orig->auth_methods[i]; i++)
+ GrowArray((void***) &ret->auth_methods,
+ (void*) copy_method(orig->auth_methods[i]));
+ }
+
+ if (orig->default_user)
+ ret->default_user = xstrdup(orig->default_user);
+ else
+ ret->default_user = 0;
+
+ if (orig->default_domain)
+ ret->default_domain = xstrdup(orig->default_domain);
+ else
+ ret->default_domain = 0;
+
+ if (orig->localaddress)
+ ret->localaddress = xstrdup(orig->localaddress);
+ else
+ ret->localaddress = 0;
+
+ if (orig->access_script)
+ ret->access_script = xstrdup(orig->access_script);
+ else
+ ret->access_script = 0;
+
+ if (orig->access_type)
+ ret->access_type = orig->access_type;
+ else
+ ret->access_type = 0;
+
+ if (orig->dynamic_script)
+ ret->dynamic_script = xstrdup(orig->dynamic_script);
+ else
+ ret->dynamic_script = 0;
+
+ if (orig->dynamic_type)
+ ret->dynamic_type = orig->dynamic_type;
+ else
+ ret->dynamic_type = 0;
+
+ return(ret);
+}
+
+static ACCESSGROUP *copy_accessgroup(ACCESSGROUP *orig)
+{
+ ACCESSGROUP *ret;
+
+ if (!orig)
+ return(0);
+ ret = xmalloc(sizeof(ACCESSGROUP));
+ memset(ConfigBit, '\0', ConfigBitsize);
+ /* copy all anyway, and update for local strings */
+ *ret = *orig;
+
+ if (orig->name)
+ ret->name = xstrdup(orig->name);
+ if (orig->key)
+ ret->key = xstrdup(orig->key);
+ if (orig->read)
+ ret->read = xstrdup(orig->read);
+ if (orig->post)
+ ret->post = xstrdup(orig->post);
+ if (orig->users)
+ ret->users = xstrdup(orig->users);
+ if (orig->rejectwith)
+ ret->rejectwith = xstrdup(orig->rejectwith);
+ if (orig->fromhost)
+ ret->fromhost = xstrdup(orig->fromhost);
+ if (orig->pathhost)
+ ret->pathhost = xstrdup(orig->pathhost);
+ if (orig->organization)
+ ret->organization = xstrdup(orig->organization);
+ if (orig->moderatormailer)
+ ret->moderatormailer = xstrdup(orig->moderatormailer);
+ if (orig->domain)
+ ret->domain = xstrdup(orig->domain);
+ if (orig->complaints)
+ ret->complaints = xstrdup(orig->complaints);
+ if (orig->nnrpdposthost)
+ ret->nnrpdposthost = xstrdup(orig->nnrpdposthost);
+ if (orig->backoff_db)
+ ret->backoff_db = xstrdup(orig->backoff_db);
+ if (orig->newsmaster)
+ ret->newsmaster = xstrdup(orig->newsmaster);
+ return(ret);
+}
+
+static void SetDefaultAuth(AUTHGROUP *curauth UNUSED)
+{
+#ifdef HAVE_SSL
+ curauth->require_ssl = false;
+#endif
+}
+
+void SetDefaultAccess(ACCESSGROUP *curaccess)
+{
+ curaccess->allownewnews = innconf->allownewnews;;
+ curaccess->allowihave = false;
+ curaccess->locpost = false;
+ curaccess->allowapproved = false;
+ curaccess->localtime = false;
+ curaccess->strippath = false;
+ curaccess->nnrpdperlfilter = true;
+ curaccess->nnrpdpythonfilter = true;
+ curaccess->fromhost = NULL;
+ if (innconf->fromhost)
+ curaccess->fromhost = xstrdup(innconf->fromhost);
+ curaccess->pathhost = NULL;
+ if (innconf->pathhost)
+ curaccess->pathhost = xstrdup(innconf->pathhost);
+ curaccess->organization = NULL;
+ if (innconf->organization)
+ curaccess->organization = xstrdup(innconf->organization);
+ curaccess->moderatormailer = NULL;
+ if (innconf->moderatormailer)
+ curaccess->moderatormailer = xstrdup(innconf->moderatormailer);
+ curaccess->domain = NULL;
+ if (innconf->domain)
+ curaccess->domain = xstrdup(innconf->domain);
+ curaccess->complaints = NULL;
+ if (innconf->complaints)
+ curaccess->complaints = xstrdup(innconf->complaints);
+ curaccess->spoolfirst = innconf->spoolfirst;
+ curaccess->checkincludedtext = innconf->checkincludedtext;
+ curaccess->clienttimeout = innconf->clienttimeout;
+ curaccess->localmaxartsize = innconf->localmaxartsize;
+ curaccess->readertrack = innconf->readertrack;
+ curaccess->strippostcc = innconf->strippostcc;
+ curaccess->addnntppostinghost = innconf->addnntppostinghost;
+ curaccess->addnntppostingdate = innconf->addnntppostingdate;
+ curaccess->nnrpdposthost = innconf->nnrpdposthost;
+ curaccess->nnrpdpostport = innconf->nnrpdpostport;
+ curaccess->nnrpdoverstats = innconf->nnrpdoverstats;
+ curaccess->backoff_auth = innconf->backoffauth;
+ curaccess->backoff_db = NULL;
+ if (innconf->backoffdb && *innconf->backoffdb != '\0')
+ curaccess->backoff_db = xstrdup(innconf->backoffdb);
+ curaccess->backoff_k = innconf->backoffk;
+ curaccess->backoff_postfast = innconf->backoffpostfast;
+ curaccess->backoff_postslow = innconf->backoffpostslow;
+ curaccess->backoff_trigger = innconf->backofftrigger;
+ curaccess->nnrpdcheckart = innconf->nnrpdcheckart;
+ curaccess->nnrpdauthsender = innconf->nnrpdauthsender;
+ curaccess->virtualhost = false;
+ curaccess->newsmaster = NULL;
+ curaccess->maxbytespersecond = 0;
+}
+
+static void free_authgroup(AUTHGROUP *del)
+{
+ int i;
+
+ if (del->name)
+ free(del->name);
+ if (del->key)
+ free(del->key);
+ if (del->hosts)
+ free(del->hosts);
+ if (del->res_methods) {
+ for (i = 0; del->res_methods[i]; i++)
+ free_method(del->res_methods[i]);
+ free(del->res_methods);
+ }
+ if (del->auth_methods) {
+ for (i = 0; del->auth_methods[i]; i++)
+ free_method(del->auth_methods[i]);
+ free(del->auth_methods);
+ }
+ if (del->default_user)
+ free(del->default_user);
+ if (del->default_domain)
+ free(del->default_domain);
+ if (del->localaddress)
+ free(del->localaddress);
+ if (del->access_script)
+ free(del->access_script);
+ if (del->dynamic_script)
+ free(del->dynamic_script);
+ free(del);
+}
+
+static void free_accessgroup(ACCESSGROUP *del)
+{
+ if (del->name)
+ free(del->name);
+ if (del->key)
+ free(del->key);
+ if (del->read)
+ free(del->read);
+ if (del->post)
+ free(del->post);
+ if (del->users)
+ free(del->users);
+ if (del->rejectwith)
+ free(del->rejectwith);
+ if (del->fromhost)
+ free(del->fromhost);
+ if (del->pathhost)
+ free(del->pathhost);
+ if (del->organization)
+ free(del->organization);
+ if (del->moderatormailer)
+ free(del->moderatormailer);
+ if (del->domain)
+ free(del->domain);
+ if (del->complaints)
+ free(del->complaints);
+ if (del->nnrpdposthost)
+ free(del->nnrpdposthost);
+ if (del->backoff_db)
+ free(del->backoff_db);
+ if (del->newsmaster)
+ free(del->newsmaster);
+ free(del);
+}
+
+static void ReportError(CONFFILE *f, const char *err)
+{
+ syslog(L_ERROR, "%s syntax error in %s(%d), %s", ClientHost,
+ f->filename, f->lineno, err);
+ Reply("%d NNTP server unavailable. Try later.\r\n", NNTP_TEMPERR_VAL);
+ ExitWithStats(1, true);
+}
+
+static void method_parse(METHOD *method, CONFFILE *f, CONFTOKEN *tok, int auth)
+{
+ int oldtype;
+
+ oldtype = tok->type;
+ tok = CONFgettoken(0, f);
+
+ if (tok == NULL) {
+ ReportError(f, "Expected value.");
+ }
+
+ switch (oldtype) {
+ case PERMheader:
+ GrowArray((void***) &method->extra_headers, (void*) xstrdup(tok->name));
+ break;
+ case PERMalsolog:
+ GrowArray((void***) &method->extra_logs, (void*) xstrdup(tok->name));
+ break;
+ case PERMusers:
+
+ if (!auth) {
+ ReportError(f, "Unexpected users: directive in file.");
+ } else if (method->users) {
+ ReportError(f, "Multiple users: directive in file.");
+ }
+
+ method->users = xstrdup(tok->name);
+ break;
+ case PERMprogram:
+ if (method->program) {
+ ReportError(f, "Multiple program: directives in auth/res decl.");
+ }
+
+ method->program = xstrdup(tok->name);
+ break;
+ }
+}
+
+static void authdecl_parse(AUTHGROUP *curauth, CONFFILE *f, CONFTOKEN *tok)
+{
+ int oldtype,boolval;
+ METHOD *m;
+ bool bit;
+ char buff[SMBUF], *oldname, *p;
+
+ oldtype = tok->type;
+ oldname = tok->name;
+
+ tok = CONFgettoken(PERMtoks, f);
+
+ if (tok == NULL) {
+ ReportError(f, "Expected value.");
+ }
+ TEST_CONFIG(oldtype, bit);
+ if (bit) {
+ snprintf(buff, sizeof(buff), "Duplicated '%s' field in authgroup.",
+ oldname);
+ ReportError(f, buff);
+ }
+
+ if (strcasecmp(tok->name, "on") == 0
+ || strcasecmp(tok->name, "true") == 0
+ || strcasecmp(tok->name, "yes") == 0)
+ boolval = true;
+ else if (strcasecmp(tok->name, "off") == 0
+ || strcasecmp(tok->name, "false") == 0
+ || strcasecmp(tok->name, "no") == 0)
+ boolval = false;
+ else
+ boolval = -1;
+
+ switch (oldtype) {
+ case PERMkey:
+ curauth->key = xstrdup(tok->name);
+ SET_CONFIG(PERMkey);
+ break;
+#ifdef HAVE_SSL
+ case PERMrequire_ssl:
+ if (boolval != -1) curauth->require_ssl = boolval;
+ SET_CONFIG(PERMrequire_ssl);
+ break;
+#endif
+ case PERMhost:
+ curauth->hosts = xstrdup(tok->name);
+ CompressList(curauth->hosts);
+ SET_CONFIG(PERMhost);
+
+ /* nnrpd.c downcases the names of connecting hosts. We should
+ therefore also downcase the wildmat patterns to make sure there
+ aren't any surprises. DNS is case-insensitive. */
+ for (p = curauth->hosts; *p; p++)
+ if (CTYPE(isupper, (unsigned char) *p))
+ *p = tolower((unsigned char) *p);
+
+ break;
+ case PERMdefdomain:
+ curauth->default_domain = xstrdup(tok->name);
+ SET_CONFIG(PERMdefdomain);
+ break;
+ case PERMdefuser:
+ curauth->default_user = xstrdup(tok->name);
+ SET_CONFIG(PERMdefuser);
+ break;
+ case PERMresolv:
+ case PERMresprog:
+ m = xcalloc(1, sizeof(METHOD));
+ memset(ConfigBit, '\0', ConfigBitsize);
+ GrowArray((void***) &curauth->res_methods, (void*) m);
+
+ if (oldtype == PERMresprog)
+ m->program = xstrdup(tok->name);
+ else {
+ m->name = xstrdup(tok->name);
+ tok = CONFgettoken(PERMtoks, f);
+ if (tok == NULL || tok->type != PERMlbrace) {
+ ReportError(f, "Expected '{' after 'res'");
+ }
+
+ tok = CONFgettoken(PERMtoks, f);
+
+ while (tok != NULL && tok->type != PERMrbrace) {
+ method_parse(m, f, tok, 0);
+ tok = CONFgettoken(PERMtoks, f);
+ }
+
+ if (tok == NULL) {
+ ReportError(f, "Unexpected EOF.");
+ }
+ }
+ break;
+ case PERMauth:
+ case PERMperl_auth:
+ case PERMpython_auth:
+ case PERMauthprog:
+ m = xcalloc(1, sizeof(METHOD));
+ memset(ConfigBit, '\0', ConfigBitsize);
+ GrowArray((void***) &curauth->auth_methods, (void*) m);
+ if (oldtype == PERMauthprog) {
+ m->type = PERMauthprog;
+ m->program = xstrdup(tok->name);
+ } else if (oldtype == PERMperl_auth) {
+#ifdef DO_PERL
+ m->type = PERMperl_auth;
+ m->program = xstrdup(tok->name);
+#else
+ ReportError(f, "perl_auth can not be used in readers.conf: inn not compiled with perl support enabled.");
+#endif
+ } else if (oldtype == PERMpython_auth) {
+#ifdef DO_PYTHON
+ m->type = PERMpython_auth;
+ m->program = xstrdup(tok->name);
+#else
+ ReportError(f, "python_auth can not be used in readers.conf: inn not compiled with python support enabled.");
+#endif
+ } else {
+ m->name = xstrdup(tok->name);
+ tok = CONFgettoken(PERMtoks, f);
+
+ if (tok == NULL || tok->type != PERMlbrace) {
+ ReportError(f, "Expected '{' after 'auth'");
+ }
+
+ tok = CONFgettoken(PERMtoks, f);
+
+ while (tok != NULL && tok->type != PERMrbrace) {
+ method_parse(m, f, tok, 1);
+ tok = CONFgettoken(PERMtoks, f);
+ }
+
+ if (tok == NULL) {
+ ReportError(f, "Unexpected EOF.");
+ }
+ }
+ break;
+ case PERMperl_access:
+#ifdef DO_PERL
+ curauth->access_script = xstrdup(tok->name);
+ curauth->access_type = PERMperl_access;
+#else
+ ReportError(f, "perl_access can not be used in readers.conf: inn not compiled with perl support enabled.");
+#endif
+ break;
+ case PERMpython_access:
+#ifdef DO_PYTHON
+ curauth->access_script = xstrdup(tok->name);
+ curauth->access_type = PERMpython_access;
+#else
+ ReportError(f, "python_access can not be used in readers.conf: inn not compiled with python support enabled.");
+#endif
+ break;
+ case PERMpython_dynamic:
+#ifdef DO_PYTHON
+ curauth->dynamic_script = xstrdup(tok->name);
+ curauth->dynamic_type = PERMpython_dynamic;
+#else
+ ReportError(f, "python_dynamic can not be used in readers.conf: inn not compiled with python support enabled.");
+#endif
+ break;
+ case PERMlocaladdress:
+ curauth->localaddress = xstrdup(tok->name);
+ CompressList(curauth->localaddress);
+ SET_CONFIG(PERMlocaladdress);
+ break;
+ default:
+ snprintf(buff, sizeof(buff), "Unexpected token: %s", tok->name);
+ ReportError(f, buff);
+ break;
+ }
+}
+
+static void accessdecl_parse(ACCESSGROUP *curaccess, CONFFILE *f, CONFTOKEN *tok)
+{
+ int oldtype, boolval;
+ bool bit;
+ char buff[SMBUF], *oldname;
+
+ oldtype = tok->type;
+ oldname = tok->name;
+
+ tok = CONFgettoken(0, f);
+
+ if (tok == NULL) {
+ ReportError(f, "Expected value.");
+ }
+ TEST_CONFIG(oldtype, bit);
+ if (bit) {
+ snprintf(buff, sizeof(buff), "Duplicated '%s' field in accessgroup.",
+ oldname);
+ ReportError(f, buff);
+ }
+ if (strcasecmp(tok->name, "on") == 0
+ || strcasecmp(tok->name, "true") == 0
+ || strcasecmp(tok->name, "yes") == 0)
+ boolval = true;
+ else if (strcasecmp(tok->name, "off") == 0
+ || strcasecmp(tok->name, "false") == 0
+ || strcasecmp(tok->name, "no") == 0)
+ boolval = false;
+ else
+ boolval = -1;
+
+ switch (oldtype) {
+ case PERMkey:
+ curaccess->key = xstrdup(tok->name);
+ SET_CONFIG(oldtype);
+ break;
+ case PERMusers:
+ curaccess->users = xstrdup(tok->name);
+ CompressList(curaccess->users);
+ SET_CONFIG(oldtype);
+ break;
+ case PERMrejectwith:
+ curaccess->rejectwith = xstrdup(tok->name);
+ SET_CONFIG(oldtype);
+ break;
+ case PERMnewsgroups:
+ TEST_CONFIG(PERMread, bit);
+ if (bit) {
+ /* syntax error.. can't set read: or post: _and_ use
+ * newsgroups: */
+ ReportError(f, "read: newsgroups already set.");
+ }
+ TEST_CONFIG(PERMpost, bit);
+ if (bit) {
+ /* syntax error.. can't set read: or post: _and_ use
+ * newsgroups: */
+ ReportError(f, "post: newsgroups already set.");
+ }
+
+ curaccess->read = xstrdup(tok->name);
+ CompressList(curaccess->read);
+ curaccess->post = xstrdup(tok->name);
+ CompressList(curaccess->post);
+ SET_CONFIG(oldtype);
+ SET_CONFIG(PERMread);
+ SET_CONFIG(PERMpost);
+ break;
+ case PERMread:
+ curaccess->read = xstrdup(tok->name);
+ CompressList(curaccess->read);
+ SET_CONFIG(oldtype);
+ break;
+ case PERMpost:
+ curaccess->post = xstrdup(tok->name);
+ CompressList(curaccess->post);
+ SET_CONFIG(oldtype);
+ break;
+ case PERMaccessrp:
+ TEST_CONFIG(PERMread, bit);
+ if (bit && strchr(tok->name, 'R') == NULL) {
+ free(curaccess->read);
+ curaccess->read = 0;
+ CLEAR_CONFIG(PERMread);
+ }
+ TEST_CONFIG(PERMpost, bit);
+ if (bit && strchr(tok->name, 'P') == NULL) {
+ free(curaccess->post);
+ curaccess->post = 0;
+ CLEAR_CONFIG(PERMpost);
+ }
+ curaccess->allowapproved = (strchr(tok->name, 'A') != NULL);
+ curaccess->allownewnews = (strchr(tok->name, 'N') != NULL);
+ curaccess->allowihave = (strchr(tok->name, 'I') != NULL);
+ curaccess->locpost = (strchr(tok->name, 'L') != NULL);
+ SET_CONFIG(oldtype);
+ break;
+ case PERMlocaltime:
+ if (boolval != -1) curaccess->localtime = boolval;
+ SET_CONFIG(oldtype);
+ break;
+ case PERMstrippath:
+ if (boolval != -1) curaccess->strippath = boolval;
+ SET_CONFIG(oldtype);
+ break;
+ case PERMnnrpdperlfilter:
+ if (boolval != -1) curaccess->nnrpdperlfilter = boolval;
+ SET_CONFIG(oldtype);
+ break;
+ case PERMnnrpdpythonfilter:
+ if (boolval != -1) curaccess->nnrpdpythonfilter = boolval;
+ SET_CONFIG(oldtype);
+ break;
+ case PERMfromhost:
+ if (curaccess->fromhost)
+ free(curaccess->fromhost);
+ curaccess->fromhost = xstrdup(tok->name);
+ SET_CONFIG(oldtype);
+ break;
+ case PERMpathhost:
+ if (curaccess->pathhost)
+ free(curaccess->pathhost);
+ curaccess->pathhost = xstrdup(tok->name);
+ SET_CONFIG(oldtype);
+ break;
+ case PERMorganization:
+ if (curaccess->organization)
+ free(curaccess->organization);
+ curaccess->organization = xstrdup(tok->name);
+ SET_CONFIG(oldtype);
+ break;
+ case PERMmoderatormailer:
+ if (curaccess->moderatormailer)
+ free(curaccess->moderatormailer);
+ curaccess->moderatormailer = xstrdup(tok->name);
+ SET_CONFIG(oldtype);
+ break;
+ case PERMdomain:
+ if (curaccess->domain)
+ free(curaccess->domain);
+ curaccess->domain = xstrdup(tok->name);
+ SET_CONFIG(oldtype);
+ break;
+ case PERMcomplaints:
+ if (curaccess->complaints)
+ free(curaccess->complaints);
+ curaccess->complaints = xstrdup(tok->name);
+ SET_CONFIG(oldtype);
+ break;
+ case PERMspoolfirst:
+ if (boolval != -1) curaccess->spoolfirst = boolval;
+ SET_CONFIG(oldtype);
+ break;
+ case PERMcheckincludedtext:
+ if (boolval != -1) curaccess->checkincludedtext = boolval;
+ SET_CONFIG(oldtype);
+ break;
+ case PERMclienttimeout:
+ curaccess->clienttimeout = atoi(tok->name);
+ SET_CONFIG(oldtype);
+ break;
+ case PERMlocalmaxartsize:
+ curaccess->localmaxartsize = atol(tok->name);
+ SET_CONFIG(oldtype);
+ break;
+ case PERMreadertrack:
+ if (boolval != -1) curaccess->readertrack = boolval;
+ SET_CONFIG(oldtype);
+ break;
+ case PERMstrippostcc:
+ if (boolval != -1) curaccess->strippostcc = boolval;
+ SET_CONFIG(oldtype);
+ break;
+ case PERMaddnntppostinghost:
+ if (boolval != -1) curaccess->addnntppostinghost = boolval;
+ SET_CONFIG(oldtype);
+ break;
+ case PERMaddnntppostingdate:
+ if (boolval != -1) curaccess->addnntppostingdate = boolval;
+ SET_CONFIG(oldtype);
+ break;
+ case PERMnnrpdposthost:
+ if (curaccess->nnrpdposthost)
+ free(curaccess->nnrpdposthost);
+ curaccess->nnrpdposthost = xstrdup(tok->name);
+ SET_CONFIG(oldtype);
+ break;
+ case PERMnnrpdpostport:
+ curaccess->nnrpdpostport = atoi(tok->name);
+ SET_CONFIG(oldtype);
+ break;
+ case PERMnnrpdoverstats:
+ if (boolval != -1) curaccess->nnrpdoverstats = boolval;
+ SET_CONFIG(oldtype);
+ break;
+ case PERMbackoff_auth:
+ if (boolval != -1) curaccess->backoff_auth = boolval;
+ SET_CONFIG(oldtype);
+ break;
+ case PERMbackoff_db:
+ if (curaccess->backoff_db)
+ free(curaccess->backoff_db);
+ curaccess->backoff_db = xstrdup(tok->name);
+ SET_CONFIG(oldtype);
+ break;
+ case PERMbackoff_k:
+ curaccess->backoff_k = atol(tok->name);
+ SET_CONFIG(oldtype);
+ break;
+ case PERMbackoff_postfast:
+ curaccess->backoff_postfast = atol(tok->name);
+ SET_CONFIG(oldtype);
+ break;
+ case PERMbackoff_postslow:
+ curaccess->backoff_postslow = atol(tok->name);
+ SET_CONFIG(oldtype);
+ break;
+ case PERMbackoff_trigger:
+ curaccess->backoff_trigger = atol(tok->name);
+ SET_CONFIG(oldtype);
+ break;
+ case PERMnnrpdcheckart:
+ if (boolval != -1) curaccess->nnrpdcheckart = boolval;
+ SET_CONFIG(oldtype);
+ break;
+ case PERMnnrpdauthsender:
+ if (boolval != -1) curaccess->nnrpdauthsender = boolval;
+ SET_CONFIG(oldtype);
+ break;
+ case PERMvirtualhost:
+ if (boolval != -1) curaccess->virtualhost = boolval;
+ SET_CONFIG(oldtype);
+ break;
+ case PERMnewsmaster:
+ if (curaccess->newsmaster)
+ free(curaccess->newsmaster);
+ curaccess->newsmaster = xstrdup(tok->name);
+ SET_CONFIG(oldtype);
+ break;
+ case PERMmaxbytespersecond:
+ curaccess->maxbytespersecond = atol(tok->name);
+ SET_CONFIG(oldtype);
+ break;
+ default:
+ snprintf(buff, sizeof(buff), "Unexpected token: %s", tok->name);
+ ReportError(f, buff);
+ break;
+ }
+}
+
+static void PERMvectortoaccess(ACCESSGROUP *acc, const char *name, struct vector *access_vec) {
+ CONFTOKEN *tok = NULL;
+ CONFFILE *file;
+ char *str;
+ unsigned int i;
+
+ file = xcalloc(1, sizeof(CONFFILE));
+ file->array = access_vec->strings;
+ file->array_len = access_vec->count;
+
+ memset(ConfigBit, '\0', ConfigBitsize);
+
+ SetDefaultAccess(acc);
+ str = xstrdup(name);
+ acc->name = str;
+
+ for (i = 0; i <= access_vec->count; i++) {
+ tok = CONFgettoken(PERMtoks, file);
+
+ if (tok != NULL) {
+ accessdecl_parse(acc, file, tok);
+ }
+ }
+ free(file);
+ return;
+}
+
+static void PERMreadfile(char *filename)
+{
+ CONFCHAIN *cf = NULL,
+ *hold = NULL;
+ CONFTOKEN *tok = NULL;
+ int inwhat;
+ GROUP *curgroup = NULL,
+ *newgroup = NULL;
+ ACCESSGROUP *curaccess = NULL;
+ AUTHGROUP *curauth = NULL;
+ int oldtype;
+ char *str = NULL;
+ char *path = NULL;
+ char buff[SMBUF];
+
+ if(filename != NULL) {
+ syslog(L_TRACE, "Reading access from %s",
+ filename == NULL ? "(NULL)" : filename);
+ }
+
+ cf = xmalloc(sizeof(CONFCHAIN));
+ if ((cf->f = CONFfopen(filename)) == NULL) {
+ syslog(L_ERROR, "%s cannot open %s: %m", ClientHost, filename);
+ Reply("%d NNTP server unavailable. Try later.\r\n", NNTP_TEMPERR_VAL);
+ ExitWithStats(1, true);
+ }
+ cf->parent = 0;
+
+ /* are we editing an AUTH or ACCESS group? */
+
+ inwhat = 0;
+ newgroup = curgroup = 0;
+
+ tok = CONFgettoken(PERMtoks, cf->f);
+
+ while (tok != NULL) {
+ if (inwhat == 0) {
+ /* top-level parser */
+
+ switch (tok->type) {
+ /* include a child file */
+
+ case PERMinclude:
+ tok = CONFgettoken(0, cf->f);
+
+ if (tok == NULL) {
+ ReportError(cf->f, "Expected filename after 'include'.");
+ }
+
+ hold = xmalloc(sizeof(CONFCHAIN));
+ hold->parent = cf;
+
+ /* unless the filename's path is fully qualified, open it
+ * relative to /news/etc */
+
+ path = concatpath(innconf->pathetc, tok->name);
+ hold->f = CONFfopen(path);
+ free(path);
+
+ if (hold->f == NULL) {
+ ReportError(cf->f, "Couldn't open 'include' filename.");
+ }
+
+ cf = hold;
+ goto again;
+ break;
+
+ /* nested group declaration. */
+ case PERMgroup:
+ tok = CONFgettoken(PERMtoks, cf->f);
+
+ if (tok == NULL) {
+ ReportError(cf->f, "Unexpected EOF at group name");
+ }
+
+ newgroup = xmalloc(sizeof(GROUP));
+ newgroup->above = curgroup;
+ newgroup->name = xstrdup(tok->name);
+ memset(ConfigBit, '\0', ConfigBitsize);
+
+ tok = CONFgettoken(PERMtoks, cf->f);
+
+ if (tok == NULL || tok->type != PERMlbrace) {
+ ReportError(cf->f, "Expected '{' after group name");
+ }
+
+ /* nested group declaration */
+ if (curgroup) {
+ newgroup->auth = copy_authgroup(curgroup->auth);
+ newgroup->access = copy_accessgroup(curgroup->access);
+ } else {
+ newgroup->auth = 0;
+ newgroup->access = 0;
+ }
+
+ curgroup = newgroup;
+ break;
+
+ /* beginning of an auth or access group decl */
+ case PERMauth:
+ case PERMaccess:
+ oldtype = tok->type;
+
+ if ((tok = CONFgettoken(PERMtoks, cf->f)) == NULL) {
+ ReportError(cf->f, "Expected identifier.");
+ }
+
+ str = xstrdup(tok->name);
+
+ tok = CONFgettoken(PERMtoks, cf->f);
+
+ if (tok == NULL || tok->type != PERMlbrace) {
+ ReportError(cf->f, "Expected '{'");
+ }
+
+ switch (oldtype) {
+ case PERMauth:
+ if (curgroup && curgroup->auth)
+ curauth = copy_authgroup(curgroup->auth);
+ else {
+ curauth = xcalloc(1, sizeof(AUTHGROUP));
+ memset(ConfigBit, '\0', ConfigBitsize);
+ SetDefaultAuth(curauth);
+ }
+
+ curauth->name = str;
+ inwhat = 1;
+ break;
+
+ case PERMaccess:
+ if (curgroup && curgroup->access)
+ curaccess = copy_accessgroup(curgroup->access);
+ else {
+ curaccess = xcalloc(1, sizeof(ACCESSGROUP));
+ memset(ConfigBit, '\0', ConfigBitsize);
+ SetDefaultAccess(curaccess);
+ }
+ curaccess->name = str;
+ inwhat = 2;
+ break;
+ }
+
+ break;
+
+ /* end of a group declaration */
+
+ case PERMrbrace:
+ if (curgroup == NULL) {
+ ReportError(cf->f, "Unmatched '}'");
+ }
+
+ newgroup = curgroup;
+ curgroup = curgroup->above;
+ if (newgroup->auth)
+ free_authgroup(newgroup->auth);
+ if (newgroup->access)
+ free_accessgroup(newgroup->access);
+ free(newgroup->name);
+ free(newgroup);
+ break;
+
+ /* stuff that belongs in an authgroup */
+ case PERMhost:
+#ifdef HAVE_SSL
+ case PERMrequire_ssl:
+#endif
+ case PERMauthprog:
+ case PERMresprog:
+ case PERMdefuser:
+ case PERMdefdomain:
+ if (curgroup == NULL) {
+ curgroup = xcalloc(1, sizeof(GROUP));
+ memset(ConfigBit, '\0', ConfigBitsize);
+ }
+ if (curgroup->auth == NULL) {
+ curgroup->auth = xcalloc(1, sizeof(AUTHGROUP));
+ memset(ConfigBit, '\0', ConfigBitsize);
+ SetDefaultAuth(curgroup->auth);
+ }
+
+ authdecl_parse(curgroup->auth, cf->f, tok);
+ break;
+
+ /* stuff that belongs in an accessgroup */
+ case PERMusers:
+ case PERMrejectwith:
+ case PERMnewsgroups:
+ case PERMread:
+ case PERMpost:
+ case PERMaccessrp:
+ case PERMlocaltime:
+ case PERMstrippath:
+ case PERMnnrpdperlfilter:
+ case PERMnnrpdpythonfilter:
+ case PERMfromhost:
+ case PERMpathhost:
+ case PERMorganization:
+ case PERMmoderatormailer:
+ case PERMdomain:
+ case PERMcomplaints:
+ case PERMspoolfirst:
+ case PERMcheckincludedtext:
+ case PERMclienttimeout:
+ case PERMlocalmaxartsize:
+ case PERMreadertrack:
+ case PERMstrippostcc:
+ case PERMaddnntppostinghost:
+ case PERMaddnntppostingdate:
+ case PERMnnrpdposthost:
+ case PERMnnrpdpostport:
+ case PERMnnrpdoverstats:
+ case PERMbackoff_auth:
+ case PERMbackoff_db:
+ case PERMbackoff_k:
+ case PERMbackoff_postfast:
+ case PERMbackoff_postslow:
+ case PERMbackoff_trigger:
+ case PERMnnrpdcheckart:
+ case PERMnnrpdauthsender:
+ case PERMvirtualhost:
+ case PERMnewsmaster:
+ if (!curgroup) {
+ curgroup = xcalloc(1, sizeof(GROUP));
+ memset(ConfigBit, '\0', ConfigBitsize);
+ }
+ if (!curgroup->access) {
+ curgroup->access = xcalloc(1, sizeof(ACCESSGROUP));
+ memset(ConfigBit, '\0', ConfigBitsize);
+ SetDefaultAccess(curgroup->access);
+ }
+ accessdecl_parse(curgroup->access, cf->f, tok);
+ break;
+ default:
+ snprintf(buff, sizeof(buff), "Unexpected token: %s", tok->name);
+ ReportError(cf->f, buff);
+ break;
+ }
+ } else if (inwhat == 1) {
+ /* authgroup parser */
+ if (tok->type == PERMrbrace) {
+ inwhat = 0;
+
+ if (curauth->name
+#ifdef HAVE_SSL
+ && ((curauth->require_ssl == false) || (ClientSSL == true))
+#endif
+ && MatchHost(curauth->hosts, ClientHost, ClientIpString)) {
+ if (!MatchHost(curauth->localaddress, ServerHost, ServerIpString)) {
+ syslog(L_TRACE, "Auth strategy '%s' does not match localhost. Removing.",
+ curauth->name == NULL ? "(NULL)" : curauth->name);
+ free_authgroup(curauth);
+ } else
+ add_authgroup(curauth);
+ } else {
+ syslog(L_TRACE, "Auth strategy '%s' does not match client. Removing.",
+ curauth->name == NULL ? "(NULL)" : curauth->name);
+ free_authgroup(curauth);
+ }
+ curauth = NULL;
+ goto again;
+ }
+
+ authdecl_parse(curauth, cf->f, tok);
+ } else if (inwhat == 2) {
+ /* accessgroup parser */
+ if (tok->type == PERMrbrace) {
+ inwhat = 0;
+
+ if (curaccess->name)
+ add_accessgroup(curaccess);
+ else
+ free_accessgroup(curaccess);
+ curaccess = NULL;
+ goto again;
+ }
+
+ accessdecl_parse(curaccess, cf->f, tok);
+ } else {
+ /* should never happen */
+ syslog(L_TRACE, "SHOULD NEVER HAPPEN!");
+ }
+again:
+ /* go back up the 'include' chain. */
+ tok = CONFgettoken(PERMtoks, cf->f);
+
+ while (tok == NULL && cf) {
+ hold = cf;
+ cf = hold->parent;
+ CONFfclose(hold->f);
+ free(hold);
+ if (cf) {
+ tok = CONFgettoken(PERMtoks, cf->f);
+ }
+ }
+ }
+
+ return;
+}
+
+void PERMgetaccess(char *nnrpaccess)
+{
+ int i;
+ char *uname;
+ int canauthenticate;
+
+ auth_realms = NULL;
+ access_realms = NULL;
+ success_auth = NULL;
+
+ PERMcanread = PERMcanpost = false;
+ PERMreadlist = PERMpostlist = false;
+ PERMaccessconf = NULL;
+
+ if (ConfigBit == NULL) {
+ if (PERMMAX % 8 == 0)
+ ConfigBitsize = PERMMAX/8;
+ else
+ ConfigBitsize = (PERMMAX - (PERMMAX % 8))/8 + 1;
+ ConfigBit = xcalloc(ConfigBitsize, 1);
+ }
+ PERMreadfile(nnrpaccess);
+
+ strip_accessgroups();
+
+ if (auth_realms == NULL) {
+ /* no one can talk, empty file */
+ syslog(L_NOTICE, "%s no_permission", ClientHost);
+ Printf("%d You have no permission to talk. Goodbye.\r\n",
+ NNTP_ACCESS_VAL);
+ ExitWithStats(1, true);
+ }
+
+ /* auth_realms are all expected to match the user. */
+ canauthenticate = 0;
+ for (i = 0; auth_realms[i]; i++)
+ if (auth_realms[i]->auth_methods)
+ canauthenticate = 1;
+ uname = 0;
+ while (!uname && i--) {
+ if ((uname = ResolveUser(auth_realms[i])) != NULL)
+ PERMauthorized = true;
+ if (!uname && auth_realms[i]->default_user)
+ uname = auth_realms[i]->default_user;
+ }
+ if (uname) {
+ strlcpy(PERMuser, uname, sizeof(PERMuser));
+ uname = strchr(PERMuser, '@');
+ if (!uname && auth_realms[i]->default_domain) {
+ /* append the default domain to the username */
+ strlcat(PERMuser, "@", sizeof(PERMuser));
+ strlcat(PERMuser, auth_realms[i]->default_domain,
+ sizeof(PERMuser));
+ }
+ PERMneedauth = false;
+ success_auth = auth_realms[i];
+ syslog(L_TRACE, "%s res %s", ClientHost, PERMuser);
+ } else if (!canauthenticate) {
+ /* couldn't resolve the user. */
+ syslog(L_NOTICE, "%s no_user", ClientHost);
+ Printf("%d Could not get your access name. Goodbye.\r\n",
+ NNTP_ACCESS_VAL);
+ ExitWithStats(1, true);
+ } else {
+ PERMneedauth = true;
+ }
+ /* check maximum allowed permissions for any host that matches (for
+ * the greeting string) */
+ for (i = 0; access_realms[i]; i++) {
+ if (!PERMcanread)
+ PERMcanread = (access_realms[i]->read != NULL);
+ if (!PERMcanpost)
+ PERMcanpost = (access_realms[i]->post != NULL);
+ }
+ if (!i) {
+ /* no applicable access groups. Zeroing all these makes INN
+ * return permission denied to client. */
+ PERMcanread = PERMcanpost = PERMneedauth = false;
+ }
+}
+
+void PERMlogin(char *uname, char *pass, char *errorstr)
+{
+ int i = 0;
+ char *runame;
+
+ if (ConfigBit == NULL) {
+ if (PERMMAX % 8 == 0)
+ ConfigBitsize = PERMMAX/8;
+ else
+ ConfigBitsize = (PERMMAX - (PERMMAX % 8))/8 + 1;
+ ConfigBit = xcalloc(ConfigBitsize, 1);
+ }
+ /* The check in CMDauthinfo uses the value of PERMneedauth to know if
+ * authentication succeeded or not. By default, authentication doesn't
+ * succeed. */
+ PERMneedauth = true;
+
+ if(auth_realms != NULL) {
+ for (i = 0; auth_realms[i]; i++) {
+ ;
+ }
+ }
+
+ runame = NULL;
+
+ while (runame == NULL && i--)
+ runame = AuthenticateUser(auth_realms[i], uname, pass, errorstr);
+ if (runame) {
+ strlcpy(PERMuser, runame, sizeof(PERMuser));
+ uname = strchr(PERMuser, '@');
+ if (!uname && auth_realms[i]->default_domain) {
+ /* append the default domain to the username */
+ strlcat(PERMuser, "@", sizeof(PERMuser));
+ strlcat(PERMuser, auth_realms[i]->default_domain,
+ sizeof(PERMuser));
+ }
+ PERMneedauth = false;
+ PERMauthorized = true;
+ success_auth = auth_realms[i];
+ }
+}
+
+static int MatchUser(char *pat, char *user)
+{
+ char *cp, **list;
+ char *userlist[2];
+ int ret;
+
+ if (!pat)
+ return(1);
+ if (!user || !*user)
+ return(0);
+ cp = xstrdup(pat);
+ list = 0;
+ NGgetlist(&list, cp);
+ userlist[0] = user;
+ userlist[1] = 0;
+ ret = PERMmatch(list, userlist);
+ free(cp);
+ free(list[0]);
+ free(list);
+ return(ret);
+}
+
+void PERMgetpermissions()
+{
+ int i;
+ char *cp, **list;
+ char *user[2];
+ static ACCESSGROUP *noaccessconf;
+ char *uname;
+ char *cpp, *script_path;
+ char **args;
+ struct vector *access_vec;
+
+ if (ConfigBit == NULL) {
+ if (PERMMAX % 8 == 0)
+ ConfigBitsize = PERMMAX/8;
+ else
+ ConfigBitsize = (PERMMAX - (PERMMAX % 8))/8 + 1;
+ ConfigBit = xcalloc(ConfigBitsize, 1);
+ }
+ if (!success_auth) {
+ /* if we haven't successfully authenticated, we can't do anything. */
+ syslog(L_TRACE, "%s no_success_auth", ClientHost);
+ if (!noaccessconf)
+ noaccessconf = xmalloc(sizeof(ACCESSGROUP));
+ PERMaccessconf = noaccessconf;
+ SetDefaultAccess(PERMaccessconf);
+ return;
+#ifdef DO_PERL
+ } else if ((success_auth->access_script != NULL) && (success_auth->access_type == PERMperl_access)) {
+ i = 0;
+ cpp = xstrdup(success_auth->access_script);
+ args = 0;
+ Argify(cpp, &args);
+ script_path = concat(args[0], (char *) 0);
+ if ((script_path != NULL) && (strlen(script_path) > 0)) {
+ if(!PerlLoaded) {
+ loadPerl();
+ }
+ PERLsetup(NULL, script_path, "access");
+ free(script_path);
+
+ uname = xstrdup(PERMuser);
+
+ access_vec = vector_new();
+
+ perlAccess(uname, access_vec);
+ free(uname);
+
+ access_realms[0] = xcalloc(1, sizeof(ACCESSGROUP));
+
+ PERMvectortoaccess(access_realms[0], "perl-dynamic", access_vec);
+
+ vector_free(access_vec);
+ } else {
+ syslog(L_ERROR, "No script specified in perl_access method.\n");
+ Reply("%d NNTP server unavailable. Try later.\r\n", NNTP_TEMPERR_VAL);
+ ExitWithStats(1, true);
+ }
+ free(cpp);
+ free(args);
+#endif /* DO_PERL */
+#ifdef DO_PYTHON
+ } else if ((success_auth->access_script != NULL) && (success_auth->access_type == PERMpython_access)) {
+ i = 0;
+ cpp = xstrdup(success_auth->access_script);
+ args = 0;
+ Argify(cpp, &args);
+ script_path = concat(args[0], (char *) 0);
+ if ((script_path != NULL) && (strlen(script_path) > 0)) {
+ uname = xstrdup(PERMuser);
+ access_vec = vector_new();
+
+ PY_access(script_path, access_vec, uname);
+ free(script_path);
+ free(uname);
+ free(args);
+
+ access_realms[0] = xcalloc(1, sizeof(ACCESSGROUP));
+ memset(access_realms[0], 0, sizeof(ACCESSGROUP));
+
+ PERMvectortoaccess(access_realms[0], "python-dynamic", access_vec);
+
+ vector_free(access_vec);
+ } else {
+ syslog(L_ERROR, "No script specified in python_access method.\n");
+ Reply("%d NNTP server unavailable. Try later.\r\n", NNTP_TEMPERR_VAL);
+ ExitWithStats(1, true);
+ }
+ free(cpp);
+#endif /* DO_PYTHON */
+ } else {
+ for (i = 0; access_realms[i]; i++)
+ ;
+ user[0] = PERMuser;
+ user[1] = 0;
+ while (i--) {
+ if ((!success_auth->key && !access_realms[i]->key) ||
+ (access_realms[i]->key && success_auth->key &&
+ strcmp(access_realms[i]->key, success_auth->key) == 0)) {
+ if (!access_realms[i]->users)
+ break;
+ else if (!*PERMuser)
+ continue;
+ cp = xstrdup(access_realms[i]->users);
+ list = 0;
+ NGgetlist(&list, cp);
+ if (PERMmatch(list, user)) {
+ syslog(L_TRACE, "%s match_user %s %s", ClientHost,
+ PERMuser, access_realms[i]->users);
+ free(cp);
+ free(list[0]);
+ free(list);
+ break;
+ } else
+ syslog(L_TRACE, "%s no_match_user %s %s", ClientHost,
+ PERMuser, access_realms[i]->users);
+ free(cp);
+ free(list[0]);
+ free(list);
+ }
+ }
+ }
+ if (i >= 0) {
+ /* found the right access group */
+ if (access_realms[i]->rejectwith) {
+ syslog(L_ERROR, "%s rejected by rule (%s)",
+ ClientHost, access_realms[i]->rejectwith);
+ Reply("%d Permission denied: %s\r\n",
+ NNTP_ACCESS_VAL, access_realms[i]->rejectwith);
+ ExitWithStats(1, true);
+ }
+ if (access_realms[i]->read) {
+ cp = xstrdup(access_realms[i]->read);
+ PERMspecified = NGgetlist(&PERMreadlist, cp);
+ free(cp);
+ PERMcanread = true;
+ } else {
+ syslog(L_TRACE, "%s no_read %s", ClientHost, access_realms[i]->name);
+ PERMcanread = false;
+ }
+ if (access_realms[i]->post) {
+ cp = xstrdup(access_realms[i]->post);
+ NGgetlist(&PERMpostlist, cp);
+ free(cp);
+ PERMcanpost = true;
+ } else {
+ syslog(L_TRACE, "%s no_post %s", ClientHost, access_realms[i]->name);
+ PERMcanpost = false;
+ }
+ PERMaccessconf = access_realms[i];
+ MaxBytesPerSecond = PERMaccessconf->maxbytespersecond;
+ if (PERMaccessconf->virtualhost) {
+ if (PERMaccessconf->domain == NULL) {
+ syslog(L_ERROR, "%s virtualhost needs domain parameter(%s)",
+ ClientHost, PERMaccessconf->name);
+ Reply("%d NNTP server unavailable. Try later.\r\n", NNTP_TEMPERR_VAL);
+ ExitWithStats(1, true);
+ }
+ if (VirtualPath)
+ free(VirtualPath);
+ if (strcmp(innconf->pathhost, PERMaccessconf->pathhost) == 0) {
+ /* use domain, if pathhost in access relm matches one in
+ inn.conf to differentiate virtual host */
+ if (innconf->domain != NULL && strcmp(innconf->domain, PERMaccessconf->domain) == 0) {
+ syslog(L_ERROR, "%s domain parameter(%s) in readers.conf must be different from the one in inn.conf",
+ ClientHost, PERMaccessconf->name);
+ Reply("%d NNTP server unavailable. Try later.\r\n", NNTP_TEMPERR_VAL);
+ ExitWithStats(1, true);
+ }
+ VirtualPath = concat(PERMaccessconf->domain, "!", (char *) 0);
+ } else {
+ VirtualPath = concat(PERMaccessconf->pathhost, "!",
+ (char *) 0);
+ }
+ VirtualPathlen = strlen(VirtualPath);
+ } else
+ VirtualPathlen = 0;
+ } else {
+ if (!noaccessconf)
+ noaccessconf = xmalloc(sizeof(ACCESSGROUP));
+ PERMaccessconf = noaccessconf;
+ SetDefaultAccess(PERMaccessconf);
+ syslog(L_TRACE, "%s no_access_realm", ClientHost);
+ }
+ /* check if dynamic access control is enabled, if so init it */
+#ifdef DO_PYTHON
+ if ((success_auth->dynamic_type == PERMpython_dynamic) && success_auth->dynamic_script) {
+ PY_dynamic_init(success_auth->dynamic_script);
+ }
+#endif /* DO_PYTHON */
+}
+
+/* strip blanks out of a string */
+static void CompressList(char *list)
+{
+ char *cpto;
+ bool inword = false;
+
+ for (cpto = list; *list; ) {
+ if (strchr("\n \t,", *list) != NULL) {
+ list++;
+ if(inword) {
+ *cpto++ = ',';
+ inword = false;
+ }
+ } else {
+ *cpto++ = *list++;
+ inword = true;
+ }
+ }
+ *cpto = '\0';
+}
+
+static bool MatchHost(char *hostlist, char *host, char *ip)
+{
+ char **list;
+ bool ret = false;
+ char *cp;
+ int iter;
+ char *pat,
+ *p;
+
+ /* If no hostlist are specified, by default they match. */
+
+ if (hostlist == NULL) {
+ return(true);
+ }
+
+ list = 0;
+ cp = xstrdup(hostlist);
+
+ NGgetlist(&list, cp);
+
+ /* default is no access */
+ for (iter = 0; list[iter]; iter++) {
+ ;
+ }
+
+ while (iter-- > 0) {
+ pat = list[iter];
+ if (*pat == '!')
+ pat++;
+ ret = uwildmat(host, pat);
+ if (!ret && *ip) {
+ ret = uwildmat(ip, pat);
+ if (!ret && (p = strchr(pat, '/')) != (char *)NULL) {
+ unsigned int bits, c;
+ struct in_addr ia, net, tmp;
+#ifdef HAVE_INET6
+ struct in6_addr ia6, net6;
+ unsigned char bits8;
+#endif
+ unsigned int mask;
+
+ *p = '\0';
+ if (inet_aton(ip, &ia) && inet_aton(pat, &net)) {
+ if (strchr(p+1, '.') == (char *)NULL) {
+ /* string following / is a masklength */
+ mask = atoi(p+1);
+ for (bits = c = 0; c < mask && c < 32; c++)
+ bits |= (1 << (31 - c));
+ mask = htonl(bits);
+ } else { /* or it may be a dotted quad bitmask */
+ if (inet_aton(p+1, &tmp))
+ mask = tmp.s_addr;
+ else /* otherwise skip it */
+ continue;
+ }
+ if ((ia.s_addr & mask) == (net.s_addr & mask))
+ ret = true;
+ }
+#ifdef HAVE_INET6
+ else if (inet_pton(AF_INET6, ip, &ia6) &&
+ inet_pton(AF_INET6, pat, &net6)) {
+ mask = atoi(p+1);
+ ret = true;
+ /* do a prefix match byte by byte */
+ for (c = 0; c*8 < mask && c < sizeof(ia6); c++) {
+ if ( (c+1)*8 <= mask &&
+ ia6.s6_addr[c] != net6.s6_addr[c] ) {
+ ret = false;
+ break;
+ } else if ( (c+1)*8 > mask ) {
+ unsigned int b;
+
+ for (bits8 = b = 0; b < (mask % 8); b++)
+ bits8 |= (1 << (7 - b));
+ if ((ia6.s6_addr[c] & bits8) !=
+ (net6.s6_addr[c] & bits8) ) {
+ ret = false;
+ break;
+ }
+ }
+ }
+ }
+#endif
+ }
+ }
+ if (ret)
+ break;
+ }
+ if (ret && list[iter][0] == '!')
+ ret = false;
+ free(list[0]);
+ free(list);
+ free(cp);
+ return(ret);
+}
+
+static void add_authgroup(AUTHGROUP *group)
+{
+ int i;
+
+ if (auth_realms == NULL) {
+ i = 0;
+ auth_realms = xmalloc(2 * sizeof(AUTHGROUP *));
+ } else {
+ for (i = 0; auth_realms[i]; i++)
+ ;
+ auth_realms = xrealloc(auth_realms, (i + 2) * sizeof(AUTHGROUP *));
+ }
+ auth_realms[i] = group;
+ auth_realms[i+1] = 0;
+}
+
+static void add_accessgroup(ACCESSGROUP *group)
+{
+ int i;
+
+ if (access_realms == NULL) {
+ i = 0;
+ access_realms = xmalloc(2 * sizeof(ACCESSGROUP *));
+ } else {
+ for (i = 0; access_realms[i]; i++)
+ ;
+ access_realms = xrealloc(access_realms, (i + 2) * sizeof(ACCESSGROUP *));
+ }
+ access_realms[i] = group;
+ access_realms[i+1] = 0;
+}
+
+/* clean out access groups that don't apply to any of our auth groups. */
+
+static void strip_accessgroups(void)
+{
+ int i, j;
+
+ /* flag the access group as used or not */
+
+ if(access_realms != NULL) {
+ for (j = 0; access_realms[j] != NULL; j++) {
+ access_realms[j]->used = 0;
+ }
+ } else {
+ syslog(L_TRACE, "No access realms to check!");
+ }
+
+ /* If there are auth realms to check... */
+
+ if(auth_realms != NULL) {
+ /* ... Then for each auth realm... */
+
+ for (i = 0; auth_realms[i] != NULL; i++) {
+
+ /* ... for each access realm... */
+
+ for (j = 0; access_realms[j] != NULL; j++) {
+
+ /* If the access realm isn't already in use... */
+
+ if (! access_realms[j]->used) {
+ /* Check to see if both the access_realm key and
+ auth_realm key are NULL... */
+
+ if (!access_realms[j]->key && !auth_realms[i]->key) {
+ /* If so, mark the realm in use and continue on... */
+
+ access_realms[j]->used = 1;
+ } else {
+ /* If not, check to see if both the access_realm and
+ auth_realm are NOT _both_ NULL, and see if they are
+ equal... */
+
+ if (access_realms[j]->key && auth_realms[i]->key &&
+ strcmp(access_realms[j]->key, auth_realms[i]->key) == 0) {
+
+ /* And if so, mark the realm in use. */
+
+ access_realms[j]->used = 1;
+ }
+ }
+ }
+ }
+ }
+ } else {
+ syslog(L_TRACE, "No auth realms to check!");
+ }
+
+ /* strip out unused access groups */
+ i = j = 0;
+
+ while (access_realms[i] != NULL) {
+ if (access_realms[i]->used)
+ access_realms[j++] = access_realms[i];
+ else
+ syslog(L_TRACE, "%s removing irrelevant access group %s",
+ ClientHost, access_realms[i]->name);
+ i++;
+ }
+ access_realms[j] = 0;
+}
+
+typedef struct _EXECSTUFF {
+ pid_t pid;
+ int rdfd, errfd, wrfd;
+} EXECSTUFF;
+
+static EXECSTUFF *ExecProg(char *arg0, char **args)
+{
+ EXECSTUFF *ret;
+ int rdfd[2], errfd[2], wrfd[2];
+ pid_t pid;
+ struct stat stb;
+
+#if !defined(S_IXUSR) && defined(_S_IXUSR)
+#define S_IXUSR _S_IXUSR
+#endif /* !defined(S_IXUSR) && defined(_S_IXUSR) */
+
+#if !defined(S_IXUSR) && defined(S_IEXEC)
+#define S_IXUSR S_IEXEC
+#endif /* !defined(S_IXUSR) && defined(S_IEXEC) */
+
+ if (stat(arg0, &stb) || !(stb.st_mode&S_IXUSR))
+ return(0);
+
+ pipe(rdfd);
+ pipe(errfd);
+ pipe(wrfd);
+ switch (pid = fork()) {
+ case -1:
+ close(rdfd[0]);
+ close(rdfd[1]);
+ close(errfd[0]);
+ close(errfd[1]);
+ close(wrfd[0]);
+ close(wrfd[1]);
+ return(0);
+ case 0:
+ close(rdfd[0]);
+ dup2(rdfd[1], 1);
+ close(errfd[0]);
+ dup2(errfd[1], 2);
+ close(wrfd[1]);
+ dup2(wrfd[0], 0);
+ execv(arg0, args);
+ /* if we got here, there was an error */
+ syslog(L_ERROR, "%s perm could not exec %s: %m", ClientHost, arg0);
+ exit(1);
+ }
+ close(rdfd[1]);
+ close(errfd[1]);
+ close(wrfd[0]);
+ ret = xmalloc(sizeof(EXECSTUFF));
+ ret->pid = pid;
+ ret->rdfd = rdfd[0];
+ ret->errfd = errfd[0];
+ ret->wrfd = wrfd[1];
+ return(ret);
+}
+
+static void GetConnInfo(METHOD *method, char *buf)
+{
+ int i;
+
+ buf[0] = '\0';
+ if (*ClientHost)
+ sprintf(buf, "ClientHost: %s\r\n", ClientHost);
+ if (*ClientIpString)
+ sprintf(buf+strlen(buf), "ClientIP: %s\r\n", ClientIpString);
+ if (ClientPort)
+ sprintf(buf+strlen(buf), "ClientPort: %d\r\n", ClientPort);
+ if (*ServerIpString)
+ sprintf(buf+strlen(buf), "LocalIP: %s\r\n", ServerIpString);
+ if (ServerPort)
+ sprintf(buf+strlen(buf), "LocalPort: %d\r\n", ServerPort);
+ /* handle this here, since we only get here when we're about to exec
+ * something. */
+ if (method->extra_headers) {
+ for (i = 0; method->extra_headers[i]; i++)
+ sprintf(buf+strlen(buf), "%s\r\n", method->extra_headers[i]);
+ }
+}
+
+static char ubuf[SMBUF];
+
+typedef void (*LineFunc)(char*);
+
+/* messages from a program's stdout */
+static void HandleProgLine(char *ln)
+{
+ if (strncasecmp(ln, "User:", strlen("User:")) == 0)
+ strlcpy(ubuf, ln + strlen("User:"), sizeof(ubuf));
+}
+
+/* messages from a programs stderr */
+static void HandleErrorLine(char *ln)
+{
+ syslog(L_NOTICE, "%s auth_err %s", ClientHost, ln);
+}
+
+static bool
+HandleProgInput(int fd, char *buf, int buflen, LineFunc f)
+{
+ char *nl;
+ char *start;
+ int curpos, got;
+
+ /* read the data */
+ curpos = strlen(buf);
+ if (curpos >= buflen-1) {
+ /* data overflow (on one line!) */
+ return false;
+ }
+ got = read(fd, buf+curpos, buflen-curpos-1);
+ if (got <= 0)
+ return false;
+ buf[curpos+got] = '\0';
+
+ /* break what we got up into lines */
+ start = nl = buf;
+ while ((nl = strchr(nl, '\n')) != NULL) {
+ if (nl != buf && *(nl-1) == '\r')
+ *(nl-1) = '\0';
+ *nl++ = '\0';
+ f(start);
+ start = nl;
+ }
+
+ /* delete all the lines we've read from the buffer. */
+ /* 'start' points to the end of the last unterminated string */
+ nl = start;
+ start = buf;
+ if (nl == start) {
+ return true;
+ }
+
+ while (*nl) {
+ *start++ = *nl++;
+ }
+
+ *start = '\0';
+
+ return true;
+}
+
+static void GetProgInput(EXECSTUFF *prog)
+{
+ fd_set rfds, tfds;
+ int maxfd;
+ int got;
+ bool okay;
+ struct timeval tmout;
+ pid_t tmp;
+ int status;
+ char rdbuf[BIG_BUFFER], errbuf[BIG_BUFFER];
+ double start, end;
+
+ FD_ZERO(&rfds);
+ FD_SET(prog->rdfd, &rfds);
+ FD_SET(prog->errfd, &rfds);
+ tfds = rfds;
+ maxfd = prog->rdfd > prog->errfd ? prog->rdfd : prog->errfd;
+ tmout.tv_sec = 5;
+ tmout.tv_usec = 0;
+ rdbuf[0] = errbuf[0] = '\0';
+ start = TMRnow_double();
+ while ((got = select(maxfd+1, &tfds, 0, 0, &tmout)) >= 0) {
+ end = TMRnow_double();
+ IDLEtime += end - start;
+ start = end;
+ tmout.tv_sec = 5;
+ tmout.tv_usec = 0;
+ if (got > 0) {
+ if (FD_ISSET(prog->rdfd, &tfds)) {
+ okay = HandleProgInput(prog->rdfd, rdbuf, sizeof(rdbuf), HandleProgLine);
+ if (!okay) {
+ close(prog->rdfd);
+ FD_CLR(prog->rdfd, &tfds);
+ kill(prog->pid, SIGTERM);
+ }
+ }
+ if (FD_ISSET(prog->errfd, &tfds)) {
+ okay = HandleProgInput(prog->errfd, errbuf, sizeof(errbuf), HandleErrorLine);
+ if (!okay) {
+ close(prog->errfd);
+ FD_CLR(prog->errfd, &tfds);
+ kill(prog->pid, SIGTERM);
+ }
+ }
+ }
+ tfds = rfds;
+ }
+ end = TMRnow_double();
+ IDLEtime += end - start;
+ /* wait for it if he's toast. */
+ do {
+ tmp = waitpid(prog->pid, &status, 0);
+ } while ((tmp >= 0 || (tmp < 0 && errno == EINTR)) &&
+ !WIFEXITED(status) && !WIFSIGNALED(status));
+ if (WIFSIGNALED(status)) {
+ ubuf[0] = '\0';
+ syslog(L_NOTICE, "%s bad_hook program caught signal %d", ClientHost,
+ WTERMSIG(status));
+ } else if (WIFEXITED(status)) {
+ if (WEXITSTATUS(status) != 0) {
+ ubuf[0] = '\0';
+ syslog(L_TRACE, "%s bad_hook program exited with status %d",
+ ClientHost, WEXITSTATUS(status));
+ }
+ } else {
+ syslog(L_ERROR, "%s bad_hook waitpid failed: %m", ClientHost);
+ ubuf[0] = '\0';
+ }
+}
+
+/* execute a series of resolvers to get the remote username */
+static char *ResolveUser(AUTHGROUP *auth)
+{
+ int i, j;
+ char *cp;
+ char **args;
+ char *arg0;
+ char *resdir;
+ char *tmp;
+ EXECSTUFF *foo;
+ int done = 0;
+ char buf[BIG_BUFFER];
+
+ if (!auth->res_methods)
+ return(0);
+
+ tmp = concatpath(innconf->pathbin, _PATH_AUTHDIR);
+ resdir = concatpath(tmp, _PATH_AUTHDIR_NOPASS);
+ free(tmp);
+
+ ubuf[0] = '\0';
+ for (i = 0; auth->res_methods[i]; i++) {
+ /* build the command line */
+ syslog(L_TRACE, "%s res starting resolver %s", ClientHost, auth->res_methods[i]->program);
+ if (auth->res_methods[i]->extra_logs) {
+ for (j = 0; auth->res_methods[i]->extra_logs[j]; j++)
+ syslog(L_NOTICE, "%s res also-log: %s", ClientHost,
+ auth->res_methods[i]->extra_logs[j]);
+ }
+ cp = xstrdup(auth->res_methods[i]->program);
+ args = 0;
+ Argify(cp, &args);
+ arg0 = args[0];
+ if (args[0][0] != '/')
+ arg0 = concat(resdir, "/", arg0, (char *) 0);
+ /* exec the resolver */
+ foo = ExecProg(arg0, args);
+ if (foo) {
+ GetConnInfo(auth->res_methods[i], buf);
+ strlcat(buf, ".\r\n", sizeof(buf));
+ xwrite(foo->wrfd, buf, strlen(buf));
+ close(foo->wrfd);
+
+ GetProgInput(foo);
+ done = (ubuf[0] != '\0');
+ if (done)
+ syslog(L_TRACE, "%s res resolver successful, user %s", ClientHost, ubuf);
+ else
+ syslog(L_TRACE, "%s res resolver failed", ClientHost);
+ free(foo);
+ } else
+ syslog(L_ERROR, "%s res couldnt start resolver: %m", ClientHost);
+ /* clean up */
+ if (args[0][0] != '/') {
+ free(arg0);
+ }
+ free(args);
+ free(cp);
+ if (done)
+ /* this resolver succeeded */
+ break;
+ }
+ free(resdir);
+ if (ubuf[0])
+ return(ubuf);
+ return(0);
+}
+
+/* execute a series of authenticators to get the remote username */
+static char *AuthenticateUser(AUTHGROUP *auth, char *username, char *password, char *errorstr)
+{
+ int i, j;
+ char *cp;
+ char **args;
+ char *arg0;
+ char *resdir;
+ char *tmp;
+ char *script_path;
+ char newUser[BIG_BUFFER];
+ EXECSTUFF *foo;
+ int done = 0;
+ int code;
+ char buf[BIG_BUFFER];
+
+ if (!auth->auth_methods)
+ return(0);
+
+ tmp = concatpath(innconf->pathbin, _PATH_AUTHDIR);
+ resdir = concatpath(tmp, _PATH_AUTHDIR_PASSWD);
+ free(tmp);
+
+ ubuf[0] = '\0';
+ newUser[0] = '\0';
+ for (i = 0; auth->auth_methods[i]; i++) {
+ if (auth->auth_methods[i]->type == PERMperl_auth) {
+#ifdef DO_PERL
+ cp = xstrdup(auth->auth_methods[i]->program);
+ args = 0;
+ Argify(cp, &args);
+ script_path = concat(args[0], (char *) 0);
+ if ((script_path != NULL) && (strlen(script_path) > 0)) {
+ if(!PerlLoaded) {
+ loadPerl();
+ }
+ PERLsetup(NULL, script_path, "authenticate");
+ free(script_path);
+ perlAuthInit();
+
+ code = perlAuthenticate(username, password, errorstr, newUser);
+ if (code == NNTP_AUTH_OK_VAL) {
+ /* Set the value of ubuf to the right username */
+ if (newUser[0] != '\0') {
+ strlcpy(ubuf, newUser, sizeof(ubuf));
+ } else {
+ strlcpy(ubuf, username, sizeof(ubuf));
+ }
+
+ syslog(L_NOTICE, "%s user %s", ClientHost, ubuf);
+ if (LLOGenable) {
+ fprintf(locallog, "%s user %s\n", ClientHost, ubuf);
+ fflush(locallog);
+ }
+ break;
+ } else {
+ syslog(L_NOTICE, "%s bad_auth", ClientHost);
+ }
+ } else {
+ syslog(L_ERROR, "No script specified in auth method.\n");
+ }
+#endif /* DO_PERL */
+ } else if (auth->auth_methods[i]->type == PERMpython_auth) {
+#ifdef DO_PYTHON
+ cp = xstrdup(auth->auth_methods[i]->program);
+ args = 0;
+ Argify(cp, &args);
+ script_path = concat(args[0], (char *) 0);
+ if ((script_path != NULL) && (strlen(script_path) > 0)) {
+ code = PY_authenticate(script_path, username, password, errorstr, newUser);
+ free(script_path);
+ if (code < 0) {
+ syslog(L_NOTICE, "PY_authenticate(): authentication skipped due to no Python authentication method defined.");
+ } else {
+ if (code == NNTP_AUTH_OK_VAL) {
+ /* Set the value of ubuf to the right username */
+ if (newUser[0] != '\0') {
+ strlcpy(ubuf, newUser, sizeof(ubuf));
+ } else {
+ strlcpy(ubuf, username, sizeof(ubuf));
+ }
+
+ syslog(L_NOTICE, "%s user %s", ClientHost, ubuf);
+ if (LLOGenable) {
+ fprintf(locallog, "%s user %s\n", ClientHost, ubuf);
+ fflush(locallog);
+ }
+ break;
+ } else {
+ syslog(L_NOTICE, "%s bad_auth", ClientHost);
+ }
+ }
+ } else {
+ syslog(L_ERROR, "No script specified in auth method.\n");
+ }
+#endif /* DO_PYTHON */
+ } else {
+ if (auth->auth_methods[i]->users &&
+ !MatchUser(auth->auth_methods[i]->users, username))
+ continue;
+
+ /* build the command line */
+ syslog(L_TRACE, "%s auth starting authenticator %s", ClientHost, auth->auth_methods[i]->program);
+ if (auth->auth_methods[i]->extra_logs) {
+ for (j = 0; auth->auth_methods[i]->extra_logs[j]; j++)
+ syslog(L_NOTICE, "%s auth also-log: %s", ClientHost,
+ auth->auth_methods[i]->extra_logs[j]);
+ }
+ cp = xstrdup(auth->auth_methods[i]->program);
+ args = 0;
+ Argify(cp, &args);
+ arg0 = args[0];
+ if (args[0][0] != '/')
+ arg0 = concat(resdir, "/", arg0, (char *) 0);
+ /* exec the authenticator */
+ foo = ExecProg(arg0, args);
+ if (foo) {
+ GetConnInfo(auth->auth_methods[i], buf);
+ snprintf(buf+strlen(buf), sizeof(buf) - strlen(buf) - 3,
+ "ClientAuthname: %s\r\n", username);
+ snprintf(buf+strlen(buf), sizeof(buf) - strlen(buf) - 3,
+ "ClientPassword: %s\r\n", password);
+ strlcat(buf, ".\r\n", sizeof(buf));
+ xwrite(foo->wrfd, buf, strlen(buf));
+ close(foo->wrfd);
+
+ GetProgInput(foo);
+ done = (ubuf[0] != '\0');
+ if (done)
+ syslog(L_TRACE, "%s auth authenticator successful, user %s", ClientHost, ubuf);
+ else
+ syslog(L_TRACE, "%s auth authenticator failed", ClientHost);
+ free(foo);
+ } else
+ syslog(L_ERROR, "%s auth couldnt start authenticator: %m", ClientHost);
+ /* clean up */
+ if (args[0][0] != '/') {
+ free(arg0);
+ }
+ free(args);
+ free(cp);
+ if (done)
+ /* this authenticator succeeded */
+ break;
+ }
+ }
+ free(resdir);
+ if (ubuf[0])
+ return(ubuf);
+ return(0);
+}
--- /dev/null
+/* $Revision: 7450 $
+**
+** Check article, send it to the local server.
+*/
+#include "config.h"
+#include "clibrary.h"
+
+#include "inn/innconf.h"
+#include "nnrpd.h"
+#include "ov.h"
+#include "post.h"
+
+#define FLUSH_ERROR(F) (fflush((F)) == EOF || ferror((F)))
+#define HEADER_DELTA 20
+
+static char *tmpPtr ;
+static char Error[SMBUF];
+static char NGSEPS[] = NG_SEPARATOR;
+char **OtherHeaders;
+int OtherCount;
+bool HeadersModified;
+static int OtherSize;
+static const char * const BadDistribs[] = {
+ BAD_DISTRIBS
+};
+
+HEADER Table[] = {
+ /* Name Canset Type Size Value */
+ { "Path", true, HTstd, 0, NULL, NULL, 0 },
+ { "From", true, HTreq, 0, NULL, NULL, 0 },
+ { "Newsgroups", true, HTreq, 0, NULL, NULL, 0 },
+ { "Subject", true, HTreq, 0, NULL, NULL, 0 },
+ { "Control", true, HTstd, 0, NULL, NULL, 0 },
+ { "Supersedes", true, HTstd, 0, NULL, NULL, 0 },
+ { "Followup-To", true, HTstd, 0, NULL, NULL, 0 },
+ { "Date", true, HTstd, 0, NULL, NULL, 0 },
+ { "Organization", true, HTstd, 0, NULL, NULL, 0 },
+ { "Lines", true, HTstd, 0, NULL, NULL, 0 },
+ { "Sender", true, HTstd, 0, NULL, NULL, 0 },
+ { "Approved", true, HTstd, 0, NULL, NULL, 0 },
+ { "Distribution", true, HTstd, 0, NULL, NULL, 0 },
+ { "Expires", true, HTstd, 0, NULL, NULL, 0 },
+ { "Message-ID", true, HTstd, 0, NULL, NULL, 0 },
+ { "References", true, HTstd, 0, NULL, NULL, 0 },
+ { "Reply-To", true, HTstd, 0, NULL, NULL, 0 },
+ { "NNTP-Posting-Host", false, HTstd, 0, NULL, NULL, 0 },
+ { "Mime-Version", true, HTstd, 0, NULL, NULL, 0 },
+ { "Content-Type", true, HTstd, 0, NULL, NULL, 0 },
+ { "Content-Transfer-Encoding", true, HTstd, 0, NULL, NULL, 0 },
+ { "X-Trace", false, HTstd, 0, NULL, NULL, 0 },
+ { "X-Complaints-To", false, HTstd, 0, NULL, NULL, 0 },
+ { "NNTP-Posting-Date", false, HTstd, 0, NULL, NULL, 0 },
+ { "Xref", false, HTstd, 0, NULL, NULL, 0 },
+ { "Injector-Info", false, HTstd, 0, NULL, NULL, 0 },
+ { "Summary", true, HTstd, 0, NULL, NULL, 0 },
+ { "Keywords", true, HTstd, 0, NULL, NULL, 0 },
+ { "Date-Received", false, HTobs, 0, NULL, NULL, 0 },
+ { "Received", false, HTobs, 0, NULL, NULL, 0 },
+ { "Posted", false, HTobs, 0, NULL, NULL, 0 },
+ { "Posting-Version", false, HTobs, 0, NULL, NULL, 0 },
+ { "Relay-Version", false, HTobs, 0, NULL, NULL, 0 },
+ { "Cc", true, HTstd, 0, NULL, NULL, 0 },
+ { "Bcc", true, HTstd, 0, NULL, NULL, 0 },
+ { "To", true, HTstd, 0, NULL, NULL, 0 },
+};
+
+HEADER *EndOfTable = ARRAY_END(Table);
+
+\f
+
+/* Join() and MaxLength() are taken from innd.c */
+/*
+** Turn any \r or \n in text into spaces. Used to splice back multi-line
+** headers into a single line.
+*/
+static char *
+Join(char *text)
+{
+ char *p;
+
+ for (p = text; *p; p++)
+ if (*p == '\n' || *p == '\r')
+ *p = ' ';
+ return text;
+}
+
+/*
+** Return a short name that won't overrun our bufer or syslog's buffer.
+** q should either be p, or point into p where the "interesting" part is.
+*/
+static char *
+MaxLength(char *p, char *q)
+{
+ static char buff[80];
+ unsigned int i;
+
+ /* Already short enough? */
+ i = strlen(p);
+ if (i < sizeof buff - 1)
+ return Join(p);
+
+ /* Don't want casts to unsigned to go horribly wrong. */
+ if (q < p || q > p + i)
+ q = p;
+
+ /* Simple case of just want the begining? */
+ if ((size_t)(q - p) < sizeof(buff) - 4) {
+ strlcpy(buff, p, sizeof(buff) - 3);
+ strlcat(buff, "...", sizeof(buff));
+ } else if ((p + i) - q < 10) {
+ /* Is getting last 10 characters good enough? */
+ strlcpy(buff, p, sizeof(buff) - 13);
+ strlcat(buff, "...", sizeof(buff) - 10);
+ strlcat(buff, &p[i - 10], sizeof(buff));
+ } else {
+ /* Not in last 10 bytes, so use double elipses. */
+ strlcpy(buff, p, sizeof(buff) - 16);
+ strlcat(buff, "...", sizeof(buff) - 13);
+ strlcat(buff, &q[-5], sizeof(buff) - 3);
+ strlcat(buff, "...", sizeof(buff));
+ }
+ return Join(buff);
+}
+/*
+** Trim trailing spaces, return pointer to first non-space char.
+*/
+int
+TrimSpaces(char *p)
+{
+ char *start;
+
+ for (start = p; ISWHITE(*start) || *start == '\n'; start++)
+ continue;
+ for (p = start + strlen(start); p > start && CTYPE(isspace, (int)p[-1]); p--)
+ continue;
+ return (int)(p - start);
+}
+
+
+/*
+** Mark the end of the header starting at p, and return a pointer
+** to the start of the next one or NULL. Handles continuations.
+*/
+static char *
+NextHeader(char *p)
+{
+ for ( ; (p = strchr(p, '\n')) != NULL; p++) {
+ if (ISWHITE(p[1]))
+ continue;
+ *p = '\0';
+ return p + 1;
+ }
+ return NULL;
+}
+
+
+/*
+** Strip any headers off the article and dump them into the table.
+** On error, return NULL and fill in Error.
+*/
+static char *
+StripOffHeaders(char *article)
+{
+ char *p;
+ char *q;
+ HEADER *hp;
+ char c;
+
+ /* Scan through buffer, a header at a time. */
+ for (p = article; ; ) {
+
+ /* See if it's a known header. */
+ c = CTYPE(islower, (int)*p) ? toupper(*p) : *p;
+ for (hp = Table; hp < ARRAY_END(Table); hp++) {
+ if (c == hp->Name[0]
+ && p[hp->Size] == ':'
+ && strncasecmp(p, hp->Name, hp->Size) == 0) {
+ if (hp->Type == HTobs) {
+ snprintf(Error, sizeof(Error), "Obsolete \"%s\" header",
+ hp->Name);
+ return NULL;
+ }
+ if (hp->Value) {
+ snprintf(Error, sizeof(Error), "Duplicate \"%s\" header",
+ hp->Name);
+ return NULL;
+ }
+ hp->Value = &p[hp->Size + 1];
+ /* '\r\n' is replaced with '\n', and unnecessary to consider
+ '\r' */
+ for (q = &p[hp->Size + 1]; ISWHITE(*q) || *q == '\n'; q++)
+ continue;
+ hp->Body = q;
+ break;
+ }
+ }
+
+ /* No; add it to the set of other headers. */
+ if (hp == ARRAY_END(Table)) {
+ if (OtherCount >= OtherSize - 1) {
+ OtherSize += HEADER_DELTA;
+ OtherHeaders = xrealloc(OtherHeaders, OtherSize * sizeof(char *));
+ }
+ OtherHeaders[OtherCount++] = p;
+ }
+
+ /* Get start of next header; if it's a blank line, we hit the end. */
+ if ((p = NextHeader(p)) == NULL) {
+ strlcpy(Error, "Article has no body -- just headers",
+ sizeof(Error));
+ return NULL;
+ }
+ if (*p == '\n')
+ break;
+ }
+
+ return p + 1;
+}
+
+\f
+
+/*
+** Check the control message, and see if it's legit. Return pointer to
+** error message if not.
+*/
+static const char *
+CheckControl(char *ctrl)
+{
+ char *p;
+ char *q;
+ char save;
+
+ /* Snip off the first word. */
+ for (p = ctrl; ISWHITE(*p); p++)
+ continue;
+ for (ctrl = p; *p && !ISWHITE(*p); p++)
+ continue;
+ if (p == ctrl)
+ return "Empty control message";
+ save = *p;
+ *p = '\0';
+
+ if (strcmp(ctrl, "cancel") == 0) {
+ for (q = p + 1; ISWHITE(*q); q++)
+ continue;
+ if (*q == '\0')
+ return "Message-ID missing in cancel";
+ }
+ else if (strcmp(ctrl, "sendsys") == 0
+ || strcmp(ctrl, "senduuname") == 0
+ || strcmp(ctrl, "version") == 0
+ || strcmp(ctrl, "checkgroups") == 0
+ || strcmp(ctrl, "ihave") == 0
+ || strcmp(ctrl, "sendme") == 0
+ || strcmp(ctrl, "newgroup") == 0
+ || strcmp(ctrl, "rmgroup") == 0)
+ ;
+ else {
+ snprintf(Error, sizeof(Error),
+ "\"%s\" is not a valid control message",
+ MaxLength(ctrl,ctrl));
+ return Error;
+ }
+ *p = save;
+ return NULL;
+}
+
+
+/*
+** Check the Distribution header, and exit on error.
+*/
+static const char *
+CheckDistribution(char *p)
+{
+ static char SEPS[] = " \t,";
+ const char * const *dp;
+
+ if ((p = strtok(p, SEPS)) == NULL)
+ return "Can't parse Distribution line.";
+ do {
+ for (dp = BadDistribs; *dp; dp++)
+ if (uwildmat(p, *dp)) {
+ snprintf(Error, sizeof(Error), "Illegal distribution \"%s\"",
+ MaxLength(p,p));
+ return Error;
+ }
+ } while ((p = strtok((char *)NULL, SEPS)) != NULL);
+ return NULL;
+}
+
+
+/*
+** Process all the headers. FYI, they're done in RFC-order.
+** Return NULL if okay, or an error message.
+*/
+static const char *
+ProcessHeaders(int linecount, char *idbuff, bool ihave)
+{
+ static char MONTHS[] = "JanFebMarAprMayJunJulAugSepOctNovDec";
+ static char datebuff[40];
+ static char localdatebuff[40];
+ static char orgbuff[SMBUF];
+ static char linebuff[40];
+ static char tracebuff[SMBUF];
+ static char complaintsbuff[SMBUF];
+ static char sendbuff[SMBUF];
+ static char *newpath = NULL;
+ HEADER *hp;
+ char *p;
+ time_t t;
+ struct tm *gmt;
+ TIMEINFO Now;
+ const char *error;
+ pid_t pid;
+ bool addvirtual = false;
+
+ /* Various things need Now to be set. */
+ if (GetTimeInfo(&Now) < 0) {
+ snprintf(Error, sizeof(Error), "Can't get the time, %s",
+ strerror(errno));
+ return Error;
+ }
+
+ /* Do some preliminary fix-ups. */
+ for (hp = Table; hp < ARRAY_END(Table); hp++) {
+ if (!ihave && !hp->CanSet && hp->Value) {
+ snprintf(Error, sizeof(Error),
+ "Can't set system \"%s\" header", hp->Name);
+ return Error;
+ }
+ if (hp->Value) {
+ hp->Len = TrimSpaces(hp->Value);
+ if (hp->Len == 0)
+ hp->Value = hp->Body = NULL;
+ }
+ }
+
+ /* If authorized, add the header based on our info. If not authorized,
+ zap the Sender so we don't put out unauthenticated data. */
+ if (PERMaccessconf->nnrpdauthsender) {
+ if (PERMauthorized && PERMuser[0] != '\0') {
+ p = strchr(PERMuser, '@');
+ if (p == NULL) {
+ snprintf(sendbuff, sizeof(sendbuff), "%s@%s", PERMuser,
+ ClientHost);
+ } else {
+ snprintf(sendbuff, sizeof(sendbuff), "%s", PERMuser);
+ }
+ HDR_SET(HDR__SENDER, sendbuff);
+ } else {
+ HDR_SET(HDR__SENDER, NULL);
+ }
+ }
+
+ /* Set Date. datebuff is used later for NNTP-Posting-Date, so we have
+ to set it and it has to be the UTC date. */
+ if (!makedate(-1, false, datebuff, sizeof(datebuff)))
+ return "Can't generate date header";
+ if (HDR(HDR__DATE) == NULL) {
+ if (ihave)
+ return "Missing \"Date\" header";
+ if (PERMaccessconf->localtime) {
+ if (!makedate(-1, true, localdatebuff, sizeof(localdatebuff)))
+ return "Can't generate local date header";
+ HDR_SET(HDR__DATE, localdatebuff);
+ } else {
+ HDR_SET(HDR__DATE, datebuff);
+ }
+ } else {
+ if ((t = parsedate(HDR(HDR__DATE), &Now)) == -1)
+ return "Can't parse \"Date\" header";
+ if (t > Now.time + DATE_FUZZ)
+ return "Article posted in the future";
+ }
+
+ /* Newsgroups are checked later. */
+
+ if (HDR(HDR__CONTROL)) {
+ if ((error = CheckControl(HDR(HDR__CONTROL))) != NULL)
+ return error;
+ } else {
+ p = HDR(HDR__SUBJECT);
+ if (p == NULL)
+ return "Required \"Subject\" header is missing";
+ if (strncmp(p, "cmsg ", 5) == 0) {
+ HDR_SET(HDR__CONTROL, p + 5);
+ if ((error = CheckControl(HDR(HDR__CONTROL))) != NULL)
+ return error;
+ }
+ }
+
+ /* Set Message-ID */
+ if (HDR(HDR__MESSAGEID) == NULL) {
+ if (ihave)
+ return "Missing \"Message-ID\" header";
+ HDR_SET(HDR__MESSAGEID, idbuff);
+ }
+
+ /* Set Path */
+ if (HDR(HDR__PATH) == NULL) {
+ if (ihave)
+ return "Missing \"Path\" header";
+ /* Note that innd will put host name here for us. */
+ HDR_SET(HDR__PATH, PATHMASTER);
+ if (VirtualPathlen > 0)
+ addvirtual = true;
+ } else if (PERMaccessconf->strippath) {
+ /* Here's where to do Path changes for new Posts. */
+ if ((p = strrchr(HDR(HDR__PATH), '!')) != NULL) {
+ p++;
+ if (*p == '\0') {
+ HDR_SET(HDR__PATH, PATHMASTER);
+ if (VirtualPathlen > 0)
+ addvirtual = true;
+ } else {
+ HDR_SET(HDR__PATH, p);
+ if ((VirtualPathlen > 0) &&
+ strcmp(p, PERMaccessconf->pathhost) != 0)
+ addvirtual = true;
+ }
+ } else if (VirtualPathlen > 0)
+ addvirtual = true;
+ } else {
+ if ((VirtualPathlen > 0) &&
+ (p = strchr(HDR(HDR__PATH), '!')) != NULL) {
+ *p = '\0';
+ if (strcmp(HDR(HDR__PATH), PERMaccessconf->pathhost) != 0)
+ addvirtual = true;
+ *p = '!';
+ } else if (VirtualPathlen > 0)
+ addvirtual = true;
+ }
+ if (addvirtual) {
+ if (newpath != NULL)
+ free(newpath);
+ newpath = concat(VirtualPath, HDR(HDR__PATH), (char *) 0);
+ HDR_SET(HDR__PATH, newpath);
+ }
+
+
+ /* Reply-To; left alone. */
+ /* Sender; set above. */
+
+ /* Check Expires. */
+ if (HDR(HDR__EXPIRES) && parsedate(HDR(HDR__EXPIRES), &Now) == -1)
+ return "Can't parse \"Expires\" header";
+
+ /* References; left alone. */
+ /* Control; checked above. */
+
+ /* Distribution. */
+ if ((p = HDR(HDR__DISTRIBUTION)) != NULL) {
+ p = xstrdup(p);
+ error = CheckDistribution(p);
+ free(p);
+ if (error != NULL)
+ return error;
+ }
+
+ /* Set Organization */
+ if (!ihave && HDR(HDR__ORGANIZATION) == NULL
+ && (p = PERMaccessconf->organization) != NULL) {
+ strlcpy(orgbuff, p, sizeof(orgbuff));
+ HDR_SET(HDR__ORGANIZATION, orgbuff);
+ }
+
+ /* Keywords; left alone. */
+ /* Summary; left alone. */
+ /* Approved; left alone. */
+
+ /* Set Lines */
+ if (!ihave) {
+ snprintf(linebuff, sizeof(linebuff), "%d", linecount);
+ HDR_SET(HDR__LINES, linebuff);
+ }
+
+ /* Supersedes; left alone. */
+
+ /* NNTP-Posting host; set. */
+ if (!ihave && PERMaccessconf->addnntppostinghost)
+ HDR_SET(HDR__NNTPPOSTINGHOST, ClientHost);
+ /* NNTP-Posting-Date - not in RFC (yet) */
+ if (!ihave && PERMaccessconf->addnntppostingdate)
+ HDR_SET(HDR__NNTPPOSTINGDATE, datebuff);
+
+ /* X-Trace; set */
+ t = time((time_t *)NULL) ;
+ pid = (long) getpid() ;
+ if ((gmt = gmtime(&Now.time)) == NULL)
+ return "Can't get the time";
+ if (VirtualPathlen > 0)
+ p = PERMaccessconf->domain;
+ else
+ if ((p = GetFQDN(PERMaccessconf->domain)) == NULL)
+ p = "unknown";
+ snprintf(tracebuff, sizeof(tracebuff),
+ "%s %ld %ld %s (%d %3.3s %d %02d:%02d:%02d GMT)",
+ p, (long) t, (long) pid, ClientIpString,
+ gmt->tm_mday, &MONTHS[3 * gmt->tm_mon], 1900 + gmt->tm_year,
+ gmt->tm_hour, gmt->tm_min, gmt->tm_sec);
+ HDR_SET(HDR__XTRACE, tracebuff);
+
+ /* X-Complaints-To; set */
+ if ((p = PERMaccessconf->complaints) != NULL)
+ snprintf (complaintsbuff, sizeof(complaintsbuff), "%s", p);
+ else {
+ static const char newsmaster[] = NEWSMASTER;
+
+ if ((p = PERMaccessconf->fromhost) != NULL && strchr(newsmaster, '@') == NULL)
+ snprintf (complaintsbuff, sizeof(complaintsbuff), "%s@%s",
+ newsmaster, p);
+ else
+ snprintf (complaintsbuff, sizeof(complaintsbuff), "%s",
+ newsmaster);
+ }
+ HDR_SET(HDR__XCOMPLAINTSTO, complaintsbuff);
+
+ /* Clear out some headers that should not be here */
+ if (!ihave && PERMaccessconf->strippostcc) {
+ HDR_SET(HDR__CC, NULL);
+ HDR_SET(HDR__BCC, NULL);
+ HDR_SET(HDR__TO, NULL);
+ }
+ /* Now make sure everything is there. */
+ for (hp = Table; hp < ARRAY_END(Table); hp++)
+ if (hp->Type == HTreq && hp->Value == NULL) {
+ snprintf(Error, sizeof(Error),
+ "Required \"%s\" header is missing", hp->Name);
+ return Error;
+ }
+
+ return NULL;
+}
+
+
+/*
+** See if the user has more included text than new text. Simple-minded,
+** but reasonably effective for catching neophyte's mistakes. Son-of-1036
+** says:
+**
+** NOTE: While encouraging trimming is desirable, the 50% rule imposed
+** by some old posting agents is both inadequate and counterproductive.
+** Posters do not respond to it by being more selective about quoting;
+** they respond by padding short responses, or by using different
+** quoting styles to defeat automatic analysis. The former adds
+** unnecessary noise and volume, while the latter also defeats more
+** useful forms of automatic analysis that reading agents might wish to
+** do.
+**
+** NOTE: At the very least, if a minimum-unquoted quota is being set,
+** article bodies shorter than (say) 20 lines, or perhaps articles
+** which exceed the quota by only a few lines, should be exempt. This
+** avoids the ridiculous situation of complaining about a 5-line
+** response to a 6-line quote.
+**
+** Accordingly, bodies shorter than 20 lines are exempt. A line starting
+** with >, |, or : is included text. Decrement the count on lines starting
+** with < so that we don't reject diff(1) output.
+*/
+static const char *
+CheckIncludedText(const char *p, int lines)
+{
+ int i;
+
+ if (lines < 20)
+ return NULL;
+ for (i = 0; ; p++) {
+ switch (*p) {
+ case '>': i++; break;
+ case '|': i++; break;
+ case ':': i++; break;
+ case '<': i--; break;
+ default: break;
+ }
+ p = strchr(p, '\n');
+ if (p == NULL)
+ break;
+ }
+ if (i * 2 > lines)
+ return "Article not posted -- more included text than new text";
+ return NULL;
+}
+
+\f
+
+/*
+** Try to mail an article to the moderator of the group.
+*/
+static const char *
+MailArticle(char *group, char *article)
+{
+ static char CANTSEND[] = "Can't send text to mailer";
+ FILE *F;
+ HEADER *hp;
+ int i;
+ char *address;
+ char buff[SMBUF];
+ char *mta;
+
+ /* Try to get the address first. */
+ if ((address = GetModeratorAddress(NULL, NULL, group, PERMaccessconf->moderatormailer)) == NULL) {
+ snprintf(Error, sizeof(Error), "No mailing address for \"%s\" -- %s",
+ group, "ask your news administrator to fix this");
+ free(group);
+ return Error;
+ }
+ free(group);
+
+ /* Now build up the command (ignore format/argument mismatch errors,
+ * in case %s isn't in inconf->mta) and send the headers. */
+ if ((mta = innconf->mta) == NULL)
+ return "Can't start mailer - mta not set";
+ snprintf(buff, sizeof(buff), innconf->mta, address);
+ if ((F = popen(buff, "w")) == NULL)
+ return "Can't start mailer";
+ fprintf(F, "To: %s\n", address);
+ if (FLUSH_ERROR(F)) {
+ pclose(F);
+ return CANTSEND;
+ }
+
+ /* Write the headers, a blank line, then the article. */
+ for (hp = Table; hp < ARRAY_END(Table); hp++)
+ if (hp->Value) {
+ if (*hp->Value == ' ' || *hp->Value == '\t')
+ fprintf(F, "%s:%s\n", hp->Name, hp->Value);
+ else
+ fprintf(F, "%s: %s\n", hp->Name, hp->Value);
+ if (FLUSH_ERROR(F)) {
+ pclose(F);
+ return CANTSEND;
+ }
+ }
+ for (i = 0; i < OtherCount; i++) {
+ fprintf(F, "%s\n", OtherHeaders[i]);
+ if (FLUSH_ERROR(F)) {
+ pclose(F);
+ return CANTSEND;
+ }
+ }
+ fprintf(F, "\n");
+ i = strlen(article);
+ if (fwrite(article, 1, i, F) != (size_t)i)
+ return "Can't send article";
+ if (FLUSH_ERROR(F)) {
+ pclose(F);
+ return CANTSEND;
+ }
+ i = pclose(F);
+ if (i) {
+ snprintf(Error, sizeof(Error), "Mailer exited with status %d -- %s",
+ i, "Article might not have been mailed");
+ return Error;
+ }
+ return NULL;
+}
+
+
+/*
+** Check the newsgroups and make sure they're all valid, that none are
+** moderated, etc.
+*/
+static const char *
+ValidNewsgroups(char *hdr, char **modgroup)
+{
+ static char distbuff[SMBUF];
+ char *groups;
+ char *p;
+ bool approved;
+ struct _DDHANDLE *h;
+ char *grplist[2];
+ bool IsNewgroup;
+ bool FoundOne;
+ int flag;
+ bool hookpresent = false;
+
+#ifdef DO_PYTHON
+ hookpresent = PY_use_dynamic;
+#endif /* DO_PYTHON */
+
+ p = HDR(HDR__CONTROL);
+ IsNewgroup = (p && strncmp(p, "newgroup", 8) == 0);
+ groups = xstrdup(hdr);
+ if ((p = strtok(groups, NGSEPS)) == NULL)
+ return "Can't parse newsgroups line";
+ Error[0] = '\0';
+
+ /* Reject all articles with Approved headers unless the user is allowed to
+ add them, even to unmoderated or local groups. We want to reject them
+ to unmoderated groups in case there's a disagreement of opinion
+ between various sites as to the moderation status. */
+ approved = HDR(HDR__APPROVED) != NULL;
+ if (approved && !PERMaccessconf->allowapproved) {
+ snprintf(Error, sizeof(Error),
+ "You are not allowed to approve postings");
+ }
+
+ FoundOne = false;
+ h = DDstart((FILE *)NULL, (FILE *)NULL);
+ do {
+ if (innconf->mergetogroups && p[0] == 't' && p[1] == 'o' && p[2] == '.')
+ p = "to";
+ if (!hookpresent && PERMspecified) {
+ grplist[0] = p;
+ grplist[1] = NULL;
+ if (!PERMmatch(PERMpostlist, grplist)) {
+ snprintf(Error, sizeof(Error),
+ "You are not allowed to post to %s\r\n", p);
+ }
+ }
+ if (!OVgroupstats(p, NULL, NULL, NULL, &flag))
+ continue;
+ FoundOne = true;
+ DDcheck(h, p);
+ switch (flag) {
+ case NF_FLAG_OK:
+#ifdef DO_PYTHON
+ if (PY_use_dynamic) {
+ char *reply;
+
+ /* Authorize user using Python module method dynamic */
+ if (PY_dynamic(PERMuser, p, true, &reply) < 0) {
+ syslog(L_NOTICE, "PY_dynamic(): authorization skipped due to no Python dynamic method defined.");
+ } else {
+ if (reply != NULL) {
+ syslog(L_TRACE, "PY_dynamic() returned a refuse string for user %s at %s who wants to read %s: %s", PERMuser, ClientHost, p, reply);
+ snprintf(Error, sizeof(Error), "%s\r\n", reply);
+ free(reply);
+ break;
+ }
+ }
+ }
+#endif /* DO_PYTHON */
+ break;
+ case NF_FLAG_MODERATED:
+ if (!approved && modgroup != NULL && !*modgroup)
+ *modgroup = xstrdup(p);
+ break;
+ case NF_FLAG_IGNORE:
+ case NF_FLAG_NOLOCAL:
+ if (!PERMaccessconf->locpost)
+ snprintf(Error, sizeof(Error),
+ "Postings to \"%s\" are not allowed here.", p);
+ break;
+ case NF_FLAG_EXCLUDED:
+ /* Do NOT return an error. */
+ break;
+ case NF_FLAG_ALIAS:
+ snprintf(Error, sizeof(Error),
+ "The newsgroup \"%s\" has been renamed.\n", p);
+ break;
+ }
+ } while ((p = strtok((char *)NULL, NGSEPS)) != NULL);
+ free(groups);
+
+ if (!FoundOne && !IsNewgroup)
+ snprintf(Error, sizeof(Error), "No valid newsgroups in \"%s\"",
+ MaxLength(hdr,hdr));
+ if (Error[0]) {
+ tmpPtr = DDend(h);
+ free(tmpPtr);
+ if (modgroup != NULL && *modgroup != NULL) {
+ free(*modgroup);
+ *modgroup = NULL;
+ }
+ return Error;
+ }
+
+ p = DDend(h);
+ if (HDR(HDR__DISTRIBUTION) == NULL && *p) {
+ strlcpy(distbuff, p, sizeof(distbuff));
+ HDR_SET(HDR__DISTRIBUTION, distbuff);
+ }
+ free(p);
+ return NULL;
+}
+
+
+/*
+** Send a quit message to the server, eat its reply.
+*/
+static void
+SendQuit(FILE *FromServer, FILE *ToServer)
+{
+ char buff[NNTP_STRLEN];
+
+ fprintf(ToServer, "quit\r\n");
+ fflush(ToServer);
+ fclose(ToServer);
+ fgets(buff, sizeof buff, FromServer);
+ fclose(FromServer);
+}
+
+
+/*
+** Offer the article to the server, return its reply.
+*/
+static int
+OfferArticle(char *buff, int buffsize, FILE *FromServer, FILE *ToServer)
+{
+ static char CANTSEND[] = "Can't send %s to server, %s";
+
+ fprintf(ToServer, "ihave %s\r\n", HDR(HDR__MESSAGEID));
+ if (FLUSH_ERROR(ToServer)
+ || fgets(buff, buffsize, FromServer) == NULL) {
+ snprintf(buff, sizeof(buff), CANTSEND, "IHAVE", strerror(errno));
+ return -1;
+ }
+ return atoi(buff);
+}
+
+
+/*
+** Spool article to temp file.
+*/
+static const char *
+SpoolitTo(char *article, char *err, char *SpoolDir)
+{
+ static char CANTSPOOL[NNTP_STRLEN+2];
+ HEADER *hp;
+ FILE *F = NULL;
+ int i, fd;
+ char *tmpspool = NULL;
+ char *spoolfile = NULL;
+ char *q;
+
+ /* Initialize the returned error message */
+ snprintf(CANTSPOOL, sizeof(CANTSPOOL),
+ "%s and can't write text to local spool file", err);
+
+ /* Try to write it to the spool dir. */
+ tmpspool = concatpath(SpoolDir, ".XXXXXX");
+ fd = mkstemp(tmpspool);
+ if (fd < 0) {
+ syslog(L_FATAL, "cant create temporary spool file %s %m", tmpspool);
+ goto fail;
+ }
+ F = fdopen(fd, "w");
+ if (F == NULL) {
+ syslog(L_FATAL, "cant open %s %m", tmpspool);
+ goto fail;
+ }
+ fchmod(fileno(F), BATCHFILE_MODE);
+
+ /* Write the headers and a blank line. */
+ for (hp = Table; hp < ARRAY_END(Table); hp++)
+ if (hp->Value) {
+ q = xstrndup(hp->Value, hp->Body - hp->Value + hp->Len);
+ if (*hp->Value == ' ' || *hp->Value == '\t')
+ fprintf(F, "%s:%s\n", hp->Name, q);
+ else
+ fprintf(F, "%s: %s\n", hp->Name, q);
+ if (FLUSH_ERROR(F)) {
+ fclose(F);
+ free(q);
+ goto fail;
+ }
+ free(q);
+ }
+ for (i = 0; i < OtherCount; i++) {
+ fprintf(F, "%s\n", OtherHeaders[i]);
+ if (FLUSH_ERROR(F)) {
+ fclose(F);
+ goto fail;
+ }
+ }
+ fprintf(F, "\n");
+
+ /* Write the article body */
+ i = strlen(article);
+ if (fwrite(article, 1, i, F) != (size_t)i) {
+ fclose(F);
+ goto fail;
+ }
+
+ /* Flush and catch any errors */
+ if (fclose(F))
+ goto fail;
+
+ /* Rename the spool file to something rnews will pick up. */
+ spoolfile = concatpath(SpoolDir, "XXXXXX");
+ fd = mkstemp(spoolfile);
+ if (fd < 0) {
+ syslog(L_FATAL, "cant create spool file %s %m", spoolfile);
+ goto fail;
+ }
+ close(fd);
+ if (rename(tmpspool, spoolfile) < 0) {
+ syslog(L_FATAL, "cant rename %s %s %m", tmpspool, spoolfile);
+ goto fail;
+ }
+
+ /* Article has been spooled */
+ free(tmpspool);
+ free(spoolfile);
+ return NULL;
+
+ fail:
+ if (tmpspool != NULL)
+ free(tmpspool);
+ if (spoolfile != NULL)
+ free(spoolfile);
+ return CANTSPOOL;
+}
+
+/*
+** Spool article to temp file.
+*/
+static const char *
+Spoolit(char *article, char *err)
+{
+ return SpoolitTo(article, err, innconf->pathincoming);
+}
+
+static char *Towire(char *p) {
+ char *q, *r, *s;
+ int curlen, len = BIG_BUFFER;
+
+ for (r = p, q = s = xmalloc(len); *r != '\0' ;) {
+ curlen = q - s;
+ if (curlen + 3 > len) {
+ len += BIG_BUFFER;
+ s = xrealloc(s, len);
+ q = s + curlen;
+ }
+ if (*r == '\n') {
+ if (r > p) {
+ if (*(r - 1) != '\r')
+ *q++ = '\r';
+ } else {
+ /* this should not happen */
+ free(s);
+ return NULL;
+ }
+ }
+ *q++ = *r++;
+ }
+ curlen = q - s;
+ if (curlen + 1 > len) {
+ len++;
+ s = xrealloc(s, len);
+ q = s + curlen;
+ }
+ *q = '\0';
+ return s;
+}
+
+const char *
+ARTpost(char *article,
+ char *idbuff,
+ bool ihave,
+ bool *permanent)
+{
+ static char CANTSEND[] = "Can't send %s to server, %s";
+ int i;
+ char *p, *q;
+ char *next;
+ HEADER *hp;
+ FILE *ToServer;
+ FILE *FromServer;
+ char buff[NNTP_STRLEN + 2], frombuf[SMBUF];
+ char *modgroup = NULL;
+ const char *error;
+ char *TrackID;
+ char *DirTrackID;
+ FILE *ftd;
+ char SDir[255];
+
+ /* Assume errors are permanent, until we discover otherwise */
+ *permanent = true;
+
+ /* Set up the other headers list. */
+ if (OtherHeaders == NULL) {
+ OtherSize = HEADER_DELTA;
+ OtherHeaders = xmalloc(OtherSize * sizeof(char *));
+ }
+
+ /* Basic processing. */
+ OtherCount = 0;
+ for (hp = Table; hp < ARRAY_END(Table); hp++) {
+ hp->Size = strlen(hp->Name);
+ hp->Value = hp->Body = NULL;
+ }
+ if ((article = StripOffHeaders(article)) == NULL)
+ return Error;
+ for (i = 0, p = article; p; i++, p = next + 1)
+ if ((next = strchr(p, '\n')) == NULL)
+ break;
+ if (PERMaccessconf->checkincludedtext) {
+ if ((error = CheckIncludedText(article, i)) != NULL)
+ return error;
+ }
+ if ((error = ProcessHeaders(i, idbuff, ihave)) != NULL)
+ return error;
+ if (i == 0 && HDR(HDR__CONTROL) == NULL)
+ return "Article is empty";
+
+ if ((error = ValidNewsgroups(HDR(HDR__NEWSGROUPS), &modgroup)) != NULL)
+ return error;
+
+ strlcpy(frombuf, HDR(HDR__FROM), sizeof(frombuf));
+ for (i = 0, p = frombuf;p < frombuf + sizeof(frombuf);)
+ if ((p = strchr(p, '\n')) == NULL)
+ break;
+ else
+ *p++ = ' ';
+ HeaderCleanFrom(frombuf);
+ p = strchr(frombuf, '@');
+ if (p) {
+ strlcpy(frombuf, p+1, sizeof(frombuf));
+ p = strrchr(frombuf, '.');
+ if (!p) {
+ if (modgroup)
+ free(modgroup);
+ return "From: address not in Internet syntax";
+ }
+ }
+ else {
+ if (modgroup)
+ free(modgroup);
+ return "From: address not in Internet syntax";
+ }
+ if ((p = HDR(HDR__FOLLOWUPTO)) != NULL
+ && strcmp(p, "poster") != 0
+ && (error = ValidNewsgroups(p, (char **)NULL)) != NULL) {
+ if (modgroup)
+ free(modgroup);
+ return error;
+ }
+ if ((PERMaccessconf->localmaxartsize > 0) &&
+ (strlen(article) > (unsigned)PERMaccessconf->localmaxartsize)) {
+ snprintf(Error, sizeof(Error),
+ "Article is bigger then local limit of %ld bytes\n",
+ PERMaccessconf->localmaxartsize);
+ if (modgroup)
+ free(modgroup);
+ return Error;
+ }
+
+#if defined(DO_PERL)
+ /* Calls the Perl subroutine for headers management */
+ p = PERMaccessconf->nnrpdperlfilter ? HandleHeaders(article) : NULL;
+ if (p != NULL) {
+ if (idbuff) {
+ if (modgroup)
+ sprintf(idbuff, "(mailed to moderator for %s)", modgroup);
+ else
+ strlcpy(idbuff, HDR(HDR__MESSAGEID), SMBUF);
+ }
+ if (strncmp(p, "DROP", 4) == 0) {
+ syslog(L_NOTICE, "%s post %s", ClientHost, p);
+ if (modgroup)
+ free(modgroup);
+ return NULL;
+ }
+ else if (strncmp(p, "SPOOL", 5) == 0) {
+ syslog(L_NOTICE, "%s post %s", ClientHost, p);
+ strlcpy(SDir, innconf->pathincoming, sizeof(SDir));
+ if (modgroup) {
+ free(modgroup);
+ strlcat(SDir, "/spam/mod", sizeof(SDir));
+ return SpoolitTo(article, p, SDir);
+ }
+ else {
+ strlcat(SDir, "/spam", sizeof(SDir));
+ return SpoolitTo(article, p, SDir);
+ }
+ }
+ else
+ {
+ if (modgroup)
+ free(modgroup);
+ return p;
+ }
+ }
+#endif /* defined(DO_PERL) */
+
+ /* handle mailing to moderated groups */
+
+ if (modgroup) {
+ if (idbuff != NULL) {
+ const char *retstr;
+ retstr = MailArticle(modgroup, article);
+ strcpy (idbuff,"(mailed to moderator)") ;
+ return retstr;
+ }
+ return MailArticle(modgroup, article);
+ }
+
+ if (idbuff)
+ strlcpy(idbuff, HDR(HDR__MESSAGEID), SMBUF);
+
+ if (PERMaccessconf->spoolfirst)
+ return Spoolit(article, Error);
+
+ if (Offlinepost)
+ return Spoolit(article,Error);
+
+ /* Open a local connection to the server. */
+ if (PERMaccessconf->nnrpdposthost != NULL)
+ i = NNTPconnect(PERMaccessconf->nnrpdposthost, PERMaccessconf->nnrpdpostport,
+ &FromServer, &ToServer, buff);
+ else {
+#if defined(HAVE_UNIX_DOMAIN_SOCKETS)
+ i = NNTPlocalopen(&FromServer, &ToServer, buff);
+#else
+ i = NNTPremoteopen(innconf->port, &FromServer,
+ &ToServer, buff);
+#endif /* defined(HAVE_UNIX_DOMAIN_SOCKETS) */
+ }
+
+ /* If we cannot open the connection, initialize the error message and
+ * attempt to recover from this by spooling it locally */
+ if (i < 0) {
+ if (buff[0])
+ strlcpy(Error, buff, sizeof(Error));
+ else
+ snprintf(Error, sizeof(Error), CANTSEND, "connect request",
+ strerror(errno));
+ return Spoolit(article,Error);
+ }
+ if (Tracing)
+ syslog(L_TRACE, "%s post_connect %s",
+ ClientHost, PERMaccessconf->nnrpdposthost ? PERMaccessconf->nnrpdposthost : "localhost");
+
+ /* The code below ignores too many return values for my tastes. At least
+ * they are all inside cases that are most likely never going to happen --
+ * for example, if the server crashes. */
+
+ /* Offer article to server. */
+ i = OfferArticle(buff, (int)sizeof buff, FromServer, ToServer);
+ if (i == NNTP_AUTH_NEEDED_VAL) {
+ /* Send authorization. */
+ if (NNTPsendpassword(PERMaccessconf->nnrpdposthost, FromServer, ToServer) < 0) {
+ snprintf(Error, sizeof(Error), "Can't authorize with %s",
+ PERMaccessconf->nnrpdposthost ? PERMaccessconf->nnrpdposthost : "innd");
+ return Spoolit(article,Error);
+ }
+ i = OfferArticle(buff, (int)sizeof buff, FromServer, ToServer);
+ }
+ if (i != NNTP_SENDIT_VAL) {
+ strlcpy(Error, buff, sizeof(Error));
+ SendQuit(FromServer, ToServer);
+ if (i != NNTP_HAVEIT_VAL)
+ return Spoolit(article, Error);
+ if (i == NNTP_REJECTIT_VAL || i == NNTP_RESENDIT_VAL) {
+ *permanent = false;
+ }
+ return Error;
+ }
+ if (Tracing)
+ syslog(L_TRACE, "%s post starting", ClientHost);
+
+ /* Write the headers and a blank line. */
+ for (hp = Table; hp < ARRAY_END(Table); hp++)
+ if (hp->Value) {
+ q = xstrndup(hp->Value, hp->Body - hp->Value + hp->Len);
+ if (strchr(q, '\n') != NULL) {
+ if ((p = Towire(q)) != NULL) {
+ /* there is no white space, if hp->Value and hp->Body is the same */
+ if (*hp->Value == ' ' || *hp->Value == '\t')
+ fprintf(ToServer, "%s:%s\r\n", hp->Name, p);
+ else
+ fprintf(ToServer, "%s: %s\r\n", hp->Name, p);
+ free(p);
+ }
+ } else {
+ /* there is no white space, if hp->Value and hp->Body is the same */
+ if (*hp->Value == ' ' || *hp->Value == '\t')
+ fprintf(ToServer, "%s:%s\r\n", hp->Name, q);
+ else
+ fprintf(ToServer, "%s: %s\r\n", hp->Name, q);
+ }
+ free(q);
+ }
+ for (i = 0; i < OtherCount; i++) {
+ if (strchr(OtherHeaders[i], '\n') != NULL) {
+ if ((p = Towire(OtherHeaders[i])) != NULL) {
+ fprintf(ToServer, "%s\r\n", p);
+ free(p);
+ }
+ } else {
+ fprintf(ToServer, "%s\r\n", OtherHeaders[i]);
+ }
+ }
+ fprintf(ToServer, "\r\n");
+ if (FLUSH_ERROR(ToServer)) {
+ snprintf(Error, sizeof(Error), CANTSEND, "headers", strerror(errno));
+ fclose(FromServer);
+ fclose(ToServer);
+ return Spoolit(article, Error);
+ }
+
+ /* Send the article, get the server's reply. */
+ if (NNTPsendarticle(article, ToServer, true) < 0
+ || fgets(buff, sizeof buff, FromServer) == NULL) {
+ snprintf(Error, sizeof(Error), CANTSEND, "article", strerror(errno));
+ fclose(FromServer);
+ fclose(ToServer);
+ return Spoolit(article, Error);
+ }
+
+ /* Did the server want the article? */
+ if ((i = atoi(buff)) != NNTP_TOOKIT_VAL) {
+ strlcpy(Error, buff, sizeof(Error));
+ SendQuit(FromServer, ToServer);
+ syslog(L_TRACE, "%s server rejects %s from %s", ClientHost, HDR(HDR__MESSAGEID), HDR(HDR__PATH));
+ if (i != NNTP_REJECTIT_VAL && i != NNTP_HAVEIT_VAL)
+ return Spoolit(article, Error);
+ if (i == NNTP_REJECTIT_VAL || i == NNTP_RESENDIT_VAL) {
+ *permanent = false;
+ }
+ return Error;
+ }
+
+ /* Send a quit and close down */
+ SendQuit(FromServer, ToServer);
+
+ /* Tracking */
+ if (PERMaccessconf->readertrack) {
+ TrackID = concat(innconf->pathlog, "/trackposts/track.",
+ HDR(HDR__MESSAGEID), (char *) 0);
+ if ((ftd = fopen(TrackID,"w")) == NULL) {
+ DirTrackID = concatpath(innconf->pathlog, "trackposts");
+ MakeDirectory(DirTrackID, false);
+ free(DirTrackID);
+ }
+ if (ftd == NULL && (ftd = fopen(TrackID,"w")) == NULL) {
+ syslog(L_ERROR, "%s (%s) open %s: %m",
+ ClientHost, Username, TrackID);
+ free(TrackID);
+ return NULL;
+ }
+ for (hp = Table; hp < ARRAY_END(Table); hp++)
+ if (hp->Value) {
+ q = xstrndup(hp->Value, hp->Body - hp->Value + hp->Len);
+ if (strchr(q, '\n') != NULL) {
+ if ((p = Towire(q)) != NULL) {
+ /* there is no white space, if hp->Value and hp->Body is the same */
+ if (*hp->Value == ' ' || *hp->Value == '\t')
+ fprintf(ftd, "%s:%s\r\n", hp->Name, p);
+ else
+ fprintf(ftd, "%s: %s\r\n", hp->Name, p);
+ free(p);
+ }
+ } else {
+ /* there is no white space, if hp->Value and hp->Body is the same */
+ if (*hp->Value == ' ' || *hp->Value == '\t')
+ fprintf(ftd, "%s:%s\r\n", hp->Name, q);
+ else
+ fprintf(ftd, "%s: %s\r\n", hp->Name, q);
+ }
+ free(q);
+ }
+ for (i = 0 ; i < OtherCount ; i++) {
+ if (strchr(OtherHeaders[i], '\n') != NULL) {
+ if ((p = Towire(OtherHeaders[i])) != NULL) {
+ fprintf(ftd, "%s\r\n", p);
+ free(p);
+ }
+ } else {
+ fprintf(ftd, "%s\r\n", OtherHeaders[i]);
+ }
+ }
+ fprintf(ftd,"\r\n");
+ NNTPsendarticle(article, ftd, true);
+ if (fclose(ftd) != EOF) {
+ syslog(L_NOTICE, "%s (%s) posttrack ok %s",
+ ClientHost, Username, TrackID);
+ if (LLOGenable)
+ fprintf(locallog, "%s (%s) posttrack ok %s\n",
+ ClientHost, Username, TrackID);
+ } else {
+ syslog(L_ERROR, "%s (%s) posttrack error 2 %s",
+ ClientHost, Username, TrackID);
+ }
+ free(TrackID);
+ }
+
+ return NULL;
+}
--- /dev/null
+/* $Id: post.h 6474 2003-09-15 07:32:56Z rra $
+**
+** Net News Reading Protocol server.
+*/
+
+typedef enum _HEADERTYPE {
+ HTobs,
+ HTreq,
+ HTstd
+} HEADERTYPE;
+
+typedef struct _HEADER {
+ const char * Name;
+ bool CanSet;
+ HEADERTYPE Type;
+ int Size;
+ char * Value; /* just after ':' in header */
+ char * Body; /* where actual body begins */
+ int Len; /* body length excluding trailing white spaces */
+} HEADER;
+
+#define HDR(_x) (Table[(_x)].Body)
+#define HDR_SET(_x, _y) do { \
+ Table[(_x)].Body = Table[(_x)].Value = _y; \
+ if (_y == NULL) { \
+ Table[(_x)].Len = 0; \
+ } else { \
+ Table[(_x)].Len = strlen(_y); \
+ } \
+} while (0)
+
+#define HDR__PATH 0
+#define HDR__FROM 1
+#define HDR__NEWSGROUPS 2
+#define HDR__SUBJECT 3
+#define HDR__CONTROL 4
+#define HDR__FOLLOWUPTO 6
+#define HDR__DATE 7
+#define HDR__ORGANIZATION 8
+#define HDR__LINES 9
+#define HDR__SENDER 10
+#define HDR__APPROVED 11
+#define HDR__DISTRIBUTION 12
+#define HDR__EXPIRES 13
+#define HDR__MESSAGEID 14
+#define HDR__NNTPPOSTINGHOST 17
+#define HDR__XTRACE 21
+#define HDR__XCOMPLAINTSTO 22
+#define HDR__NNTPPOSTINGDATE 23
+#define HDR__CC 33
+#define HDR__BCC 34
+#define HDR__TO 35
--- /dev/null
+/* $Id: python.c 7893 2008-06-22 10:24:42Z iulius $
+**
+** 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 <ilya@glas.net>, 1999.
+**
+** This code bases on Python work for innd filtering done by
+** G.J. Andruk <meowing@banet.net>. Also it borrows some ideas from
+** TCL/Perl work done by Bob Heiney and Christophe Wolfhugel.
+**
+** A quick note regarding Python exceptions: functions like
+** PyObject_GetAttrString(PyObject *o, const char *attr_name)
+** raise an exception when they fail, even though they return NULL.
+** And as exceptions accumulate from caller to caller and so on,
+** it generates weird issues with Python scripts afterwards. So such
+** uses should be checked before. For instance with:
+** PyObject_HasAttrString(PyObject *o, const char *attr_name).
+*/
+
+#include "config.h"
+#include "clibrary.h"
+
+#include "inn/innconf.h"
+#include "nnrpd.h"
+#include "inn/hashtab.h"
+
+#if defined(DO_PYTHON)
+
+#include "Python.h"
+
+/* values relate name of hook to array index */
+#define PYTHONauthen 1
+#define PYTHONaccess 2
+#define PYTHONdynamic 3
+
+#define PYTHONtypes_max 4
+
+/* values relate type of method to array index */
+#define PYTHONmain 1
+#define PYTHONinit 2
+#define PYTHONclose 3
+
+#define PYTHONmethods_max 4
+
+/* key names for attributes dictionary */
+#define PYTHONhostname "hostname"
+#define PYTHONipaddress "ipaddress"
+#define PYTHONport "port"
+#define PYTHONinterface "interface"
+#define PYTHONintipaddr "intipaddr"
+#define PYTHONintport "intport"
+#define PYTHONuser "user"
+#define PYTHONpass "pass"
+#define PYTHONtype "type"
+#define PYTHONnewsgroup "newsgroup"
+
+/* Max number of items in dictionary to pass to auth methods */
+#define _PY_MAX_AUTH_ITEM 10
+
+
+/* Pointers to external Python objects */
+PyObject *PYAuthObject = NULL;
+
+/* Dictionary of params to pass to authentication methods */
+PyObject *PYauthinfo = NULL;
+PyObject **PYauthitem = NULL;
+
+/* Forward declaration */
+static PyObject *PY_set_auth_hook(PyObject *dummy, PyObject *args);
+void PY_load_python(void);
+PyObject* PY_setup(int type, int method, char *file);
+static const void *file_key(const void *p);
+static bool file_equal(const void *k, const void *p);
+static void file_free(void *p);
+static void file_trav(void *data, void* null);
+
+bool PythonLoaded = false;
+
+/* structure for storage of attributes for a module file */
+typedef struct PyFile {
+ char *file;
+ bool loaded[PYTHONtypes_max];
+ PyObject *procs[PYTHONtypes_max][PYTHONmethods_max];
+} PyFile;
+
+/* hash for storing files */
+struct hash *files;
+
+/* for passing the dynamic module filename from perm.c */
+char* dynamic_file;
+
+/*
+** Authenticate connecting host by username&password.
+**
+** Return NNTP reply code as returned by Python method or -1 if method
+** is not defined.
+*/
+int PY_authenticate(char* file, char *Username, char *Password, char *errorstring, char *newUser) {
+ PyObject *result, *item, *proc;
+ int authnum;
+ int code, i;
+ char *temp;
+
+ PY_load_python();
+ proc = PY_setup(PYTHONauthen, PYTHONmain, file);
+
+ /* Return if authentication method is not defined */
+ if (proc == NULL)
+ return -1;
+
+ /* Initialize PythonAuthObject with connect method specific items */
+ authnum = 0;
+
+ /* Client hostname */
+ PYauthitem[authnum] = PyBuffer_FromMemory(ClientHost, strlen(ClientHost));
+ PyDict_SetItemString(PYauthinfo, PYTHONhostname, PYauthitem[authnum++]);
+
+ /* Client IP number */
+ PYauthitem[authnum] = PyBuffer_FromMemory(ClientIpString, strlen(ClientIpString));
+ PyDict_SetItemString(PYauthinfo, PYTHONipaddress, PYauthitem[authnum++]);
+
+ /* Client port number */
+ PYauthitem[authnum] = PyInt_FromLong(ClientPort);
+ PyDict_SetItemString(PYauthinfo, PYTHONport, PYauthitem[authnum++]);
+
+ /* Server interface the connection comes to */
+ PYauthitem[authnum] = PyBuffer_FromMemory(ServerHost, strlen(ServerHost));
+ PyDict_SetItemString(PYauthinfo, PYTHONinterface, PYauthitem[authnum++]);
+
+ /* Server IP number */
+ PYauthitem[authnum] = PyBuffer_FromMemory(ServerIpString, strlen(ServerIpString));
+ PyDict_SetItemString(PYauthinfo, PYTHONintipaddr, PYauthitem[authnum++]);
+
+ /* Server port number */
+ PYauthitem[authnum] = PyInt_FromLong(ServerPort);
+ PyDict_SetItemString(PYauthinfo, PYTHONintport, PYauthitem[authnum++]);
+
+ /* Username if known */
+ if (Username == NULL) {
+ PYauthitem[authnum] = Py_None;
+ } else {
+ PYauthitem[authnum] = PyBuffer_FromMemory(Username, strlen(Username));
+ }
+ PyDict_SetItemString(PYauthinfo, PYTHONuser, PYauthitem[authnum++]);
+
+ /* Password if known */
+ if (Password == NULL) {
+ PYauthitem[authnum] = Py_None;
+ } else {
+ PYauthitem[authnum] = PyBuffer_FromMemory(Password, strlen(Password));
+ }
+ PyDict_SetItemString(PYauthinfo, PYTHONpass, PYauthitem[authnum++]);
+
+ /* Now invoke authenticate method and see if it likes this user */
+ result = PyObject_CallFunction(proc, "O", PYauthinfo);
+
+ /* Check the response */
+ if (result == NULL || !PyTuple_Check(result)
+ || ((PyTuple_Size(result) != 2) && (PyTuple_Size(result) != 3)))
+ {
+ syslog(L_ERROR, "python authenticate method returned wrong result");
+ Reply("%d Internal Error (7). Goodbye\r\n", NNTP_ACCESS_VAL);
+ ExitWithStats(1, true);
+ }
+
+ /* Get the NNTP response code */
+ item = PyTuple_GetItem(result, 0);
+
+ /* Check the item */
+ if (!PyInt_Check(item))
+ {
+ syslog(L_ERROR, "python authenticate method returned bad NNTP response code");
+ Reply("%d Internal Error (7). Goodbye\r\n", NNTP_ACCESS_VAL);
+ ExitWithStats(1, true);
+ }
+
+ /* Store the code */
+ code = PyInt_AS_LONG(item);
+
+ /* Get the error string */
+ item = PyTuple_GetItem(result, 1);
+
+ /* Check the item */
+ if (!PyString_Check(item))
+ {
+ syslog(L_ERROR, "python authenticate method returned bad error string");
+ Reply("%d Internal Error (7). Goodbye\r\n", NNTP_ACCESS_VAL);
+ ExitWithStats(1, true);
+ }
+
+ /* Store error string */
+ temp = PyString_AS_STRING(item);
+ errorstring = xstrdup(temp);
+
+ if (PyTuple_Size(result) == 3) {
+
+ /* Get the username string */
+ item = PyTuple_GetItem(result, 2);
+
+ /* Check the item */
+ if (!PyString_Check(item)) {
+ syslog(L_ERROR, "python authenticate method returned bad username string");
+ Reply("%d Internal Error (7). Goodbye\r\n", NNTP_ACCESS_VAL);
+ ExitWithStats(1, true);
+ }
+
+ /* Store error string */
+ temp = PyString_AS_STRING(item);
+ newUser = xstrdup(temp);
+ }
+
+ /* 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 succeeded, return code %d, error string %s", code, errorstring);
+
+ /* Return response code */
+ return code;
+}
+
+/*
+** Create an access group based on the values returned by the script in file
+**
+*/
+void PY_access(char* file, struct vector *access_vec, char *Username) {
+ PyObject *result, *key, *value, *proc;
+ char *buffer;
+ int authnum;
+ int i;
+
+ PY_load_python();
+ proc = PY_setup(PYTHONaccess, PYTHONmain, file);
+
+ /* Exit if access method is not defined */
+ if (proc == NULL) {
+ syslog(L_ERROR, "python access method not defined");
+ Reply("%d Internal Error (7). Goodbye\r\n", NNTP_ACCESS_VAL);
+ ExitWithStats(1, true);
+ }
+
+ /* Initialize PythonAuthObject with group method specific items */
+ authnum = 0;
+
+ /* Client hostname */
+ PYauthitem[authnum] = PyBuffer_FromMemory(ClientHost, strlen(ClientHost));
+ PyDict_SetItemString(PYauthinfo, PYTHONhostname, PYauthitem[authnum++]);
+
+ /* Client IP number */
+ PYauthitem[authnum] = PyBuffer_FromMemory(ClientIpString, strlen(ClientIpString));
+ PyDict_SetItemString(PYauthinfo, PYTHONipaddress, PYauthitem[authnum++]);
+
+ /* Client port number */
+ PYauthitem[authnum] = PyInt_FromLong(ClientPort);
+ PyDict_SetItemString(PYauthinfo, PYTHONport, PYauthitem[authnum++]);
+
+ /* Server interface the connection comes to */
+ PYauthitem[authnum] = PyBuffer_FromMemory(ServerHost, strlen(ServerHost));
+ PyDict_SetItemString(PYauthinfo, PYTHONinterface, PYauthitem[authnum++]);
+
+ /* Server IP number */
+ PYauthitem[authnum] = PyBuffer_FromMemory(ServerIpString, strlen(ServerIpString));
+ PyDict_SetItemString(PYauthinfo, PYTHONintipaddr, PYauthitem[authnum++]);
+
+ /* Server port number */
+ PYauthitem[authnum] = PyInt_FromLong(ServerPort);
+ PyDict_SetItemString(PYauthinfo, PYTHONintport, PYauthitem[authnum++]);
+
+ /* Username */
+ PYauthitem[authnum] = PyBuffer_FromMemory(Username, strlen(Username));
+ PyDict_SetItemString(PYauthinfo, PYTHONuser, PYauthitem[authnum++]);
+
+ /* Password is not known */
+ PYauthitem[authnum] = Py_None;
+ PyDict_SetItemString(PYauthinfo, PYTHONpass, PYauthitem[authnum++]);
+
+ /*
+ * Now invoke newsgroup access method
+ */
+ result = PyObject_CallFunction(proc, "O", PYauthinfo);
+
+ /* Check the response */
+ if (result == NULL || result == Py_None || !PyDict_Check(result)) {
+ syslog(L_ERROR, "python access method returned wrong result -- expected a dictionary");
+ Reply("%d Internal Error (7). Goodbye\r\n", NNTP_ACCESS_VAL);
+ ExitWithStats(1, true);
+ }
+
+ /* resize vector to dictionary length */
+ vector_resize(access_vec, PyDict_Size(result) - 1);
+
+ /* store dict values in proper format in access vector */
+ i = 0;
+ buffer = xmalloc(BIG_BUFFER);
+
+ while(PyDict_Next(result, &i, &key, &value)) {
+ if (!PyString_Check(key)) {
+ syslog(L_ERROR, "python access method return dictionary key %i not a string", i);
+ Reply("%d Internal Error (7). Goodbye\r\n", NNTP_ACCESS_VAL);
+ ExitWithStats(1, false);
+ }
+ if (!PyString_Check(value)) {
+ syslog(L_ERROR, "python access method return dictionary value %i not a string", i);
+ Reply("%d Internal Error (7). Goodbye\r\n", NNTP_ACCESS_VAL);
+ ExitWithStats(1, false);
+ }
+
+ strlcpy(buffer, PyString_AsString(key), BIG_BUFFER);
+ strlcat(buffer, ": \"", BIG_BUFFER);
+ strlcat(buffer, PyString_AsString(value), BIG_BUFFER);
+ strlcat(buffer, "\"\n", BIG_BUFFER);
+
+ vector_add(access_vec, xstrdup(buffer));
+ }
+
+ free(buffer);
+
+ /* 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 access method succeeded");
+}
+
+/*
+** Initialize dynamic access control code
+*/
+
+void PY_dynamic_init (char* file) {
+ dynamic_file = xstrdup(file);
+ PY_use_dynamic = true;
+}
+
+
+/*
+** Determine dynamic user access rights to a given newsgroup.
+**
+** Return 0 if requested privelege is granted or positive value
+** and a reply_message pointer initialized with reply message.
+** Return negative value if dynamic method is not defined.
+*/
+int PY_dynamic(char *Username, char *NewsGroup, int PostFlag, char **reply_message) {
+ PyObject *result, *item, *proc;
+ char *string, *temp;
+ int authnum;
+ int i;
+
+ PY_load_python();
+ proc = PY_setup(PYTHONdynamic, PYTHONmain, dynamic_file);
+
+ /* Return if dynamic method is not defined */
+ if (proc == NULL)
+ return -1;
+
+ /* Initialize PythonAuthObject with group method specific items */
+ authnum = 0;
+
+ /* Client hostname */
+ PYauthitem[authnum] = PyBuffer_FromMemory(ClientHost, strlen(ClientHost));
+ PyDict_SetItemString(PYauthinfo, PYTHONhostname, PYauthitem[authnum++]);
+
+ /* Client IP number */
+ PYauthitem[authnum] = PyBuffer_FromMemory(ClientIpString, strlen(ClientIpString));
+ PyDict_SetItemString(PYauthinfo, PYTHONipaddress, PYauthitem[authnum++]);
+
+ /* Client port number */
+ PYauthitem[authnum] = PyInt_FromLong(ClientPort);
+ PyDict_SetItemString(PYauthinfo, PYTHONport, PYauthitem[authnum++]);
+
+ /* Server interface the connection comes to */
+ PYauthitem[authnum] = PyBuffer_FromMemory(ServerHost, strlen(ServerHost));
+ PyDict_SetItemString(PYauthinfo, PYTHONinterface, PYauthitem[authnum++]);
+
+ /* Server IP number */
+ PYauthitem[authnum] = PyBuffer_FromMemory(ServerIpString, strlen(ServerIpString));
+ PyDict_SetItemString(PYauthinfo, PYTHONintipaddr, PYauthitem[authnum++]);
+
+ /* Server port number */
+ PYauthitem[authnum] = PyInt_FromLong(ServerPort);
+ PyDict_SetItemString(PYauthinfo, PYTHONintport, PYauthitem[authnum++]);
+
+ /* Username */
+ PYauthitem[authnum] = PyBuffer_FromMemory(Username, strlen(Username));
+ PyDict_SetItemString(PYauthinfo, PYTHONuser, PYauthitem[authnum++]);
+
+ /* Password is not known */
+ PYauthitem[authnum] = Py_None;
+ PyDict_SetItemString(PYauthinfo, PYTHONpass, PYauthitem[authnum++]);
+
+ /* Assign authentication type */
+ PYauthitem[authnum] = PyBuffer_FromMemory(PostFlag ? "post" : "read", 4);
+ PyDict_SetItemString(PYauthinfo, PYTHONtype, PYauthitem[authnum++]);
+
+ /* Newsgroup user tries to access */
+ PYauthitem[authnum] = PyBuffer_FromMemory(NewsGroup, strlen(NewsGroup));
+ PyDict_SetItemString(PYauthinfo, PYTHONnewsgroup, PYauthitem[authnum++]);
+
+ /*
+ * Now invoke newsgroup dynamic access method and see if
+ * it likes this user to access this newsgroup.
+ */
+ result = PyObject_CallFunction(proc, "O", PYauthinfo);
+
+ /* Check the response */
+ if (result == NULL || (result != Py_None && !PyString_Check(result)))
+ {
+ syslog(L_ERROR, "python dynamic method (%s access) returned wrong result: %s", PostFlag ? "post" : "read", result);
+ Reply("%d Internal Error (7). Goodbye\r\n", NNTP_ACCESS_VAL);
+ ExitWithStats(1, false);
+ }
+
+ /* Get the response string */
+ if (result == Py_None) {
+ string = NULL;
+ } else {
+ temp = PyString_AS_STRING(result);
+ string = xstrdup(temp);
+ }
+ /* Clean up the dictionary object */
+ PyDict_Clear(PYauthinfo);
+
+ /* Clean up dictionary items */
+ for (i = 0; i < authnum; i++) {
+ if (PYauthitem[i] != Py_None) {
+ Py_DECREF(PYauthitem[i]);
+ }
+ }
+
+ /* Log auth result */
+ syslog(L_NOTICE, "python dynamic method (%s access) succeeded, refusion string: %s", PostFlag ? "post" : "read", string == NULL ? "<empty>" : string);
+
+ /* Initialize reply string */
+ if (reply_message != NULL)
+ *reply_message = string;
+
+ /* Return result */
+ return string == NULL ? 0 : 1;
+}
+
+
+/*
+** This runs when nnrpd shuts down. If Python is closed and reopened
+** in the same process, files and dynamic_file are reused so they
+** must point to NULL.
+*/
+void
+PY_close_python(void)
+{
+ if (files != NULL) {
+ hash_traverse(files, file_trav, NULL);
+ hash_free(files);
+ files = NULL;
+ }
+ if (dynamic_file != NULL) {
+ free(dynamic_file);
+ dynamic_file = NULL;
+ }
+}
+
+/*
+** Traversal function for PY_close_python
+*/
+void
+file_trav(void *data, void* null UNUSED)
+{
+ PyFile *fp = data;
+ int j;
+ PyObject *result, *func;
+
+ for (j = 1; j < PYTHONtypes_max; j++) {
+ if (fp->loaded[j] != false) {
+ func = fp->procs[j][PYTHONclose];
+ if (func != NULL) {
+ result = PyObject_CallFunction(func, NULL);
+ Py_XDECREF(result);
+ }
+ }
+ }
+}
+
+/*
+** Python's syslog module isn't compiled in by default. It's easier
+** to do it this way, and the switch block looks pretty in a color
+** editor).
+*/
+static PyObject *
+PY_syslog(PyObject *self UNUSED, PyObject *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(PyObject *dummy UNUSED, PyObject *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;
+}
+
+/*
+** Load the Python interpreter
+*/
+void PY_load_python() {
+ if (!PythonLoaded) {
+ /* add path for nnrpd module */
+ setenv("PYTHONPATH", innconf->pathfilter, 1);
+
+ /* Load up the interpreter ;-O */
+ Py_Initialize();
+
+ /* It makes Python sad when its stdout and stderr are closed. */
+ if ((fileno(stdout) == -1) || (fileno(stderr) == -1))
+ PyRun_SimpleString("import sys; sys.stdout=sys.stderr=open('/dev/null', 'a')");
+
+ /* See if Python initialized OK */
+ if (!Py_IsInitialized ()) {
+ syslog(L_ERROR, "python interpreter NOT initialized");
+ return;
+ }
+
+
+ /* Build a module interface to certain nnrpd functions */
+ (void) Py_InitModule("nnrpd", nnrpdPyMethods);
+
+ /*
+ ** Grab space for authinfo dictionary so we aren't forever
+ ** recreating them.
+ */
+ PYauthinfo = PyDict_New();
+ PYauthitem = xcalloc(_PY_MAX_AUTH_ITEM, sizeof(PyObject *));
+
+ /* create hash to store file attributes */
+
+ files = hash_create(4, hash_string, file_key,
+ file_equal, file_free);
+
+ PythonLoaded = true;
+
+ syslog(L_NOTICE, "python interpreter initialized OK");
+ }
+}
+
+/*
+** Check that a method exists and is callable. Set up a pointer to
+** the corresponding PyObject, or NULL if not found.
+*/
+void
+PYdefonemethod(PyFile *fp, int type, int method, char *methname, int realtype) {
+ PyObject **methptr;
+
+ methptr = &fp->procs[type][method];
+ /* There is no need to check the existence of methods useless for our realtype. */
+ if (type == realtype) {
+ /*
+ ** We check with HasAttrString() the existence of the method because
+ ** otherwise, in case it does not exist, an exception is raised by Python,
+ ** although the result of the function is NULL.
+ */
+ if (PyObject_HasAttrString(PYAuthObject, (char *) methname) == 1) {
+ /* Get a pointer to given method. */
+ *methptr = PyObject_GetAttrString(PYAuthObject, (char *) methname);
+ } else {
+ *methptr = NULL;
+ }
+
+ /* 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;
+ }
+ }
+ } else {
+ *methptr = NULL;
+ }
+}
+
+
+/*
+** Look up all the known python methods and set up
+** pointers to them so that we could call them from nnrpd.
+*/
+void
+PYdefmethods(PyFile *fp, int realtype)
+{
+ /* Get a reference to authenticate() method */
+ PYdefonemethod(fp, PYTHONauthen, PYTHONmain, "authenticate", realtype);
+
+ /* Get a reference to authen_init() method */
+ PYdefonemethod(fp, PYTHONauthen, PYTHONinit, "authen_init", realtype);
+
+ /* Get a reference to authen_close() method */
+ PYdefonemethod(fp, PYTHONauthen, PYTHONclose, "authen_close", realtype);
+
+ /* Get a reference to access() method */
+ PYdefonemethod(fp, PYTHONaccess, PYTHONmain, "access", realtype);
+
+ /* Get a reference to access_init() method */
+ PYdefonemethod(fp, PYTHONaccess, PYTHONinit, "access_init", realtype);
+
+ /* Get a reference to access_close() method */
+ PYdefonemethod(fp, PYTHONaccess, PYTHONclose, "access_close", realtype);
+
+ /* Get a reference to dynamic() method */
+ PYdefonemethod(fp, PYTHONdynamic, PYTHONmain, "dynamic", realtype);
+
+ /* Get a reference to dynamic_init() method */
+ PYdefonemethod(fp, PYTHONdynamic, PYTHONinit, "dynamic_init", realtype);
+
+ /* Get a reference to dynamic_close() method */
+ PYdefonemethod(fp, PYTHONdynamic, PYTHONclose, "dynamic_close", realtype);
+}
+
+
+/*
+** Called when a python hook is needed -- this gets the scripts hooked in.
+*/
+PyObject*
+PY_setup(int type, int method, char *file)
+{
+ int i;
+ PyFile *fp;
+ PyObject *result;
+
+ /* check to see if this file is in files */
+ if (!(hash_lookup(files, file))) {
+ fp = xmalloc(sizeof(PyFile));
+ fp->file = xstrdup(file);
+
+ for (i = 1; i < PYTHONtypes_max; i++) {
+ fp->loaded[i] = false;
+ }
+
+ /* Load up external module */
+ (void) PyImport_ImportModule(file);
+
+ /* See if nnrpd auth object is defined in auth module */
+ if (PYAuthObject == NULL) {
+ syslog(L_ERROR, "python auth object is not defined");
+ Reply("%d Internal Error (7). Goodbye\r\n", NNTP_ACCESS_VAL);
+ PY_close_python();
+ ExitWithStats(1, false);
+ } else {
+ /* Set up pointers to known Python methods */
+ PYdefmethods(fp, type);
+ }
+ hash_insert(files, file, fp);
+
+ if ((!fp->loaded[type]) && (fp->procs[type][PYTHONinit] != NULL)) {
+ result = PyObject_CallFunction(fp->procs[type][PYTHONinit], NULL);
+ if (result != NULL) {
+ Py_XDECREF(result);
+ }
+ fp->loaded[type] = true;
+ }
+ return fp->procs[type][method];
+ }
+ return NULL;
+}
+
+/*
+** Return the key (filename) from a file struct, used by the hash table.
+*/
+static const void *
+file_key(const void *p)
+{
+ const struct PyFile *f = p;
+
+ return f->file;
+}
+
+/*
+** Check to see if a provided key matches the key of a PyFile struct,
+** used by the hash table.
+*/
+static bool
+file_equal(const void *k, const void *p)
+{
+ const char *key = k;
+ const struct PyFile *f = p;
+
+ return strcmp(key, f->file) == 0;
+}
+
+/*
+** Free a file, used by the hash table.
+*/
+static void
+file_free(void *p)
+{
+ struct PyFile *fp = p;
+ int i, j;
+
+ free(fp->file);
+
+ for (i = 1; i < PYTHONtypes_max; i++) {
+ for (j = 1; j < PYTHONmethods_max; j++) {
+ if (fp->procs[i][j] != NULL) {
+ Py_DECREF(fp->procs[i][j]);
+ }
+ }
+ }
+
+ free(fp);
+}
+
+#endif /* defined(DO_PYTHON) */
--- /dev/null
+/* sasl_config.c -- Configuration routines
+ Copyright (C) 2000 Kenichi Okada <okada@opaopa.org>
+
+ Author: Kenichi Okada <okada@opaopa.org>
+ Created: 2000-03-04
+*/
+
+#include "config.h"
+#include "clibrary.h"
+#include <ctype.h>
+#include <syslog.h>
+
+#include "inn/innconf.h"
+#include "nnrpd.h"
+#include "paths.h"
+#include "sasl_config.h"
+
+#ifdef HAVE_SSL
+
+struct configlist {
+ char *key;
+ char *value;
+};
+
+static struct configlist *configlist;
+static int nconfiglist;
+
+const char *sasl_config_getstring(key, def)
+const char *key;
+const char *def;
+{
+ int opt;
+
+ for (opt = 0; opt < nconfiglist; opt++) {
+ if (*key == configlist[opt].key[0] &&
+ !strcmp(key, configlist[opt].key))
+ return configlist[opt].value;
+ }
+ return def;
+}
+
+int sasl_config_getint(key, def)
+const char *key;
+int def;
+{
+ const char *val = sasl_config_getstring(key, (char *)0);
+
+ if (!val) return def;
+ if (!isdigit(*val) && (*val != '-' || !isdigit(val[1]))) return def;
+ return atoi(val);
+}
+
+int sasl_config_getswitch(key, def)
+const char *key;
+int def;
+{
+ const char *val = sasl_config_getstring(key, (char *)0);
+
+ if (!val) return def;
+
+ if (*val == '0' || *val == 'n' ||
+ (*val == 'o' && val[1] == 'f') || *val == 'f') {
+ return 0;
+ }
+ else if (*val == '1' || *val == 'y' ||
+ (*val == 'o' && val[1] == 'n') || *val == 't') {
+ return 1;
+ }
+ return def;
+}
+
+const char *sasl_config_partitiondir(partition)
+const char *partition;
+{
+ char buf[80];
+
+ if (strlen(partition) > 70) return 0;
+ snprintf(buf, sizeof(buf), "partition-%s", partition);
+
+ return sasl_config_getstring(buf, (char *)0);
+}
+
+#define CONFIGLISTGROWSIZE 10 /* 100 */
+void
+sasl_config_read()
+{
+ FILE *infile;
+ int lineno = 0;
+ int alloced = 0;
+ char buf[4096];
+ char *p, *key;
+ static char *SASL_CONFIG = NULL;
+
+ if (!SASL_CONFIG)
+ SASL_CONFIG = concatpath(innconf->pathetc, _PATH_SASL_CONFIG);
+ infile = fopen(SASL_CONFIG, "r");
+ if (!infile) {
+ fprintf(stderr, "can't open configuration file %s\n", SASL_CONFIG);
+ exit(1);
+ }
+
+ while (fgets(buf, sizeof(buf), infile)) {
+ lineno++;
+
+ if (buf[strlen(buf)-1] == '\n') buf[strlen(buf)-1] = '\0';
+ for (p = buf; *p && isspace(*p); p++);
+ if (!*p || *p == '#') continue;
+
+ key = p;
+ while (*p && (isalnum(*p) || *p == '-' || *p == '_')) {
+ if (isupper(*p)) *p = tolower(*p);
+ p++;
+ }
+ if (*p != ':') {
+ fprintf(stderr,
+ "invalid option name on line %d of configuration file\n",
+ lineno);
+ exit(1);
+ }
+ *p++ = '\0';
+
+ while (*p && isspace(*p)) p++;
+
+ if (!*p) {
+ fprintf(stderr, "empty option value on line %d of configuration file\n",
+ lineno);
+ exit(1);
+ }
+
+ if (nconfiglist == alloced) {
+ alloced += CONFIGLISTGROWSIZE;
+ configlist = xrealloc(configlist, alloced * sizeof(struct configlist));
+ }
+
+ configlist[nconfiglist].key = xstrdup(key);
+ configlist[nconfiglist].value = xstrdup(p);
+ nconfiglist++;
+ }
+ fclose(infile);
+}
+
+#endif /* HAVE_SSL */
--- /dev/null
+/* sasl_config.h
+ Copyright (C) 2000 Kenichi Okada <okada@opaopa.org>
+
+ Author: Kenichi Okada <okada@opaopa.org>
+ Created: 2000-03-04
+*/
+
+#ifndef SASL_CONFIG_H
+#define SASL_CONFIG_H
+
+#ifndef P
+#ifdef __STDC__
+#define P(x) x
+#else
+#define P(x) ()
+#endif
+#endif
+
+extern void sasl_config_read P((void));
+extern const char *sasl_config_getstring P((const char *key, const char *def));
+extern int sasl_config_getint P((const char *key, int def));
+extern int sasl_config_getswitch P((const char *key, int def));
+extern const char *sasl_config_partitiondir P((const char *partition));
+
+#endif /* SASL_SASL_CONFIG_H */
--- /dev/null
+/* tls.c --- TLSv1 functions
+ Copyright (C) 2000 Kenichi Okada <okada@opaopa.org>
+
+ Author: Kenichi Okada <okada@opaopa.org>
+ Created: 2000-02-22
+
+ Keywords: TLS, OpenSSL
+
+ Commentary:
+
+ [RFC 2246] "The TLS Protocol Version 1.0"
+ by Christopher Allen <callen@certicom.com> and
+ Tim Dierks <tdierks@certicom.com> (1999/01)
+
+ [RFC 2595] "Using TLS with IMAP, POP3 and ACAP"
+ by Chris Newman <chris.newman@innosoft.com> (1999/06)
+
+*/
+
+#include <sys/types.h>
+#include "config.h"
+#include "nnrpd.h"
+
+#ifdef HAVE_SSL
+
+/* System library. */
+
+#include <unistd.h>
+#include <stdio.h>
+#include <string.h>
+#include <syslog.h>
+#include <sys/stat.h>
+#include <sys/uio.h>
+
+#endif
+
+/* outside the ifdef so `make depend` works even ifndef HAVE_SSL */
+#include "tls.h"
+#include "sasl_config.h"
+
+#ifdef HAVE_SSL
+
+/* We must keep some of the info available */
+static const char hexcodes[] = "0123456789ABCDEF";
+
+static bool tls_initialized = false;
+
+static int verify_depth;
+static int verify_error = X509_V_OK;
+static int do_dump = 0;
+static SSL_CTX *CTX = NULL;
+SSL *tls_conn = NULL;
+
+#define CCERT_BUFSIZ 256
+
+int tls_serverengine = 0;
+int tls_serveractive = 0; /* available or not */
+char *tls_peer_subject = NULL;
+char *tls_peer_issuer = NULL;
+char *tls_peer_fingerprint = NULL;
+
+int tls_clientactive = 0; /* available or not */
+char *tls_peer_CN = NULL;
+char *tls_issuer_CN = NULL;
+
+const char *tls_protocol = NULL;
+const char *tls_cipher_name = NULL;
+int tls_cipher_usebits = 0;
+int tls_cipher_algbits = 0;
+
+
+int tls_loglevel = 0;
+
+
+/* taken from OpenSSL apps/s_cb.c
+ * tim - this seems to just be giving logging messages
+ */
+
+static void apps_ssl_info_callback(SSL * s, int where, int ret)
+{
+ const char *str;
+ int w;
+
+ if (tls_loglevel==0) return;
+
+ w = where & ~SSL_ST_MASK;
+
+ if (w & SSL_ST_CONNECT)
+ str = "SSL_connect";
+ else if (w & SSL_ST_ACCEPT)
+ str = "SSL_accept";
+ else
+ str = "undefined";
+
+ if (where & SSL_CB_LOOP) {
+ if (tls_serverengine && (tls_loglevel >= 2))
+ Printf("%s:%s", str, SSL_state_string_long(s));
+ } else if (where & SSL_CB_ALERT) {
+ str = (where & SSL_CB_READ) ? "read" : "write";
+ if ((tls_serverengine && (tls_loglevel >= 2)) ||
+ ((ret & 0xff) != SSL3_AD_CLOSE_NOTIFY))
+ Printf("SSL3 alert %s:%s:%s", str,
+ SSL_alert_type_string_long(ret),
+ SSL_alert_desc_string_long(ret));
+ } else if (where & SSL_CB_EXIT) {
+ if (ret == 0)
+ Printf("%s:failed in %s",
+ str, SSL_state_string_long(s));
+ else if (ret < 0) {
+ Printf("%s:error in %s",
+ str, SSL_state_string_long(s));
+ }
+ }
+}
+
+
+/*
+ * Hardcoded DH parameter files, from OpenSSL.
+ * For information on how these files were generated, see
+ * "Assigned Number for SKIP Protocols"
+ * (http://www.skip-vpn.org/spec/numbers.html.
+ */
+static const char file_dh512[] =
+"-----BEGIN DH PARAMETERS-----\n\
+MEYCQQD1Kv884bEpQBgRjXyEpwpy1obEAxnIByl6ypUM2Zafq9AKUJsCRtMIPWak\n\
+XUGfnHy9iUsiGSa6q6Jew1XpKgVfAgEC\n\
+-----END DH PARAMETERS-----\n";
+
+static const char file_dh1024[] =
+"-----BEGIN DH PARAMETERS-----\n\
+MIGHAoGBAPSI/VhOSdvNILSd5JEHNmszbDgNRR0PfIizHHxbLY7288kjwEPwpVsY\n\
+jY67VYy4XTjTNP18F1dDox0YbN4zISy1Kv884bEpQBgRjXyEpwpy1obEAxnIByl6\n\
+ypUM2Zafq9AKUJsCRtMIPWakXUGfnHy9iUsiGSa6q6Jew1XpL3jHAgEC\n\
+-----END DH PARAMETERS-----\n";
+
+static const char file_dh2048[] =
+"-----BEGIN DH PARAMETERS-----\n\
+MIIBCAKCAQEA9kJXtwh/CBdyorrWqULzBej5UxE5T7bxbrlLOCDaAadWoxTpj0BV\n\
+89AHxstDqZSt90xkhkn4DIO9ZekX1KHTUPj1WV/cdlJPPT2N286Z4VeSWc39uK50\n\
+T8X8dryDxUcwYc58yWb/Ffm7/ZFexwGq01uejaClcjrUGvC/RgBYK+X0iP1YTknb\n\
+zSC0neSRBzZrM2w4DUUdD3yIsxx8Wy2O9vPJI8BD8KVbGI2Ou1WMuF040zT9fBdX\n\
+Q6MdGGzeMyEstSr/POGxKUAYEY18hKcKctaGxAMZyAcpesqVDNmWn6vQClCbAkbT\n\
+CD1mpF1Bn5x8vYlLIhkmuquiXsNV6TILOwIBAg==\n\
+-----END DH PARAMETERS-----\n";
+
+static const char file_dh4096[] =
+"-----BEGIN DH PARAMETERS-----\n\
+MIICCAKCAgEA+hRyUsFN4VpJ1O8JLcCo/VWr19k3BCgJ4uk+d+KhehjdRqNDNyOQ\n\
+l/MOyQNQfWXPeGKmOmIig6Ev/nm6Nf9Z2B1h3R4hExf+zTiHnvVPeRBhjdQi81rt\n\
+Xeoh6TNrSBIKIHfUJWBh3va0TxxjQIs6IZOLeVNRLMqzeylWqMf49HsIXqbcokUS\n\
+Vt1BkvLdW48j8PPv5DsKRN3tloTxqDJGo9tKvj1Fuk74A+Xda1kNhB7KFlqMyN98\n\
+VETEJ6c7KpfOo30mnK30wqw3S8OtaIR/maYX72tGOno2ehFDkq3pnPtEbD2CScxc\n\
+alJC+EL7RPk5c/tgeTvCngvc1KZn92Y//EI7G9tPZtylj2b56sHtMftIoYJ9+ODM\n\
+sccD5Piz/rejE3Ome8EOOceUSCYAhXn8b3qvxVI1ddd1pED6FHRhFvLrZxFvBEM9\n\
+ERRMp5QqOaHJkM+Dxv8Cj6MqrCbfC4u+ZErxodzuusgDgvZiLF22uxMZbobFWyte\n\
+OvOzKGtwcTqO/1wV5gKkzu1ZVswVUQd5Gg8lJicwqRWyyNRczDDoG9jVDxmogKTH\n\
+AaqLulO7R8Ifa1SwF2DteSGVtgWEN8gDpN3RBmmPTDngyF2DHb5qmpnznwtFKdTL\n\
+KWbuHn491xNO25CQWMtem80uKw+pTnisBRF/454n1Jnhub144YRBoN8CAQI=\n\
+-----END DH PARAMETERS-----\n";
+
+/*
+ * Load hardcoded DH parameters.
+ */
+static DH *
+load_dh_buffer (const char *buffer, size_t len)
+{
+ BIO *bio;
+ DH *dh = NULL;
+
+ bio = BIO_new_mem_buf((char *) buffer, len);
+ if (bio == NULL)
+ return NULL;
+ dh = PEM_read_bio_DHparams(bio, NULL, NULL, NULL);
+/* if (dh == NULL) log error */
+ BIO_free(bio);
+
+ return dh;
+}
+
+/*
+ * Generate empheral DH key. Because this can take a long
+ * time to compute, we use precomputed parameters of the
+ * common key sizes.
+ *
+ * These values can be static (once loaded or computed) since
+ * the OpenSSL library can effectively generate random keys
+ * from the information provided.
+ *
+ * EDH keying is slightly less efficient than static RSA keying,
+ * but it offers Perfect Forward Secrecy (PFS).
+ *
+ * FIXME: support user-specified files, to eliminate risk of
+ * "small group" attacks.
+ */
+static DH *tmp_dh_cb(SSL *s UNUSED, int export UNUSED, int keylength)
+{
+ DH *r = NULL;
+ static DH *dh = NULL;
+ static DH *dh512 = NULL;
+ static DH *dh1024 = NULL;
+ static DH *dh2048 = NULL;
+ static DH *dh4096 = NULL;
+
+ switch (keylength)
+ {
+ case 512:
+ if (dh512 == NULL)
+ dh512 = load_dh_buffer(file_dh512, sizeof file_dh512);
+ r = dh512;
+ break;
+ case 1024:
+ if (dh1024 == NULL)
+ dh1024 = load_dh_buffer(file_dh1024, sizeof file_dh1024);
+ r = dh1024;
+ break;
+ case 2048:
+ if (dh2048 == NULL)
+ dh2048 = load_dh_buffer(file_dh2048, sizeof file_dh2048);
+ r = dh2048;
+ break;
+ case 4096:
+ if (dh4096 == NULL)
+ dh4096 = load_dh_buffer(file_dh4096, sizeof file_dh4096);
+ r = dh4096;
+ break;
+ default:
+ /* we should check current keylength vs. requested keylength */
+ /* also, this is an extremely expensive operation! */
+ dh = DH_generate_parameters(keylength, DH_GENERATOR_2, NULL, NULL);
+ r = dh;
+ }
+
+ return r;
+}
+
+/* taken from OpenSSL apps/s_cb.c */
+
+static int verify_callback(int ok, X509_STORE_CTX * ctx)
+{
+ char buf[256];
+ X509 *err_cert;
+ int err;
+ int depth;
+
+ syslog(L_NOTICE,"Doing a peer verify");
+
+ err_cert = X509_STORE_CTX_get_current_cert(ctx);
+ err = X509_STORE_CTX_get_error(ctx);
+ depth = X509_STORE_CTX_get_error_depth(ctx);
+
+ X509_NAME_oneline(X509_get_subject_name(err_cert), buf, 256);
+ if ((tls_serveractive) && (tls_loglevel >= 1))
+ Printf("Peer cert verify depth=%d %s", depth, buf);
+ if (ok==0)
+ {
+ syslog(L_NOTICE, "verify error:num=%d:%s", err,
+ X509_verify_cert_error_string(err));
+
+ if (verify_depth >= depth) {
+ ok = 0;
+ verify_error = X509_V_OK;
+ } else {
+ ok = 0;
+ verify_error = X509_V_ERR_CERT_CHAIN_TOO_LONG;
+ }
+ }
+ switch (ctx->error) {
+ case X509_V_ERR_UNABLE_TO_GET_ISSUER_CERT:
+ X509_NAME_oneline(X509_get_issuer_name(ctx->current_cert), buf, 256);
+ syslog(L_NOTICE, "issuer= %s", buf);
+ break;
+ case X509_V_ERR_CERT_NOT_YET_VALID:
+ case X509_V_ERR_ERROR_IN_CERT_NOT_BEFORE_FIELD:
+ syslog(L_NOTICE, "cert not yet valid");
+ break;
+ case X509_V_ERR_CERT_HAS_EXPIRED:
+ case X509_V_ERR_ERROR_IN_CERT_NOT_AFTER_FIELD:
+ syslog(L_NOTICE, "cert has expired");
+ break;
+ }
+ if ((tls_serveractive) && (tls_loglevel >= 1))
+ Printf("verify return:%d", ok);
+
+ return (ok);
+}
+
+
+/*
+ * taken from OpenSSL crypto/bio/b_dump.c, modified to save a lot of strcpy
+ * and strcat by Matti Aarnio.
+ */
+
+#define TRUNCATE
+#define DUMP_WIDTH 16
+
+static int tls_dump(const char *s, int len)
+{
+ int ret = 0;
+ char buf[160 + 1];
+ char *ss;
+ int i;
+ int j;
+ int rows;
+ int trunc;
+ unsigned char ch;
+
+ trunc = 0;
+
+
+#ifdef TRUNCATE
+ for (; (len > 0) && ((s[len - 1] == ' ') || (s[len - 1] == '\0')); len--)
+ trunc++;
+#endif
+
+ rows = (len / DUMP_WIDTH);
+ if ((rows * DUMP_WIDTH) < len)
+ rows++;
+
+ for (i = 0; i < rows; i++) {
+ buf[0] = '\0'; /* start with empty string */
+ ss = buf;
+
+ sprintf(ss, "%04x ", i * DUMP_WIDTH);
+ ss += strlen(ss);
+ for (j = 0; j < DUMP_WIDTH; j++) {
+ if (((i * DUMP_WIDTH) + j) >= len) {
+ strcpy(ss, " ");
+ } else {
+ ch = ((unsigned char) *((const char *)(s) + i * DUMP_WIDTH + j))
+ & 0xff;
+ sprintf(ss, "%02x%c", ch, j == 7 ? '|' : ' ');
+ ss += 3;
+ }
+ }
+ ss += strlen(ss);
+ *ss+= ' ';
+ for (j = 0; j < DUMP_WIDTH; j++) {
+ if (((i * DUMP_WIDTH) + j) >= len)
+ break;
+ ch = ((unsigned char) *((const char *)(s) + i * DUMP_WIDTH + j))
+ & 0xff;
+ *ss+= (((ch >= ' ') && (ch <= '~')) ? ch : '.');
+ if (j == 7) *ss+= ' ';
+ }
+ *ss = 0;
+ /*
+ * if this is the last call then update the ddt_dump thing so that
+ * we will move the selection point in the debug window
+ */
+ if (tls_loglevel>0)
+ Printf("%s", buf);
+ ret += strlen(buf);
+ }
+#ifdef TRUNCATE
+ if (trunc > 0) {
+ snprintf(buf, sizeof(buf), "%04x - <SPACES/NULS>\n", len+ trunc);
+ if (tls_loglevel>0)
+ Printf("%s", buf);
+ ret += strlen(buf);
+ }
+#endif
+ return (ret);
+}
+
+ /*
+ * Set up the cert things on the server side. We do need both the
+ * private key (in key_file) and the cert (in cert_file).
+ * Both files may be identical.
+ *
+ * This function is taken from OpenSSL apps/s_cb.c
+ */
+
+static int set_cert_stuff(SSL_CTX * ctx, char *cert_file, char *key_file)
+{
+ struct stat buf;
+
+ if (cert_file != NULL) {
+ if (SSL_CTX_use_certificate_file(ctx, cert_file,
+ SSL_FILETYPE_PEM) <= 0) {
+ syslog(L_ERROR, "unable to get certificate from '%s'", cert_file);
+ return (0);
+ }
+ if (key_file == NULL)
+ key_file = cert_file;
+
+ /* check ownership and permissions of key file */
+ if (lstat(key_file, &buf) == -1) {
+ syslog(L_ERROR, "unable to stat private key '%s'", key_file);
+ return (0);
+ }
+ if (!S_ISREG(buf.st_mode) || (buf.st_mode & 0077) != 0 ||
+ buf.st_uid != getuid()) {
+ syslog(L_ERROR, "bad ownership or permissions on private key"
+ " '%s': private key must be mode 600 and owned by "
+ NEWSUSER, cert_file);
+ return (0);
+ }
+
+ if (SSL_CTX_use_PrivateKey_file(ctx, key_file,
+ SSL_FILETYPE_PEM) <= 0) {
+ syslog(L_ERROR, "unable to get private key from '%s'", key_file);
+ return (0);
+ }
+ /* Now we know that a key and cert have been set against
+ * the SSL context */
+ if (!SSL_CTX_check_private_key(ctx)) {
+ syslog(L_ERROR, "Private key does not match the certificate public key");
+ return (0);
+ }
+ }
+ return (1);
+}
+
+
+
+ /*
+ * This is the setup routine for the SSL server. As smtpd might be called
+ * more than once, we only want to do the initialization one time.
+ *
+ * The skeleton of this function is taken from OpenSSL apps/s_server.c.
+
+ * returns -1 on error
+ */
+
+int tls_init_serverengine(int verifydepth,
+ int askcert,
+ int requirecert,
+ char *tls_CAfile,
+ char *tls_CApath,
+ char *tls_cert_file,
+ char *tls_key_file
+ )
+{
+ int off = 0;
+ int verify_flags = SSL_VERIFY_NONE;
+ char *CApath;
+ char *CAfile;
+ char *s_cert_file;
+ char *s_key_file;
+ struct stat buf;
+
+ if (tls_serverengine)
+ return (0); /* already running */
+
+ if (tls_loglevel >= 2)
+ Printf("starting TLS engine");
+
+ SSL_load_error_strings();
+ SSLeay_add_ssl_algorithms();
+
+ CTX = SSL_CTX_new(SSLv23_server_method());
+ if (CTX == NULL) {
+ return (-1);
+ };
+
+ off |= SSL_OP_ALL; /* Work around all known bugs */
+ SSL_CTX_set_options(CTX, off);
+ SSL_CTX_set_info_callback(CTX, apps_ssl_info_callback);
+ SSL_CTX_sess_set_cache_size(CTX, 128);
+
+ if (strlen(tls_CAfile) == 0)
+ CAfile = NULL;
+ else
+ CAfile = tls_CAfile;
+ if (strlen(tls_CApath) == 0)
+ CApath = NULL;
+ else
+ CApath = tls_CApath;
+
+ if ((!SSL_CTX_load_verify_locations(CTX, CAfile, CApath)) ||
+ (!SSL_CTX_set_default_verify_paths(CTX))) {
+ if (tls_loglevel >= 2)
+ Printf("TLS engine: cannot load CA data\n");
+ return (-1);
+ }
+
+ if (strlen(tls_cert_file) == 0)
+ s_cert_file = NULL;
+ else
+ s_cert_file = tls_cert_file;
+ if (strlen(tls_key_file) == 0)
+ s_key_file = NULL;
+ else
+ s_key_file = tls_key_file;
+
+ if (!set_cert_stuff(CTX, s_cert_file, s_key_file)) {
+ if (tls_loglevel >= 2)
+ Printf("TLS engine: cannot load cert/key data\n");
+ return (-1);
+ }
+
+ /* load some randomization data from /dev/urandom, if it exists */
+ /* FIXME: should also check for ".rand" file, update it on exit */
+ if (stat("/dev/urandom", &buf) == 0)
+ RAND_load_file("/dev/urandom", 16 * 1024);
+
+ SSL_CTX_set_tmp_dh_callback(CTX, tmp_dh_cb);
+ SSL_CTX_set_options(CTX, SSL_OP_SINGLE_DH_USE);
+
+ verify_depth = verifydepth;
+ if (askcert!=0)
+ verify_flags |= SSL_VERIFY_PEER | SSL_VERIFY_CLIENT_ONCE;
+ if (requirecert)
+ verify_flags |= SSL_VERIFY_PEER | SSL_VERIFY_FAIL_IF_NO_PEER_CERT
+ | SSL_VERIFY_CLIENT_ONCE;
+ SSL_CTX_set_verify(CTX, verify_flags, verify_callback);
+
+ SSL_CTX_set_client_CA_list(CTX, SSL_load_client_CA_file(CAfile));
+
+ tls_serverengine = 1;
+ return (0);
+}
+
+
+/*
+** The function called by nnrpd to initialize the TLS support. Calls
+** tls_init_server_engine and checks the result. On any sort of failure,
+** nnrpd will exit.
+*/
+void
+tls_init(void)
+{
+ int ssl_result;
+
+ if (tls_initialized)
+ return;
+ sasl_config_read();
+ ssl_result = tls_init_serverengine(5, /* depth to verify */
+ 0, /* can client auth? */
+ 0, /* required client to auth? */
+ (char *)sasl_config_getstring("tls_ca_file", ""),
+ (char *)sasl_config_getstring("tls_ca_path", ""),
+ (char *)sasl_config_getstring("tls_cert_file", ""),
+ (char *)sasl_config_getstring("tls_key_file", ""));
+ if (ssl_result == -1) {
+ Reply("%d Error initializing TLS\r\n", NNTP_STARTTLS_BAD_VAL);
+ syslog(L_ERROR, "error initializing TLS: "
+ "[CA_file: %s] [CA_path: %s] [cert_file: %s] [key_file: %s]",
+ sasl_config_getstring("tls_ca_file", ""),
+ sasl_config_getstring("tls_ca_path", ""),
+ sasl_config_getstring("tls_cert_file", ""),
+ sasl_config_getstring("tls_key_file", ""));
+ ExitWithStats(1, false);
+ }
+ tls_initialized = true;
+}
+
+
+/* taken from OpenSSL apps/s_cb.c */
+
+static long bio_dump_cb(BIO * bio, int cmd, const char *argp, int argi,
+ long argl UNUSED, long ret)
+{
+ if (!do_dump)
+ return (ret);
+
+ if (cmd == (BIO_CB_READ | BIO_CB_RETURN)) {
+ Printf("read from %08X [%08lX] (%d bytes => %ld (0x%X))", (unsigned int) bio, (long unsigned int) argp,
+ argi, ret, (unsigned int) ret);
+ tls_dump(argp, (int) ret);
+ return (ret);
+ } else if (cmd == (BIO_CB_WRITE | BIO_CB_RETURN)) {
+ Printf("write to %08X [%08lX] (%d bytes => %ld (0x%X))", (unsigned int) bio, (long unsigned int)argp,
+ argi, ret, (unsigned int) ret);
+ tls_dump(argp, (int) ret);
+ }
+ return (ret);
+}
+
+ /*
+ * This is the actual startup routine for the connection. We expect
+ * that the buffers are flushed and the "220 Ready to start TLS" was
+ * send to the client, so that we can immediately can start the TLS
+ * handshake process.
+ *
+ * layerbits and authid are filled in on sucess. authid is only
+ * filled in if the client authenticated
+ *
+ */
+int tls_start_servertls(int readfd, int writefd)
+{
+ int sts;
+ int keepalive;
+ SSL_SESSION *session;
+ SSL_CIPHER *cipher;
+
+ if (!tls_serverengine)
+ {
+ /* should never happen */
+ syslog(L_ERROR, "tls_engine not running");
+ return (-1);
+ }
+ if (tls_loglevel >= 1)
+ Printf("setting up TLS connection");
+
+ if (tls_conn == NULL)
+ {
+ tls_conn = (SSL *) SSL_new(CTX);
+ }
+ if (tls_conn == NULL)
+ {
+ return (-1);
+ }
+ SSL_clear(tls_conn);
+
+#if defined(SOL_SOCKET) && defined(SO_KEEPALIVE)
+ /* Set KEEPALIVE to catch broken socket connections. */
+ keepalive = 1;
+ if (setsockopt(readfd, SOL_SOCKET, SO_KEEPALIVE, &keepalive, sizeof(keepalive)) < 0)
+ syslog(L_ERROR, "fd %d can't setsockopt(KEEPALIVE) %m", readfd);
+ if (setsockopt(writefd, SOL_SOCKET, SO_KEEPALIVE, &keepalive, sizeof(keepalive)) < 0)
+ syslog(L_ERROR, "fd %d can't setsockopt(KEEPALIVE) %m", writefd);
+#endif /* SOL_SOCKET && SO_KEEPALIVE */
+
+ /* set the file descriptors for SSL to use */
+ if (SSL_set_rfd(tls_conn, readfd)==0)
+ {
+ return (-1);
+ }
+
+ if (SSL_set_wfd(tls_conn, writefd)==0)
+ {
+ return (-1);
+ }
+
+ /*
+ * This is the actual handshake routine. It will do all the negotiations
+ * and will check the client cert etc.
+ */
+ SSL_set_accept_state(tls_conn);
+
+ /*
+ * We do have an SSL_set_fd() and now suddenly a BIO_ routine is called?
+ * Well there is a BIO below the SSL routines that is automatically
+ * created for us, so we can use it for debugging purposes.
+ */
+ if (tls_loglevel >= 3)
+ BIO_set_callback(SSL_get_rbio(tls_conn), bio_dump_cb);
+
+ /* Dump the negotiation for loglevels 3 and 4*/
+ if (tls_loglevel >= 3)
+ do_dump = 1;
+
+ if ((sts = SSL_accept(tls_conn)) <= 0) { /* xxx <= 0 */
+ session = SSL_get_session(tls_conn);
+
+ if (session) {
+ SSL_CTX_remove_session(CTX, session);
+ }
+ if (tls_conn)
+ SSL_free(tls_conn);
+ tls_conn = NULL;
+ return (-1);
+ }
+ /* Only loglevel==4 dumps everything */
+ if (tls_loglevel < 4)
+ do_dump = 0;
+
+ tls_protocol = SSL_get_version(tls_conn);
+ cipher = SSL_get_current_cipher(tls_conn);
+
+ tls_cipher_name = SSL_CIPHER_get_name(cipher);
+ tls_cipher_usebits = SSL_CIPHER_get_bits(cipher,
+ &tls_cipher_algbits);
+ tls_serveractive = 1;
+
+ syslog(L_NOTICE, "starttls: %s with cipher %s (%d/%d bits) no authentication", tls_protocol, tls_cipher_name,
+ tls_cipher_usebits, tls_cipher_algbits);
+
+ return (0);
+}
+
+ssize_t
+SSL_writev (ssl, vector, count)
+ SSL *ssl;
+ const struct iovec *vector;
+ int count;
+{
+ static char *buffer = NULL;
+ static size_t allocsize = 0;
+ char *bp;
+ size_t bytes, to_copy;
+ int i;
+ /* Find the total number of bytes to be written. */
+ bytes = 0;
+ for (i = 0; i < count; ++i)
+ bytes += vector[i].iov_len;
+ /* Allocate a buffer to hold the data. */
+ if (NULL == buffer) {
+ buffer = (char *) xmalloc(bytes);
+ allocsize = bytes;
+ } else if (bytes > allocsize) {
+ buffer = (char *) xrealloc (buffer, bytes);
+ allocsize = bytes;
+ }
+ /* Copy the data into BUFFER. */
+ to_copy = bytes;
+ bp = buffer;
+ for (i = 0; i < count; ++i)
+ {
+#define min(a, b) ((a) > (b) ? (b) : (a))
+ size_t copy = min (vector[i].iov_len, to_copy);
+ memcpy (bp, vector[i].iov_base, copy);
+ bp += copy;
+ to_copy -= copy;
+ if (to_copy == 0)
+ break;
+ }
+ return SSL_write (ssl, buffer, bytes);
+}
+
+
+#endif /* HAVE_SSL */
--- /dev/null
+/* tls.h --- TLSv1 functions
+ Copyright (C) 2000 Kenichi Okada <okada@opaopa.org>
+
+ Author: Kenichi Okada <okada@opaopa.org>
+ Created: 2000-02-22
+
+ Keywords: TLS, OpenSSL
+
+ Commentary:
+
+ [RFC 2246] "The TLS Protocol Version 1.0"
+ by Christopher Allen <callen@certicom.com> and
+ Tim Dierks <tdierks@certicom.com> (1999/01)
+
+ [RFC 2595] "Using TLS with IMAP, POP3 and ACAP"
+ by Chris Newman <chris.newman@innosoft.com> (1999/06)
+
+*/
+
+#ifdef HAVE_SSL
+
+#ifndef TLS_H
+#define TLS_H
+
+#include <openssl/lhash.h>
+#include <openssl/bn.h>
+#include <openssl/err.h>
+#include <openssl/pem.h>
+#include <openssl/rand.h>
+#include <openssl/x509.h>
+#include <openssl/ssl.h>
+
+/* init tls engine */
+int tls_init_serverengine(int verifydepth, /* depth to verify */
+ int askcert, /* 1 = verify client */
+ int requirecert, /* 1 = another client verify? */
+ char *tls_CAfile,
+ char *tls_CApath,
+ char *tls_cert_file,
+ char *tls_key_file);
+
+/* init tls */
+void tls_init(void);
+
+/* start tls negotiation */
+int tls_start_servertls(int readfd, int writefd);
+
+ssize_t SSL_writev (SSL *ssl, const struct iovec *vector, int count);
+
+#endif /* CYRUSTLS_H */
+
+#endif /* HAVE_SSL */
--- /dev/null
+/* $Revision: 6124 $
+**
+** User and post tracking database.
+*/
+
+#include "config.h"
+#include "clibrary.h"
+
+#include "inn/innconf.h"
+#include "nnrpd.h"
+
+#define MAX_LEN 180
+
+/* TrackClient determines whether or not
+ we are interested in tracking the activities
+ of the currently connected host. We have to
+ rely on an external process to set up the
+ entries in the database though which makes
+ this only as reliable as the process that
+ sets this up...
+*/
+
+/* Format of the input line is <host>:<username>
+*/
+
+int TrackClient(char *client, char *user)
+{
+ int RARTon;
+ FILE *fd;
+ char line[MAX_LEN],*p,*pp,*lp;
+ char *dbfile;
+
+ dbfile = concatpath(innconf->pathetc, "nnrpd.track");
+
+ RARTon=false;
+ strcpy(user, "unknown");
+
+ if ((fd=fopen(dbfile,"r"))!=NULL) {
+ while((fgets(line,(MAX_LEN - 1),fd))!=NULL) {
+ if (line[0] == '#' || line[0] == '\n') continue;
+ if ((p=strchr(line,' ')) != NULL) *p='\0';
+ if ((p=strchr(line,'\n')) != NULL) *p='\0';
+ if ((p=strchr(line,':')) != NULL) {
+ *p++='\0';
+ } else {
+ p=NULL;
+ }
+ pp=line;
+ if ((lp=strchr(pp,'*')) != NULL) {
+ pp=++lp;
+ }
+ if (strstr(client,pp)!=NULL) {
+ RARTon=true;
+ if (p != NULL)
+ strcpy(user,p);
+ break;
+ }
+ }
+ fclose(fd);
+ } else {
+ RARTon=false;
+ syslog(L_NOTICE, "%s No logging - can't read %s", ClientHost, dbfile);
+ }
+
+ free(dbfile);
+ return RARTon;
+}
--- /dev/null
+## $Id: INN.py 7897 2008-06-22 18:04:31Z iulius $
+##
+## This module supplies stub Python functions corresponding to the ones
+## provided by innd. It is not used by the server; it is only here so
+## that you can test your filter scripts before loading.
+## See the INN Python Filtering and Authentication Hooks documentation
+## for more information.
+
+from types import *
+
+def set_filter_hook(anObject):
+ if type(anObject) == InstanceType:
+ print "** set_filter_hook for " + repr(anObject)
+ else:
+ print "** <Your object is not a class instance.>"
+
+def addhist(messageid):
+ print "** addhist Message-ID: " + messageid
+
+def havehist(messageid):
+ print "** havehist Message-ID: " + messageid
+
+def cancel(messageid):
+ print "** cancel Message-ID: " + messageid
+
+def newsgroup(groupname):
+ print "** newsgroup: " + groupname
+
+def head(messageid):
+ print "** head Message-ID: " + messageid
+
+def article(messageid):
+ print "** article Message-ID: " + messageid
+
+def hashstring(mystring):
+ print "** hash: " + mystring
+
+def syslog(level, message):
+ print "-- syslog level: %s message: %s" % (level, message)
--- /dev/null
+## $Id: Makefile 6299 2003-04-20 19:04:14Z vinocur $
+##
+## All the actual installation work of any files in the samples directory
+## is done via the site directory, so that one can maintain one's news
+## configuration in the site directory and use make commands to update the
+## server automatically. All this Makefile does is run fixscript on a few
+## files that don't need the full power of configure (and clean up after
+## them on make clean).
+
+include ../Makefile.global
+
+top = ..
+
+ALL = nnrpd_auth.pl nnrpd_access.pl \
+ nnrpd_auth_wrapper.pl nnrpd_access_wrapper.pl
+
+EXTRA = inn.conf innreport.conf newsfeeds sasl.conf
+
+all: $(ALL) $(EXTRA)
+
+clean:
+ rm -f $(ALL)
+
+clobber distclean: clean
+ rm -f $(EXTRA)
+
+install:
+depend:
+
+profiled: all
+
+$(EXTRA) $(FIXSCRIPT):
+ @echo Run configure before running make. See INSTALL for details.
+ @exit 1
+
+
+## Build rules.
+
+FIX = $(FIXSCRIPT)
+
+nnrpd_auth.pl: nnrpd_auth.pl.in $(FIX) ; $(FIX) $@.in
+nnrpd_access.pl: nnrpd_access.pl.in $(FIX) ; $(FIX) $@.in
+nnrpd_auth_wrapper.pl: nnrpd_auth_wrapper.pl.in $(FIX) ; $(FIX) $@.in
+nnrpd_access_wrapper.pl: nnrpd_access_wrapper.pl.in $(FIX) ; $(FIX) $@.in
--- /dev/null
+control 0000000000 0000000001 n
+control.cancel 0000000000 0000000001 n
+control.checkgroups 0000000000 0000000001 n
+control.newgroup 0000000000 0000000001 n
+control.rmgroup 0000000000 0000000001 n
+junk 0000000000 0000000001 n
--- /dev/null
+# $Id: actsync.cfg 7081 2004-12-22 04:20:47Z rra $
+
+host=ftp.isc.org
+ftppath=/pub/usenet/CONFIG/active.gz
+flags=-v 0 -p 80
+ignore_file=actsync.ign
--- /dev/null
+# $Id: actsync.ign 7079 2004-12-22 04:20:14Z rra $
+#
+# Sample actsync ignore_file.
+
+# For now by default do not sync
+#
+i *
+
+# sync on the 8 majors
+#
+c comp.*
+c humanities.*
+c misc.*
+c news.*
+c rec.*
+c sci.*
+c soc.*
+c talk.*
+
+# don't compare to.* groups as they will differ
+#
+i to.*
+
+# we always want our special top level groups
+#
+i control
+i general
+i junk
+i test
+i to
--- /dev/null
+#
+# overview buffer configuration file
+#
+# The order in this items appear in this file is not important
+
+# Format:
+# index(0-65535) : path to buffer file :
+# length of buffer in kilobytes in decimal (1KB = 1024 bytes)
+
+0:/var/news/spool/overview/OV1:1536000
+1:/var/news/spool/overview/OV2:1536000
--- /dev/null
+## control.ctl - Access control for control messages.
+## Last modified: 2008-03-05
+##
+## Based on rone's unified control.ctl file.
+##
+## For a web presentation of the information recorded here, as well as
+## other useful information about Usenet hierarchies, please see:
+##
+## <http://usenet.trigofacile.com/hierarchies/>
+##
+## Please copy usenet-config@isc.org on any updates to this file so that
+## it can be updated in the INN CVS repository and on ftp.isc.org. For
+## changes to a public hierarchy, please also post the changes to
+## news.admin.hierarchies.
+##
+## The canonical version of this file can be found in the latest INN
+## release and at <ftp://ftp.isc.org/pub/usenet/CONFIG/control.ctl>; these
+## two files will be kept in sync. Please refer to the latest version of
+## this file for the most up-to-date hierarchy control information and
+## please use the latest version if you intend to carry all hierarchies.
+##
+## You may wish to review and change the policy for alt.*, free.*,
+## it-alt.*, and oesterreich.* below before using this file on your
+## server.
+##
+## Format:
+## <message>:<from>:<newsgroups>:<action>
+##
+## <message> Control message or "all" if it applies to all control
+## messages.
+## <from> Pattern that must match the From line.
+## <newsgroups> Pattern that must match the newsgroup being newgroup'd
+## or rmgroup'd (ignored for other messages).
+## <action> What to do:
+## doit Perform action
+## drop Ignore message
+## log One line to error log
+## mail Send mail to admin
+## verify-pgp_userid Do PGP verification on user.
+## All actions except drop and mail can be given a log
+## location by following the action with an = and the
+## log ("mail" says to mail the admin, an empty location
+## tosses the log information, and a relative path xxx
+## logs to $LOG/xxx.log).
+##
+## The *last* matching entry is used. See the expire.ctl(5) man page for
+## complete information.
+##
+## This file follows the following policies:
+##
+## * Most unknown or invalid control messages no longer result in mail.
+## This is due to the proliferation of forged control messages filling
+## up mailboxes. Some news servers even get overwhelmed with trying to
+## log failure, so unsigned control messages for hierarchies that use
+## PGP are simply dropped.
+##
+## * The assumption is that you now have PGP on your system. If you
+## don't, you should get it to help protect yourself against all the
+## control message forgeries. See <ftp://ftp.isc.org/pub/pgpcontrol/>.
+## PGP control message verification comes with all versions of INN since
+## 1.5, but you will need to install either PGP or GnuPG; see the
+## installation instructions for your news server.
+##
+## If for some reason you can't use PGP, search for the *PGP* comments
+## and modify the control lines to change "verify-..." in the action
+## field to "mail" or "doit=mail" or "doit=<log file>" or whatever you
+## prefer (replacing <log file> with the name of an appropriate log
+## file).
+##
+## * A number of hierarchies are for local use only but have leaked out
+## into the general stream. In this config file, they are set so that
+## the groups will be easy to remove, and are marked with a comment of
+## *LOCAL* (for use by that organization only, not generally
+## distributed), *DEFUNCT* (a hierarchy that's no longer used), or
+## *PRIVATE* (should only be carried after making arrangements with the
+## given contact address). Please delete all groups in those
+## hierarchies from your server if you carry them, unless you've
+## contacted the listed contact address and arranged a feed.
+##
+## If you have permission to carry any of the hierarchies so listed in
+## this file, you should change the entries for those hierarchies below.
+##
+## The comments of this file aren't in any formal or well-defined syntax,
+## but they are meant to use a consistent syntax to allow eventual parsing
+## by scripts into a better database format. Please follow the syntax of
+## existing entries when providing new ones. The recognized "fields" are
+## Contact (contact e-mail address), Admin group (the administrative group
+## for the hierarchy), URL, Key URL (URL for PGP key), Key fingerprint, Key
+## mail (address to mail for PGP key), and Syncable server (for actsync or
+## a similar tool).
+##
+## Names used in this file that cannot be encoded in 7bit ASCII are in
+## UTF-8. The only non-7bit-ASCII content is in comments.
+
+## -------------------------------------------------------------------------
+## DEFAULT
+## -------------------------------------------------------------------------
+
+# Default to dropping control messages that aren't recognized to allow
+# people to experiment without inadvertently mailbombing news admins.
+all:*:*:drop
+
+## -------------------------------------------------------------------------
+## CHECKGROUPS MESSAGES
+## -------------------------------------------------------------------------
+
+# Default to mailing all checkgroups messages to the administrator.
+checkgroups:*:*:mail
+
+## -------------------------------------------------------------------------
+## MISCELLANEOUS CONTROL MESSAGES
+## -------------------------------------------------------------------------
+
+# Mostly only used for UUCP feeds, very rarely used these days.
+ihave:*:*:drop
+sendme:*:*:drop
+
+# Request to send a copy of the newsfeeds file, intended for mapping
+# projects. Almost never used for anything other than mailbombing now.
+sendsys:*:*:log=sendsys
+
+# Request to send the server's path entry. Not particularly useful.
+senduuname:*:*:log=senduuname
+
+# Request to send the server's version number.
+version:*:*:log=version
+
+## -------------------------------------------------------------------------
+## NEWGROUP/RMGROUP MESSAGES
+## -------------------------------------------------------------------------
+
+## Default (for any group)
+newgroup:*:*:mail
+rmgroup:*:*:mail
+
+## A.BSU (*DEFUNCT* -- Ball State University, USA)
+# This hierarchy is defunct. Please remove it.
+newgroup:*:a.bsu.*:mail
+rmgroup:*:a.bsu.*:doit
+
+## ACS & OSU (*LOCAL* -- Ohio State University, USA)
+# Contact: Albert J. School <school.1@osu.edu>
+# Contact: Harpal Chohan <chohan+@osu.edu>
+# For local use only, contact the above address for information.
+newgroup:*:acs.*|osu.*:mail
+rmgroup:*:acs.*|osu.*:doit
+
+## ADASS (Astronomical Data Analysis Software and Systems)
+# URL: http://iraf.noao.edu/iraf/web/adass_news.html
+checkgroups:news@iraf.noao.edu:adass.*:doit
+newgroup:news@iraf.noao.edu:adass.*:doit
+rmgroup:news@iraf.noao.edu:adass.*:doit
+
+## AHN (Athens-Clarke County, Georgia, USA)
+checkgroups:greg@*.ucns.uga.edu:ahn.*:doit
+newgroup:greg@*.ucns.uga.edu:ahn.*:doit
+rmgroup:greg@*.ucns.uga.edu:ahn.*:doit
+
+## AIOE (Aioe.org)
+# Contact: usenet@aioe.org
+# URL: http://news.aioe.org/hierarchy/
+# Admin group: aioe.system
+# Key URL: http://news.aioe.org/hierarchy/aioe.txt
+# Key fingerprint = 2203 1AAC 51E7 C7FD 664F 1D80 90DF 6C71 2322 A7F8
+# Syncable server: nntp.aioe.org
+# *PGP* See comment at top of file.
+newgroup:*:aioe.*:drop
+rmgroup:*:aioe.*:drop
+checkgroups:usenet@aioe.org:aioe.*:verify-usenet@aioe.org
+newgroup:usenet@aioe.org:aioe.*:verify-usenet@aioe.org
+rmgroup:usenet@aioe.org:aioe.*:verify-usenet@aioe.org
+
+## AIR (*DEFUNCT* -- Stanford University, USA)
+# Contact: news@news.stanford.edu
+# This hierarchy is defunct. Please remove it.
+newgroup:*:air.*:mail
+rmgroup:*:air.*:doit
+
+## AKR (Akron, Ohio, USA)
+checkgroups:red@redpoll.mrfs.oh.us:akr.*:doit
+newgroup:red@redpoll.mrfs.oh.us:akr.*:doit
+rmgroup:red@redpoll.mrfs.oh.us:akr.*:doit
+
+## ALABAMA & HSV (Huntsville, Alabama, USA)
+# Contact: jpc@suespammers.org
+# Admin group: alabama.config
+# *PGP* See comment at top of file.
+newgroup:*:alabama.*|hsv.*:drop
+rmgroup:*:alabama.*|hsv.*:drop
+checkgroups:jpc@suespammers.org:alabama.*|hsv.*:verify-alabama-group-admin
+newgroup:jpc@suespammers.org:alabama.*|hsv.*:verify-alabama-group-admin
+rmgroup:jpc@suespammers.org:alabama.*|hsv.*:verify-alabama-group-admin
+
+## ALIVE (*DEFUNCT* -- ?)
+# Contact: thijs@kink.xs4all.nl
+# This hierarchy is defunct. Please remove it.
+newgroup:*:alive.*:mail
+rmgroup:*:alive.*:doit
+
+## ALT
+#
+# Accept all newgroups (except ones forged from Big 8 newgroup issuers,
+# who never issue alt.* control messages) and silently ignore all
+# rmgroups.
+#
+# What policy to use for alt.* groups varies widely from site to site.
+# For a small site, it is strongly recommended that this policy be changed
+# to drop all newgroups and rmgroups for alt.*. The local news admin can
+# then add new alt.* groups only on user request. Tons of alt.* newgroups
+# are sent out regularly with the intent more to create nonsense entries
+# in active files than to actually create a useable newsgroup. The admin
+# may still want to check the control message archive, as described below.
+#
+# Quality, user-desirable new groups can often be discovered by a quick
+# perusal of recent alt.* newgroup messages after discarding obvious junk
+# groups. One good initial filter is to check the archive of control
+# messages for a requested group to see if a syntactically valid newgroup
+# message was issued. Many of the junk control messages are invalid and
+# won't be archived, and many sites will only add alt.* groups with valid
+# control messages. To check the archive, see if:
+#
+# ftp://ftp.isc.org/pub/usenet/control/alt/<group-name>.gz
+#
+# exists (replacing <group-name> with the name of the group) and read the
+# first and last few control messages to see if the newsgroup should be
+# moderated. (Some alt.* groups that should be moderated are created
+# unmoderated by hijackers to try to damage the newsgroup.)
+#
+# Be aware that there is no official, generally accepted alt.* policy and
+# all information about alt.* groups available is essentially someone's
+# opinion, including these comments. There are nearly as many different
+# policies with regard to alt.* groups as there are Usenet sites.
+#
+newgroup:*:alt.*:doit
+newgroup:group-admin@isc.org:alt.*:drop
+newgroup:tale@*uu.net:alt.*:drop
+rmgroup:*:alt.*:drop
+
+## AR (Argentina)
+checkgroups:jorge_f@nodens.fisica.unlp.edu.ar:ar.*:doit
+newgroup:jorge_f@nodens.fisica.unlp.edu.ar:ar.*:doit
+rmgroup:jorge_f@nodens.fisica.unlp.edu.ar:ar.*:doit
+
+## ARC (*LOCAL* -- NASA Ames Research Center, USA)
+# Contact: news@arc.nasa.gov
+# For local use only, contact the above address for information.
+newgroup:*:arc.*:mail
+rmgroup:*:arc.*:doit
+
+## ARKANE (Arkane Systems, UK)
+# Contact: newsbastard@arkane.demon.co.uk
+checkgroups:newsbastard@arkane.demon.co.uk:arkane.*:doit
+newgroup:newsbastard@arkane.demon.co.uk:arkane.*:doit
+rmgroup:newsbastard@arkane.demon.co.uk:arkane.*:doit
+
+## AT (Austria)
+# URL: http://www.usenet.at/
+# Admin group: at.usenet.gruppen
+# Key URL: http://www.usenet.at/pgpkey.asc
+# *PGP* See comment at top of file.
+newgroup:*:at.*:drop
+rmgroup:*:at.*:drop
+checkgroups:control@usenet.backbone.at:at.*:verify-control@usenet.backbone.at
+newgroup:control@usenet.backbone.at:at.*:verify-control@usenet.backbone.at
+rmgroup:control@usenet.backbone.at:at.*:verify-control@usenet.backbone.at
+
+## AUS (Australia)
+# Contact: ausadmin@aus.news-admin.org
+# URL: http://aus.news-admin.org/
+# Admin group: aus.net.news
+# Key URL: http://aus.news-admin.org/ausadmin.asc
+# *PGP* See comment at top of file.
+newgroup:*:aus.*:drop
+rmgroup:*:aus.*:drop
+checkgroups:ausadmin@aus.news-admin.org:aus.*:verify-ausadmin@aus.news-admin.org
+newgroup:ausadmin@aus.news-admin.org:aus.*:verify-ausadmin@aus.news-admin.org
+rmgroup:ausadmin@aus.news-admin.org:aus.*:verify-ausadmin@aus.news-admin.org
+
+## AUSTIN (Austin, Texas, USA)
+# URL: http://frenzy.austin.tx.us/austin/
+# Admin group: austin.usenet.config
+checkgroups:chip@unicom.com:austin.*:doit
+checkgroups:fletcher@cs.utexas.edu:austin.*:doit
+checkgroups:pug@pug.net:austin.*:doit
+newgroup:chip@unicom.com:austin.*:doit
+newgroup:fletcher@cs.utexas.edu:austin.*:doit
+newgroup:pug@pug.net:austin.*:doit
+rmgroup:chip@unicom.com:austin.*:doit
+rmgroup:fletcher@cs.utexas.edu:austin.*:doit
+rmgroup:pug@pug.net:austin.*:doit
+
+## AZ (Arizona, USA)
+checkgroups:system@asuvax.eas.asu.edu:az.*:doit
+newgroup:system@asuvax.eas.asu.edu:az.*:doit
+rmgroup:system@asuvax.eas.asu.edu:az.*:doit
+
+## BA (San Francisco Bay Area, USA)
+# Contact: ba-mod@nas.nasa.gov
+# URL: http://ennui.org/ba/
+# Admin group: ba.news.config
+# Key URL: http://ennui.org/ba/ba-mod.asc
+# *PGP* See comment at top of file.
+newgroup:*:ba.*:drop
+rmgroup:*:ba.*:drop
+checkgroups:ba-mod@nas.nasa.gov:ba.*:verify-ba.news.config
+newgroup:ba-mod@nas.nasa.gov:ba.*:verify-ba.news.config
+rmgroup:ba-mod@nas.nasa.gov:ba.*:verify-ba.news.config
+
+## BACKBONE (*LOCAL* -- ruhr.de/ruhrgebiet.individual.net in Germany)
+# Contact: admin@ruhr.de
+# For local use only, contact the above address for information.
+newgroup:*:backbone.*:mail
+rmgroup:*:backbone.*:doit
+
+## BC (British Columbia, Canada)
+checkgroups:bc_van_usenet@fastmail.ca:bc.*:doit
+newgroup:bc_van_usenet@fastmail.ca:bc.*:doit
+rmgroup:bc_van_usenet@fastmail.ca:bc.*:doit
+
+## BDA (German groups?)
+checkgroups:news@*netuse.de:bda.*:doit
+newgroup:news@*netuse.de:bda.*:doit
+rmgroup:news@*netuse.de:bda.*:doit
+
+## BE (Belgique/Belgie/Belgien/Belgium)
+# Contact: be-hierarchy-admin@usenet.be
+# URL: http://usenet.be/
+# Admin group: be.announce
+# Key URL: http://usenet.be/be.announce.newgroups.asc
+# Key fingerprint = 30 2A 45 94 70 DE 1F D5 81 8C 58 64 D2 F7 08 71
+# *PGP* See comment at top of file.
+newgroup:*:be.*:drop
+rmgroup:*:be.*:drop
+checkgroups:group-admin@usenet.be:be.*:verify-be.announce.newgroups
+newgroup:group-admin@usenet.be:be.*:verify-be.announce.newgroups
+rmgroup:group-admin@usenet.be:be.*:verify-be.announce.newgroups
+
+## BELWUE (Baden-Wuerttemberg, Germany)
+# Admin group: belwue.infos
+# *PGP* See comment at top of file.
+newgroup:*:belwue.*:drop
+rmgroup:*:belwue.*:drop
+checkgroups:news@news.belwue.de:belwue.*:verify-belwue-hir-control
+newgroup:news@news.belwue.de:belwue.*:verify-belwue-hir-control
+rmgroup:news@news.belwue.de:belwue.*:verify-belwue-hir-control
+
+## BERMUDA (Bermuda)
+checkgroups:news@*ibl.bm:bermuda.*:doit
+newgroup:news@*ibl.bm:bermuda.*:doit
+rmgroup:news@*ibl.bm:bermuda.*:doit
+
+## BES (*PRIVATE* -- Beijing Electron Spectrometer)
+# Contact: news@news.stanford.edu
+# For private use only, contact the above address for information.
+newgroup:news@news.stanford.edu:bes.*:mail
+rmgroup:news@news.stanford.edu:bes.*:doit
+
+## BEST (*LOCAL* -- Best Internet Communications, Inc.)
+# Contact: news@best.net
+# For local use only, contact the above address for information.
+newgroup:*:best.*:mail
+rmgroup:*:best.*:doit
+
+## BIONET (Biology Network)
+# URL: http://www.bio.net/
+# Admin group: bionet.general
+# Key fingerprint = EB C0 F1 BA 26 0B C6 D6 FB 8D ED C4 AE 5D 10 54
+# *PGP* See comment at top of file.
+newgroup:*:bionet.*:drop
+rmgroup:*:bionet.*:drop
+checkgroups:Biosci-control-key@net.bio.net:bionet.*:verify-Biosci-control-key@net.bio.net
+newgroup:Biosci-control-key@net.bio.net:bionet.*:verify-Biosci-control-key@net.bio.net
+rmgroup:Biosci-control-key@net.bio.net:bionet.*:verify-Biosci-control-key@net.bio.net
+
+## BIRK (*LOCAL* -- University of Oslo, Norway)
+# Contact: birk-admin@ping.uio.no
+# For local use only, contact the above address for information.
+newgroup:*:birk.*:mail
+rmgroup:*:birk.*:doit
+
+## BIT (Gatewayed Mailing lists)
+# URL: http://www.newsadmin.com/bit/bit.htm
+# Admin group: bit.admin
+# *PGP* See comment at top of file.
+newgroup:*:bit.*:drop
+rmgroup:*:bit.*:drop
+checkgroups:bit@newsadmin.com:bit.*:verify-bit@newsadmin.com
+newgroup:bit@newsadmin.com:bit.*:verify-bit@newsadmin.com
+rmgroup:bit@newsadmin.com:bit.*:verify-bit@newsadmin.com
+
+## BIZ (Business Groups)
+checkgroups:edhew@xenitec.on.ca:biz.*:doit
+newgroup:edhew@xenitec.on.ca:biz.*:doit
+rmgroup:edhew@xenitec.on.ca:biz.*:doit
+
+## BLGTN (Bloomington, In, USA)
+checkgroups:control@news.bloomington.in.us:blgtn.*:doit
+newgroup:control@news.bloomington.in.us:blgtn.*:doit
+rmgroup:control@news.bloomington.in.us:blgtn.*:doit
+
+## BLN (Berlin, Germany)
+checkgroups:news@*fu-berlin.de:bln.*:doit
+newgroup:news@*fu-berlin.de:bln.*:doit
+rmgroup:news@*fu-berlin.de:bln.*:doit
+
+## BNE (Brisbane, Australia)
+# Contact: ausadmin@aus.news-admin.org
+# URL: http://bne.news-admin.org/
+# Key URL: http://aus.news-admin.org/ausadmin.asc
+# *PGP* See comment at top of file.
+newgroup:*:bne.*:drop
+rmgroup:*:bne.*:drop
+checkgroups:ausadmin@aus.news-admin.org:bne.*:verify-ausadmin@aus.news-admin.org
+newgroup:ausadmin@aus.news-admin.org:bne.*:verify-ausadmin@aus.news-admin.org
+rmgroup:ausadmin@aus.news-admin.org:bne.*:verify-ausadmin@aus.news-admin.org
+
+## BOFH (*PRIVATE* -- Bastard Operator From Hell)
+# Contact: myname@myhost.mydomain.com
+# For private use only, contact the above address for information.
+newgroup:*:bofh.*:mail
+rmgroup:*:bofh.*:doit
+
+## CA (California, USA)
+# Contact: ikluft@thunder.sbay.org
+# URL: http://www.sbay.org/ca/
+checkgroups:ikluft@thunder.sbay.org:ca.*:doit
+newgroup:ikluft@thunder.sbay.org:ca.*:doit
+rmgroup:ikluft@thunder.sbay.org:ca.*:doit
+
+## CAIS (*LOCAL* -- Capital Area Internet Services)
+# Contact: news@cais.com
+# For local use only, contact the above address for information.
+newgroup:*:cais.*:mail
+rmgroup:*:cais.*:doit
+
+## CALSTATE (California State University)
+checkgroups:*@*calstate.edu:calstate.*:doit
+newgroup:*@*calstate.edu:calstate.*:doit
+rmgroup:*@*calstate.edu:calstate.*:doit
+
+## CANB (Canberra, Australia)
+# Contact: ausadmin@aus.news-admin.org
+# URL: http://canb.news-admin.org/
+# Key URL: http://aus.news-admin.org/ausadmin.asc
+# *PGP* See comment at top of file.
+newgroup:*:canb.*:drop
+rmgroup:*:canb.*:drop
+checkgroups:ausadmin@aus.news-admin.org:canb.*:verify-ausadmin@aus.news-admin.org
+newgroup:ausadmin@aus.news-admin.org:canb.*:verify-ausadmin@aus.news-admin.org
+rmgroup:ausadmin@aus.news-admin.org:canb.*:verify-ausadmin@aus.news-admin.org
+
+## CAPDIST (Albany, The Capital District, New York, USA)
+checkgroups:danorton@albany.net:capdist.*:doit
+newgroup:danorton@albany.net:capdist.*:doit
+rmgroup:danorton@albany.net:capdist.*:doit
+
+## CARLETON (Carleton University, Canada)
+newgroup:news@cunews.carleton.ca:carleton.*:doit
+newgroup:news@cunews.carleton.ca:carleton*class.*:mail
+rmgroup:news@cunews.carleton.ca:carleton.*:doit
+
+## CD-ONLINE (*LOCAL* -- ?)
+# Contact: newsmaster@worldonline.nl
+# For local use only, contact the above address for information.
+newgroup:*:cd-online.*:mail
+rmgroup:*:cd-online.*:doit
+
+## CENTRAL (*LOCAL* -- The Internet Company of New Zealand, Wellington, NZ)
+# Contact: usenet@iconz.co.nz
+# For local use only, contact the above address for information.
+newgroup:*:central.*:mail
+rmgroup:*:central.*:doit
+
+## CERN (*PRIVATE* -- CERN European Laboratory for Particle Physics)
+# Contact: Dietrich Wiegandt <News.Support@cern.ch>
+# For private use only, contact the above address for information.
+newgroup:News.Support@cern.ch:cern.*:mail
+rmgroup:News.Support@cern.ch:cern.*:doit
+
+## CH (Switzerland)
+# Contact: ch-news-admin@use-net.ch
+# URL: http://www.use-net.ch/Usenet/
+# Key URL: http://www.use-net.ch/Usenet/adminkey.html
+# Key fingerprint = 71 80 D6 8C A7 DE 2C 70 62 4A 48 6E D9 96 02 DF
+# *PGP* See comment at top of file.
+newgroup:*:ch.*:drop
+rmgroup:*:ch.*:drop
+checkgroups:felix.rauch@nice.ch:ch.*:verify-ch-news-admin@use-net.ch
+newgroup:felix.rauch@nice.ch:ch.*:verify-ch-news-admin@use-net.ch
+rmgroup:felix.rauch@nice.ch:ch.*:verify-ch-news-admin@use-net.ch
+
+## CHAVEN (*LOCAL* -- Celestian Haven ISP, Midwest, USA)
+# Contact: news@chaven.com
+# For local use only, contact the above address for information.
+newgroup:*:chaven.*:mail
+rmgroup:*:chaven.*:doit
+
+## CHI (Chicago, USA)
+# URL: http://lull.org/pub/chi-newsgroup-faq
+checkgroups:lisbon@*chi.il.us:chi.*:doit
+newgroup:lisbon@*chi.il.us:chi.*:doit
+rmgroup:lisbon@*chi.il.us:chi.*:doit
+
+## CHILE (Chile and Chilean affairs)
+# Contact: mod-cga@usenet.cl
+# URL: http://www.usenet.cl/
+# Admin group: chile.grupos.anuncios
+checkgroups:mod-cga@*lj.cl:chile.*:doit
+newgroup:mod-cga@*lj.cl:chile.*:doit
+rmgroup:mod-cga@*lj.cl:chile.*:doit
+
+## CHINESE (China and Chinese language groups)
+checkgroups:pinghua@stat.berkeley.edu:chinese.*:doit
+newgroup:pinghua@stat.berkeley.edu:chinese.*:doit
+rmgroup:pinghua@stat.berkeley.edu:chinese.*:doit
+
+## CHRISTNET (Christian Discussion)
+checkgroups:news@fdma.com:christnet.*:doit
+newgroup:news@fdma.com:christnet.*:doit
+rmgroup:news@fdma.com:christnet.*:doit
+
+## CL (*PRIVATE* -- CL-Netz, German)
+# Contact: koordination@cl-netz.de
+# URL: http://www.cl-netz.de/
+# Key URL: http://www.cl-netz.de/control.txt
+# For private use only, contact above address for questions.
+# *PGP* See comment at top of file.
+newgroup:*:cl.*:drop
+rmgroup:*:cl.*:doit
+# The following three lines are only for authorized cl.* sites.
+#checkgroups:koordination@cl-netz.de:cl.*:verify-cl.netz.infos
+#newgroup:koordination@cl-netz.de:cl.*:verify-cl.netz.infos
+#rmgroup:koordination@cl-netz.de:cl.*:verify-cl.netz.infos
+
+## CLARI (*PRIVATE* -- Features and News, available on a commercial basis)
+# Contact: support@clari.net
+# Admin group: clari.net.admin
+# Key URL: http://www.clari.net/tech/clarikey.txt
+# For private use only, contact the above address for information.
+# *PGP* See comment at top of file.
+newgroup:*:clari.*:drop
+rmgroup:*:clari.*:drop
+newgroup:cl*@clarinet.com:clari.*:mail
+rmgroup:cl*@clarinet.com:clari.*:verify-ClariNet.Group
+
+## CMI (*LOCAL* -- Champaign County, IL, USA)
+# Contact: news@ks.uiuc.edu
+# For local use only, contact the above address for information.
+newgroup:*:cmi.*:mail
+rmgroup:*:cmi.*:doit
+
+## CMU (*LOCAL* -- Carnegie-Mellon University, Pennsylvania, USA)
+# Contact: Daniel Edward Lovinger <del+@CMU.EDU>
+# For local use only, contact the above address for information.
+newgroup:*:cmu.*:mail
+rmgroup:*:cmu.*:doit
+
+## CN (China)
+# URL: http://news.yaako.com/
+# Admin group: cn.announce
+# Key fingerprint = 62 97 EE 33 F7 16 25 C1 A4 9E 47 BA C5 3E 5E 9E
+# *PGP* See comment at top of file.
+newgroup:*:cn.*:drop
+rmgroup:*:cn.*:drop
+checkgroups:control@bentium.com:cn.*:verify-cn.admin.news.announce
+newgroup:control@bentium.com:cn.*:verify-cn.admin.news.announce
+rmgroup:control@bentium.com:cn.*:verify-cn.admin.news.announce
+
+## CN.BBS (China)
+# URL: http://bbs.cn.news-admin.org/
+# Admin group: cn.bbs.admin.announce
+# *PGP* See comment at top of file.
+newgroup:*:cn.bbs.*:drop
+rmgroup:*:cn.bbs.*:drop
+checkgroups:control@cn-bbs.org:cn.bbs.*:verify-cn.bbs.admin.announce
+newgroup:control@cn-bbs.org:cn.bbs.*:verify-cn.bbs.admin.announce
+rmgroup:control@cn-bbs.org:cn.bbs.*:verify-cn.bbs.admin.announce
+
+## CO (Colorado, USA)
+# Contact: coadmin@boyznoyz.com (Bill of Denver)
+checkgroups:coadmin@boyznoyz.com:co.*:doit
+newgroup:coadmin@boyznoyz.com:co.*:doit
+rmgroup:coadmin@boyznoyz.com:co.*:doit
+
+## CODEWARRIOR (CodeWarrior discussion)
+checkgroups:news@supernews.net:codewarrior.*:doit
+newgroup:news@supernews.net:codewarrior.*:doit
+rmgroup:news@supernews.net:codewarrior.*:doit
+
+## COMP, HUMANITIES, MISC, NEWS, REC, SCI, SOC, TALK (The Big Eight)
+# Contact: board@big-8.org
+# URL: http://www.big-8.org/
+# Admin group: news.announce.newgroups
+# Key fingerprint = F5 35 58 D3 55 64 10 14 07 C6 95 53 13 6F D4 07
+# *PGP* See comment at top of file.
+newgroup:*:comp.*|humanities.*|misc.*|news.*|rec.*|sci.*|soc.*|talk.*:drop
+rmgroup:*:comp.*|humanities.*|misc.*|news.*|rec.*|sci.*|soc.*|talk.*:drop
+checkgroups:group-admin@isc.org:comp.*|humanities.*|misc.*|news.*|rec.*|sci.*|soc.*|talk.*:verify-news.announce.newgroups
+newgroup:group-admin@isc.org:comp.*|humanities.*|misc.*|news.*|rec.*|sci.*|soc.*|talk.*:verify-news.announce.newgroups
+rmgroup:group-admin@isc.org:comp.*|humanities.*|misc.*|news.*|rec.*|sci.*|soc.*|talk.*:verify-news.announce.newgroups
+
+## COMPUTER42 (Computer 42, Germany)
+# Contact: Dirk Schmitt <news@computer42.org>
+checkgroups:news@computer42.org:computer42.*:doit
+newgroup:news@computer42.org:computer42.*:doit
+rmgroup:news@computer42.org:computer42.*:doit
+
+## CONCORDIA (Concordia University, Montreal, Canada)
+# Contact: newsmaster@concordia.ca
+# URL: General University info at http://www.concordia.ca/
+checkgroups:news@newsflash.concordia.ca:concordia.*:doit
+newgroup:news@newsflash.concordia.ca:concordia.*:doit
+rmgroup:news@newsflash.concordia.ca:concordia.*:doit
+
+## COURTS (*DEFUNCT* -- Court discussion)
+# Contact: trier@ins.cwru.edu
+# This hierarchy is defunct. Please remove it.
+newgroup:*:courts.*:mail
+rmgroup:*:courts.*:doit
+
+## CPCUIIA (Chartered Prop. Casulty Underwriter/Insurance Institute of America)
+# Contact: miller@cpcuiia.org
+# URL: http://www.aicpcu.org/
+checkgroups:miller@cpcuiia.org:cpcuiia.*:doit
+newgroup:miller@cpcuiia.org:cpcuiia.*:doit
+rmgroup:miller@cpcuiia.org:cpcuiia.*:doit
+
+## CU (*LOCAL* -- University of Colorado)
+# Contact: Doreen Petersen <news@colorado.edu>
+# For local use only, contact the above address for information.
+newgroup:*:cu.*:mail
+rmgroup:*:cu.*:doit
+
+## CUHK (*LOCAL* -- Chinese University of Hong Kong)
+# Contact: shlam@ie.cuhk.edu.hk (Alan S H Lam)
+# For local use only, contact the above address for information.
+newgroup:*:cuhk.*:mail
+rmgroup:*:cuhk.*:doit
+
+## CZ (Czech Republic)
+# URL: ftp://ftp.vslib.cz/pub/news/config/cz/newsgroups (text)
+checkgroups:petr.kolar@vslib.cz:cz.*:doit
+newgroup:petr.kolar@vslib.cz:cz.*:doit
+rmgroup:petr.kolar@vslib.cz:cz.*:doit
+
+## DC (Washington, D.C., USA)
+checkgroups:news@mattress.atww.org:dc.*:doit
+newgroup:news@mattress.atww.org:dc.*:doit
+rmgroup:news@mattress.atww.org:dc.*:doit
+
+## DE (German language)
+# Contact: moderator@dana.de
+# URL: http://www.dana.de/mod/
+# Admin group: de.admin.news.announce
+# Key URL: http://www.dana.de/mod/pgp/dana.asc
+# Key fingerprint = 5B B0 52 88 BF 55 19 4F 66 7D C2 AE 16 26 28 25
+# *PGP* See comment at top of file.
+newgroup:*:de.*:drop
+rmgroup:*:de.*:drop
+checkgroups:moderator@dana.de:de.*:verify-de.admin.news.announce
+newgroup:moderator@dana.de:de.*:verify-de.admin.news.announce
+rmgroup:moderator@dana.de:de.*:verify-de.admin.news.announce
+
+## DE.ALT (German language alternative hierarchy)
+# *PGP* See comment at top of file.
+newgroup:*:de.alt.*:doit
+rmgroup:moderator@dana.de:de.alt.*:verify-de.admin.news.announce
+
+## DFW (Dallas/Fort Worth, Texas, USA)
+# URL: http://www.cirr.com/dfw/
+# Admin group: dfw.usenet.config
+checkgroups:eric@*cirr.com:dfw.*:doit
+newgroup:eric@*cirr.com:dfw.*:doit
+rmgroup:eric@*cirr.com:dfw.*:doit
+
+## DK (Denmark)
+# URL: http://www.usenet.dk/dk-admin/
+# Key URL: http://www.usenet.dk/dk-admin/pubkey.html
+# Key fingerprint = 7C B2 C7 50 F3 7D 5D 73 8C EE 2E 3F 55 80 72 FF
+# *PGP* See comment at top of file.
+newgroup:*:dk.*:drop
+rmgroup:*:dk.*:drop
+checkgroups:news@news.dknet.dk:dk.*:verify-news@news.dknet.dk
+newgroup:news@news.dknet.dk:dk.*:verify-news@news.dknet.dk
+rmgroup:news@news.dknet.dk:dk.*:verify-news@news.dknet.dk
+
+## DUKE (*LOCAL* -- Duke University, USA)
+# Contact: news@newsgate.duke.edu
+# For local use only, contact the above address for information.
+newgroup:*:duke.*:mail
+rmgroup:*:duke.*:doit
+
+## EASYNET (Easynet PLC, UK)
+# Contact: Christiaan Keet <newsmaster@easynet.net>
+# Admin group: easynet.support
+# *PGP* See comment at top of file.
+newgroup:*:easynet.*:drop
+rmgroup:*:easynet.*:drop
+checkgroups:newsmaster@easynet.net:easynet.*:verify-easynet.news
+newgroup:newsmaster@easynet.net:easynet.*:verify-easynet.news
+rmgroup:newsmaster@easynet.net:easynet.*:verify-easynet.news
+
+## EE (Estonia)
+# Contact: usenet@news.ut.ee
+# URL: http://news.ut.ee/
+# Key URL: http://news.ut.ee/pubkey.asc
+# *PGP* See comment at top of file.
+newgroup:*:ee.*:drop
+rmgroup:*:ee.*:drop
+checkgroups:news@news.ut.ee:ee.*:verify-ee.news
+newgroup:news@news.ut.ee:ee.*:verify-ee.news
+rmgroup:news@news.ut.ee:ee.*:verify-ee.news
+
+## EFN & EUG (Eugene Free Computer Network, Eugene/Springfield, Oregon, USA)
+# Admin group: eug.config
+# *PGP* See comment at top of file.
+newgroup:*:efn.*|eug.*:drop
+rmgroup:*:efn.*|eug.*:drop
+checkgroups:newsadmin@efn.org:efn.*|eug.*:verify-eug.config
+newgroup:newsadmin@efn.org:efn.*|eug.*:verify-eug.config
+rmgroup:newsadmin@efn.org:efn.*|eug.*:verify-eug.config
+
+## EHIME-U (? University, Japan ?)
+checkgroups:news@cc.nias.ac.jp:ehime-u.*:doit
+checkgroups:news@doc.dpc.ehime-u.ac.jp:ehime-u.*:doit
+newgroup:news@cc.nias.ac.jp:ehime-u.*:doit
+newgroup:news@doc.dpc.ehime-u.ac.jp:ehime-u.*:doit
+rmgroup:news@cc.nias.ac.jp:ehime-u.*:doit
+rmgroup:news@doc.dpc.ehime-u.ac.jp:ehime-u.*:doit
+
+## ENGLAND (England)
+# Contact: admin@england.news-admin.org
+# Admin group: england.news.policy
+# Key fingerprint = DA 3E C2 01 46 E5 61 CB A2 43 09 CA 13 6D 31 1F
+# *PGP* See comment at top of file.
+newgroup:*:england.*:drop
+rmgroup:*:england.*:drop
+checkgroups:admin@england.news-admin.org:england.*:verify-england-usenet
+newgroup:admin@england.news-admin.org:england.*:verify-england-usenet
+rmgroup:admin@england.news-admin.org:england.*:verify-england-usenet
+
+## ES (Spain)
+# Contact: moderador@corus-es.org
+# URL: http://www.corus-es.org/docs/es_newsadmins_faq.txt
+# Admin group: es.news.anuncios
+# Key URL: http://www.corus-es.org/docs/esnews.asc
+# *PGP* See comment at top of file.
+newgroup:*:es.*:drop
+rmgroup:*:es.*:drop
+checkgroups:moderador@corus-es.org:es.*:verify-es.news
+newgroup:moderador@corus-es.org:es.*:verify-es.news
+rmgroup:moderador@corus-es.org:es.*:verify-es.news
+
+## ESP (Spanish-language newsgroups)
+# Contact: <mod-ena@ennui.org>
+# URL: http://ennui.org/esp/
+# Key URL: http://ennui.org/esp/mod-ena.asc
+# *PGP* See comment at top of file.
+newgroup:*:esp.*:drop
+rmgroup:*:esp.*:drop
+checkgroups:mod-ena@ennui.org:esp.*:verify-esp.news.administracion
+newgroup:mod-ena@ennui.org:esp.*:verify-esp.news.administracion
+rmgroup:mod-ena@ennui.org:esp.*:verify-esp.news.administracion
+
+## EUNET (Europe)
+checkgroups:news@noc.eu.net:eunet.*:doit
+newgroup:news@noc.eu.net:eunet.*:doit
+rmgroup:news@noc.eu.net:eunet.*:doit
+
+## EUROPA (Europe)
+# URL: http://www.europa.usenet.eu.org/
+# Admin group: europa.usenet.admin
+# Key URL: http://www.europa.usenet.eu.org/pgp/index.html
+# Key fingerprint = 3A 05 A8 49 FB 16 29 25 75 E3 DE BB 69 E0 1D B4
+# *PGP* See comment at top of file.
+newgroup:*:europa.*:drop
+rmgroup:*:europa.*:drop
+checkgroups:group-admin@usenet.eu.org:europa.*:verify-group-admin@usenet.eu.org
+newgroup:group-admin@usenet.eu.org:europa.*:verify-group-admin@usenet.eu.org
+rmgroup:group-admin@usenet.eu.org:europa.*:verify-group-admin@usenet.eu.org
+
+## EXAMPLE (Bogus hierarchy reserved for standards documents)
+newgroup:*:example.*:mail
+rmgroup:*:example.*:doit
+
+## FA (Gated mailing lists)
+# This hierarchy was removed in the "Great Renaming" of 1988.
+#
+# A site in Norway is currently (as of 2002) gatewaying various mailing
+# lists into fa.* newsgroups, but that site does not appear to be issuing
+# any control messages for those groups.
+#
+newgroup:*:fa.*:mail
+rmgroup:*:fa.*:doit
+
+## FFM (Frankfurt/M., Germany)
+# URL: http://ffm.arcornews.de/
+# Key URL: ftp://ftp.arcor-online.net/pub/news/PGPKEY.FFM
+# *PGP* See comment at top of file.
+newgroup:*:ffm.*:drop
+rmgroup:*:ffm.*:drop
+checkgroups:ffm.admin@arcor.de:ffm.*:verify-ffm.admin
+newgroup:ffm.admin@arcor.de:ffm.*:verify-ffm.admin
+rmgroup:ffm.admin@arcor.de:ffm.*:verify-ffm.admin
+
+## FIDO (FidoNet)
+checkgroups:root@mbh.org:fido.*:doit
+newgroup:root@mbh.org:fido.*:doit
+rmgroup:root@mbh.org:fido.*:doit
+
+## FIDO.BELG (Belgian FidoNet)
+# Admin group: fido.belg.news
+# *PGP* See comment at top of file.
+newgroup:*:fido.belg.*:drop
+rmgroup:*:fido.belg.*:drop
+checkgroups:fidobelg@mail.z2.fidonet.org:fido.belg.*:verify-fido.belg.news
+newgroup:fidobelg@mail.z2.fidonet.org:fido.belg.*:verify-fido.belg.news
+rmgroup:fidobelg@mail.z2.fidonet.org:fido.belg.*:verify-fido.belg.news
+
+## FIDO.GER (German FIDO Net Echos)
+# URL: ftp://ftp.fu-berlin.de/doc/news/fido.ger/fido.ger-info.english
+# Key URL: ftp://ftp.fu-berlin.de/doc/news/fido.ger/PGP-Key
+# *PGP* See comment at top of file.
+newgroup:*:fido.ger.*:drop
+rmgroup:*:fido.ger.*:drop
+checkgroups:fido.ger@news.fu-berlin.de:fido.ger.*:verify-fido.ger@news.fu-berlin.de
+newgroup:fido.ger@news.fu-berlin.de:fido.ger.*:verify-fido.ger@news.fu-berlin.de
+rmgroup:fido.ger@news.fu-berlin.de:fido.ger.*:verify-fido.ger@news.fu-berlin.de
+
+## FIDO7 (Russian FidoNet)
+# URL: http://www.fido7.ru/
+# Admin group: fido7.postmasters
+# Key URL: http://www.fido7.ru/pgpcontrol.html
+# *PGP* See comment at top of file.
+newgroup:*:fido7.*:drop
+rmgroup:*:fido7.*:drop
+checkgroups:newgroups-request@fido7.ru:fido7.*:verify-fido7.announce.newgroups
+newgroup:newgroups-request@fido7.ru:fido7.*:verify-fido7.announce.newgroups
+rmgroup:newgroups-request@fido7.ru:fido7.*:verify-fido7.announce.newgroups
+
+## FINET (Finland and Finnish language alternative newsgroups)
+checkgroups:*@*.fi:finet.*:doit
+newgroup:*@*.fi:finet.*:doit
+rmgroup:*@*.fi:finet.*:doit
+
+## FJ (Japan and Japanese language)
+# Contact: committee@fj-news.org
+# URL: http://www.fj-news.org/index.html.en
+# Admin group: fj.news.announce
+# Key URL: http://www.is.tsukuba.ac.jp/~yas/fj/fj.asc
+# *PGP* See comment at top of file.
+newgroup:*:fj.*:drop
+rmgroup:*:fj.*:drop
+checkgroups:committee@fj-news.org:fj.*:verify-fj.news.announce
+newgroup:committee@fj-news.org:fj.*:verify-fj.news.announce
+rmgroup:committee@fj-news.org:fj.*:verify-fj.news.announce
+
+## FL (Florida, USA)
+checkgroups:hgoldste@news1.mpcs.com:fl.*:doit
+checkgroups:scheidell@fdma.fdma.com:fl.*:doit
+newgroup:hgoldste@news1.mpcs.com:fl.*:doit
+newgroup:scheidell@fdma.fdma.com:fl.*:doit
+rmgroup:hgoldste@news1.mpcs.com:fl.*:doit
+rmgroup:scheidell@fdma.fdma.com:fl.*:doit
+
+## FLORA (FLORA Community WEB, Canada)
+# Contact: russell@flora.org
+# Admin group: flora.general
+# *PGP* See comment at top of file.
+newgroup:*:flora.*:drop
+rmgroup:*:flora.*:drop
+checkgroups:news@flora.ottawa.on.ca:flora.*:verify-flora-news
+newgroup:news@flora.ottawa.on.ca:flora.*:verify-flora-news
+rmgroup:news@flora.ottawa.on.ca:flora.*:verify-flora-news
+
+## FR (French language)
+# URL: http://www.usenet-fr.news.eu.org/
+# Admin group: fr.usenet.forums.annonces
+# Key URL: http://www.usenet-fr.news.eu.org/fur/usenet/presentation-fr.html
+# *PGP* See comment at top of file.
+newgroup:*:fr.*:drop
+rmgroup:*:fr.*:drop
+checkgroups:control@usenet-fr.news.eu.org:fr.*:verify-control@usenet-fr.news.eu.org
+newgroup:control@usenet-fr.news.eu.org:fr.*:verify-control@usenet-fr.news.eu.org
+rmgroup:control@usenet-fr.news.eu.org:fr.*:verify-control@usenet-fr.news.eu.org
+
+## FRANCE (France)
+# Contact: control@usenet-france.news.eu.org
+# Admin group: france.admin.evolutions
+# *PGP* See comment at top of file.
+newgroup:*:france.*:drop
+rmgroup:*:france.*:drop
+checkgroups:control@usenet-france.news.eu.org:france.*:verify-control@usenet-france.news.eu.org
+newgroup:control@usenet-france.news.eu.org:france.*:verify-control@usenet-france.news.eu.org
+rmgroup:control@usenet-france.news.eu.org:france.*:verify-control@usenet-france.news.eu.org
+
+## FREE (Open Hierarchy where anyone can create a group)
+newgroup:*:free.*:doit
+newgroup:group-admin@isc.org:free.*:drop
+newgroup:tale@*uu.net:free.*:drop
+rmgroup:*:free.*:drop
+
+## FUDAI (Japanese ?)
+checkgroups:news@picard.cs.osakafu-u.ac.jp:fudai.*:doit
+newgroup:news@picard.cs.osakafu-u.ac.jp:fudai.*:doit
+rmgroup:news@picard.cs.osakafu-u.ac.jp:fudai.*:doit
+
+## FUR (*PRIVATE* -- furrynet)
+# Contact: fur-config@news.furry.net
+# For private use only, contact the above address for information.
+newgroup:*:fur.*:mail
+rmgroup:*:fur.*:doit
+
+## GER & HANNET & HANNOVER & HILDESHEIM & HISS (Hannover, Germany)
+checkgroups:fifi@hiss.han.de:ger.*|hannover.*|hannet.*|hildesheim.*|hiss.*:doit
+newgroup:fifi@hiss.han.de:ger.*|hannover.*|hannet.*|hildesheim.*|hiss.*:doit
+rmgroup:fifi@hiss.han.de:ger.*|hannover.*|hannet.*|hildesheim.*|hiss.*:doit
+
+## GIT (Georgia Institute of Technology, USA)
+newgroup:news@news.gatech.edu:git.*:doit
+newgroup:news@news.gatech.edu:git*class.*:mail
+rmgroup:news@news.gatech.edu:git.*:doit
+
+## GNU (Free Software Foundation)
+# URL: http://www.gnu.org/usenet/usenet.html
+# Admin group: gnu.gnusenet.config
+# Key URL: http://www.gnu.org/usenet/usenet-pgp-key.txt
+# *PGP* See comment at top of file.
+newgroup:*:gnu.*:drop
+rmgroup:*:gnu.*:drop
+checkgroups:usenet@gnu.org:gnu.*:verify-usenet@gnu.org
+newgroup:usenet@gnu.org:gnu.*:verify-usenet@gnu.org
+rmgroup:usenet@gnu.org:gnu.*:verify-usenet@gnu.org
+
+## GNUU (*PRIVATE* -- GNUU e.V., Oberursel, Germany)
+# Contact: news@gnuu.de
+# For private use only, contact the above address for information.
+newgroup:*:gnuu.*:mail
+rmgroup:*:gnuu.*:doit
+
+## GOV (Government Information)
+# Admin group: gov.usenet.announce
+# *PGP* See comment at top of file.
+newgroup:*:gov.*:drop
+rmgroup:*:gov.*:drop
+checkgroups:gov-usenet-announce-moderator@govnews.org:gov.*:verify-gov.usenet.announce
+newgroup:gov-usenet-announce-moderator@govnews.org:gov.*:verify-gov.usenet.announce
+rmgroup:gov-usenet-announce-moderator@govnews.org:gov.*:verify-gov.usenet.announce
+
+## GWU (George Washington University, Washington, DC)
+# Contact: Sweth Chandramouli <news@nit.gwu.edu>
+checkgroups:news@nit.gwu.edu:gwu.*:doit
+newgroup:news@nit.gwu.edu:gwu.*:doit
+rmgroup:news@nit.gwu.edu:gwu.*:doit
+
+## HAMBURG (City of Hamburg, Germany)
+# Contact: hamburg@steering-group.net
+# URL: http://www.steering-group.net/hamburg/
+# Admin group: hamburg.koordination
+# Key URL: http://www.steering-group.net/hamburg/hamburg.koordination.txt
+# Key fingerprint = 3E E7 0C BB 6E 01 94 EE 45 6F C5 57 F4 B9 54 8E
+# *PGP* See comment at top of file.
+newgroup:*:hamburg.*:drop
+rmgroup:*:hamburg.*:drop
+checkgroups:hamburg@steering-group.net:hamburg.*:verify-hamburg.koordination
+newgroup:hamburg@steering-group.net:hamburg.*:verify-hamburg.koordination
+rmgroup:hamburg@steering-group.net:hamburg.*:verify-hamburg.koordination
+
+## HAMILTON (Canadian)
+checkgroups:news@*dcss.mcmaster.ca:hamilton.*:doit
+newgroup:news@*dcss.mcmaster.ca:hamilton.*:doit
+rmgroup:news@*dcss.mcmaster.ca:hamilton.*:doit
+
+## HAMSTER (Hamster, a Win32 news and mail proxy server)
+# Contact: hamster-contact@snafu.de
+# Admin group: hamster.de.config
+# Key fingerprint = 12 75 A9 42 8A D6 1F 77 6A CF B4 0C 79 15 5F 93
+# *PGP* See comment at top of file.
+newgroup:*:hamster.*:drop
+rmgroup:*:hamster.*:drop
+checkgroups:hamster-control@snafu.de:hamster.*:verify-hamster-control@snafu.de
+newgroup:hamster-control@snafu.de:hamster.*:verify-hamster-control@snafu.de
+rmgroup:hamster-control@snafu.de:hamster.*:verify-hamster-control@snafu.de
+
+## HAN (Korean Hangul)
+# Contact: newgroups-request@usenet.or.kr
+# Admin group: han.news.admin
+# Key URL: ftp://ftp.usenet.or.kr/pub/korea/usenet/pgp/PGPKEY.han
+# *PGP* See comment at top of file.
+newgroup:*:han.*:drop
+rmgroup:*:han.*:drop
+checkgroups:newgroups-request@usenet.or.kr:han.*:verify-han.news.admin
+newgroup:newgroups-request@usenet.or.kr:han.*:verify-han.news.admin
+rmgroup:newgroups-request@usenet.or.kr:han.*:verify-han.news.admin
+
+## HARVARD (*LOCAL* -- Harvard University, Cambridge, MA)
+# For local use only.
+newgroup:*@*.harvard.edu:harvard.*:mail
+rmgroup:*@*.harvard.edu:harvard.*:doit
+
+## HAWAII (Hawaii, USA)
+checkgroups:news@lava.net:hawaii.*:doit
+newgroup:news@lava.net:hawaii.*:doit
+rmgroup:news@lava.net:hawaii.*:doit
+
+## HFX (Halifax, Nova Scotia)
+checkgroups:stevemackie@gmail.com:hfx.*:doit
+newgroup:stevemackie@gmail.com:hfx.*:doit
+rmgroup:stevemackie@gmail.com:hfx.*:doit
+
+## HIV (HIVNET Foundation, for HIV+/AIDS information)
+# Contact: news@hivnet.org
+# Admin group: hiv.config
+# Key fingerprint = 5D D6 0E DC 1E 2D EA 0B B0 56 4D D6 52 53 D7 A4
+# *PGP* See comment at top of file.
+newgroup:*:hiv.*:drop
+rmgroup:*:hiv.*:drop
+checkgroups:news@hivnet.org:hiv.*:verify-news@hivnet.org
+newgroup:news@hivnet.org:hiv.*:verify-news@hivnet.org
+rmgroup:news@hivnet.org:hiv.*:verify-news@hivnet.org
+
+## HK (Hong Kong)
+checkgroups:hknews@comp.hkbu.edu.hk:hk.*:doit
+newgroup:hknews@comp.hkbu.edu.hk:hk.*:doit
+rmgroup:hknews@comp.hkbu.edu.hk:hk.*:doit
+
+## HOUSTON (Houston, Texas, USA)
+# Admin group: houston.usenet.config
+# *PGP* See comment at top of file.
+newgroup:*:houston.*:drop
+rmgroup:*:houston.*:drop
+checkgroups:news@academ.com:houston.*:verify-houston.usenet.config
+newgroup:news@academ.com:houston.*:verify-houston.usenet.config
+rmgroup:news@academ.com:houston.*:verify-houston.usenet.config
+
+## HR (Croatian language)
+# Contact: newsmaster@carnet.hr
+# URL: http://newsfeed.carnet.hr/control/
+# Admin group: hr.news.admin
+# Key URL: http://newsfeed.carnet.hr/control/key.txt
+# Key fingerprint = 0EE5 74FB 1C40 7ADB 0AAC A52F 7192 1BA3 ED63 AD9A
+# Syncable server: news.carnet.hr
+# *PGP* See comment at top of file.
+newgroup:*:hr.*:drop
+rmgroup:*:hr.*:drop
+checkgroups:newsmaster@carnet.hr:hr.*:verify-newsmaster@carnet.hr
+newgroup:newsmaster@carnet.hr:hr.*:verify-newsmaster@carnet.hr
+rmgroup:newsmaster@carnet.hr:hr.*:verify-newsmaster@carnet.hr
+
+## HUMANITYQUEST (Humanities discussion)
+# Contact: news-admin@humanityquest.com
+# URL: http://www.humanityquest.com/projects/newsgroups/
+# Key URL: http://www.humanityquest.com/projects/newsgroups/PGP.htm
+# Key fingerprint = BA3D B306 B6F5 52AA BA8F 32F0 8C4F 5040 16F9 C046
+# *PGP* See comment at top of file.
+newgroup:*:humanityquest.*:drop
+rmgroup:*:humanityquest.*:drop
+checkgroups:news-admin@humanityquest.com:humanityquest.*:verify-humanityquest.admin.config
+newgroup:news-admin@humanityquest.com:humanityquest.*:verify-humanityquest.admin.config
+rmgroup:news-admin@humanityquest.com:humanityquest.*:verify-humanityquest.admin.config
+
+## HUN (Hungary)
+# URL: http://www.sztaki.hu/~kissg/news/hiteles.html
+# Admin group: hun.admin.news
+# Key URL: http://gatling.ikk.sztaki.hu/~kissg/news/hun.admin.news.asc
+# *PGP* See comment at top of file.
+newgroup:*:hun.*:drop
+rmgroup:*:hun.*:drop
+checkgroups:hun-mnt@news.sztaki.hu:hun.*:verify-hun.admin.news
+newgroup:hun-mnt@news.sztaki.hu:hun.*:verify-hun.admin.news
+rmgroup:hun-mnt@news.sztaki.hu:hun.*:verify-hun.admin.news
+
+## IA (Iowa, USA)
+checkgroups:skunz@iastate.edu:ia.*:doit
+newgroup:skunz@iastate.edu:ia.*:doit
+rmgroup:skunz@iastate.edu:ia.*:doit
+
+## IBMNET (*LOCAL* -- ?)
+# Contact: news@ibm.net
+# For local use only, contact the above address for information.
+newgroup:*:ibmnet.*:mail
+rmgroup:*:ibmnet.*:doit
+
+## ICONZ (*LOCAL* -- The Internet Company of New Zealand, New Zealand)
+# Contact: usenet@iconz.co.nz
+# For local use only, contact the above address for information.
+newgroup:*:iconz.*:mail
+rmgroup:*:iconz.*:doit
+
+## IDOCTRA (Idoctra Translation Software, Translation Discussion)
+# Contact: support@idoctra.com
+checkgroups:support@idoctra.com:idoctra.*:doit
+newgroup:support@idoctra.com:idoctra.*:doit
+rmgroup:support@idoctra.com:idoctra.*:doit
+
+## IE (Ireland)
+# Contact: control@usenet.ie
+# Admin group: ie.news.group
+# *PGP* See comment at top of file.
+newgroup:*:ie.*:drop
+rmgroup:*:ie.*:drop
+checkgroups:control@usenet.ie:ie.*:verify-control@usenet.ie
+newgroup:control@usenet.ie:ie.*:verify-control@usenet.ie
+rmgroup:control@usenet.ie:ie.*:verify-control@usenet.ie
+
+## IEEE (*DEFUNCT* -- Institute of Electrical and Electronic Engineers)
+# Contact: postoffice@ieee.org
+# This hierarchy is defunct. Please remove it.
+newgroup:*:ieee.*:mail
+rmgroup:*:ieee.*:doit
+
+## INFO (Gatewayed mailing lists)
+checkgroups:rjoyner@uiuc.edu:info.*:doit
+newgroup:rjoyner@uiuc.edu:info.*:doit
+rmgroup:rjoyner@uiuc.edu:info.*:doit
+
+## IS (Iceland)
+# Contact: IS Group Admins <group-admin@usenet.is>
+# URL: http://www.usenet.is/
+# Admin group: is.isnet
+# Key URL: http://www.usenet.is/group-admin.asc
+# Key fingerprint = 33 32 8D 46 1E 5E 1C 7F 48 60 8E 72 E5 3E CA EA
+# *PGP* See comment at top of file.
+newgroup:*:is.*:drop
+rmgroup:*:is.*:drop
+checkgroups:group-admin@usenet.is:is.*:verify-group-admin@usenet.is
+newgroup:group-admin@usenet.is:is.*:verify-group-admin@usenet.is
+rmgroup:group-admin@usenet.is:is.*:verify-group-admin@usenet.is
+
+## ISC (Japanese ?)
+checkgroups:news@sally.isc.chubu.ac.jp:isc.*:doit
+newgroup:news@sally.isc.chubu.ac.jp:isc.*:doit
+rmgroup:news@sally.isc.chubu.ac.jp:isc.*:doit
+
+## ISRAEL & IL (Israel)
+newgroup:news@news.biu.ac.il:israel.*:doit
+rmgroup:news@news.biu.ac.il:israel.*|il.*:doit
+
+## ISU (I-Shou University, Taiwan)
+# Contact: news@news.isu.edu.tw
+# URL: http://news.isu.edu.tw/
+# Admin group: isu.newgroups
+# Key URL: http://news.isu.edu.tw/isu.asc
+# *PGP* See comment at top of file.
+newgroup:*:isu.*:drop
+rmgroup:*:isu.*:drop
+checkgroups:news@news.isu.edu.tw:isu.*:verify-news@news.isu.edu.tw
+newgroup:news@news.isu.edu.tw:isu.*:verify-news@news.isu.edu.tw
+rmgroup:news@news.isu.edu.tw:isu.*:verify-news@news.isu.edu.tw
+
+## IT (Italian)
+# Contact: gcn@news.nic.it
+# URL: http://www.news.nic.it/
+# Admin group: it.news.annunci
+# Key URL: http://www.news.nic.it/pgp.txt
+# Key fingerprint = 94 A4 F7 B5 46 96 D6 C7 A6 73 F2 98 C4 8C D0 E0
+# *PGP* See comment at top of file.
+newgroup:*:it.*:drop
+rmgroup:*:it.*:drop
+checkgroups:gcn@news.nic.it:it.*:verify-gcn@news.nic.it
+newgroup:gcn@news.nic.it:it.*:verify-gcn@news.nic.it
+rmgroup:gcn@news.nic.it:it.*:verify-gcn@news.nic.it
+
+## IT-ALT (Alternate Italian)
+#
+# There is no one official control message issuer for the it-alt.*
+# hierarchy, so this file doesn't choose any particular one. Several
+# different people issue control messages for this hierarchy, which may
+# or may not agree, and sites carrying this hierarchy are encouraged to
+# pick one and add it below.
+#
+# Newgroup and removal requests are to be posted to it-alt.config. A list
+# of people issuing PGP/GPG signed control messages is available in a
+# periodic posting to news.admin.hierarchies and it-alt.config.
+#
+newgroup:*:it-alt.*:drop
+rmgroup:*:it-alt.*:drop
+
+## ITALIA (Italy)
+# Contact: news@news.cineca.it
+# URL: http://news.cineca.it/italia/
+# Admin group: italia.announce.newgroups
+# Key URL: http://news.cineca.it/italia/italia-pgp.txt
+# Key fingerprint = 0F BB 71 62 DA 5D 5D B8 D5 86 FC 28 02 67 1A 6B
+# *PGP* See comment at top of file.
+newgroup:*:italia.*:drop
+rmgroup:*:italia.*:drop
+checkgroups:news@news.cineca.it:italia.*:verify-italia.announce.newgroups
+newgroup:news@news.cineca.it:italia.*:verify-italia.announce.newgroups
+rmgroup:news@news.cineca.it:italia.*:verify-italia.announce.newgroups
+
+## IU (Indiana University)
+newgroup:news@usenet.ucs.indiana.edu:iu.*:doit
+newgroup:root@usenet.ucs.indiana.edu:iu.*:doit
+newgroup:*@usenet.ucs.indiana.edu:iu*class.*:mail
+rmgroup:news@usenet.ucs.indiana.edu:iu.*:doit
+rmgroup:root@usenet.ucs.indiana.edu:iu.*:doit
+
+## JAPAN (Japan)
+# Contact: Tsuneo Tanaka <tt+null@efnet.com>
+# URL: http://www.asahi-net.or.jp/~AE5T-KSN/japan-e.html
+# Admin group: japan.admin.announce
+# Key URL: http://grex.cyberspace.org/~tt/japan.admin.announce.asc
+# Key fingerprint = 6A FA 19 47 69 1B 10 74 38 53 4B 1B D8 BA 3E 85
+# *PGP* See comment at top of file.
+newgroup:*:japan.*:drop
+rmgroup:*:japan.*:drop
+checkgroups:japan.admin.announce@news.efnet.com:japan.*:verify-japan.admin.announce@news.efnet.com
+newgroup:japan.admin.announce@news.efnet.com:japan.*:verify-japan.admin.announce@news.efnet.com
+rmgroup:japan.admin.announce@news.efnet.com:japan.*:verify-japan.admin.announce@news.efnet.com
+
+## JLUG (Japan Linux Users Group)
+# Contact: news@linux.or.jp
+# URL: http://www.linux.or.jp/community/news/index.html
+# Admin group: jlug.config
+# Key URL: http://www.linux.or.jp/pgpkey/news
+# *PGP* See comment at top of file.
+newgroup:*:jlug.*:drop
+rmgroup:*:jlug.*:drop
+checkgroups:news@linux.or.jp:jlug.*:verify-news@linux.or.jp
+newgroup:news@linux.or.jp:jlug.*:verify-news@linux.or.jp
+rmgroup:news@linux.or.jp:jlug.*:verify-news@linux.or.jp
+
+## K12 (US Educational Network)
+# URL: http://www.k12groups.org/
+checkgroups:braultr@*csmanoirs.qc.ca:k12.*:doit
+newgroup:braultr@*csmanoirs.qc.ca:k12.*:doit
+rmgroup:braultr@*csmanoirs.qc.ca:k12.*:doit
+
+## KA (*PRIVATE* -- Karlsruhe, Germany)
+# Contact: usenet@karlsruhe.org
+# URL: http://www.karlsruhe.org/
+# Key URL: http://www.karlsruhe.org/pubkey-news.karlsruhe.org.asc
+# Key fingerprint = DE 19 BB 25 76 19 81 17 F0 67 D2 23 E8 C8 7C 90
+# For private use only, contact the above address for information.
+# *PGP* See comment at top of file.
+newgroup:*:ka.*:drop
+rmgroup:*:ka.*:drop
+# The following three lines are only for authorized ka.* sites.
+#checkgroups:usenet@karlsruhe.org:ka.*:verify-usenet@karlsruhe.org
+#newgroup:usenet@karlsruhe.org:ka.*:verify-usenet@karlsruhe.org
+#rmgroup:usenet@karlsruhe.org:ka.*:verify-usenet@karlsruhe.org
+
+## KANTO (?)
+# *PGP* See comment at top of file.
+rmgroup:*:kanto.*:drop
+checkgroups:ty@kamoi.imasy.or.jp:kanto.*:verify-kanto.news.network
+# NOTE: newgroups aren't verified...
+newgroup:*@*.jp:kanto.*:doit
+rmgroup:ty@kamoi.imasy.or.jp:kanto.*:verify-kanto.news.network
+
+## KASSEL (Kassel, Germany)
+# *PGP* See comment at top of file.
+newgroup:*:kassel.*:drop
+rmgroup:*:kassel.*:drop
+checkgroups:dirk.meyer@dinoex.sub.org:kassel.*:verify-kassel-admin
+newgroup:dirk.meyer@dinoex.sub.org:kassel.*:verify-kassel-admin
+rmgroup:dirk.meyer@dinoex.sub.org:kassel.*:verify-kassel-admin
+
+## KC (Kansas City, Kansas/Missouri, USA)
+checkgroups:dan@sky.net:kc.*:doit
+newgroup:dan@sky.net:kc.*:doit
+rmgroup:dan@sky.net:kc.*:doit
+
+## KGK (Administered by KGK, Japan)
+# Contact: Keiji KOSAKA <kgk@film.rlss.okayama-u.ac.jp>
+# URL: http://film.rlss.okayama-u.ac.jp/~kgk/kgk/index.html
+# Admin group: kgk.admin
+checkgroups:usenet@film.rlss.okayama-u.ac.jp:kgk.*:doit
+newgroup:usenet@film.rlss.okayama-u.ac.jp:kgk.*:doit
+rmgroup:usenet@film.rlss.okayama-u.ac.jp:kgk.*:doit
+
+## KIEL (Kiel, Germany)
+# URL: http://news.koehntopp.de/kiel/
+checkgroups:kris@koehntopp.de:kiel.*:doit
+newgroup:kris@koehntopp.de:kiel.*:doit
+rmgroup:kris@koehntopp.de:kiel.*:doit
+
+## KRST (*LOCAL* -- University of Oslo, Norway)
+# Contact: jani@ifi.uio.no
+# For local use only, contact the above address for information.
+newgroup:*:krst.*:mail
+rmgroup:*:krst.*:doit
+
+## KWNET (*LOCAL* -- Kitchener-Waterloo?)
+# Contact: Ed Hew <edhew@xenitec.on.ca>
+# For local use only, contact the above address for information.
+newgroup:*:kwnet.*:mail
+rmgroup:*:kwnet.*:doit
+
+## LAW (?)
+# Contact: Jim Burke <jburke@kentlaw.edu>
+checkgroups:*@*.kentlaw.edu:law.*:doit
+checkgroups:*@*.law.vill.edu:law.*:doit
+newgroup:*@*.kentlaw.edu:law.*:doit
+newgroup:*@*.law.vill.edu:law.*:doit
+rmgroup:*@*.kentlaw.edu:law.*:doit
+rmgroup:*@*.law.vill.edu:law.*:doit
+
+## LINUX (Gated Linux mailing lists)
+# Contact: Marco d'Itri <md@linux.it>
+# Admin group: linux.admin.news
+# Key fingerprint = 81 B3 27 99 4F CE 32 D1 1B C9 01 0D BB B3 2E 41
+# *PGP* See comment at top of file.
+newgroup:*:linux.*:drop
+rmgroup:*:linux.*:drop
+checkgroups:linux-admin@bofh.it:linux.*:verify-linux-admin@bofh.it
+newgroup:linux-admin@bofh.it:linux.*:verify-linux-admin@bofh.it
+rmgroup:linux-admin@bofh.it:linux.*:verify-linux-admin@bofh.it
+
+## LOCAL (Local-only groups)
+# It is not really a good idea for sites to use these since they may occur
+# on many unconnected sites.
+newgroup:*:local.*:mail
+rmgroup:*:local.*:drop
+
+## LUEBECK (Luebeck, Germany)
+# Contact: usenet@zybrkat.org
+# Admin group: luebeck.admin
+checkgroups:usenet@zybrkat.org:luebeck.*:doit
+newgroup:usenet@zybrkat.org:luebeck.*:doit
+rmgroup:usenet@zybrkat.org:luebeck.*:doit
+
+## MALTA (Nation of Malta)
+# Contact: cmeli@cis.um.edu.mt
+# URL: http://www.malta.news-admin.org/
+# Admin group: malta.config
+# Key URL: http://www.cis.um.edu.mt/news-malta/PGP.PUBLICKEY
+# Key fingerprint = 20 17 01 5C F0 D0 1A 42 E4 13 30 58 0B 14 48 A6
+# *PGP* See comment at top of file.
+newgroup:*:malta.*:drop
+rmgroup:*:malta.*:drop
+checkgroups:cmeli@cis.um.edu.mt:malta.*:verify-malta.config
+newgroup:cmeli@cis.um.edu.mt:malta.*:verify-malta.config
+rmgroup:cmeli@cis.um.edu.mt:malta.*:verify-malta.config
+
+## MANAWATU (*LOCAL* -- Manawatu district, New Zealand)
+# Contact: alan@manawatu.gen.nz or news@manawatu.gen.nz
+# For local use only, contact the above address for information.
+newgroup:*:manawatu.*:mail
+rmgroup:*:manawatu.*:doit
+
+## MAUS (MausNet, Germany)
+# Admin group: maus.info
+# Key fingerprint = 82 52 C7 70 26 B9 72 A1 37 98 55 98 3F 26 62 3E
+# *PGP* See comment at top of file.
+newgroup:*:maus.*:drop
+rmgroup:*:maus.*:drop
+checkgroups:guenter@gst0hb.hb.provi.de:maus.*:verify-maus-info
+checkgroups:guenter@gst0hb.north.de:maus.*:verify-maus-info
+newgroup:guenter@gst0hb.hb.provi.de:maus.*:verify-maus-info
+newgroup:guenter@gst0hb.north.de:maus.*:verify-maus-info
+rmgroup:guenter@gst0hb.hb.provi.de:maus.*:verify-maus-info
+rmgroup:guenter@gst0hb.north.de:maus.*:verify-maus-info
+
+## MCMASTER (*LOCAL* -- McMaster University, Ontario)
+# Contact: Brian Beckberger <news@informer1.cis.mcmaster.ca>
+# For local use only, contact the above address for information.
+newgroup:*:mcmaster.*:mail
+rmgroup:*:mcmaster.*:doit
+
+## MCOM (*LOCAL* -- Netscape Inc, USA)
+# For local use only.
+newgroup:*:mcom.*:mail
+rmgroup:*:mcom.*:doit
+
+## ME (Maine, USA)
+checkgroups:kerry@maine.maine.edu:me.*:doit
+newgroup:kerry@maine.maine.edu:me.*:doit
+rmgroup:kerry@maine.maine.edu:me.*:doit
+
+## MEDLUX (All-Russia medical teleconferences)
+# URL: ftp://ftp.medlux.ru/pub/news/medlux.grp
+checkgroups:neil@new*.medlux.ru:medlux.*:doit
+newgroup:neil@new*.medlux.ru:medlux.*:doit
+rmgroup:neil@new*.medlux.ru:medlux.*:doit
+
+## MELB (Melbourne, Australia)
+# Contact: ausadmin@aus.news-admin.org
+# URL: http://melb.news-admin.org/
+# Key URL: http://aus.news-admin.org/ausadmin.asc
+# *PGP* See comment at top of file.
+newgroup:*:melb.*:drop
+rmgroup:*:melb.*:drop
+checkgroups:ausadmin@aus.news-admin.org:melb.*:verify-ausadmin@aus.news-admin.org
+newgroup:ausadmin@aus.news-admin.org:melb.*:verify-ausadmin@aus.news-admin.org
+rmgroup:ausadmin@aus.news-admin.org:melb.*:verify-ausadmin@aus.news-admin.org
+
+## MENSA (The Mensa Organisation)
+# Contact: usenet@newsgate.mensa.org
+# Admin group: mensa.config
+# Key fingerprint = 52B9 3963 85D9 0806 8E19 7344 973C 5005 DC7D B7A7
+# *PGP* See comment at top of file.
+newgroup:*:mensa.*:drop
+rmgroup:*:mensa.*:drop
+checkgroups:usenet@newsgate.mensa.org:mensa.*:verify-mensa.config
+newgroup:usenet@newsgate.mensa.org:mensa.*:verify-mensa.config
+rmgroup:usenet@newsgate.mensa.org:mensa.*:verify-mensa.config
+
+## METOCEAN (ISP in Japan)
+checkgroups:fwataru@*.metocean.co.jp:metocean.*:doit
+newgroup:fwataru@*.metocean.co.jp:metocean.*:doit
+rmgroup:fwataru@*.metocean.co.jp:metocean.*:doit
+
+## METROPOLIS (*LOCAL* -- ?)
+# Contact: newsmaster@worldonline.nl
+# For local use only, contact the above address for information.
+newgroup:*:metropolis.*:mail
+rmgroup:*:metropolis.*:doit
+
+## MI (Michigan, USA)
+# Contact: Steve Simmons <scs@lokkur.dexter.mi.us>
+checkgroups:scs@lokkur.dexter.mi.us:mi.*:doit
+newgroup:scs@lokkur.dexter.mi.us:mi.*:doit
+rmgroup:scs@lokkur.dexter.mi.us:mi.*:doit
+
+## MICROSOFT (Microsoft Corporation, USA)
+#
+# Control articles for that hierarchy are not issued by Microsoft itself
+# but by a Usenet active participant in order to improve the quality of
+# the propagation of Microsoft newsgroups. Their official URL is:
+# http://www.microsoft.com/communities/newsgroups/list/en-us/default.aspx
+#
+# Contact: control-microsoft@trigofacile.com
+# URL: http://www.trigofacile.com/divers/usenet/clefs/index.htm
+# Admin group: microsoft.public.news.server
+# Key URL: http://www.trigofacile.com/divers/usenet/clefs/pgpkey-microsoft.asc
+# Key fingerprint = DF70 5FC9 F615 D52E 02DB A3CB 63A9 8D13 E60E 2FAA
+# Syncable server: msnews.microsoft.com
+# *PGP* See comment at top of file.
+newgroup:*:microsoft.*:drop
+rmgroup:*:microsoft.*:drop
+checkgroups:control-microsoft@trigofacile.com:microsoft.*:verify-control-microsoft@trigofacile.com
+newgroup:control-microsoft@trigofacile.com:microsoft.*:verify-control-microsoft@trigofacile.com
+rmgroup:control-microsoft@trigofacile.com:microsoft.*:verify-control-microsoft@trigofacile.com
+
+## MILW (Milwaukee, Wisconsin, USA)
+# Contact: milw@usenet.mil.wi.us
+# URL: http://usenet.mil.wi.us/
+# Admin group: milw.config
+# Key URL: http://usenet.mil.wi.us/pgpkey
+# Key fingerprint = 6E 9B 9F 70 98 AB 9C E5 C3 C0 05 82 21 5B F4 9E
+# *PGP* See comment at top of file.
+newgroup:*:milw.*:drop
+rmgroup:*:milw.*:drop
+checkgroups:milw@usenet.mil.wi.us:milw.*:verify-milw.config
+newgroup:milw@usenet.mil.wi.us:milw.*:verify-milw.config
+rmgroup:milw@usenet.mil.wi.us:milw.*:verify-milw.config
+
+## MOD (*DEFUNCT* -- Original top level moderated hierarchy)
+# This hierarchy is defunct. Please remove it.
+newgroup:*:mod.*:mail
+rmgroup:*:mod.*:doit
+
+## MUC (Munchen [Munich], Germany)
+# Admin group: muc.admin
+# Key fingerprint = 43 C7 0E 7C 45 C7 06 E0 BD 6F 76 CE 07 39 5E 66
+# *PGP* See comment at top of file.
+newgroup:*:muc.*:drop
+rmgroup:*:muc.*:drop
+checkgroups:muc-cmsg@muenchen.pro-bahn.org:muc.*:verify-muc.admin
+newgroup:muc-cmsg@muenchen.pro-bahn.org:muc.*:verify-muc.admin
+rmgroup:muc-cmsg@muenchen.pro-bahn.org:muc.*:verify-muc.admin
+
+## NAGASAKI-U (Nagasaki University, Japan ?)
+checkgroups:root@*nagasaki-u.ac.jp:nagasaki-u.*:doit
+newgroup:root@*nagasaki-u.ac.jp:nagasaki-u.*:doit
+rmgroup:root@*nagasaki-u.ac.jp:nagasaki-u.*:doit
+
+## NAS (*LOCAL* -- NAS, NASA Ames Research Center, USA)
+# Contact: news@nas.nasa.gov
+# For local use only, contact the above address for information.
+newgroup:*:nas.*:mail
+rmgroup:*:nas.*:doit
+
+## NASA (*LOCAL* -- National Aeronautics and Space Administration, USA)
+# Contact: news@nas.nasa.gov
+# For local use only, contact the above address for information.
+newgroup:*:nasa.*:mail
+rmgroup:*:nasa.*:doit
+
+## NC (North Carolina, USA)
+#
+# Tim Seaver <tas@bellsouth.net> says he hasn't had any dealings with nc.*
+# for over two years and the hierarchy is basically "open to anyone who
+# wants it."
+#
+# newgroup:tas@ncren.net:nc.*:doit
+# rmgroup:tas@ncren.net:nc.*:doit
+
+## NCF (*LOCAL* -- National Capital Freenet, Ottawa, Ontario, Canada)
+# Contact: news@freenet.carleton.ca
+# For local use only, contact the above address for information.
+newgroup:*:ncf.*:mail
+rmgroup:*:ncf.*:doit
+
+## NCTU (Taiwan)
+checkgroups:chen@cc.nctu.edu.tw:nctu.*:doit
+newgroup:chen@cc.nctu.edu.tw:nctu.*:doit
+rmgroup:chen@cc.nctu.edu.tw:nctu.*:doit
+
+## NCU (*LOCAL* -- National Central University, Taiwan)
+# Contact: Ying-Hao Chang <aqlott@db.csie.ncu.edu.tw>
+# Contact: <runn@news.ncu.edu.tw>
+# For local use only, contact the above address for information.
+newgroup:*:ncu.*:mail
+rmgroup:*:ncu.*:doit
+
+## NERSC (National Energy Research Scientific Computing Center)
+# Contact: <usenet@nersc.gov>
+# newgroup:*:nersc.*:mail
+# rmgroup:*:nersc.*:doit
+
+## NET (Usenet 2)
+#
+# This was a failed experiment in a different newsgroup creation policy and
+# administrative policy which has now been almost entirely abandoned. The
+# information is retained here for the few sites still using it, but sites
+# not already carrying the groups probably won't be interested.
+#
+# (This was also the original unmoderated Usenet hierarchy from before the
+# Great Renaming. The groups that used to be in net.* in the 1980s are now
+# in the Big Eight hierarchies.)
+#
+# URL: http://www.usenet2.org
+# Admin group: net.config
+# Key URL: http://www.usenet2.org/control@usenet2.org.asc
+# Key fingerprint = D7 D3 5C DB 18 6A 29 79 BF 74 D4 58 A3 78 9D 22
+# *PGP* See comment at top of file.
+newgroup:*:net.*:drop
+rmgroup:*:net.*:drop
+#checkgroups:control@usenet2.org:net.*:verify-control@usenet2.org
+#newgroup:control@usenet2.org:net.*:verify-control@usenet2.org
+#rmgroup:control@usenet2.org:net.*:verify-control@usenet2.org
+
+## NETINS (*LOCAL* -- netINS, Inc)
+# Contact: news@netins.net
+# For local use only, contact the above address for information.
+newgroup:*:netins.*:mail
+rmgroup:*:netins.*:doit
+
+## NETSCAPE (Netscape Communications Corp)
+# Contact: news@netscape.com
+# URL: http://www.mozilla.org/community.html
+# Admin group: netscape.public.admin
+# Key URL: http://www.mozilla.org/newsfeeds.html
+# Key fingerprint = B7 80 55 12 1F 9C 17 0B 86 66 AD 3B DB 68 35 EC
+# *PGP* See comment at top of file.
+newgroup:*:netscape.*:drop
+rmgroup:*:netscape.*:drop
+checkgroups:news@netscape.com:netscape.*:verify-netscape.public.admin
+newgroup:news@netscape.com:netscape.*:verify-netscape.public.admin
+rmgroup:news@netscape.com:netscape.*:verify-netscape.public.admin
+
+## NF (Newfoundland and Labrador, Canada)
+# Contact: randy@mun.ca
+checkgroups:randy@mun.ca:nf.*:doit
+newgroup:randy@mun.ca:nf.*:doit
+rmgroup:randy@mun.ca:nf.*:doit
+
+## NIAGARA (Niagara Peninsula, USA/Canada)
+checkgroups:news@niagara.com:niagara.*:doit
+newgroup:news@niagara.com:niagara.*:doit
+rmgroup:news@niagara.com:niagara.*:doit
+
+## NIAS (Japanese ?)
+checkgroups:news@cc.nias.ac.jp:nias.*:doit
+newgroup:news@cc.nias.ac.jp:nias.*:doit
+rmgroup:news@cc.nias.ac.jp:nias.*:doit
+
+## NIGERIA (Nigeria)
+checkgroups:news@easnet.net:nigeria.*:doit
+newgroup:news@easnet.net:nigeria.*:doit
+rmgroup:news@easnet.net:nigeria.*:doit
+
+## NIHON (Japan)
+checkgroups:ktomita@jade.dti.ne.jp:nihon.*:doit
+newgroup:ktomita@jade.dti.ne.jp:nihon.*:doit
+rmgroup:ktomita@jade.dti.ne.jp:nihon.*:doit
+
+## NIPPON (*PRIVATE* -- Japan)
+# URL: http://www.gcd.org/news/nippon/
+# Admin group: nippon.news.group
+# Key URL: http://www.gcd.org/news/nippon/
+# Key fingerprint = BC CF 15 CD B1 3C DF B3 C3 DE 35 6F 2F F7 46 DB
+# For private use only.
+# *PGP* See comment at top of file.
+newgroup:*:nippon.*:drop
+rmgroup:*:nippon.*:drop
+newgroup:news@gcd.org:nippon.*:mail
+rmgroup:news@gcd.org:nippon.*:verify-nippon.news.group
+
+## NJ (New Jersey, USA)
+# Contact: nj-admin@gunslinger.net
+# URL: http://www.exit109.com/~jeremy/nj/
+checkgroups:nj-admin@gunslinger.net:nj.*:doit
+newgroup:nj-admin@gunslinger.net:nj.*:doit
+rmgroup:nj-admin@gunslinger.net:nj.*:doit
+
+## NL (Netherlands)
+# Contact: nl-admin@nic.surfnet.nl
+# URL: http://nl.news-admin.org/info/nladmin.html
+# Admin group: nl.newsgroups
+# Key fingerprint = 45 20 0B D5 A1 21 EA 7C EF B2 95 6C 25 75 4D 27
+# *PGP* See comment at top of file.
+newgroup:*:nl.*:drop
+rmgroup:*:nl.*:drop
+checkgroups:nl-admin@nic.surfnet.nl:nl.*:verify-nl.newsgroups
+newgroup:nl-admin@nic.surfnet.nl:nl.*:verify-nl.newsgroups
+rmgroup:nl-admin@nic.surfnet.nl:nl.*:verify-nl.newsgroups
+
+## NL-ALT (Alternative Netherlands groups)
+# Key fingerprint = 6B 62 EB 53 4D 5D 2F 96 35 D9 C8 9C B0 65 0E 4C
+# *PGP* See comment at top of file.
+checkgroups:nl-alt-janitor@surfer.xs4all.nl:nl-alt.*:verify-nl-alt.config.admin
+newgroup:*:nl-alt.*:doit
+rmgroup:nl-alt-janitor@surfer.xs4all.nl:nl-alt.*:verify-nl-alt.config.admin
+rmgroup:news@kink.xs4all.nl:nl-alt.*:verify-nl-alt.config.admin
+
+## NLO (Open Source / Free Software, hosted by nl.linux.org)
+# URL: http://news.nl.linux.org/doc/nlo.html
+# Key URL: http://news.nl.linux.org/doc/nlo-3.html#ss3.1
+# Key fingerprint = 63 DC B2 51 0A F3 DD 72 C2 BD C6 FD C1 C5 44 CF
+# *PGP* See comment at top of file.
+newgroup:*:nlo.*:drop
+rmgroup:*:nlo.*:drop
+checkgroups:news@nl.linux.org:nlo.*:verify-nlo.newsgroups
+newgroup:news@nl.linux.org:nlo.*:verify-nlo.newsgroups
+rmgroup:news@nl.linux.org:nlo.*:verify-nlo.newsgroups
+
+## NM (New Mexico, USA)
+checkgroups:news@tesuque.cs.sandia.gov:nm.*:doit
+newgroup:news@tesuque.cs.sandia.gov:nm.*:doit
+rmgroup:news@tesuque.cs.sandia.gov:nm.*:doit
+
+## NO (Norway)
+# URL: http://www.usenet.no/
+# Admin group: no.usenet.admin
+# Key URL: http://www.usenet.no/pgp-key.txt
+# *PGP* See comment at top of file.
+newgroup:*:no.*:drop
+rmgroup:*:no.*:drop
+checkgroups:control@usenet.no:no.*:verify-no-hir-control
+newgroup:control@usenet.no:no.*:verify-no-hir-control
+rmgroup:control@usenet.no:no.*:verify-no-hir-control
+
+## NO.ALT (Norway alternative hierarchy)
+# *PGP* See comment at top of file.
+newgroup:*:no.alt.*:drop
+rmgroup:*:no.alt.*:drop
+newgroup:*@*.no:no.alt.*:doit
+rmgroup:control@usenet.no:no.alt.*:verify-no-hir-control
+
+## NORD (Northern Germany)
+# thilo@own.deceiver.org no longer a valid address
+# newgroup:thilo@own.deceiver.org:nord.*:doit
+# rmgroup:thilo@own.deceiver.org:nord.*:doit
+
+## NRW (Northrine-Westfalia, Germany)
+# Contact: moderator@nrw.usenetverwaltung.de
+# URL: http://nrw.usenetverwaltung.de/
+# Admin group: nrw.admin.announce
+# Key URL: http://nrw.usenetverwaltung.de/pgp/nrw.asc
+# Key fingerprint = 13 4A 80 FE D6 34 B4 64 AF 32 08 3F 62 0E B1 E2
+# *PGP* See comment at top of file.
+newgroup:*:nrw.*:drop
+rmgroup:*:nrw.*:drop
+checkgroups:moderator@nrw.usenetverwaltung.de:nrw.*:verify-moderator@nrw.usenetverwaltung.de
+newgroup:moderator@nrw.usenetverwaltung.de:nrw.*:verify-moderator@nrw.usenetverwaltung.de
+rmgroup:moderator@nrw.usenetverwaltung.de:nrw.*:verify-moderator@nrw.usenetverwaltung.de
+
+## NV (Nevada)
+checkgroups:cshapiro@netcom.com:nv.*:doit
+checkgroups:doctor@netcom.com:nv.*:doit
+newgroup:cshapiro@netcom.com:nv.*:doit
+newgroup:doctor@netcom.com:nv.*:doit
+rmgroup:cshapiro@netcom.com:nv.*:doit
+rmgroup:doctor@netcom.com:nv.*:doit
+
+## NY (New York State, USA)
+checkgroups:root@ny.psca.com:ny.*:doit
+newgroup:root@ny.psca.com:ny.*:doit
+rmgroup:root@ny.psca.com:ny.*:doit
+
+## NYC (New York City)
+# Contact: Perry E. Metzger <perry@piermont.com>
+checkgroups:perry@piermont.com:nyc.*:doit
+newgroup:perry@piermont.com:nyc.*:doit
+rmgroup:perry@piermont.com:nyc.*:doit
+
+## NZ (New Zealand)
+# Contact: root@usenet.net.nz
+# URL: http://www.faqs.org/faqs/usenet/nz-news-hierarchy
+# Admin group: nz.net.announce
+# Key fingerprint = 07 DF 48 AA D0 ED AA 88 16 70 C5 91 65 3D 1A 28
+# *PGP* See comment at top of file.
+newgroup:*:nz.*:drop
+rmgroup:*:nz.*:drop
+checkgroups:root@usenet.net.nz:nz.*:verify-nz-hir-control
+newgroup:root@usenet.net.nz:nz.*:verify-nz-hir-control
+rmgroup:root@usenet.net.nz:nz.*:verify-nz-hir-control
+
+## OC (Orange County, California, USA)
+checkgroups:bob@tsunami.sugarland.unocal.com:oc.*:doit
+newgroup:bob@tsunami.sugarland.unocal.com:oc.*:doit
+rmgroup:bob@tsunami.sugarland.unocal.com:oc.*:doit
+
+## OESTERREICH (Free Austria)
+#
+# This is apparently another alt.* or free.* but specific to Austria.
+# Currently, the ftp.isc.org list doesn't honor newgroup messages in the
+# hierarchy due to lack of requests, but here is the information in case
+# any news admin wishes to carry it.
+#
+# URL: http://www.tahina.priv.at/~cm/oe/index.en.html
+#newgroup:*:oesterreich.*:doit
+#newgroup:group-admin@isc.org:oesterreich.*:drop
+#newgroup:tale@*uu.net:oesterreich.*:drop
+#rmgroup:*:oesterreich.*:drop
+
+## OH (Ohio, USA)
+checkgroups:trier@ins.cwru.edu:oh.*:doit
+newgroup:trier@ins.cwru.edu:oh.*:doit
+rmgroup:trier@ins.cwru.edu:oh.*:doit
+
+## OK (Oklahoma, USA)
+checkgroups:quentin@*qns.com:ok.*:doit
+newgroup:quentin@*qns.com:ok.*:doit
+rmgroup:quentin@*qns.com:ok.*:doit
+
+## OKINAWA (Okinawa, Japan)
+checkgroups:news@opus.or.jp:okinawa.*:doit
+newgroup:news@opus.or.jp:okinawa.*:doit
+rmgroup:news@opus.or.jp:okinawa.*:doit
+
+## ONT (Ontario, Canada)
+checkgroups:pkern@gpu.utcc.utoronto.ca:ont.*:doit
+newgroup:pkern@gpu.utcc.utoronto.ca:ont.*:doit
+rmgroup:pkern@gpu.utcc.utoronto.ca:ont.*:doit
+
+## OPENNEWS (Open News Network)
+# URL: http://www.open-news-network.org/
+# *PGP* See comment at top of file.
+newgroup:*:opennews.*:drop
+rmgroup:*:opennews.*:drop
+checkgroups:schiller@babsi.de:opennews.*:verify-news@news2.open-news-network.org
+newgroup:schiller@babsi.de:opennews.*:verify-news@news2.open-news-network.org
+rmgroup:schiller@babsi.de:opennews.*:verify-news@news2.open-news-network.org
+
+## OPENWATCOM (Open Watcom compilers)
+# Contact: admin@openwatcom.news-admin.org
+# URL: http://www.openwatcom.org/
+# Admin group: openwatcom.contributors
+# Key URL: http://cmeerw.org/files/openwatcom/pgp-openwatcom.asc
+# Syncable server: news.openwatcom.org
+# *PGP* See comment at top of file.
+newgroup:*:openwatcom.*:drop
+rmgroup:*:openwatcom.*:drop
+checkgroups:admin@openwatcom.news-admin.org:openwatcom.*:verify-admin@openwatcom.news-admin.org
+newgroup:admin@openwatcom.news-admin.org:openwatcom.*:verify-admin@openwatcom.news-admin.org
+rmgroup:admin@openwatcom.news-admin.org:openwatcom.*:verify-admin@openwatcom.news-admin.org
+
+## OPERA (Opera Software, Oslo, Norway)
+# Contact: usenet@opera.com
+# Syncable server: news.opera.com
+# *PGP* See comment at top of file.
+newgroup:*:opera.*:drop
+rmgroup:*:opera.*:drop
+checkgroups:*@opera.com:opera.*:verify-opera-group-admin
+newgroup:*@opera.com:opera.*:verify-opera-group-admin
+rmgroup:*@opera.com:opera.*:verify-opera-group-admin
+
+## OTT (Ottawa, Ontario, Canada)
+# Contact: onag@pinetree.org
+# URL: http://www.pinetree.org/ONAG/
+checkgroups:clewis@ferret.ocunix.on.ca:ott.*:doit
+checkgroups:dave@revcan.ca:ott.*:doit
+checkgroups:gordon@*pinetree.org:ott.*:doit
+checkgroups:news@*pinetree.org:ott.*:doit
+checkgroups:news@bnr.ca:ott.*:doit
+checkgroups:news@ferret.ocunix.on.ca:ott.*:doit
+checkgroups:news@nortel.ca:ott.*:doit
+newgroup:clewis@ferret.ocunix.on.ca:ott.*:doit
+newgroup:dave@revcan.ca:ott.*:doit
+newgroup:gordon@*pinetree.org:ott.*:doit
+newgroup:news@*pinetree.org:ott.*:doit
+newgroup:news@bnr.ca:ott.*:doit
+newgroup:news@ferret.ocunix.on.ca:ott.*:doit
+newgroup:news@nortel.ca:ott.*:doit
+rmgroup:clewis@ferret.ocunix.on.ca:ott.*:doit
+rmgroup:dave@revcan.ca:ott.*:doit
+rmgroup:gordon@*pinetree.org:ott.*:doit
+rmgroup:news@*pinetree.org:ott.*:doit
+rmgroup:news@bnr.ca:ott.*:doit
+rmgroup:news@ferret.ocunix.on.ca:ott.*:doit
+rmgroup:news@nortel.ca:ott.*:doit
+
+## PA (Pennsylvania, USA)
+# URL: http://www.netcom.com/~rb1000/pa_hierarchy/
+checkgroups:fxp@epix.net:pa.*:doit
+newgroup:fxp@epix.net:pa.*:doit
+rmgroup:fxp@epix.net:pa.*:doit
+
+## PBINFO (Paderborn, Germany)
+# Contact: news@uni-paderborn.de
+# *PGP* See comment at top of file.
+newgroup:*:pbinfo.*:drop
+rmgroup:*:pbinfo.*:drop
+checkgroups:postmaster@upb.de:pbinfo.*:verify-news@uni-paderborn.de
+newgroup:postmaster@upb.de:pbinfo.*:verify-news@uni-paderborn.de
+rmgroup:postmaster@upb.de:pbinfo.*:verify-news@uni-paderborn.de
+
+## PERL (Perl Programming Language)
+# Contact: newsadmin@perl.org
+# URL: http://www.nntp.perl.org/about/
+# Key URL: http://www.nntp.perl.org/about/newsadmin@perl.org.pgp
+# Key fingerprint = 438F D1BA 4DCC 3B1A BED8 2BCC 3298 8A7D 8B2A CFBB
+# *PGP* See comment at top of file.
+newgroup:*:perl.*:drop
+rmgroup:*:perl.*:drop
+checkgroups:newsadmin@perl.org:perl.*:verify-newsadmin@perl.org
+newgroup:newsadmin@perl.org:perl.*:verify-newsadmin@perl.org
+rmgroup:newsadmin@perl.org:perl.*:verify-newsadmin@perl.org
+
+## PGH (Pittsburgh, Pennsylvania, USA)
+# Admin group: pgh.config
+# *PGP* See comment at top of file.
+newgroup:*:pgh.*:drop
+rmgroup:*:pgh.*:drop
+checkgroups:pgh-config@psc.edu:pgh.*:verify-pgh.config
+newgroup:pgh-config@psc.edu:pgh.*:verify-pgh.config
+rmgroup:pgh-config@psc.edu:pgh.*:verify-pgh.config
+
+## PGSQL (Gated PostgreSQL mailing lists)
+# Contact: news@postgresql.org
+# URL: http://news.hub.org/gpg_public_keys.html
+# Key URL: http://news.hub.org/gpg_public_keys.html
+# *PGP* See comment at top of file.
+newgroup:*:pgsql.*:drop
+rmgroup:*:pgsql.*:drop
+checkgroups:news@postgresql.org:pgsql.*:verify-news@postgresql.org
+newgroup:news@postgresql.org:pgsql.*:verify-news@postgresql.org
+rmgroup:news@postgresql.org:pgsql.*:verify-news@postgresql.org
+
+## PHL (Philadelphia, Pennsylvania, USA)
+checkgroups:news@vfl.paramax.com:phl.*:doit
+newgroup:news@vfl.paramax.com:phl.*:doit
+rmgroup:news@vfl.paramax.com:phl.*:doit
+
+## PIN (Personal Internauts' NetNews)
+checkgroups:pin-admin@forus.or.jp:pin.*:doit
+newgroup:pin-admin@forus.or.jp:pin.*:doit
+rmgroup:pin-admin@forus.or.jp:pin.*:doit
+
+## PIPEX (UUNET WorldCom UK)
+# Contact: Russell Vincent <news-control@ops.pipex.net>
+checkgroups:news-control@ops.pipex.net:pipex.*:doit
+newgroup:news-control@ops.pipex.net:pipex.*:doit
+rmgroup:news-control@ops.pipex.net:pipex.*:doit
+
+## PITT (University of Pittsburgh, PA)
+checkgroups:news+@pitt.edu:pitt.*:doit
+checkgroups:news@toads.pgh.pa.us:pitt.*:doit
+newgroup:news+@pitt.edu:pitt.*:doit
+newgroup:news@toads.pgh.pa.us:pitt.*:doit
+rmgroup:news+@pitt.edu:pitt.*:doit
+rmgroup:news@toads.pgh.pa.us:pitt.*:doit
+
+## PL (Poland and Polish language)
+# URL: http://www.usenet.pl/doc/news-pl-new-site-faq.html
+# Admin group: pl.news.admin
+# Key URL: http://www.usenet.pl/doc/news-pl-new-site-faq.html#pgp
+# *PGP* See comment at top of file.
+newgroup:*:pl.*:drop
+rmgroup:*:pl.*:drop
+checkgroups:michalj@*fuw.edu.pl:pl.*:verify-pl.announce.newgroups
+checkgroups:newgroup@usenet.pl:pl.*:verify-pl.announce.newgroups
+newgroup:michalj@*fuw.edu.pl:pl.*:verify-pl.announce.newgroups
+newgroup:newgroup@usenet.pl:pl.*:verify-pl.announce.newgroups
+rmgroup:michalj@*fuw.edu.pl:pl.*:verify-pl.announce.newgroups
+rmgroup:newgroup@usenet.pl:pl.*:verify-pl.announce.newgroups
+
+## PLANET (*LOCAL* -- PlaNet FreeNZ co-operative, New Zealand)
+# Contact: office@pl.net
+# For local use only, contact the above address for information.
+newgroup:*:planet.*:mail
+rmgroup:*:planet.*:doit
+
+## PRIMA (*LOCAL* -- prima.ruhr.de/Prima e.V. in Germany)
+# Contact: admin@prima.ruhr.de
+# For local use only, contact the above address for information.
+newgroup:*:prima.*:mail
+rmgroup:*:prima.*:doit
+
+## PSU (*LOCAL* -- Penn State University, USA)
+# Contact: Dave Barr (barr@math.psu.edu)
+# For local use only, contact the above address for information.
+newgroup:*:psu.*:mail
+rmgroup:*:psu.*:doit
+
+## PT (Portugal and Portuguese language)
+# URL: http://www.usenet-pt.org/
+# Admin group: pt.internet.usenet
+# Key URL: http://www.usenet-pt.org/control@usenet-pt.org.asc
+# *PGP* See comment at top of file.
+newgroup:*:pt.*:drop
+rmgroup:*:pt.*:drop
+checkgroups:pmelo@*.inescc.pt:pt.*:verify-control@usenet-pt.org
+newgroup:pmelo@*.inescc.pt:pt.*:verify-control@usenet-pt.org
+rmgroup:pmelo@*.inescc.pt:pt.*:verify-control@usenet-pt.org
+
+## PUBNET (*DEFUNCT* -- ?)
+# URL: ftp://ftp.isc.org/pub/usenet/control/pubnet/pubnet.config.Z
+# This hierarchy is defunct. Please remove it.
+newgroup:*:pubnet.*:mail
+rmgroup:*:pubnet.*:doit
+
+## RELCOM (Commonwealth of Independent States)
+# URL: ftp://ftp.relcom.ru/pub/relcom/netinfo/
+# Admin group: relcom.netnews
+# Key URL: ftp://ftp.relcom.ru/pub/relcom/netinfo/coordpubkey.txt
+# *PGP* See comment at top of file.
+newgroup:*:relcom.*:drop
+rmgroup:*:relcom.*:drop
+checkgroups:coord@*.relcom.ru:relcom.*:verify-relcom.newsgroups
+newgroup:coord@*.relcom.ru:relcom.*:verify-relcom.newsgroups
+rmgroup:coord@*.relcom.ru:relcom.*:verify-relcom.newsgroups
+
+## RPI (*LOCAL* -- Rensselaer Polytechnic Institute, Troy, NY, USA)
+# Contact: sofkam@rpi.edu
+# For local use only, contact the above address for information.
+newgroup:*:rpi.*:mail
+rmgroup:*:rpi.*:doit
+
+## SAAR (Saarland Region, Germany)
+# URL: http://www.saar-admin-news.de/
+# Admin group: saar.admin.news
+# Key URL: http://www.saar-admin-news.de/saar-control.asc
+# *PGP* See comment at top of file.
+newgroup:*:saar.*:drop
+rmgroup:*:saar.*:drop
+checkgroups:control@saar-admin-news.de:saar.*:verify-saar-control
+newgroup:control@saar-admin-news.de:saar.*:verify-saar-control
+rmgroup:control@saar-admin-news.de:saar.*:verify-saar-control
+
+## SACHSNET (German)
+checkgroups:root@lusatia.de:sachsnet.*:doit
+newgroup:root@lusatia.de:sachsnet.*:doit
+rmgroup:root@lusatia.de:sachsnet.*:doit
+
+## SAT (San Antonio, Texas, USA)
+# Contact: satgroup@endicor.com
+# Admin group: sat.usenet.config
+# *PGP* See comment at top of file.
+newgroup:*:sat.*:drop
+rmgroup:*:sat.*:drop
+checkgroups:satgroup@endicor.com:sat.*:verify-satgroup@endicor.com
+newgroup:satgroup@endicor.com:sat.*:verify-satgroup@endicor.com
+rmgroup:satgroup@endicor.com:sat.*:verify-satgroup@endicor.com
+
+## SBAY (South Bay/Silicon Valley, California)
+# URL: http://www.sbay.org/sbay-newsgroups.html
+checkgroups:ikluft@thunder.sbay.org:sbay.*:doit
+checkgroups:steveh@grafex.sbay.org:sbay.*:doit
+newgroup:ikluft@thunder.sbay.org:sbay.*:doit
+newgroup:steveh@grafex.sbay.org:sbay.*:doit
+rmgroup:ikluft@thunder.sbay.org:sbay.*:doit
+rmgroup:steveh@grafex.sbay.org:sbay.*:doit
+
+## SCHULE (?)
+# Contact: schule-admin@roxel.ms.sub.org
+# URL: http://home.pages.de/~schule-admin/
+# Admin group: schule.admin
+# Key URL: http://www.afaik.de/usenet/admin/schule/control/schule.asc
+# Key fingerprint = 64 06 F0 AE E1 46 85 0C BD CA 0E 53 8B 1E 73 D2
+# *PGP* See comment at top of file.
+newgroup:*:schule.*:drop
+rmgroup:*:schule.*:drop
+checkgroups:newsctrl@schule.de:schule.*:verify-schule.konfig
+newgroup:newsctrl@schule.de:schule.*:verify-schule.konfig
+rmgroup:newsctrl@schule.de:schule.*:verify-schule.konfig
+
+## SCOT (Scotland)
+# URL: http://scot.news-admin.org/
+# Admin group: scot.newsgroups.discuss
+# Key URL: http://scot.news-admin.org/signature.html
+# *PGP* See comment at top of file.
+newgroup:*:scot.*:drop
+rmgroup:*:scot.*:drop
+checkgroups:control@scot.news-admin.org:scot.*:verify-control@scot.news-admin.org
+newgroup:control@scot.news-admin.org:scot.*:verify-control@scot.news-admin.org
+rmgroup:control@scot.news-admin.org:scot.*:verify-control@scot.news-admin.org
+
+## SCOUT (Scouts and guides)
+# URL: http://news.scoutnet.org/
+# Admin group: scout.admin
+# Key URL: http://news.scoutnet.org/scout-pgpkey.asc
+# *PGP* See comment at top of file.
+newgroup:*:scout.*:drop
+rmgroup:*:scout.*:drop
+checkgroups:control@news.scoutnet.org:scout.*:verify-control@news.scoutnet.org
+newgroup:control@news.scoutnet.org:scout.*:verify-control@news.scoutnet.org
+rmgroup:control@news.scoutnet.org:scout.*:verify-control@news.scoutnet.org
+
+## SDNET (Greater San Diego Area, California, USA)
+# URL: http://www-rohan.sdsu.edu/~wk/sdnet/sdnet.html
+checkgroups:wkronert@sunstroke.sdsu.edu:sdnet.*:doit
+newgroup:wkronert@sunstroke.sdsu.edu:sdnet.*:doit
+rmgroup:wkronert@sunstroke.sdsu.edu:sdnet.*:doit
+
+## SDSU (*LOCAL* -- San Diego State University, CA)
+# Contact: Craig R. Sadler <usenet@sdsu.edu>
+# For local use only, contact the above address for information.
+newgroup:*:sdsu.*:mail
+rmgroup:*:sdsu.*:doit
+
+## SE (Sweden)
+# Contact: usenet@usenet-se.net
+# Admin group: se.internet.news.meddelanden
+# Key fingerprint = 68 03 F0 FD 0C C3 4E 69 6F 0D 0C 60 3C 58 63 96
+# *PGP* See comment at top of file.
+newgroup:*:se.*:drop
+rmgroup:*:se.*:drop
+checkgroups:usenet@usenet-se.net:se.*:verify-usenet-se
+newgroup:usenet@usenet-se.net:se.*:verify-usenet-se
+rmgroup:usenet@usenet-se.net:se.*:verify-usenet-se
+
+## SEATTLE (Seattle, Washington, USA)
+checkgroups:billmcc@akita.com:seattle.*:doit
+checkgroups:graham@ee.washington.edu:seattle.*:doit
+newgroup:billmcc@akita.com:seattle.*:doit
+newgroup:graham@ee.washington.edu:seattle.*:doit
+rmgroup:billmcc@akita.com:seattle.*:doit
+rmgroup:graham@ee.washington.edu:seattle.*:doit
+
+## SFNET (Finland)
+# Contact: sfnet@cs.tut.fi
+# URL: http://www.cs.tut.fi/sfnet/
+# Admin group: sfnet.ryhmat+listat
+# Key fingerprint = DE79 33C2 D359 D128 44E5 6A0C B6E3 0E53 6933 A636
+# *PGP* See comment at top of file.
+newgroup:*:sfnet.*:drop
+rmgroup:*:sfnet.*:drop
+checkgroups:sfnet@*cs.tut.fi:sfnet.*:verify-sfnet@cs.tut.fi
+newgroup:sfnet@*cs.tut.fi:sfnet.*:verify-sfnet@cs.tut.fi
+rmgroup:sfnet@*cs.tut.fi:sfnet.*:verify-sfnet@cs.tut.fi
+
+## SHAMASH (Jewish)
+checkgroups:archives@israel.nysernet.org:shamash.*:doit
+newgroup:archives@israel.nysernet.org:shamash.*:doit
+rmgroup:archives@israel.nysernet.org:shamash.*:doit
+
+## SI (The Republic of Slovenia)
+# URL: http://www.arnes.si/news/config/
+# Admin group: si.news.announce.newsgroups
+# Key URL: http://www.arnes.si/news/config/
+# *PGP* See comment at top of file.
+newgroup:*:si.*:drop
+rmgroup:*:si.*:drop
+checkgroups:news-admin@arnes.si:si.*:verify-si.news.announce.newsgroups
+newgroup:news-admin@arnes.si:si.*:verify-si.news.announce.newsgroups
+rmgroup:news-admin@arnes.si:si.*:verify-si.news.announce.newsgroups
+
+## SJ (St. John's, Newfoundland and Labrador, Canada)
+# Contact: randy@mun.ca
+checkgroups:randy@mun.ca:sj.*:doit
+newgroup:randy@mun.ca:sj.*:doit
+rmgroup:randy@mun.ca:sj.*:doit
+
+## SK (Slovakia)
+checkgroups:uhlar@ccnews.ke.sanet.sk:sk.*:doit
+newgroup:uhlar@ccnews.ke.sanet.sk:sk.*:doit
+rmgroup:uhlar@ccnews.ke.sanet.sk:sk.*:doit
+
+## SLAC (*PRIVATE* -- Stanford Linear Accelerator Center, Stanford, USA)
+# Contact: news@news.stanford.edu
+# For private use only, contact the above address for information.
+newgroup:news@news.stanford.edu:slac.*:mail
+rmgroup:news@news.stanford.edu:slac.*:doit
+
+## SLO (San Luis Obispo, CA)
+checkgroups:news@punk.net:slo.*:doit
+newgroup:news@punk.net:slo.*:doit
+rmgroup:news@punk.net:slo.*:doit
+
+## SOLENT (Solent region, England)
+checkgroups:news@tcp.co.uk:solent.*:doit
+newgroup:news@tcp.co.uk:solent.*:doit
+rmgroup:news@tcp.co.uk:solent.*:doit
+
+## SPOKANE (Spokane, Washington, USA)
+checkgroups:usenet@news.spokane.wa.us:spokane.*:doit
+newgroup:usenet@news.spokane.wa.us:spokane.*:doit
+rmgroup:usenet@news.spokane.wa.us:spokane.*:doit
+
+## SSLUG (*PRIVATE* -- Skåne Sjælland Linux User Group)
+# URL: http://en.sslug.dk/
+# For private use only.
+newgroup:sparre@sslug.se:sslug.*:mail
+rmgroup:sparre@sslug.se:sslug.*:doit
+
+## STAROFFICE (StarOffice business suite, Sun Microsystems, Inc.)
+# Contact: news@starnews.sun.com
+# Admin group: staroffice.admin
+# Key fingerprint = C6 3E 81 6F 2A 19 D3 84 72 51 F9 1B E3 B9 B2 C9
+# Syncable server: starnews.sun.com
+# *PGP* See comment at top of file.
+newgroup:*:staroffice.*:drop
+rmgroup:*:staroffice.*:drop
+checkgroups:news@stardivision.de:staroffice.*:verify-staroffice.admin
+newgroup:news@stardivision.de:staroffice.*:verify-staroffice.admin
+rmgroup:news@stardivision.de:staroffice.*:verify-staroffice.admin
+
+## STGT (Stuttgart, Germany)
+# URL: http://news.uni-stuttgart.de/hierarchie/stgt/
+# Key URL: http://news.uni-stuttgart.de/hierarchie/stgt/stgt-control.txt
+# *PGP* See comment at top of file.
+newgroup:*:stgt.*:drop
+rmgroup:*:stgt.*:drop
+checkgroups:stgt-control@news.uni-stuttgart.de:stgt.*:verify-stgt-control
+newgroup:stgt-control@news.uni-stuttgart.de:stgt.*:verify-stgt-control
+rmgroup:stgt-control@news.uni-stuttgart.de:stgt.*:verify-stgt-control
+
+## STL (Saint Louis, Missouri, USA)
+checkgroups:news@icon-stl.net:stl.*:doit
+newgroup:news@icon-stl.net:stl.*:doit
+rmgroup:news@icon-stl.net:stl.*:doit
+
+## SU (*LOCAL* -- Stanford University, USA)
+# Contact: news@news.stanford.edu
+# For local use only, contact the above address for information.
+newgroup:*:su.*:mail
+rmgroup:*:su.*:doit
+
+## SUNET (Swedish University Network)
+checkgroups:ber@*.sunet.se:sunet.*:doit
+newgroup:ber@*.sunet.se:sunet.*:doit
+rmgroup:ber@*.sunet.se:sunet.*:doit
+
+## SURFNET (Dutch Universities network)
+checkgroups:news@info.nic.surfnet.nl:surfnet.*:doit
+newgroup:news@info.nic.surfnet.nl:surfnet.*:doit
+rmgroup:news@info.nic.surfnet.nl:surfnet.*:doit
+
+## SWNET (Sverige, Sweden)
+checkgroups:ber@sunic.sunet.se:swnet.*:doit
+newgroup:ber@sunic.sunet.se:swnet.*:doit
+rmgroup:ber@sunic.sunet.se:swnet.*:doit
+
+## SYD (Sydney, Australia)
+# Contact: ausadmin@aus.news-admin.org
+# URL: http://syd.news-admin.org/
+# Key URL: http://aus.news-admin.org/ausadmin.asc
+# *PGP* See comment at top of file.
+newgroup:*:syd.*:drop
+rmgroup:*:syd.*:drop
+checkgroups:ausadmin@aus.news-admin.org:syd.*:verify-ausadmin@aus.news-admin.org
+newgroup:ausadmin@aus.news-admin.org:syd.*:verify-ausadmin@aus.news-admin.org
+rmgroup:ausadmin@aus.news-admin.org:syd.*:verify-ausadmin@aus.news-admin.org
+
+## T-NETZ (*DEFUNCT* -- Germany)
+# This hierarchy is defunct. Please remove it.
+newgroup:*:t-netz.*:mail
+rmgroup:*:t-netz.*:doit
+
+## TAMU (Texas A&M University)
+# Contact: Philip Kizer <news@tamu.edu>
+checkgroups:news@tamsun.tamu.edu:tamu.*:doit
+newgroup:news@tamsun.tamu.edu:tamu.*:doit
+rmgroup:news@tamsun.tamu.edu:tamu.*:doit
+
+## TAOS (Taos, New Mexico, USA)
+# Contact: Chris Gunn <cgunn@laplaza.org>
+checkgroups:cgunn@laplaza.org:taos.*:doit
+newgroup:cgunn@laplaza.org:taos.*:doit
+rmgroup:cgunn@laplaza.org:taos.*:doit
+
+## TCFN (Toronto Free Community Network, Canada)
+checkgroups:news@t-fcn.net:tcfn.*:doit
+newgroup:news@t-fcn.net:tcfn.*:doit
+rmgroup:news@t-fcn.net:tcfn.*:doit
+
+## TELE (*LOCAL* -- Tele Danmark Internet)
+# Contact: usenet@tdk.net
+# For local use only, contact the above address for information.
+newgroup:*:tele.*:mail
+rmgroup:*:tele.*:doit
+
+## TERMVAKT (*LOCAL* -- University of Oslo, Norway)
+# Contact: jani@ifi.uio.no
+# For local use only, contact the above address for information.
+newgroup:*:termvakt.*:mail
+rmgroup:*:termvakt.*:doit
+
+## TEST (Local test hierarchy)
+# It is not really a good idea for sites to use these since they may occur
+# on many unconnected sites.
+newgroup:*:test.*:mail
+rmgroup:*:test.*:mail
+
+## THUR (Thuringia, Germany)
+# Key fingerprint = 7E 3D 73 13 93 D4 CA 78 39 DE 3C E7 37 EE 22 F1
+# *PGP* See comment at top of file.
+newgroup:*:thur.*:drop
+rmgroup:*:thur.*:drop
+checkgroups:usenet@thur.de:thur.*:verify-thur.net.news.groups
+newgroup:usenet@thur.de:thur.*:verify-thur.net.news.groups
+rmgroup:usenet@thur.de:thur.*:verify-thur.net.news.groups
+
+## TNN (*DEFUNCT* -- The Network News, Japan)
+# This hierarchy is defunct. Please remove it.
+newgroup:netnews@news.iij.ad.jp:tnn.*:mail
+newgroup:tnn@iij-mc.co.jp:tnn.*:mail
+rmgroup:netnews@news.iij.ad.jp:tnn.*:doit
+rmgroup:tnn@iij-mc.co.jp:tnn.*:doit
+
+## TRIANGLE (Research Triangle, Central North Carolina, USA)
+checkgroups:jfurr@acpub.duke.edu:triangle.*:doit
+checkgroups:news@news.duke.edu:triangle.*:doit
+checkgroups:tas@concert.net:triangle.*:doit
+newgroup:jfurr@acpub.duke.edu:triangle.*:doit
+newgroup:news@news.duke.edu:triangle.*:doit
+newgroup:tas@concert.net:triangle.*:doit
+rmgroup:jfurr@acpub.duke.edu:triangle.*:doit
+rmgroup:news@news.duke.edu:triangle.*:doit
+rmgroup:tas@concert.net:triangle.*:doit
+
+## TUM (Technische Universitaet Muenchen)
+checkgroups:news@informatik.tu-muenchen.de:tum.*:doit
+newgroup:news@informatik.tu-muenchen.de:tum.*:doit
+rmgroup:news@informatik.tu-muenchen.de:tum.*:doit
+
+## TW (Taiwan)
+checkgroups:ltc@news.cc.nctu.edu.tw:tw.*:doit
+newgroup:ltc@news.cc.nctu.edu.tw:tw.*:doit
+rmgroup:ltc@news.cc.nctu.edu.tw:tw.*:doit
+
+## TW.K-12 (Taiwan K-12 Discussion)
+checkgroups:k-12@news.nchu.edu.tw:tw.k-12.*:doit
+newgroup:k-12@news.nchu.edu.tw:tw.k-12.*:doit
+rmgroup:k-12@news.nchu.edu.tw:tw.k-12.*:doit
+
+## TX (Texas, USA)
+checkgroups:eric@cirr.com:tx.*:doit
+checkgroups:fletcher@cs.utexas.edu:tx.*:doit
+checkgroups:usenet@academ.com:tx.*:doit
+newgroup:eric@cirr.com:tx.*:doit
+newgroup:fletcher@cs.utexas.edu:tx.*:doit
+newgroup:usenet@academ.com:tx.*:doit
+rmgroup:eric@cirr.com:tx.*:doit
+rmgroup:fletcher@cs.utexas.edu:tx.*:doit
+rmgroup:usenet@academ.com:tx.*:doit
+
+## UCB (University of California Berkeley, USA)
+# Contact: Chris van den Berg <usenet@agate.berkeley.edu>
+# URL: http://www.net.berkeley.edu/usenet/
+# Key URL: http://www.net.berkeley.edu/usenet/usenet.asc
+# Key fingerprint = 96 B8 8E 9A 98 09 37 7D 0E EC 81 88 DB 90 29 BF
+# *PGP* See comment at top of file.
+newgroup:*:ucb.*:drop
+rmgroup:*:ucb.*:drop
+checkgroups:usenet@agate.berkeley.edu:ucb.*:verify-ucb.news
+newgroup:usenet@agate.berkeley.edu:ucb.*:verify-ucb.news
+rmgroup:usenet@agate.berkeley.edu:ucb.*:verify-ucb.news
+
+## UCD (University of California Davis, USA)
+checkgroups:usenet@mark.ucdavis.edu:ucd.*:doit
+checkgroups:usenet@rocky.ucdavis.edu:ucd.*:doit
+newgroup:usenet@mark.ucdavis.edu:ucd.*:doit
+newgroup:usenet@rocky.ucdavis.edu:ucd.*:doit
+rmgroup:usenet@mark.ucdavis.edu:ucd.*:doit
+rmgroup:usenet@rocky.ucdavis.edu:ucd.*:doit
+
+## UFRA (Unterfranken, Deutschland)
+# Contact: news@mayn.de
+# URL: http://www.mayn.de/users/news/
+# Admin group: ufra.admin
+# Key fingerprint = F7 AD 96 D8 7A 3F 7E 84 02 0C 83 9A DB 8F EB B8
+# Syncable server: news.mayn.de (contact news@mayn.de if permission denied)
+# *PGP* See comment at top of file.
+newgroup:*:ufra.*:drop
+rmgroup:*:ufra.*:drop
+checkgroups:news@mayn.de:ufra.*:verify-news.mayn.de
+newgroup:news@mayn.de:ufra.*:verify-news.mayn.de
+rmgroup:news@mayn.de:ufra.*:verify-news.mayn.de
+
+## UIUC (*LOCAL* -- University of Illinois at Urbana-Champaign, USA)
+# Contact: news@ks.uiuc.edu
+# For local use only, contact the above address for information.
+newgroup:*:uiuc.*:mail
+rmgroup:*:uiuc.*:doit
+
+## UK (United Kingdom of Great Britain and Northern Ireland)
+# URL: http://www.usenet.org.uk/
+# Admin group: uk.net.news.announce
+# Key URL: http://www.usenet.org.uk/newsadmins.html
+# *PGP* See comment at top of file.
+newgroup:*:uk.*:drop
+rmgroup:*:uk.*:drop
+checkgroups:control@usenet.org.uk:uk.*:verify-uk.net.news.announce
+newgroup:control@usenet.org.uk:uk.*:verify-uk.net.news.announce
+rmgroup:control@usenet.org.uk:uk.*:verify-uk.net.news.announce
+
+## UKR (Ukraine)
+checkgroups:ay@sita.kiev.ua:ukr.*:doit
+newgroup:ay@sita.kiev.ua:ukr.*:doit
+rmgroup:ay@sita.kiev.ua:ukr.*:doit
+
+## UMICH (University of Michigan, USA)
+checkgroups:*@*.umich.edu:umich.*:doit
+newgroup:*@*.umich.edu:umich.*:doit
+rmgroup:*@*.umich.edu:umich.*:doit
+
+## UMN (University of Minnesota, USA)
+newgroup:edh@*.tc.umn.edu:umn.*:doit
+newgroup:news@*.tc.umn.edu:umn.*:doit
+newgroup:Michael.E.Hedman-1@umn.edu:umn.*:doit
+newgroup:edh@*.tc.umn.edu:umn*class.*:mail
+newgroup:news@*.tc.umn.edu:umn*class.*:mail
+newgroup:Michael.E.Hedman-1@umn.edu:umn*class.*:mail
+rmgroup:news@*.tc.umn.edu:umn.*:doit
+rmgroup:edh@*.tc.umn.edu:umn.*:doit
+rmgroup:Michael.E.Hedman-1@umn.edu:umn.*:doit
+
+## UN (The United Nations)
+# Admin group: un.public.usenet.admin
+# *PGP* See comment at top of file.
+newgroup:*:un.*:drop
+rmgroup:*:un.*:drop
+checkgroups:news@news.itu.int:un.*:verify-ungroups@news.itu.int
+newgroup:news@news.itu.int:un.*:verify-ungroups@news.itu.int
+rmgroup:news@news.itu.int:un.*:verify-ungroups@news.itu.int
+
+## UO (University of Oregon, Eugene, Oregon, USA)
+checkgroups:newsadmin@news.uoregon.edu:uo.*:doit
+newgroup:newsadmin@news.uoregon.edu:uo.*:doit
+rmgroup:newsadmin@news.uoregon.edu:uo.*:doit
+
+## US (United States of America)
+# Contact: admin@usenetnews.us
+# URL: http://www.usenetnews.us/
+# Admin group: us.config
+checkgroups:control@usenetnews.us:us.*:doit
+newgroup:control@usenetnews.us:us.*:doit
+rmgroup:control@usenetnews.us:us.*:doit
+
+## UT (*LOCAL* -- University of Toronto, Canada)
+# URL: http://www.utoronto.ca/ns/utornews/
+#newgroup:news@ecf.toronto.edu:ut.*:doit
+#newgroup:news@ecf.toronto.edu:ut.class.*:mail
+#rmgroup:news@ecf.toronto.edu:ut.*:doit
+
+## UTA (Finnish)
+checkgroups:news@news.cc.tut.fi:uta.*:doit
+newgroup:news@news.cc.tut.fi:uta.*:doit
+rmgroup:news@news.cc.tut.fi:uta.*:doit
+
+## UTEXAS (*LOCAL* -- University of Texas, USA)
+# URL: http://www.utexas.edu/its/usenet/index.php
+newgroup:fletcher@cs.utexas.edu:utexas.*:doit
+newgroup:news@geraldo.cc.utexas.edu:utexas.*:doit
+newgroup:fletcher@cs.utexas.edu:utexas*class.*:mail
+newgroup:news@geraldo.cc.utexas.edu:utexas*class.*:mail
+rmgroup:fletcher@cs.utexas.edu:utexas.*:doit
+rmgroup:news@geraldo.cc.utexas.edu:utexas.*:doit
+
+## UTWENTE (*LOCAL* -- University of Twente, Netherlands)
+# Contact: newsmaster@utwente.nl
+# For local use only, contact the above address for information.
+newgroup:*:utwente.*:mail
+rmgroup:*:utwente.*:doit
+
+## UVA (*LOCAL* -- University of Virginia, USA)
+# Contact: usenet@virginia.edu
+# For local use only, contact the above address for information.
+newgroup:*:uva.*:mail
+rmgroup:*:uva.*:doit
+
+## UW (University of Waterloo, Canada)
+# Admin group: uw.newsgroups
+# Syncable server: news.uwaterloo.ca
+# *PGP* See comment at top of file.
+newgroup:*:uw.*:drop
+rmgroup:*:uw.*:drop
+checkgroups:newsgroups@news.uwaterloo.ca:uw.*:verify-uw.newsgroups
+newgroup:newsgroups@news.uwaterloo.ca:uw.*:verify-uw.newsgroups
+rmgroup:newsgroups@news.uwaterloo.ca:uw.*:verify-uw.newsgroups
+
+## UWARWICK (*LOCAL* -- University of Warwick, UK)
+# Contact: Jon Harley <news@csv.warwick.ac.uk>
+# For local use only, contact the above address for information.
+newgroup:*:uwarwick.*:mail
+rmgroup:*:uwarwick.*:doit
+
+## UWO (University of Western Ontario, London, Canada)
+# URL: http://www.uwo.ca/its/news/groups.uwo.html
+checkgroups:reggers@julian.uwo.ca:uwo.*:doit
+newgroup:reggers@julian.uwo.ca:uwo.*:doit
+rmgroup:reggers@julian.uwo.ca:uwo.*:doit
+
+## VAN (Vancouver, British Columbia, Canada)
+checkgroups:bc_van_usenet@fastmail.ca:van.*:doit
+newgroup:bc_van_usenet@fastmail.ca:van.*:doit
+rmgroup:bc_van_usenet@fastmail.ca:van.*:doit
+
+## VEGAS (Las Vegas, Nevada, USA)
+checkgroups:cshapiro@netcom.com:vegas.*:doit
+checkgroups:doctor@netcom.com:vegas.*:doit
+newgroup:cshapiro@netcom.com:vegas.*:doit
+newgroup:doctor@netcom.com:vegas.*:doit
+rmgroup:cshapiro@netcom.com:vegas.*:doit
+rmgroup:doctor@netcom.com:vegas.*:doit
+
+## VGC (Japan groups?)
+checkgroups:news@isl.melco.co.jp:vgc.*:doit
+newgroup:news@isl.melco.co.jp:vgc.*:doit
+rmgroup:news@isl.melco.co.jp:vgc.*:doit
+
+## VMSNET (VMS Operating System)
+checkgroups:cts@dragon.com:vmsnet.*:doit
+newgroup:cts@dragon.com:vmsnet.*:doit
+rmgroup:cts@dragon.com:vmsnet.*:doit
+
+## WA (Western Australia)
+# Contact: ausadmin@aus.news-admin.org
+# URL: http://wa.news-admin.org/
+# Key URL: http://aus.news-admin.org/ausadmin.asc
+# *PGP* See comment at top of file.
+newgroup:*:wa.*:drop
+rmgroup:*:wa.*:drop
+checkgroups:ausadmin@aus.news-admin.org:wa.*:verify-ausadmin@aus.news-admin.org
+newgroup:ausadmin@aus.news-admin.org:wa.*:verify-ausadmin@aus.news-admin.org
+rmgroup:ausadmin@aus.news-admin.org:wa.*:verify-ausadmin@aus.news-admin.org
+
+## WADAI (Japanese ?)
+checkgroups:kohe-t@*wakayama-u.ac.jp:wadai.*:doit
+newgroup:kohe-t@*wakayama-u.ac.jp:wadai.*:doit
+rmgroup:kohe-t@*wakayama-u.ac.jp:wadai.*:doit
+
+## WALES (Wales)
+# Contact: committee@wales-usenet.org
+# URL: http://www.wales-usenet.org/
+# Admin group: wales.usenet.config
+# Key URL: http://www.wales-usenet.org/english/newsadmin.txt
+# Key fingerprint = 2D 9E DE DF 12 DA 34 5C 49 E1 EE 28 E3 AB 0D AD
+# *PGP* See comment at top of file.
+newgroup:*:wales.*:drop
+rmgroup:*:wales.*:drop
+checkgroups:control@wales-usenet.org:wales.*:verify-wales-usenet
+newgroup:control@wales-usenet.org:wales.*:verify-wales-usenet
+rmgroup:control@wales-usenet.org:wales.*:verify-wales-usenet
+
+## WASH (Washington State, USA)
+checkgroups:graham@ee.washington.edu:wash.*:doit
+newgroup:graham@ee.washington.edu:wash.*:doit
+rmgroup:graham@ee.washington.edu:wash.*:doit
+
+## WEST-VIRGINIA (West Virginia, USA)
+# Note: checkgroups only by bryan27, not mark.
+checkgroups:bryan27@hgo.net:west-virginia.*:doit
+newgroup:mark@bluefield.net:west-virginia.*:doit
+newgroup:bryan27@hgo.net:west-virginia.*:doit
+rmgroup:mark@bluefield.net:west-virginia.*:doit
+rmgroup:bryan27@hgo.net:west-virginia.*:doit
+
+## WORLDONLINE (*LOCAL* -- ?)
+# Contact: newsmaster@worldonline.nl
+# For local use only, contact the above address for information.
+newgroup:*:worldonline.*:mail
+rmgroup:*:worldonline.*:doit
+
+## WPG (Winnipeg, Manitoba, Canada)
+# Contact: Gary Mills <mills@cc.umanitoba.ca>
+checkgroups:mills@cc.umanitoba.ca:wpg.*:doit
+newgroup:mills@cc.umanitoba.ca:wpg.*:doit
+rmgroup:mills@cc.umanitoba.ca:wpg.*:doit
+
+## WPI (*LOCAL* -- Worcester Polytechnic Institute, Worcester, MA)
+# For local use only.
+newgroup:aej@*.wpi.edu:wpi.*:mail
+rmgroup:aej@*.wpi.edu:wpi.*:doit
+
+## WU (Washington University at St. Louis, MO)
+checkgroups:*@*.wustl.edu:wu.*:doit
+newgroup:*@*.wustl.edu:wu.*:doit
+rmgroup:*@*.wustl.edu:wu.*:doit
+
+## X-PRIVAT (Italian)
+# Contact: dmitry@x-privat.org
+# URL: http://www.x-privat.org/
+# Admin group: x-privat.info
+# Key URL: http://www.x-privat.org/dmitry.asc
+# Key fingerprint = 9B 0A 7E 68 27 80 C7 96 47 6B 03 90 51 05 68 43
+# *PGP* See comment at top of file.
+newgroup:*:x-privat.*:drop
+rmgroup:*:x-privat.*:drop
+checkgroups:dmitry@x-privat.org:x-privat.*:verify-dmitry@x-privat.org
+newgroup:dmitry@x-privat.org:x-privat.*:verify-dmitry@x-privat.org
+rmgroup:dmitry@x-privat.org:x-privat.*:verify-dmitry@x-privat.org
+
+## XS4ALL (XS4ALL, Netherlands)
+# Contact: Cor Bosman <news@xs4all.nl>
+checkgroups:news@*xs4all.nl:xs4all.*:doit
+newgroup:news@*xs4all.nl:xs4all.*:doit
+rmgroup:news@*xs4all.nl:xs4all.*:doit
+
+## YORK (*LOCAL* -- York University, Toronto, ON)
+# Contact: Peter Marques <news@yorku.ca>
+# For local use only, contact the above address for information.
+newgroup:*:york.*:mail
+rmgroup:*:york.*:doit
+
+## Z-NETZ (German non-Internet based network)
+# Contact: teko@dinoex.sub.org
+# Admin group: z-netz.koordination.user+sysops
+# Key URL: ftp://ftp.dinoex.de/pub/keys/z-netz.koordination.user+sysops.asc
+# *PGP* See comment at top of file.
+newgroup:*:z-netz.*:drop
+rmgroup:*:z-netz.*:drop
+checkgroups:teko@dinoex.sub.org:z-netz.*:verify-z-netz.koordination.user+sysops
+newgroup:teko@dinoex.sub.org:z-netz.*:verify-z-netz.koordination.user+sysops
+rmgroup:teko@dinoex.sub.org:z-netz.*:verify-z-netz.koordination.user+sysops
+
+## ZA (South Africa)
+checkgroups:ccfj@hippo.ru.ac.za:za.*:doit
+checkgroups:root@duvi.eskom.co.za:za.*:doit
+newgroup:ccfj@hippo.ru.ac.za:za.*:doit
+newgroup:root@duvi.eskom.co.za:za.*:doit
+rmgroup:ccfj@hippo.ru.ac.za:za.*:doit
+rmgroup:root@duvi.eskom.co.za:za.*:doit
+
+## ZER (*DEFUNCT* -- Germany)
+# This hierarchy is defunct. Please remove it.
+newgroup:*:zer.*:mail
+rmgroup:*:zer.*:doit
--- /dev/null
+#
+# Meta cnfs cyclic buffer configuration file (and assignments of newsgroups to
+# metacyclic buffers)
+#
+# The order of lines in this file is not important among the same item.
+# But all cycbuff item should be presented before any metacycbuff item.
+
+# 1. Cyclic buffers
+# Format:
+# "cycbuff" (literally) : symbolic buffer name : path to buffer file :
+# length of symbolic buffer in kilobytes in decimal (1KB = 1024 bytes)
+
+cycbuff:ONE:/export/cycbuffs/one:512000
+cycbuff:TWO:/export/cycbuffs/two:512000
+cycbuff:THREE:/export/cycbuffs/three:512000
+
+# 2. Meta-cyclic buffers
+# Format:
+# "metacycbuff" (literally) : symbolic meta-cyclic buffer name :
+# comma separated list of cyclic buffer symbolic names
+#
+# symbolic meta-cyclic buffer names are used in storage.conf in the
+# options field
+
+metacycbuff:BIGAREA:ONE,TWO
+metacycbuff:SMALLAREA:THREE
--- /dev/null
+## $Revision: 6312 $
+## distrib.pats -- specify default Distribution header for newsgroups
+## Format:
+## <weight>:<pattern>:<value>
+## All articles are matched against all patterns, value to be used is the
+## one with the highest weight.
+## <weight> The weight assigned to this match, integer
+## <pattern> Newsgroup name or single wildmat(3) pattern
+## <value> Value of Distribution header.
+##
+##
+## Uncomment to default all local.* groups to a distribution of local.
+#10:local.*:local
--- /dev/null
+## $Revision: 7143 $
+## expire.ctl - expire control file
+## Format:
+## /remember/:<keep>
+## <class>:<min>:<default>:<max>
+## <wildmat>:<flag>:<min>:<default>:<max>
+## First line gives history retention; second line specifies expiration
+## for classes; third line specifies expiration for group if groupbaseexpiry
+## is true
+## <class> class specified in storage.conf
+## <wildmat> wildmat-style patterns for the newsgroups
+## <min> Mininum number of days to keep article
+## <default> Default number of days to keep the article
+## <max> Flush article after this many days
+## <min>, <default>, and <max> can be floating-point numbers or the
+## word "never." Times are based on when received unless -p is used;
+## see expire.8
+
+## If article expires before 10 days, we still remember it for 10 days in
+## case we get offered it again. Depending on what you use for the innd
+## -c flag and how paranoid you are about old news, you might want to
+## make this 28, 30, etc, but it's probably safe to reduce it to 7 in most
+## cases if you want to keep your history file smaller.
+/remember/:10
+
+## Keep for 1-10 days, allow Expires headers to work. This entry uses
+## the syntax appropriate when groupbaseexpiry is true in inn.conf.
+*:A:1:10:never
+
+## Keep for 1-10 days, allow Expires headers to work. This is an entry
+## based on storage class, used when groupbaseexpiry is false.
+#0:1:10:never
--- /dev/null
+# -*- tcl -*-
+#
+# $Revision: 4171 $
+#
+# A TCL procedure that will be run over every article. See doc/hook-tcl
+# for more details.
+
+proc filter_news {} {
+# global o Headers
+# set sum [checksum_article]
+# puts $o "$Headers(Message-ID) $sum"
+# set newsgroups [split $Headers(Newsgroups) ,]
+# foreach i $newsgroups {
+# if {$i=="alt.test" && [string match "*heiney@pa.dec.com*" $Headers(From)]} {
+# return "dont like alt.test from heiney"
+# }
+# }
+ return "accept"
+}
--- /dev/null
+#
+# $Id: filter_innd.pl 7860 2008-06-07 12:46:49Z iulius $
+#
+# Sample Perl filtering file for the innd hooks.
+#
+
+# This file gets loaded at innd process startup, and everytime a
+# "ctlinnd reload filter.perl 'reason'" or a
+# "ctlinnd reload all 'reason'" is done.
+#
+# Before this file is loaded, the perl routine `filter_before_reload' is
+# called, and after it's finished loading, the perl routine
+# `filter_after_reload' is called. See startup_innd.pl for more details.
+#
+# The following routines can be defined here for use by innd:
+#
+# sub filter_art { ... }
+#
+# This routine is called before every article is accepted for
+# posting. Is is called with no arguments, but has access to
+# all the non-empty standard headers of the article via the
+# global associative array `%hdr.' If it returns the empty
+# string ("") then the article is accepted. If it returns any
+# non-null string value, then the article is rejected and the
+# returned string value is logged as the reason why.
+#
+# The standard headers are:
+#
+# Approved, Control, Date, Distribution, Expires,
+# From, Lines, Message-ID, Newsgroups, Path,
+# Reply-To, Sender, Subject, Supersedes, Bytes,
+# Also-Control, References
+#
+# sub filter_mode { ... }
+#
+# This routine is called every time `go', `pause', or
+# `throttle' is called. It is called with no arguments and
+# returns no value. The global associative array `%mode' has
+# three keyed values stored in it:
+#
+# 'Mode' The current mode
+# ("running", "paused", "throttled")
+# 'NewMode" The new mode
+# 'reason' The reason given.
+#
+# For example: %mode = ('Mode', 'running',
+# 'NewMode', 'throttled',
+# 'reason', 'doing nightly backups')
+#
+# If filter_art is not defined when this file is done loading, then
+# filtering is disabled. If any syntax error occurs when loading the file,
+# then filtering is disabled.
+#
+# sub filter_messageid { ... }
+#
+# This routine is called when each article (in streaming
+# mode only) is checked to see if INN wants to accept the
+# article. If it returns the empty string, the article
+# is accepted. If it returns a non-empty value, the
+# article is refused. It is called with one argument,
+# the message-id to check.
+
+
+
+#
+# Called on each article innd receives from a peer. Return "" to accept,
+# and any other non-null string to reject. If rejecting the string returned
+# will be part of the logged reason.
+#
+
+sub filter_art {
+ my $rval = "" ; # Assume we'll accept. Cannot be `0'
+
+### Remove two leading '##' from the following section (and then
+### "ctlinnd reload filter.perl 'reason'" and the filter will reject articles that
+### have "make money" in the subject, or are posted to more than 10
+### newsgroups.
+
+## my ($maxgroups) = 10 ;
+##
+### Normally this output would be lost, but if you run innd with '-d -f' you
+### can see what's going on.
+###
+### foreach $key (sort keys %hdr) {
+### print "Header:\t$key Value:\t $hdr{$key}\n" ;
+### }
+##
+## if ($hdr{"Subject"} =~ /\$*make.*money.*\$*/i ) {
+## $rval = "no money requests here"
+## } elsif ( ( @_ = split(",",$hdr{'Newsgroups'}) ) > $maxgroups ) {
+## $rval = "too many groups" ;
+### Kill article with "Re: " but no References:
+## } elsif ($hdr{'Subject'} =~ /^Re: /o and $hdr{'References'} eq "") {
+## $rval = "Followup without References:";
+### Kill article with invalid From:
+## } elsif ($hdr{'From'} =~ /^\w*$/o or
+## $hdr{'From'} !~ /^(.+?)\@([-\w\d]+\.)*([-\w\d]+)\.([-\w\d]{2,})$/o) {
+## $rval = "From: is invalid, must be user\@[host.]domain.tld";
+## }
+###
+### print "Accepting\n" if ! $rval ;
+
+ $rval ;
+}
+
+sub filter_mode {
+ if ($mode{'NewMode'} eq "throttled" || $mode{'NewMode'} eq "paused") {
+# print "Closing spam database\n" ; # won't kill server.
+# &close_spam_database ;
+ } else {
+# print "Opening spam database\n" ; # won't kill server
+# &open_spam_database ;
+ }
+}
+
+sub filter_messageid {
+ my ($messageid) = @_;
+ $rval = '';
+# $rval = 'No' if ($messageid =~ /a\.spam\.domain>?/i);
+ $rval;
+}
+
+
+
+
+
+###########################################################################
+##
+## Another sample. More elaborate, but cleaner... from Christophe
+## Wolfhugel <wolf@pasteur.fr>.
+##
+
+
+#### Regular expressions we reject.
+#### Format : Header => regexp => reason
+##%reject = (
+## 'Subject' => {
+## 'make.*money.*fast' => 'MMF rejected',
+## 'cash.*cash.*cash' => 'Cash rejected'
+## },
+##);
+##
+##sub filter_art {
+## my($rval) = '';
+## my(@ng, $i, $j, $k, $l);
+##
+## if ($hdr{'From'} !~ /\@/o) {
+## $rval = 'Invalid From';
+## } else {
+## while (($i, $j) = each %reject) {
+## while (($k, $l) = each %{$j}) {
+## if ($hdr{$i} =~ /$k/i) {
+## $rval = $l;
+## goto the_end;
+## }
+## }
+## }
+## }
+## @ng = split(/,/, $hdr{'Newsgroups'});
+## if ($#ng > 10) {
+## $rval = 'ECP rejected';
+## }
+##the_end:
+## undef %hdr;
+## return $rval
+##}
+##
+##sub filter_mode {
+##}
+##
+###%hdr = (
+### 'Subject' => 'Make money fast',
+### 'From' => 'bozo@gov.org'
+###);
+###&filter_art;
+
+
+
+###########################################################################
+##
+## From Chrisophe Wolfhugel again (wolf@pasteur.fr). This is not
+## standalone code.
+##
+
+##Just for the fun, I've added following code to filter_innd.pl :
+##
+## ## Keep track of the From and subject.
+## $i = "$hdr{'From'} $hdr{'Subject'}";
+## push(@history, $i);
+## $history{$i}++;
+##
+## ## Reject the EMP.
+## if ($history{$i} > 10) {
+## $rval = "EMP rejected (appeared $history{$i} times): $i";
+## }
+##
+## ## Remove too old things.
+## while ($#history > 1000) {
+## delete($history{shift(@history)});
+## }
+##
+##It is pretty successfull in detecting and refusing excessive multi-posting.
+##Same sender, same subject, appearing more than 10 times without the last
+##1000 articles gets junked.
+##
+##Already catched a few hundreds :
+##
+##Nov 20 08:27:23.175 - vishnu.jussieu.fr <3292ac9a.4064710@nntp.cts.com> 437 EMP rejected (btr@trenet.com Be a Beta Tester!)
+##
+##That was just for the pleasure. It is still sucking a non significant CPU
+##time on my slow Alpha.
+
--- /dev/null
+## $Id: filter_innd.py 7903 2008-06-22 20:41:59Z iulius $
+##
+## This is a sample filter for the Python innd hook.
+##
+## See the INN Python Filtering and Authentication Hooks documentation
+## for more information.
+##
+## You have access to the following methods from the module INN:
+## - addhist(message-id)
+## - article(message-id)
+## - cancel(message-id)
+## - havehist(message-id)
+## - hashstring(string)
+## - head(message-id)
+## - newsgroup(groupname)
+## - set_filter_hook(instance)
+## - syslog(level, message)
+
+import re
+from string import *
+
+## This looks weird, but creating and interning these strings should
+## let us get faster access to header keys (which innd also interns) by
+## losing some strcmps under the covers.
+Also_Control = intern("Also-Control")
+Approved = intern("Approved")
+Bytes = intern("Bytes")
+Cancel_Key = intern("Cancel-Key")
+Cancel_Lock = intern("Cancel-Lock")
+Content_Base = intern("Content-Base")
+Content_Disposition = intern("Content-Disposition")
+Content_Transfer_Encoding = intern("Content-Transfer-Encoding")
+Content_Type = intern("Content-Type")
+Control = intern("Control")
+Date = intern("Date")
+Date_Received = intern("Date-Received")
+Distribution = intern("Distribution")
+Expires = intern("Expires")
+Face = intern("Face")
+Followup_To = intern("Followup-To")
+From = intern("From")
+In_Reply_To = intern("In-Reply-To")
+Injection_Date = intern("Injection-Date")
+Injection_Info = intern("Injection-Info")
+Keywords = intern("Keywords")
+Lines = intern("Lines")
+List_ID = intern("List-ID")
+Message_ID = intern("Message-ID")
+MIME_Version = intern("MIME-Version")
+Newsgroups = intern("Newsgroups")
+NNTP_Posting_Date = intern("NNTP-Posting-Date")
+NNTP_Posting_Host = intern("NNTP-Posting-Host")
+Organization = intern("Organization")
+Originator = intern("Originator")
+Path = intern("Path")
+Posted = intern("Posted")
+Posting_Version = intern("Posting-Version")
+Received = intern("Received")
+References = intern("References")
+Relay_Version = intern("Relay-Version")
+Reply_To = intern("Reply-To")
+Sender = intern("Sender")
+Subject = intern("Subject")
+Supersedes = intern("Supersedes")
+User_Agent = intern("User-Agent")
+X_Auth = intern("X-Auth")
+X_Canceled_By = intern("X-Canceled-By")
+X_Cancelled_By = intern("X-Cancelled-By")
+X_Complaints_To = intern("X-Complaints-To")
+X_Face = intern("X-Face")
+X_HTTP_UserAgent = intern("X-HTTP-UserAgent")
+X_HTTP_Via = intern("X-HTTP-Via")
+X_Mailer = intern("X-Mailer")
+X_Modbot = intern("X-Modbot")
+X_Modtrace = intern("X-Modtrace")
+X_Newsposter = intern("X-Newsposter")
+X_Newsreader = intern("X-Newsreader")
+X_No_Archive = intern("X-No-Archive")
+X_Original_Message_ID = intern("X-Original-Message-ID")
+X_Original_Trace = intern("X-Original-Trace")
+X_Originating_IP = intern("X-Originating-IP")
+X_PGP_Key = intern("X-PGP-Key")
+X_PGP_Sig = intern("X-PGP-Sig")
+X_Poster_Trace = intern("X-Poster-Trace")
+X_Postfilter = intern("X-Postfilter")
+X_Proxy_User = intern("X-Proxy-User")
+X_Submissions_To = intern("X-Submissions-To")
+X_Trace = intern("X-Trace")
+X_Usenet_Provider = intern("X-Usenet-Provider")
+Xref = intern("Xref")
+__BODY__ = intern("__BODY__")
+_LINES__ = intern("__LINES__")
+
+
+class InndFilter:
+ """Provide filtering callbacks to innd."""
+
+ def __init__(self):
+ """This runs every time the filter is loaded or reloaded.
+ This is a good place to initialize variables and precompile
+ regular expressions, or maybe reload stats from disk.
+ """
+ self.re_newrmgroup = re.compile('(?:new|rm)group\s')
+ self.re_obsctl = re.compile('(?:sendsys|version|uuname)')
+ # Message-ID pattern from a once-common spambot.
+ self.re_none44 = re.compile('none\d+\.yet>')
+ # There is a mad newgrouper who likes to meow.
+ self.re_meow = re.compile("^Meow\!", re.M)
+ # One of my silly addresses.
+ self.re_fluffymorph = re.compile("andruQ@myremarQ.coM", re.I)
+
+ def filter_before_reload(self):
+ """Runs just before the filter gets reloaded.
+
+ You can use this method to save state information to be
+ restored by the __init__() method or down in the main module.
+ """
+ syslog('notice', "filter_before_reload executing...")
+
+ def filter_close(self):
+ """Runs when innd exits.
+
+ You can use this method to save state information to be
+ restored by the __init__() method or down in the main module.
+ """
+ syslog('notice', "filter_close running, bye!")
+
+ def filter_messageid(self, msgid):
+ """Filter articles just by their Message-IDs.
+
+ This method interacts with the IHAVE and CHECK NNTP commands.
+ If you return a non-empty string here, the offered article
+ will be refused before you ever have to waste any bandwidth
+ looking at it. This is not foolproof, so you should do your
+ ID checks both here and in filter_art. (TAKETHIS does not
+ offer the ID for examination, and a TAKETHIS isn't always
+ preceded by a CHECK.)
+ """
+ return "" # Deactivate the samples.
+
+ if self.re_none44.search(msgid):
+ return "But I don't like spam!"
+ if msgid[0:8] == '<cancel.':
+ return "I don't do cybercancels."
+
+ def filter_art(self, art):
+ """Decide whether to keep offered articles.
+
+ art is a dictionary with a bunch of headers, the article's
+ body, and innd's reckoning of the line count. Items not
+ in the article will have a value of None.
+
+ The available headers are the ones listed near the top of
+ innd/art.c. At this writing, they are:
+
+ Also-Control, Approved, Bytes, Cancel-Key, Cancel-Lock,
+ Content-Base, Content-Disposition, Content-Transfer-Encoding,
+ Content-Type, Control, Date, Date-Received, Distribution, Expires,
+ Face, Followup-To, From, In-Reply-To, Injection-Date, Injection-Info,
+ Keywords, Lines, List-ID, Message-ID, MIME-Version, Newsgroups,
+ NNTP-Posting-Date, NNTP-Posting-Host, Organization, Originator,
+ Path, Posted, Posting-Version, Received, References, Relay-Version,
+ Reply-To, Sender, Subject, Supersedes, User-Agent,
+ X-Auth, X-Canceled-By, X-Cancelled-By, X-Complaints-To, X-Face,
+ X-HTTP-UserAgent, X-HTTP-Via, X-Mailer, X-Modbot, X-Modtrace,
+ X-Newsposter, X-Newsreader, X-No-Archive, X-Original-Message-ID,
+ X-Original-Trace, X-Originating-IP, X-PGP-Key, X-PGP-Sig,
+ X-Poster-Trace, X-Postfilter, X-Proxy-User, X-Submissions-To,
+ X-Trace, X-Usenet-Provider, Xref.
+
+ The body is the buffer in art['__BODY__'] and the INN-reckoned
+ line count is held as an integer in art['__LINES__']. (The
+ Lines: header is often generated by the poster, and large
+ differences can be a good indication of a corrupt article.)
+
+ If you want to keep an article, return None or "". If you
+ want to reject, return a non-empty string. The rejection
+ string will appear in transfer and posting response banners,
+ and local posters will see them if their messages are
+ rejected.
+ """
+ return "" # Deactivate the samples.
+
+ # Catch bad Message-IDs from articles fed with TAKETHIS but no CHECK.
+ idcheck = self.filter_messageid(art[Message_ID])
+ if idcheck:
+ return idcheck
+
+ # There are some control messages we don't want to process or
+ # forward to other sites.
+ try:
+ if art[Control] is not None:
+ if self.re_newrmgroup.match(art[Control]):
+ if self.re_meow.search(art[__BODY__]):
+ return "The fake tale meows again."
+ if art[Distribution] == buffer('mxyzptlk'):
+ return "Evil control message from the 10th dimension"
+ if self.re_obsctl.match(art[Control]):
+ return "Obsolete control message"
+
+ # If you don't know, you don't want to know.
+ if self.re_fluffymorph.search(art[From]):
+ return "No, you may NOT meow."
+ except:
+ syslog('n', str(sys.exc_info[1]))
+
+ def filter_mode(self, oldmode, newmode, reason):
+ """Capture server events and do something useful.
+
+ When the admin throttles or pauses innd (and lets it go
+ again), this method will be called. oldmode is the state we
+ just left, and newmode is where we are going. reason is
+ usually just a comment string.
+
+ The possible values of newmode and oldmode are the five
+ strings 'running', 'paused', 'throttled', 'shutdown' and
+ 'unknown'. Actually 'unknown' shouldn't happen; it's there
+ in case feeping creatures invade innd.
+ """
+ syslog('notice', 'state change from %s to %s - %s'
+ % (oldmode, newmode, reason))
+
+
+"""
+Okay, that's the end of our class definition. What follows is the
+stuff you need to do to get it all working inside innd.
+"""
+
+## This import must succeed, or your filter won't work. I'll repeat
+## that: You MUST import INN.
+from INN import *
+
+## Some of the stuff below is gratuitous, just demonstrating how the
+## INN.syslog call works. That first thingy tells the Unix syslogger
+## what severity to use; you can abbreviate down to one letter and
+## it's case insensitive. Available levels are (in increasing levels
+## of seriousness) Debug, Info, Notice, Warning, Err, Crit, and
+## Alert. If you provide any other string, it will be defaulted to
+## Notice. You'll find the entries in the same log files innd itself
+## uses, with an 'innd: python:' prefix.
+##
+## The native Python syslog module seems to clash with INN, so use
+## INN's. Oh yeah -- you may notice that stdout and stderr have been
+## redirected to /dev/null -- if you want to print stuff, open your
+## own files.
+
+try:
+ import sys
+except Exception, errmsg:
+ syslog('Error', "import boo-boo: " + errmsg[0])
+
+
+## If you want to do something special when the server first starts
+## up, this is how to find out when it's time.
+
+if 'spamfilter' not in dir():
+ syslog('n', "First load, so I can do initialization stuff.")
+ # You could unpickle a saved hash here, so that your hard-earned
+ # spam scores aren't lost whenever you shut down innd.
+else:
+ syslog('NoTicE', "I'm just reloading, so skip the formalities.")
+
+
+## Finally, here is how we get our class on speaking terms with innd.
+## The hook is refreshed on every reload, so that you can change the
+## methods on a running server. Don't forget to test your changes
+## before reloading!
+spamfilter = InndFilter()
+try:
+ set_filter_hook(spamfilter)
+ syslog('n', "spamfilter successfully hooked into INN")
+except Exception, errmsg:
+ syslog('e', "Cannot obtain INN hook for spamfilter: %s" % errmsg[0])
+
--- /dev/null
+#
+# $Id: filter_nnrpd.pl 5981 2002-12-12 05:01:42Z vinocur $
+#
+# Sample perl filtering code for nnrpd hook.
+#
+
+#
+# This file is loaded when nnrpd starts up. If it defines a sub named
+# `filter_post', then that function will be called during processing of a
+# posting. It has access to the headers of the article via the associative
+# array `%hdr'. If it returns a null string then the article is accepted
+# for posting. A non-null string rejects it, and the value returned is used
+# in the rejection message.
+#
+
+#
+# Do any initialization steps.
+#
+my %config = (checkincludedtext => 0,
+ includedcutoff => 40,
+ includedratio => 0.6,
+ quotere => '^[>:]',
+ antiquotere => '^[<]', # so as not to reject dict(1) output
+ );
+
+
+#
+# Sample filter
+#
+sub filter_post {
+ my $rval = "" ; # assume we'll accept.
+
+### Uncomment this next block to reject articles that have 'make money'
+### in their subject, or which have a "Re: " subject, but no References:
+### header, or which have an invalid From.
+
+## if ($hdr{"Subject"} =~ /make.*money/i) {
+## $rval = "Spam is not acceptable here..." ;
+## } elsif ($hdr{'Subject'} =~ /^Re: /o and $hdr{'References'} eq "") {
+## $rval = "Followup without References:";
+## } elsif ($hdr{'From'} =~ /^\w*$/o or
+## $hdr{'From'} !~ /^(.+?)\@([-\w\d]+\.)*([-\w\d]+)\.([-\w\d]{2,})$/o) {
+## $rval = "From: is invalid, must be user\@[host.]domain.tld";
+## }
+
+
+### The next block rejects articles with too much quoted text, if the
+### config hash directs it to.
+
+ if ($config{checkincludedtext}) {
+ my ($lines, $quoted, $antiquoted) = analyze($body);
+ if ($lines > $config{includedcutoff}
+ && $quoted - $antiquoted > $lines * $config{includedratio}) {
+ $rval = "Article contains too much quoted text";
+ }
+ }
+
+ return $rval;
+}
+
+sub analyze {
+ my ($lines, $quoted, $antiquoted) = (0, 0, 0);
+ local $_ = shift;
+
+ do {
+ if ( /\G$config{quotere}/mgc ) {
+ $quoted++;
+ } elsif ( /\G$config{antiquotere}/mgc ) {
+ $antiquoted++;
+ }
+ } while ( /\G(.*)\n/gc && ++$lines );
+
+ return ($lines, $quoted, $antiquoted);
+}
--- /dev/null
+## $Revision: 2372 $
+## incoming.conf - names and addresses that feed us news
+##
+## This file consists of three types of entries: key/value, peer and group.
+## Comments are taken from the hash character ``#'' to the end of the line.
+## Blank lines are ignored.
+##
+## Key/value entries are a keyword immediatly followed by a colon, at least
+## one blank and a value. For example:
+##
+## max-connections: 10
+##
+## A legal key contains nor blanks, nor colon, nor ``#''.
+## There are 5 different type of values: integers, booleans, and strings.
+## Integers are as to be expected. A boolean value is either ``true'' or
+## ``false'' (case is significant). A string value is any other sequence of
+## characters. If the string needs to contain whitespace, then it must be
+## quoted with double quotes.
+##
+## Peer entries look like:
+##
+## peer <name> {
+## # body
+## }
+##
+## The word ``peer'' is required. <name> is a label for this peer. It is
+## any string valid as a key. The body of a peer entry contains some number
+## of key/value entries.
+##
+## Group entries look like:
+##
+## group <name> {
+## # body
+## }
+##
+## The word ``group'' is required. The ``<name>'' is any string valid as a
+## key. The body of a group entry contains any number of the three types of
+## entries. So key/value pairs can be defined inside a group, and peers can
+## be nested inside a group, and other groups can be nested inside a group.
+##
+## Key/value entries that are defined outside of all peer and group entries
+## are said to be at ``global scope''. Global key/value entries act as
+## defaults for peers. When innd looks for a specific value in a peer entry
+## (for example, the maximum number of connections to allow), if the value
+## is not defined in the peer entry, then the enclosing groups are examined
+## for the entry (starting at the closest enclosing group). If there are no
+## enclosing groups, or the enclosing groups don't define the key/value,
+## then the value at global scope is used.
+##
+## A small example could be:
+##
+## # Global value applied to all peers that have no value of their own.
+## max-connections: 5
+##
+## # A peer definition.
+## peer uunet {
+## hostname: usenet1.uu.net
+## }
+##
+## peer vixie {
+## hostname: gw.home.vix.com
+## max-connections: 10 # override global value.
+## }
+##
+## # A group of two peers who can open more connections than normal
+## group fast-sites {
+## max-connections: 15
+##
+## # Another peer. The ``max-connections'' value from the
+## # ``fast-sites'' group scope is used.
+## peer data.ramona.vix.com {
+## hostname: data.ramona.vix.com
+## }
+##
+## peer bb.home.vix.com {
+## hostname: bb.home.vix.com
+## max-connections: 20 # he can really cook.
+## }
+## }
+##
+## Given the above configuration file, the defined peers would have the
+## following values for the ``max-connections'' key.
+##
+## uunet 5
+## vixie 10
+## data.ramona.vix.com 15
+## bb.home.vix.com 20
+##
+## Height keys are allowed:
+##
+## hostname:
+## This key is mandatory in a peer block. The value is a string representing
+## a list of hostnames separated by a comma. A hostname is the host's FQDN,
+## or the dotted quad ip-address of the peer.
+##
+## streaming:
+## This key requires a boolean value. It defines whether streaming commands
+## are allowed from this peer. (default=true)
+##
+## max-connections:
+## This key requires positive integer value. It defines the maximum number
+## of connections allowed. A value of zero specifies an unlimited number
+## of maximum connections (``unlimited'' or ``none'' can be used as synonym).
+## (default=0)
+##
+## hold-time:
+## This key requires positive integer value. It defines the hold time before
+## close, if the connection is over max-connections. A value of zero
+## specifies immediate close. (default=0)
+##
+## password:
+## This key requires a string value. It is used if you wish to require a peer
+## to supply a password. (default=no password)
+##
+## patterns:
+## This key requires a string value. It is a list of newsfeeds(5)-style list
+## of newsgroups which are to be accepted from this host. (default="*")
+##
+## email:
+## This key requires a string value. Reserved for future use. (default=empty)
+##
+## comment:
+## This key requires a string value. Reserved for future use. (default=empty)
+##
+## skip:
+## This key requires a boolean value. Setting this entry causes this peer
+## to be skipped. Reserved for future use. (default=false)
+##
+## noresendid:
+## This key requires a boolean value. It defines whether innd should send
+## "431 RESENDID" (stream mode) or "436 Retry later" (non-stream mode)
+## responses if a message is offered that is already received from another
+## peer. This can be useful for peers that resend messages right away,
+## as innfeed does. (default=false)
+##
+
+streaming: true # streaming allowed by default
+max-connections: 8 # per feed
+
+peer ME {
+ hostname: "localhost, 127.0.0.1"
+}
--- /dev/null
+## $Id: inn.conf.in 7751 2008-04-06 14:35:40Z iulius $
+##
+## inn.conf -- INN configuration data
+##
+## Format:
+## <parameter>:<whitespace><value>
+##
+## Blank values are allowed for certain parameters.
+##
+## See the inn.conf(5) man page for a full description of each of these
+## options. This sample file is divided into two sections; first, there
+## are the parameters that must be set (or should be set in nearly all
+## cases), and then all parameters are given with their defaults for
+## reference in the same order and with the same organization as the
+## inn.conf(5) documentation.
+
+# The following parameters are most likely to need setting, although the
+# defaults generated by configure may be reasonable.
+
+mta: "@SENDMAIL@ -oi -oem %s"
+organization: "A poorly-installed InterNetNews site"
+ovmethod: tradindexed
+hismethod: hisv6
+pathhost: @HOSTNAME@
+pathnews: @prefix@
+
+# General Settings
+
+#domain:
+#innflags:
+mailcmd: @prefix@/bin/innmail
+#server:
+
+# Feed Configuration
+
+artcutoff: 10
+#bindaddress:
+#bindaddress6:
+dontrejectfiltered: false
+hiscachesize: 0
+ignorenewsgroups: false
+immediatecancel: false
+linecountfuzz: 0
+maxartsize: 1000000
+maxconnections: 50
+#pathalias:
+#pathcluster:
+pgpverify: @pgpverify@
+port: 119
+refusecybercancels: false
+remembertrash: true
+#sourceaddress:
+#sourceaddress6:
+verifycancels: false
+wanttrash: false
+wipcheck: 5
+wipexpire: 10
+
+# Article Storage
+
+cnfscheckfudgesize: 0
+enableoverview: true
+groupbaseexpiry: true
+mergetogroups: false
+overcachesize: 15
+#ovgrouppat:
+storeonxref: true
+useoverchan: false
+wireformat: false
+xrefslave: false
+nfswriter: false
+
+# Reading
+
+allownewnews: true
+articlemmap: false
+clienttimeout: 600
+initialtimeout: 10
+msgidcachesize: 10000
+nnrpdcheckart: true
+nnrpdflags: ""
+noreader: false
+readerswhenstopped: false
+readertrack: false
+nfsreader: false
+nfsreaderdelay: 60
+tradindexedmmap: true
+nnrpdloadlimit: 16
+
+# Reading -- Keyword Support
+#
+# Enabling this without stopping innd and deleting the existing overview
+# database and adding will probably confuse a lot of things. You must
+# have compiled this support in too.
+
+keywords: false
+keyartlimit: 100000
+keylimit: 512
+keymaxwords: 250
+
+# Posting
+
+addnntppostingdate: true
+addnntppostinghost: true
+checkincludedtext: false
+#complaints:
+#fromhost:
+localmaxartsize: 1000000
+#moderatormailer:
+nnrpdauthsender: false
+#nnrpdposthost:
+nnrpdpostport: 119
+spoolfirst: false
+strippostcc: false
+
+# Posting -- Exponential Backoff
+
+backoffauth: false
+#backoffdb:
+backoffk: 1
+backoffpostfast: 0
+backoffpostslow: 1
+backofftrigger: 10000
+
+# Monitoring
+
+doinnwatch: true
+innwatchbatchspace: 800
+innwatchlibspace: 25000
+innwatchloload: 1000
+innwatchhiload: 2000
+innwatchpauseload: 1500
+innwatchsleeptime: 600
+innwatchspoolnodes: 200
+innwatchspoolspace: 8000
+
+# Logging
+
+docnfsstat: false
+logartsize: true
+logcancelcomm: false
+logcycles: 3
+logipaddr: true
+logsitename: true
+nnrpdoverstats: false
+nntpactsync: 200
+nntplinklog: false
+status: 0
+timer: 0
+
+# System Tuning
+
+badiocount: 5
+blockbackoff: 120
+chaninacttime: 600
+chanretrytime: 300
+datamovethreshold: 8192
+icdsynccount: 10
+keepmmappedthreshold: 1024
+#maxcmdreadsize:
+maxforks: 10
+nicekids: 4
+nicenewnews: 0
+nicennrpd: 0
+pauseretrytime: 300
+peertimeout: 3600
+rlimitnofile: -1
+
+# Paths
+
+patharchive: @SPOOLDIR@/archive
+patharticles: @SPOOLDIR@/articles
+pathbin: @prefix@/bin
+pathcontrol: @CONTROLDIR@
+pathdb: @DBDIR@
+pathetc: @ETCDIR@
+pathfilter: @FILTERDIR@
+pathhttp: @LOGDIR@
+pathincoming: @SPOOLDIR@/incoming
+pathlog: @LOGDIR@
+pathoutgoing: @SPOOLDIR@/outgoing
+pathoverview: @SPOOLDIR@/overview
+pathrun: @RUNDIR@
+pathspool: @SPOOLDIR@
+pathtmp: @tmpdir@
--- /dev/null
+# $Revision: 7559 $
+#
+# Sample innfeed config file. See the comment block at the
+# end for a fuller description of the format, and innfeed.conf(5) for a
+# description of the entries.
+#
+
+##
+## Global values. Not specific to any peer. These are optional, but if
+## used will override the compiled in values.
+##
+
+pid-file: innfeed.pid # relative to pathrun
+debug-level: 0
+use-mmap: false
+log-file: innfeed.log # relative to pathlog
+stdio-fdmax: 0
+
+## Uncomment the next line to include the contents
+## of ``testfile'' at this point.
+
+#$INCLUDE testfile
+
+backlog-directory: innfeed # relative to pathspool
+backlog-rotate-period: 60
+backlog-ckpt-period: 30
+backlog-newfile-period: 600
+
+dns-retry: 900
+dns-expire: 86400
+close-period: 86400
+gen-html: false
+status-file: innfeed.status # relative to pathlog
+connection-stats: false
+host-queue-highwater: 200
+stats-period: 600
+stats-reset: 43200
+
+max-reconnect-time: 3600
+initial-reconnect-time: 30
+
+
+##
+## Defaults for all peers. These must all exist at
+## global scope. Any of them can be redefined
+## inside a peer or group definition.
+##
+
+article-timeout: 600
+response-timeout: 300
+initial-connections: 1
+max-connections: 5
+max-queue-size: 5
+streaming: true
+no-check-high: 95.0
+no-check-low: 90.0
+no-check-filter: 50.0
+port-number: 119
+force-ipv4: false
+drop-deferred: false
+min-queue-connection: false
+backlog-limit: 0
+backlog-factor: 1.10
+backlog-limit-highwater: 0
+dynamic-method: 3
+dynamic-backlog-filter: 0.7
+dynamic-backlog-low: 25.0
+dynamic-backlog-high: 50.0
+no-backlog: false
+backlog-feed-first: false
+
+##
+## Peers.
+##
+#peer decwrl {
+# ip-name: news1.pa.dec.com
+#}
+
+#peer uunet {
+# ip-name: news.uunet.uu.net
+# max-connections: 10
+#}
+
+
+##
+## Group peers together to give second level defaults.
+##
+#group fast-sites {
+# max-connections: 7
+#
+# peer data.ramona.vix.com {
+# # ip-name defaults to data.ramona.vix.com
+# streaming: false
+# }
+#
+# peer bb.home.vix.com {
+# ip-name: 192.5.5.33
+# }
+#}
+
+
+
+# Blank lines are ignored. Exerything after a '#'
+# is ignored too.
+#
+# Format is:
+# key : value
+#
+# See innfeed.conf(5) for a description of
+# necessary & useful keys. Unknown keys and their
+# values are ignored.
+#
+# Values may be a integer, floating-point, c-style
+# single-quoted characters, boolean, and strings.
+#
+# If a string value contains whitespace, or
+# embedded quotes, or the comment character
+# (``#''), then the whole string must be quoted
+# with double quotes. Inside the quotes, you may
+# use the standard c-escape sequence
+# (\t,\n,\r,\f,\v,\",\').
+#
+# Examples:
+# eg-string: "New\tConfig\tfile\n"
+# eg-long-string: "A long string that goes
+# over multiple lines. The
+# newline is kept in the
+# string except when quoted
+# with a backslash \
+# as here."
+# eg-simple-string: A-no-quote-string
+# eg-integer: 10
+# eg-boolean: true
+# eg-char: 'a'
+# eg-ctrl-g: '\007'
--- /dev/null
+##########################################################
+# Configuration file for innreport (3.*).
+#
+# Sample file for INN.
+# Tested with INN 2.3, 2.1, 1.7.2 and 1.5.1.
+#
+# (c) 1997, 1998, 1999 by Fabien Tassin <fta@sofaraway.org>
+# version 3.0.2
+##########################################################
+
+# Default parameters
+section default {
+ libpath "@LIBDIR@";
+ logpath "@LOGDIR@";
+ unknown true; # want unknown entries.
+ max_unknown 50; # max unknown entries to display.
+ casesensitive true;
+ module "innreport_inn"; # ${libpath}/${module}.pm
+ unwanted_log "unwanted.log"; # ${logpath}/${unwanted_log}
+ text true;
+ html false;
+ graph true; # need 'html'
+ archive true; # use false to keep only the latest HTML report.
+ index "index.html"; # name of the HTML index file.
+ # html_dir "/var/www/News/stats"; # default to pathhttp in inn.conf
+ img_dir "pics"; # images will go to ${html_dir}/${img_dir}
+ cycle none; # use a number or 'none'.
+ separator "."; # use a valid filename character.
+ title "Daily Usenet report";
+ # title "Daily Usenet report for <A HREF=\"/News/stats/\">news.y.z</A>";
+ # footer "Local contact: <A HREF=\"mailto:x@y.z\">x@y.z</A>";
+ # html_body "BGCOLOR=\"#FFFFFF\" TEXT=\"#000000\"";
+ # html_header_file "header.html"; # ${html_dir}/${html_header_file}
+ # html_footer_file "footer.html"; # ${html_dir}/${html_footer_file}
+ graph_width 550; # graph width (in pixels)
+ transparent true; # graph background transparent ?
+ graph_fg "#000000"; # graph foreground color.
+ graph_bg "#FFFFFF"; # graph background color.
+};
+
+###########################################################################
+# Index page
+section index {
+ column {
+ title "Dates";
+ value "date";
+ };
+ column {
+ title "Incoming feeds";
+ name "Offered|Accepted|Volume";
+ value "total(%innd_offered) | total(%innd_accepted) |
+ bytes(total(%inn_flow_size))";
+ };
+ column {
+ title "Outgoing feeds";
+ name "Offered|Accepted|Volume";
+ value "total(%innfeed_offered) + total(%nntplink_offered) +
+ total(%innxmit_offered) | total(%innfeed_accepted) +
+ total(%nntplink_accepted) + total(%innxmit_accepted) |
+ bytes(total(%innfeed_accepted_size) +
+ total(%innfeed_rejected_size) +
+ total(%innxmit_bytes) +
+ total(%innxmit_accepted_size) +
+ total(%innxmit_rejected_size))";
+ };
+ graph {
+ title "Incoming feeds";
+ value val1;
+ color "#FFFFCE";
+ unit "art";
+ data {
+ name "Offered";
+ color "#50A0D0";
+ value "val2"; # Incoming feeds: Offered
+ };
+ data {
+ name "Accepted";
+ color "#0000FF";
+ value "val3"; # Incoming feeds: Accepted
+ };
+ };
+ graph {
+ title "Outgoing feeds";
+ value val1;
+ color "#FFFFCE";
+ unit "art";
+ data {
+ name "Offered";
+ color "#50A0D0";
+ value "val5"; # Outgoing feeds: Offered
+ };
+ data {
+ name "Accepted";
+ color "#0000FF";
+ value "val6"; # Outgoing feeds: Accepted
+ };
+ };
+ graph {
+ title "Bandwidth";
+ value val1;
+ color "#FFFFCE";
+ unit "Kb";
+ data {
+ name "Incoming";
+ color "#50A0D0";
+ value "byte(val4)"; # Incoming feeds: Volume
+ };
+ data {
+ name "Outgoing";
+ color "#0000FF";
+ value "byte(val7)"; # Outgoing feeds: Volume
+ };
+ };
+};
+
+###########################################################################
+# Report
+
+section prog_type {
+ # skip true; # used to skip a section.
+ title "Log entries by program:";
+ data "%prog_type";
+ sort "$prog_type{$b} <=> $prog_type{$a}";
+ # text false; # to skip this section in the text report
+ # html false; # to skip this section in the HTML report
+ column {
+ name "Program name";
+ format "%-46.46s";
+ value "$key";
+ format_total "TOTAL: %-39.39s";
+ total "$num";
+ };
+ column {
+ name "Lines";
+ format_name "%7s";
+ format "%7d";
+ # text false; # to skip this column in the text report
+ value "$prog_type{$key}";
+ total "total(%prog_type)";
+ };
+ column {
+ name "%Lines";
+ format_name "%7s";
+ format "%6.1f%%";
+ # html false; # to skip this column in the HTML report
+ value "$prog_type{$key} / total(%prog_type) * 100";
+ total "100";
+ };
+ column {
+ name "Size";
+ format "%9s";
+ value "bytes($prog_size{$key})";
+ total "bytes(total(%prog_size))";
+ };
+ column {
+ name "%Size";
+ format_name "%6s";
+ format "%5.1f%%";
+ value "$prog_size{$key} / total(%prog_size) * 100";
+ total "100";
+ };
+};
+
+# INN 2.*
+section innd_his {
+ title "History cache:";
+ data "%innd_his";
+ sort "$innd_his{$b} <=> $innd_his{$a}";
+ column {
+ name "Reason";
+ format "%-57.57s";
+ value "$key";
+ format_total "TOTAL: %-50.50s";
+ total "$num";
+ };
+ column {
+ name "Count";
+ format_name "%10s";
+ format "%10d";
+ value "$innd_his{$key}";
+ total "total(%innd_his)";
+ };
+ column {
+ name "%Count";
+ format_name "%10s";
+ format "%9.1f%%";
+ value "100 * $innd_his{$key} / total(%innd_his)";
+ total "100";
+ };
+};
+
+# INN 1.*
+section innd_cache {
+ title "History cache:";
+ data "%innd_cache";
+ sort "$innd_cache{$b} <=> $innd_cache{$a}";
+ column {
+ name "Reason";
+ format "%-57.57s";
+ value "$key";
+ format_total "TOTAL: %-50.50s";
+ total "$num";
+ };
+ column {
+ name "Count";
+ format_name "%10s";
+ format "%10d";
+ value "$innd_cache{$key}";
+ total "total(%innd_cache)";
+ };
+ column {
+ name "%Count";
+ format_name "%10s";
+ format "%9.1f%%";
+ value "100 * $innd_cache{$key} / total(%innd_cache)";
+ total "100";
+ };
+};
+
+section innd_timer {
+ title "INND timer:";
+ data "%innd_time_time";
+ column {
+ name "Code region";
+ format "%-15.15s";
+ value "$key";
+ format_total "TOTAL: %-8.8s";
+ total "time_ms($innd_time_times)";
+ };
+ column {
+ name "Time";
+ format "%13s";
+ value "time_ms($innd_time_time{$key})";
+ total "time_ms(total(%innd_time_time))";
+ };
+ column {
+ name "Pct";
+ format_name "%6s";
+ format "%5.1f%%";
+ value "100 * $innd_time_time{$key} / $innd_time_times";
+ total "100 * total(%innd_time_time) /
+ $innd_time_times";
+ };
+ column {
+ name "Invoked";
+ format "%10s";
+ value "$innd_time_num{$key}";
+ format_total "%9s-";
+ total "";
+ };
+ column {
+ name "Min(ms)";
+ format_name "%9s";
+ format "%9.3f";
+ value "$innd_time_min{$key}";
+ format_total "%8s-";
+ total "";
+ };
+ column {
+ name "Avg(ms)";
+ format_name "%10s";
+ format "%10.3f";
+ value "$innd_time_time{$key} /
+ ($innd_time_num{$key} ? $innd_time_num{$key} : 1)";
+ format_total "%9s-";
+ total "";
+ };
+ column {
+ name "Max(ms)";
+ format_name "%10s";
+ format "%10.3f";
+ value "$innd_time_max{$key}";
+ format_total "%9s-";
+ total "";
+ };
+};
+
+section innfeed_timer {
+ title "INNfeed timer:";
+ data "%innfeed_time_time";
+ column {
+ name "Code region";
+ format "%-15.15s";
+ value "$key";
+ format_total "TOTAL: %-8.8s";
+ total "time_ms($innfeed_time_times)";
+ };
+ column {
+ name "Time";
+ format "%13s";
+ value "time_ms($innfeed_time_time{$key})";
+ total "time_ms(total(%innfeed_time_time))";
+ };
+ column {
+ name "Pct";
+ format_name "%6s";
+ format "%5.1f%%";
+ value "100 * $innfeed_time_time{$key} / $innfeed_time_times";
+ total "100 * total(%innfeed_time_time) /
+ $innfeed_time_times";
+ };
+ column {
+ name "Invoked";
+ format "%10s";
+ value "$innfeed_time_num{$key}";
+ format_total "%9s-";
+ total "";
+ };
+ column {
+ name "Min(ms)";
+ format_name "%9s";
+ format "%9.3f";
+ value "$innfeed_time_min{$key}";
+ format_total "%8s-";
+ total "";
+ };
+ column {
+ name "Avg(ms)";
+ format_name "%10s";
+ format "%10.3f";
+ value "$innfeed_time_time{$key} /
+ ($innfeed_time_num{$key} ? $innfeed_time_num{$key} : 1)";
+ format_total "%9s-";
+ total "";
+ };
+ column {
+ name "Max(ms)";
+ format_name "%10s";
+ format "%10.3f";
+ value "$innfeed_time_max{$key}";
+ format_total "%9s-";
+ total "";
+ };
+};
+
+section nnrpd_timer {
+ title "nnrpd timer:";
+ data "%nnrpd_time_time";
+ column {
+ name "Code region";
+ format "%-15.15s";
+ value "$key";
+ format_total "TOTAL: %-8.8s";
+ total "time_ms($nnrpd_time_times)";
+ };
+ column {
+ name "Time";
+ format "%13s";
+ value "time_ms($nnrpd_time_time{$key})";
+ total "time_ms(total(%nnrpd_time_time))";
+ };
+ column {
+ name "Pct";
+ format_name "%6s";
+ format "%5.1f%%";
+ value "100 * $nnrpd_time_time{$key} / $nnrpd_time_times";
+ total "100 * total(%nnrpd_time_time) /
+ $nnrpd_time_times";
+ };
+ column {
+ name "Invoked";
+ format "%10s";
+ value "$nnrpd_time_num{$key}";
+ format_total "%9s-";
+ total "";
+ };
+ column {
+ name "Min(ms)";
+ format_name "%9s";
+ format "%9.3f";
+ value "$nnrpd_time_min{$key}";
+ format_total "%8s-";
+ total "";
+ };
+ column {
+ name "Avg(ms)";
+ format_name "%10s";
+ format "%10.3f";
+ value "$nnrpd_time_time{$key} /
+ ($nnrpd_time_num{$key} ? $nnrpd_time_num{$key} : 1)";
+ format_total "%9s-";
+ total "";
+ };
+ column {
+ name "Max(ms)";
+ format_name "%10s";
+ format "%10.3f";
+ value "$nnrpd_time_max{$key}";
+ format_total "%9s-";
+ total "";
+ };
+};
+
+section innd_control {
+ title "Control commands to INND:";
+ data "%innd_control";
+ column {
+ name "Command";
+ format "%-71.71s";
+ value "$key";
+ format_total "TOTAL: %-64.64s";
+ total "$num";
+ };
+ column {
+ name "Number";
+ format_name "%7s";
+ value "$innd_control{$key}";
+ format "%7d";
+ total "total(%innd_control)";
+ };
+};
+
+section innd_newgroup {
+ title "Newsgroups created:";
+ data "%innd_newgroup";
+ column {
+ name "Group";
+ format "%-71.71s";
+ value "$key";
+ format_total "TOTAL%-66.66s";
+ total "";
+ };
+ column {
+ name "Mode";
+ value "$innd_newgroup{$key}";
+ format "%7s";
+ total "$num";
+ };
+};
+
+section innd_changegroup {
+ title "Newsgroups changed:";
+ data "%innd_changegroup";
+ column {
+ name "Group";
+ format "%-68.68s";
+ value "$key";
+ format_total "TOTAL%-63.63s";
+ total "";
+ };
+ column {
+ name "New mode";
+ format "%10s";
+ value "$innd_changegroup{$key}";
+ total "$num";
+ };
+};
+
+section innd_rmgroup {
+ title "Newsgroups removed:";
+ data "%innd_rmgroup";
+ column {
+ name "Group";
+ format "%-78.78s";
+ value "$key";
+ format_total "TOTAL: %-71.71s";
+ total "$num";
+ };
+};
+
+section controlchan {
+ title "Control Channel:";
+ data "%controlchan_who";
+ column {
+ name "Sender";
+ format "%-25.25s";
+ value "$key";
+ format_total "TOTAL%-20.20s";
+ total "";
+ };
+ column {
+ name "newgroup";
+ value "$controlchan_new{$key}";
+ format "%8s";
+ total "total(%controlchan_new)";
+ };
+ column {
+ name "rmgroup";
+ value "$controlchan_rm{$key}";
+ format "%8s";
+ total "total(%controlchan_rm)";
+ };
+ column {
+ name "Other";
+ value "$controlchan_other{$key}";
+ format "%8s";
+ total "total(%controlchan_other)";
+ };
+ column {
+ name "Bad PGP";
+ value "$controlchan_skippgp{$key}";
+ format "%8s";
+ total "total(%controlchan_skippgp)";
+ };
+ column {
+ name "DoIt";
+ value "$controlchan_doit{$key}";
+ format "%8s";
+ total "total(%controlchan_doit)";
+ };
+ column {
+ name "OK";
+ value "$controlchan_ok{$key}";
+ format "%8s";
+ total "total(%controlchan_ok)";
+ };
+};
+
+section innd_connect {
+ title "Incoming Feeds (INN):";
+ data "%innd_seconds";
+ sort "$innd_accepted{$b} <=> $innd_accepted{$a}";
+ numbering true;
+ column {
+ name "Server";
+ format_name "%-21.21s";
+ format "%-24.24s";
+ value "$key";
+ format_total "TOTAL: %-17.17s";
+ total "$num";
+ };
+ column {
+ name "Connects";
+ format_name "%5s";
+ format "%5d";
+ value "$innd_connect{$key}";
+ total "total(%innd_connect)";
+ };
+ column {
+ name "Offered";
+ format_name "%8s";
+ format "%8d";
+ value "$innd_offered{$key}";
+ total "total(%innd_offered)";
+ };
+ column {
+ name "Taken";
+ format_name "%7s";
+ format "%7d";
+ value "$innd_accepted{$key}";
+ total "total(%innd_accepted)";
+ };
+ column {
+ name "Refused";
+ format_name "%7s";
+ format "%7d";
+ value "$innd_refused{$key}";
+ total "total(%innd_refused)";
+ };
+ column {
+ name "Reject";
+ format_name "%7s";
+ format "%7d";
+ value "$innd_rejected{$key}";
+ total "total(%innd_rejected)";
+ };
+ column {
+ name "%Accpt";
+ format_name "%6s";
+ format "%4d%%";
+ value "$innd_offered{$key} == 0 ? 0 :
+ $innd_accepted{$key} / $innd_offered{$key} * 100";
+ total "total(%innd_offered) == 0 ? 0 :
+ total(%innd_accepted) / total(%innd_offered) * 100";
+ };
+ column {
+ name "Elapsed";
+ format_name "%8s";
+ format "%9s";
+ value "time($innd_seconds{$key})";
+ total "time(total(%innd_seconds))";
+ };
+ graph {
+ title "Articles received by server";
+ type histo3d;
+ sort "%innd_accepted";
+ data {
+ name "Articles accepted";
+ color "#0000FF";
+ value "%innd_accepted";
+ };
+ data {
+ name "Articles refused";
+ color "#FFAF00";
+ value "%innd_refused";
+ };
+ data {
+ name "Articles rejected";
+ color "#FF0000";
+ value "%innd_rejected";
+ };
+ };
+};
+
+section innd_incoming_vol {
+ title "Incoming Volume (INN):";
+ data "%innd_seconds";
+ sort "$innd_stored_size{$b} <=> $innd_stored_size{$a}";
+ numbering true;
+ column {
+ name "Server";
+ format "%-24.24s";
+ value "$key";
+ format_total "TOTAL: %-17.17s";
+ total "$num";
+ };
+ column {
+ name "AcceptVol";
+ format "%9s";
+ value "bytes($innd_stored_size{$key})";
+ total "bytes(total(%innd_stored_size))";
+ };
+ column {
+ name "DupVol";
+ format "%9s";
+ value "bytes($innd_duplicated_size{$key})";
+ total "bytes(total(%innd_duplicated_size))";
+ };
+ column {
+ name "TotalVol";
+ format "%9s";
+ value "bytes($innd_stored_size{$key} +
+ $innd_duplicated_size{$key})";
+ total "bytes(total(%innd_stored_size) +
+ total(%innd_duplicated_size))";
+ };
+ column {
+ name "%Acc";
+ format_name "%4s";
+ format "%3d%%";
+ value "$innd_offered_size{$key} == 0 ? 0 :
+ $innd_stored_size{$key} / $innd_offered_size{$key} * 100";
+ total "total(%innd_offered_size) == 0 ? 0 :
+ total(%innd_stored_size) / total(%innd_offered_size) * 100";
+ };
+ column {
+ name "Vol/Art";
+ format "%9s";
+ value "bytes(($innd_stored_size{$key} +
+ $innd_duplicated_size{$key}) /
+ ($innd_accepted{$key} +
+ $innd_rejected{$key}))";
+ total "bytes((total(%innd_stored_size) +
+ total(%innd_duplicated_size)) /
+ (total(%innd_accepted) +
+ total(%innd_rejected)))";
+ };
+ column {
+ name "Elapsed";
+ format "%9s";
+ value "time($innd_seconds{$key})";
+ total "time(total(%innd_seconds))";
+ };
+ graph {
+ title "Incoming Volume received by server";
+ type histo3d;
+ sort "%innd_stored_size";
+ data {
+ name "Accepted Volume";
+ color "#0000FF";
+ value "%innd_stored_size";
+ };
+ data {
+ name "Duplicated Volume";
+ color "#FFAF00";
+ value "%innd_duplicated_size";
+ };
+ };
+};
+
+section inn_flow {
+ title "Incoming articles:";
+ data "%inn_flow";
+ sort "&DateCompare";
+ column {
+ name "Date";
+ format "%-27.27s";
+ value "$key";
+ format_total "TOTAL: %-20.20s";
+ total "time(total(%inn_flow_time))";
+ };
+ column {
+ name "Articles";
+ format_name "%8s";
+ value "$inn_flow{$key}";
+ format "%8d";
+ total "total(%inn_flow)";
+ };
+ column {
+ name "%Arts";
+ format_name "%8s";
+ value "$inn_flow{$key} / $inn_flow_total * 100";
+ format "%7.1f%%";
+ total "100";
+ };
+ column {
+ name "Art/sec";
+ format_name "%7s";
+ value "$inn_flow{$key} / $inn_flow_time{$key}";
+ format "%7.2f";
+ total "total(%inn_flow) / total(%inn_flow_time)";
+ };
+ column {
+ name "Size";
+ value "bytes($inn_flow_size{$key})";
+ format "%9s";
+ total "bytes(total(%inn_flow_size))";
+ };
+ column {
+ name "%Size";
+ format_name "%7s";
+ value "$inn_flow_size{$key} /
+ total(%inn_flow_size) * 100";
+ format "%6.1f%%";
+ total "100";
+ };
+ column {
+ name "KB/sec";
+ format_name "%7s";
+ value "$inn_flow_size{$key} /
+ $inn_flow_time{$key} / 1024";
+ format "%7.2f";
+ total "total(%inn_flow_size) /
+ total(%inn_flow_time) / 1024";
+ };
+ graph {
+ title "Incoming articles";
+ type histo;
+ data {
+ name "Hours";
+ value "%inn_flow_labels";
+ };
+ data {
+ name "Art/sec";
+ factor 3600;
+ value "%inn_flow";
+ };
+ };
+ graph {
+ title "Incoming articles (size)";
+ type histo;
+ data {
+ name "Hours";
+ value "%inn_flow_labels";
+ };
+ data {
+ name "Kb/sec";
+ factor 3686400; # 3600 * 1024
+ value "%inn_flow_size";
+ };
+ };
+};
+
+section cnfsstat {
+ title "CNFS buffer status:";
+ data "%cnfsstat";
+ column {
+ name "Buffer";
+ format "%-13.13s";
+ value "$key";
+ format_total "TOTAL: %-6.6s";
+ total "$num";
+ };
+ column {
+ name "Class";
+ format "%-13.13s";
+ value "$cnfsstat{$key}";
+ format_total "-%12s";
+ total "";
+ };
+ column {
+ name "Size";
+ format "%9s";
+ value "bytes($cnfsstat_size{$key})";
+ total "bytes(total(%cnfsstat_size))";
+ };
+ column {
+ name "Used";
+ format "%9s";
+ value "bytes($cnfsstat_used{$key})";
+ total "bytes(total(%cnfsstat_used))";
+ };
+ column {
+ name "%Used";
+ format_name "%7s";
+ value "$cnfsstat_used{$key} /
+ $cnfsstat_size{$key} * 100";
+ format "%6.1f%%";
+ total "total(%cnfsstat_used) /
+ total(%cnfsstat_size) * 100";
+ };
+ column {
+ name "Cycles";
+ format_name "%6s";
+ format "%6d";
+ value "$cnfsstat_cycles{$key}";
+ total "total(%cnfsstat_cycles)";
+ };
+ column {
+ name "KB/sec";
+ format_name "%7s";
+ value "$cnfsstat_rate{$key} /
+ $cnfsstat_samples{$key} / 1024";
+ format "%7.2f";
+ total "total(%cnfsstat_rate) /
+ total(%cnfsstat_samples) / 1024";
+ };
+ column {
+ name "Days";
+ format_name "%8s";
+ value "$cnfsstat_size{$key} /
+ ($cnfsstat_rate{$key} /
+ $cnfsstat_samples{$key}) / 86400";
+ format "%8.2f";
+ format_total "%7s-";
+ total "";
+ };
+};
+
+section inn_unwanted {
+ title "Sites sending bad articles:";
+ data "%inn_badart";
+ sort "$inn_badart{$b} <=> $inn_badart{$a}";
+ numbering true;
+ column {
+ name "Server";
+ format "%-23.23s";
+ value "$key";
+ format_total "TOTAL: %-16.16s";
+ total "$num";
+ };
+ column {
+ name "Total";
+ format_name "%6s";
+ format "%6d";
+ value "$inn_badart{$key}";
+ total "total(%inn_badart)";
+ };
+ column {
+ name "Group";
+ format_name "%6s";
+ format "%6d";
+ value "$inn_uw_ng_s{$key}";
+ total "total(%inn_uw_ng_s)";
+ };
+ column {
+ name "Dist";
+ format_name "%5s";
+ format "%5d";
+ value "$inn_uw_dist_s{$key}";
+ total "total(%inn_uw_dist_s)";
+ };
+ column {
+ name "Duplic";
+ format_name "%6s";
+ format "%6d";
+ value "$inn_duplicate{$key}";
+ total "total(%inn_duplicate)";
+ };
+ column {
+ name "Unapp";
+ format_name "%5s";
+ format "%5d";
+ value "$inn_unapproved{$key}";
+ total "total(%inn_unapproved)";
+ };
+ column {
+ name "TooOld";
+ format_name "%6s";
+ format "%6d";
+ value "$inn_tooold{$key}";
+ total "total(%inn_tooold)";
+ };
+ column {
+ name "Site";
+ format_name "%4s";
+ format "%4d";
+ value "$inn_uw_site{$key}";
+ total "total(%inn_uw_site)";
+ };
+ column {
+ name "Line";
+ format_name "%4s";
+ format "%4d";
+ value "$inn_linecount{$key}";
+ total "total(%inn_linecount)";
+ };
+ column {
+ name "Other";
+ format_name "%5s";
+ format "%5d";
+ value "$innd_others{$key}";
+ total "total(%innd_others)";
+ };
+};
+
+section inn_unwanted_group {
+ title "Unwanted newsgroups:";
+ top 20; # default 'top' value or use 'top_text' and 'top_html'
+ # to specify different values for text and HTML reports.
+ data "%inn_uw_ng";
+ sort "$inn_uw_ng{$b} <=> $inn_uw_ng{$a}";
+ column {
+ name "Newsgroup";
+ format "%-71.71s";
+ value "$key";
+ format_total "TOTAL: %-64.64s";
+ total "$num";
+ };
+ column {
+ name "Count";
+ format_name "%7s";
+ format "%7d";
+ value "$inn_uw_ng{$key}";
+ total "total(%inn_uw_ng)";
+ };
+};
+
+section inn_unwanted_dist {
+ title "Unwanted distributions:";
+ top 20;
+ data "%inn_uw_dist";
+ sort "$inn_uw_dist{$b} <=> $inn_uw_dist{$a}";
+ column {
+ name "Distribution";
+ format "%-71.71s";
+ value "$key";
+ format_total "TOTAL: %-64.64s";
+ total "$num";
+ };
+ column {
+ name "Count";
+ format_name "%7s";
+ format "%7d";
+ value "$inn_uw_dist{$key}";
+ total "total(%inn_uw_dist)";
+ };
+};
+
+section inn_unwanted_unapp {
+ title "Supposedly-moderated groups with unmoderated postings:";
+ top 20;
+ data "%inn_unapproved_g";
+ sort "$inn_unapproved_g{$b} <=> $inn_unapproved_g{$a}";
+ column {
+ name "Groups";
+ format "%-71.71s";
+ value "$key";
+ format_total "TOTAL: %-64.64s";
+ total "$num";
+ };
+ column {
+ name "Count";
+ format_name "%7s";
+ format "%7d";
+ value "$inn_unapproved_g{$key}";
+ total "total(%inn_unapproved_g)";
+ };
+};
+
+section inn_unwanted_path {
+ title "Unwanted sites in Path:";
+ top 20;
+ data "%inn_site_path";
+ sort "$inn_site_path{$b} <=> $inn_site_path{$a}";
+ column {
+ name "Site";
+ format "%-71.71s";
+ value "$key";
+ format_total "TOTAL: %-64.64s";
+ total "$num";
+ };
+ column {
+ name "Count";
+ format_name "%7s";
+ format "%7d";
+ value "$inn_site_path{$key}";
+ total "total(%inn_site_path)";
+ };
+};
+
+section innd_perl {
+ title "INND Perl filter:";
+ top 20;
+ data "%innd_filter_perl";
+ sort "$innd_filter_perl{$b} <=> $innd_filter_perl{$a}";
+ column {
+ name "Reason";
+ format "%-71.71s";
+ value "$key";
+ format_total "TOTAL: %-64.64s";
+ total "$num";
+ };
+ column {
+ name "Count";
+ format_name "%7s";
+ format "%7d";
+ value "$innd_filter_perl{$key}";
+ total "total(%innd_filter_perl)";
+ };
+};
+
+section innd_python {
+ title "INND Python filter:";
+ top 20;
+ data "%innd_filter_python";
+ sort "$innd_filter_python{$b} <=> $innd_filter_python{$a}";
+ column {
+ name "Reason";
+ format "%-71.71s";
+ value "$key";
+ format_total "TOTAL: %-64.64s";
+ total "$num";
+ };
+ column {
+ name "Count";
+ format_name "%7s";
+ format "%7d";
+ value "$innd_filter_python{$key}";
+ total "total(%innd_filter_python)";
+ };
+};
+
+section nocem {
+ title "NoCeM on Spool:";
+ data "%nocem_goodsigs";
+ sort "$nocem_goodsigs{$b} <=> $nocem_goodsigs{$a}";
+ column {
+ name "Id";
+ format "%-47.47s";
+ value "$key";
+ format_total "TOTAL: %-40.40s";
+ total "$num";
+ };
+ column {
+ name "Good";
+ format "%7s";
+ value "$nocem_goodsigs{$key}";
+ total "total(%nocem_goodsigs)";
+ };
+ column {
+ name "Bad";
+ format "%7s";
+ value "$nocem_badsigs{$key}";
+ total "total(%nocem_badsigs)";
+ };
+ column {
+ name "Unique";
+ format "%7s";
+ value "$nocem_newids{$key}";
+ total "total(%nocem_newids)";
+ };
+ column {
+ name "Total";
+ format "%7s";
+ value "$nocem_totalids{$key}";
+ total "total(%nocem_totalids)";
+ };
+};
+
+section innd_no_permission {
+ title "INND no permission servers:";
+ data "%innd_no_permission";
+ sort "$innd_no_permission{$b} <=> $innd_no_permission{$a}";
+ column {
+ name "System";
+ format "%-71.71s";
+ value "$key";
+ format_total "TOTAL: %-64.64s";
+ total "$num";
+ };
+ column {
+ name "Conn";
+ format_name "%7s";
+ format "%7d";
+ value "$innd_no_permission{$key}";
+ total "total(%innd_no_permission)";
+ };
+};
+
+section innd_max_conn {
+ title "Too many incoming connections (innd):";
+ data "%innd_max_conn";
+ sort "$innd_max_conn{$b} <=> $innd_max_conn{$a}";
+ column {
+ name "Server";
+ format "%-70.70s";
+ value "$key";
+ format_total "TOTAL: %-63.63s";
+ total "$num";
+ };
+ column {
+ name "Conn";
+ format_name "%8s";
+ format "%8d";
+ value "$innd_max_conn{$key}";
+ total "total(%innd_max_conn)";
+ };
+};
+
+section innd_too_many_connects_per_minute {
+ title "INND too many connects per minute:";
+ data "%innd_too_many_connects_per_minute";
+ sort "$innd_too_many_connects_per_minute{$b} <=>
+ $innd_too_many_connects_per_minute{$a}";
+ column {
+ name "System";
+ format "%-71.71s";
+ value "$key";
+ format_total "TOTAL: %-64.64s";
+ total "$num";
+ };
+ column {
+ name "Conn";
+ format_name "%7s";
+ format "%7d";
+ value "$innd_too_many_connects_per_minute{$key}";
+ total "total(%innd_too_many_connects_per_minute)";
+ };
+};
+
+section innd_misc {
+ title "INND misc events:";
+ data "%innd_misc";
+ sort "$innd_misc{$b} <=> $innd_misc{$a}";
+ column {
+ name "Events";
+ format "%-71.71s";
+ value "$key";
+ format_total "TOTAL: %-64.64s";
+ total "$num";
+ };
+ column {
+ name "Count";
+ format_name "%7s";
+ format "%7d";
+ value "$innd_misc{$key}";
+ total "total(%innd_misc)";
+ };
+};
+
+section innd_misc_stat {
+ title "Miscellaneous innd statistics:";
+ data "%innd_misc_stat";
+ sort "$innd_misc_stat{$b} <=> $innd_misc_stat{$a}";
+ double true;
+ top 10;
+ #numbering true;
+ column {
+ primary true;
+ name "Event";
+ format "%-69.69s";
+ value "$key1";
+ format_total "TOTAL: %-62.62s";
+ total "$num";
+ };
+ column {
+ name "Server";
+ format " %-67.67s";
+ value "$key2";
+ total "$num";
+ format_total "TOTAL: %-60.60s";
+ };
+ column {
+ name "Number";
+ format_name "%9s";
+ format "%9d";
+ value "$innd_misc_stat{$key1}{$key2}";
+ total "total(%innd_misc_stat)";
+ };
+};
+
+section innfeed_connect {
+ title "Outgoing Feeds (innfeed) by Articles:";
+ data "%innfeed_offered";
+ sort "$innfeed_accepted{$b} <=> $innfeed_accepted{$a}";
+ numbering true;
+ column {
+ name "Server";
+ format "%-18.18s";
+ value "$key";
+ format_total "TOTAL: %-11.11s";
+ total "$num";
+ };
+ column {
+ name "Offered";
+ format_name "%7s";
+ format "%7d";
+ value "$innfeed_offered{$key}";
+ total "total(%innfeed_offered)";
+ };
+ column {
+ name "Taken";
+ format_name "%7s";
+ format "%7d";
+ value "$innfeed_accepted{$key}";
+ total "total(%innfeed_accepted)";
+ };
+ column {
+ name "Refused";
+ format_name "%7s";
+ format "%7d";
+ value "$innfeed_refused{$key}";
+ total "total(%innfeed_refused)";
+ };
+ column {
+ name "Reject";
+ format_name "%6s";
+ format "%6d";
+ value "$innfeed_rejected{$key}";
+ total "total(%innfeed_rejected)";
+ };
+ column {
+ name "Miss";
+ format_name "%6s";
+ format "%6d";
+ value "$innfeed_missing{$key}";
+ total "total(%innfeed_missing)";
+ };
+ column {
+ name "Spool";
+ format_name "%7s";
+ format "%7d";
+ value "$innfeed_spooled{$key}";
+ total "total(%innfeed_spooled)";
+ };
+ column {
+ name "%Took";
+ format_name "%5s";
+ format "%3d%%";
+ value "$innfeed_offered{$key} == 0 ? 0 :
+ $innfeed_accepted{$key} / $innfeed_offered{$key} * 100";
+ total "total(%innfeed_offered) == 0 ? 0 :
+ total(%innfeed_accepted) / total(%innfeed_offered) * 100";
+ };
+ column {
+ name "Elapsed";
+ format_name "%8s";
+ format "%9s";
+ value "time($innfeed_seconds{$key})";
+ total "time(total(%innfeed_seconds))";
+ };
+ graph {
+ title "Outgoing feeds (innfeed) by Articles";
+ type histo3d;
+ sort "%innfeed_accepted";
+ data {
+ name "Accepted";
+ color "#0000FF";
+ value "%innfeed_accepted";
+ };
+ data {
+ name "Refused";
+ color "#FFAF00";
+ value "%innfeed_refused";
+ };
+ data {
+ name "Rejected";
+ color "#FF0000";
+ value "%innfeed_rejected";
+ };
+ data {
+ name "Missing";
+ color "#00FF00";
+ value "%innfeed_missing";
+ };
+ data {
+ name "Spooled";
+ color "#AF00FF";
+ value "%innfeed_spooled,";
+ };
+ };
+};
+
+section innfeed_volume {
+ title "Outgoing Feeds (innfeed) by Volume:";
+ data "%innfeed_offered";
+ sort "$innfeed_accepted_size{$b} <=> $innfeed_accepted_size{$a}";
+ numbering true;
+ column {
+ name "Server";
+ format "%-17.17s";
+ value "$key";
+ format_total "TOTAL: %-10.10s";
+ total "$num";
+ };
+ column {
+ name "AcceptVol";
+ format "%9s";
+ value "bytes($innfeed_accepted_size{$key})";
+ total "bytes(total(%innfeed_accepted_size))";
+ };
+ column {
+ name "RejectVol";
+ format "%9s";
+ value "bytes($innfeed_rejected_size{$key})";
+ total "bytes(total(%innfeed_rejected_size))";
+ };
+ column {
+ name "TotalVol";
+ format "%9s";
+ value "bytes($innfeed_accepted_size{$key} +
+ $innfeed_rejected_size{$key})";
+ total "bytes(total(%innfeed_accepted_size) +
+ total(%innfeed_rejected_size))";
+ };
+ column {
+ name "Volume/sec";
+ format_name "%11s";
+ format "%9s/s";
+ value "bytes(($innfeed_accepted_size{$key} +
+ $innfeed_rejected_size{$key}) /
+ $innfeed_seconds{$key})";
+ total "bytes((total(%innfeed_accepted_size) +
+ total(%innfeed_rejected_size)) /
+ total(%innfeed_seconds))";
+ };
+ column {
+ name "Vol/Art";
+ format "%9s";
+ value "bytes(($innfeed_accepted_size{$key} +
+ $innfeed_rejected_size{$key}) /
+ ($innfeed_accepted{$key} +
+ $innfeed_rejected{$key}))";
+ total "bytes((total(%innfeed_accepted_size) +
+ total(%innfeed_rejected_size)) /
+ (total(%innfeed_accepted) +
+ total(%innfeed_rejected)))";
+ };
+ column {
+ name "Elapsed";
+ format "%9s";
+ value "time($innfeed_seconds{$key})";
+ total "time(total(%innfeed_seconds))";
+ };
+ graph {
+ title "Outgoing feeds (innfeed) by Volume";
+ type histo3d;
+ sort "%innfeed_accepted_size";
+ data {
+ name "Accepted";
+ color "#0000FF";
+ value "%innfeed_accepted_size";
+ };
+ data {
+ name "Rejected";
+ color "#FFAF00";
+ value "%innfeed_rejected_size";
+ };
+ data {
+ name "Total";
+ color "#00FF00";
+ value "%innfeed_accepted_size +
+ %innfeed_rejected_size";
+ };
+ };
+};
+
+section innfeed_shrunk {
+ title "Backlog files shrunk by innfeed:";
+ data "%innfeed_shrunk";
+ sort "$innfeed_shrunk{$b} <=> $innfeed_shrunk{$a}";
+ column {
+ name "Server";
+ format "%-70.70s";
+ value "$key";
+ format_total "TOTAL: %-63.63s";
+ total "$num";
+ };
+ column {
+ name "Size";
+ format "%8s";
+ value "bytes($innfeed_shrunk{$key})";
+ total "bytes(total(%innfeed_shrunk))";
+ };
+};
+
+section nntplink_connect {
+ title "Outgoing Feeds (nntplink):";
+ data "%nntplink_site";
+ sort "$nntplink_accepted{$b} <=> $nntplink_accepted{$a}";
+ numbering true;
+ column {
+ name "Server";
+ format "%-25.25s";
+ value "$key";
+ format_total "TOTAL: %-18.18s";
+ total "$num";
+ };
+ column {
+ name "Offered";
+ format_name "%8s";
+ format "%8d";
+ value "$nntplink_offered{$key}";
+ total "total(%nntplink_offered)";
+ };
+ column {
+ name "Taken";
+ format_name "%8s";
+ format "%8d";
+ value "$nntplink_accepted{$key}";
+ total "total(%nntplink_accepted)";
+ };
+ column {
+ name "Rejected";
+ format_name "%8s";
+ format "%8d";
+ value "$nntplink_rejected{$key}";
+ total "total(%nntplink_rejected)";
+ };
+ column {
+ name "Failed";
+ format_name "%8s";
+ format "%8d";
+ value "$nntplink_failed{$key}";
+ total "total(%nntplink_failed)";
+ };
+ column {
+ name "%Accpt";
+ format_name "%6s";
+ format "%5d%%";
+ value "$nntplink_offered{$key} == 0 ? 0 :
+ $nntplink_accepted{$key} / $nntplink_offered{$key} * 100";
+ total "total(%nntplink_offered) == 0 ? 0 :
+ total(%nntplink_accepted) / total(%nntplink_offered) * 100";
+ };
+ column {
+ name "Elapsed";
+ format "%10s";
+ value "time($nntplink_times{$key})";
+ total "time(total(%nntplink_times))";
+ };
+ graph {
+ title "Outgoing Feeds (nntplink)";
+ type histo3d;
+ sort "%nntplink_accepted";
+ data {
+ name "Articles accepted";
+ color "#0000FF";
+ value "%nntplink_accepted";
+ };
+ data {
+ name "Articles rejected";
+ color "#FFAF00";
+ value "%nntplink_rejected";
+ };
+ data {
+ name "Articles failed";
+ color "#FF0000";
+ value "%nntplink_failed";
+ };
+ };
+};
+
+section nntplink_connect2 {
+ title "Outgoing Feeds (nntplink) - other information:";
+ data "%nntplink_site";
+ sort "$nntplink_accepted{$b} <=> $nntplink_accepted{$a}";
+ numbering true;
+ column {
+ name "Server";
+ format "%-20.20s";
+ value "$key";
+ format_total "TOTAL: %-13.13s";
+ total "$num";
+ };
+ column {
+ name "Conn";
+ format_name "%4s";
+ format "%4d";
+ value "$nntplink_site{$key}";
+ total "total(%nntplink_site)";
+ };
+ column {
+ name "Ok";
+ format_name "%4s";
+ format "%4d";
+ value "$nntplink_site{$key} - ($nntplink_eof{$key} +
+ $nntplink_sockerr{$key} +
+ $nntplink_selecterr{$key} +
+ $nntplink_hiload{$key} + $nntplink_bpipe{$key} +
+ $nntplink_nospace{$key} + $nntplink_auth{$key} +
+ $nntplink_expire{$key} + $nntplink_fail{$key})";
+ total "total(%nntplink_site) - (total(%nntplink_eof) +
+ total(%nntplink_sockerr) +
+ total(%nntplink_selecterr) +
+ total(%nntplink_hiload) +
+ total(%nntplink_bpipe) +
+ total(%nntplink_nospace) +
+ total(%nntplink_auth) +
+ total(%nntplink_expire) +
+ total(%nntplink_fail))";
+ };
+ column {
+ name "EOF";
+ format_name "%3s";
+ format "%3d";
+ value "$nntplink_eof{$key}";
+ total "total(%nntplink_eof)";
+ };
+ column {
+ name "Sock";
+ format_name "%4s";
+ format "%4d";
+ value "$nntplink_sockerr{$key}";
+ total "total(%nntplink_sockerr)";
+ };
+ column {
+ name "Slct";
+ format_name "%4s";
+ format "%4d";
+ value "$nntplink_selecterr{$key}";
+ total "total(%nntplink_selecterr)";
+ };
+ column {
+ name "Load";
+ format_name "%4s";
+ format "%4d";
+ value "$nntplink_hiload{$key}";
+ total "total(%nntplink_hiload)";
+ };
+ column {
+ name "Bpip";
+ format_name "%4s";
+ format "%4d";
+ value "$nntplink_bpipe{$key}";
+ total "total(%nntplink_bpipe)";
+ };
+ column {
+ name "Spce";
+ format_name "%4s";
+ format "%4d";
+ value "$nntplink_nospace{$key}";
+ total "total(%nntplink_nospace)";
+ };
+ column {
+ name "Exp";
+ format_name "%4s";
+ format "%4d";
+ value "$nntplink_expire{$key}";
+ total "total(%nntplink_expire)";
+ };
+ column {
+ name "Auth";
+ format_name "%4s";
+ format "%4d";
+ value "$nntplink_auth{$key}";
+ total "total(%nntplink_auth)";
+ };
+ column {
+ name "Othr";
+ format_name "%4s";
+ format "%4d";
+ value "$nntplink_fail{$key}";
+ total "total(%nntplink_fail)";
+ };
+ column {
+ name "Pct";
+ format_name "%4s";
+ format "%3d%%";
+ value "$nntplink_site{$key} ?
+ 100 * ($nntplink_site{$key} -
+ ($nntplink_eof{$key} + $nntplink_sockerr{$key} +
+ $nntplink_selecterr{$key} +
+ $nntplink_hiload{$key} + $nntplink_bpipe{$key} +
+ $nntplink_nospace{$key} + $nntplink_auth{$key} +
+ $nntplink_expire{$key} +
+ $nntplink_fail{$key})) / $nntplink_site{$key} : 0";
+ total "total(%nntplink_site) ?
+ 100 * (total(%nntplink_site) -
+ (total(%nntplink_eof) +
+ total(%nntplink_sockerr) +
+ total(%nntplink_selecterr) +
+ total(%nntplink_hiload) +
+ total(%nntplink_bpipe) +
+ total(%nntplink_nospace) +
+ total(%nntplink_auth) +
+ total(%nntplink_expire) +
+ total(%nntplink_fail))) / total(%nntplink_site) : 0";
+ };
+};
+
+section innxmit_connect {
+ title "Outgoing Feeds (innxmit) by Articles:";
+ data "%innxmit_times";
+ sort "$innxmit_accepted{$b} <=> $innxmit_accepted{$a}";
+ numbering true;
+ column {
+ name "Server";
+ format "%-27.27s";
+ value "$key";
+ format_total "TOTAL: %-20.20s";
+ total "$num";
+ };
+ column {
+ name "Offered";
+ format_name "%7s";
+ format "%7d";
+ value "$innxmit_offered{$key}";
+ total "total(%innxmit_offered)";
+ };
+ column {
+ name "Taken";
+ format_name "%7s";
+ format "%7d";
+ value "$innxmit_accepted{$key}";
+ total "total(%innxmit_accepted)";
+ };
+ column {
+ name "Refused";
+ format_name "%7s";
+ format "%7d";
+ value "$innxmit_refused{$key}";
+ total "total(%innxmit_refused)";
+ };
+ column {
+ name "Reject";
+ format_name "%7s";
+ format "%7d";
+ value "$innxmit_rejected{$key}";
+ total "total(%innxmit_rejected)";
+ };
+ column {
+ name "Miss";
+ format_name "%5s";
+ format "%5d";
+ value "$innxmit_missing{$key}";
+ total "total(%innxmit_rejected)";
+ };
+ column {
+ name "%Acc";
+ format_name "%4s";
+ format "%3d%%";
+ value "$innxmit_offered{$key} == 0 ? 0 :
+ $innxmit_accepted{$key} / $innxmit_offered{$key} * 100";
+ total "total(%innxmit_offered) == 0 ? 0 :
+ total(%innxmit_accepted) / total(%innxmit_offered) * 100";
+ };
+ column {
+ name "Elapsed";
+ format "%8s";
+ value "time($innxmit_times{$key})";
+ total "time(total(%innxmit_times))";
+ };
+ graph {
+ title "Outgoing Feeds (innxmit)";
+ type histo3d;
+ sort "%innxmit_accepted";
+ data {
+ name "Art. accepted";
+ color "#0000FF";
+ value "%innxmit_accepted";
+ };
+ data {
+ name "Art. refused";
+ color "#FFAF00";
+ value "%innxmit_refused";
+ };
+ data {
+ name "Art. rejected";
+ color "#FF0000";
+ value "%innxmit_rejected";
+ };
+ data {
+ name "Art. missing";
+ color "#00FF00";
+ value "%innxmit_missing";
+ };
+ };
+};
+
+section innxmit_volume {
+ title "Outgoing Feeds (innxmit) by Volume:";
+ data "%innxmit_accepted_size";
+ sort "$innxmit_accepted_size{$b} <=> $innxmit_accepted_size{$a}";
+ numbering true;
+ column {
+ name "Server";
+ format "%-24.24s";
+ value "$key";
+ format_total "TOTAL: %-17.17s";
+ total "$num";
+ };
+ column {
+ name "AcceptVol";
+ format "%9s";
+ value "bytes($innxmit_accepted_size{$key})";
+ total "bytes(total(%innxmit_accepted_size))";
+ };
+ column {
+ name "RejectVol";
+ format "%9s";
+ value "bytes($innxmit_rejected_size{$key})";
+ total "bytes(total(%innxmit_rejected_size))";
+ };
+ column {
+ name "TotalVol";
+ format "%9s";
+ value "bytes($innxmit_accepted_size{$key} +
+ $innxmit_rejected_size{$key} +
+ $innxmit_bytes{$key})";
+ total "bytes(total(%innxmit_accepted_size) +
+ total(%innxmit_rejected_size) +
+ total(%innxmit_bytes))";
+ };
+ column {
+ name "KB/s";
+ format_name "%5s";
+ format "%5.1f";
+ value "($innxmit_accepted_size{$key} +
+ $innxmit_rejected_size{$key} +
+ $innxmit_bytes{$key}) /
+ $innxmit_times{$key} / 1024";
+ total "(total(%innxmit_accepted_size) +
+ total(%innxmit_rejected_size) +
+ total(%innxmit_bytes)) /
+ total(%innxmit_times) / 1024";
+ };
+ column {
+ name "Vol/Art";
+ format "%9s";
+ value "bytes(($innxmit_accepted_size{$key} +
+ $innxmit_rejected_size{$key} +
+ $innxmit_bytes{$key}) /
+ ($innxmit_accepted{$key} +
+ $innxmit_rejected{$key}))";
+ total "bytes((total(%innxmit_accepted_size) +
+ total(%innxmit_rejected_size) +
+ total(%innxmit_bytes)) /
+ (total(%innxmit_accepted) +
+ total(%innxmit_rejected)))";
+ };
+ column {
+ name "Elapsed";
+ format "%8s";
+ value "time($innxmit_times{$key})";
+ total "time(total(%innxmit_times))";
+ };
+ graph {
+ title "Outgoing Feeds (innxmit)";
+ type histo3d;
+ sort "%innxmit_accepted";
+ data {
+ name "Articles accepted";
+ color "#0000FF";
+ value "%innxmit_accepted_size";
+ };
+ data {
+ name "Articles rejected";
+ color "#FFAF00";
+ value "%innxmit_rejected_size";
+ };
+ data {
+ name "Total";
+ color "#FF0000";
+ value "%innxmit_missing";
+ };
+ };
+};
+
+
+section innxmit_connect2 {
+ title "Outgoing Feeds (innxmit) - other information:";
+ data "%innxmit_site";
+ sort "$innxmit_accepted{$b} <=> $innxmit_accepted{$a}";
+ numbering true;
+ column {
+ name "Server";
+ format "%-25.25s";
+ value "$key";
+ format_total "TOTAL: %-18.18s";
+ total "$num";
+ };
+ column {
+ name "Conn";
+ format_name "%5s";
+ format "%5d";
+ value "$innxmit_site{$key}";
+ total "total(%innxmit_site)";
+ };
+ column {
+ name "Ok";
+ format_name "%5s";
+ format "%5d";
+ value "$innxmit_site{$key} -
+ ($innxmit_afail_host{$key} +
+ $innxmit_hiload{$key} + $innxmit_nospace{$key} +
+ $innxmit_cfail_host{$key} +
+ $innxmit_expire{$key} + $innxmit_crefused{$key})";
+ total "total(%innxmit_site) -
+ (total(%innxmit_afail_host) +
+ total(%innxmit_hiload) +
+ total(%innxmit_nospace) +
+ total(%innxmit_cfail_host) +
+ total(%innxmit_expire) +
+ total(%innxmit_crefused))";
+ };
+ column {
+ name "Auth";
+ format_name "%4s";
+ format "%4d";
+ value "$innxmit_afail_host{$key}";
+ total "total(%innxmit_afail_host)";
+ };
+ column {
+ name "Load";
+ format_name "%4s";
+ format "%4d";
+ value "$innxmit_hiload{$key}";
+ total "total(%innxmit_hiload)";
+ };
+ column {
+ name "Space";
+ format_name "%5s";
+ format "%5d";
+ value "$innxmit_nospace{$key}";
+ total "total(%innxmit_nospace)";
+ };
+ column {
+ name "Expire";
+ format_name "%6s";
+ format "%6d";
+ value "$innxmit_expire{$key}";
+ total "total(%innxmit_expire)";
+ };
+ column {
+ name "Connct";
+ format_name "%6s";
+ format "%6d";
+ value "$innxmit_cfail_host{$key}";
+ total "total(%innxmit_cfail_host)";
+ };
+ column {
+ name "Other";
+ format_name "%6s";
+ format "%6d";
+ value "$innxmit_crefused{$key}";
+ total "total(%innxmit_crefused)";
+ };
+ column {
+ name "Pct";
+ format_name "%4s";
+ format "%3d%%";
+ value "$innxmit_site{$key} ? 100 *
+ ($innxmit_site{$key} -
+ ($innxmit_afail_host{$key} +
+ $innxmit_hiload{$key} + $innxmit_nospace{$key} +
+ $innxmit_cfail_host{$key} +
+ $innxmit_expire{$key} +
+ $innxmit_crefused{$key})) / $innxmit_site{$key} : 0";
+ total "total(%innxmit_site) ?
+ 100 * (total(%innxmit_site) -
+ (total(%innxmit_afail_host) +
+ total(%innxmit_hiload) +
+ total(%innxmit_nospace) +
+ total(%innxmit_cfail_host) +
+ total(%innxmit_expire) +
+ total(%innxmit_crefused))) / total(%innxmit_site) : 0";
+ };
+};
+
+section innxmit_unwanted {
+ title "Sites fed by innxmit rejecting bad articles:";
+ data "%innxmit_badart";
+ sort "$innxmit_badart{$b} <=> $innxmit_badart{$a}";
+ column {
+ numbering true;
+ name "Server";
+ format "%-23.23s";
+ value "$key";
+ format_total "TOTAL: %-16.16s";
+ total "$num";
+ };
+ column {
+ name "Total";
+ format_name "%6s";
+ format "%6d";
+ value "$innxmit_badart{$key}";
+ total "total(%innxmit_badart)";
+ };
+ column {
+ name "Group";
+ format_name "%6s";
+ format "%6d";
+ value "$innxmit_uw_ng_s{$key}";
+ total "total(%innxmit_uw_ng_s)";
+ };
+ column {
+ name "Dist";
+ format_name "%5s";
+ format "%5d";
+ value "$innxmit_uw_dist_s{$key}";
+ total "total(%innxmit_uw_dist_s)";
+ };
+ column {
+ name "Duplic";
+ format_name "%6s";
+ format "%6d";
+ value "$innxmit_duplicate{$key}";
+ total "total(%innxmit_duplicate)";
+ };
+ column {
+ name "Unapp";
+ format_name "%5s";
+ format "%5d";
+ value "$innxmit_unapproved{$key}";
+ total "total(%innxmit_unapproved)";
+ };
+ column {
+ name "TooOld";
+ format_name "%6s";
+ format "%6d";
+ value "$innxmit_tooold{$key}";
+ total "total(%innxmit_tooold)";
+ };
+ column {
+ name "Site";
+ format_name "%4s";
+ format "%4d";
+ value "$innxmit_uw_site{$key}";
+ total "total(%innxmit_uw_site)";
+ };
+ column {
+ name "Line";
+ format_name "%4s";
+ format "%4d";
+ value "$innxmit_linecount{$key}";
+ total "total(%innxmit_linecount)";
+ };
+ column {
+ name "Other";
+ format_name "%5s";
+ format "%5d";
+ value "$innxmit_others{$key}";
+ total "total(%innxmit_others)";
+ };
+};
+
+section crosspost {
+ title "Crosspost stats:";
+ data "%crosspost";
+ column {
+ name "Events";
+ format "%-63.63s";
+ value "$key";
+ format_total "TOTAL: %-56.56s";
+ total "$num";
+ };
+ column {
+ name "Number";
+ value "$crosspost{$key}";
+ format "%7s";
+ total "total(%crosspost)";
+ };
+ column {
+ name "Num/min";
+ value "$crosspost_times{$key}";
+ format "%7s";
+ total "total(%crosspost_times)";
+ };
+};
+
+section batcher_elapsed {
+ title "UUCP batches created:";
+ data "%batcher_elapsed";
+ column {
+ name "Server";
+ format "%-41.41s";
+ value "$key";
+ format_total "TOTAL: %-34.34s";
+ total "$num";
+ };
+ column {
+ name "Offered";
+ format_name "%7s";
+ format "%7d";
+ value "$batcher_offered{$key}";
+ total "total(%batcher_offered)";
+ };
+ column {
+ name "Articles";
+ format_name "%8s";
+ format "%8d";
+ value "$batcher_articles{$key}";
+ total "total(%batcher_articles)";
+ };
+ column {
+ name "Size";
+ format "%10s";
+ value "bytes($batcher_bytes{$key})";
+ total "bytes(total(%batcher_bytes))";
+ };
+ column {
+ name "Elapsed";
+ format "%9s";
+ value "time($batcher_elapsed{$key})";
+ total "time(total(%batcher_elapsed))";
+ };
+};
+
+section rnews_host {
+ title "Rnews articles offered from:";
+ data "%rnews_host";
+ sort "$rnews_host{$b} <=> $rnews_host{$a}";
+ column {
+ name "System";
+ format "%-71.71s";
+ value "$key";
+ format_total "TOTAL: %-64.64s";
+ total "$num";
+ };
+ column {
+ name "Offered";
+ format_name "%7s";
+ format "%7d";
+ value "$rnews_host{$key}";
+ total "total(%rnews_host)";
+ };
+};
+
+section rnews_rejected {
+ title "Rnews connections rejected:";
+ data "%rnews_rejected";
+ sort "$rnews_rejected{$b} <=> $rnews_rejected{$a}";
+ column {
+ name "Reason";
+ format "%-71.71s";
+ value "$key";
+ format_total "TOTAL: %-64.64s";
+ total "$num";
+ };
+ column {
+ name "Conn";
+ format_name "%7s";
+ format "%7d";
+ value "$rnews_rejected{$key}";
+ total "total(%rnews_rejected)";
+ };
+};
+
+section rnews_misc {
+ title "Miscellaneous rnews statistics:";
+ data "%rnews_misc";
+ sort "$rnews_misc{$b} <=> $rnews_misc{$a}";
+ double true;
+ column {
+ primary true;
+ name "Event";
+ format "%-69.69s";
+ value "$key1";
+ format_total "TOTAL: %-62.62s";
+ total "";
+ };
+ column {
+ name "Element";
+ format " %-67.67s";
+ value "$key2";
+ total "";
+ };
+ column {
+ name "Number";
+ format_name "%9s";
+ format "%9d";
+ value "$rnews_misc{$key1}{$key2}";
+ total "total(%rnews_misc)";
+ };
+};
+
+section nnrpd_groups {
+ title "NNRP readership statistics:";
+ data "%nnrpd_articles";
+ sort "$nnrpd_articles{$b} <=> $nnrpd_articles{$a}";
+ numbering true;
+ column {
+ name "System";
+ format "%-30.30s";
+ value "$key";
+ format_total "TOTAL: %-23.23s";
+ total "$num";
+ };
+ column {
+ name "Conn";
+ format_name "%4s";
+ format "%4d";
+ value "$nnrpd_connect{$key}";
+ total "total(%nnrpd_connect)";
+ };
+ column {
+ name "Arts";
+ format_name "%6s";
+ format "%6d";
+ value "$nnrpd_articles{$key}";
+ total "total(%nnrpd_articles)";
+ };
+ column {
+ name "Size";
+ format "%9s";
+ value "bytes($nnrpd_bytes{$key})";
+ total "bytes(total(%nnrpd_bytes))";
+ };
+ column {
+ name "Groups";
+ format_name "%6s";
+ format "%6d";
+ value "$nnrpd_groups{$key}";
+ total "total(%nnrpd_groups)";
+ };
+ column {
+ name "Post";
+ format_name "%4s";
+ format "%4d";
+ value "$nnrpd_post_ok{$key}";
+ total "total(%nnrpd_post_ok)";
+ };
+ column {
+ name "Rej";
+ format_name "%4s";
+ format "%4d";
+ value "$nnrpd_post_rej{$key} +
+ $nnrpd_post_error{$key}";
+ total "total(%nnrpd_post_rej) +
+ total(%nnrpd_post_error)";
+ };
+ column {
+ name "Elapsed";
+ format "%9s";
+ value "time($nnrpd_times{$key})";
+ total "time(total(%nnrpd_times))";
+ };
+};
+
+section nnrpd_dom_groups {
+ title "NNRP readership statistics (by domain):";
+ data "%nnrpd_dom_connect";
+ sort "$nnrpd_dom_articles{$b} <=> $nnrpd_dom_articles{$a}";
+ numbering true;
+ column {
+ name "System";
+ format "%-30.30s";
+ value "$key";
+ format_total "TOTAL: %-23.23s";
+ total "$num";
+ };
+ column {
+ name "Conn";
+ format_name "%4s";
+ format "%4d";
+ value "$nnrpd_dom_connect{$key}";
+ total "total(%nnrpd_dom_connect)";
+ };
+ column {
+ name "Arts";
+ format_name "%6s";
+ format "%6d";
+ value "$nnrpd_dom_articles{$key}";
+ total "total(%nnrpd_dom_articles)";
+ };
+ column {
+ name "Size";
+ format "%9s";
+ value "bytes($nnrpd_dom_bytes{$key})";
+ total "bytes(total(%nnrpd_dom_bytes))";
+ };
+ column {
+ name "Groups";
+ format_name "%6s";
+ format "%6d";
+ value "$nnrpd_dom_groups{$key}";
+ total "total(%nnrpd_dom_groups)";
+ };
+ column {
+ name "Post";
+ format_name "%4s";
+ format "%4d";
+ value "$nnrpd_dom_post_ok{$key}";
+ total "total(%nnrpd_dom_post_ok)";
+ };
+ column {
+ name "Rej";
+ format_name "%4s";
+ format "%4d";
+ value "$nnrpd_dom_post_rej{$key} +
+ $nnrpd_dom_post_error{$key}";
+ total "total(%nnrpd_dom_post_rej) +
+ total(%nnrpd_dom_post_error)";
+ };
+ column {
+ name "Elapsed";
+ format "%9s";
+ value "time($nnrpd_dom_times{$key})";
+ total "time(total(%nnrpd_dom_times))";
+ };
+};
+
+section nnrpd_auth {
+ title "NNRP auth users:";
+ data "%nnrpd_auth";
+ top 20;
+ sort "$nnrpd_auth{$b} <=> $nnrpd_auth{$a}";
+ column {
+ name "User";
+ format "%-71.71s";
+ value "$key";
+ format_total "TOTAL: %-64.64s";
+ total "$num";
+ };
+ column {
+ name "Conn";
+ format_name "%7s";
+ format "%7d";
+ value "$nnrpd_auth{$key}";
+ total "total(%nnrpd_auth)";
+ };
+};
+
+section nnrpd_resource {
+ title "NNRP total resource statistics:";
+ data "%nnrpd_resource_elapsed";
+ top 20;
+ sort "$nnrpd_resource_elapsed{$b} <=> $nnrpd_resource_elapsed{$a}";
+ column {
+ name "System";
+ format "%-40.40s";
+ format_total "TOTAL: %-33.33s";
+ value "$key";
+ total "$num";
+ };
+ column {
+ name "User(ms)";
+ format_name "%9s";
+ format "%9.3f";
+ value "$nnrpd_resource_user{$key}";
+ total "total(%nnrpd_resource_user)";
+ };
+ column {
+ name "System(ms)";
+ format_name "%9s";
+ format "%9.3f";
+ value "$nnrpd_resource_system{$key}";
+ total "total(%nnrpd_resource_system)";
+ };
+ column {
+ name "Idle(ms)";
+ format_name "%9s";
+ format "%9.3f";
+ value "$nnrpd_resource_idle{$key}";
+ total "total(%nnrpd_resource_idle)";
+ };
+ column {
+ name "Elapsed";
+ format_name "%8s";
+ format "%9s";
+ value "time($nnrpd_resource_elapsed{$key})";
+ total "time(total(%nnrpd_resource_elapsed))";
+ };
+};
+
+section nnrpd_curious {
+ title "Curious NNRP server explorers:";
+ data "%nnrpd_curious";
+ top 20;
+ sort "$nnrpd_curious{$b} <=> $nnrpd_curious{$a}";
+ column {
+ name "System";
+ format "%-71.71s";
+ value "$key";
+ format_total "TOTAL: %-64.64s";
+ total "$num";
+ };
+ column {
+ name "Conn";
+ format_name "%7s";
+ format "%7d";
+ value "$nnrpd_curious{$key}";
+ total "total(%nnrpd_curious)";
+ };
+};
+
+section nnrpd_no_permission {
+ title "NNRP no permission clients:";
+ data "%nnrpd_no_permission";
+ sort "$nnrpd_no_permission{$b} <=> $nnrpd_no_permission{$a}";
+ column {
+ name "System";
+ format "%-71.71s";
+ value "$key";
+ format_total "TOTAL: %-64.64s";
+ total "$num";
+ };
+ column {
+ name "Conn";
+ format_name "%7s";
+ format "%7d";
+ value "$nnrpd_no_permission{$key}";
+ total "total(%nnrpd_no_permission)";
+ };
+};
+
+section nnrpd_gethostbyaddr {
+ title "NNRP gethostbyaddr failures:";
+ data "%nnrpd_gethostbyaddr";
+ top 20;
+ sort "$nnrpd_gethostbyaddr{$b} <=> $nnrpd_gethostbyaddr{$a}";
+ column {
+ name "System";
+ format "%-71.71s";
+ value "$key";
+ format_total "TOTAL: %-64.64s";
+ total "$num";
+ };
+ column {
+ name "Conn";
+ format_name "%7s";
+ format "%7d";
+ value "$nnrpd_gethostbyaddr{$key}";
+ total "total(%nnrpd_gethostbyaddr)";
+ };
+};
+
+section nnrpd_unrecognized {
+ title "NNRP unrecognized commands (by host):";
+ data "%nnrpd_unrecognized";
+ sort "$nnrpd_unrecognized{$b} <=> $nnrpd_unrecognized{$a}";
+ column {
+ name "System";
+ format "%-71.71s";
+ value "$key";
+ format_total "TOTAL: %-64.64s";
+ total "$num";
+ };
+ column {
+ name "Conn";
+ format_name "%7s";
+ format "%7d";
+ value "$nnrpd_unrecognized{$key}";
+ total "total(%nnrpd_unrecognized)";
+ };
+};
+
+section nnrpd_unrecognized2 {
+ title "NNRP unrecognized commands (by command):";
+ data "%nnrpd_unrecogn_cmd";
+ sort "$nnrpd_unrecogn_cmd{$b} <=> $nnrpd_unrecogn_cmd{$a}";
+ column {
+ name "Command";
+ format "%-71.71s";
+ value "$key";
+ format_total "TOTAL: %-64.64s";
+ total "$num";
+ };
+ column {
+ name "Count";
+ format_name "%7s";
+ format "%7d";
+ value "$nnrpd_unrecogn_cmd{$key}";
+ total "total(%nnrpd_unrecogn_cmd)";
+ };
+};
+
+section nnrpd_timeout {
+ title "NNRP client timeouts:";
+ data "%nnrpd_timeout";
+ top 20;
+ sort "$nnrpd_timeout{$b} <=> $nnrpd_timeout{$a}";
+ column {
+ name "System";
+ format "%-67.67s";
+ value "$key";
+ format_total "TOTAL: %-60.60s";
+ total "$num";
+ };
+ column {
+ name "Conn";
+ format_name "%5s";
+ format "%5d";
+ value "$nnrpd_timeout{$key}";
+ total "total(%nnrpd_timeout)";
+ };
+ column {
+ name "Peer";
+ format_name "%5s";
+ format "%5d";
+ value "$nnrpd_reset_peer{$key}";
+ total "total(%nnrpd_reset_peer)";
+ };
+};
+
+section nnrpd_hierarchy {
+ title "Newsgroup request counts (by category):";
+ data "%nnrpd_hierarchy";
+ sort "$nnrpd_hierarchy{$b} <=> $nnrpd_hierarchy{$a}";
+ numbering true;
+ column {
+ name "Category";
+ format "%-64.64s";
+ value "$key";
+ format_total "TOTAL: %-57.57s";
+ total "$num";
+ };
+ column {
+ name "Count";
+ format_name "%7s";
+ format "%7d";
+ value "$nnrpd_hierarchy{$key}";
+ total "total(%nnrpd_hierarchy)";
+ };
+ column {
+ name "Pct";
+ format_name "%6s";
+ format "%5.1f%%";
+ value "$nnrpd_hierarchy{$key} /
+ total(%nnrpd_hierarchy) * 100";
+ total "100";
+ };
+ # graph : type piechart
+};
+
+section nnrpd_group {
+ title "Newsgroup request counts (by newsgroup):";
+ data "%nnrpd_group";
+ sort "$nnrpd_group{$b} <=> $nnrpd_group{$a}";
+ top 100;
+ numbering true;
+ column {
+ name "Newsgroup";
+ format "%-71.71s";
+ value "$key";
+ format_total "TOTAL: %-64.64s";
+ total "$num";
+ };
+ column {
+ name "Count";
+ format_name "%7s";
+ format "%7d";
+ value "$nnrpd_group{$key}";
+ total "total(%nnrpd_group)";
+ };
+};
+
+section ihave_site {
+ title "IHAVE messages offered from:";
+ data "%controlchan_ihave_site";
+ sort "$controlchan_ihave_site{$b} <=> $controlchan_ihave_site{$a}";
+ column {
+ name "System";
+ format "%-71.71s";
+ value "$key";
+ format_total "TOTAL: %-64.64s";
+ total "$num";
+ };
+ column {
+ name "Offered";
+ format_name "%7s";
+ format "%7d";
+ value "$controlchan_ihave_site{$key}";
+ total "total(%controlchan_ihave_site)";
+ };
+};
+
+section sendme_site {
+ title "SENDME messages offered from:";
+ data "%controlchan_sendme_site";
+ sort "$controlchan_sendme_site{$b} <=>
+ $controlchan_sendme_site{$a}";
+ column {
+ name "System";
+ format "%-71.71s";
+ value "$key";
+ format_total "TOTAL: %-64.64s";
+ total "$num";
+ };
+ column {
+ name "Offered";
+ format_name "%7s";
+ format "%7d";
+ value "$controlchan_sendme_site{$key}";
+ total "total(%controlchan_sendme_site)";
+ };
+};
--- /dev/null
+## $Revision: 4351 $
+## innwatch.ctl -- control file for innwatch.
+## Indicates what to run to test the state of the news system, and what
+## to do about it. Format:
+## !state!when!command!test!limit!command!reason/comment
+## where
+## <!> Delimiter; pick from [,:@;?!]
+## <state> State to enter if true.
+## <when> States we must be in to match.
+## <command> Command to run to test condition.
+## <test> Operator to use in test(1) command.
+## <limit> Value to test against.
+## <command> Command for innwatch to perform; use exit,
+## flush, go, pause, shutdown, skip, or throttle.
+## <reason> Used in ctlinnd command (if needed).
+
+## First, just exit innwatch if innd has gone away.
+!!! test -f ${LOCKS}/innd.pid && echo 0 || echo 1 ! eq ! 1 ! exit ! innd dead
+
+## If another innwatch has started, exit.
+!!! cat ${LOCKS}/LOCK.${PROGNAME} ! ne ! $$ ! exit ! innwatch replaced
+
+## Next test the load average. Above first threshold pause, above higher
+## threshold throttle, below restart limit undo whatever was done.
+!load!load hiload! uptime | tr -d ,. | awk '{ print $(NF - 2) }' ! lt ! ${INNWATCHLOLOAD} ! go ! loadav
+!hiload!+ load! uptime | tr -d ,. | awk '{ print $(NF - 2) }' ! gt ! ${INNWATCHHILOAD} ! throttle ! loadav
+!load!+! uptime | tr -d ,. | awk '{ print $(NF - 2) }' ! gt ! ${INNWATCHPAUSELOAD} ! pause ! loadav
+##
+## Uncomment these to keep overchan backlog in check. Assumes your overchan
+## feed is named 'overview!'.
+#::overblog:ctlinnd feedinfo overview!|awk 'NR==1{print $7}':lt:100000:go:overviewbacklog
+#:overblog:+:ctlinnd feedinfo overview!|awk 'NR==1{print $7}':gt:400000:throttle:overviewbacklog
+##
+
+## If load is OK, check space (and inodes) on various filesystems
+!!! ${INNDF} . ! lt ! ${INNWATCHSPOOLSPACE} ! throttle ! No space (spool)
+!!! ${INNDF} ${BATCH} ! lt ! ${INNWATCHBATCHSPACE} ! throttle ! No space (newsq)
+!!! ${INNDF} ${PATHDB} ! lt ! ${INNWATCHLIBSPACE} ! throttle ! No space (newslib)
+!!! ${INNDF} -i . ! lt ! ${INNWATCHSPOOLNODES} ! throttle ! No space (spool inodes)
+!!! ${INNDF} ${OVERVIEWDIR} ! lt ! ${INNWATCHSPOOLSPACE} ! throttle ! No space (overview)
--- /dev/null
+## $Id: moderators 7448 2005-12-11 22:21:06Z eagle $
+##
+## moderators - Mailing addresses for moderators.
+##
+## Whenever possible, the master moderator database at moderators.isc.org
+## should be used rather than adding specific entries to this file. The
+## master database will list any publically propagated moderated group;
+## changes should be sent to moderators-request@isc.org.
+##
+## Exceptions listed in this file are mostly hierarchies for which the
+## master database isn't accurate or updated quickly enough. Local
+## moderated newsgroups can also be added to this file.
+##
+## Format:
+## <newsgroup>:<pathname>
+##
+## <newsgroup> Shell-style newsgroup pattern or specific newsgroup
+## <pathname> Mail address, "%s" becomes newgroup name with dots
+## changed to dashes.
+##
+## The first matching entry is used.
+
+## Public hierarchies with exceptions.
+fido7.*:%s@fido7.ru
+ffm.*:%s@moderators.arcornews.de
+fj.*:%s@moderators.fj-news.org
+medlux.*:%s@news.medlux.ru
+nl.*:%s@nl.news-admin.org
+relcom.*:%s@moderators.relcom.ru
+ukr.*:%s@sita.kiev.ua
+
+## Direct all other public hierarchies to the master moderator database.
+*:%s@moderators.isc.org
--- /dev/null
+Sample MOTD file. Any text you put in here will be returned
+to the news reader when it issues the LIST MOTD command. It is not
+an error if this file does not exist nor if it is empty.
--- /dev/null
+# Sample config file for news2mail. Format is:
+#
+# mailing-list-name address
+#
+#
+# In newsfeeds put an entry like:
+#
+# n2m!:!*:Tc,Ac,Wn*:/usr/news/bin/news2mail
+#
+# and for each mailing list have an entry list:
+#
+# news-software@localhost.our.domain.com:rec.pets.redants.*:Tm:n2m!
+#
+# The site name used in the newfeeds entry for a mailing list (above
+# ``news-software@localhost.our.domain.com'') must be the same as the first
+# field in an entry in this file. It is what is put into the ``To'' header of
+# mailed messages.
+#
+#
+news-software@localhost.our.domain.com news-software@real-host.somewhere.com
--- /dev/null
+## $Id: newsfeeds.in 7741 2008-04-06 09:51:47Z iulius $
+##
+## newsfeeds - determine where Usenet articles get sent
+##
+## Format:
+## site[/exclude,exclude...]\
+## :pattern,pattern...[/distrib,distrib...]\
+## :flag,flag...\
+## :param
+##
+## This file is complicated -- see newsfeeds(5) for full details.
+
+## The ME feed entry is special magic.
+##
+## "/exclude" entries for this feed entry will cause INN to reject all
+## articles that have passed through those listed sites (by matching
+## Path: entries). There are some "pseudo-sites" in general use that can
+## be listed as exclusions to reject specific types of 3rd-party cancel
+## messages (see the "Cancel FAQ" in news.admin.net-abuse.usenet):
+##
+## cyberspam Cancels for spam, munged articles, binaries
+## spewcancel Cancels for munged articles and runaway gateways
+## bincancel Cancels for binary postings to non-binary groups
+## udpcancel Cancels to force sites to enforce antispam policies
+##
+## The "pattern" field for this feed entry gives the initial subscription
+## list for all other feeds specified in this file. These patterns are
+## *prepended* to all other feed patterns. Using this feature is
+## confusing and mildly discouraged; make sure you understand the man
+## page before using it.
+##
+## "/distrib" for this feed entry specifies what distributions the server
+## will accept. If any distributions are listed there, the server will
+## accept only articles with those distributions. If all the
+## distributions listed are negated (starting with !), then the server
+## will only accept articles without those distributions.
+##
+## For the ME line (and the ME line *only*), patterns affect *outgoing*
+## feeds and distributions affect *incoming* feeds.
+
+# Empty default subscription list, reject all incoming articles with a
+# distribution of "local" or "collabra-internal," accept all others.
+ME:!*/!local,!collabra-internal::
+
+# The same as the above, but would reject all posts that have
+# news.example.com in the path (posts passing through that site).
+#ME/news.example.com:!*/!local,!collabra-internal::
+
+# The special feed that handles all newsgroup control messages. Only
+# disable this if you want to ignore all newsgroup control messages; INN
+# no longer handles any control messages except cancel internally.
+controlchan!\
+ :!*,control,control.*,!control.cancel\
+ :Tc,Wnsm:@prefix@/bin/controlchan
+
+## Uncomment if you're using innfeed. This feed tells INN how to run
+## innfeed, and then every site you're feeding with innfeed has a
+## flag of Tm and an argument of "innfeed!" to funnel into this feed.
+## The feed pattern for innfeed should *always* be "!*"; don't ever
+## feed articles directly into innfeed.
+##
+## Add "-y" as an option to startinnfeed to use the name of each feed as
+## the name of the host to feed articles to; without "-y" an innfeed.conf
+## file is needed.
+
+# innfeed funnel master.
+#innfeed!\
+# :!*\
+# :Tc,Wnm*:@prefix@/bin/startinnfeed
+
+## Only uncomment this feed if both enableoverview and useoverchan are
+## set to true in inn.conf. By default, innd will write out overview
+## internally and doesn't need or want this feed, but useoverchan can
+## optionally be set to true and this feed uncommented to move those
+## operations out of innd's main loop.
+
+# News overview.
+#overview!:*:Tc,WnteO:@prefix@/bin/overchan
+
+
+## OUTGOING NORMAL FEED EXAMPLES
+
+# A real-time feed through innfeed. Don't send articles with a distribution
+# of "foo", since those articles are internal.
+# Note that control messages will be sent even though "!control,!control.*"
+# is specified. It is useful not to forget that pattern since control
+# messages for local.* would still be sent with "*,@local.*" only.
+#news.uu.net/uunet\
+# :*,!junk,!control,!control.*/!foo\
+# :Tm:innfeed!
+
+# Create a batch file in @SPOOLDIR@/outgoing for all articles
+# that haven't already passed through nic.near.net. The batch file will
+# be named nic.near.net, the default file name, and either nntpsend or
+# send-nntp can send articles from that spool file.
+#nic.near.net\
+# :*,!junk,!control,!control.*/!foo\
+# :Tf,Wnm:
+
+# A UUCP feed, where we try to keep the "batching" between 4 KB and 1 KB.
+# You can use send-uucp(8) to process these batch files.
+#ihnp4\
+# :*,!junk,!control,!control.*/!foo\
+# :Tf,Wnb,B4096/1024:
+
+
+## OUTGOING SPECIAL FEED EXAMPLES
+
+# Accumulate Path header statistics. See ninpaths(8) for more details on
+# how to set this up.
+#inpaths!:*:Tc,WP:@prefix@/bin/ninpaths -p -d @LOGDIR@/path/inpaths.%d
+
+# Feed all moderated source postings to an archiver.
+#source-archive!:!*,*sources*,!*wanted*,!*.d\
+# :Tc,Wn:@prefix@/bin/archive -f -i @SPOOLDIR@/archive/INDEX
+
+# News to mail gateway. Similar to innfeed, this uses a master feed and
+# then individual feeds for every separate address that news is being
+# gated to. This sends all posts to rec.pets.redants.* to the address
+# news-software@example.com.
+#news2mail!:!*:Tc,Ac,Wn*:@prefix@/bin/news2mail
+#news-software@example.com:rec.pets.redants.*:Tm:news2mail!
+
+# Capture all local postings (with a distribution of "foo" and no more
+# than two sites in the Path) using a local program (that doesn't come with
+# INN).
+#capture\
+# :*/foo\
+# :Tp,H2:/usr/local/bin/capture %s
--- /dev/null
+control Various control messages (no posting).
+control.cancel Cancel messages (no posting).
+control.checkgroups Hierarchy check control messages (no posting).
+control.newgroup Newsgroup creation control messages (no posting).
+control.rmgroup Newsgroup removal control messages (no posting).
+junk Unfiled articles (no posting).
--- /dev/null
+## $Id: nnrpd.py 7897 2008-06-22 18:04:31Z iulius $
+##
+## 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 filter scripts before loading.
+## See the INN Python Filtering and Authentication Hooks documentation
+## for more information.
+
+from types import *
+
+def set_auth_hook(anObject):
+ if type(anObject) == InstanceType:
+ print "** set_auth_hook for " + repr(anObject)
+ else:
+ print "** <Your object is not a class instance.>"
+
+def syslog(level, message):
+ print "-- syslog level: %s message: %s" % (level, message)
--- /dev/null
+## $Revision: 513 $
+## nnrpd.track - file to specify which hosts to be tracked by nnrpd
+## Format:
+## <host>:<identity>
+## Where:
+## <host> Wildcard name or IP address
+## <identity> String to be displayed in the logs
+##
+## By adding a host to this file, it will be tracked using the
+## nnrpd tracking system if enabled in inn.conf(5). Each read/post
+## will have an entry logged with the <identity> in the log message
+##
+# nasty.foo.com:nasty@foo.com
+# *.bar.com:VeryNastyClient
--- /dev/null
+#! /usr/bin/perl
+# fixscript will replace this line with require innshellvars.pl
+
+##
+## Sample code for the nnrpd Perl access hooks.
+
+## This file is loaded when a perl_access: parameter is reached in
+## readers.conf. If it defines a sub named access, which will be
+## called during processing of a perl_access: parameter. Attributes
+## about the connection are passed to the program in the %attributes
+## global variable. It should return a hash containing
+## parameter-value pairs for the access group. If there is a problem,
+## nnrpd will die and syslog the exact error.
+
+## The default behavior of the following code is to look for nnrp.access
+## in INN's configuration file directory and to attempt to implement about
+## the same host-based access control as the previous nnrp.access code in
+## earlier versions of INN. This may be useful for backward compatibility.
+
+## This file cannot be run as a standalone script, although it would be
+## worthwhile to add some code so that it could so that one could test the
+## results of various authentication and connection queries from the
+## command line. The #! line at the top is just so that fixscript will
+## work.
+
+# This function is called when perl_access: is reached in readers.conf.
+# For details on all the information passed to it, see
+# ~news/doc/hook-perl.
+sub access {
+ &loadnnrp($inn::newsetc . '/nnrp.access');
+ return &checkhost($attributes{hostname}, $attributes{ipaddress});
+}
+
+# Called at startup, this loads the nnrp.access file and converts it into a
+# convenient internal format for later queries.
+sub loadnnrp {
+ my $file = shift;
+ my ($block, $perm, $user, $pass);
+
+ open (ACCESS, $file) or die "Could not open $file: $!\n";
+ local $_;
+ while (<ACCESS>) {
+ my %tmp;
+
+ chomp;
+ s/\#.*//;
+ ($block, $perm, $user, $pass, $tmp{groups}) = split /:/;
+ next unless (defined $tmp{groups});
+
+ # We don't support username/password entries, so be safe.
+ next if ($user || $pass);
+
+ # Change the wildmat pattern to a regex (this isn't thorough, as
+ # some ranges won't be converted properly, but it should be good
+ # enough for this purpose).
+ if ($block !~ m%^(?:\d+\.){3}\d+/\d+$%) {
+ $block =~ s/\./\\./g;
+ $block =~ s/\?/./g;
+ $block =~ s/\*/.*/g;
+ }
+ $tmp{block} = $block;
+
+ $tmp{canread} = ($perm =~ /r/i);
+ $tmp{canpost} = ($perm =~ /p/i);
+
+ unshift(@hosts, { %tmp });
+ }
+ close ACCESS;
+}
+
+# Given the hostname and IP address of a connecting host, use our @hosts
+# array constructed from nnrp.access and see what permissions that host has.
+sub checkhost {
+ my ($host, $ip) = @_;
+ my %return_hash;
+ my $key;
+ for $key (@hosts) {
+ my ($read, $post) = ($key->{canread}, $key->{canpost});
+
+ # First check for CIDR-style blocks.
+ if ($key->{block} =~ m%^(\d+\.\d+\.\d+\.\d+)/(\d+)$%) {
+ my $block = unpack('N', pack('C4', split(/\./, $1)));
+ my $mask = (0xffffffff << (32 - $2)) & 0xffffffff;
+ $block = $block & $mask;
+ my $packedip = unpack('N', pack('C4', split(/\./, $ip)));
+ if (($packedip & $mask) == $block) {
+ if ($read) {
+ $return_hash{"read"} = $key->{groups};
+ }
+ if ($post) {
+ $return_hash{"post"} = $key->{groups};
+ }
+ return %return_hash;
+ }
+ }
+
+ if ($ip =~ /^$key->{block}$/) {
+ if ($read) {
+ $return_hash{"read"} = $key->{groups};
+ }
+ if ($post) {
+ $return_hash{"post"} = $key->{groups};
+ }
+ return %return_hash;
+ }
+
+ if ($host =~ /^$key->{block}$/) {
+ if ($read) {
+ $return_hash{"read"} = $key->{groups};
+ }
+ if ($post) {
+ $return_hash{"post"} = $key->{groups};
+ }
+ return %return_hash;
+ }
+ }
+
+ # If we fell through to here, nothing matched, so we should deny
+ # permissions.
+ return %return_hash;
+}
--- /dev/null
+## $Id: nnrpd_access.py 7906 2008-06-23 05:44:49Z iulius $
+##
+## This is a sample access module for the Python nnrpd hook.
+##
+## See the INN Python Filtering and Authentication Hooks documentation
+## for more information.
+## The perl_access: parameter in readers.conf is used to load this script.
+##
+## An instance of ACCESS class is passed to nnrpd via the set_auth_hook()
+## function imported from nnrpd. The following methods of that class
+## are known to nnrpd:
+##
+## __init__() - Use this method to initialize your
+## general variables or open a common
+## database connection. May be omitted.
+## access_init() - Init function specific to access
+## control. May be omitted.
+## access(attributes) - Called when a python_access
+## statement is reached in the
+## processing of readers.conf. Returns
+## a dictionary of values representing
+## statements to be included in an
+## access group.
+## access_close() - Called on nnrpd termination. Save
+## your state variables or close a
+## database connection. May be omitted.
+##
+## 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 access class. It defines all access methods known to nnrpd.
+class ACCESS:
+ """Provide access callbacks to nnrpd."""
+
+ def __init__(self):
+ """This is a good place to initialize variables or open a
+ database connection."""
+ syslog('notice', 'nnrpd access class instance created')
+
+ def access_init(self):
+ """Called when this script is initialized."""
+ pass
+
+ def access(self, attributes):
+ """Called when python_access: is encountered in readers.conf."""
+
+ # Just for debugging purposes.
+ syslog('notice', 'n_a access() invoked: hostname %s, ipaddress %s, interface %s, user %s' % (\
+ attributes['hostname'], \
+ attributes['ipaddress'], \
+ attributes['interface'], \
+ attributes['user']))
+
+ # Allow newsreading from specific host only.
+ if '127.0.0.1' == str(attributes['ipaddress']):
+ syslog('notice', 'authentication access by IP address succeeded')
+ return {'read':'*', 'post':'*'}
+ else:
+ syslog('notice', 'authentication access by IP address failed')
+ return {'read':'!*', 'post':'!*'}
+
+ def access_close(self):
+ """Called on nnrpd termination."""
+ pass
+
+
+## The rest is used to hook up the access 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.
+myaccess = ACCESS()
+
+## ...and try to hook up on nnrpd. This would make auth object methods visible
+## to nnrpd.
+try:
+ set_auth_hook(myaccess)
+ syslog('notice', "access module successfully hooked into nnrpd")
+except Exception, errmsg:
+ syslog('error', "Cannot obtain nnrpd hook for access method: %s" % errmsg[0])
--- /dev/null
+#! /usr/bin/perl
+# fixscript will replace this line with require innshellvars.pl
+
+# Example wrapper nnrpd_access.pl for support of old perl authentication
+# scripts, by Erik Klavon.
+
+# This file contains a sample perl script which can be used to
+# duplicate the behavior of the old nnrpperlauth functionality. This
+# script only supports access control.
+
+# How to use this wrapper:
+# - append your old script to this file with two changes:
+# - rename the old "auth_init" sub to "old_auth_init"
+# - rename the old "authenticate" sub to "old_authenticate"
+
+
+# access
+# This sub modifies the global hash attributes so that it has all the
+# entries required in the old way of doing things, calls
+# old_authenticate, and creates a return hash with the right attributes.
+
+sub access {
+ # Comment this out if you don't need auth_init.
+ old_auth_init();
+
+ $attributes{type} = "connect";
+ my @connect_array = old_authenticate();
+ my %hash;
+
+ # handle max rate
+ if ($connect_array[4]) {
+ # Force perl to make a C string out of this integer,
+ # or else bad things will happen. Sigh.
+ $hash{"max_rate"} = $connect_array[4] . "\0";
+ }
+
+ # handle read boolean, set to wildmat
+ if ($connect_array[1]) {
+ $hash{"read"} = $connect_array[3];
+ }
+
+ # handle post boolean, set to wildmat
+ if ($connect_array[2]) {
+ $hash{"post"} = $connect_array[3];
+ }
+
+ return %hash;
+}
--- /dev/null
+## $Id: nnrpd_access_wrapper.py 7899 2008-06-22 18:22:07Z iulius $
+##
+## Example wrapper for support of old Python authentication scripts,
+## by Erik Klavon.
+##
+## This file contains a sample Python script which can be used to
+## duplicate the behaviour of the old nnrppythonauth functionality.
+## This script only supports access control.
+##
+## How to use this wrapper:
+## - insert your authentication class into this file;
+## - rename your authentication class OLDAUTH.
+##
+## See the INN Python Filtering and Authentication Hooks documentation
+## for more information.
+## The use of this file is *discouraged*.
+
+## Old AUTH class.
+## Insert your old auth class here.
+## Do not include the code which sets the hook.
+
+
+
+
+## Wrapper ACCESS class. It creates an instance of the old class and
+## calls its methods. Arguments and return values are munged as
+## needed to fit the new way of doing things.
+
+class MYACCESS:
+ """Provide access callbacks to nnrpd."""
+ def access_init(self):
+ self.old = OLDAUTH()
+
+ def access(self, attributes):
+ attributes['type'] = buffer('connect')
+ perm = (self.old).authenticate(attributes)
+ result = dict([('users','*')])
+ if perm[1] == 1:
+ result['read'] = perm[3]
+ if perm[2] == 1:
+ result['post'] = perm[3]
+ return result
+
+ def access_close(self):
+ (self.old).close()
+
+
+## The rest is used to hook up the access 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.
+myaccess = MYACCESS()
+
+## ...and try to hook up on nnrpd. This would make access object methods visible
+## to nnrpd.
+try:
+ set_auth_hook(myaccess)
+ syslog('notice', "access module successfully hooked into nnrpd")
+except Exception, errmsg:
+ syslog('error', "Cannot obtain nnrpd hook for access method: %s" % errmsg[0])
--- /dev/null
+#! /usr/bin/perl
+# fixscript will replace this line with require innshellvars.pl
+
+##
+## Sample code for the nnrpd Perl authentication hooks.
+##
+## This file is loaded when a perl_auth: parameter is reached in
+## readers.conf. If it defines a sub named authenticate, that
+## function will be called during processing of a perl_auth:
+## parameter. Attributes about the connection are passed to the
+## program in the %attributes global variable. It should return an
+## array with two elements:
+##
+## 1) NNTP response code. Should be one of the codes from %authcodes
+## below to not risk violating the protocol.
+## 2) An error string to be passed to the client.
+## Both elements are required. If there is a problem, nnrpd will die
+## and syslog the exact error.
+
+## The code below uses a user database based on CDB_File. It is
+## provided here as an example of an authentication script.
+
+## This file cannot be run as a standalone script, although it would be
+## worthwhile to add some code so that it could so that one could test the
+## results of various authentication and connection queries from the
+## command line. The #! line at the top is just so that fixscript will
+## work.
+
+use strict;
+use vars qw(%attributes %authcodes %users);
+
+# These codes are a widely implemented de facto standard.
+%authcodes = ('allowed' => 281, 'denied' => 502);
+
+# This sub should perform any initialization work that the
+# authentication stuff needs.
+sub auth_init {
+ require CDB_File;
+ tie (%users, 'CDB_File', $inn::pathdb . '/users.cdb')
+ or warn "Could not open $inn::pathdb/users.cdb for users: $!\n";
+}
+
+# This function is called for authentication requests. For details on
+# all the information passed to it, see ~news/doc/hook-perl.
+sub authenticate {
+ return &checkuser();
+}
+
+# This function assumes that there's a database tied as %users that
+# contains, keyed by users, a tab-separated list of the password (in
+# crypt format), whether they can post, a wildmat matching what
+# newsgroups they have access to, and the number of bytes per second
+# they're allowed to use. This section of the code only accesses the
+# username and password fields. See the file nnrpd_access.pl for
+# access rights based on the other fields.
+sub checkuser {
+ my $user = $attributes{'username'};
+ my $pass = $attributes{'password'};
+
+ return ($authcodes{denied}, "No username given.")
+ unless defined $users{$user};
+
+ my ($password, $post, $speed, $subscription) = split(/\t/, $users{$user});
+ return ($authcodes{denied}, "Incorrect password.")
+ if (crypt($pass, $password) ne $password);
+
+ return ($authcodes{allowed}, "");
+}
--- /dev/null
+## $Id: nnrpd_auth.py 7906 2008-06-23 05:44:49Z iulius $
+##
+## This is a sample authentication module for the Python nnrpd hook.
+##
+## See the INN Python Filtering and Authentication Hooks documentation
+## for more information.
+## The perl_auth: parameter in readers.conf is used to load this script.
+##
+## An instance of AUTH class is passed to nnrpd via the set_auth_hook()
+## function imported from nnrpd. The following methods of that class
+## are known to nnrpd:
+##
+## __init__() - Use this method to initialize your
+## general variables or open a common
+## database connection. May be omitted.
+## authen_init() - Init function specific to
+## authentication. May be omitted.
+## authenticate(attributes) - Called when a python_auth statement
+## is reached in the processing of
+## readers.conf. Returns a response
+## code, an error string and an
+## optional string to appear in the
+## logs as the username.
+## authen_close() - Called on nnrpd termination. Save
+## your state variables or close a database
+## connection. May be omitted.
+##
+## 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 class. It defines all auth methods known to nnrpd.
+class AUTH:
+ """Provide authentication callbacks to nnrpd."""
+
+ def __init__(self):
+ """This is a good place to initialize variables or open a
+ database connection."""
+
+ # Create a list of NNTP codes to respond on connect.
+ self.connectcodes = { 'READPOST':200,
+ '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 authen_init(self):
+ """Called when this script is initialized."""
+ pass
+
+ def authenticate(self, attributes):
+ """Called when python_auth: is encountered in readers.conf."""
+
+ # Just for debugging purposes.
+ syslog('notice', 'n_a authenticate() invoked: hostname %s, ipaddress %s, interface %s, user %s' % (\
+ attributes['hostname'], \
+ attributes['ipaddress'], \
+ attributes['interface'], \
+ attributes['user']))
+
+ # Do username password authentication.
+ if 'foo' == str(attributes['user']) \
+ and 'foo' == str(attributes['pass']):
+ syslog('notice', 'authentication by username succeeded')
+ return (self.authcodes['ALLOWED'], 'No error', 'default_user')
+ else:
+ syslog('notice', 'authentication by username failed')
+ return (self.authcodes['DENIED'], 'Access Denied!')
+
+ def authen_close(self):
+ """Called on nnrpd termination."""
+ pass
+
+
+## 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])
--- /dev/null
+#! /usr/bin/perl
+# fixscript will replace this line with require innshellvars.pl
+
+# Example wrapper nnrpd_auth.pl for support of old perl authentication
+# scripts, by Erik Klavon.
+
+# This file contains a sample perl script which can be used to
+# duplicate the behavior of the old nnrpperlauth functionality. This
+# script only supports authentication.
+
+# How to use this wrapper:
+# - append your old script to this file with two changes:
+# - rename the old "auth_init" sub to "old_auth_init"
+# - rename the old "authenticate" sub to "old_authenticate"
+
+
+# auth_init
+# This sub simply calls old_auth_init
+# Comment this out if you don't need auth_init
+
+sub auth_init {
+ old_auth_init();
+}
+
+
+# authenticate
+# This sub modifies the global hash attributes so that it has all the
+# entries required in the old way of doing things, calls
+# old_authenticate, and transforms the return array into the new
+# format.
+
+sub authenticate {
+ $attributes{type} = "authenticate";
+ my @auth_array = old_authenticate();
+ my @return_array;
+
+ # copy return code
+ $return_array[0] = $auth_array[0];
+
+ # simple error report
+ if ($auth_array[0] != 281) {
+ $return_array[1] = "Perl authentication error!";
+ return @return_array;
+ } else {
+ $return_array[1] = "";
+ }
+
+ return @return_array;
+}
--- /dev/null
+## $Id: nnrpd_auth_wrapper.py 7899 2008-06-22 18:22:07Z iulius $
+##
+## Example wrapper for support of old Python authentication scripts,
+## by Erik Klavon.
+##
+## This file contains a sample Python script which can be used to
+## duplicate the behaviour of the old nnrppythonauth functionality.
+## This script only supports authentication.
+##
+## How to use this wrapper:
+## - insert your authentication class into this file;
+## - rename your authentication class OLDAUTH.
+##
+## See the INN Python Filtering and Authentication Hooks documentation
+## for more information.
+## The use of this file is *discouraged*.
+
+## Old AUTH class.
+## Insert your old auth class here.
+## Do not include the code which sets the hook.
+
+
+
+
+## Wrapper AUTH class. It creates an instance of the old class and
+## calls its methods. Arguments and return values are munged as
+## needed to fit the new way of doing things.
+
+class MYAUTH:
+ """Provide auth callbacks to nnrpd."""
+ def authen_init(self):
+ self.old = OLDAUTH()
+
+ def authenticate(self, attributes):
+ attributes['type'] = buffer('authinfo')
+ perm = (self.old).authenticate(attributes)
+ err_str = "No error"
+ if perm[0] == 502:
+ err_str = "Python authentication error!"
+ return (perm[0],err_str)
+
+ def authen_close(self):
+ (self.old).close()
+
+
+## The rest is used to hook up the auth module on nnrpd. It is unlikely
+## you will ever need to modify this.
+
+## Import functions exposed by nnrpd. This import must succeed, or nothing
+## will work!
+from nnrpd import *
+
+## Create a class instance.
+myauth = MYAUTH()
+
+## ...and try to hook up on nnrpd. This would make auth object methods visible
+## to nnrpd.
+try:
+ set_auth_hook(myauth)
+ syslog('notice', "authentication module successfully hooked into nnrpd")
+except Exception, errmsg:
+ syslog('error', "Cannot obtain nnrpd hook for authentication method: %s" % errmsg[0])
--- /dev/null
+## $Id: nnrpd_dynamic.py 7906 2008-06-23 05:44:49Z iulius $
+##
+## This is a sample dynamic access module for the Python nnrpd hook.
+##
+## See the INN Python Filtering and Authentication Hooks documentation
+## for more information.
+## The perl_dynamic: parameter in readers.conf is used to load this script.
+##
+## An instance of DYNACCESS class is passed to nnrpd via the set_auth_hook()
+## function imported from nnrpd. The following methods of that class
+## are known to nnrpd:
+##
+## __init__() - Use this method to initialize your
+## general variables or open a common
+## database connection. May be omitted.
+## dynamic_init() - Init function specific to
+## authentication. May be omitted.
+## dynamic(attributes) - Called whenever a reader requests either
+## read or post access to a
+## newsgroup. Returns None to grant
+## access, or a non-empty string (which
+## will be reported back to reader)
+## otherwise.
+## dynamic_close() - Called on nnrpd termination. Save
+## your state variables or close a database
+## connection. May be omitted.
+##
+## If there is a problem with return codes from any of these methods, then nnrpd
+## will die and syslog the exact reason.
+##
+## 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 dynamic access class. It defines all dynamic access methods known
+## to nnrpd.
+class DYNACCESS:
+ """Provide dynamic access callbacks to nnrpd."""
+
+ def __init__(self):
+ """This is a good place to initialize variables or open a
+ database connection."""
+ syslog('notice', 'nnrpd dynamic access class instance created')
+
+ def dynamic_init(self):
+ """Called when this script is initialized."""
+ pass
+
+ def dynamic(self, attributes):
+ """Called when python_dynamic: is reached in the processing of
+ readers.conf and a reader requests either read or post
+ permission for particular newsgroup."""
+
+ # Just for debugging purposes.
+ syslog('notice', 'n_a dynamic() invoked against type %s, hostname %s, ipaddress %s, interface %s, user %s' % (\
+ attributes['type'], \
+ attributes['hostname'], \
+ attributes['ipaddress'], \
+ attributes['interface'], \
+ attributes['user']))
+
+ # Allow reading of any newsgroup but not posting.
+ if 'post' == str(attributes['type']):
+ syslog('notice', 'dynamic authorization access for post access denied')
+ return "no posting for you"
+ elif 'read' == str(attributes['type']):
+ syslog('notice', 'dynamic authorization access for read access granted')
+ return None
+ else:
+ syslog('notice', 'dynamic authorization access type is not known: %s' % attributes['type'])
+ return "Internal error";
+
+ def dynamic_close(self):
+ """Called on nnrpd termination."""
+ pass
+
+
+## The rest is used to hook up the dynamic access 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.
+mydynaccess = DYNACCESS()
+
+## ...and try to hook up on nnrpd. This would make auth object methods visible
+## to nnrpd.
+try:
+ set_auth_hook(mydynaccess)
+ syslog('notice', "dynamic access module successfully hooked into nnrpd")
+except Exception, errmsg:
+ syslog('error', "Cannot obtain nnrpd hook for dynamic access method: %s" % errmsg[0])
--- /dev/null
+## $Id: nnrpd_dynamic_wrapper.py 7899 2008-06-22 18:22:07Z iulius $
+##
+## Example wrapper for support of old Python authentication scripts,
+## by Erik Klavon.
+##
+## This file contains a sample Python script which can be used to
+## duplicate the behaviour of the old nnrppythonauth functionality.
+## This script only supports dynamic access control by group.
+##
+## How to use this wrapper:
+## - insert your authentication class into this file;
+## - rename your authentication class OLDAUTH.
+##
+## See the INN Python Filtering and Authentication Hooks documentation
+## for more information.
+## The use of this file is *discouraged*.
+
+## Old AUTH class.
+## Insert your old auth class here.
+## Do not include the code which sets the hook.
+
+
+
+
+## Wrapper DYNACCESS class. It creates an instance of the old class and
+## calls its methods. Arguments and return values are munged as
+## needed to fit the new way of doing things.
+
+class MYDYNACCESS:
+ """Provide dynamic access callbacks to nnrpd."""
+ def dynamic_init(self):
+ self.old = OLDAUTH()
+
+ def dynamic(self, attributes):
+ return (self.old).authorize(attributes)
+
+ def dynamic_close(self):
+ (self.old).close()
+
+
+## The rest is used to hook up the dynamic access 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.
+mydynaccess = MYDYNACCESS()
+
+## ...and try to hook up on nnrpd. This would make auth object methods visible
+## to nnrpd.
+try:
+ set_auth_hook(mydynaccess)
+ syslog('notice', "dynamic access module successfully hooked into nnrpd")
+except Exception, errmsg:
+ syslog('error', "Cannot obtain nnrpd hook for dynamic access method: %s" % errmsg[0])
--- /dev/null
+## $Revision: 1165 $
+## Control file for nntpsend.
+## Format:
+## site:fqdn:max_size:[<args...>]
+## <site> The name used in the newsfeeds file for this site;
+## this determines the name of the batchfile, etc.
+## <fqdn> The fully-qualified domain name of the site,
+## passed as the parameter to innxmit.
+## <size> Size to truncate batchfile if it gets too big;
+## see shrinkfile(1).
+## <args> Other args to pass to innxmit
+## Everything after the pound sign is ignored.
+#nsavax:erehwon.nsavax.gov::-S -t60
+#walldrug:walldrug.com:4m-1m:-T1800 -t300
+#kremvax:kremvax.cis:2m:
--- /dev/null
+# The directory that overview will be stored in (the DB_HOME directory)
+# is set in inn.conf with the 'pathoverview' option. Other parameters
+# for tuning ovdb are in this file.
+
+# Size of the memory pool cache, in Kilobytes. The cache will have a
+# backing store file in the DB directory which will be at least as big.
+# In general, the bigger the cache, the better. Use C<ovdb_stat -m> to see
+# cache hit percentages. If they're less than 80%, try increasing the
+# cache size. To make a change of this parameter take effect, shut down
+# and restart INN (be sure to kill all of the nnrpds when shutting down).
+# Default is 8000, which is adequate for small to medium sized servers.
+# Large servers will probably need at least 14000.
+#cachesize 8000
+
+# Overview data is split between this many files. Currently,
+# innd will keep all of the files open, so don't set this too high
+# or innd may run out of file descriptors. The nnrpds only open one
+# at a time, regardless. May be set to one, or just a few, but only
+# do that if your OS supports large (>2G) files. Changing this
+# parameter has no effect on an already-established database.
+#numdbfiles 32
+
+# If txn_nosync is set to false, BerkeleyDB flushes the log after every
+# transaction. This minimizes the number of transactions that may be
+# lost in the event of a crash, but results in significantly degraded
+# performance. Default is true.
+#txn_nosync true
+
+# If useshm is set to true, BerkeleyDB will use shared memory instead
+# of mmap for its environment regions (cache, lock, etc). With some
+# platforms, this may improve performance. Default is false.
+# This parameter is ignored if you have BerkeleyDB 2.x
+#useshm false
+
+# Sets the shared memory key used by BerkeleyDB when 'useshm' is true.
+# BerkeleyDB will create several (usually 5) shared memory segments,
+# using sequentially numbered keys starting with 'shmkey'.
+# Choose a key that does not conflict with any existing shared memory
+# segments on your system. Default is 6400. This parameter is only
+# used with BerkeleyDB 3.1 or newer.
+#shmkey 6400
+
+# Sets the page size for the DB files (in bytes). Must be a power of 2.
+# Best choices are 4096 or 8192. The default is 8192.
+# Changing this parameter has no effect on an already-established database.
+#pagesize 8192
+
+# Sets the minimum number of keys per page. See the BerkeleyDB
+# documentation for more info. Default is based on page size:
+#
+# default_minkey = MAX(2, pagesize / 2048 - 1)
+#
+# The lowest allowed minkey is 2. Setting minkey higher than the
+# default is not recommended, as it will cause the databases to have
+# a lot of overflow pages.
+# Changing this parameter has no effect on an already-established database.
+#minkey 3
+
+# Sets the BerkeleyDB "lk_max" parameter, which is the maxmium number
+# of locks that can exist in the database at the same time. Default
+# is 4000.
+#maxlocks 4000
+
+# The nocompact parameter affects expireover's behavior. The expireover
+# function in ovdb can do its job in one of two ways: By simply deleting
+# expired records from the database; or by re-writing the overview records
+# into a different location leaving out the expired records. The first
+# method is faster, but it leaves 'holes' that result in space that can
+# not immediately be reused. The second method 'compacts' the records
+# by rewriting them.
+#
+# If this parameter is set to 0, expireover will compact all newsgroups;
+# if set to 1, expireover will not compact any newsgroups; and if set to
+# a value greater than one, expireover will only compact groups that
+# have less than that number of articles. Default is 1000.
+#
+# Experience has shown that compacting has minimal effect (other than
+# making expireover take longer) so the default is now 1. This parameter
+# will probably be removed in the future.
+#nocompact 1
+
+# Normally, each nnrpd process directly accesses the BerkeleyDB environment.
+# The process of attaching to the database (and detaching when finished) is
+# fairly expensive, and can result in high loads in situations when there are
+# lots of reader connections of relatively short duration.
+#
+# When the readserver parameter is "true", the nnrpds will access overview
+# via a helper server (ovdb_server -- which is started by ovdb_init).
+# Default is false.
+#readserver false
+
+# This parameter is only used when 'readserver' is true. It sets the number
+# of ovdb_server processes. As each ovdb_server can process only one
+# transaction at a time, running more servers can improve reader response
+# times. Default is 5.
+#numrsprocs 5
+
+# This parameter is only used when 'readserver' is true. It sets a maximum
+# number of readers that a given ovdb_server process will serve at one time.
+# This means the maximum number of readers for all of the ovdb_server
+# processes is (numrsprocs * maxrsconn). Default is 0, which means an
+# umlimited number of connections is allowed.
+#maxrsconn 0
+
--- /dev/null
+## $Revision: 573 $
+## overview.fmt - format of news overview database
+## Format
+## <header>
+## <header>:full
+## header is a news article header, known by innd. If ":full" appears,
+## then header name will be prepended. Order of lines is important!
+Subject:
+From:
+Date:
+Message-ID:
+References:
+Bytes:
+Lines:
+Xref:full
+#Keywords:full
--- /dev/null
+## $Revision: 1165 $
+## passwd.nntp - passwords for connecting to remote NNTP servers
+## Format:
+## <host>:<name>:<pass>[:<style>]
+## Clients need only one entry, for where innd is running. The
+## server will have more entries for connecting to peers to feed them
+## articles.
+## <host> Host this line is for.
+## <name> Name to use to authenticate with
+## <pass> Password to send, after sending name
+## <style> Optional authentication style, defaults to "authinfo"
+## <name> and <pass> can be empty string; a peer innd doesn't need a
+## <name>, for example.
+#news.foo.com:rsalz:martha
--- /dev/null
+# $Id: radius.conf 7556 2006-08-28 02:00:28Z eagle $
+#
+# Sample RADIUS configuration file for the RADIUS readers.conf
+# authenticator. If you're not using that authenticator, this file is not
+# used.
+
+server radius {
+
+# Hostname of the RADIUS server.
+
+#radhost: radius-server.example.com
+
+# Port to query on the RADIUS server.
+
+radport: 1645
+
+# Local hostname or IP address.
+#
+# The RADIUS server expects an IP address; a hostname will be translated
+# into an IP address with gethostbyname(). If not given, not included in
+# the request (not all RADIUS setups need this information).
+
+#lochost: news.example.com
+
+# Local port of connection.
+#
+# The port the client we're authenticating is connecting to. If not
+# given, defaults to 119. You'll only need to set this if you're readers
+# are connecting on a non-standard port.
+
+#locport: 119
+
+# Shared secret with RADIUS server.
+#
+# Be careful not to use the '#' symbol in your secret, since in this
+# file that indicates the beginning of a comment.
+
+#secret: SECRET-WORD
+
+# Prefix for username.
+#
+# Before given to the RADIUS server, usernames will be rewritten by
+# prepending the prefix, if given, and then appending the suffix, if
+# given.
+
+#prefix: news-
+
+# Suffix for username.
+
+#suffix: @example.com
+
+# Whether to ignore bad reply IP.
+#
+# If set to false, the RADIUS authenticator will check to ensure that the
+# response it receives is from the same IP address as it sent the request
+# to (for some added security). If set to true, it will skip this
+# verification check (if your RADIUS server has multiple IP addresses or
+# if other odd things are going on, it may be perfectly normal for the
+# response to come from a different IP address).
+
+ignore-source: false
+
+}
--- /dev/null
+## $Id: readers.conf 4371 2001-01-16 15:35:38Z rra $
+##
+## readers.conf - Access control and configuration for nnrpd
+##
+## Format:
+## auth "<name>" {
+## hosts: "<hostlist>"
+## auth: "<authprog>"
+## res: "<resprog>"
+## default: "<identity>"
+## default-domain: "<email-domain>"
+## }
+## access "<name>" {
+## users: "<userlist>"
+## newsgroups: "<newsgroups>"
+## read: "<read>"
+## post: "<post>"
+## access: "<perm>"
+## }
+##
+## Other parameters are possible. See readers.conf(5) for all the
+## details. Only one of newsgroups or read/post may be used in a single
+## access group.
+##
+## If the connecting host is not matched by any hosts: parameter of any
+## auth group, it will be denied access. auth groups assign an identity
+## string to connections, access groups grant privileges to identity
+## strings matched by their users: parameters.
+##
+## In all cases, the last match found is used, so put defaults first.
+##
+## For a news server that allows connections from anyone within a
+## particular domain or IP address range, just uncomment the "local" auth
+## group and the "local" access group below and adjust the hosts: and
+## default: parameters of the auth group and the users: parameter of the
+## access group for your local network and domain name. That's all there
+## is to it.
+##
+## For more complicated configurations, read the comments on the examples
+## and also see the examples and explanations in readers.conf(5). The
+## examples in readers.conf(5) include setups that require the user to
+## log in with a username and password (the example in this file only
+## uses simple host-based authentication).
+##
+## NOTE: Unlike in previous versions of INN, nnrpd will now refuse any
+## post from anyone to a moderated newsgroup that contains an Approved:
+## header unless their access block has an access: key containing the
+## "A" flag. This is to prevent abuse of moderated groups, but it means
+## that if you support any newsgroup moderators, you need to make sure
+## to add such a line to the access group that affects them. See the
+## access group for localhost below for an example.
+
+# The only groups enabled by default (the rest of this file is
+# commented-out examples). This assigns the identity of <localhost> to
+# the local machine
+
+auth "localhost" {
+ hosts: "localhost, 127.0.0.1, stdin"
+ default: "<localhost>"
+}
+
+# Grant that specific identity access to read and post to any newsgroup
+# and allow it to post articles with Approved: headers to moderated
+# groups.
+
+access "localhost" {
+ users: "<localhost>"
+ newsgroups: "*"
+ access: RPA
+}
+
+
+# This auth group matches all connections from example.com or machines in
+# the example.com domain and gives them the identity <local>@example.com.
+# Instead of using wildmat patterns to match machine names, you could also
+# put a wildmat pattern matching IP addresses or an IP range specified
+# using CIDR notation (like 10.10.10.0/24) here.
+
+#auth "local" {
+# hosts: "*.example.com, example.com"
+# default: "<local>@example.com"
+#}
+
+# This auth group matches a subset of machines and assigns connections
+# from there an identity of "<read>@example.com"; these systems should
+# only have read access, no posting privileges.
+
+#auth "read-only" {
+# hosts: "*.newuser.example.com"
+# default: "<read>@example.com"
+#}
+
+# This auth group matches the systems at a guest institution that should
+# be allowed to read the example.events.* hierarchy but nothing else.
+
+#auth "events-only" {
+# hosts: "*.example.org"
+# default: "<events-only>@example.org"
+#}
+
+# Finally, this auth group matches some particular systems which have been
+# abusing the server. Note that it doesn't assign them an identity at
+# all; the "empty" identity created in this fashion won't match any users:
+# parameters. Note also that it's last, so anything matching this entry
+# will take precedent over everything above it.
+
+#auth "abusers" {
+# hosts: "badguy-dsl.example.com, kiosk.public-access.example.com"
+#}
+
+
+# Now for the access groups. All of our access groups should have users:
+# parameters so there are no access groups that match connections without
+# an identity (such as are generated by the "abusers" entry above).
+# First, the default case of local users, who get to read and post to
+# everything.
+
+#access "local" {
+# users: "<local>@example.com"
+# newsgroups: "*"
+#}
+
+# Now, the read-only folks, who only get to read everything.
+
+#access "read-only" {
+# users: "<read>@example.com"
+# read: "*"
+#}
+
+# Finally, the events-only people who get to read and post but only to a
+# specific hierarchy.
+
+#access "events-only" {
+# users: "<events-only>@example.org"
+# newsgroups: "example.events.*"
+#}
--- /dev/null
+tls_ca_path: @prefix@/lib
+tls_cert_file: @prefix@/lib/cert.pem
+tls_key_file: @prefix@/lib/cert.pem
--- /dev/null
+## $Id: startup.tcl 4353 2001-01-15 13:32:40Z rra $
+##
+## Tcl filter initialization code
+##
+## If you compile with Tcl support enabled, this file (even if empty) must
+## exist as pathfilter/_PATH_TCL_STARTUP (as defined in paths.h). This
+## sample file defines the two functions that are called before and after
+## reloading the filter code, but defines them as empty procs that do
+## nothing.
+
+proc filter_before_reload {} {
+}
+
+proc filter_after_reload {} {
+}
--- /dev/null
+#
+# RCSId: $Id: startup_innd.pl 6312 2003-05-04 21:40:11Z rra $
+# Description: Sample startup code for Perl hooks in INN. This file, after
+# it's installed in the right spot, will be loaded when
+# innd starts up. The following functions should be defined
+# by it (they don't have to be, in fact this file can be
+# empty, but it must exist if you've compiled in Perl support).
+#
+# sub filter_before_reload { ... }
+# Called before the filter definition file filter_innd.pl
+# is loaded (every time).
+# sub filter_after_reload { ... }
+# Called after the filter definition file filter_innd.pl
+# is loaded (every time).
+#
+# See the sample file filter_innd.pl for details on what it does.
+
+
+my $before_count = 1 ;
+# Gets no arguments, and its caller expects no return value.
+sub filter_before_reload {
+ if ($before_count == 1) {
+# Do one thing
+# print "First time (before)\n" ;
+ $before_count++ ;
+ } else {
+# Do something else
+# print "Time number $before_count (before)\n" ;
+ $before_count++ ;
+ }
+}
+
+my $after_count = 1 ;
+# Gets no arguments, and its caller expects no return value.
+sub filter_after_reload {
+ if ($after_count == 1) {
+# Do one thing
+# print "First time (after)\n" ;
+ $after_count++ ;
+ } else {
+# Do another
+# print "Time number $after_count (after)\n" ;
+ $after_count++ ;
+ }
+}
+
--- /dev/null
+## $Id: storage.conf 6567 2003-12-27 04:18:33Z rra $
+##
+## Rules for where INN should store incoming articles.
+##
+## This file is used to determine which storage method articles are sent
+## to to be stored and which storage class they are stored as. Each
+## method is described as follows:
+##
+## method <methodname> {
+## newsgroups: <wildmat>
+## class: <storage class #>
+## size: <minsize>[,<maxsize>]
+## expires: <mintime>[,<maxtime>]
+## options: <options>
+## }
+##
+## Only newsgroups, class, and (for CNFS, to specify the metacycbuff)
+## options are required; the other keys are optional. If any CNFS
+## methods are configured, you will also need to set up cycbuff.conf.
+
+# By default, store everything in tradspool.
+method tradspool {
+ newsgroups: *
+ class: 0
+}
+
+## Here are some samples for a CNFS configuration. This assumes that you
+## have two metacycbuffs configured, one for text newsgroups and one for
+## binaries. Cancel messages, which tend to be very high-volume, are
+## stored in the binary metacycbuff as well. This assumes storeonxref is
+## set to true in inn.conf.
+
+# Pick off the binary newsgroups first.
+#method cnfs {
+# newsgroups: *.bina*,control.cancel
+# class: 1
+# options: BINARY
+#}
+
+# Put the remaining (text) groups in the other cycbuff.
+#method cnfs {
+# newsgroups: *
+# class: 2
+# options: TEXT
+#}
--- /dev/null
+news.announce.newusers
+news.newusers.questions
+misc.test
+misc.test.moderated
+news.announce.newgroups
+news.answers
--- /dev/null
+## $Id: Makefile 7739 2008-04-06 09:38:31Z iulius $
+##
+## Files that can be handled by fixscript (and need to be so handled) need
+## a rule to build them from the .in version, and then all files need an
+## installation rule. Do the installation rules individually so as to
+## avoid needless work if the files haven't changed. We also need lists
+## of files to build and files to install for the all and install rules.
+
+include ../Makefile.global
+
+top = ..
+
+ALL = innmail innreport innstat innupgrade innwatch rc.news \
+ scanlogs simpleftp tally.control writelog
+
+EXTRA = inncheck innshellvars innshellvars.pl innshellvars.tcl \
+ news.daily
+
+INSTALLED = $(D)$(PATHBIN)/inncheck \
+ $(D)$(PATHBIN)/innmail \
+ $(D)$(PATHBIN)/innreport \
+ $(D)$(PATHBIN)/innstat \
+ $(D)$(PATHBIN)/innupgrade \
+ $(D)$(PATHBIN)/innwatch \
+ $(D)$(PATHBIN)/news.daily \
+ $(D)$(PATHBIN)/rc.news \
+ $(D)$(PATHBIN)/scanlogs \
+ $(D)$(PATHBIN)/simpleftp \
+ $(D)$(PATHBIN)/tally.control \
+ $(D)$(PATHBIN)/writelog \
+ $(D)$(PATHLIB)/innreport_inn.pm \
+ $(D)$(PATHLIB)/innshellvars \
+ $(D)$(PATHLIB)/innshellvars.pl \
+ $(D)$(PATHLIB)/innshellvars.tcl
+
+all: $(ALL) $(EXTRA)
+
+install: all
+ for F in innmail innreport simpleftp ; do \
+ $(CP_XPUB) $$F $D$(PATHBIN)/$$F ; \
+ done
+ for F in inncheck innstat innupgrade innwatch news.daily rc.news \
+ scanlogs tally.control writelog ; do \
+ $(CP_XPRI) $$F $D$(PATHBIN)/$$F ; \
+ done
+ for F in innreport_inn.pm innshellvars innshellvars.pl \
+ innshellvars.tcl ; do \
+ $(CP_RPUB) $$F $D$(PATHLIB)/$$F ; \
+ done
+
+clean:
+ rm -f $(ALL)
+
+clobber distclean: clean
+ rm -f $(EXTRA)
+
+depend:
+
+profiled: all
+
+$(EXTRA) $(FIXSCRIPT):
+ @echo Run configure before running make. See INSTALL for details.
+ @exit 1
+
+
+## Build rules.
+
+FIX = $(FIXSCRIPT)
+
+innmail: innmail.in $(FIX) ; $(FIX) innmail.in
+innreport: innreport.in $(FIX) ; $(FIX) innreport.in
+innstat: innstat.in $(FIX) ; $(FIX) innstat.in
+innupgrade: innupgrade.in $(FIX) ; $(FIX) -i innupgrade.in
+innwatch: innwatch.in $(FIX) ; $(FIX) innwatch.in
+rc.news: rc.news.in $(FIX) ; $(FIX) rc.news.in
+scanlogs: scanlogs.in $(FIX) ; $(FIX) scanlogs.in
+simpleftp: simpleftp.in $(FIX) ; $(FIX) -i simpleftp.in
+tally.control: tally.control.in $(FIX) ; $(FIX) tally.control.in
+writelog: writelog.in $(FIX) ; $(FIX) writelog.in
--- /dev/null
+#!@_PATH_PERL@ --
+## $Revision: 7748 $
+## Sanity-check the configuration of an INN system
+## by Brendan Kehoe <brendan@cygnus.com> and Rich $alz.
+
+require "@LIBDIR@/innshellvars.pl" ;
+
+$ST_MODE = 2;
+$ST_UID = 4;
+$ST_GID = 5;
+
+$newsuser = '@NEWSUSER@';
+$newsgroup = '@NEWSGRP@';
+
+## We use simple names, mapping them to the real filenames only when
+## we actually need a filename.
+%paths = (
+ 'active', "$inn::pathdb/active",
+ 'archive', "$inn::patharchive",
+ 'badnews', "$inn::pathincoming/bad",
+ 'batchdir', "$inn::pathoutgoing",
+ 'control.ctl', "$inn::pathetc/control.ctl",
+ 'ctlprogs', "$inn::pathcontrol",
+ 'expire.ctl', "$inn::pathetc/expire.ctl",
+ 'history', "$inn::pathdb/history",
+ 'incoming.conf', "$inn::pathetc/incoming.conf",
+ 'inews', "$inn::pathbin/inews",
+ 'inn.conf', "$inn::pathetc/inn.conf",
+ 'innd', "$inn::pathbin/innd",
+ 'innddir', "$inn::pathrun",
+ 'inndstart', "$inn::pathbin/inndstart",
+ 'moderators', "$inn::pathetc/moderators",
+ 'most_logs', "$inn::pathlog",
+ 'newsbin', "$inn::pathbin",
+ 'newsboot', "$inn::pathbin/rc.news",
+ 'newsfeeds', "$inn::pathetc/newsfeeds",
+ 'overview.fmt', "$inn::pathetc/overview.fmt",
+ 'newsetc', "$inn::pathetc",
+ 'newslib', "@LIBDIR@",
+ 'nnrpd', "$inn::pathbin/nnrpd",
+ 'nntpsend.ctl', "$inn::pathetc/nntpsend.ctl",
+ 'oldlogs', "$inn::pathlog/OLD",
+ 'passwd.nntp', "$inn::pathetc/passwd.nntp",
+ 'readers.conf', "$inn::pathetc/readers.conf",
+ 'rnews', "$inn::pathbin/rnews",
+ 'rnewsprogs', "$inn::pathbin/rnews.libexec",
+ 'spooltemp', "$inn::pathtmp",
+ 'spool', "$inn::patharticles",
+ 'spoolnews', "$inn::pathincoming"
+);
+
+## The sub's that check the config files.
+%checklist = (
+ 'active', 'active',
+ 'control.ctl', 'control_ctl',
+ 'expire.ctl', 'expire_ctl',
+ 'incoming.conf', 'incoming_conf',
+ 'inn.conf', 'inn_conf',
+ 'moderators', 'moderators',
+ 'newsfeeds', 'newsfeeds',
+ 'overview.fmt', 'overview_fmt',
+ 'nntpsend.ctl', 'nntpsend_ctl',
+ 'passwd.nntp', 'passwd_nntp',
+ 'readers.conf', 'readers_conf'
+);
+
+## The modes of the config files we can check.
+%modes = (
+ 'active', @FILEMODE@,
+ 'control.ctl', 0644,
+ 'expire.ctl', 0644,
+ 'incoming.conf', 0640,
+ 'inn.conf', 0644,
+ 'moderators', 0644,
+ 'newsfeeds', 0644,
+ 'overview.fmt', 0644,
+ 'nntpsend.ctl', 0644,
+ 'passwd.nntp', 0640,
+ 'readers.conf', 0644
+);
+
+
+sub
+spacious
+{
+ local ($i);
+
+ chop;
+ study;
+ if ( /^#/ || /^$/ ) {
+ $i = 1;
+ } elsif ( /^\s/ ) {
+ print "$file:$line: starts with whitespace\n";
+ $i = 1;
+ } elsif ( /\s$/ ) {
+ print "$file:$line: ends with whitespace\n";
+ $i = 1;
+ }
+ $i;
+}
+\f
+##
+## These are the functions that verify each individual file, called
+## from the main code. Each function gets <IN> as the open file, $line
+## as the linecount, and $file as the name of the file.
+##
+
+
+##
+## active
+##
+sub
+active
+{
+ local ($group, $hi, $lo, $f, $alias, %groups, %aliases);
+
+ input: while ( <IN> ) {
+ $line++;
+ unless ( ($group, $hi, $lo, $f) = /^([^ ]+) (\d+) (\d+) (.+)\n$/ ) {
+ print "$file:$line: malformed line.\n";
+ next input;
+ }
+
+ print "$file:$line: group `$group' already appeared\n"
+ if $groups{$group}++;
+ print "$file:$line: `$hi' < '$lo'.\n"
+ if $hi < $lo && $lo != $hi + 1;
+
+ next input if $f =~ /^[jmynx]$/;
+ unless ( ($alias) = $f =~ /^=(.*)$/ ) {
+ print "$file:$line: bad flag `$f'.\n";
+ next input;
+ }
+ if ($alias eq "") {
+ print "$file:$line: empty alias.\n";
+ next input;
+ }
+ $aliases{$alias} = $line
+ unless defined $groups{$alias};
+ }
+ foreach $key ( keys %aliases ) {
+ print "$file:$aliases{$group} aliased to unknown group `$key'.\n"
+ unless defined $groups{$key};
+ }
+ 1;
+}
+
+
+##
+## control.ctl
+##
+%control'messages = (
+ 'all', 1,
+ 'checkgroups', 1,
+ 'ihave', 1,
+ 'newgroup', 1,
+ 'rmgroup', 1,
+ 'sendme', 1,
+ 'sendsys', 1,
+ 'senduuname', 1,
+ 'version', 1,
+);
+%control'actions = (
+ 'drop', 1,
+ 'log', 1,
+ 'mail', 1,
+ 'doit', 1,
+ 'doifarg', 1,
+ 'verify', 1
+);
+
+sub
+control_ctl
+{
+ local ($msg, $from, $ng, $act);
+
+ input: while ( <IN> ) {
+ next input if &spacious($file, ++$line);
+
+ unless ( ($msg, $from, $ng, $act) =
+ /^([^:]+):([^:]+):([^:]+):(.+)$/ ) {
+ print "$file:$line: malformed line.\n";
+ next input;
+ }
+ if ( !defined $control'messages{$msg} ) {
+ print "$file:$line: unknown control message `$msg'.\n";
+ next input;
+ }
+ print "$file:$line: action for unknown control messages is `doit'.\n"
+ if $msg eq "default" && $act eq "doit";
+ print "$file:$line: empty from field.\n"
+ if $from eq "";
+ print "$file:$line: bad email address.\n"
+ if $from ne "*" && $from !~ /[@!]/;
+
+ ## Perhaps check for conflicting rules, or warn about the last-match
+ ## rule? Maybe later...
+ print "$file:$line: may not match groups properly.\n"
+ if $ng ne "*" && $ng !~ /\./;
+ if ( $act !~ /([^=]+)(=.+)?/ ) {
+ print "$file:$line: malformed line.\n";
+ next input;
+ }
+ $act =~ s/=.*//;
+ $act = "verify" if ($act =~ /^verify-.+/) ;
+ print "$file:$line: unknown action `$act'\n"
+ if !defined $control'actions{$act};
+ }
+ 1;
+}
+
+
+##
+## expire.ctl
+##
+sub
+expire_ctl
+{
+ local ($rem, $v, $def, $class, $pat, $flag, $keep, $default, $purge, $groupbaseexpiry);
+
+ $groupbaseexpiry = $inn::groupbaseexpiry;
+ $groupbaseexpiry =~ tr/A-Z/a-z/;
+ input: while ( <IN> ) {
+ next input if &spacious($file, ++$line);
+
+ if ( ($v) = m@/remember/:(.+)@ ) {
+ print "$file:$line: more than one /remember/ line.\n"
+ if $rem++;
+ if ( $v !~ /[\d\.]+/ ) {
+ print "$file:$line: illegal value `$v' for remember.\n";
+ next input;
+ }
+ print "$file:$line: are you sure about your /remember/ value?\n"
+ ## These are arbitrary "sane" values.
+ if $v != 0 && ($v > 60.0 || $v < 5.0);
+ next input;
+ }
+
+ ## Could check for conflicting lines, but that's hard.
+ if ($groupbaseexpiry =~ /^true$/ || $groupbaseexpiry =~ /^yes$/ ||
+ $groupbaseexpiry =~ /^on$/) {
+ unless ( ($pat, $flag, $keep, $default, $purge) =
+ /^([^:])+:([^:]+):([\d\.]+|never):([\d\.]+|never):([\d\.]+|never)$/ ) {
+ print "$file:$line: malformed line.\n";
+ next input;
+ }
+ print "$file:$line: duplicate default line\n"
+ if $pat eq "*" && $flag eq "a" && $def++;
+ print "$file:$line: unknown modflag `$flag'\n"
+ if $flag !~ /[mMuUaAxX]/;
+ } else {
+ unless ( ($class, $keep, $default, $purge) =
+ /^(\d+):([\d\.]+|never):([\d\.]+|never):([\d\.]+|never)$/ ) {
+ print "$file:$line: malformed line.\n";
+ next input;
+ }
+ print "$file:$line: invalid class\n"
+ if $class < 0;
+ }
+ print "$file:$line: purge `$purge' younger than default `$default'.\n"
+ if $purge ne "never" && $default > $purge;
+ print "$file:$line: default `$default' younger than keep `$keep'.\n"
+ if $default ne "never" && $keep ne "never" && $keep > $default;
+ }
+ 1;
+}
+
+
+##
+## incoming.conf
+##
+sub
+incoming_conf
+{
+ 1;
+}
+
+
+##
+## inn.conf
+##
+sub
+inn_conf
+{
+ system ("$inn::pathbin/innconfval", '-C');
+
+# if ( $k eq "domain" ) {
+# print "$file:$line: domain (`$v') isn't local domain\n"
+# if $fqdn =~ /[^\.]+\(\..*\)/ && $v ne $1;
+# print "$file:$line: domain should not have a leading period\n"
+# if $v =~ /^\./;
+# } elsif ( $k eq "fromhost" ) {
+# print "$file:$line: fromhost isn't a valid FQDN\n"
+# if $v !~ /[\w\-]+\.[\w\-]+/;
+# } elsif ( $k eq "moderatormailer" ) {
+# # FIXME: shouldn't warn about blank lines if the
+# # moderators file exists
+# print "$file:$line: moderatormailer has bad address\n"
+# if $v !~ /[\w\-]+\.[\w\-]+/ && $v ne "%s";
+# } elsif ( $k eq "organization" ) {
+# print "$file:$line: org is blank\n"
+# if $v eq "";
+# } elsif ( $k eq "pathhost" ) {
+# print "$file:$line: pathhost has a ! in it\n"
+# if $v =~ /!/;
+# } elsif ( $k eq "pathalias" ) {
+# print "$file:$line: pathalias has a ! in it\n"
+# if $v =~ /!/;
+# } elsif ( $k eq "pathcluster" ) {
+# print "$file:$line: pathcluster has a ! in it\n"
+# if $v =~ /!/;
+# } elsif ( $k eq "server" ) {
+# print "$file:$line: server (`$v') isn't local hostname\n"
+# if $pedantic && $fqdn !~ /^$v/;
+# }
+#
+# if ( $key eq "moderatormailer" ) {
+# printf "$file:$line: missing $key and no moderators file.\n"
+# if ! -f $paths{"moderators"};
+# }
+
+ 1;
+}
+
+
+##
+## moderators
+##
+sub
+moderators
+{
+ local ($k, $v);
+
+ input: while ( <IN> ) {
+ next input if &spacious($file, ++$line);
+
+ unless ( ($k, $v) = /^([^:]+):(.+)$/ ) {
+ print "$file:$line: malformed line.\n";
+ next input;
+ }
+
+ if ( $k eq "" || $v eq "" ) {
+ print "$file:$line: missing field\n";
+ next input;
+ }
+ print "$file:$line: not an email address\n"
+ if $pedantic && $v !~ /[@!]/;
+ print "$file:$line: `$v' goes to local address\n"
+ if $pedantic && $v eq "%s";
+ print "$file:$line: more than one %s in address field\n"
+ if $v =~ /%s.*%s/;
+ }
+ 1;
+}
+
+
+##
+## newsfeeds
+##
+%newsfeeds'flags = (
+ '<', '^\d+$',
+ '>', '^\d+$',
+ 'A', '^[cCdeoOp]+$',
+ 'B', '^\d+(/\d+)?$',
+ 'C', '^\d+$',
+ 'F', '^.+$',
+ 'G', '^\d+$',
+ 'H', '^\d+$',
+ 'I', '^\d+$',
+ 'N', '^[mu]$',
+ 'O', '^\S+$',
+ 'P', '^\d+$',
+ 'Q', '^@?\d+(-\d+)?/\d+(_\d+)?$',
+ 'S', '^\d+$',
+ 'T', '^[cflmpx]$',
+ 'W', '^[befghmnpst*DGHNPOR]*$',
+);
+
+sub
+newsfeeds
+{
+ local ($next, $start, $me_empty, @muxes, %sites);
+ local ($site, $pats, $dists, $flags, $param, $type, $k, $v, $defsub);
+ local ($bang, $nobang, $prog, $dir);
+
+ input: while ( <IN> ) {
+ $line++;
+ next input if /^$/;
+ chop;
+ print "$file:$line: starts with whitespace\n"
+ if /^\s+/;
+
+ ## Read continuation lines.
+ $start = $line;
+ while ( /\\$/ ) {
+ chop;
+ chop($next = <IN>);
+ $line++;
+ $next =~ s/^\s*//;
+ $_ .= $next;
+ }
+ next input if /^#/;
+ print "$file:$line: ends with whitespace\n"
+ if /\s+$/;
+
+ # Catch a variable setting.
+ if ( /^\$([A-Za-z0-9]+)=/ ) {
+ print "$file:$line: variable name too long\n"
+ if length ($1) > 31;
+ next input;
+ }
+
+ unless ( ($site, $pats, $flags, $param) =
+ /^([^:]+):([^:]*):([^:]*):(.*)$/ ) {
+ print "$file:$line: malformed line.\n";
+ next input;
+ }
+
+ print "$file:$line: Newsfeed `$site' has whitespace in its name\n"
+ if $site =~ /\s/;
+ print "$file:$line: comma-space in site name\n"
+ if $site =~ m@, @;
+ print "$file:$line: comma-space in subscription list\n"
+ if $pats =~ m@, @;
+ print "$file:$line: comma-space in flags\n"
+ if $flags =~ m@, @;
+
+ print "$file:$start: ME has exclusions\n"
+ if $site =~ m@^ME/@;
+ print "$file:$start: multiple slashes in exclusions for `$site'\n"
+ if $site =~ m@/.*/@;
+ $site =~ s@([^/]*)/.*@$1@;
+ print "$site, "
+ if $verbose;
+
+ if ( $site eq "ME" ) {
+ $defsub = $pats;
+ $defsub =~ s@(.*)/.*@$1@;
+ } elsif ( $defsub ne "" ) {
+ $pats = "$defsub,$pats";
+ }
+ print "$file:$start: Multiple slashes in distribution for `$site'\n"
+ if $pats =~ m@/.*/@;
+
+ if ( $site eq "ME" ) {
+ print "$file:$start: ME flags should be empty\n"
+ if $flags ne "";
+ print "$file:$start: ME param should be empty\n"
+ if $param ne "";
+ $me_empty = 1
+ if $pats !~ "/.+";
+ }
+
+ ## If we don't have !junk,!control, give a helpful warning.
+# if ( $site ne "ME" && $pats =~ /!\*,/ ) {
+# print "$file:$start: consider adding !junk to $site\n"
+# if $pats !~ /!junk/;
+# print "$file:$start: consider adding !control to $site\n"
+# if $pats !~ /!control/;
+# }
+
+ ## Check distributions.
+ if ( ($dists) = $pats =~ m@.*/(.*)@ ) {
+ $bang = $nobang = 0;
+ dist: foreach $d ( split(/,/, $dists) ) {
+ if ( $d =~ /^!/ ) {
+ $bang++;
+ }
+ else {
+ $nobang++;
+ }
+ print "$file:$start: questionable distribution `$d'\n"
+ if $d !~ /^!?[a-z0-9-]+$/;
+ }
+ print "$file:$start: both ! and non-! distributions\n"
+ if $bang && $nobang;
+ }
+ $type = "f";
+ flag: foreach $flag ( split(/,/, $flags) ) {
+ ($k, $v) = $flag =~ /(.)(.*)/;
+ if ( !defined $newsfeeds'flags{$k} ) {
+ print "$file:$start: unknown flag `$flag'\n";
+ next flag;
+ }
+ if ( $v !~ /$newsfeeds'flags{$k}/ ) {
+ print "$file:$start: bad value `$v' for flag `$k'\n";
+ next flag;
+ }
+ $type = $v
+ if $k eq "T";
+ }
+
+ ## Warn about multiple feeds.
+ if ( !defined $sites{$site} ) {
+ $sites{$site} = $type;
+ } elsif ( $sites{$site} ne $type ) {
+ print "$file:$start: feed $site multiple conflicting feeds\n";
+ }
+
+ if ( $type =~ /[cpx]/ ) {
+ $prog = $param;
+ $prog =~ s/\s.*//;
+ print "$file:$start: relative path for $site\n"
+ if $prog !~ m@^/@;
+ print "$file:$start: `$prog' is not executable for $site\n"
+ if ! -x $prog;
+ }
+ if ( $type eq "f" && $param =~ m@/@ ) {
+ $dir = $param;
+ $dir =~ s@(.*)/.*@$1@;
+ $dir = $paths{'batchdir'} . "/" . $dir
+ unless $dir =~ m@^/@;
+ print "$file:$start: directory `$dir' does not exist for $site\n"
+ if ! -d $dir;
+ }
+
+ ## If multiplex target not known, add to multiplex list.
+ push(@muxes, "$start: undefined multiplex `$param'")
+ if $type eq "m" && !defined $sites{$param};
+ }
+
+ ## Go through and make sure all referenced multiplex exist.
+ foreach (@muxes) {
+ print "$file:$_\n"
+ if /`(.*)'/ && !defined $sites{$1};
+ }
+ print "$file:0: warning you accept all incoming article distributions\n"
+ if !defined $sites{"ME"} || $me_empty;
+
+ print "done.\n"
+ if $verbose;
+ 1;
+}
+
+
+##
+## overview.fmt
+##
+#%overview_fmtheaders = (
+# 'Approved', 1,
+# 'Bytes', 1,
+# 'Control', 1,
+# 'Date', 1,
+# 'Distribution', 1,
+# 'Expires', 1,
+# 'From', 1,
+# 'Lines', 1,
+# 'Message-ID', 1,
+# 'Newsgroups', 1,
+# 'Path', 1,
+# 'References', 1,
+# 'Reply-To', 1,
+# 'Sender', 1,
+# 'Subject', 1,
+# 'Supersedes', 1,
+#);
+
+sub
+overview_fmt
+{
+ local ($header, $mode, $sawfull);
+
+ $sawfull = 0;
+ input: while ( <IN> ) {
+ next input if &spacious($file, ++$line);
+
+ unless ( ($header, $mode) = /^([^:]+):([^:]*)$/ ) {
+ print "$file:$line: malformed line.\n";
+ next input;
+ }
+
+ #print "$file:$line: unknown header `$header'\n"
+ # if !defined $overview_fmtheaders{$header};
+ if ( $mode eq "full" ) {
+ $sawfull++;
+ } elsif ( $mode eq "" ) {
+ print "$file:$line: short header `$header' appears after full one\n"
+ if $sawfull;
+ } else {
+ print "$file:$line: unknown mode `$mode'\n";
+ }
+ }
+ 1;
+}
+
+
+##
+## nntpsend.ctl
+##
+sub
+nntpsend_ctl
+{
+ local ($site, $fqdn, $flags, $f, $v);
+
+ input: while ( <IN> ) {
+ next input if &spacious($file, ++$line);
+
+ ## Ignore the size info for now.
+ unless ( ($site, $fqdn, $flags) =
+ /^([\w\-\.]+):([^:]*):[^:]*:([^:]*)$/ ) {
+ print "$file:$line: malformed line.\n";
+ next input;
+ }
+ print "$file:$line: FQDN is empty for `$site'\n"
+ if $fqdn eq "";
+
+ next input if $flags eq "";
+ flag: foreach (split(/ /, $flags)) {
+ unless ( ($f, $v) = /^-([adrvtTpSP])(.*)$/ ) {
+ print "$file:$line: unknown argument for `$site'\n";
+ next flag;
+ }
+ print "$file:$line: unknown argument to option `$f': $flags\n"
+ if ( $f eq "t" || $f eq "T" || $f eq "P") && $v !~ /\d+/;
+ }
+ }
+ 1;
+}
+
+
+##
+## passwd.nntp
+##
+sub
+passwd_nntp
+{
+ local ($name, $pass);
+
+ input: while ( <IN> ) {
+ next input if &spacious($file, ++$line);
+
+ unless ( ($name, $pass) = /[\w\-\.]+:(.*):(.*)(:authinfo)?$/ ) {
+ next input;
+ print "$file:$line: malformed line.\n";
+ }
+ print "$file:$line: username/password must both be blank or non-blank\n"
+ if ( $name eq "" && $pass ne "" ) || ($name ne "" && $pass eq "");
+ }
+ 1;
+}
+
+
+##
+## readers.conf
+##
+sub
+readers_conf
+{
+ 1;
+}
+\f
+
+##
+## Routines to check permissions
+##
+
+## Given a file F, check its mode to be M, and its ownership to be by the
+## user U in the group G. U and G have defaults.
+sub
+checkperm
+{
+ local ($f, $m, $u, $g) = ( @_, $newsuser, $newsgroup);
+ local (@sb, $owner, $group, $mode);
+
+ die "Internal error, undefined name in perm from ", (caller(0))[2], "\n"
+ if !defined $f;
+ die "Internal error, undefined mode in perm from ", (caller(0))[2], "\n"
+ if !defined $m;
+
+ if ( ! -e $f ) {
+ print "$pfx$f:0: missing\n";
+ }
+ else {
+ @sb = stat _;
+ $owner = (getpwuid($sb[$ST_UID]))[0];
+ $group = (getgrgid($sb[$ST_GID]))[0];
+ $mode = $sb[$ST_MODE] & ~0770000;
+
+ ## Ignore setgid bit on directories.
+ $mode &= ~0777000
+ if -d _;
+
+ if ( $owner ne $u ) {
+ print "$pfx$f:0: owned by $owner, should be $u\n";
+ print "chown $u $f\n"
+ if $fix;
+ }
+ if ( $group ne $g ) {
+ print "$pfx$f:0: in group $group, should be $g\n";
+ print "chgrp $g $f\n"
+ if $fix;
+ }
+ if ( $mode ne $m ) {
+ printf "$pfx$f:0: mode %o, should be %o\n", $mode, $m;
+ printf "chmod %o $f\n", $m
+ if $fix;
+ }
+ }
+}
+
+## Return 1 if the Intersection of the files in the DIR and FILES is empty.
+## Otherwise, report an error for each illegal file, and return 0.
+sub
+intersect
+{
+ local ($dir, @files) = @_;
+ local (@in, %dummy, $i);
+
+ if ( !opendir(DH, $dir) ) {
+ print "$pfx$dir:0: can't open directory\n";
+ }
+ else {
+ @in = grep($_ ne "." && $_ ne "..", readdir(DH));
+ closedir(DH);
+ }
+
+ $i = 1;
+ if ( scalar(@in) ) {
+ foreach ( @files ) {
+ $dummy{$_}++;
+ }
+ foreach ( grep ($dummy{$_} == 0, @in) ) {
+ print "$pfx$dir:0: ERROR: illegal file `$_' in directory\n";
+ $i = 0;
+ }
+ }
+ $i;
+}
+
+@directories = (
+ 'archive', 'badnews', 'batchdir', 'ctlprogs', 'most_logs', 'newsbin',
+ 'newsetc', 'newslib', 'oldlogs', 'rnewsprogs', 'spooltemp', 'spool', 'spoolnews'
+);
+@rnews_programs = (
+ 'c7unbatch', 'decode', 'encode', 'gunbatch'
+);
+@newsbin_public = (
+ 'archive', 'batcher', 'buffchan', 'convdate', 'cvtbatch', 'expire',
+ 'filechan', 'getlist', 'grephistory', 'innconfval', 'innxmit',
+ 'makehistory', 'nntpget', 'overchan', 'prunehistory', 'shlock',
+ 'shrinkfile'
+);
+@newsbin_private = (
+ 'ctlinnd', 'expirerm', 'inncheck', 'innstat', 'innwatch',
+ 'news.daily', 'nntpsend', 'scanlogs', 'sendbatch',
+ 'tally.control', 'writelog',
+ 'send-ihave', 'send-nntp', 'send-uucp'
+);
+#@newslib_private_read = (
+# 'innlog.pl'
+#);
+
+## The modes for the various programs.
+%prog_modes = (
+ 'inews', @INEWSMODE@,
+ 'innd', 0550,
+ 'newsboot', 0550,
+ 'nnrpd', 0555,
+ 'rnews', @RNEWSMODE@,
+);
+
+## Check the permissions of nearly every file in an INN installation.
+sub
+check_all_perms
+{
+ local ($rnewsprogs) = $paths{'rnewsprogs'};
+ local ($newsbin) = $paths{'newsbin'};
+ local ($newslib) = $paths{'newslib'};
+
+ foreach ( @directories ) {
+ &checkperm($paths{$_}, 0755);
+ }
+ &checkperm($paths{'innddir'}, 0750);
+ foreach ( keys %prog_modes ) {
+ &checkperm($paths{$_}, $prog_modes{$_});
+ }
+ &checkperm($paths{'inndstart'}, 04550, 'root', $newsgroup);
+ foreach ( keys %paths ) {
+ &checkperm($paths{$_}, $modes{$_})
+ if defined $modes{$_};
+ }
+ &checkperm($paths{'history'}, 0644);
+ # Commented out for now since it depends on the history type.
+ #&checkperm($paths{'history'} . ".dir", 0644);
+ #&checkperm($paths{'history'} . ".index", 0644);
+ #&checkperm($paths{'history'} . ".hash", 0644);
+ #foreach ( @newslib_private_read ) {
+ # &checkperm("$newslib/$_", 0440);
+ #}
+ foreach ( @newsbin_private ) {
+ &checkperm("$newsbin/$_", 0550);
+ }
+ foreach ( @newsbin_public ) {
+ &checkperm("$newsbin/$_", 0555);
+ }
+ foreach ( @rnews_programs ) {
+ &checkperm("$rnewsprogs/$_", 0555);
+ }
+
+ ## Also make sure that @rnews_programs are the *only* programs in there;
+ ## anything else is probably someone trying to spoof rnews into being bad.
+ &intersect($rnewsprogs, @rnews_programs);
+
+ 1;
+}
+
+\f
+##
+## Parsing, main routine.
+##
+
+sub
+Usage
+{
+ local ($i) = 0;
+
+ print "Usage error: @_.\n";
+ print
+"Usage:
+ $program [-v] [-noperm] [-pedantic] [-perms [-fix] ] [-a|file...]
+File to check may be followed by \"=path\" to use the specified path. All
+files are checked if -a is used or if -perms is not used. Files that may
+be checked are:\n";
+ foreach ( sort(keys %checklist) ) {
+ printf " %-20s", $_;
+ if ( ++$i == 3) {
+ print "\n";
+ $i = 0;
+ }
+ }
+ print "\n"
+ if $i;
+ exit 0;
+}
+
+
+sub
+parse_flags
+{
+ $all = 0;
+ $fix = 0;
+ $perms = 0;
+ $noperms = 0;
+ $verbose = 0;
+ @todo = ();
+
+ arg: foreach ( @ARGV ) {
+ if ( /-a/ ) {
+ $all++;
+ next arg;
+ }
+ if ( /^-v/ ) {
+ $verbose++;
+ next arg;
+ }
+ if ( /^-ped/ ) {
+ $pedantic++;
+ next arg;
+ }
+ if ( /^-f/ ) {
+ $fix++;
+ next arg;
+ }
+ if ( /^-per/ ) {
+ $perms++;
+ next arg;
+ }
+ if ( /^-noperm/ ) {
+ $noperms++;
+ next arg;
+ }
+ if ( /^-/ ) {
+ &Usage("Unknown flag `$_'");
+ }
+ if ( ($k, $v) = /(.*)=(.*)/ ) {
+ &Usage("Can't check `$k'")
+ if !defined $checklist{$k};
+ push(@todo, $k);
+ $paths{$k} = $v;
+ next arg;
+ }
+ &Usage("Can't check `$_'")
+ if !defined $checklist{$_};
+ push(@todo, $_);
+ }
+
+ &Usage("Can't use `-fix' without `-perm'")
+ if $fix && !$perms;
+ &Usage("Can't use `-noperm' with `-perm'")
+ if $noperms && $perms;
+ $pfx = $fix ? '# ' : '';
+
+ @todo = grep(defined $checklist{$_}, sort(keys %paths))
+ if $all || (scalar(@todo) == 0 && ! $perms);
+}
+
+
+$program = $0;
+$program =~ s@.*/@@;
+$| = 1;
+&parse_flags();
+action: foreach $workfile ( @todo ) {
+ $file = $paths{$workfile};
+ if ( ! -f $file ) {
+ print "$file:0: file missing\n";
+ next action;
+ }
+ print "Looking at $file...\n"
+ if $verbose;
+ if ( !open(IN, $file) ) {
+ print "$pfx$workfile:0: can't open $!\n";
+ next action;
+ }
+ &checkperm($file, $modes{$workfile})
+ if $noperms == 0 && !$perms && defined $modes{$workfile};
+ $line = 0;
+ eval "&$checklist{$workfile}" || warn "$@";
+ close(IN);
+}
+
+&check_all_perms()
+ if $perms;
+exit(0);
+
+if ( 0 ) {
+ &active();
+ &control_ctl();
+ &incoming_conf();
+ &expire_ctl();
+ &inn_conf();
+ &moderators();
+ &nntpsend_ctl();
+ &newsfeeds();
+ &overview_fmt();
+ &passwd_nntp();
+ &readers_conf();
+}
--- /dev/null
+#! /usr/bin/perl
+# fixscript will replace this line with require innshellvars.pl
+
+# Author: James Brister <brister@vix.com> -- berkeley-unix --
+# Start Date: Fri, 25 Apr 1997 14:11:23 +0200
+# Project: INN
+# File: innmail.pl
+# RCSId: $Id: innmail.in 2677 1999-11-15 06:33:13Z rra $
+# Description: A simple replacement for UCB Mail to avoid nasty security
+# problems.
+#
+
+$0 =~ s!.*/!! ;
+
+require 5.001 ;
+require 'getopts.pl' ;
+
+die "$0: No \$inn::mta variable defined.\n"
+ if ! defined ($inn::mta);
+
+$sm = $inn::mta ;
+
+die "$0: MTA path is not absolute\n" unless ($sm =~ m!^/!) ;
+
+$usage = "usage: $0 -s subject addresses\n\n" .
+ "Reads stdin for message body\n" ;
+
+&Getopts ("s:h") || die $usage ;
+
+die $usage if $opt_h ;
+
+if ( !$opt_s ) {
+ warn "No subject given. Hope that's ok\n" ;
+ $opt_s = "NO SUBJECT" ;
+} else {
+ $opt_s =~ s/\n+\Z//;
+}
+
+# fix up any addresses.
+foreach ( @ARGV ) {
+ s![^-a-zA-Z0-9+_.@%]!!g ;
+
+ push (@addrs,$_) if ($_ ne "") ;
+}
+
+die "$0: No addresses specified\n\n$usage" unless @addrs ;
+
+if ($sm =~ m!%s!) {
+ $sm = sprintf $sm,join (' ',@addrs);
+} else {
+ $sm .= " " . join(' ', @addrs);
+}
+
+@smarr = split(/\s+/,$sm);
+
+($t = $inn::mta) =~ s!\s.*!!;
+die "$0: MTA variable definition is changed after subsitution\n"
+ if ($t ne $smarr[0]);
+
+die "$0: MTA excutable doesn't appear to exist: $smarr[0]\n"
+ if ! -x $smarr[0];
+
+# startup mta without using the shell
+$pid = open (MTA,"|-") ;
+if ($pid == 0) {
+ exec (@smarr) || die "$0: exec of $sm failed: $!\n" ;
+} elsif ($pid < 0) {
+ die "$0: Fork failed: $!\n" ;
+}
+
+print MTA "To: ", join (",\n\t",@addrs), "\n" ;
+print MTA "Subject: $opt_s\n" ;
+print MTA "\n" ;
+while (<STDIN>) {
+ print MTA $_ ;
+}
+close (MTA) ;
+exit ;
--- /dev/null
+#! /usr/bin/perl
+# fixscript will replace this line with require innshellvars.pl
+
+##########################################################################
+#
+# innreport: Perl script to summarize news log files
+# (with optional HTML output and graphs).
+#
+# version: 3.0.2
+#
+# Copyright (c) 1996-1999, Fabien Tassin (fta@sofaraway.org).
+#
+##########################################################################
+#
+# Usage: innreport -f config_file [-[no]options] logfile [logfile2 [...]]
+# where options are:
+# -h (or -help) : this help page
+# -html : HTML output
+# -v : display the version number of INNreport
+# -f config_file : name of the configuration file
+# -config : print INNreport configuration information
+# -g : want graphs [default]
+# -graph : an alias for option -g
+# -d directory : directory for Web pages
+# -dir directory : an alias for option -d
+# -p directory : pictures path (file space)
+# -path directory : an alias for option -p
+# -w directory : pictures path (web space)
+# -webpath directory : an alias for option -w
+# -i : name of index page
+# -index : an alias for option -i
+# -a : want to archive HTML results
+# -archive : an alias for option -a
+# -c number : how many report files to keep (0 = all)
+# -cycle number : an alias for option -c
+# -s char : separator for filename
+# -separator char : an alias for option -s
+# -unknown : Unknown entries from news log file
+# -maxunrec : Max number of unrecognized line to display
+# -casesensitive : Case sensitive
+# -notdaily : Never perform daily actions
+#
+# Use no in front of boolean options to unset them.
+# For example, "-html" is set by default. Use "-nohtml" to remove this
+# feature.
+#
+##########################################################################
+#
+# ABSOLUTELY NO WARRANTY WITH THIS PACKAGE. USE IT AT YOUR OWN RISKS.
+#
+# Note: You need the Perl graphic library GD.pm if you want the graphs.
+# GD is available on all good CPAN ftp sites:
+# ex: [CPAN_DIR]/authors/id/LDS/GD-1.1_.tar.gz (or greater)
+# or directly to:
+# <URL:http://www-genome.wi.mit.edu/pub/software/WWW/GD.html>
+# Note : innreport will create PNG or GIF files depending upon
+# the GD version.
+#
+# Documentation: for a short explaination of the different options, you
+# can read the usage (obtained with the -h or -help switch).
+#
+# Install: - check the Perl location (first line). Require Perl 5.002
+# or greater.
+# - look at the parameters in the configuration file (section
+# 'default')
+# - copy the configuration file into ${PATHETC}/innreport.conf
+# - copy the INN module into ${PATHETC}/innreport_inn.pm
+# - copy this script into ${PATHETC}/innreport
+# - be sure that the news user can run it (chmod 755 or 750)
+# - in "scanlog", comment the line containing innlog and add:
+# ${PATHETC}/innreport -f ${PATHETC}/innreport.conf ${OLD_SYSLOG}
+# or, if you want to change some options:
+# ${PATHETC}/innreport -f ${PATHETC}/innreport.conf options ${OLD_SYSLOG}
+#
+# Report: please report bugs (preferably) to the INN mailing list
+# (see README) or directly to the author (do not forget to
+# include the result of the "-config" switch, the parameters
+# passed on the command line and the INN version).
+# Please also report unknown entries.
+# Be sure your are using the latest version of this script before
+# any report.
+#
+##########################################################################
+
+# Note: References to <ftp://ftp.sofaraway.org/pub/innreport/> have been
+# removed from the output because this site appears to no longer exist. It
+# used to be the upstream source for innreport. If there is a new site for
+# innreport releases, please notify the INN maintainers.
+
+# remember to add '-w' on the first line and to uncomment the 'use strict'
+# below before doing any changes to this file.
+
+use strict;
+
+## Do you want to create a Web page. Pick DO or DONT.
+my $HTML = "DO";
+
+## Do you want the graphs (need $HTML too). Pick DO or DONT.
+my $GRAPH = "DO";
+
+## Directory for the Web pages (used only if the previous line is active)
+my $HTML_dir = "$inn::pathhttp";
+
+## Directory for the pictures (need HTML support) in the file space
+my $IMG_dir = "$HTML_dir/pics";
+
+## Directory for the pictures (need HTML support) in the Web space
+## (can be relative or global)
+my $IMG_pth = "pics";
+
+## Do you want to archive HTML results (& pics) [ will add a date in each
+## name ]. Pick DO or DONT.
+my $ARCHIVE = "DO";
+
+## index page will be called:
+my $index = "index.html";
+
+## How many report files to keep (0 = all) (need $ARCHIVE).
+my $CYCLE = 0;
+
+## separator between hours-minutes-seconds in filenames
+## (normaly a ":" but some web-browsers (Lynx, MS-IE, Mosaic) can't read it)
+## Warning: never use "/". Use only a _valid_ filename char.
+my $SEPARATOR = ".";
+
+## Do you want the "Unknown entries from news log file" report. Pick DO or
+## DONT.
+my $WANT_UNKNOWN = "DO";
+
+## Max number of unrecognized lines to display (if $WANT_UNKNOWN)
+## (-1 = no limit)
+my $MAX_UNRECOGNIZED = 50;
+
+## Do you want to be case sensitive. Pick DO or DONT.
+my $CASE_SENSITIVE = "DO";
+
+## Some actions must only be performed daily (once for a log file).
+## (ex: unwanted.log with INN). Default value (DONT) means to perform
+## these actions each . Pick DO or DONT.
+my $NOT_DAILY = "DONT";
+
+###############################################
+## THERE'S NOTHING TO CHANGE AFTER THIS LINE ##
+###############################################
+
+my $version = "3.0.2";
+my %output; # content of the configuration file.
+my $DEBUG = 0; # set to 1 to verify the structure/content of the conf file.
+my $start_time = time;
+
+# Require Perl 5.002 or greater.
+require 5.002;
+use Getopt::Long;
+use vars qw/$HAVE_GD $GD_FORMAT/;
+
+my @old_argv = @ARGV;
+
+# Convert DO/DONT into boolean values.
+{
+ my $i;
+ foreach $i (\$HTML, \$GRAPH, \$ARCHIVE, \$WANT_UNKNOWN,
+ \$CASE_SENSITIVE, \$NOT_DAILY) {
+ $$i = $$i eq 'DO' ? 1 : 0 ;
+ }
+}
+
+my %ref;
+GetOptions (\%ref,
+ qw(-h -help
+ -html!
+ -config
+ -f=s
+ -g! -graph!
+ -d=s -dir=s
+ -p=s -path=s
+ -w=s -webpath=s
+ -i=s -index=s
+ -a! -archive!
+ -c=i -cycle=i
+ -s=s -separator=s
+ -unknown!
+ -html-unknown!
+ -maxunrec=i
+ -casesensitive!
+ -notdaily!
+ -v
+ ));
+
+&Version if $ref{'v'};
+
+&Decode_Config_File($ref{'f'}) if defined $ref{'f'};
+&Usage if $ref{'h'} || $ref{'help'} || !defined $ref{'f'};
+
+$HTML = 0 if defined $output{'default'}{'html'};
+$HTML = 1 if $output{'default'}{'html'} eq 'true';
+$HTML = 0 if defined $ref{'html'};
+$HTML = 1 if $ref{'html'};
+
+$GRAPH = 0 if defined $output{'default'}{'graph'};
+$GRAPH = 1 if $HTML && ($output{'default'}{'graph'} eq 'true');
+$GRAPH = 0 if defined $ref{'g'} || defined $ref{'graph'};
+$GRAPH = 1 if $HTML && ($ref{'g'} || $ref{'graph'});
+
+$HTML_dir = &GetValue ($output{'default'}{'html_dir'})
+ if defined $output{'default'}{'html_dir'};
+$HTML_dir = $ref{'d'} if defined $ref{'d'};
+$HTML_dir = $ref{'dir'} if defined $ref{'dir'};
+
+$IMG_pth = &GetValue ($output{'default'}{'img_dir'})
+ if defined $output{'default'}{'img_dir'};
+$IMG_pth = $ref{'w'} if defined $ref{'w'};
+$IMG_pth = $ref{'webpath'} if defined $ref{'webpath'};
+
+$IMG_dir = $HTML_dir . "/" . $IMG_pth
+ if (defined $output{'default'}{'html_dir'} ||
+ defined $ref{'w'} || defined $ref{'webpath'})
+ &&
+ (defined $output{'default'}{'html_dir'} ||
+ defined $ref{'d'} || defined $ref{'dir'});
+
+$IMG_dir = $ref{'p'} if defined $ref{'p'};
+$IMG_dir = $ref{'path'} if defined $ref{'path'};
+
+$index = &GetValue ($output{'default'}{'index'})
+ if defined $output{'default'}{'index'};
+$index = $ref{'i'} if defined $ref{'i'};
+$index = $ref{'index'} if defined $ref{'index'};
+
+$ARCHIVE = &GetValue ($output{'default'}{'archive'})
+ if defined $output{'default'}{'archive'};
+$ARCHIVE = $ARCHIVE eq 'true';
+$ARCHIVE = 0 if defined $ref{'a'} || defined $ref{'archive'};
+$ARCHIVE = 1 if ($ref{'a'} || $ref{'archive'}) && $HTML;
+$ARCHIVE = 0 unless $HTML;
+
+$CYCLE = &GetValue ($output{'default'}{'cycle'})
+ if defined $output{'default'}{'cycle'};
+$CYCLE = 0 if $CYCLE eq 'none';
+$CYCLE = $ref{'c'} if defined $ref{'c'};
+$CYCLE = $ref{'cycle'} if defined $ref{'cycle'};
+
+$SEPARATOR = &GetValue ($output{'default'}{'separator'})
+ if defined $output{'default'}{'separator'};
+$SEPARATOR = $ref{'s'} if defined $ref{'s'};
+$SEPARATOR = $ref{'separator'} if defined $ref{'separator'};
+
+if (defined $output{'default'}{'unknown'}) {
+ $WANT_UNKNOWN = &GetValue ($output{'default'}{'unknown'});
+ $WANT_UNKNOWN = $WANT_UNKNOWN eq 'true' ? 1 : 0;
+}
+$WANT_UNKNOWN = 0 if defined $ref{'unknown'};
+$WANT_UNKNOWN = 1 if $ref{'unknown'};
+
+my $WANT_HTML_UNKNOWN = $WANT_UNKNOWN;
+if (defined $output{'default'}{'html-unknown'}) {
+ $WANT_HTML_UNKNOWN = &GetValue ($output{'default'}{'html-unknown'});
+ $WANT_HTML_UNKNOWN = $WANT_HTML_UNKNOWN eq 'true' ? 1 : 0;
+}
+$WANT_HTML_UNKNOWN = 0 if defined $ref{'html-unknown'};
+$WANT_HTML_UNKNOWN = 1 if $ref{'html-unknown'};
+
+$NOT_DAILY = 0 if defined $ref{'notdaily'};
+$NOT_DAILY = 1 if $ref{'notdaily'};
+
+$MAX_UNRECOGNIZED = &GetValue ($output{'default'}{'max_unknown'})
+ if defined $output{'default'}{'max_unknown'};
+$MAX_UNRECOGNIZED = $ref{'maxunrec'} if defined ($ref{'maxunrec'});
+
+$CASE_SENSITIVE = &GetValue ($output{'default'}{'casesensitive'})
+ if defined $output{'default'}{'casesensitive'};
+$CASE_SENSITIVE = 1 if $CASE_SENSITIVE eq 'true';
+$CASE_SENSITIVE = 0 if defined $ref{'casesensitive'};
+$CASE_SENSITIVE = 1 if $ref{'casesensitive'};
+
+my $CLASS = &GetValue ($output{'default'}{'module'});
+my $LIBPATH = &GetValue ($output{'default'}{'libpath'});
+
+umask 022;
+
+BEGIN {
+ eval "use GD;";
+ $HAVE_GD = $@ eq '';
+ if ($HAVE_GD) {
+ my $gd = new GD::Image(1,1);
+ $GD_FORMAT = "gif" if $gd->can('gif');
+ $GD_FORMAT = "png" if $gd->can('png');
+ }
+ $HAVE_GD;
+};
+undef $GRAPH unless $HTML;
+if ($GRAPH && !$::HAVE_GD) {
+ print "WARNING: can't make graphs as required.\n" .
+ " Install GD.pm or disable this option.\n\n";
+ undef $GRAPH;
+}
+
+if ($HTML) {
+ if ($GRAPH) {
+ $IMG_dir = "." if defined $IMG_dir && $IMG_dir eq '';
+ $IMG_pth .= "/" if $IMG_pth;
+ $IMG_pth =~ s|/+|/|g;
+ $IMG_dir =~ s|/+|/|g;
+ unless (-w $IMG_dir) {
+ print "WARNING: can't write in \"$IMG_dir\" as required by -g " .
+ "switch.\n Option -g removed. Please see the -p switch.\n\n";
+ undef $GRAPH;
+ }
+ }
+ $HTML_dir = "." if defined $HTML_dir && $HTML_dir eq '';
+ unless (-w $HTML_dir) {
+ print "WARNING: can't write in \"$HTML_dir\" as required by -html " .
+ "switch.\n Option -html and -a removed. Please see the " .
+ "-d switch.\n\n";
+ undef $HTML;
+ $ARCHIVE = 0;
+ }
+}
+
+# Now, we are sure that HTML and graphs can be made if options are active.
+&Summary if defined $ref{'config'};
+
+my $unrecognize_max = 0;
+my @unrecognize;
+my ($total_line, $total_size) = (0, 0);
+my ($suffix, $HTML_output, %config, $first_date, $last_date,
+ %prog_type, %prog_size);
+
+my $HTML_header = '';
+my $HTML_footer = '';
+
+my $MIN = 1E10;
+my $MAX = -1;
+
+my $xmax = &GetValue ($output{'default'}{'graph_width'}) # Graph size..
+ if defined $output{'default'}{'graph_width'};
+$xmax = 550 unless $xmax;
+
+my $transparent = &GetValue ($output{'default'}{'transparent'})
+ if defined $output{'default'}{'transparent'};
+$transparent = (defined $transparent && $transparent eq 'true') ? 1 : 0;
+
+my $repeated = 1;
+
+my $first_date_cvt = $MIN;
+my $last_date_cvt = $MAX;
+
+
+#########################################################################
+my $s = sprintf "use lib qw($LIBPATH); use $CLASS;";
+eval $s; # initialization
+die "Can't find/load $CLASS.pm : $@\n" if $@;
+
+my $save_line = <>;
+$_ = $save_line;
+local $^W = 0 if $] < 5.004; # to avoid a warning for each '+=' first use.
+LINE: while (!eof ()) {
+ $total_line++;
+ my $size = length;
+ $total_size += $size;
+
+ # Syslog optimization
+ if ($repeated) {
+ $repeated--;
+ $_ = $save_line;
+ }
+ else {
+ $_ = <>;
+ if ($_ =~ /last message repeated (\d+) times?$/o) {
+ $repeated = $1;
+ $_ = $save_line;
+ }
+ else {
+ $save_line = $_;
+ }
+ }
+
+ # skip empty lines
+ next LINE if $_ eq '';
+
+ my $res;
+ my ($day, $hour, $prog, $left) =
+ $_ =~ m/^(\S+\s+\S+) (\S+) \S+ (\S+): \[ID \d+ \S+\] (.*)$/o;
+ ($day, $hour, $prog, $left) =
+ $_ =~ m/^(\S+\s+\S+) (\S+) \S+ (\S+): (.*)$/o unless $day;
+ ($day, $hour, $prog, $left) =
+ $_ =~ m/^(\S+\s+\S+) (\S+) \d+ \S+ (\S+): (.*)$/o unless $day;
+
+ unless ($day) {
+ ($day, $hour, $res, $left) = $_ =~ m/^(\S+\s+\S+) (\S+)\.\d+ (\S+) (.*)$/o;
+ if ($day) {
+ my $cvtdate = &ConvDate ("$day $hour");
+ if ($cvtdate < $first_date_cvt) {
+ $first_date_cvt = $cvtdate;
+ $first_date = "$day $hour";
+ }
+ elsif ($cvtdate > $last_date_cvt) {
+ $last_date_cvt = $cvtdate;
+ $last_date = "$day $hour";
+ }
+ $prog = "inn";
+ }
+ else {
+ next if $_ =~ /^$/;
+ # Unrecognize line... skip
+ $unrecognize[$unrecognize_max] = $_
+ unless $unrecognize_max > $MAX_UNRECOGNIZED
+ && $MAX_UNRECOGNIZED > 0;
+ $unrecognize_max++;
+ next LINE;
+ }
+ }
+ else {
+ my $cvtdate = &ConvDate ("$day $hour");
+ if ($cvtdate < $first_date_cvt) {
+ $first_date_cvt = $cvtdate;
+ $first_date = "$day $hour";
+ }
+ elsif ($cvtdate > $last_date_cvt) {
+ $last_date_cvt = $cvtdate;
+ $last_date = "$day $hour";
+ }
+ }
+
+ ########
+ ## Program name
+ # word[7164] -> word
+ my ($pid) = $prog =~ s/\[(\d+)\]$//o;
+ # word: -> word
+ $prog =~ s/:$//o;
+ # wordX -> word (where X is a digit)
+ $prog =~ s/\d+$//o;
+
+ $prog_type{$prog}++;
+ $prog_size{$prog} = 0 unless defined $prog_size{$prog}; # stupid warning :(
+ $prog_size{$prog} += $size;
+
+ # The "heart" of the tool.
+ {
+ no strict;
+ next LINE if
+ &{$CLASS."::collect"} ($day, $hour, $prog, $res, $left, $CASE_SENSITIVE);
+ }
+
+ $unrecognize[$unrecognize_max] = $_
+ unless $unrecognize_max > $MAX_UNRECOGNIZED
+ && $MAX_UNRECOGNIZED > 0;
+ $unrecognize_max++;
+}
+
+{
+ no strict;
+ &{$CLASS . "::adjust"} ($first_date, $last_date);
+}
+
+$| = 1;
+
+die "no data. Abort.\n" unless $total_line;
+
+my $sec_glob = &ConvDate ("$last_date") - &ConvDate ("$first_date");
+unless ($sec_glob) {
+ print "WARNING: bad date (\"$last_date\" or \"$first_date\")\n" .
+ " Please, contact the author of innreport.\n";
+ $sec_glob = 24 * 60 * 60; # one day
+}
+
+$HTML_output = '';
+
+if ($HTML) {
+ # Create a new filename (unique and _sortable_)
+ if ($ARCHIVE) {
+ # The filename will contain the first date of the log or the current time.
+ my ($ts, $tm, $th, $dd, $dm, $dy) = localtime;
+ my ($m, $d, $h, $mn, $s) =
+ $first_date =~ /^(\S+)\s+(\d+)\s+(\d+):(\d+):(\d+)$/;
+ if ($m) {
+ my $ddm = (index "JanFebMarAprMayJunJulAugSepOctNovDec", $m) / 3;
+ # Adjust the year because syslog doesn't record it. We assume that
+ # it's the current year unless the last date is in the future.
+ my $ld = &ConvDate($last_date);
+ $dy-- if $ld > $ts + 60 * ($tm + 60 * ($th + 24 * ($dd - 1 +
+ substr("000031059090120151181212243273304334", $dm * 3, 3)))) ||
+ $ld < &ConvDate($first_date);
+ ($dm, $dd, $th, $tm, $ts) = ($ddm, $d, $h, $mn, $s);
+ }
+ $dm++; # because January = 0 and we prefer 1
+ $dy += 100 if $dy < 90; # Try to pacify the year 2000 !
+ $dy += 1900;
+ $suffix = sprintf ".%02d.%02d.%02d-%02d$SEPARATOR%02d$SEPARATOR%02d",
+ $dy, $dm, $dd, $th, $tm, $ts;
+ }
+ else {
+ $suffix = '';
+ }
+ $HTML_output = "$HTML_dir" . "/news-notice" . "$suffix" . ".html";
+ $HTML_output =~ s|/+|/|g;
+ if (defined $output{'default'}{'html_header_file'}) {
+ my $file = &GetValue ($output{'default'}{'html_header_file'});
+ $file = $HTML_dir . "/" . $file;
+ open (F, $file) && do {
+ local $/ = undef;
+ $HTML_header = <F>;
+ close F;
+ };
+ }
+ if (defined $output{'default'}{'html_footer_file'}) {
+ my $file = &GetValue ($output{'default'}{'html_footer_file'});
+ $file = $HTML_dir . "/" . $file;
+ open (F, $file) && do {
+ local $/ = undef;
+ $HTML_footer = <F>;
+ close F;
+ };
+ }
+}
+
+&Write_all_results ($HTML_output, \%output);
+
+&Make_Index ($HTML_dir, $index, "news-notice$suffix.html", \%output)
+ if $HTML && $index;
+
+#====================================================================
+
+if ($ARCHIVE) {
+ # rotate html files
+ &Rotate ($CYCLE, $HTML_dir, "news-notice", ".html");
+
+ # rotate pictures
+ my $report;
+ foreach $report (@{$output{'_order_'}}) {
+ next if $report =~ m/^(default|index)$/;
+ next unless defined $output{$report}{'graph'};
+
+ my $i = 0;
+ while ($GRAPH && defined ${${$output{$report}{'graph'}}[$i]}{'type'}) {
+ my $name = $report . ($i ? $i : '');
+ &Rotate ($CYCLE, $IMG_dir, $name, '.' . $GD_FORMAT);
+ $i++;
+ }
+ }
+}
+
+# Code needed by INN only. It must be in innreport_inn.pm to keep things clean.
+if (!$NOT_DAILY && defined $output{'default'}{'unwanted_log'}) {
+ my $logfile = &GetValue ($output{'default'}{'unwanted_log'});
+ my $logpath = &GetValue ($output{'default'}{'logpath'});
+ {
+ no strict;
+ &{$CLASS . "::report_unwanted_ng"} ("$logpath/$logfile");
+ }
+}
+
+################
+# End of report.
+###################################################################
+
+######
+# Misc...
+
+# Compare 2 dates (+hour)
+sub DateCompare {
+ # ex: "May 12 06" for May 12, 6:00am
+ local $[ = 0;
+ # The 2 dates are near. The range is less than a few days that's why we
+ # can cheat to determine the order. It is only important if one date
+ # is in January and the other in December.
+
+ my $date1 = substr ($a, 4, 2) * 24;
+ my $date2 = substr ($b, 4, 2) * 24;
+ $date1 += index("JanFebMarAprMayJunJulAugSepOctNovDec",substr($a,0,3)) * 288;
+ $date2 += index("JanFebMarAprMayJunJulAugSepOctNovDec",substr($b,0,3)) * 288;
+ if ($date1 - $date2 > 300 * 24) {
+ $date2 += 288 * 3 * 12;
+ }
+ elsif ($date2 - $date1 > 300 * 24) {
+ $date1 += 288 * 3 * 12;
+ }
+ $date1 += substr($a, 7, 2);
+ $date2 += substr($b, 7, 2);
+ $date1 - $date2;
+}
+
+
+# Convert: seconds to hh:mm:ss
+sub second2time {
+ my $temp;
+ my $t = shift;
+ # Hours
+ $temp = sprintf "%02d", $t / 3600;
+ my $chaine = "$temp:";
+ $t %= 3600;
+ # Min
+ $temp = sprintf "%02d", $t / 60;
+ $chaine .= "$temp:";
+ $t %= 60;
+ # Sec
+ $chaine .= sprintf "%02d", $t;
+ return $chaine;
+}
+
+# Convert: milliseconds to hh:mm:ss:mm
+sub ms2time {
+ my $temp;
+ my $t = shift;
+ # Hours
+ $temp = sprintf "%02d", $t / 3600000;
+ my $chaine = "$temp:";
+ $t %= 3600000;
+ # Min
+ $temp = sprintf "%02d", $t / 60000;
+ $chaine .= "$temp:";
+ $t %= 60000;
+ # Sec
+ $temp = sprintf "%02d", $t / 1000;
+ $chaine .= "$temp.";
+ $t %= 1000;
+ # Millisec
+ $chaine .= sprintf "%03d", $t;
+ return $chaine;
+}
+
+# Rotate the archive files..
+sub Rotate {
+ # Usage: &Rotate ($max_files, "$directory", "prefix", "suffix");
+ my ($max, $rep, $prefix, $suffix) = @_;
+ my ($file, $num, %files);
+ local ($a, $b);
+
+ return 1 unless $max;
+ opendir (DIR, "$rep") || die "Error: Cant open directory \"$rep\"\n";
+
+ FILE : while (defined ($file = readdir (DIR))) {
+ next FILE
+ unless $file =~ /^ # e.g. news-notice.1997.05.14-01:34:29.html
+ $prefix # Prefix : news-notice
+ \. # dot : .
+ (\d\d)?\d\d # Year : 1997 (or 97)
+ \. # dot : .
+ \d\d # Month : 05
+ \. # dot : .
+ \d\d # Day : 14
+ - # Separator : -
+ \d\d # Hour : 01
+ $SEPARATOR # Separator : ":"
+ \d\d # Minute : 34
+ $SEPARATOR # Separator : ":"
+ \d\d # Second : 29
+ $suffix # Suffix : ".html"
+ $/x;
+ $files{$file}++;
+ }
+ closedir DIR;
+ $num = 0;
+ foreach $file (sort {$b cmp $a} (keys (%files))) {
+ unlink "$rep/$file" if $num++ >= $max && -f "$rep/$file";
+ }
+ return 1;
+}
+
+# convert a date to a number of seconds
+sub ConvDate {
+ # usage: $num = &ConvDate ($date);
+ # date format is Aug 22 01:49:40
+ my $T = shift;
+ my ($m, $d, $h, $mn, $s) = $T =~ /^(\S+)\s+(\d+)\s+(\d+):(\d+):(\d+)$/;
+ my $out = $s + 60 * $mn + 3600 * $h + 86400 * ($d - 1);
+
+ $m = substr("000031059090120151181212243273304334",
+ index ("JanFebMarAprMayJunJulAugSepOctNovDec", $m), 3);
+ $out += $m * 86400;
+ return $out;
+}
+
+# Compare 2 filenames
+sub filenamecmp {
+ local $[ = 0;
+ my ($la, $lb) = ($a, $b);
+ my ($ya) = $la =~ m/news-notice\.(\d+)\./o;
+ $ya += 100 if $ya < 90; # Try to pacify the year 2000 !
+ $ya += 1900 if $ya < 1900; # xx -> xxxx
+ my ($yb) = $lb =~ m/news-notice\.(\d+)\./o;
+ $yb += 100 if $yb < 90; # Try to pacify the year 2000 !
+ $yb += 1900 if $yb < 1900; # xx -> xxxx
+
+ $la =~ s/news-notice\.(\d+)\./$ya\./;
+ $lb =~ s/news-notice\.(\d+)\./$yb\./;
+ $la =~ s/[\.\-\:html]//g;
+ $lb =~ s/[\.\-\:html]//g;
+
+ $lb <=> $la;
+}
+
+sub ComputeTotal {
+ my $h = shift;
+ my $total = 0;
+ my $key;
+ foreach $key (keys (%$h)) {
+ $total += $$h{$key};
+ }
+ $total;
+}
+
+sub ComputeTotalDouble {
+ my $h = shift;
+ my $total = 0;
+ my ($key1, $key2);
+ foreach $key1 (keys (%$h)) {
+ foreach $key2 (keys (%{$$h{$key1}})) {
+ $total += ${$$h{$key1}}{$key2};
+ }
+ }
+ $total;
+}
+
+# make an index for archive pages
+sub Make_Index {
+ my ($rep, $index, $filename, $data) = @_;
+ my %output = %$data;
+
+ $index =~ s/^\"\s*(.*?)\s*\"$/$1/o;
+
+ # add requested data at the end of the database.
+ open (DATA, ">> $rep/innreport.db") || die "can't open $rep/innreport.db\n";
+ my $i = 0;
+ my $res = "$filename";
+ while (defined ${${$output{'index'}{'column'}}[$i]}{'value'}) {
+ my $data = &GetValue (${${$output{'index'}{'column'}}[$i]}{'value'});
+ $data =~ s/\n//sog;
+ my @list = split /\|/, $data;
+ my $val;
+ foreach $val (@list) {
+ $res .= ($val eq 'date' ? "|$first_date -- $last_date"
+ : "|" . &EvalExpr($val));
+ }
+ $i++;
+ }
+ print DATA "$res\n";
+ close DATA;
+
+ # sort the database (reverse order), remove duplicates.
+ open (DATA, "$rep/innreport.db") || die "can't open $rep/innreport.db\n";
+ my %data;
+ while (<DATA>) {
+ m/^([^\|]+)\|(.*)$/o;
+ $data{$1} = $2;
+ }
+ close DATA;
+ open (DATA, "> $rep/innreport.db") || die "can't open $rep/innreport.db\n";
+ $i = 0;
+ foreach (sort {$b cmp $a} (keys %data)) {
+ print DATA "$_|$data{$_}\n" if $CYCLE == 0 || $i < $CYCLE;
+ $i++;
+ }
+ close DATA;
+
+ my $title = "Daily Usenet report";
+ $title = &GetValue ($output{'default'}{'title'})
+ if defined $output{'default'}{'title'};
+ $title =~ s/\\\"/\"/g;
+ my $Title = $title;
+ $Title =~ s/<.*?>//g;
+ my $body = '';
+ $body = &GetValue ($output{'default'}{'html_body'})
+ if defined $output{'default'}{'html_body'};
+ $body =~ s/\\\"/\"/go;
+ my $result = sprintf <<EOF;
+<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
+<HTML><HEAD>
+<TITLE>$Title: index</TITLE>
+</HEAD><BODY $body>
+$HTML_header
+<HR ALIGN=CENTER SIZE=\"4\" WIDTH=\"100%%\">
+<BR><CENTER><FONT SIZE=\"+2\">
+<B>$title - archives</B>
+</FONT></CENTER>
+<BR CLEAR=ALL>
+<HR ALIGN=CENTER SIZE=4 WIDTH=\"100%%\"><P>
+<CENTER>
+EOF
+
+ if ($GRAPH) {
+ my $i = 0;
+ while (defined ${${$output{'index'}{'graph'}}[$i]}{'title'}) {
+ my $title = &GetValue (${${$output{'index'}{'graph'}}[$i]}{'title'});
+ my $filename = "index$i.$GD_FORMAT";
+ my $color_bg = &GetValue (${${$output{'index'}{'graph'}}[$i]}{'color'});
+ my $unit = &GetValue (${${$output{'index'}{'graph'}}[$i]}{'unit'});
+ my $date_idx = &GetValue (${${$output{'index'}{'graph'}}[$i]}{'value'});
+ $date_idx =~ s/^val(\d+)$/$1/o;
+ my @c = @{${${$output{'index'}{'graph'}}[$i]}{'data'}};
+ my $label_in = &GetValue (${$c[0]}{'name'});
+ my $color_in = &GetValue (${$c[0]}{'color'});
+ my $value_in = &GetValue (${$c[0]}{'value'});
+ my $type_in = 0;
+ $type_in = $value_in =~ s/^byte\((.*?)\)$/$1/o;
+ $value_in =~ s/^val(\d+)$/$1/o;
+ my $label_out = &GetValue (${$c[1]}{'name'});
+ my $color_out = &GetValue (${$c[1]}{'color'});
+ my $value_out = &GetValue (${$c[1]}{'value'});
+ my $type_out = 0;
+ $type_out = $value_out =~ s/^byte\((.*?)\)$/$1/o;
+ $value_out =~ s/^val(\d+)$/$1/o;
+ my (%in, %out, %dates, $k);
+ foreach $k (keys (%data)) {
+ my @res = split /\|/, $data{$k};
+ my ($year) = $k =~ m/^news-notice\.(\d+)\.\d+\.\d+-\d+.\d+.\d+\.html/;
+ next unless $year; # bad filename.. strange.
+ my ($start, $end) =
+ $res[$date_idx - 1] =~ m/^(\w+\s+\d+ \S+) -- (\w+\s+\d+ \S+)$/o;
+ next unless $start; # bad date
+ $start = &ConvDate ($start);
+ $end = &ConvDate ($end);
+ # 31/12 - 1/1 ?
+ my $inc = $end < $start ? 1 : 0;
+ $start += (($year - 1970) * 365 +
+ int (($year - 1968) / 4)) * 3600 * 24;
+ $year += $inc;
+ $end += (($year - 1970) * 365 + int (($year - 1968) / 4)) * 3600 * 24;
+ $in{$start} = $type_in ? &kb2i($res[$value_in - 1])
+ : $res[$value_in - 1];
+ $out{$start} = $type_out ? &kb2i($res[$value_out - 1])
+ : $res[$value_out - 1];
+ $dates{$start} = $end;
+ }
+ my ($xmax, $ymax) = (500, 170);
+ &Chrono ("$IMG_dir/$filename", $title, $color_bg, $xmax, $ymax,
+ \%in, \%out, \%dates, $label_in, $label_out,
+ $color_in, $color_out, $unit);
+ $result .= "<IMG WIDTH=\"$xmax\" HEIGHT=\"$ymax\" ";
+ $result .= "SRC=\"$IMG_pth$filename\" ALT=\"Graph\">\n";
+ $i++;
+ }
+ $result .= "<P>\n";
+ }
+ $i = 0;
+ $result .= "<TABLE BORDER=\"1\"><TR>";
+ my $temp = '';
+ while (defined ${${$output{'index'}{'column'}}[$i]}{'title'}) {
+ my $title = &GetValue (${${$output{'index'}{'column'}}[$i]}{'title'});
+ my $name = '';
+ $name = &GetValue (${${$output{'index'}{'column'}}[$i]}{'name'})
+ if defined ${${$output{'index'}{'column'}}[$i]}{'name'};
+ my @list = split /\|/, $name;
+ if ($name) {
+ $result .= sprintf "<TH COLSPAN=%d>$title</TH>", $#list + 1;
+ }
+ else {
+ $result .= "<TH ROWSPAN=\"2\">$title</TH>";
+ }
+ foreach (@list) {
+ $temp .= "<TH>$_</TH>";
+ }
+ $i++;
+ }
+ $result .= "</TR>\n<TR>$temp</TR>\n";
+
+ $i = 0;
+ foreach (sort {$b cmp $a} (keys %data)) {
+ if ($CYCLE == 0 || $i < $CYCLE) {
+ my @list = split /\|/, $data{$_};
+ my $str = "<TR><TD ALIGN=LEFT>";
+ $str .= "<A HREF=\"$_\">" if -e "$rep/$_";
+ $str .= shift @list;
+ $str .= "</A>" if -e "$rep/$_";;
+ $str .= "</TD>";
+ while (@list) {
+ $str .= "<TD ALIGN=RIGHT>";
+ my $t = shift @list;
+ $t =~ s/^\0+//o; # remove garbage, if any.
+ $str .= "$t</TD>";
+ }
+ $str .= "</TR>\n";
+ $result .= "$str";
+ }
+ $i++;
+ }
+ $result .= "</TABLE>\n</CENTER>\n<P><HR>";
+ $result .= "innreport $version (c) 1996-1999 ";
+ $result .= "by Fabien Tassin <<A HREF=\"mailto:fta\@sofaraway.org\">";
+ $result .= "fta\@sofaraway.org</A>>.\n";
+ if (defined ($output{'default'}{'footer'})) {
+ my ($t) = $output{'default'}{'footer'} =~ m/^\"\s*(.*?)\s*\"$/o;
+ $t =~ s/\\\"/\"/go;
+ $result .= "<BR>" . $t;
+ }
+ $result .= "$HTML_footer\n</BODY>\n</HTML>\n";
+ my $name = $rep . "/" . $index;
+ while ($name =~ m/\/\.\.\//o) {
+ $name =~ s|^\./||o; # ^./xxx => ^xxx
+ $name =~ s|/\./|/|go; # xxx/./yyy => xxx/yyy
+ $name =~ s|/+|/|go; # xxx//yyy => xxx/yyy
+ $name =~ s|^/\.\./|/|o; # ^/../xxx => ^/xxx
+ $name =~ s|^[^/]+/\.\./||o; # ^xxx/../ => ^nothing
+ $name =~ s|/[^/]+/\.\./|/|go; # /yyy/../ => /
+ }
+
+ open (INDEX, "> $name") || die "Error: Unable to create $name\n";
+ print INDEX $result;
+ close INDEX;
+ 1;
+}
+
+sub Graph3d {
+ my $filename = shift; # filename
+ my $title = shift; # title
+ my $xmax = shift; # width
+ my $n = shift; # Number of hash code tables
+
+ no strict;
+ my ($i, $k, $t);
+ my @val;
+ for $i (0 .. $n - 1) {
+ push @val, shift; # hash code table
+ }
+ my $colors = shift; # colors table
+ my $labels = shift; # labels
+
+ my $max = 0;
+ my $max_size = 0;
+ my $size = 0;
+ foreach $k (sort keys (%{$val[0]})) {
+ $t = 0;
+ $size++;
+ for $i (0 .. $n - 1) {
+ $t += ${$val[$i]}{$k} if defined ${$val[$i]}{$k};
+ }
+ $max = $t if $max < $t;
+ $t = length "$k";
+ $max_size = $t if $max_size < $t;
+ }
+ $max = 1 unless $max;
+ $max_size *= gdSmallFont->width;
+
+ # relief
+ my ($rx, $ry) = (15, 5);
+
+ # margins
+ my ($mt, $mb) = (40, 40);
+ my $ml = $max_size > 30 ? $max_size + 8 : 30;
+
+ my $mr = 7 + (length "$max") * gdSmallFont->width;
+ $mr = 30 if $mr < 30;
+
+ # height of each bar
+ my $h = 12;
+
+ # difference between 2 bars
+ my $d = 25;
+
+ my $ymax = $size * $d + $mt + $mb;
+ my $image = new GD::Image ($xmax, $ymax);
+
+ my ($white, $black);
+ if (defined $output{'default'}{'graph_fg'}) {
+ my $t = $output{'default'}{'graph_fg'};
+ $t =~ s/^\"\s*\#(.*?)\s*\"$/$1/o;
+ $t =~ m/^[\da-fA-F]{6}$/o ||
+ die "Error in section 'default' section 'graph_fg'. Bad color.\n";
+ my @c = map { hex ($_) } ($t =~ m/^(..)(..)(..)$/);
+ $black = $image->colorAllocate (@c);
+ }
+ else {
+ $black = $image->colorAllocate ( 0, 0, 0);
+ }
+ if (defined $output{'default'}{'graph_bg'}) {
+ my $t = $output{'default'}{'graph_bg'};
+ $t =~ s/^\"\s*\#(.*?)\s*\"$/$1/o;
+ $t =~ m/^[\da-fA-F]{6}$/o ||
+ die "Error in section 'default' section 'graph_bg'. Bad color.\n";
+ my @c = map { hex ($_) } ($t =~ m/^(..)(..)(..)$/);
+ $white = $image->colorAllocate (@c);
+ }
+ else {
+ $white = $image->colorAllocate (255, 255, 255);
+ }
+ $image->filledRectangle (0, 0, $xmax, $ymax, $white);
+ my @col;
+ for $i (0 .. $n - 1) {
+ $col[$i][0] = $image->colorAllocate
+ ($$colors[$i][0], $$colors[$i][1], $$colors[$i][2]);
+ $col[$i][1] = $image->colorAllocate
+ ($$colors[$i][0] * 3 / 4, $$colors[$i][1] * 3 / 4,
+ $$colors[$i][2] * 3 / 4);
+ $col[$i][2] = $image->colorAllocate
+ ($$colors[$i][0] * 2 / 3, $$colors[$i][1] * 2 / 3,
+ $$colors[$i][2] * 2 / 3);
+ }
+
+ $image->transparent ($white) if $transparent;
+
+ $image->rectangle (0, 0, $xmax - 1, $size * $d + $mt + $mb - 1, $black);
+ $image->line (0, $mt - 5, $xmax - 1, $mt - 5, $black);
+ for $i (0 .. $n - 1) {
+ $image->string (gdSmallFont, $i * $xmax / $n + $mt - 10 + $rx,
+ ($mt - gdSmallFont->height) / 2, "$$labels[$i]", $black);
+ $image->filledRectangle ($i * $xmax / $n + 10, 8 + $ry / 2,
+ $i * $xmax / $n + $mt - 10, $mt - 12, $col[$i][0]);
+ $image->rectangle ($i * $xmax / $n + 10, 8 + $ry / 2,
+ $i * $xmax / $n + $mt - 10, $mt - 12, $black);
+ {
+ my $poly = new GD::Polygon;
+ $poly->addPt($i * $xmax / $n + 10, 8 + $ry / 2);
+ $poly->addPt($i * $xmax / $n + 10 + $rx / 2, 8);
+ $poly->addPt($i * $xmax / $n + $mt - 10 + $rx / 2, 8);
+ $poly->addPt($i * $xmax / $n + $mt - 10, 8 + $ry / 2);
+
+ $image->filledPolygon($poly, $col[$i][1]);
+ $image->polygon($poly, $black);
+ }
+ {
+ my $poly = new GD::Polygon;
+ $poly->addPt($i * $xmax / $n + $mt - 10 + $rx / 2, 8);
+ $poly->addPt($i * $xmax / $n + $mt - 10, 8 + $ry / 2);
+ $poly->addPt($i * $xmax / $n + $mt - 10, $mt - 12);
+ $poly->addPt($i * $xmax / $n + $mt - 10 + $rx / 2, $mt - 12 - $ry / 2);
+
+ $image->filledPolygon($poly, $col[$i][2]);
+ $image->polygon($poly, $black);
+ }
+ }
+ # Title
+ $image->string (gdMediumBoldFont, ($xmax - gdMediumBoldFont->width *
+ (length "$title")) / 2, $ymax - gdMediumBoldFont->height - 7,
+ "$title", $black);
+
+ my $e = $mt - $h + $d;
+ my $r = ($xmax - $ml - $mr - $rx) / $max;
+
+ # Axe Oz
+ $image->line ($ml + $rx, $mt, $ml + $rx, $size * $d + $mt - $ry, $black);
+ $image->line ($ml + $rx + $max * $r, $mt, $ml + $rx + $max * $r,
+ $size * $d + $mt - $ry, $black);
+ $image->line ($ml, $mt + $ry, $ml, $size * $d + $mt, $black);
+ # Axe Ox
+ $image->line ($ml + $rx, $size * $d + $mt - $ry,
+ $ml + $rx - 2 * $rx, $size * $d + $mt + $ry, $black);
+ # Axe Oy
+ $image->line ($ml + $rx, $size * $d + $mt - $ry,
+ $xmax - $mr / 2, $size * $d + $mt - $ry, $black);
+ $image->line ($ml, $size * $d + $mt,
+ $xmax - $mr - $rx, $size * $d + $mt, $black);
+
+ # Graduations..
+ my $nn = 10;
+ for $k (1 .. ($nn - 1)) {
+ $image->dashedLine ($ml + $rx + $k * ($xmax - $ml - $mr - $rx) / $nn,
+ $mt + 10, $ml + $rx + $k * ($xmax - $ml - $mr - $rx) / $nn,
+ $size * $d + $mt - $ry, $black);
+ $image->dashedLine ($ml + $rx + $k * ($xmax - $ml - $mr - $rx) / $nn,
+ $size * $d + $mt - $ry,
+ $ml + $k * ($xmax - $ml - $mr - $rx) / $nn,
+ $size * $d + $mt, $black);
+ $image->line ($ml + $k * ($xmax - $ml - $mr - $rx) / $nn,
+ $size * $d + $mt,
+ $ml + $k * ($xmax - $ml - $mr - $rx) / $nn,
+ $size * $d + $mt + 5, $black);
+ my $t = sprintf "%d%%", $k * 10;
+ $image->string (gdSmallFont, $ml + $k * ($xmax - $ml - $mr - $rx) / $nn -
+ (length "$t") * gdSmallFont->width / 2,
+ $size * $d + $mt + 6, "$t", $black);
+ }
+ {
+ my $t = sprintf "%d%%", 0;
+ $image->line ($ml, $size * $d + $mt, $ml, $size * $d + $mt + 5, $black);
+ $image->string (gdSmallFont, $ml - (length "$t") * gdSmallFont->width / 2,
+ $size * $d + $mt + 6, "$t", $black);
+ $image->line ($xmax - $mr, $size * $d + $mt - $ry,
+ $xmax - $mr - $rx, $size * $d + $mt, $black);
+ $image->line ($xmax - $mr - $rx, $size * $d + $mt,
+ $xmax - $mr - $rx, $size * $d + $mt + 5, $black);
+ $t = sprintf "%d%%", 100;
+ $image->string (gdSmallFont, $xmax - $mr - $rx
+ - (length "$t") * gdSmallFont->width / 2,
+ $size * $d + $mt + 6, "$t", $black);
+ }
+ foreach $k (sort {${$val[0]}{$b} <=> ${$val[0]}{$a}} keys (%{$val[0]})) {
+ $image->string (gdSmallFont, $ml - (length "$k") * gdSmallFont->width - 3,
+ $e + $h / 2 - gdSmallFont->height / 2, "$k", $black);
+ my $t = 0;
+ $image->line ($ml + ($t + ${$val[0]}{$k}) * $r + $rx - $rx, $e + $h,
+ $ml + ($t + ${$val[0]}{$k}) * $r + $rx, $e - $ry + $h,
+ $black);
+ for $i (0 .. $n - 1) {
+ next unless defined ${$val[$i]}{$k};
+ {
+ my $poly = new GD::Polygon;
+ $poly->addPt($ml + $t * $r, $e);
+ $poly->addPt($ml + $t * $r + $rx, $e - $ry);
+ $poly->addPt($ml + ($t + ${$val[$i]}{$k}) * $r + $rx, $e - $ry);
+ $poly->addPt($ml + ($t + ${$val[$i]}{$k}) * $r, $e);
+
+ $image->filledPolygon($poly, $col[$i][1]);
+ $image->polygon($poly, $black);
+ }
+ unless (${$val[$i + 1]}{$k} || ${$val[$i]}{$k} == 0) {
+ my $poly = new GD::Polygon;
+ $poly->addPt($ml + ($t + ${$val[$i]}{$k}) * $r + $rx, $e - $ry);
+ $poly->addPt($ml + ($t + ${$val[$i]}{$k}) * $r + $rx - $rx, $e);
+ $poly->addPt($ml + ($t + ${$val[$i]}{$k}) * $r + $rx - $rx, $e + $h);
+ $poly->addPt($ml + ($t + ${$val[$i]}{$k}) * $r + $rx, $e - $ry + $h);
+
+ $image->filledPolygon($poly, $col[$i][2]);
+ $image->polygon($poly, $black);
+ }
+ $image->filledRectangle ($ml + $t * $r, $e,
+ $ml + ($t + ${$val[$i]}{$k}) * $r, $e + $h,
+ $col[$i][0]);
+ $image->rectangle ($ml + $t * $r, $e, $ml + ($t + ${$val[$i]}{$k}) * $r,
+ $e + $h, $black);
+ $t += ${$val[$i]}{$k};
+ }
+ # total length (offered)
+ $image->filledRectangle ($ml + $t * $r + $rx + 3,
+ $e - 2 - gdSmallFont->height / 2,
+ $ml + $t * $r + $rx + 4 +
+ gdSmallFont->width * length $t,
+ $e - 6 + gdSmallFont->height / 2, $white);
+ $image->string (gdSmallFont, $ml + $t * $r + $rx + 5,
+ $e - 3 - gdSmallFont->height / 2, "$t", $black);
+ # first value (accepted)
+ $image->filledRectangle ($ml + $t * $r + $rx + 3,
+ $e - 4 + gdSmallFont->height / 2,
+ $ml + $t * $r + $rx + 4 +
+ gdSmallFont->width * length "${$val[0]}{$k}",
+ $e - 2 + gdSmallFont->height, $white);
+ $image->string (gdSmallFont, $ml + $t * $r + $rx + 5,
+ $e - 5 + gdSmallFont->height / 2, ${$val[0]}{$k}, $black);
+ $e += $d;
+ }
+ open (IMG, "> $filename") || die "Error: Can't open \"$filename\": $!\n";
+ if ($GD_FORMAT eq 'png') {
+ print IMG $image->png;
+ }
+ else {
+ print IMG $image->gif;
+ }
+ close IMG;
+ $ymax;
+}
+
+sub Histo {
+ my ($filename, $title, $xmax, $factor,
+ $labelx, $labely, $val1, $labels1) = @_;
+
+ no strict;
+ my $max = 0;
+ my $ymax = 300;
+ my $nb = 0;
+ # A hugly hack to convert hashes to lists..
+ # and to adjust the first and the last value...
+ # this function should be rewritten..
+ my (@a, @b, $kk);
+ foreach $kk (sort keys (%$val1)) {
+ if (defined $$val1{$kk}) {
+ $nb++;
+ # Arg... the following MUST be removed !!!!!!!!!
+ $$val1{$kk} = $$val1{$kk} / $innreport_inn::inn_flow_time{$kk} * 3600
+ if ($innreport_inn::inn_flow_time{$kk} != 3600) &&
+ ($innreport_inn::inn_flow_time{$kk} != 0);
+ push @a, $$val1{$kk};
+ $max = $$val1{$kk} if $$val1{$kk} > $max;
+ push @b, $$labels1{$kk};
+ }
+ }
+ return 0 unless $nb; # strange, no data.
+ my $val = \@a;
+ my $labels = \@b;
+ my ($i, $j);
+ my ($marginl, $marginr, $margint, $marginb, $shx, $shy);
+
+ my $image = new GD::Image($xmax, $ymax);
+ my ($white, $black);
+ if (defined $output{'default'}{'graph_fg'}) {
+ my $t = $output{'default'}{'graph_fg'};
+ $t =~ s/^\"\s*\#(.*?)\s*\"$/$1/o;
+ $t =~ m/^[\da-fA-F]{6}$/o ||
+ die "Error in section 'default' section 'graph_fg'. Bad color.\n";
+ my @c = map { hex ($_) } ($t =~ m/^(..)(..)(..)$/);
+ $black = $image->colorAllocate (@c);
+ }
+ else {
+ $black = $image->colorAllocate ( 0, 0, 0);
+ }
+ if (defined $output{'default'}{'graph_bg'}) {
+ my $t = $output{'default'}{'graph_bg'};
+ $t =~ s/^\"\s*\#(.*?)\s*\"$/$1/o;
+ $t =~ m/^[\da-fA-F]{6}$/o ||
+ die "Error in section 'default' section 'graph_bg'. Bad color.\n";
+ my @c = map { hex $_ } ($t =~ m/^(..)(..)(..)$/);
+ $white = $image->colorAllocate (@c);
+ }
+ else {
+ $white = $image->colorAllocate (255, 255, 255);
+ }
+ $image->filledRectangle (0, 0, $xmax, $ymax, $white);
+ my $gray = $image->colorAllocate (128, 128, 128);
+ my $red = $image->colorAllocate (255, 0, 0);
+ my $red2 = $image->colorAllocate (189, 0, 0);
+ my $red3 = $image->colorAllocate (127, 0, 0);
+ my $coltxt = $black;
+
+ $image->transparent ($white) if $transparent;
+
+ my $FontWidth = gdSmallFont->width;
+ my $FontHeight = gdSmallFont->height;
+
+ $marginl = 60;
+ $marginr = 30;
+ $margint = 60;
+ $marginb = 30;
+ $shx = 7;
+ $shy = 7;
+
+ $max = 1 unless $max;
+ my $part = 8;
+ $max /= $factor;
+
+ my $old_max = $max;
+ {
+ my $t = log ($max) / log 10;
+ $t = sprintf "%.0f", $t - 1;
+ $t = exp ($t * log 10);
+ $max = sprintf "%.0f", $max / $t * 10 + 0.4;
+ my $t2 = sprintf "%.0f", $max / $part;
+ unless ($part * $t2 == $max) {
+ while ($part * $t2 != $max) {
+ $max++;
+ $t2 = sprintf "%d", $max / $part;
+ }
+ }
+ $max = $max * $t / 10;
+ }
+
+ # Title
+ $image->string (gdMediumBoldFont,
+ ($xmax - length ($title) * gdMediumBoldFont->width) / 2,
+ ($margint - $shy - gdMediumBoldFont->height) / 2,
+ $title, $coltxt);
+
+ # Labels
+ $image->string (gdSmallFont, $marginl / 2, $margint / 2, $labely, $coltxt);
+ $image->string (gdSmallFont, $xmax - $marginr / 2 -
+ $FontWidth * length ($labelx), $ymax - $marginb / 2,
+ $labelx, $coltxt);
+
+ # Max
+ $image->line ($marginl, $ymax - $marginb - $shy -
+ $old_max * ($ymax - $marginb - $margint - $shy) / $max,
+ $xmax - $marginr, $ymax - $marginb - $shy -
+ $old_max * ($ymax - $marginb - $margint - $shy) / $max, $red);
+ $image->line ($marginl, $ymax - $marginb - $shy -
+ $old_max * ($ymax - $marginb - $margint - $shy) / $max,
+ $marginl - $shx, $ymax - $marginb -
+ $old_max * ($ymax - $marginb - $margint - $shy) / $max, $red);
+
+ # Left
+ $image->line ($marginl - $shx, $margint + $shy,
+ $marginl - $shx, $ymax - $marginb, $coltxt);
+ $image->line ($marginl, $margint,
+ $marginl, $ymax - $marginb - $shy, $coltxt);
+ $image->line ($marginl, $margint,
+ $marginl - $shx, $margint + $shy, $coltxt);
+ $image->line ($marginl - $shx, $ymax - $marginb,
+ $marginl, $ymax - $marginb - $shy, $coltxt);
+
+ # Right
+ $image->line ($xmax - $marginr, $margint,
+ $xmax - $marginr, $ymax - $marginb - $shy, $coltxt);
+ $image->line ($xmax - $marginr - $shx, $ymax - $marginb,
+ $xmax - $marginr, $ymax - $marginb - $shy, $coltxt);
+
+ # Bottom
+ $image->line ($marginl - $shx, $ymax - $marginb,
+ $xmax - $marginr - $shx, $ymax - $marginb, $coltxt);
+ $image->line ($marginl, $ymax - $marginb - $shy,
+ $xmax - $marginr, $ymax - $marginb - $shy, $coltxt);
+ $image->fill ($xmax / 2, $ymax - $marginb - $shy / 2, $gray);
+
+ # Top
+ $image->line ($marginl, $margint,
+ $xmax - $marginr, $margint, $coltxt);
+ $image->setStyle ($coltxt, $coltxt, &GD::gdTransparent,
+ &GD::gdTransparent, &GD::gdTransparent);
+ # Graduations
+ for ($i = 0; $i <= $part; $i++) {
+ $j = $max * $i / $part ; # Warning to floor
+ # $j = ($max / $part) * ($i / 10000);
+ # $j *= 10000;
+
+ # Little hack...
+ $j = sprintf "%d", $j if $j > 100;
+
+ $image->line ($marginl - $shx - 3, $ymax - $marginb -
+ $i * ($ymax - $marginb - $margint - $shy) / $part,
+ $marginl - $shx, $ymax - $marginb -
+ $i * ($ymax - $marginb - $margint - $shy) / $part, $coltxt);
+ $image->line ($marginl - $shx, $ymax - $marginb -
+ $i * ($ymax - $marginb - $margint - $shy) / $part,
+ $marginl, $ymax - $marginb - $shy -
+ $i * ($ymax - $marginb - $margint - $shy) / $part, gdStyled);
+ $image->line ($marginl, $ymax - $marginb - $shy -
+ $i * ($ymax - $marginb - $margint - $shy) / $part,
+ $xmax - $marginr, $ymax - $marginb - $shy -
+ $i * ($ymax - $marginb - $margint - $shy) / $part, gdStyled);
+ $image->string (gdSmallFont,
+ $marginl - $shx - $FontWidth * length ("$j") - 7,
+ $ymax - $marginb -
+ ($i) * ($ymax - $marginb - $margint - $shy) / ($part) -
+ $FontHeight / 2, "$j", $coltxt);
+ }
+
+ # Graduation (right bottom corner)
+ $image->line ($xmax - $marginr - $shx, $ymax - $marginb,
+ $xmax - $marginr - $shx, $ymax - $marginb + 3, $coltxt);
+ # Bars
+ $i = 0;
+ my $w = ($xmax - $marginl - $marginr) / $nb;
+ my $k = $w / 5;
+ $$val[$nb - 1] = 0 unless $$val[$nb - 1];
+ foreach $j (@$val) {
+ my $MAX = 1;
+ if ($i++ <= $nb) {
+ # Graduation
+ $image->line ($marginl + ($i - 1) * $w - $shx, $ymax - $marginb,
+ $marginl + ($i - 1) * $w - $shx, $ymax - $marginb + 3,
+ $coltxt);
+ my $ii = sprintf "%d", $i / $MAX;
+ $image->string (gdSmallFont,
+ $marginl + ($i - 0.5) * $w + 1 -
+ ($FontWidth * length ($$labels[$i-1])) / 2 - $shx,
+ $ymax - $marginb + 3, $$labels[$i-1], $coltxt)
+ unless ($w < $FontWidth * length ($$labels[$i-1]))
+ && ($i != $MAX * $ii);
+
+ # Right
+ my $poly = new GD::Polygon;
+ $poly->addPt($marginl + ($i) * $w - $k, $ymax - $marginb - $shy -
+ $j / $factor * ($ymax - $marginb - $margint - $shy) / $max);
+ $poly->addPt($marginl + ($i) * $w - $k, $ymax - $marginb - $shy);
+ $poly->addPt($marginl + ($i) * $w - $k - $shx, $ymax - $marginb);
+ $poly->addPt($marginl + ($i) * $w - $k - $shx, $ymax - $marginb -
+ $j / $factor * ($ymax - $marginb - $margint - $shy) / $max);
+
+ $image->filledPolygon($poly, $red3);
+ $image->polygon($poly, $coltxt);
+
+ # Front
+ $image->filledRectangle ($marginl + ($i - 1) * $w + $k - $shx,
+ $ymax - $marginb -
+ $j / $factor * ($ymax - $marginb - $margint - $shy) / $max,
+ $marginl + ($i) * $w - $k - $shx,
+ $ymax - $marginb, $red);
+ $image->rectangle ($marginl + ($i - 1) * $w + $k - $shx,
+ $ymax - $marginb -
+ $j / $factor * ($ymax - $marginb - $margint - $shy) / $max,
+ $marginl + ($i) * $w - $k - $shx,
+ $ymax - $marginb, $coltxt);
+ # Top
+ my $poly2 = new GD::Polygon;
+ $poly2->addPt($marginl + ($i - 1) * $w + $k, $ymax - $marginb - $shy -
+ $j / $factor * ($ymax - $marginb - $margint - $shy) / $max);
+ $poly2->addPt($marginl + ($i) * $w - $k, $ymax - $marginb - $shy -
+ $j / $factor * ($ymax - $marginb - $margint - $shy) / $max);
+ $poly2->addPt($marginl + ($i) * $w - $k - $shx, $ymax - $marginb -
+ $j / $factor * ($ymax - $marginb - $margint - $shy) / $max);
+ $poly2->addPt($marginl + ($i - 1) * $w + $k - $shx, $ymax - $marginb -
+ $j / $factor * ($ymax - $marginb - $margint - $shy) / $max);
+
+ $image->rectangle (0, 0, $xmax - 1, $ymax - 1, $coltxt);
+ $image->filledPolygon($poly2, $red2);
+ $image->polygon($poly2, $coltxt);
+ }
+ }
+
+ open (IMG, "> $filename") || die "Can't create '$filename'\n";
+ if ($GD_FORMAT eq 'png') {
+ print IMG $image->png;
+ }
+ else {
+ print IMG $image->gif;
+ }
+ close IMG;
+ 1;
+}
+
+sub Chrono {
+ my $filename = shift; # filename
+ my $title = shift; # title
+ my $color_bg = shift; # background color
+ my $xmax = shift; # width
+ my $ymax = shift; # height
+
+ my $in = shift;
+ my $out = shift;
+ my $dates = shift;
+
+ my $legend_in = shift;
+ my $legend_out = shift;
+
+ my $color_in = shift;
+ my $color_out = shift;
+
+ my $unit = shift;
+
+ my $key;
+ my $x_min = 1E30;
+ my $x_max = 0;
+ my $y_min = 0;
+ my $y_max;
+ my $y_max_in = 0;
+ my $y_max_out = 0;
+
+ foreach $key (sort keys %$dates) {
+ $x_min = $key if $x_min > $key;
+ $x_max = $$dates{$key} if $x_max < $$dates{$key};
+ my $t = $$out{$key} / ($$dates{$key} - $key);
+ $y_max_out = $t if $y_max_out < $t;
+ $t = $$in{$key} / ($$dates{$key} - $key);
+ $y_max_in = $t if $y_max_in < $t;
+ }
+ $y_max = $y_max_out > $y_max_in ? $y_max_out : $y_max_in;
+ my $factor = 1;
+ if ($y_max < 1) {
+ $factor = 60;
+ if ($y_max < 4 / 60) {
+ $y_max = 4 / 60;
+ }
+ else {
+ $y_max = int ($y_max * $factor) + 1;
+ $y_max += (4 - ($y_max % 4)) % 4;
+ $y_max /= $factor;
+ }
+ }
+ else {
+ $y_max = int ($y_max) + 1;
+ $y_max += (4 - ($y_max % 4)) % 4;
+ }
+
+ $unit .= "/" . ($factor == 60 ? "min" : "sec");
+
+ # min range is 4 weeks.
+ my $delta = $x_max - $x_min;
+ $x_min = $x_max - 3024000 if $delta < 3024000;
+ # between 4 weeks and one year, range is a year.
+ $x_min = $x_max - 31536000 if ($delta < 31536000 && $delta > 3024000);
+ # max range is 13 months
+ $x_min = $x_max - 34128000 if $delta > 34128000;
+ my $image = new GD::Image ($xmax, $ymax);
+ my ($white, $black);
+ if (defined $output{'default'}{'graph_fg'}) {
+ my $t = $output{'default'}{'graph_fg'};
+ $t =~ s/^\"\s*\#(.*?)\s*\"$/$1/o;
+ $t =~ m/^[\da-fA-F]{6}$/o ||
+ die "Error in section 'default' section 'graph_fg'. Bad color.\n";
+ my @c = map { hex $_ } ($t =~ m/^(..)(..)(..)$/);
+ $black = $image->colorAllocate (@c);
+ }
+ else {
+ $black = $image->colorAllocate ( 0, 0, 0);
+ }
+ if (defined $output{'default'}{'graph_bg'}) {
+ my $t = $output{'default'}{'graph_bg'};
+ $t =~ s/^\"\s*\#(.*?)\s*\"$/$1/o;
+ $t =~ m/^[\da-fA-F]{6}$/o ||
+ die "Error in section 'default' section 'graph_bg'. Bad color.\n";
+ my @c = map { hex $_ } ($t =~ m/^(..)(..)(..)$/);
+ $white = $image->colorAllocate (@c);
+ }
+ else {
+ $white = $image->colorAllocate (255, 255, 255);
+ }
+ my $bg;
+ if (defined $color_bg) {
+ $color_bg =~ m/^\#[\da-fA-F]{6}$/o ||
+ die "Error in section 'index'. Bad color $color_bg.\n";
+ my @c = map { hex $_ } ($color_bg =~ m/^\#(..)(..)(..)$/);
+ $bg = $image->colorAllocate (@c);
+ }
+ else {
+ $bg = $image->colorAllocate (255, 255, 206);
+ }
+ my $col_in;
+ if (defined $color_in) {
+ $color_in =~ m/^\#[\da-fA-F]{6}$/o ||
+ die "Error in section 'index'. Bad color $color_in.\n";
+ my @c = map { hex $_ } ($color_in =~ m/^\#(..)(..)(..)$/);
+ $col_in = $image->colorAllocate (@c);
+ }
+ else {
+ $col_in = $image->colorAllocate ( 80, 159, 207);
+ }
+ my $col_out;
+ my @col_out = ( 0, 0, 255);
+ if (defined $color_out) {
+ $color_out =~ m/^\#[\da-fA-F]{6}$/o ||
+ die "Error in section 'index'. Bad color $color_out.\n";
+ my @c = map { hex $_ } ($color_out =~ m/^\#(..)(..)(..)$/);
+ $col_out = $image->colorAllocate (@c);
+ @col_out = @c;
+ }
+ else {
+ $col_out = $image->colorAllocate (@col_out);
+ }
+
+ my $white2 = $image->colorAllocate (255, 255, 255);
+ my $gray = $image->colorAllocate (192, 192, 192);
+ my $red = $image->colorAllocate (255, 0, 0);
+ my $coltxt = $black;
+
+ my $size = 22; # legend
+ # legend statistics
+ my ($max_in, $max_out) = (0, 0); # min
+ my ($min_in, $min_out) = (1E10, 1E10); # max
+ my ($t_in, $t_out) = (0, 0); # time
+ my ($s_in, $s_out) = (0, 0); # sum
+
+ $image->filledRectangle (0, 0, $xmax, $ymax, $gray);
+ $image->transparent ($gray) if $transparent;
+
+ my $FontWidth = gdSmallFont->width;
+ my $FontHeight = gdSmallFont->height;
+ $image->setStyle ($black, &GD::gdTransparent, &GD::gdTransparent);
+
+ my $marginl = 13 + $FontWidth * length (sprintf "%d", $y_max * $factor);
+ my $marginr = 15 + 4 * $FontWidth; # "100%"
+ my $margint = 2 * $FontHeight + gdMediumBoldFont->height;
+ my $marginb = 2 * $FontHeight + $size;
+ my $xratio = ($xmax - $marginl - $marginr) / ($x_max - $x_min);
+ my $yratio = ($ymax - $margint - $marginb) / ($y_max - $y_min);
+
+ my $frame = new GD::Polygon;
+ $frame->addPt(2, $margint - $FontHeight -3);
+ $frame->addPt($xmax - 2, $margint - $FontHeight -3);
+ $frame->addPt($xmax - 2, $ymax - 3);
+ $frame->addPt(2, $ymax - 3);
+ $image->filledPolygon($frame, $white2);
+ $image->polygon($frame, $black);
+
+ $image->filledRectangle ($marginl, $margint,
+ $xmax - $marginr, $ymax - $marginb, $bg);
+ my $brush = new GD::Image(1, 2);
+ my $b_col = $brush->colorAllocate(@col_out);
+ $brush->line(0, 0, 0, 1, $b_col);
+ $image->setBrush($brush);
+ my ($old_x, $old_y_in, $old_y_out);
+ foreach $key (sort keys %$dates) {
+ next if $key < $x_min;
+ my $delta = $$dates{$key} - $key;
+ $min_in = $$in{$key} / $delta if $min_in > $$in{$key} / $delta;
+ $max_in = $$in{$key} / $delta if $max_in < $$in{$key} / $delta;
+ $min_out = $$out{$key} / $delta if $min_out > $$out{$key} / $delta;
+ $max_out = $$out{$key} / $delta if $max_out < $$out{$key} / $delta;
+ $t_in += $delta;
+ $s_in += $$in{$key};
+ $s_out += $$out{$key};
+
+ my $tt_in = $$in{$key} / ($$dates{$key} - $key) * $yratio;
+ my $tt_out = $$out{$key} / ($$dates{$key} - $key) * $yratio;
+ my $new_x = $marginl + ($key - $x_min) * $xratio;
+ $image->filledRectangle ($marginl + ($key - $x_min) * $xratio,
+ $ymax - $marginb - $tt_in,
+ $marginl + ($$dates{$key} - $x_min) * $xratio,
+ $ymax - $marginb, $col_in);
+ if (defined $old_x) {
+ $old_x = $new_x if $old_x > $new_x;
+ my $poly = new GD::Polygon;
+ $poly->addPt($old_x, $old_y_in);
+ $poly->addPt($new_x, $ymax - $marginb - $tt_in);
+ $poly->addPt($new_x, $ymax - $marginb);
+ $poly->addPt($old_x, $ymax - $marginb);
+ $image->filledPolygon($poly, $col_in);
+ }
+ $image->line ($marginl + ($key - $x_min) * $xratio,
+ $ymax - $marginb - $tt_out,
+ $marginl + ($$dates{$key} - $x_min) * $xratio,
+ $ymax - $marginb - $tt_out, &GD::gdBrushed);
+ $image->line ($old_x, $old_y_out, $new_x,
+ $ymax - $marginb - $tt_out, $col_out) if defined $old_x;
+ $old_x = $marginl + ($$dates{$key} - $x_min) * $xratio;
+ $old_y_in = $ymax - $marginb - $tt_in;
+ $old_y_out = $ymax - $marginb - $tt_out;
+ }
+ $t_out = $t_in;
+
+ # main frame
+ $image->rectangle ($marginl, $margint,
+ $xmax - $marginr, $ymax - $marginb, $black);
+ # graduations
+ my $i;
+ foreach $i (0, 25, 50, 75, 100) {
+ my $t = $ymax - $margint - $marginb;
+ $image->line ($marginl, $ymax - $marginb - $i / 100 * $t,
+ $xmax - $marginr, $ymax - $marginb - $i / 100 * $t,
+ &GD::gdStyled);
+ $image->line ($xmax - $marginr, $ymax - $marginb - $i / 100 * $t,
+ $xmax - $marginr + 3, $ymax - $marginb - $i / 100 * $t,
+ $black);
+ $image->line ($marginl - 3, $ymax - $marginb - $i / 100 * $t,
+ $marginl, $ymax - $marginb - $i / 100 * $t,
+ $black);
+ $image->string (&GD::gdSmallFont, $xmax - $marginr + 8, - $FontHeight / 2 +
+ $ymax - $marginb - $i / 100 * $t, "$i%", $black);
+ my $s = sprintf "%d", $y_max * $i / 100 * $factor;
+ $image->string (&GD::gdSmallFont, $marginl - 5 - $FontWidth * length $s,
+ - $FontHeight / 2 +
+ $ymax - $marginb - $i / 100 * $t, $s, $black);
+ }
+ ##
+ my $w = 604800; # number of seconds in a week
+ my $y = 31536000; # number of seconds in a 365 days year
+ my $mm = 2592000; # number of seconds in a 30 days month
+ if ($x_max - $x_min <= 3024000) { # less than five weeks
+ # unit is a week
+ # 1/1/1990 is a monday. Use this as a basis.
+ my $d = 631152000; # number of seconds between 1/1/1970 and 1/1/1990
+ my $n = int ($x_min / $y);
+ my $t = $x_min - $n * $y - int (($n - 2) / 4) * 24 * 3600;
+ my $f = int ($t / $w);
+ $n = $d + int (($x_min - $d) / $w) * $w;
+ while ($n < $x_max) {
+ $t = $marginl + ($n - $x_min) * $xratio;
+ if ($n > $x_min) {
+ $image->line ($t, $margint, $t, $ymax - $marginb, &GD::gdStyled);
+ $image->line ($t, $ymax - $marginb, $t, $ymax - $marginb + 2, $black);
+ }
+ $image->string (&GD::gdSmallFont, $FontWidth * 7 / 2 + $t,
+ $ymax - $marginb + 4, (sprintf "Week %02d", $f), $black)
+ if ($n + $w / 2 > $x_min) && ($n + $w / 2 < $x_max);
+ $f++;
+ $n += $w;
+ $t = int ($n / $y);
+ $f = 0
+ if $n - $y * $t - int (($t - 2) / 4) * 24 * 3600 < $w && $f > 50;
+ }
+ $d = 86400; # 1 day
+ $n = int ($x_min / $y);
+ $t = $n * $y + int (($n - 2) / 4) * 24 * 3600;
+ $i = 0;
+ my $x;
+ while ($t < $x_max) {
+ $x = $marginl + ($t - $x_min) * $xratio;
+ $image->line ($x, $margint, $x, $ymax - $marginb + 2, $red)
+ if $t > $x_min;
+ $t += $mm;
+ $t += $d if $i == 0 || $i == 2 || $i == 4 ||
+ $i == 6 || $i == 7 || $i == 9 || $i == 11; # 31 days months
+ if ($i == 1) { # february ?
+ $t -= 2 * $d;
+ $t += $d unless (1970 + int ($t / $y)) % 4;
+ }
+ $i++;
+ $i = 0 if $i == 12; # Happy New Year !!
+ }
+ }
+ else {
+ # unit is a month
+ my $n = int ($x_min / $y);
+ my $t = $n * $y + int (($n - 2) / 4) * 24 * 3600;
+ my @m = ("Jan", "Feb", "Mar", "Apr", "May", "Jun",
+ "Jul", "Aug", "Sep", "Oct", "Nov", "Dec");
+ my $d = 86400; # 1 day
+ my $i = 0;
+ my $x;
+ while ($t < $x_max) {
+ $x = $marginl + ($t - $x_min) * $xratio;
+ if ($t > $x_min) {
+ $image->line ($x, $margint, $x, $ymax - $marginb, &GD::gdStyled);
+ $image->line ($x, $ymax - $marginb, $x,
+ $ymax - $marginb + 2, $black);
+ $image->line ($x, $margint, $x, $ymax - $marginb, $red) unless $i;
+ }
+ $image->string (&GD::gdSmallFont,
+ $mm * $xratio / 2 - $FontWidth * 3 / 2 +
+ $x, $ymax - $marginb + 4, (sprintf "%s", $m[$i]),
+ $black)
+ if ($t + 2 * $w > $x_min) && ($x_max > 2 * $w + $t);
+ $t += $mm;
+ $t += $d if ($i == 0 || $i == 2 || $i == 4 ||
+ $i == 6 || $i == 7 || $i == 9 || $i == 11); # 31 days months
+ if ($i == 1) { # february ?
+ $t -= 2 * $d;
+ $t += $d unless (1970 + int ($t / $y)) % 4;
+ }
+ $i++;
+ $i = 0 if $i == 12; # Happy New Year !!
+ }
+ }
+
+ # Add the little red arrow
+ my $poly = new GD::Polygon;
+ $poly->addPt($xmax - $marginr - 2, $ymax - $marginb - 3);
+ $poly->addPt($xmax - $marginr + 4, $ymax - $marginb);
+ $poly->addPt($xmax - $marginr - 2, $ymax - $marginb + 3);
+ $image->filledPolygon($poly, $red);
+
+ # Title
+ $image->string (&GD::gdMediumBoldFont,
+ $xmax / 2 - $FontWidth * length ($title) / 2, 4,
+ $title, $black);
+
+ # Legend
+ my $y_in = $ymax - $size - $FontHeight + 5;
+ $image->string (&GD::gdSmallFont, $marginl, $y_in, $legend_in, $col_in);
+ $image->string (&GD::gdSmallFont, $xmax / 4, $y_in,
+ (sprintf "Min: %5.1f $unit", $min_in * $factor), $black);
+ $image->string (&GD::gdSmallFont, $xmax / 2, $y_in,
+ (sprintf "Avg: %5.1f $unit", $s_in / $t_in * $factor), $black);
+ $image->string (&GD::gdSmallFont, 3 * $xmax / 4, $y_in,
+ (sprintf "Max: %5.1f $unit", $max_in * $factor), $black);
+
+ my $y_out = $ymax - $size + 5;
+ $image->string (&GD::gdSmallFont, $marginl, $y_out, $legend_out, $col_out);
+ $image->string (&GD::gdSmallFont, $xmax / 4, $y_out,
+ (sprintf "Min: %5.1f $unit", $min_out * $factor), $black);
+ $image->string (&GD::gdSmallFont, $xmax / 2, $y_out,
+ (sprintf "Avg: %5.1f $unit", $s_out / $t_out * $factor), $black);
+ $image->string (&GD::gdSmallFont, 3 * $xmax / 4, $y_out,
+ (sprintf "Max: %5.1f $unit", $max_out * $factor), $black);
+
+ open (IMG, "> $filename") || die "Error: Can't open \"$filename\": $!\n";
+ if ($GD_FORMAT eq 'png') {
+ print IMG $image->png;
+ }
+ else {
+ print IMG $image->gif;
+ }
+ close IMG;
+ return $ymax;
+}
+
+sub Write_all_results {
+ my $HTML_output = shift;
+ my $h = shift;
+ my $k;
+
+ my $title = $$h{'default'}{'title'} ?
+ $$h{'default'}{'title'} : "Daily Usenet report";
+ $title =~ s/^\"\s*(.*?)\s*\"$/$1/o;
+ $title =~ s/\\\"/\"/go;
+ my $Title = $title;
+ $Title =~ s/<.*?>//go;
+ {
+ my $Title = $Title;
+ $Title =~ s/\&/&/go;
+ $Title =~ s/\</</go;
+ $Title =~ s/\>/>/go;
+ print "$Title from $first_date to $last_date\n\n";
+ }
+
+ if ($HTML) {
+ my $body = defined $output{'default'}{'html_body'} ?
+ $output{'default'}{'html_body'} : '';
+ $body =~ s/^\"\s*(.*?)\s*\"$/ $1/o;
+ $body =~ s/\\\"/\"/go;
+ open (HTML, "> $HTML_output") || die "Error: cant open $HTML_output\n";
+
+ print HTML "<!DOCTYPE HTML PUBLIC \"-//W3C//DTD HTML 4.01 Transitional//EN\" \"http://www.w3.org/TR/html4/loose.dtd\">\n" .
+ "<HTML>\n<HEAD>\n<TITLE>$Title: $first_date</TITLE>\n" .
+ "<!-- innreport $version -->\n</HEAD>\n<BODY $body>\n" .
+ "$HTML_header\n<CENTER><H1>$title</H1>\n" .
+ "<H3>$first_date -- $last_date</H3>\n</CENTER>\n<P><HR><P>\n";
+
+ # Index
+ print HTML "<UL>\n";
+ foreach $k (@{$$h{'_order_'}}) {
+ next if $k =~ m/^(default|index)$/;
+ my ($data) = $$h{$k}{'data'} =~ m/^\"\s*(.*?)\s*\"$/o;
+ $data =~ s/^\%/\%$CLASS\:\:/ unless $data eq '%prog_type';
+ my %data;
+ { local $^W = 0; no strict; %data = eval $data }
+ my ($string) = $$h{$k}{'title'} =~ m/^\"\s*(.*?)\s*\"$/o;
+ $string =~ s/\s*:$//o;
+ my $want = 1;
+
+ ($want) = $$h{$k}{'skip'} =~ m/^\"?\s*(.*?)\s*\"?$/o
+ if defined $$h{$k}{'skip'};
+ $want = $want eq 'true' ? 0 : 1;
+ print HTML "<LI><A HREF=\"#$k\">$string</A>\n" if %data && $want;
+ }
+ print HTML "</UL><P><HR><P>\n";
+ }
+ if (@unrecognize && $WANT_UNKNOWN) {
+ my $mm = $#unrecognize;
+ print HTML "<A NAME=\"unrecognize\">" if $HTML && $WANT_HTML_UNKNOWN;
+ print "Unknown entries from news log file:\n";
+ print HTML "<STRONG>Unknown entries from news log file:</STRONG></A><P>\n"
+ if $HTML && $WANT_HTML_UNKNOWN;
+ $mm = $MAX_UNRECOGNIZED - 1
+ if $MAX_UNRECOGNIZED > 0 && $mm > $MAX_UNRECOGNIZED - 1;
+ if ($mm < $unrecognize_max && $unrecognize_max > 0) {
+ printf HTML "First %d / $unrecognize_max lines (%3.1f%%)<BR>\n", $mm + 1,
+ ($mm + 1) / $unrecognize_max * 100 if $HTML && $WANT_HTML_UNKNOWN;
+ printf "First %d / $unrecognize_max lines (%3.1f%%)\n", $mm + 1,
+ ($mm + 1) / $unrecognize_max * 100;
+ }
+
+ my $l;
+ for $l (0 .. $mm) {
+ chomp $unrecognize[$l]; # sometimes, the last line need a CR
+ print "$unrecognize[$l]\n"; # so, we always add one
+ if ($HTML && $WANT_HTML_UNKNOWN) {
+ $unrecognize[$l] =~ s/&/\&/g;
+ $unrecognize[$l] =~ s/</\</g;
+ $unrecognize[$l] =~ s/>/\>/g;
+ print HTML "$unrecognize[$l]<BR>\n";
+ }
+ }
+ print "\n";
+ print HTML "<P><HR><P>\n" if $HTML && $WANT_HTML_UNKNOWN;
+ }
+
+ close HTML if $HTML;
+ foreach $k (@{$$h{'_order_'}}) {
+ next if $k =~ m/^(default|index)$/;
+ &Write_Results($HTML_output, $k, $h);
+ }
+ if ($HTML) {
+ open (HTML, ">> $HTML_output") || die "Error: cant open $HTML_output\n";
+ print HTML <<EOT;
+innreport $version (c) 1996-1999 by Fabien Tassin
+<<A HREF="mailto:fta\@sofaraway.org">fta\@sofaraway.org</A>>.
+EOT
+ if (defined $$h{'default'}{'footer'}) {
+ my ($t) = $$h{'default'}{'footer'} =~ m/^\"\s*(.*?)\s*\"$/o;
+ $t =~ s/\\\"/\"/go;
+ print HTML "<BR>" . $t;
+ }
+ print HTML "\n$HTML_footer";
+ printf HTML "\n<!-- Running time: %s -->", second2time(time - $start_time);
+ print HTML "\n</BODY>\n</HTML>\n";
+ close HTML;
+ }
+}
+
+sub Write_Results {
+ my $HTML_output = shift;
+ my $report = shift;
+ my $data = shift;
+ my %output = %$data;
+ return 0 unless defined $output{$report}; # no data to write
+ return 0 if defined $output{$report}{'skip'} &&
+ $output{$report}{'skip'} =~ m/^true$/io;
+ my ($TEXT, $HTML, $DOUBLE);
+
+ # Need a text report ?
+ $TEXT = defined $output{$report}{'text'} ? $output{$report}{'text'} :
+ (defined $output{'default'}{'text'} ? $output{'default'}{'text'} : '');
+ die "Error in config file. Field 'text' is mandatory.\n" unless $TEXT;
+ $TEXT = ($TEXT =~ m/^true$/io) ? 1 : 0;
+
+ # Need an html report ?
+ if ($HTML_output) {
+ $HTML = defined $output{$report}{'html'} ? $output{$report}{'html'} :
+ (defined $output{'default'}{'html'} ? $output{'default'}{'html'} : '');
+ die "Error in config file. Field 'html' is mandatory.\n" unless $HTML;
+ $HTML = ($HTML =~ m/^true$/io) ? 1 : 0;
+ }
+ # Double table ?
+ $DOUBLE = defined $output{$report}{'double'} ?
+ $output{$report}{'double'} : 0;
+ $DOUBLE = ($DOUBLE =~ m/^true$/io) ? 1 : 0;
+
+ # Want to truncate the report ?
+ my $TOP = defined $output{$report}{'top'} ? $output{$report}{'top'} : -1;
+ my $TOP_HTML = defined $output{$report}{'top_html'} ?
+ $output{$report}{'top_html'} : $TOP;
+ my $TOP_TEXT = defined $output{$report}{'top_text'} ?
+ $output{$report}{'top_text'} : $TOP;
+
+ my (%h, %d, $h);
+ {
+ my $t = $output{$report}{'data'} ||
+ die "Error in section $report. Need a 'data' field.\n";
+ $t =~ s/^\"\s*(.*?)\s*\"$/$1/o;
+ $t =~ s/^\%/\%$CLASS\:\:/ unless $t eq '%prog_type';
+ %d = eval $t;
+ return unless %d; # nothing to report. exit.
+ return unless keys (%d); # nothing to report. exit.
+ }
+ {
+ my $t = defined $output{$report}{'sort'} ? $output{$report}{'sort'} :
+ "\$a cmp \$b";
+ $t =~ s/\n/ /smog;
+ $t =~ s/^\"\s*(.*?)\s*\"$/$1/o;
+ $t =~ s/([\$\%\@])/$1${CLASS}\:\:/go;
+ $t =~ s/([\$\%\@])${CLASS}\:\:(prog_(?:size|type)|key|num)/$1$2/go;
+ $t =~ s/\{\$${CLASS}\:\:(a|b)\}/\{\$$1\}/go;
+ $t =~ s/\$${CLASS}\:\:(a|b)/\$$1/go;
+ $h = $t;
+ }
+
+ if ($HTML) {
+ open (HTML, ">> $HTML_output") || die "Error: cant open $HTML_output\n";
+ }
+ print "\n" if $TEXT;
+ my ($key, $key1, $key2);
+ if (defined $output{$report}{'title'}) {
+ my $t = $output{$report}{'title'};
+ $t =~ s/^\"\s*(.*?)\s*\"$/$1/o;
+ if ($HTML) {
+ print HTML "<A NAME=\"$report\">";
+ my $html = $t;
+ $html =~ s/(:?)$/ [Top $TOP_HTML]$1/o if $TOP_HTML > 0;
+ $html =~ s|^(.*)$|<STRONG>$1</STRONG>|;
+ print HTML "$html</A>\n<P>\n<CENTER>\n<TABLE BORDER=\"1\">\n";
+ }
+ $t =~ s/(:?)$/ [Top $TOP_TEXT]$1/o if $TOP_TEXT > 0;
+ print "$t\n" if $TEXT;
+ }
+ my $numbering = 0;
+ $numbering = 1 if defined $output{$report}{'numbering'} &&
+ $output{$report}{'numbering'} =~ m/^true$/o;
+ my $i;
+ my $s = '';
+ my $html = '';
+ my $first = 0;
+
+ foreach $i (@{$output{$report}{'column'}}) {
+ my ($v1, $v2);
+
+ my $wtext = defined $$i{'text'} ? $$i{'text'} : 1;
+ $wtext = $wtext =~ m/^(1|true)$/io ? 1 : 0;
+ my $whtml = defined $$i{'html'} ? $$i{'html'} : 1;
+ $whtml = $whtml =~ m/^(1|true)$/io ? 1 : 0;
+
+ $v1 = defined ($$i{'format_name'}) ? $$i{'format_name'} :
+ (defined ($$i{'format'}) ? $$i{'format'} : "%s");
+ $v1 =~ s/^\"\s*(.*?)\s*\"$/$1/o;
+ $v2 = $$i{'name'};
+ $v2 =~ s/^\"\s*(.*?)\s*\"$/$1/o;
+ $s .= sprintf $v1 . " ", $v2 if $wtext && !($DOUBLE && $first == 1);
+ if ($HTML && $whtml) {
+ my $v1 = $v1;
+ $v1 =~ s/\%-?(?:\d+(?:\.\d+)?)?(\w)/\%$1/g;
+ my $temp = $first ? "CENTER" : "LEFT";
+ $temp .= "\" COLSPAN=\"2" if $numbering && !$first;
+ $html .= sprintf "<TH ALIGN=\"$temp\">$v1</TH>", $v2;
+ }
+ $first++;
+ }
+ $s =~ s/\s*$//;
+ print "$s\n" if $TEXT;
+ $s = '';
+ if ($HTML) {
+ print HTML "<TR>$html</TR>\n<TR><TD></TD></TR>\n";
+ $html = '';
+ }
+ my $num = 0;
+ my $done;
+ if ($DOUBLE) {
+ my $num_d = 0;
+ foreach $key1 (sort keys (%d)) {
+ $done = 0;
+ $num = 0;
+ $num_d++;
+ $s = '';
+ $html = '';
+ my @res;
+ foreach $key2 (sort {$d{$key1}{$b} <=> $d{$key1}{$a}}
+ keys (%{$d{$key1}})) {
+ my $first = 0;
+ $num++;
+ foreach $i (@{$output{$report}{'column'}}) {
+ my ($v1, $v2, $p);
+
+ my $wtext = defined $$i{'text'} ? $$i{'text'} : 1;
+ $wtext = $wtext =~ m/^(1|true)$/io ? 1 : 0;
+ my $whtml = defined $$i{'html'} ? $$i{'html'} : 1;
+ $whtml = $whtml =~ m/^(1|true)$/io ? 1 : 0;
+
+ # is it the primary key ?
+ $p = 0;
+ $p = 1 if defined $$i{'primary'} && $$i{'primary'} =~ m/true/;
+
+ # format
+ $v1 = defined ($$i{'format'}) ? $$i{'format'} : "%s";
+ $v1 =~ s/^\"\s*(.*?)\s*\"$/$1/o;
+
+ # value
+ $v2 = $$i{'value'};
+ $v2 =~ s/^\"\s*(.*?)\s*\"$/$1/o;
+ my $r ='';
+ if ($v2) {
+ $r = &EvalExpr ($v2, $key2, $num, $key1);
+ die "Error in section $report column $$i{'name'}. " .
+ "Invalid 'value' value.\n" unless defined $r;
+ }
+ $res[$first] += $r if $v1 =~ m/\%-?(?:\d+(?:\.\d+)?)?d/o;
+ if ($p) {
+ $s .= sprintf $v1. "\n", $r unless $done || !$wtext;
+ if ($HTML && $whtml) {
+ if ($done) {
+ $html .= "<TD></TD>";
+ }
+ else {
+ $v1 =~ s/\%-?(?:\d+(?:\.\d+)?)?s/\%s/g;
+ $html .= $numbering ? "<TH ALIGN=\"CENTER\">$num_d</TH>" : '';
+ # unless $first;
+ $html .= sprintf "<TD ALIGN=\"LEFT\">$v1</TD></TR>\n", $r;
+ $html .= "<TR><TD></TD>";
+ }
+ }
+ }
+ else {
+ if ($wtext) {
+ $s .= " " if $first == 1;
+ $s .= sprintf $v1 . " ", $r;
+ }
+ if ($HTML && $whtml) {
+ $html .= $numbering ? "<TD></TD>" : '' if $first == 1;
+ $v1 =~ s/\%-?(?:\d+(?:\.\d+)?)?s/\%s/g;
+ my $temp = $first > 1 ? "RIGHT" : "LEFT";
+ $html .= sprintf "<TD ALIGN=\"$temp\">$v1</TD>", $r;
+ }
+ }
+ $done = 1 if $p;
+ $first++;
+ }
+ $s =~ s/\s*$//;
+ $s =~ s/\\n/\n/g;
+ print "$s\n" if $TEXT && ($num <= $TOP_TEXT || $TOP_TEXT == -1);
+ if ($HTML && ($num <= $TOP_HTML || $TOP_HTML == -1)) {
+ $html =~ s/\\n//g;
+ print HTML "<TR>$html</TR>\n";
+ }
+ $s = '';
+ $html = '';
+ }
+ $first = 0;
+ $s = '';
+ $html = '';
+ if ($TOP_TEXT != -1 && $TOP_HTML != -1) {
+ foreach $i (@{$output{$report}{'column'}}) {
+ if (defined $$i{'primary'} && $$i{'primary'} =~ m/true/o) {
+ $first++;
+ $s .= ' ';
+ $html .= "<TD></TD>" if $HTML;
+ $html .= "<TD></TD>" if $HTML && $numbering;
+ next;
+ }
+ my ($v1, $v2);
+ $v1 = defined ($$i{'format_total'}) ? $$i{'format_total'} :
+ (defined ($$i{'format'}) ? $$i{'format'} : "%s");
+ $v1 =~ s/^\"\s*(.*?)\s*\"$/$1/o;
+ my $r = $first == 1 ? $num : $res[$first];
+ $s .= sprintf $v1 . " ", $r;
+ if ($HTML) {
+ my $temp = $first > 1 ? "RIGHT" : "LEFT";
+ $v1 =~ s/\%-?(?:\d+(?:\.\d+)?)?s/\%s/g;
+ $v1 =~ s|(.*)|<STRONG>$1</STRONG>|o unless $first > 1;
+ $html .= sprintf "<TD ALIGN=\"$temp\">$v1</TD>", $r;
+ }
+ $first++;
+ }
+ $s =~ s/\s*$//;
+ $s =~ s/\\n//g;
+ print "$s\n" if $TEXT;
+ print HTML "<TR>$html</TR>\n" if $HTML;
+ }
+ }
+ print "\n" if $TEXT;
+ print HTML "<TR><TD></TD></TR>\n" if $HTML;
+ $first = 0;
+ $num = $num_d;
+ $s = '';
+ $html = '';
+ foreach $i (@{$output{$report}{'column'}}) {
+ my $wtext = defined $$i{'text'} ? $$i{'text'} : 1;
+ $wtext = $wtext =~ m/^(1|true)$/io ? 1 : 0;
+ my $whtml = defined $$i{'html'} ? $$i{'html'} : 1;
+ $whtml = $whtml =~ m/^(1|true)$/io ? 1 : 0;
+
+ my ($v1, $v2);
+ $v1 = defined $$i{'format_total'} ? $$i{'format_total'} :
+ (defined $$i{'format'} ? $$i{'format'} : "%s");
+ $v1 =~ s/^\"\s*(.*?)\s*\"$/$1/o;
+ $v2 = $$i{'total'} ||
+ die "Error in section $report column $$i{'name'}. " .
+ "Need a 'total' field.\n";
+ $v2 =~ s/^\"\s*(.*?)\s*\"$/$1/o;
+ my $r = '';
+ if ($v2) {
+ $r = &EvalExpr ($v2, $key2, $num, 1);
+ die "Error in section $report column $$i{'name'}. " .
+ "Invalid 'total' value.\n" unless defined $r;
+ }
+ $s .= sprintf $v1 . " ", $r if $wtext && $first != 1;
+ if ($HTML && $whtml) {
+ my $temp = $first ? "RIGHT" : "LEFT";
+ $temp .= "\" COLSPAN=\"2" if $numbering && !$first;
+ $v1 =~ s/\%-?(?:\d+(?:\.\d+)?)?s/\%s/g;
+ $v1 =~ s|(.*)|<STRONG>$1</STRONG>|o unless $first;
+ $html .= $first == 1 ? "<TD></TD>" :
+ sprintf "<TD ALIGN=\"$temp\">$v1</TD>", $r;
+ }
+ $first++;
+ }
+ $s =~ s/\s*$//;
+ $s =~ s/\\n//g;
+ print "$s\n" if $TEXT;
+ print HTML "<TR>$html</TR>\n</TABLE>\n</CENTER>\n<P>\n<HR>\n" if $HTML;
+ }
+ else {
+ # foreach $key (sort { local $^W = 0; no strict; eval $h } (keys (%d)))
+ foreach $key ((eval "sort {local \$^W = 0; no strict; $h} (keys (%d))")) {
+ next unless defined $key;
+ next unless defined $d{$key}; # to avoid problems after some undef()
+ $num++;
+ next unless $num <= $TOP_HTML || $TOP_HTML == -1 ||
+ $num <= $TOP_TEXT || $TOP_TEXT == -1;
+ my $first = 0;
+ foreach $i (@{$output{$report}{'column'}}) {
+ my $wtext = defined $$i{'text'} ? $$i{'text'} : 1;
+ $wtext = $wtext =~ m/^(1|true)$/io ? 1 : 0;
+ my $whtml = defined $$i{'html'} ? $$i{'html'} : 1;
+ $whtml = $whtml =~ m/^(1|true)$/io ? 1 : 0;
+
+ my ($v1, $v2);
+ $v1 = defined ($$i{'format'}) ? $$i{'format'} : "%s";
+ $v1 =~ s/^\"\s*(.*?)\s*\"$/$1/o;
+ $v2 = $$i{'value'};
+ $v2 =~ s/^\"\s*(.*?)\s*\"$/$1/o;
+ my $r ='';
+ if ($v2) {
+ $r = &EvalExpr ($v2, $key, $num);
+ die "Error in section $report column $$i{'name'}. " .
+ "Invalid 'value' value.\n" unless defined $r;
+ }
+ $s .= sprintf $v1 . " ", $r
+ if $wtext && (($num <= $TOP_TEXT) || ($TOP_TEXT == -1));
+ if ($HTML && $whtml && ($num <= $TOP_HTML || $TOP_HTML == -1)) {
+ $v1 =~ s/\%-?(?:\d+(?:\.\d+)?)?s/\%s/g;
+ $html .= "<TH ALIGN=\"CENTER\">$num</TH>" if $numbering && !$first;
+ my $temp = $first ? "RIGHT" : "LEFT";
+ $html .= sprintf "<TD ALIGN=\"$temp\">$v1</TD>", $r;
+ }
+ $first++;
+ }
+ $s =~ s/\s*$//;
+ print "$s\n" if $TEXT && ($num <= $TOP_TEXT || $TOP_TEXT == -1);
+ $s = '';
+ if ($HTML && ($num <= $TOP_HTML || $TOP_HTML == -1)) {
+ print HTML "<TR>$html</TR>\n";
+ $html = '';
+ }
+ }
+ print "\n" if $TEXT;
+ print HTML "<TR><TD></TD></TR>\n" if $HTML;
+ $first = 0;
+ foreach $i (@{$output{$report}{'column'}}) {
+ my $wtext = defined $$i{'text'} ? $$i{'text'} : 1;
+ $wtext = $wtext =~ m/^(1|true)$/io ? 1 : 0;
+ my $whtml = defined $$i{'html'} ? $$i{'html'} : 1;
+ $whtml = $whtml =~ m/^(1|true)$/io ? 1 : 0;
+
+ my ($v1, $v2);
+ $v1 = defined ($$i{'format_total'}) ? $$i{'format_total'} :
+ (defined ($$i{'format'}) ? $$i{'format'} : "%s");
+ $v1 =~ s/^\"\s*(.*?)\s*\"$/$1/o;
+ $v2 = $$i{'total'} ||
+ die "Error in section $report column $$i{'name'}. " .
+ "Need a 'total' field.\n";
+ $v2 =~ s/^\"\s*(.*?)\s*\"$/$1/o;
+ my $r = '';
+ if ($v2) {
+ $r = &EvalExpr ($v2, $key, $num);
+ die "Error in section $report column $$i{'name'}. " .
+ "Invalid 'total' value.\n" unless defined $r;
+ }
+ $s .= sprintf $v1 . " ", $r if $wtext;
+ if ($HTML && $whtml) {
+ $v1 =~ s/\%-?(?:\d+(?:\.\d+)?)?s/\%s/g;
+ my $temp = $first ? "RIGHT" : "LEFT";
+ $temp .= "\" COLSPAN=\"2" if $numbering && !$first;
+ $v1 =~ s|(.*)|<STRONG>$1</STRONG>|o unless $first;
+ $html .= sprintf "<TD ALIGN=\"$temp\">$v1</TD>", $r;
+ }
+ $first++;
+ }
+ $s =~ s/\s*$//;
+ print "$s\n" if $TEXT;
+ if ($HTML) {
+ print HTML "<TR>$html</TR>\n";
+ print HTML "</TABLE>\n</CENTER><P>\n";
+
+ my $i = 0;
+ while ($GRAPH && defined ${${$output{$report}{'graph'}}[$i]}{'type'}) {
+ my $type = ${${$output{$report}{'graph'}}[$i]}{'type'};
+ my ($title) = ${${$output{$report}{'graph'}}[$i]}{'title'} =~
+ m/^\"\s*(.*?)\s*\"$/o;
+ if ($type eq 'histo3d') {
+ my (@values, @colors, @labels);
+ my $num = 0;
+ my $j;
+ foreach $j (@{${${$output{$report}{'graph'}}[$i]}{'data'}}) {
+ $num++;
+ my ($h) = $$j{'value'} =~ m/^\"\s*(.*?)\s*\"$/o;
+ my %hh;
+ $h =~ s/^\%/\%$CLASS\:\:/ unless $h eq '%prog_type';
+ { local $^W = 0; no strict; %hh = eval $h }
+ push @values, \%hh;
+ my ($t) = $$j{'name'} =~ m/^\"\s*(.*?)\s*\"$/o;
+ push @labels, $t;
+ $t = $$j{'color'} ||
+ die "Error in section $report section 'graph'. " .
+ "No color specified for 'value' $$j{'value'}.\n";
+ $t =~ s/^\"\s*\#(.*?)\s*\"$/$1/o;
+ $t =~ m/^[\da-fA-F]{6}$/o ||
+ die "Error in section $report section 'graph'. " .
+ "Bad color for 'value' $$j{'value'}.\n";
+ my @c = map { hex $_ } ($t =~ m/^(..)(..)(..)$/);
+ push @colors, \@c;
+ }
+ $suffix = '' unless defined $suffix;
+ my $s = ($i ? $i : '') . $suffix;
+ print HTML "<CENTER><IMG ALT=\"$title\" ";
+ close HTML;
+ my $y = &Graph3d ("$IMG_dir/$report$s.$GD_FORMAT",
+ $title, $xmax, $num, @values, \@colors, \@labels);
+ open (HTML, ">> $HTML_output") ||
+ die "Error: cant open $HTML_output\n";
+ print HTML "WIDTH=\"$xmax\" HEIGHT=\"$y\" ";
+ print HTML "SRC=\"$IMG_pth$report$s.$GD_FORMAT\"></CENTER>\n";
+ }
+ elsif ($type eq 'histo') {
+ my (%values, %labels);
+ my $factor =
+ ${${${${$output{$report}{'graph'}}[$i]}{'data'}}[1]}{'factor'}
+ || die "Error in section $report section 'graph'. " .
+ "No factor specified for 'value' " .
+ ${${${${$output{$report}{'graph'}}[$i]}{'data'}}[1]}{'name'} .
+ ".\n";
+ $factor =~ s/^\"\s*(.*?)\s*\"$/$1/o;
+ my $labelx =
+ ${${${${$output{$report}{'graph'}}[$i]}{'data'}}[0]}{'name'}
+ || die "Error in section $report section 'graph'. " .
+ "No name specified for value.\n";
+ $labelx =~ s/^\"\s*(.*?)\s*\"$/$1/o;
+ my $labely =
+ ${${${${$output{$report}{'graph'}}[$i]}{'data'}}[1]}{'name'}
+ || die "Error in section $report section 'graph'. " .
+ "No name specified for value.\n";
+ $labely =~ s/^\"\s*(.*?)\s*\"$/$1/o;
+ my $t = ${${${${$output{$report}{'graph'}}[$i]}{'data'}}[0]}{'value'}
+ || die "Error in section $report section 'graph'. " .
+ "No 'value' specified for " .
+ ${${${${$output{$report}{'graph'}}[$i]}{'data'}}[0]}{'name'} .
+ ".\n";
+ $t =~ s/^\"\s*(.*?)\s*\"$/$1/o;
+ $t =~ s/^\%/\%$CLASS\:\:/ unless $t eq '%prog_type';
+ { local $^W = 0; no strict; %labels = eval $t }
+
+ $t = ${${${${$output{$report}{'graph'}}[$i]}{'data'}}[1]}{'value'} ||
+ die "Error in section $report section 'graph'. " .
+ "No 'value' specified for " .
+ ${${${${$output{$report}{'graph'}}[$i]}{'data'}}[1]}{'name'} .
+ ".\n";
+ $t =~ s/^\"\s*(.*?)\s*\"$/$1/o;
+ $t =~ s/^\%/\%$CLASS\:\:/ unless $t eq '%prog_type';
+ { local $^W = 0; no strict; %values = eval $t }
+ my $s = ($i ? $i : '') . $suffix;
+ {
+ my $r;
+ close HTML;
+ $r = &Histo ("$IMG_dir/$report$s.$GD_FORMAT", $title, $xmax,
+ $factor, $labelx, $labely, \%values, \%labels);
+ open (HTML, ">> $HTML_output") ||
+ die "Error: cant open $HTML_output\n";
+ print HTML "<CENTER><IMG ALT=\"$title\" WIDTH=\"$xmax\" " .
+ "SRC=\"$IMG_pth$report$s.$GD_FORMAT\"></CENTER>\n" if $r;
+ }
+ }
+ elsif ($type eq 'piechart') {
+ print "Sorry, graph type 'piechart' not supported yet..\n";
+ }
+ else {
+ die "Error in section $report section 'graph'. " .
+ "Invalid 'type' value.\n"
+ }
+ $i++;
+ print HTML "<P>\n";
+ }
+ print HTML "\n<HR>\n";
+ }
+ }
+ close HTML if $HTML;
+}
+
+sub EvalExpr {
+ my $v = shift;
+ my ($key, $num, $key1) = @_;
+ my $key2;
+
+ $v =~ s/\n/ /smog;
+ $v =~ s/^\"(.*?)\"$/$1/o;
+ if ($key1) {
+ $key2 = $key;
+ $v =~ s/([^a-zA-Z_\-]?)total\s*\(\s*%/$1&ComputeTotalDouble\(\\%/og;
+ }
+ else {
+ $v =~ s/([^a-zA-Z_\-]?)total\s*\(\s*%/$1&ComputeTotal\(\\%/og;
+ # $v =~ s/([^a-zA-Z_\-]?)total\s*\(\s*%([^\)]*)\)/$1&ComputeTotal\("$2"\)/og;
+ }
+ $v =~ s/([^a-zA-Z_\-]?)bytes\s*\(\s*/$1&NiceByte\(/og;
+ $v =~ s/([^a-zA-Z_\-]?)time\s*\(\s*/$1&second2time\(/og;
+ $v =~ s/([^a-zA-Z_\-]?)time_ms\s*\(\s*/$1&ms2time\(/og;
+ # $v =~ s/([\$\%\@])/$1${CLASS}\:\:/og;
+ $v =~ s/([\$\%\@])([^{\s\d])/$1${CLASS}\:\:$2/og;
+ $v =~ s/([\$\%\@])${CLASS}\:\:(prog_(?:size|type)|key|sec_glob|num)/$1$2/og;
+ my $r;
+ # eval { local $^W = 0; no strict; ($r) = eval $v; };
+ eval " local \$^W = 0; no strict; (\$r) = $v; ";
+ $r = 0 unless defined $r;
+ $r;
+}
+
+sub NiceByte {
+ my $size = shift;
+ my $t;
+
+ $size = 0 unless defined $size;
+ $t = $size / 1024 / 1024 / 1024 > 1 ?
+ sprintf "%.1f GB", $size / 1024 / 1024 / 1024 :
+ ($size / 1024 / 1024 > 1 ? sprintf "%.1f MB", $size / 1024 / 1024 :
+ sprintf "%.1f KB", $size / 1024);
+ return $t;
+}
+
+sub kb2i {
+ my $s = shift;
+ my ($i, $u) = $s =~ m/^(\S+) (\S+)$/;
+ $i *= 1024 * 8 if $u =~ m/MB/o;
+ $i *= 1024 * 1024 * 8 if $u =~ m/GB/o;
+ return $i;
+}
+
+sub Decode_Config_File {
+ my $file = shift;
+ my ($line, $section);
+ my $linenum = 0;
+ my $info;
+ my @list;
+ open (FILE, "$file") || die "Can\'t open config file \"$file\". Abort.\n";
+ while (defined ($line = <FILE>)) {
+ $linenum++;
+ last if eof (FILE);
+ ($info, $linenum, $line) = &read_conf ($linenum, $line, \*FILE);
+ die "Error in $file line $linenum: must be 'section' instead of '$info'\n"
+ unless ($info eq 'section');
+ ($info, $linenum, $line) = &read_conf ($linenum, $line, \*FILE);
+ die "Error in $file line $linenum: invalid section name '$info'\n"
+ unless $info =~ /^\w+$/;
+ print "section $info {\n" if $DEBUG;
+ $section = $info;
+ ($info, $linenum, $line) = &read_conf ($linenum, $line, \*FILE);
+ die "Error in $file line $linenum: must be a '{' instead of '$info'\n"
+ unless ($info eq '{');
+ ($info, $linenum, $line) = &read_conf ($linenum, $line, \*FILE);
+ push @list, $section;
+ while ($info ne '}') { # it is a block
+ last if eof (FILE);
+ my $keyword = $info;
+ ($info, $linenum, $line) = &read_conf ($linenum, $line, \*FILE);
+ my $value = $info;
+ if ($info eq '{') { # it is a sub-block
+ my @a;
+ $output{$section}{$keyword} = \@a unless $output{$section}{$keyword};
+ my %hash;
+ print "\t$keyword {\n" if $DEBUG;
+ ($info, $linenum, $line) = &read_conf ($linenum, $line, \*FILE);
+ my @sublist; # to store the "data" blocks
+
+ while ($info ne '}') {
+ last if eof (FILE);
+ my $subkeyword = $info;
+ ($info, $linenum, $line) = &read_conf ($linenum, $line, \*FILE);
+ my $subvalue = $info;
+ if ($info eq '{') {
+ # it is a sub-sub-block
+ my %subhash;
+ print "\t\t$subkeyword {\n" if $DEBUG;
+ my @b;
+ $hash{$subkeyword} = \@b unless ${hash}{$subkeyword};
+ ($info, $linenum, $line) = &read_conf ($linenum, $line, \*FILE);
+ while ($info ne '}') {
+ last if eof (FILE);
+ my $subsubkeyword = $info;
+ ($info, $linenum, $line) = &read_conf ($linenum, $line, \*FILE);
+ my $subsubvalue = $info;
+ if ($info eq '{') {
+ die "Error in $file line $linenum: too many blocks.\n";
+ }
+ else {
+ ($info, $linenum, $line) =
+ &read_conf ($linenum, $line, \*FILE);
+ die "Error in $file line $linenum: must be a ';' instead " .
+ "of '$info'\n" unless ($info eq ';');
+ print "\t\t\t$subsubkeyword\t$subsubvalue;\n" if $DEBUG;
+ $subhash{$subsubkeyword} = $subsubvalue;
+ ($info, $linenum, $line) =
+ &read_conf ($linenum, $line, \*FILE);
+ }
+ }
+ ($info, $linenum, $line) = &read_conf ($linenum, $line, \*FILE);
+ die "Error in $file line $linenum: must be a ';' instead of " .
+ "'$info'\n" unless $info eq ';';
+ push @{$hash{$subkeyword}} , \%subhash;
+ ($info, $linenum, $line) = &read_conf ($linenum, $line, \*FILE);
+ print "\t\t};\n" if $DEBUG;
+ }
+ else {
+ ($info, $linenum, $line) = &read_conf ($linenum, $line, \*FILE);
+ die "Error in $file line $linenum: must be a ';' instead " .
+ "of '$info'\n" unless $info eq ';';
+ print "\t\t$subkeyword\t$subvalue;\n" if $DEBUG;
+ $hash{$subkeyword} = $subvalue;
+ ($info, $linenum, $line) = &read_conf ($linenum, $line, \*FILE);
+ }
+ }
+ ($info, $linenum, $line) = &read_conf ($linenum, $line, \*FILE);
+ die "Error in $file line $linenum: must be a ';' instead of '$info'\n"
+ unless $info eq ';';
+ push @{$output{$section}{$keyword}}, \%hash;
+ ($info, $linenum, $line) = &read_conf ($linenum, $line, \*FILE);
+ print "\t};\n" if $DEBUG;
+ }
+ else {
+ ($info, $linenum, $line) = &read_conf ($linenum, $line, \*FILE);
+ die "Error in $file line $linenum: must be a ';' instead of '$info'\n"
+ unless $info eq ';';
+ print "\t$keyword\t$value;\n" if $DEBUG;
+ $output{$section}{$keyword} = $value;
+ ($info, $linenum, $line) = &read_conf ($linenum, $line, \*FILE);
+ }
+ }
+ die "Error in $file line $linenum: must be a '}' instead of '$info'\n"
+ unless $info eq '}';
+ ($info, $linenum, $line) = &read_conf ($linenum, $line, \*FILE);
+ die "Error in $file line $linenum: must be a ';' instead of '$info'\n"
+ unless $info eq ';';
+ print "};\n\n" if $DEBUG;
+ }
+ close FILE;
+ $output{'_order_'} = \@list;
+}
+
+sub read_conf {
+ my ($linenum, $line, $file) = @_;
+ *FILE = *$file;
+
+ $line =~ s,^\s+,,o; # remove useless blanks
+ $line =~ s,^(\#|//).*$,,o; # remove comments (at the beginning)
+ while (($line =~ m/^$/o || $line =~ m/^\"[^\"]*$/o) && !(eof (FILE))) {
+ $line .= <FILE>; # read one line
+ $linenum++;
+ $line =~ s,^\s*,,om; # remove useless blanks
+ $line =~ s,^(\#|//).*$,,om; # remove comments (at the beginning)
+ }
+ $line =~ s/^( # at the beginning
+ [{};] # match '{', '}', or ';'
+ | # OR
+ \" # a double quoted string
+ (?:\\.|[^\"\\])*
+ \"
+ | # OR
+ [^{};\"\s]+ # a word
+ )\s*//mox;
+ my $info = $1;
+ if (defined $info && $info) {
+ chomp $info;
+ }
+ else {
+ warn "Syntax error in conf file line $linenum.\n";
+ }
+ return ($info, $linenum, $line);
+}
+
+sub GetValue {
+ my $v = shift;
+ my ($r) = $v =~ m/^(?:\"\s*)?(.*?)(?:\s*\")?$/so;
+ return $r;
+}
+
+sub Usage {
+ my ($base) = $0 =~ /([^\/]+)$/;
+ print "Usage: $base -f innreport.conf [-[no]options]\n";
+ print " where options are:\n";
+ print " -h (or -help) this help page\n";
+ print " -v display the version number of INNreport\n";
+ print " -config print INNreport configuration information\n";
+ print " -html HTML output";
+ print " [default]" if ($HTML);
+ print "\n";
+ print " -g want graphs";
+ print " [default]" if ($GRAPH);
+ print "\n";
+ print " -graph an alias for option -g\n";
+ print " -d directory directory for Web pages";
+ print "\n [default=$HTML_dir]"
+ if (defined ($HTML_dir));
+ print "\n";
+ print " -dir directory an alias for option -d\n";
+ print " -p directory pictures path (file space)";
+ print "\n [default=$IMG_dir]"
+ if (defined ($IMG_dir));
+ print "\n";
+ print " -path directory an alias for option -p\n";
+ print " -w directory pictures path (web space)";
+ print " [default=$IMG_pth]" if (defined ($IMG_pth));
+ print "\n";
+ print " -webpath directory an alias for option -w\n";
+ print "\n";
+ print " -i file Name of index file";
+ print " [default=$index]" if (defined ($index));
+ print "\n";
+ print " -index file an alias for option -i\n";
+ print " -a want to archive HTML results";
+ print " [default]" if ($ARCHIVE);
+ print "\n";
+ print " -archive an alias for option -a\n";
+ print " -c number how many report files to keep (0 = all)\n";
+ print " [default=$CYCLE]"
+ if (defined ($CYCLE));
+ print "\n";
+ print " -cycle number an alias for option -c\n";
+ print " -s char separator for filename";
+ print " [default=\"$SEPARATOR\"]\n";
+ print " -separator char an alias for option -s\n";
+ print " -unknown \"Unknown entries from news log file\"\n";
+ print " report";
+ print " [default]" if ($WANT_UNKNOWN);
+ print "\n";
+ print " -html-unknown Same as above, but in generated HTML output.";
+ print " [default]" if ($WANT_UNKNOWN);
+ print "\n";
+ print " -maxunrec Max number of unrecognized lines to display\n";
+ print " [default=$MAX_UNRECOGNIZED]"
+ if (defined ($MAX_UNRECOGNIZED));
+ print "\n";
+ print " -notdaily Never perform daily actions";
+ print " [default]" if $NOT_DAILY;
+ print "\n";
+ print " -casesensitive Case sensitive";
+ print " [default]" if ($CASE_SENSITIVE);
+ print "\n\n";
+ print "Use no in front of boolean options to unset them.\n";
+ print "For example, \"-html\" is set by default. Use \"-nohtml\" to remove this\n";
+ print "feature.\n";
+ exit 0;
+}
+
+sub Version {
+ print "\nThis is INNreport version $version\n\n";
+ print "Copyright 1996-1999, Fabien Tassin <fta\@sofaraway.org>\n";
+ exit 0;
+}
+
+sub Summary {
+ use Config;
+
+ # Convert empty arguments into null string ("")
+ my $i = 0;
+ foreach (@old_argv) {
+ $old_argv[$i] = '""' if $_ eq '';
+ $i++;
+ }
+
+ # Display the summary
+ print "\nSummary of my INNreport (version $version) configuration:\n";
+ print " General options:\n";
+ print " command line='@old_argv' (please, check this value)\n";
+ print " html=" . ($HTML?"yes":"no") . ", graph=" .
+ ($GRAPH?"yes":"no") . ", haveGD=" .
+ ($::HAVE_GD?"yes":"no") . "\n";
+ print " archive=" . ($ARCHIVE?"yes":"no") .
+ ", cycle=$CYCLE, separator=\"" . $SEPARATOR . "\"\n";
+ print " case_sensitive=" .
+ ($CASE_SENSITIVE?"yes":"no") . ", want_unknown=" .
+ ($WANT_UNKNOWN?"yes":"no") .
+ ", max_unrecog=$MAX_UNRECOGNIZED\n";
+ print " Paths:\n";
+ print " html_dir=$HTML_dir\n";
+ print " img_dir=$IMG_dir\n";
+ print " img_pth=$IMG_pth\n";
+ print " index=$index\n";
+ print " Platform:\n";
+ print " perl version $::Config{baserev} "
+ . "patchlevel $::Config{patchlevel} "
+ . "subversion $::Config{subversion}\n";
+ print " libperl=$::Config{libperl}, useshrplib=$::Config{useshrplib}, "
+ . "bincompat3=$::Config{bincompat3}\n";
+ print " osname=$::Config{osname}, osvers=$::Config{osvers}, "
+ . "archname=$::Config{archname}\n";
+ print " uname=$::Config{myuname}\n\n";
+
+ exit 0;
+}
+
+######################### End of File ##########################
--- /dev/null
+##########################################################
+# INN module for innreport (3.*).
+#
+# Sample file tested with INN 2.4, 2.3, 2.2, 1.7.2 and 1.5.1
+#
+# (c) 1997-1999 by Fabien Tassin <fta@sofaraway.org>
+# version 3.0.2
+##########################################################
+
+# TODO: add the map file.
+
+package innreport_inn;
+
+my $MIN = 1E10;
+my $MAX = -1;
+
+my %ctlinnd = ('a', 'addhist', 'D', 'allow',
+ 'b', 'begin', 'c', 'cancel',
+ 'u', 'changegroup', 'd', 'checkfile',
+ 'e', 'drop', 'f', 'flush',
+ 'g', 'flushlogs', 'h', 'go',
+ 'i', 'hangup', 's', 'mode',
+ 'j', 'name', 'k', 'newgroup',
+ 'l', 'param', 'm', 'pause',
+ 'v', 'readers', 't', 'refile',
+ 'C', 'reject', 'o', 'reload',
+ 'n', 'renumber', 'z', 'reserve',
+ 'p', 'rmgroup', 'A', 'send',
+ 'q', 'shutdown', 'B', 'signal',
+ 'r', 'throttle', 'w', 'trace',
+ 'x', 'xabort', 'y', 'xexec',
+ 'E', 'logmode', 'F', 'feedinfo',
+ 'T', 'filter', 'P', 'perl',);
+
+my %timer_names = (idle => 'idle',
+ hishave => 'history lookup',
+ hisgrep => 'history grep',
+ hiswrite => 'history write',
+ hissync => 'history sync',
+ nntpread => 'nntp read',
+ artparse => 'article parse',
+ artclean => 'article cleanup',
+ artwrite => 'article write',
+ artcncl => 'article cancel',
+ artlog => 'article logging',
+ sitesend => 'site send',
+ overv => 'overview write',
+ perl => 'perl filter',
+ python => 'python filter',
+ datamove => 'data move'
+);
+
+my %innfeed_timer_names = (
+ 'idle' => 'idle',
+ 'blstats' => 'backlog stats',
+ 'stsfile' => 'status file',
+ 'newart' => 'article new',
+ 'prepart' => 'article prepare',
+ 'readart' => 'article read',
+ 'read' => 'data read',
+ 'write' => 'data write',
+ 'cb' => 'callbacks',
+);
+
+my %nnrpd_timer_names = (
+ 'idle' => 'idle',
+ 'newnews' => 'newnews',
+);
+
+# init innd timer
+foreach (values %timer_names) {
+ $innd_time_min{$_} = $MIN;
+ $innd_time_max{$_} = $MAX;
+ $innd_time_time{$_} = 0; # to avoid a warning... Perl < 5.004
+ $innd_time_num{$_} = 0; # ...
+}
+$innd_time_times = 0; # ...
+
+# init innfeed timer
+foreach (values %innfeed_timer_names) {
+ $innfeed_time_min{$_} = $MIN;
+ $innfeed_time_max{$_} = $MAX;
+ $innfeed_time_time{$_} = 0; # to avoid a warning... Perl < 5.004
+ $innfeed_time_num{$_} = 0; # ...
+}
+$innfeed_time_times = 0; # ...
+
+# init nnrpd timer
+foreach (values %nnrpd_timer_names) {
+ $nnrpd_time_min{$_} = $MIN;
+ $nnrpd_time_max{$_} = $MAX;
+ $nnrpd_time_time{$_} = 0; # to avoid a warning... Perl < 5.004
+ $nnrpd_time_num{$_} = 0; # ...
+}
+$nnrpd_time_times = 0; # ...
+
+# collect: Used to collect the data.
+sub collect {
+ my ($day, $hour, $prog, $res, $left, $CASE_SENSITIVE) = @_;
+
+ return 1 if $left =~ /Reading config from (\S+)$/o;
+
+ ########
+ ## inn (from the "news" log file - not from "news.notice")
+ ##
+ if ($prog eq "inn") {
+ # accepted article
+ if ($res =~ m/[\+j]/o) {
+ $hour =~ s/:.*$//o;
+ $inn_flow{"$day $hour"}++;
+ $inn_flow_total++;
+
+ # Memorize the size. This can only be done with INN >= 1.5xx and
+ # DO_LOG_SIZE = DO.
+
+ # server <msg-id> size [feeds]
+ # or
+ # server <msg-id> (filename) size [feeds]
+
+ my ($s) = $left =~ /^\S+ \S+ (?:\(\S+\) )?(\d+)(?: |$)/o;
+ if ($s) {
+ $inn_flow_size{"$day $hour"} += $s;
+ $inn_flow_size_total += $s;
+ }
+ return 1;
+ }
+
+ # 437 Duplicate article
+ if ($left =~ /(\S+) <[^>]+> 437 Duplicate(?: article)?$/o) {
+ my $server = $1;
+ $server = lc $server unless $CASE_SENSITIVE;
+ $inn_badart{$server}++;
+ $inn_duplicate{$server}++;
+ return 1;
+ }
+ # 437 Unapproved for
+ if ($left =~ /(\S+) <[^>]+> 437 Unapproved for \"([^\"]+)\"$/o) {
+ my ($server, $group) = ($1, $2);
+ $server = lc $server unless $CASE_SENSITIVE;
+ $inn_badart{$server}++;
+ $inn_unapproved{$server}++;
+ $inn_unapproved_g{$group}++;
+ return 1;
+ }
+ # 437 Too old -- ...
+ if ($left =~ /(\S+) <[^>]+> 437 Too old -- /o) {
+ my $server = $1;
+ $server = lc $server unless $CASE_SENSITIVE;
+ $inn_badart{$server}++;
+ $inn_tooold{$server}++;
+ return 1;
+ }
+ # 437 Unwanted site ... in path
+ if ($left =~ /(\S+) <[^>]+> 437 Unwanted site (\S+) in path$/o) {
+ my ($server, $site) = ($1, $2);
+ $server = lc $server unless $CASE_SENSITIVE;
+ $inn_badart{$server}++;
+ $inn_uw_site{$server}++;
+ $inn_site_path{$site}++;
+ return 1;
+ }
+ # 437 Unwanted newsgroup "..."
+ if ($left =~ /(\S+) <[^>]+> 437 Unwanted newsgroup \"(\S+)\"$/o) {
+ my ($server, $group) = ($1, $2);
+ ($group) = split(/,/, $group);
+ $server = lc $server unless $CASE_SENSITIVE;
+ $inn_badart{$server}++;
+ $inn_uw_ng_s{$server}++;
+ $inn_uw_ng{$group}++;
+ return 1;
+ }
+ # 437 Unwanted distribution "..."
+ if ($left =~ /(\S+) <[^>]+> 437 Unwanted distribution \"(\S+)\"$/o) {
+ my ($server, $dist) = ($1, $2);
+ $server = lc $server unless $CASE_SENSITIVE;
+ $inn_badart{$server}++;
+ $inn_uw_dist_s{$server}++;
+ $inn_uw_dist{$dist}++;
+ return 1;
+ }
+ # 437 Linecount x != y +- z
+ if ($left =~ /(\S+) <[^>]+> 437 Linecount/o) {
+ my $server = $1;
+ $server = lc $server unless $CASE_SENSITIVE;
+ $inn_badart{$server}++;
+ $inn_linecount{$server}++;
+ return 1;
+ }
+ # 437 No colon-space in "xxxx" header
+ if ($left =~ /(\S+) <[^>]+> 437 No colon-space in \"[^\"]+\" header/o) {
+ my $server = $1;
+ $server = lc $server unless $CASE_SENSITIVE;
+ $inn_badart{$server}++;
+ $innd_others{$server}++;
+ $innd_no_colon_space{$server}++;
+ return 1;
+ }
+ # 437 Article posted in the future -- "xxxxx"
+ if ($left =~ /(\S+) <[^>]+> 437 Article posted in the future -- \"[^\"]+\"/o) {
+ my $server = $1;
+ $server = lc $server unless $CASE_SENSITIVE;
+ $innd_posted_future{$server}++;
+ $innd_others{$server}++;
+ $inn_badart{$server}++;
+ return 1;
+ }
+ # 437 article includes "....."
+ if ($left =~ /(\S+) <[^>]+> 437 article includes/o) {
+ my $server = $1;
+ $server = lc $server unless $CASE_SENSITIVE;
+ $innd_strange_strings{$server}++;
+ $innd_others{$server}++;
+ $inn_badart{$server}++;
+ return 1;
+ }
+ # Cancelling <...>
+ if ($left =~ /(\S+) <[^>]+> Cancelling/o) {
+ return 1;
+ }
+ # all others are just counted as "Other"
+ if ($left =~ /(\S+) /o) {
+ my $server = $1;
+ $server = lc $server unless $CASE_SENSITIVE;
+ $inn_badart{$server}++;
+ $innd_others{$server}++;
+ return 1;
+ }
+ }
+
+ ########
+ ## innd
+ if ($prog eq "innd") {
+ ## Note for innd logs:
+ ## there's a lot of entries detected but still not used
+ ## (because of a lack of interest).
+
+ # think it's a dotquad
+ return 1 if $left =~ /^think it\'s a dotquad$/o;
+ if ($left =~ /^SERVER /o) {
+ # SERVER perl filtering enabled
+ return 1 if $left =~ /^SERVER perl filtering enabled$/o;
+ # SERVER perl filtering disabled
+ return 1 if $left =~ /^SERVER perl filtering disabled$/o;
+ # SERVER Python filtering enabled
+ return 1 if $left =~ /^SERVER Python filtering enabled$/o;
+ # SERVER Python filtering disabled
+ return 1 if $left =~ /^SERVER Python filtering disabled$/o;
+ # SERVER cancelled +id
+ return 1 if $left =~ /^SERVER cancelled /o;
+ }
+ # Python filter
+ return 1 if $left =~ /^defined python methods$/o;
+ return 1 if $left =~ /^reloading pyfilter$/o;
+ return 1 if $left =~ /^reloaded pyfilter OK$/o;
+ return 1 if $left =~ /^python interpreter initialized OK$/o;
+ return 1 if $left =~ /^python method \w+ not found$/o;
+ return 1 if $left =~ /^python: First load, so I can do initialization stuff\.$/o;
+ return 1 if $left =~ /^python: filter_before_reload executing\.\.\.$/o;
+ return 1 if $left =~ /^python: I\'m just reloading, so skip the formalities\.$/o;
+ return 1 if $left =~ /^python: spamfilter successfully hooked into INN$/o;
+ return 1 if $left =~ /^python: state change from \w+ to \w+ - /o;
+ return 1 if $left =~ /^python: filter_close running, bye!$/o;
+ # rejecting[perl]
+ if ($left =~ /^rejecting\[perl\] <[^>]+> \d+ (.*)/o) {
+ $innd_filter_perl{$1}++;
+ return 1;
+ }
+ # rejecting[python]
+ if ($left =~ /^rejecting\[python\] <[^>]+> \d+ (.*)/o) {
+ $innd_filter_python{$1}++;
+ return 1;
+ }
+ # closed lost
+ return 1 if $left =~ /^\S+ closed lost \d+/o;
+ # new control command
+ if ($left =~ /^ctlinnd command (\w)(:.*)?/o) {
+ my $command = $1;
+ my $cmd = $ctlinnd{$command};
+ $cmd = $command unless $cmd;
+ return 1 if $cmd eq 'flush'; # to avoid a double count
+ $innd_control{"$cmd"}++;
+ return 1;
+ }
+ # old control command (by letter)
+ if ($left =~ /^(\w)$/o) {
+ my $command = $1;
+ my $cmd = $ctlinnd{$command};
+ $cmd = $command unless $cmd;
+ return 1 if $cmd eq 'flush'; # to avoid a double count
+ $innd_control{"$cmd"}++;
+ return 1;
+ }
+ # old control command (letter + reason)
+ if ($left =~ /^(\w):.*$/o) {
+ my $command = $1;
+ my $cmd = $ctlinnd{$command};
+ $cmd = $command unless $cmd;
+ return 1 if $cmd eq 'flush'; # to avoid a double count
+ $innd_control{"$cmd"}++;
+ return 1;
+ }
+ # opened
+ return 1 if $left =~ /\S+ opened \S+:\d+:file$/o;
+ # buffered
+ return 1 if $left =~ /\S+ buffered$/o;
+ # spawned
+ return 1 if $left =~ /\S+ spawned \S+:\d+:proc:\d+$/o;
+ return 1 if $left =~ /\S+ spawned \S+:\d+:file$/o;
+ # running
+ return 1 if $left =~ /\S+ running$/o;
+ # sleeping
+ if ($left =~ /(\S+):\d+:proc:\d+ sleeping$/o) {
+ my $server = $1;
+ $server = lc $server unless $CASE_SENSITIVE;
+ $innd_blocked{$server}++;
+ return 1;
+ }
+ # blocked sleeping
+ if ($left =~ /(\S+):\d+:proc:\d+ blocked sleeping/o) {
+ my $server = $1;
+ $server = lc $server unless $CASE_SENSITIVE;
+ $innd_blocked{$server}++;
+ return 1;
+ }
+ if ($left =~ /(\S+):\d+ blocked sleeping/o) {
+ my $server = $1;
+ $server = lc $server unless $CASE_SENSITIVE;
+ $innd_blocked{$server}++;
+ return 1;
+ }
+ # restarted
+ return 1 if $left =~ m/^\S+ restarted$/o;
+ # starting
+ return 1 if $left =~ m/^\S+ starting$/o;
+ # readclose
+ return 1 if $left =~ m/^\S+:\d+ readclose+$/o;
+ # rejected 502
+ if ($left =~ m/^(\S+) rejected 502$/) {
+ my $server = $1;
+ $server = lc $server unless $CASE_SENSITIVE;
+ $innd_no_permission{$server}++;
+ return 1;
+ }
+ # rejected 505
+ if ($left =~ m/^(\S+) rejected 505$/) {
+ my $server = $1;
+ $server = lc $server unless $CASE_SENSITIVE;
+ $innd_too_many_connects_per_minute{$server}++;
+ return 1;
+ }
+ # connected
+ if ($left =~ /^(\S+) connected \d+/o) {
+ my $server = $1;
+ $server = lc $server unless $CASE_SENSITIVE;
+ $innd_connect{$server}++;
+ return 1;
+ }
+ # closed (with times)
+ if ($left =~ /(\S+):\d+ closed seconds (\d+) accepted (\d+) refused (\d+) rejected (\d+) duplicate (\d+) accepted size (\d+) duplicate size (\d+)(?: rejected size (\d+))?$/o) {
+ my ($server, $seconds, $accepted, $refused, $rejected, $duplicate, $accptsize, $dupsize) =
+ ($1, $2, $3, $4, $5, $6, $7, $8);
+ $server = lc $server unless $CASE_SENSITIVE;
+ $innd_seconds{$server} += $seconds;
+ $innd_accepted{$server} += $accepted;
+ $innd_refused{$server} += $refused;
+ $innd_rejected{$server} += $rejected;
+ $innd_stored_size{$server} += $accptsize;
+ $innd_duplicated_size{$server} += $dupsize;
+ return 1;
+ } elsif ($left =~ /(\S+):\d+ closed seconds (\d+) accepted (\d+) refused (\d+) rejected (\d+)$/o) {
+ # closed (with times)
+ my ($server, $seconds, $accepted, $refused, $rejected) =
+ ($1, $2, $3, $4, $5);
+ $server = lc $server unless $CASE_SENSITIVE;
+ $innd_seconds{$server} += $seconds;
+ $innd_accepted{$server} += $accepted;
+ $innd_refused{$server} += $refused;
+ $innd_rejected{$server} += $rejected;
+ return 1;
+ }
+ # closed (without times (?))
+ return 1 if $left =~ m/\S+ closed$/o;
+ # closed (for a cancel feed - MODE CANCEL)
+ return 1 if $left =~ m/localhost:\d+ closed seconds \d+ cancels \d+$/o;
+ # checkpoint
+ return 1 if $left =~ m/^\S+:\d+ checkpoint /o;
+ # if ($left =~ /(\S+):\d+ checkpoint seconds (\d+) accepted (\d+)
+ # refused (\d+) rejected (\d+)$/) {
+ # # Skipped...
+ # my ($server, $seconds, $accepted, $refused, $rejected) =
+ # ($1, $2, $3, $4, $5);
+ # $innd_seconds{$server} += $seconds;
+ # $innd_accepted{$server} += $accepted;
+ # $innd_refused{$server} += $refused;
+ # $innd_rejected{$server} += $rejected;
+ # return 1;
+ # }
+
+ # flush
+ if ($left =~ /(\S+) flush$/o) {
+ $innd_control{"flush"}++;
+ return 1;
+ }
+ # flush-file
+ if ($left =~ /flush_file/) {
+ $innd_control{"flush_file"}++;
+ return 1;
+ }
+ # too many connections from site
+ if ($left =~ /too many connections from (\S+)/o) {
+ $innd_max_conn{$1}++;
+ return 1;
+ }
+ # overview exit 0 elapsed 23 pid 28461
+ return 1 if $left =~ m/\S+ exit \d+ .*$/o;
+ # internal rejecting huge article
+ if ($left =~ /(\S+) internal rejecting huge article/o) {
+ my $server = $1;
+ $server =~ s/:\d+$//o;
+ $server = lc $server unless $CASE_SENSITIVE;
+ $innd_huge{$server}++;
+ return 1;
+ }
+ # internal closing free channel
+ if ($left =~ /(\S+) internal closing free channel/o) {
+ $innd_misc{"Free channel"}++;
+ return 1;
+ }
+ # internal (other)
+ return 1 if $left =~ /\S+ internal/o;
+ # wakeup
+ return 1 if $left =~ /\S+ wakeup$/o;
+ # throttle
+ if ($left =~ /(\S+) throttled? /) {
+ $innd_control{"throttle"}++;
+ return 1;
+ }
+ # profile timer
+ # ME time X nnnn X(X) [...]
+ # The exact timers change from various versions of INN, so try to deal
+ # with this in a general fashion.
+ if ($left =~ m/^\S+\s+ # ME
+ time\ (\d+)\s+ # time
+ ((?:\S+\ \d+\(\d+\)\s*)+) # timer values
+ $/ox) {
+ $innd_time_times += $1;
+ my $timers = $2;
+
+ while ($timers =~ /(\S+) (\d+)\((\d+)\)\s*/g) {
+ my $name = $timer_names{$1} || $1;
+ my $average = $2 / ($3 || 1);
+ $innd_time_time{$name} += $2;
+ $innd_time_num{$name} += $3;
+ $innd_time_min{$name} = $average
+ if ($3 && $innd_time_min{$name} > $average);
+ $innd_time_max{$name} = $average
+ if ($3 && $innd_time_max{$name} < $average);
+ }
+ return 1;
+ }
+ # ME time xx idle xx(xx) [ bug ? a part of timer ?]
+ return 1 if $left =~ m/^ME time \d+ idle \d+\(\d+\)\s*$/o;
+ # ME HISstats x hitpos x hitneg x missed x dne
+ #
+ # from innd/his.c:
+ # HIShitpos: the entry existed in the cache and in history.
+ # HIShitneg: the entry existed in the cache but not in history.
+ # HISmisses: the entry was not in the cache, but was in the history file.
+ # HISdne: the entry was not in cache or history.
+ if ($left =~ m/^ME\ HISstats # ME HISstats
+ \ (\d+)\s+hitpos # hitpos
+ \ (\d+)\s+hitneg # hitneg
+ \ (\d+)\s+missed # missed
+ \ (\d+)\s+dne # dne
+ $/ox) {
+ $innd_his{'Positive hits'} += $1;
+ $innd_his{'Negative hits'} += $2;
+ $innd_his{'Cache misses'} += $3;
+ $innd_his{'Do not exist'} += $4;
+ return 1;
+ }
+ # SERVER history cache final: 388656 lookups, 1360 hits
+ if ($left =~ m/^SERVER history cache final: (\d+) lookups, (\d+) hits$/) {
+ $innd_cache{'Lookups'} += $1;
+ $innd_cache{'Hits'} += $2;
+ return 1;
+ }
+ # bad_hosts (appears after a "cant gesthostbyname" from a feed)
+ return 1 if $left =~ m/\S+ bad_hosts /o;
+ # cant read
+ return 1 if $left =~ m/\S+ cant read/o;
+ # cant write
+ return 1 if $left =~ m/\S+ cant write/o;
+ # cant flush
+ return 1 if $left =~ m/\S+ cant flush/o;
+ # spoolwake
+ return 1 if $left =~ m/\S+ spoolwake$/o;
+ # spooling
+ return 1 if $left =~ m/\S+ spooling/o;
+ # DEBUG
+ return 1 if $left =~ m/^DEBUG /o;
+ # NCmode
+ return 1 if $left =~ m/\S+ NCmode /o;
+ # outgoing
+ return 1 if $left =~ m/\S+ outgoing/o;
+ # inactive
+ return 1 if $left =~ m/\S+ inactive/o;
+ # timeout
+ return 1 if $left =~ m/\S+ timeout/o;
+ # lcsetup
+ return 1 if $left =~ m/\S+ lcsetup/o;
+ # rcsetup
+ return 1 if $left =~ m/\S+ rcsetup/o;
+ # flush_all
+ return 1 if $left =~ m/\S+ flush_all/o;
+ # buffered
+ return 1 if $left =~ m/\S+ buffered$/o;
+ # descriptors
+ return 1 if $left =~ m/\S+ descriptors/o;
+ # ccsetup
+ return 1 if $left =~ m/\S+ ccsetup/o;
+ # renumbering
+ return 1 if $left =~ m/\S+ renumbering/o;
+ # renumber
+ return 1 if $left =~ m/\S+ renumber /o;
+ # ihave from me
+ if ($left =~ m/\S+ ihave_from_me /o) {
+ $controlchan_ihave_site{'ME'}++;
+ return 1;
+ }
+ # sendme from me
+ if ($left =~ m/\S+ sendme_from_me /o) {
+ $controlchan_sendme_site{'ME'}++;
+ return 1;
+ }
+ # newgroup
+ if ($left =~ m/\S+ newgroup (\S+) as (\S)/o) {
+ $innd_newgroup{$1} = $2;
+ return 1;
+ }
+ # rmgroup
+ if ($left =~ m/\S+ rmgroup (\S+)$/o) {
+ $innd_rmgroup{$1}++;
+ return 1;
+ }
+ # changegroup
+ if ($left =~ m/\S+ change_group (\S+) to (\S)/o) {
+ $innd_changegroup{$1} = $2;
+ return 1;
+ }
+ # paused
+ if ($left =~ m/(\S+) paused /o) {
+ $innd_control{"paused"}++;
+ return 1;
+ }
+ # throttled
+ return 1 if $left =~ m/\S+ throttled/o;
+ # reload
+ if ($left =~ m/(\S+) reload/o) {
+ $innd_control{"reload"}++;
+ return 1;
+ }
+ # shutdown
+ if ($left =~ m/(\S+) shutdown/o) {
+ $innd_control{"shutdown"}++;
+ return 1;
+ }
+ # SERVER servermode paused
+ return 1 if ($left =~ /(\S+) servermode paused$/o);
+ # SERVER servermode running
+ return 1 if ($left =~ /(\S+) servermode running$/o);
+ # SERVER flushlogs paused
+ if ($left =~ /(\S+) flushlogs /) {
+ $innd_control{"flushlogs"}++;
+ return 1;
+ }
+ # think it's a dotquad
+ return 1 if $left =~ /think it\'s a dotquad: /o;
+ # bad_ihave
+ if ($left =~ /(\S+) bad_ihave /) {
+ my $server = $1;
+ $server =~ s/:\d+$//o;
+ $server = lc $server unless $CASE_SENSITIVE;
+ $innd_bad_ihave{$server}++;
+ return 1;
+ }
+ # bad_messageid
+ if ($left =~ /(\S+) bad_messageid/o) {
+ my $server = $1;
+ $server =~ s/:\d+$//o;
+ $server = lc $server unless $CASE_SENSITIVE;
+ $innd_bad_msgid{$server}++;
+ return 1;
+ }
+ # bad_sendme
+ if ($left =~ /(\S+) bad_sendme /o) {
+ my $server = $1;
+ $server =~ s/:\d+$//o;
+ $server = lc $server unless $CASE_SENSITIVE;
+ $innd_bad_sendme{$server}++;
+ return 1;
+ }
+ # bad_command
+ if ($left =~ /(\S+) bad_command /o) {
+ my $server = $1;
+ $server =~ s/:\d+$//o;
+ $server = lc $server unless $CASE_SENSITIVE;
+ $innd_bad_command{$server}++;
+ return 1;
+ }
+ # bad_newsgroup
+ if ($left =~ /(\S+) bad_newsgroup /o) {
+ my $server = $1;
+ $server =~ s/:\d+$//o;
+ $innd_bad_newsgroup{$server}++;
+ $server = lc $server unless $CASE_SENSITIVE;
+ return 1;
+ }
+ if ($left =~ m/ cant /o) {
+ # cant select Bad file number
+ if ($left =~ / cant select Bad file number/o) {
+ $innd_misc{"Bad file number"}++;
+ return 1;
+ }
+ # cant gethostbyname
+ if ($left =~ / cant gethostbyname/o) {
+ $innd_misc{"gethostbyname error"}++;
+ return 1;
+ }
+ # cant accept RCreader
+ if ($left =~ / cant accept RCreader /o) {
+ $innd_misc{"RCreader"}++;
+ return 1;
+ }
+ # cant sendto CCreader
+ if ($left =~ / cant sendto CCreader /o) {
+ $innd_misc{"CCreader"}++;
+ return 1;
+ }
+ # cant (other) skipped - not particularly interesting
+ return 1;
+ }
+ # bad_newsfeeds no feeding sites
+ return 1 if $left =~ /\S+ bad_newsfeeds no feeding sites/o;
+ # CNFS: cycbuff rollover - possibly interesting
+ return 1 if $left =~ /CNFS(?:-sm)?: cycbuff \S+ rollover to cycle/o;
+ # CNFS: CNFSflushallheads: flushing - possibly interesting
+ return 1 if $left =~ /CNFS(?:-sm)?: CNFSflushallheads: flushing /o;
+ # CNFS: metacycbuff rollover with SEQUENTIAL
+ return 1 if $left =~ /CNFS(?:-sm)?: metacycbuff \S+ cycbuff is moved to /o;
+ # Cleanfeed status reports
+ return 1 if $left =~ /^filter: status/o;
+ return 1 if $left =~ /^filter: Reloading bad files/o;
+ }
+ ########
+ ## innfeed
+ if ($prog eq "innfeed") {
+ # connected
+ if ($left =~ /(\S+):\d+ connected$/) {
+ my $server = $1;
+ $server = lc $server unless $CASE_SENSITIVE;
+ $innfeed_connect{$server}++;
+ return 1;
+ }
+ # closed periodic
+ return 1 if $left =~ m/\S+:\d+ closed periodic$/o;
+ # periodic close
+ return 1 if $left =~ m/\S+:\d+ periodic close$/o;
+ # final (child)
+ return 1 if $left =~ m/\S+:\d+ final seconds \d+ offered \d+ accepted \d+ refused \d+ rejected \d+/o;
+ # global (real)
+ return 1 if $left =~ m/\S+ global seconds \d+ offered \d+ accepted \d+ refused \d+ rejected \d+ missing \d+/o;
+ # final (real) (new format)
+ if ($left =~ /(\S+) final seconds (\d+) offered (\d+) accepted (\d+) refused (\d+) rejected (\d+) missing (\d+) accsize (\d+) rejsize (\d+) spooled (\d+)/o) {
+ my ($server, $seconds, $offered, $accepted, $refused, $rejected,
+ $missing, $accepted_size, $rejected_size, $spooled) = ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10);
+ $server = lc $server unless $CASE_SENSITIVE;
+ $innfeed_seconds{$server} += $seconds;
+ $innfeed_offered{$server} += $offered;
+ $innfeed_accepted{$server} += $accepted;
+ $innfeed_refused{$server} += $refused;
+ $innfeed_rejected{$server} += $rejected;
+ $innfeed_missing{$server} += $missing;
+ $innfeed_spooled{$server} += $spooled;
+ $innfeed_accepted_size{$server} += $accepted_size;
+ $innfeed_rejected_size{$server} += $rejected_size;
+ return 1;
+ } elsif ($left =~ /(\S+) final seconds (\d+) offered (\d+) accepted (\d+) refused (\d+) rejected (\d+) missing (\d+) spooled (\d+)/o) {
+ my ($server, $seconds, $offered, $accepted, $refused, $rejected,
+ $missing, $spooled) = ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10);
+ $server = lc $server unless $CASE_SENSITIVE;
+ $innfeed_seconds{$server} += $seconds;
+ $innfeed_offered{$server} += $offered;
+ $innfeed_accepted{$server} += $accepted;
+ $innfeed_refused{$server} += $refused;
+ $innfeed_rejected{$server} += $rejected;
+ $innfeed_missing{$server} += $missing;
+ $innfeed_spooled{$server} += $spooled;
+ return 1;
+ }
+ # final (only seconds & spooled)
+ if ($left =~ /(\S+) final seconds (\d+) spooled (\d+)/o) {
+ my ($server, $seconds, $spooled) = ($1, $2, $3);
+ $server = lc $server unless $CASE_SENSITIVE;
+ $innfeed_seconds{$server} += $seconds;
+ $innfeed_spooled{$server} += $spooled;
+ return 1;
+ }
+ # checkpoint
+ return 1 if $left =~ m/\S+ checkpoint seconds/o;
+ # ME file xxxx shrunk from yyyy to zzz
+ if ($left =~ /^ME file (.*)\.output shrunk from (\d+) to (\d+)$/) {
+ my ($file, $s1, $s2) = ($1, $2, $3);
+ $file =~ s|^.*/([^/]+)$|$1|; # keep only the server name
+ $innfeed_shrunk{$file} += $s1 - $s2;
+ return 1;
+ }
+ # profile timer
+ # ME time X nnnn X(X) [...]
+ return 1 if $left =~ m/backlogstats/;
+ if ($left =~ m/^\S+\s+ # ME
+ time\ (\d+)\s+ # time
+ ((?:\S+\ \d+\(\d+\)\s*)+) # timer values
+ $/ox) {
+ $innfeed_time_times += $1;
+ my $timers = $2;
+
+ while ($timers =~ /(\S+) (\d+)\((\d+)\)\s*/g) {
+ my $name = $innfeed_timer_names{$1} || $1;
+ my $average = $2 / ($3 || 1);
+ $innfeed_time_time{$name} += $2;
+ $innfeed_time_num{$name} += $3;
+ $innfeed_time_min{$name} = $average
+ if ($3 && $innfeed_time_min{$name} > $average);
+ $innfeed_time_max{$name} = $average
+ if ($3 && $innfeed_time_max{$name} < $average);
+ }
+ return 1;
+ }
+ # xxx grabbing external tape file
+ return 1 if $left =~ m/ grabbing external tape file/o;
+ # hostChkCxns - maxConnections was
+ return 1 if $left =~ m/hostChkCxns - maxConnections was /o;
+ # cxnsleep
+ return 1 if $left =~ m/\S+ cxnsleep .*$/o;
+ # idle
+ return 1 if $left =~ m/\S+ idle tearing down connection$/o;
+ # remote
+ return 1 if $left =~ m/\S+ remote .*$/o;
+ # spooling
+ return 1 if $left =~ m/\S+ spooling no active connections$/o;
+ # ME articles total
+ return 1 if $left =~ m/(?:SERVER|ME) articles total \d+ bytes \d+/o;
+ # ME articles active
+ return 1 if $left =~ m/(?:SERVER|ME) articles active \d+ bytes \d+/o;
+ # connect : Connection refused
+ return 1 if $left =~ m/connect : Connection refused/o;
+ # connect : Network is unreachable
+ return 1 if $left =~ m/connect : Network is unreachable/o;
+ # connect : Address family not supported by protocol
+ return 1 if $left =~ m/connect : Address family not supported by protocol/o;
+ # connect : No route to host
+ return 1 if $left =~ m/connect : No route to host/o;
+ # connection vanishing
+ return 1 if $left =~ m/connection vanishing/o;
+ # can't resolve hostname
+ return 1 if $left =~ m/can\'t resolve hostname/o;
+ # new hand-prepared backlog file
+ return 1 if $left =~ m/new hand-prepared backlog file/o;
+ # flush re-connect failed
+ return 1 if $left =~ m/flush re-connect failed/o;
+ # internal QUIT while write pending
+ return 1 if $left =~ m/internal QUIT while write pending/o;
+ # ME source lost . Exiting
+ return 1 if $left =~ m/(?:SERVER|ME) source lost . Exiting/o;
+ # ME starting innfeed (+version & date)
+ return 1 if $left =~ m/(?:SERVER|ME) starting (?:innfeed|at)/o;
+ # ME finishing at (date)
+ return 1 if $left =~ m/(?:SERVER|ME) finishing at /o;
+ # mode no-CHECK entered
+ return 1 if $left =~ m/mode no-CHECK entered/o;
+ # mode no-CHECK exited
+ return 1 if $left =~ m/mode no-CHECK exited/o;
+ # closed
+ return 1 if $left =~ m/^(\S+) closed$/o;
+ # global (+ seconds offered accepted refused rejected missing)
+ return 1 if $left =~ m/^(\S+) global/o;
+ # idle connection still has articles
+ return 1 if $left =~ m/^(\S+) idle connection still has articles$/o;
+ # missing article for IHAVE-body
+ return 1 if $left =~ m/^(\S+) missing article for IHAVE-body$/o;
+ # cannot continue
+ return 1 if $left =~ m/^cannot continue/o;
+ if ($left =~ /^(?:SERVER|ME)/o) {
+ # ME dropping articles into ...
+ return 1 if $left =~ / dropping articles into /o;
+ # ME dropped ...
+ return 1 if $left =~ / dropped /o;
+ # ME internal bad data in checkpoint file
+ return 1 if $left =~ m/ internal bad data in checkpoint/o;
+ # ME two filenames for same article
+ return 1 if $left =~ m/ two filenames for same article/o;
+ # ME unconfigured peer
+ return 1 if $left =~ m/ unconfigured peer/o;
+ # exceeding maximum article size
+ return 1 if $left =~ m/ exceeding maximum article byte/o;
+ # no space left on device errors
+ return 1 if $left =~ m/ ioerr fclose/o;
+ return 1 if $left =~ m/ lock failed for host/o;
+ return 1 if $left =~ m/ lock file pid-write/o;
+ return 1 if $left =~ m/ locked cannot setup peer/o;
+ return 1 if $left =~ m/ received shutdown signal/o;
+ # unconfigured peer
+ return 1 if $left =~ m/ unconfigured peer/o;
+ # ME lock
+ return 1 if $left =~ m/ lock/o;
+ # ME exception: getsockopt (0): Socket operation on non-socket
+ return 1 if $left =~ m/ exception: getsockopt /o;
+ # ME config aborting fopen (...) Permission denied
+ return 1 if $left =~ m/ config aborting fopen /o;
+ # ME cant chmod innfeed.pid....
+ return 1 if $left =~ m/ cant chmod \S+\/innfeed.pid/o;
+ return 1 if $left =~ m/ tape open failed /o;
+ return 1 if $left =~ m/ oserr open checkpoint file:/o;
+ # ME finishing (quickly)
+ return 1 if $left =~ m/\(quickly\) /o;
+ # ME config: value of streaming is not a boolean
+ return 1 if $left =~ m/config: value of \S+ is not/o;
+ }
+ # hostChkCxn - now: x.xx, prev: x.xx, abs: xx, curr: x
+ return 1 if $left =~ m/ hostChkCxn - now/o;
+ # loading path_to_config_file/innfeed.conf
+ return 1 if $left =~ m/loading /o;
+ # Finnaly, to avoid problems with strange error lines, ignore them.
+ #return 1 if ($left =~ /ME /);
+ }
+ ########
+ ## innxmit
+ if ($prog eq "innxmit") {
+ # 437 Duplicate article
+ if ($left =~ /(\S+) rejected [^\s]+ \(.*?\) 437 Duplicate article$/o) {
+ my $server = $1;
+ $server = lc $server unless $CASE_SENSITIVE;
+ $innxmit_badart{$server}++;
+ $innxmit_duplicate{$server}++;
+ return 1;
+ }
+ # 437 Unapproved for
+ if ($left =~ /(\S+) rejected [^\s]+ \(.*\) 437 Unapproved for \"(.*?)\"$/o) {
+ my ($server, $group) = ($1, $2);
+ $server = lc $server unless $CASE_SENSITIVE;
+ $innxmit_badart{$server}++;
+ $innxmit_unapproved{$server}++;
+ $innxmit_unapproved_g{$group}++;
+ return 1;
+ }
+ # 437 Too old -- ...
+ if ($left =~ /(\S+) rejected [^\s]+ \(.*\) 437 Too old -- \".*?\"$/o) {
+ my $server = $1;
+ $server = lc $server unless $CASE_SENSITIVE;
+ $innxmit_badart{$server}++;
+ $innxmit_tooold{$server}++;
+ return 1;
+ }
+ # 437 Unwanted site ... in path
+ if ($left =~
+ /(\S+) rejected [^\s]+ \(.*?\) 437 Unwanted site (\S+) in path$/o) {
+ my ($server, $site) = ($1, $2);
+ $server = lc $server unless $CASE_SENSITIVE;
+ $innxmit_badart{$server}++;
+ $innxmit_uw_site{$server}++;
+ # $innxmit_site_path{$site}++;
+ return 1;
+ }
+ # 437 Unwanted newsgroup "..."
+ if ($left =~
+ /(\S+) rejected [^\s]+ \(.*?\) 437 Unwanted newsgroup \"(\S+)\"$/o) {
+ my ($server, $group) = ($1, $2);
+ $server = lc $server unless $CASE_SENSITIVE;
+ $innxmit_badart{$server}++;
+ $innxmit_uw_ng_s{$server}++;
+ $innxmit_uw_ng{$group}++;
+ return 1;
+ }
+ # 437 Unwanted distribution "..."
+ if ($left =~
+ /(\S+) rejected [^\s]+ \(.*?\) 437 Unwanted distribution \"(\S+)\"$/o) {
+ my ($server, $dist) = ($1, $2);
+ $server = lc $server unless $CASE_SENSITIVE;
+ $innxmit_badart{$server}++;
+ $innxmit_uw_dist_s{$server}++;
+ $innxmit_uw_dist{$dist}++;
+ return 1;
+ }
+ # xx rejected foo.bar/12345 (foo/bar/12345) 437 Unwanted distribution "..."
+ if ($left =~ /^(\S+) rejected .* 437 Unwanted distribution \"(\S+)\"$/o) {
+ my ($server, $dist) = ($1, $2);
+ $server = lc $server unless $CASE_SENSITIVE;
+ $innxmit_badart{$server}++;
+ $innxmit_uw_dist_s{$server}++;
+ $innxmit_uw_dist{$dist}++;
+ return 1;
+ }
+ # 437 Linecount x != y +- z
+ if ($left =~ /(\S+) rejected [^\s]+ \(.*?\) 437 Linecount/o) {
+ my $server = $1;
+ $server = lc $server unless $CASE_SENSITIVE;
+ $innxmit_badart{$server}++;
+ $innxmit_linecount{$server}++;
+ return 1;
+ }
+ # 437 Newsgroup name illegal -- "xxx"
+ if ($left =~ /(\S+) rejected .* 437 Newsgroup name illegal -- "[^\"]*"$/) {
+ my $server = $1;
+ $server = lc $server unless $CASE_SENSITIVE;
+ $innxmit_others{$server}++;
+ $innxmit_badart{$server}++;
+ return 1;
+ }
+ # Streaming retries
+ return 1 if ($left =~ /\d+ Streaming retries$/o);
+ # ihave failed
+ if ($left =~ /(\S+) ihave failed/o) {
+ my $server = $1;
+ $server = lc $server unless $CASE_SENSITIVE;
+ $innxmit_ihfail{$server} = 1;
+ if ($left = /436 \S+ NNTP \S+ out of space/o) {
+ $innxmit_nospace{$server}++;
+ return 1;
+ }
+ if ($left = /400 \S+ space/o) {
+ $innxmit_nospace{$server}++;
+ return 1;
+ }
+ if ($left = /400 Bad file/o) {
+ $innxmit_crefused{$server}++;
+ return 1;
+ }
+ if ($left = /480 Transfer permission denied/o) {
+ $innxmit_crefused{$server}++;
+ return 1;
+ }
+ }
+ # stats (new format)
+ if ($left =~
+ /(\S+) stats offered (\d+) accepted (\d+) refused (\d+) rejected (\d+) missing (\d+) accsize (\d+) rejsize (\d+)$/o) {
+ my ($server, $offered, $accepted, $refused, $rejected, $missing, $accbytes, $rejbytes) =
+ ($1, $2, $3, $4, $5, $6, $7, $8);
+ $server = lc $server unless $CASE_SENSITIVE;
+ $innxmit_offered{$server} += $offered;
+ $innxmit_offered{$server} -= $innxmit_ihfail{$server}
+ if ($innxmit_ihfail{$server});
+ $innxmit_accepted{$server} += $accepted;
+ $innxmit_refused{$server} += $refused;
+ $innxmit_rejected{$server} += $rejected;
+ $innxmit_missing{$server} += $missing;
+ $innxmit_accepted_size{$server} += $accbytes;
+ $innxmit_rejected_size{$server} += $rejbytes;
+ $innxmit_site{$server}++;
+ $innxmit_ihfail{$server} = 0;
+ return 1;
+ }
+ # stats
+ if ($left =~
+ /(\S+) stats offered (\d+) accepted (\d+) refused (\d+) rejected (\d+)$/o) {
+ my ($server, $offered, $accepted, $refused, $rejected) =
+ ($1, $2, $3, $4, $5);
+ $server = lc $server unless $CASE_SENSITIVE;
+ $innxmit_offered{$server} += $offered;
+ $innxmit_offered{$server} -= $innxmit_ihfail{$server}
+ if ($innxmit_ihfail{$server});
+ $innxmit_accepted{$server} += $accepted;
+ $innxmit_refused{$server} += $refused;
+ $innxmit_rejected{$server} += $rejected;
+ $innxmit_site{$server}++;
+ $innxmit_ihfail{$server} = 0;
+ return 1;
+ }
+ # times
+ if ($left =~ /(\S+) times user (\S+) system (\S+) elapsed (\S+)$/o) {
+ my ($server, $user, $system, $elapsed) = ($1, $2, $3, $4);
+ $server = lc $server unless $CASE_SENSITIVE;
+ $innxmit_times{$server} += $elapsed;
+ return 1;
+ }
+ # connect & no space
+ if ($left =~ /(\S+) connect \S+ 400 No space/o) {
+ my $server = $1;
+ $server = lc $server unless $CASE_SENSITIVE;
+ $innxmit_nospace{$server}++;
+ $innxmit_site{$server}++;
+ return 1;
+ }
+ # connect & NNTP no space
+ if ($left =~ /(\S+) connect \S+ 400 \S+ out of space/o) {
+ my $server = $1;
+ $server = lc $server unless $CASE_SENSITIVE;
+ $innxmit_nospace{$server}++;
+ $innxmit_site{$server}++;
+ return 1;
+ }
+ # connect & loadav
+ if ($left =~ /(\S+) connect \S+ 400 loadav/o) {
+ my $server = $1;
+ if ($left =~ /expir/i) {
+ $server = lc $server unless $CASE_SENSITIVE;
+ $innxmit_expire{$server}++;
+ $innxmit_site{$server}++;
+ return 1;
+ }
+ }
+ # connect 400 (other)
+ if ($left =~ /(\S+) connect \S+ 400/o) {
+ my $server = $1;
+ $server = lc $server unless $CASE_SENSITIVE;
+ $innxmit_crefused{$server}++;
+ $innxmit_site{$server}++;
+ return 1;
+ }
+ # connect failed
+ if ($left =~ /(\S+) connect failed/o) {
+ my $server = $1;
+ $server = lc $server unless $CASE_SENSITIVE;
+ $innxmit_cfail_host{$server}++;
+ $innxmit_site{$server}++;
+ return 1;
+ }
+ # authenticate failed
+ if ($left =~ /(\S+) authenticate failed/o) {
+ my $server = $1;
+ $server = lc $server unless $CASE_SENSITIVE;
+ $innxmit_afail_host{$server}++;
+ $innxmit_site{$server}++;
+ return 1;
+ }
+ # xxx ihave failed 400 loadav [innwatch:hiload] yyy gt zzz
+ if ($left =~ /^(\S+) ihave failed 400 loadav/o) {
+ my $server = $1;
+ $server = lc $server unless $CASE_SENSITIVE;
+ $innxmit_hiload{$server}++;
+ return 1;
+ }
+ # ihave failed
+ return 1 if ($left =~ /\S+ ihave failed/o);
+ # requeued (....) 436 No space
+ return 1 if ($left =~ /\S+ requeued \S+ 436 No space/o);
+ # requeued (....) 400 No space
+ return 1 if ($left =~ /\S+ requeued \S+ 400 No space/o);
+ # requeued (....) 436 Can't write history
+ return 1 if ($left =~ /\S+ requeued \S+ 436 Can\'t write history/o);
+ # unexpected response code
+ return 1 if ($left =~ /unexpected response code /o);
+ }
+
+ ########
+ ## nntplink
+ if ($prog eq "nntplink") {
+ $left =~ s/^(\S+):/$1/;
+ # EOF
+ if ($left =~ /(\S+) EOF /o) {
+ my $server = $1;
+ $server = lc $server unless $CASE_SENSITIVE;
+ $nntplink_site{$server}++;
+ $nntplink_eof{$server}++;
+ return 1;
+ }
+ # Broken pipe
+ if ($left =~ /(\S+) Broken pipe$/o) {
+ my $server = $1;
+ $server = lc $server unless $CASE_SENSITIVE;
+ $nntplink_site{$server}++;
+ $nntplink_bpipe{$server}++;
+ return 1;
+ }
+ # already running - won't die
+ return 1 if $left =~ /\S+ nntplink.* already running /o;
+ # connection timed out
+ if ($left =~ /(\S+) connection timed out/o) {
+ my $server = $1;
+ $server = lc $server unless $CASE_SENSITIVE;
+ $nntplink_site{$server}++;
+ $nntplink_bpipe{$server}++;
+ return 1;
+ }
+ # greeted us with 400 No space
+ if ($left =~ /(\S+) greeted us with 400 No space/o) {
+ my $server = $1;
+ $server = lc $server unless $CASE_SENSITIVE;
+ $nntplink_site{$server}++;
+ $nntplink_nospace{$server}++;
+ return 1;
+ }
+ # greeted us with 400 loadav
+ if ($left =~ /(\S+) greeted us with 400 loadav/o) {
+ my $server = $1;
+ $server = lc $server unless $CASE_SENSITIVE;
+ $nntplink_site{$server}++;
+ $nntplink_hiload{$server}++;
+ return 1;
+ }
+ # greeted us with 400 (other)
+ if ($left =~ /(\S+) greeted us with 400/o) {
+ my $server = $1;
+ $server = lc $server unless $CASE_SENSITIVE;
+ $nntplink_site{$server}++;
+ if ($left =~ /expir/i) {
+ $nntplink_expire{$server}++;
+ } else {
+ $nntplink_fail{$server}++;
+ }
+ return 1;
+ }
+ # greeted us with 502
+ if ($left =~ /(\S+) greeted us with 502/o) {
+ my $server = $1;
+ $server = lc $server unless $CASE_SENSITIVE;
+ $nntplink_site{$server}++;
+ $nntplink_auth{$server}++;
+ return 1;
+ }
+ # sent authinfo
+ if ($left =~ /(\S+) sent authinfo/o) {
+ my $server = $1;
+ $server = lc $server unless $CASE_SENSITIVE;
+ $nntplink_site{$server}++;
+ $nntplink_auth{$server}++;
+ return 1;
+ }
+ # socket()
+ if ($left =~ /(\S+) socket\(\): /o) {
+ my $server = $1;
+ $server = lc $server unless $CASE_SENSITIVE;
+ $nntplink_site{$server}++;
+ $nntplink_sockerr{$server}++;
+ return 1;
+ }
+ # select()
+ if ($left =~ /(\S+) select\(\) /o) {
+ my $server = $1;
+ $server = lc $server unless $CASE_SENSITIVE;
+ $nntplink_site{$server}++;
+ $nntplink_selecterr{$server}++;
+ return 1;
+ }
+ # sent IHAVE
+ if ($left =~ /(\S+) sent IHAVE/o) {
+ my $server = $1;
+ $server = lc $server unless $CASE_SENSITIVE;
+ $nntplink_ihfail{$server}++;
+ if (($left =~ / 436 /) && ($left =~ / out of space /)) {
+ $nntplink_fake_connects{$server}++;
+ $nntplink_nospace{$server}++;
+ }
+ return 1;
+ }
+ # article .... failed(saved): 436 No space
+ if ($left =~ /(\S+) .* failed\(saved\): 436 No space$/o) {
+ my $server = $1;
+ $server = lc $server unless $CASE_SENSITIVE;
+ $nntplink_nospace{$server}++;
+ return 1;
+ }
+ # article .. 400 No space left on device writing article file -- throttling
+ if ($left =~ /(\S+) .* 400 No space left on device writing article file -- throttling$/o) {
+ my $server = $1;
+ $server = lc $server unless $CASE_SENSITIVE;
+ $nntplink_nospace{$server}++;
+ return 1;
+ }
+ # stats
+ if ($left =~ /(\S+) stats (\d+) offered (\d+) accepted (\d+) rejected (\d+) failed (\d+) connects$/o) {
+ my ($server, $offered, $accepted, $rejected, $failed, $connects) =
+ ($1, $2, $3, $4, $5, $6);
+ $server = lc $server unless $CASE_SENSITIVE;
+ $nntplink_offered{$server} += $offered - $nntplink_ihfail{$server}++;
+ $nntplink_accepted{$server} += $accepted;
+ $nntplink_rejected{$server} += $rejected;
+ $nntplink_failed{$server} += $failed;
+ $nntplink_connects{$server} += $connects;
+ $nntplink_ihfail{$server} = 0;
+ if ($nntplink_fake_connects{$server}) {
+ $nntplink_site{$server} += $nntplink_fake_connects{$server};
+ $nntplink_fake_connects{$server} = 0;
+ } else {
+ $nntplink_site{$server}++;
+ }
+ return 1;
+ }
+ # xmit
+ if ($left =~ /(\S+) xmit user (\S+) system (\S+) elapsed (\S+)$/o) {
+ my ($server, $user, $system, $elapsed) = ($1, $2, $3, $4);
+ $server = lc $server unless $CASE_SENSITIVE;
+ $nntplink_times{$server} += $elapsed;
+ return 1;
+ }
+ # xfer
+ return 1 if $left =~ /\S+ xfer/o;
+ # Links down .. x hours
+ if ($left =~ /(\S+) Links* down \S+ \d+/o) {
+ # Collected but not used
+ # my $server = $1;
+ # $server = lc $server unless $CASE_SENSITIVE;
+ # $nntplink_down{$server} += $hours;
+ return 1;
+ }
+ # 503 Timeout
+ if ($left =~ /^(\S+) \S+ \S+ \S+ 503 Timeout/o) {
+ # Collected but not used
+ # my $server = $1;
+ # $server = lc $server unless $CASE_SENSITIVE;
+ # $nntplink_timeout{$server}++;
+ return 1;
+ }
+ # read() error while reading reply
+ if ($left =~ /^(\S+): read\(\) error while reading reply/o) {
+ my $server = $1;
+ $server = lc $server unless $CASE_SENSITIVE;
+ $nntplink_failed{$server}++;
+ return 1;
+ }
+ # Password file xxxx not found
+ return 1 if $left =~ /^\S+ Password file \S+ not found/;
+ # No such
+ return 1 if $left =~ /^\S+ \S+ \S+ No such/;
+ # already running
+ return 1 if $left =~ /^\S+ \S+ already running/;
+ # error reading version from datafile
+ return 1 if $left =~ /error reading version from datafile/;
+ }
+ ########
+ ## nnrpd
+ if ($prog =~ /^nnrpd(?:-ssl)?$/)
+ {
+ # Fix a small bug of nnrpd (inn 1.4*)
+ $left =~ s/^ /\? /o;
+ # Another bug (in INN 1.5b1)
+ return 1 if $left =~ /^\020\002m$/o; # ^P^Bm
+ # bad_history at num for <ref>
+ return 1 if $left =~ /bad_history at \d+ for /o;
+ # timeout short
+ return 1 if $left =~ /\S+ timeout short$/o;
+ # < or > + (blablabla)
+ return 1 if $left =~ /^\S+ [\<\>] /o;
+ # cant opendir ... I/O error
+ return 1 if $left =~ /\S+ cant opendir \S+ I\/O error$/o;
+ # perl filtering enabled
+ return 1 if $left =~ /perl filtering enabled$/o;
+ # Python filtering enabled
+ return 1 if $left =~ /Python filtering enabled$/o;
+ return 1 if $left =~ /^python interpreter initialized OK$/o;
+ return 1 if $left =~ /^python method \S+ not found$/o;
+ return 1 if $left =~ /^python authenticate method succeeded, return code \d+, error string /o;
+ return 1 if $left =~ /^python access method succeeded$/o;
+ return 1 if $left =~ /^python dynamic method \(\w+ access\) succeeded, refusion string: /o;
+ return 1 if $left =~ /^python: .+ module successfully hooked into nnrpd$/o;
+ return 1 if $left =~ /^python: nnrpd .+ class instance created$/o;
+ return 1 if $left =~ /^python: n_a authenticate\(\) invoked: hostname \S+, ipaddress \S+, interface \S+, user /o;
+ return 1 if $left =~ /^python: n_a access\(\) invoked: hostname \S+, ipaddress \S+, interface \S+, user /o;
+ return 1 if $left =~ /^python: n_a dynamic\(\) invoked against type \S+, hostname \S+, ipaddress \S+, interface \S+, user /o;
+ return 1 if $left =~ /^python: authentication by username succeeded$/o;
+ return 1 if $left =~ /^python: authentication by username failed$/o;
+ return 1 if $left =~ /^python: authentication access by IP address succeeded$/o;
+ return 1 if $left =~ /^python: authentication access by IP address failed$/o;
+ return 1 if $left =~ /^python: dynamic access module successfully hooked into nnrpd$/o;
+ return 1 if $left =~ /^python: dynamic authorization access for read access granted$/o;
+ return 1 if $left =~ /^python: dynamic authorization access type is not known: /o;
+ # connect
+ if ($left =~ /(\S+) (\([0-9a-fA-F:.]*\) )?connect$/o) {
+ my $cust = $1;
+ $cust = lc $cust unless $CASE_SENSITIVE;
+ my $dom = &host2dom($cust);
+ $nnrpd_dom_connect{$dom}++;
+ $nnrpd_connect{$cust}++;
+ return 1;
+ }
+ # group
+ if ($left =~ /(\S+) group (\S+) (\d+)$/o) {
+ my ($cust, $group, $num) = ($1, $2, $3);
+ if ($num) {
+ $nnrpd_group{$group} += $num;
+ my ($hierarchy) = $group =~ /^([^\.]+).*$/o;
+ $nnrpd_hierarchy{$hierarchy} += $num;
+ }
+ return 1;
+ }
+ # post failed
+ if ($left =~ /(\S+) post failed (.*)$/o) {
+ my ($cust, $error) = ($1, $2);
+ $nnrpd_post_error{$error}++;
+ return 1;
+ }
+ # post ok
+ return 1 if $left =~ /\S+ post ok/o;
+ # posts
+ if ($left =~ /(\S+) posts received (\d+) rejected (\d+)$/o) {
+ my ($cust, $received, $rejected) = ($1, $2, $3);
+ $cust = lc $cust unless $CASE_SENSITIVE;
+ my $dom = &host2dom($cust);
+ $nnrpd_dom_post_ok{$dom} += $received;
+ $nnrpd_dom_post_rej{$dom} += $rejected;
+ $nnrpd_post_ok{$cust} += $received;
+ $nnrpd_post_rej{$cust} += $rejected;
+ return 1;
+ }
+ # noperm post without permission
+ if ($left =~ /(\S+) noperm post without permission/o) {
+ my $cust = $1;
+ $cust = lc $cust unless $CASE_SENSITIVE;
+ my $dom = &host2dom($cust);
+ $nnrpd_dom_post_rej{$dom} ++;
+ $nnrpd_post_rej{$cust} ++;
+ return 1;
+ }
+ # no_permission
+ if ($left =~ /(\S+) no_(permission|access)$/o) {
+ my $cust = $1;
+ $cust = lc $cust unless $CASE_SENSITIVE;
+ my $dom = &host2dom($cust);
+ $nnrpd_no_permission{$cust}++;
+ $nnrpd_dom_no_permission{$dom}++;
+ return 1;
+ }
+ # bad_auth
+ if ($left =~ /(\S+) bad_auth$/o) {
+ my $cust = $1;
+ $cust = lc $cust unless $CASE_SENSITIVE;
+ my $dom = &host2dom($cust);
+ $nnrpd_dom_no_permission{$dom}++;
+ $nnrpd_no_permission{$cust}++;
+ return 1;
+ }
+ # Authentication failure
+ # User not known to the underlying authentication module
+ return 1 if $left =~ / ckpasswd: pam_authenticate failed: /o;
+ return 1 if $left =~ / ckpasswd: user .+ unknown$/o;
+ # authinfo
+ if ($left =~ /\S+ user (\S+)$/o) {
+ my $user = $1;
+ $nnrpd_auth{$user}++;
+ return 1;
+ }
+ # unrecognized + command
+ if ($left =~ /(\S+) unrecognized (.*)$/o) {
+ my ($cust, $error) = ($1, $2);
+ $cust = lc $cust unless $CASE_SENSITIVE;
+ my $dom = &host2dom($cust);
+ $error = "_null command_" if ($error !~ /\S/);
+ $error =~ s/^(xmotd) .*$/$1/i if ($error =~ /^xmotd .*$/i);
+ $nnrpd_dom_unrecognized{$dom}++;
+ $nnrpd_unrecognized{$cust}++;
+ $nnrpd_unrecogn_cmd{$error}++;
+ return 1;
+ }
+ # exit
+ if ($left =~ /(\S+) exit articles (\d+) groups (\d+)$/o) {
+ my ($cust, $articles, $groups) = ($1, $2, $3);
+ $cust = lc $cust unless $CASE_SENSITIVE;
+ my $dom = &host2dom($cust) || '?';
+ $nnrpd_connect{$cust}++, $nnrpd_dom_connect{$dom}++ if $cust eq '?';
+ $nnrpd_groups{$cust} += $groups;
+ $nnrpd_dom_groups{$dom} += $groups;
+ $nnrpd_articles{$cust} += $articles;
+ $nnrpd_dom_articles{$dom} += $articles;
+ return 1;
+ }
+ # times
+ if ($left =~ /(\S+) times user (\S+) system (\S+) idle (\S+) elapsed (\S+)$/o) {
+ my ($cust, $user, $system, $idle, $elapsed) = ($1, $2, $3, $4, $5);
+ $cust = lc $cust unless $CASE_SENSITIVE;
+ my $dom = &host2dom($cust);
+ $nnrpd_times{$cust} += $elapsed;
+ $nnrpd_resource_user{$cust} += $user;
+ $nnrpd_resource_system{$cust} += $system;
+ $nnrpd_resource_idle{$cust} += $idle;
+ $nnrpd_resource_elapsed{$cust} += $elapsed;
+ $nnrpd_dom_times{$dom} += $elapsed;
+ return 1;
+ }
+ # artstats
+ if ($left =~ /(\S+) artstats get (\d+) time (\d+) size (\d+)$/o) {
+ my ($cust, $articles, $time, $bytes) = ($1, $2, $3, $4);
+ $cust = lc $cust unless $CASE_SENSITIVE;
+ my $dom = &host2dom($cust);
+ $nnrpd_bytes{$cust} += $bytes;
+ $nnrpd_dom_bytes{$dom} += $bytes;
+ return 1;
+ }
+ # timeout
+ if ($left =~ /(\S+) timeout$/o) {
+ my $cust = $1;
+ $cust = lc $cust unless $CASE_SENSITIVE;
+ my $dom = &host2dom($cust);
+ $nnrpd_dom_timeout{$dom}++;
+ $nnrpd_timeout{$cust}++;
+ return 1;
+ }
+ # timeout in post
+ if ($left =~ /(\S+) timeout in post$/o) {
+ my $cust = $1;
+ $cust = lc $cust unless $CASE_SENSITIVE;
+ my $dom = &host2dom($cust);
+ $nnrpd_dom_timeout{$dom}++;
+ $nnrpd_timeout{$cust}++;
+ return 1;
+ }
+ # can't read: Connection timed out
+ if ($left =~ /(\S+) can\'t read: Connection timed out$/o) {
+ my $cust = $1;
+ $cust = lc $cust unless $CASE_SENSITIVE;
+ my $dom = &host2dom($cust);
+ $nnrpd_dom_timeout{$dom}++;
+ $nnrpd_timeout{$cust}++;
+ return 1;
+ }
+ # can't read: Operation timed out
+ if ($left =~ /(\S+) can\'t read: Operation timed out$/o) {
+ my $cust = $1;
+ $cust = lc $cust unless $CASE_SENSITIVE;
+ my $dom = &host2dom($cust);
+ $nnrpd_dom_timeout{$dom}++;
+ $nnrpd_timeout{$cust}++;
+ return 1;
+ }
+ # can't read: Connection reset by peer
+ if ($left =~ /(\S+) can\'t read: Connection reset by peer$/o) {
+ my $cust = $1;
+ $cust = lc $cust unless $CASE_SENSITIVE;
+ my $dom = &host2dom($cust);
+ $nnrpd_dom_reset_peer{$dom}++;
+ $nnrpd_reset_peer{$cust}++;
+ return 1;
+ }
+ # can't read: Network is unreachable
+ return 1 if $left =~ /(\S+) can\'t read: Network is unreachable$/o;
+ # gethostbyaddr: xxx.yyy.zzz != a.b.c.d
+ if ($left =~ /^gethostbyaddr: (.*)$/o) {
+ my $msg = $1;
+ $nnrpd_gethostbyaddr{$msg}++;
+ return 1;
+ }
+ # cant gethostbyaddr
+ if ($left =~ /\? cant gethostbyaddr (\S+) .*$/o) {
+ my $ip = $1;
+ $nnrpd_gethostbyaddr{$ip}++;
+ return 1;
+ }
+ # cant getpeername
+ if ($left =~ /\? cant getpeername/o) {
+ # $nnrpd_getpeername++;
+ $nnrpd_gethostbyaddr{"? (can't getpeername)"}++;
+ return 1;
+ }
+ # can't getsockname
+ return 1 if $left =~ /^\S+ can\'t getsockname$/o;
+ # reverse lookup failed
+ return 1 if $left =~ /^\? reverse lookup for \S+ failed: .* -- using IP address for access$/o;
+ # profile timer
+ # ME time X nnnn X(X) [...]
+ # The exact timers change from various versions of INN, so try to deal
+ # with this in a general fashion.
+ if ($left =~ m/^\S+\s+ # ME
+ time\ (\d+)\s+ # time
+ ((?:\S+\ \d+\(\d+\)\s*)+) # timer values
+ $/ox) {
+ $nnrpd_time_times += $1;
+ my $timers = $2;
+
+ while ($timers =~ /(\S+) (\d+)\((\d+)\)\s*/g) {
+ my $name = $nnrpd_timer_names{$1} || $1;
+ my $average = $2 / ($3 || 1);
+ $nnrpd_time_time{$name} += $2;
+ $nnrpd_time_num{$name} += $3;
+ if ($3) {
+ my $min = $nnrpd_time_min{$name};
+ $nnrpd_time_min{$name} = $average
+ if (defined($min) && $min > $average);
+ my $max = $nnrpd_time_max{$name};
+ $nnrpd_time_max{$name} = $average
+ if (defined($max) && $max < $average);
+ }
+ }
+ return 1;
+ }
+ # ME dropping articles into ...
+ return 1 if $left =~ /ME dropping articles into /o;
+ # newnews (interesting but ignored till now)
+ return 1 if $left =~ /^\S+ newnews /o;
+ # cant fopen (ignored too)
+ return 1 if $left =~ /^\S+ cant fopen /o;
+ # can't read: No route to host
+ return 1 if $left =~ /can\'t read: No route to host/o;
+ # can't read: Broken pipe
+ return 1 if $left =~ /can\'t read: Broken pipe/o;
+ # eof in post
+ return 1 if $left =~ /^\S+ eof in post$/o;
+ # ioctl: ...
+ return 1 if $left =~ /^ioctl: /o;
+ # other stats
+ return 1 if $left =~ /^\S+ overstats count \d+ hit \d+ miss \d+ time \d+ size \d+ dbz \d+ seek \d+ get \d+ artcheck \d+$/o;
+ # starttls
+ return 1 if $left =~ /^starttls: \S+ with cipher \S+ \(\d+\/\d+ bits\) no authentication$/o;
+ }
+ ########
+ ## inndstart
+ if ($prog eq "inndstart") {
+ # cant bind Address already in use
+ # cant bind Permission denied
+ return 1 if $left =~ /cant bind /o;
+ # cant setgroups Operation not permitted
+ return 1 if $left =~ /cant setgroups /o;
+ }
+ ########
+ ## overchan
+ if ($prog eq "overchan") {
+ # times
+ if ($left =~ /timings (\d+) arts (\d+) of (\d+) ms$/o) {
+ my ($articles, $work_time, $run_time) = ($1, $2, $3);
+ # ??? What to do with numbers
+ return 1;
+ }
+ }
+ ########
+ ## batcher
+ if ($prog eq "batcher") {
+ # times
+ if ($left =~ /(\S+) times user (\S+) system (\S+) elapsed (\S+)$/o) {
+ my ($server, $user, $system, $elapsed) = ($1, $2, $3, $4);
+ $server = lc $server unless $CASE_SENSITIVE;
+ # $batcher_user{$server} += $user;
+ # $batcher_system{$server} += $system;
+ $batcher_elapsed{$server} += $elapsed;
+ return 1;
+ }
+ # stats
+ if ($left =~ /(\S+) stats batches (\d+) articles (\d+) bytes (\d+)$/o) {
+ my ($server, $batches, $articles, $bytes) = ($1, $2, $3, $4);
+ $server = lc $server unless $CASE_SENSITIVE;
+ $batcher_offered{$server} += $batches;
+ $batcher_articles{$server} += $articles;
+ $batcher_bytes{$server} += $bytes;
+ return 1;
+ }
+ }
+ ########
+ ## rnews
+ if ($prog eq "rnews") {
+ # rejected connection
+ if ($left =~ /rejected connection (.*)$/o) {
+ $rnews_rejected{$1}++;
+ return 1;
+ }
+ # cant open_remote
+ if ($left =~ /(cant open_remote .*)$/o) {
+ $rnews_rejected{$1}++;
+ return 1;
+ }
+ # rejected 437 Unwanted newsgroup
+ if ($left =~ /rejected 437 Unwanted newsgroup \"(.*)\"$/o) {
+ $rnews_bogus_ng{$1}++;
+ return 1;
+ }
+ # rejected 437 Unapproved for "xx"
+ if ($left =~ /rejected 437 Unapproved for \"(.*)\"$/o) {
+ $rnews_unapproved{$1}++;
+ return 1;
+ }
+ # rejected 437 Unwanted distribution
+ if ($left =~ /rejected 437 Unwanted distribution (.*)$/o) {
+ $rnews_bogus_dist{$1}++;
+ return 1;
+ }
+ # rejected 437 Bad "Date"
+ if ($left =~ /rejected 437 Bad \"Date\" (.*)$/o) {
+ $rnews_bogus_date{$1}++;
+ return 1;
+ }
+ # rejected 437 Article posted in the future
+ if ($left =~ /rejected 437 Article posted in the future -- \"(.*)\"$/o) {
+ $rnews_bogus_date{"(future) $1"}++;
+ return 1;
+ }
+ # rejected 437 Too old -- "..."
+ if ($left =~ /rejected 437 Too old -- (.*)$/o) {
+ $rnews_too_old++;
+ return 1;
+ }
+ # rejected 437 Linecount...
+ if ($left =~ /rejected 437 (Linecount) \d+ \!= \d+/o) {
+ $rnews_linecount++;
+ return 1;
+ }
+ # rejected 437 Duplicate
+ if ($left =~ /rejected 437 Duplicate$/o) {
+ $rnews_duplicate++;
+ return 1;
+ }
+ # rejected 437 Duplicate article
+ if ($left =~ /rejected 437 (Duplicate article)/o) {
+ $rnews_duplicate++;
+ return 1;
+ }
+ # rejected 437 No colon-space ...
+ if ($left =~ /rejected 437 No colon-space in \"(.*)\" header$/o) {
+ $rnews_no_colon_space++;
+ return 1;
+ }
+ # duplicate <msg-id> path..
+ if ($left =~ /^duplicate /o) {
+ $rnews_duplicate++;
+ return 1;
+ }
+ # offered <msg-id> feed
+ if ($left =~ /^offered \S+ (\S+)/o) {
+ my $host = $1;
+ $host = lc $host unless $CASE_SENSITIVE;
+ # Small hack used to join article spooled when innd is throttle.
+ # In this situation, the hostname is a 8 hex digits string
+ # To avoid confusions with real feeds, the first character is forced
+ # to be a '3' or a '4' (will work between 9/7/1995 and 13/7/2012).
+ $host = "Local postings" if $host =~ /^[34][0-9a-f]{7}$/;
+ $rnews_host{$host}++;
+ return 1;
+ }
+ # rejected 437 ECP rejected
+ return 1 if $left =~ m/rejected 437 ECP rejected/o;
+ # rejected 437 "Subject" header too long
+ return 1 if $left =~ m/header too long/o;
+ # rejected 437 Too long line in header 1163 bytes
+ return 1 if $left =~ m/rejected 437 Too long line in header/o;
+ # rejected 437 Too many newsgroups (meow)
+ return 1 if $left =~ m/rejected 437 Too many newsgroups/o;
+ # rejected 437 Space before colon in "<a" header
+ return 1 if $left =~ m/rejected 437 Space before colon in/o;
+ # rejected 437 EMP (phl)
+ return 1 if $left =~ m/rejected 437 EMP/o;
+ # rejected 437 Scoring filter (8)
+ return 1 if $left =~ m/rejected 437 Scoring filter/o;
+ # bad_article missing Message-ID
+ return 1 if $left =~ m/bad_article missing Message-ID/o;
+ # cant unspool saving to xxx
+ return 1 if $left =~ m/cant unspool saving to/o;
+ }
+
+ ###########
+ ## ncmspool
+ if ($prog eq "ncmspool") {
+ # <article> good signature from foo@bar.com
+ if ($left =~ /good signature from (.*)/o) {
+ $nocem_goodsigs{$1}++;
+ $nocem_totalgood++;
+ $nocem_lastid = $1;
+ return 1;
+ }
+ # <article> bad signature from foo@bar.com
+ if ($left =~ /bad signature from (.*)/o) {
+ $nocem_badsigs{$1}++;
+ $nocem_goodsigs{$1} = 0 unless ($nocem_goodsigs{$1});
+ $nocem_totalbad++;
+ $nocem_lastid = $1;
+ return 1;
+ }
+ # <article> contained 123 new 456 total ids
+ if ($left =~ /contained (\d+) new (\d+) total ids/o) {
+ $nocem_newids += $1;
+ $nocem_newids{$nocem_lastid} += $1;
+ $nocem_totalids += $2;
+ $nocem_totalids{$nocem_lastid} += $2;
+ return 1;
+ }
+ return 1;
+ }
+
+ ########
+ ## nocem
+ if ($prog eq "nocem") {
+ if ($left =~ /processed notice .* by (.*) \((\d+) ids,/o) {
+ $nocem_goodsigs{$1}++;
+ $nocem_totalgood++;
+ $nocem_lastid = $1;
+ $nocem_newids += $2;
+ $nocem_newids{$nocem_lastid} += $2;
+ $nocem_totalids += $2;
+ $nocem_totalids{$nocem_lastid} += $2;
+ return 1;
+ }
+ if ($left =~ /bad signature from (.*)/o) {
+ $nocem_badsigs{$1}++;
+ $nocem_goodsigs{$1} = 0 unless ($nocem_goodsigs{$1});
+ $nocem_totalbad++;
+ $nocem_lastid = $1;
+ return 1;
+ }
+ return 1;
+ }
+
+ ###########
+ ## controlchan
+ if ($prog eq "controlchan") {
+ # loaded /x/y/z/foo.pl
+ return 1 if $left =~ m/^loaded /;
+ # starting
+ return 1 if $left =~ m/^starting/;
+ # skipping rmgroup x@y (pgpverify failed) in <foo@bar>
+ if ($left =~ m/^skipping \S+ (\S+) \(pgpverify failed\) in /) {
+ $controlchan_skippgp{$1}++;
+ $controlchan_who{$1}++;
+ return 1;
+ }
+ if ($left =~ m/^control_(sendme|ihave), [^,]+, (\S+), doit,/o) {
+ if ($1 eq "sendme") {
+ $controlchan_sendme_site{$2}++;
+ } else {
+ $controlchan_ihave_site{$2}++;
+ }
+ return 1;
+ }
+ # control_XXgroup, foo.bar [moderated] who who /x/y/12, peer, action, 1
+ #
+ # Various other random junk can end up in the moderated field, like y,
+ # unmoderated, m, etc. depending on what the control message says. It
+ # can even have multiple words, which we still don't handle.
+ if ($left =~ m/^control_(\S+), # type of msg
+ \s(?:\S+)? # newsgroup name
+ (\s\S+)? # optional
+ \s(\S+) # e-mail
+ \s\S+ # e-mail
+ \s\S+, # filename
+ \s\S+, # server
+ \s([^=,]+(?:=\S+)?), # action
+ \s*(.*) # code
+ /x) {
+ if ($1 eq 'newgroup') {
+ $controlchan_new{$3}++;
+ } elsif ($1 eq 'rmgroup') {
+ $controlchan_rm{$3}++;
+ } else {
+ $controlchan_other{$3}++;
+ }
+ $controlchan_who{$3}++;
+ $controlchan_ok{$3} += $5;
+ my $action = $4;
+ my $email = $3;
+ $action =~ s/=.*//;
+ $controlchan_doit{$email}++ if $action eq 'doit';
+ return 1;
+ }
+ # checkgroups processed (no change or not)
+ return 1 if $left =~ /^checkgroups by \S+ processed/o;
+ }
+
+ ###########
+ ## crosspost
+ if ($prog eq "crosspost") {
+ # seconds 1001 links 3182 0 symlinks 0 0 mkdirs 0 0
+ # missing 13 toolong 0 other 0
+ if ($left =~ /^seconds\ (\d+)
+ \ links\ (\d+)\ (\d+)
+ \ symlinks\ (\d+)\ (\d+)
+ \ mkdirs\ (\d+)\ (\d+)
+ \ missing\ (\d+)
+ \ toolong\ (\d+)
+ \ other\ (\d+)
+ $/ox) {
+ $crosspost_time += $1;
+ $crosspost{'Links made'} += $2;
+ $crosspost{'Links failed'} += $3;
+ $crosspost{'Symlinks made'} += $4;
+ $crosspost{'Symlinks failed'} += $5;
+ $crosspost{'Mkdirs made'} += $6;
+ $crosspost{'Mkdirs failed'} += $7;
+ $crosspost{'Files missing'} += $8;
+ $crosspost{'Paths too long'} += $9;
+ $crosspost{'Others'} += $10;
+ return 1;
+ }
+ }
+
+ ###########
+ ## cnfsstat
+ if ($prog eq "cnfsstat") {
+ # Class ALT for groups matching "alt.*" article size min/max: 0/1048576
+ # Buffer T3, len: 1953 Mbytes, used: 483.75 Mbytes (24.8%) 0 cycles
+ if ($left =~ m|^Class\ (\S+)\ for\ groups\ matching\ \S+
+ (\ article\ size\ min/max:\ \d+/\d+)?
+ \ Buffer\ (\S+),
+ \ len:\ ([\d.]+)\s+Mbytes,
+ \ used:\ ([\d.]+)\ Mbytes\ \(\s*[\d.]+%\)
+ \s+(\d+)\ cycles\s*
+ $|ox) {
+ my ($class, $buffer, $size, $used, $cycles) = ($1, $3, $4, $5, $6);
+ my ($h, $m, $s) = $hour =~ m/^(\d+):(\d+):(\d+)$/;
+ my $time = $h * 3600 + $m * 60 + $s;
+ $size *= 1024 * 1024;
+ $used *= 1024 * 1024;
+ $cnfsstat{$buffer} = $class;
+
+ # If the size changed, invalidate all of our running fill rate stats.
+ if (!exists($cnfsstat_size{$buffer}) || $size != $cnfsstat_size{$buffer}) {
+ delete $cnfsstat_rate{$buffer};
+ delete $cnfsstat_samples{$buffer};
+ delete $cnfsstat_time{$buffer};
+ $cnfsstat_size{$buffer} = $size;
+ }
+ elsif ($cnfsstat_time{$buffer}) {
+ # We want to gather the rate at which cycbuffs fill. Store a
+ # running total of bytes/second and a total number of samples.
+ # Ideally we'd want a weighted average of those samples by the
+ # length of the sample period, but we'll ignore that and assume
+ # cnfsstat runs at a roughly consistent interval.
+ my ($period, $added);
+ $period = $time - $cnfsstat_time{$buffer};
+ $period = 86400 - $cnfsstat_time{$buffer} + $time if $period <= 0;
+ $added = $used - $cnfsstat_used{$buffer};
+ if ($cycles > $cnfsstat_cycles{$buffer}) {
+ $added += $size * ($cycles - $cnfsstat_cycles{$buffer});
+ }
+ if ($added > 0) {
+ $cnfsstat_rate{$buffer} += $added / $period;
+ $cnfsstat_samples{$buffer}++;
+ }
+ }
+ $cnfsstat_used{$buffer} = $used;
+ $cnfsstat_cycles{$buffer} = $cycles;
+ $cnfsstat_time{$buffer} = $time;
+ return 1;
+ }
+ }
+
+ # Ignore following programs :
+ return 1 if ($prog eq "uxfxn");
+ return 1 if ($prog eq "beverage");
+ return 1 if ($prog eq "newsx");
+ return 1 if ($prog eq "demmf");
+ return 1 if ($prog eq "nnnn");
+ return 1 if ($prog eq "slurp");
+ return 0;
+}
+
+#################################
+# Adjust some values..
+
+sub adjust {
+ my ($first_date, $last_date) = @_;
+
+ my $nnrpd_doit = 0;
+ my $curious;
+
+ {
+ my $serv;
+ if (%nnrpd_connect) {
+ my $c = keys (%nnrpd_connect);
+ foreach $serv (keys (%nnrpd_connect)) {
+ my $dom = &host2dom($serv);
+ if ($nnrpd_no_permission{$serv}) {
+ $nnrpd_dom_connect{$dom} -= $nnrpd_connect{$serv}
+ if defined $nnrpd_dom_connect{$dom} && defined $nnrpd_connect{$serv};
+ $nnrpd_dom_groups{$dom} -= $nnrpd_groups{$serv}
+ if defined $nnrpd_dom_groups{$dom} && defined $nnrpd_groups{$serv};
+ $nnrpd_dom_times{$dom} -= $nnrpd_times{$serv}
+ if defined $nnrpd_dom_times{$dom};
+ $nnrpd_connect{$serv} -= $nnrpd_no_permission{$serv};
+ $nnrpd_groups{$serv} -= $nnrpd_no_permission{$serv}
+ if defined $nnrpd_groups{$serv};
+ delete $nnrpd_connect{$serv} unless $nnrpd_connect{$serv};
+ delete $nnrpd_groups{$serv} unless $nnrpd_groups{$serv};
+ delete $nnrpd_times{$serv} unless $nnrpd_times{$serv};
+ delete $nnrpd_usr_times{$serv} unless $nnrpd_usr_times{$serv};
+ delete $nnrpd_sys_times{$serv} unless $nnrpd_sys_times{$serv};
+ delete $nnrpd_dom_connect{$dom} unless $nnrpd_dom_connect{$dom};
+ delete $nnrpd_dom_groups{$dom} unless $nnrpd_dom_groups{$dom};
+ delete $nnrpd_dom_times{$dom} unless $nnrpd_dom_times{$dom};
+ $c--;
+ }
+ $nnrpd_doit++
+ if $nnrpd_groups{$serv} || $nnrpd_post_ok{$serv};
+ }
+ undef %nnrpd_connect unless $c;
+ }
+ foreach $serv (keys (%nnrpd_groups)) {
+ $curious = "ok" unless $nnrpd_groups{$serv} || $nnrpd_post_ok{$serv} ||
+ $nnrpd_articles{$serv};
+ }
+ }
+
+ # Fill some hashes
+ {
+ my $key;
+ foreach $key (keys (%innd_connect)) {
+ $innd_offered{$key} = ($innd_accepted{$key} || 0)
+ + ($innd_refused{$key} || 0)
+ + ($innd_rejected{$key} || 0);
+ $innd_offered_size{$key} = ($innd_stored_size{$key} || 0)
+ + ($innd_duplicated_size{$key} || 0);
+ }
+
+
+ # adjust min/max of innd timer stats.
+ if (%innd_time_min) {
+ foreach $key (keys (%innd_time_min)) {
+ $innd_time_min{$key} = 0 if ($innd_time_min{$key} == $MIN);
+ $innd_time_max{$key} = 0 if ($innd_time_max{$key} == $MAX);
+
+ #$innd_time_min{$key} /= 1000;
+ #$innd_time_max{$key} /= 1000;
+ }
+ }
+ if (%innfeed_time_min) {
+ foreach $key (keys (%innfeed_time_min)) {
+ $innfeed_time_min{$key} = 0 if ($innfeed_time_min{$key} == $MIN);
+ $innfeed_time_max{$key} = 0 if ($innfeed_time_max{$key} == $MAX);
+ }
+ }
+ if (%nnrpd_time_min) {
+ foreach $key (keys (%nnrpd_time_min)) {
+ $nnrpd_time_min{$key} = 0 if ($nnrpd_time_min{$key} == $MIN);
+ $nnrpd_time_max{$key} = 0 if ($nnrpd_time_max{$key} == $MAX);
+ }
+ }
+ # remove the innd timer stats if not used.
+ unless ($innd_time_times) {
+ undef %innd_time_min;
+ undef %innd_time_max;
+ undef %innd_time_num;
+ undef %innd_time_time;
+ }
+ # same thing for innfeed timer
+ unless ($innfeed_time_times) {
+ undef %innfeed_time_min;
+ undef %innfeed_time_max;
+ undef %innfeed_time_num;
+ undef %innfeed_time_time;
+ }
+ # same thing for nnrpd timer
+ unless ($nnrpd_time_times) {
+ undef %nnrpd_time_min;
+ undef %nnrpd_time_max;
+ undef %nnrpd_time_num;
+ undef %nnrpd_time_time;
+ }
+
+ # adjust the crosspost stats.
+ if (%crosspost) {
+ foreach $key (keys (%crosspost)) {
+ $crosspost_times{$key} = $crosspost_time ?
+ sprintf "%.2f", $crosspost{$key} / $crosspost_time * 60 : "?";
+ }
+ }
+ }
+
+ if (%inn_flow) {
+ my ($prev_dd, $prev_d, $prev_h) = ("", -1, -1);
+ my $day;
+ foreach $day (sort datecmp keys (%inn_flow)) {
+ my ($r, $h) = $day =~ /^(.*) (\d+)$/;
+ my $d = index ("JanFebMarAprMayJunJulAugSepOctNovDec",
+ substr ($r,0,3)) / 3 * 31 + substr ($r, 4, 2);
+ $prev_h = $h if ($prev_h == -1);
+ if ($prev_d == -1) {
+ $prev_d = $d;
+ $prev_dd = $r;
+ }
+ if ($r eq $prev_dd) { # Same day and same month ?
+ if ($h != $prev_h) {
+ if ($h == $prev_h + 1) {
+ $prev_h++;
+ }
+ else {
+ my $j;
+ for ($j = $prev_h + 1; $j < $h; $j++) {
+ my $t = sprintf "%02d", $j;
+ $inn_flow{"$r $t"} = 0;
+ }
+ $prev_h = $h;
+ }
+ }
+ }
+ else {
+ my $j;
+ # then end of the first day...
+ for ($j = ($prev_h == 23) ? 24 : $prev_h + 1; $j < 24; $j++) {
+ my $t = sprintf "%02d", $j;
+ $inn_flow{"$prev_dd $t"} = 0;
+ }
+
+ # all the days between (if any)
+ # well, we can forget them as it is supposed to be a tool
+ # launched daily.
+
+ # the beginning of the last day..
+ for ($j = 0; $j < $h; $j++) {
+ my $t = sprintf "%02d", $j;
+ $inn_flow{"$r $t"} = 0;
+ }
+ $prev_dd = $r;
+ $prev_d = $d;
+ $prev_h = $h;
+ }
+ }
+ my $first = 1;
+ my (%hash, %hash_time, %hash_size, $date, $delay);
+ foreach $day (sort datecmp keys (%inn_flow)) {
+ my ($r, $h) = $day =~ /^(.*) (\d+)$/o;
+ if ($first) {
+ $first = 0;
+ my ($t) = $first_date =~ m/:(\d\d:\d\d)$/o;
+ $date = "$day:$t - $h:59:59";
+ $t =~ m/(\d\d):(\d\d)/o;
+ $delay = 3600 - $1 * 60 - $2;
+ }
+ else {
+ $date = "$day:00:00 - $h:59:59";
+ $delay = 3600;
+ }
+ $hash{$date} = $inn_flow{$day};
+ $hash_size{$date} = $inn_flow_size{$day};
+ $inn_flow_labels{$date} = $h;
+ $hash_time{$date} = $delay;
+ }
+ my ($h, $t) = $last_date =~ m/ (\d+):(\d\d:\d\d)$/o;
+ my ($h2) = $date =~ m/ (\d+):\d\d:\d\d /o;
+ my $date2 = $date;
+ $date2 =~ s/$h2:59:59$/$h:$t/;
+ $hash{$date2} = $hash{$date};
+ delete $hash{"$date"};
+ $hash_size{$date2} = $hash_size{$date};
+ delete $hash_size{"$date"};
+ $t =~ m/(\d\d):(\d\d)/o;
+ $hash_time{$date2} = $hash_time{$date} - ($h2 == $h) * 3600 + $1 * 60 + $2;
+ delete $hash_time{"$date"};
+ $inn_flow_labels{$date2} = $h;
+ %inn_flow = %hash;
+ %inn_flow_time = %hash_time;
+ %inn_flow_size = %hash_size;
+ }
+
+ if (%innd_bad_ihave) {
+ my $key;
+ my $msg = 'Bad ihave control messages received';
+ foreach $key (keys %innd_bad_ihave) {
+ $innd_misc_stat{$msg}{$key} = $innd_bad_ihave{$key};
+ }
+ }
+ if (%innd_bad_msgid) {
+ my $key;
+ my $msg = 'Bad Message-ID\'s offered';
+ foreach $key (keys %innd_bad_msgid) {
+ $innd_misc_stat{$msg}{$key} = $innd_bad_msgid{$key};
+ }
+ }
+ if (%innd_bad_sendme) {
+ my $key;
+ my $msg = 'Ignored sendme control messages received';
+ foreach $key (keys %innd_bad_sendme) {
+ $innd_misc_stat{$msg}{$key} = $innd_bad_sendme{$key};
+ }
+ }
+ if (%innd_bad_command) {
+ my $key;
+ my $msg = 'Bad command received';
+ foreach $key (keys %innd_bad_command) {
+ $innd_misc_stat{$msg}{$key} = $innd_bad_command{$key};
+ }
+ }
+ if (%innd_bad_newsgroup) {
+ my $key;
+ my $msg = 'Bad newsgroups received';
+ foreach $key (keys %innd_bad_newsgroup) {
+ $innd_misc_stat{$msg}{$key} = $innd_bad_newsgroup{$key};
+ }
+ }
+ if (%innd_posted_future) {
+ my $key;
+ my $msg = 'Article posted in the future';
+ foreach $key (keys %innd_posted_future) {
+ $innd_misc_stat{$msg}{$key} = $innd_posted_future{$key};
+ }
+ }
+ if (%innd_no_colon_space) {
+ my $key;
+ my $msg = 'No colon-space in header';
+ foreach $key (keys %innd_no_colon_space) {
+ $innd_misc_stat{$msg}{$key} = $innd_no_colon_space{$key};
+ }
+ }
+ if (%innd_huge) {
+ my $key;
+ my $msg = 'Huge articles';
+ foreach $key (keys %innd_huge) {
+ $innd_misc_stat{$msg}{$key} = $innd_huge{$key};
+ }
+ }
+ if (%innd_blocked) {
+ my $key;
+ my $msg = 'Blocked server feeds';
+ foreach $key (keys %innd_blocked) {
+ $innd_misc_stat{$msg}{$key} = $innd_blocked{$key};
+ }
+ }
+ if (%innd_strange_strings) {
+ my $key;
+ my $msg = 'Including strange strings';
+ foreach $key (keys %innd_strange_strings) {
+ $innd_misc_stat{$msg}{$key} = $innd_strange_strings{$key};
+ }
+ }
+ if (%rnews_bogus_ng) {
+ my $key;
+ my $msg = 'Unwanted newsgroups';
+ foreach $key (keys %rnews_bogus_ng) {
+ $rnews_misc{$msg}{$key} = $rnews_bogus_ng{$key};
+ }
+ }
+ if (%rnews_bogus_dist) {
+ my $key;
+ my $msg = 'Unwanted distributions';
+ foreach $key (keys %rnews_bogus_dist) {
+ $rnews_misc{$msg}{$key} = $rnews_bogus_dist{$key};
+ }
+ }
+ if (%rnews_unapproved) {
+ my $key;
+ my $msg = 'Articles unapproved';
+ foreach $key (keys %rnews_unapproved) {
+ $rnews_misc{$msg}{$key} = $rnews_unapproved{$key};
+ }
+ }
+ if (%rnews_bogus_date) {
+ my $key;
+ my $msg = 'Bad Date';
+ foreach $key (keys %rnews_bogus_date) {
+ $rnews_misc{$msg}{$key} = $rnews_bogus_date{$key};
+ }
+ }
+
+ $rnews_misc{'Too old'}{'--'} = $rnews_too_old if $rnews_too_old;
+ $rnews_misc{'Bad linecount'}{'--'} = $rnews_linecount if $rnews_linecount;
+ $rnews_misc{'Duplicate articles'}{'--'} = $rnews_duplicate
+ if $rnews_duplicate;
+ $rnews_misc{'No colon-space'}{'--'} = $rnews_no_colon_space
+ if $rnews_no_colon_space;
+
+ if (%nnrpd_groups) {
+ my $key;
+ foreach $key (keys (%nnrpd_connect)) {
+ unless ($nnrpd_groups{"$key"} || $nnrpd_post_ok{"$key"} ||
+ $nnrpd_articles{"$key"}) {
+ $nnrpd_curious{$key} = $nnrpd_connect{$key};
+ undef $nnrpd_connect{$key};
+ }
+ }
+ }
+}
+
+sub report_unwanted_ng {
+ my $file = shift;
+ open (FILE, "$file") && do {
+ while (<FILE>) {
+ my ($c, $n) = $_ =~ m/^\s*(\d+)\s+(.*)$/;
+ next unless defined $n;
+ $n =~ s/^newsgroup //o; # for pre 1.8 logs
+ $inn_uw_ng{$n} += $c;
+ }
+ close (FILE);
+ };
+
+ unlink ("${file}.old");
+ rename ($file, "${file}.old");
+
+ open (FILE, "> $file") && do {
+ my $g;
+ foreach $g (sort {$inn_uw_ng{$b} <=> $inn_uw_ng{$a}} (keys (%inn_uw_ng))) {
+ printf FILE "%d %s\n", $inn_uw_ng{$g}, $g;
+ }
+ close (FILE);
+ chmod(0660, "$file");
+ };
+ unlink ("${file}.old");
+}
+
+###########################################################################
+
+# Compare 2 dates (+hour)
+sub datecmp {
+ # ex: "May 12 06" for May 12, 6:00am
+ local($[) = 0;
+ # The 2 dates are near. The range is less than a few days that's why we
+ # can cheat to determine the order. It is only important if one date
+ # is in January and the other in December.
+
+ my($date1) = substr($a, 4, 2) * 24;
+ my($date2) = substr($b, 4, 2) * 24;
+ $date1 += index("JanFebMarAprMayJunJulAugSepOctNovDec",substr($a,0,3)) * 288;
+ $date2 += index("JanFebMarAprMayJunJulAugSepOctNovDec",substr($b,0,3)) * 288;
+ if ($date1 - $date2 > 300 * 24) {
+ $date2 += 288 * 3 * 12;
+ }
+ elsif ($date2 - $date1 > 300 * 24) {
+ $date1 += 288 * 3 * 12;
+ }
+ $date1 += substr($a, 7, 2);
+ $date2 += substr($b, 7, 2);
+ $date1 - $date2;
+}
+
+sub host2dom {
+ my $host = shift;
+
+ $host =~ m/^[^\.]+(.*)/;
+ $host =~ m/^[\d\.]+$/ ? "unresolved" : $1 ? "*$1" : "?";
+}
+
+1;
--- /dev/null
+#!@_PATH_SH@
+## $Revision: 7466 $
+## Set up any and all shell variables that an INN shell script
+## might need. Also sets umask.
+
+## NOTE: When adding stuff here, add the corresponding variables to
+## innshellvars.pl and innshellvars.tcl and innshellvars.csh
+
+eval `@prefix@/bin/innconfval -s`
+
+NEWSHOME=${PATHNEWS}
+SPOOLBASE=${PATHSPOOL}
+MOST_LOGS=${PATHLOG}
+export NEWSHOME SPOOL MOST_LOGS
+
+NEWSBIN=${PATHBIN}
+NEWSETC=${PATHETC}
+NEWSLIB=@LIBDIR@
+INNDDIR=${PATHRUN}
+LOCKS=${PATHRUN}
+export NEWSBIN NEWSETC INNDDIR NEWSHOME
+
+ERRLOG=${MOST_LOGS}/errlog
+LOG=${MOST_LOGS}/news
+
+ARCHIVEDIR=${PATHARCHIVE}
+SPOOL=${PATHARTICLES}
+BATCH=${PATHOUTGOING}
+INCOMING=${PATHINCOMING}
+OVERVIEWDIR=${PATHOVERVIEW}
+SPOOLNEWS=${PATHINCOMING}
+BADNEWS=${PATHINCOMING}/bad
+
+ACTIVE=${PATHDB}/active
+ACTIVETIMES=${PATHDB}/active.times
+CTLFILE=${NEWSETC}/control.ctl
+CTLWATCH=${NEWSETC}/innwatch.ctl
+HISTORY=${PATHDB}/history
+NEWACTIVE=${PATHDB}/active.tmp
+NEWSFEEDS=${NEWSETC}/newsfeeds
+NEWSGROUPS=${PATHDB}/newsgroups
+OLDACTIVE=${PATHDB}/active.old
+PATH_MOTD=${NEWSETC}/motd.news
+EXPIRECTL=${NEWSETC}/expire.ctl
+LOCALGROUPS=${NEWSETC}/localgroups
+
+CONTROLPROGS=${PATHCONTROL}
+INNCONFVAL=${NEWSBIN}/innconfval
+INND=${NEWSBIN}/innd
+INNDSTART=${NEWSBIN}/inndstart
+INNWATCH=${NEWSBIN}/innwatch
+INEWS=${NEWSBIN}/inews
+RNEWS=${NEWSBIN}/rnews
+PERL_STARTUP_INND=${PATHFILTER}/startup_innd.pl
+PERL_FILTER_INND=${PATHFILTER}/filter_innd.pl
+PERL_FILTER_NNRPD=${PATHFILTER}/filter_nnrpd.pl
+PYTHON_FILTER_INND=${PATHFILTER}/filter_innd.py
+PATH_PYTHON_INN_MODULE=${PATHFILTER}/INN.py
+PATH_TCL_STARTUP=${PATHFILTER}/startup.tcl
+PATH_TCL_FILTER=${PATHFILTER}/filter.tcl
+
+DAILY=${LOCKS}/LOCK.news.daily
+
+NEWSCONTROL=${INNDDIR}/control
+NNTPCONNECT=${INNDDIR}/nntpin
+SERVERPID=${INNDDIR}/innd.pid
+INNWSTATUS=${INNDDIR}/innwatch.status
+WATCHPID=${INNDDIR}/innwatch.pid
+
+AWK=@_PATH_AWK@
+SED=@_PATH_SED@
+INNDF=${NEWSBIN}/inndf
+EGREP=@_PATH_EGREP@
+PERL=@_PATH_PERL@
+GPGV=@PATH_GPGV@
+PGP=@_PATH_PGP@
+SORT="@_PATH_SORT@"
+GETFTP="@GETFTP@"
+UUX=@_PATH_UUX@
+
+COMPRESS=@COMPRESS@
+GZIP=@GZIP@
+UNCOMPRESS="@UNCOMPRESS@"
+LOG_COMPRESS=@LOG_COMPRESS@
+Z=@LOG_COMPRESSEXT@
+
+if [ "$OVMETHOD" = "ovdb" ]; then
+ DB_HOME="${PATHOVERVIEW}"
+ export DB_HOME
+fi
+
+TEMPSOCK=`basename ${INNDDIR}/ctlinndXXXXXX | ${SED} -e 's/XXXXXX$/*/'`
+TEMPSOCKDIR=`echo ${INNDDIR}/ctlinndXXXXXX | ${SED} -e 's@/[^/]*$@@'`
+
+HAVE_UUSTAT=@HAVE_UUSTAT@
+
+NEWSMASTER=@NEWSMASTER@
+NEWSUSER=@NEWSUSER@
+NEWSGROUP=@NEWSGRP@
+
+TMPDIR=${PATHTMP}; export TMPDIR;
+
+SPOOLTEMP=${PATHTMP}
+
+NEWSLBIN=${NEWSHOME}/local
+export NEWSLBIN
+
+umask @NEWSUMASK@
+
+PATH=${NEWSLBIN}:${NEWSBIN}:${PATH}:/bin:/usr/bin:/usr/ucb
+export PATH
+
+HOME=$PATHNEWS
+export HOME
--- /dev/null
+#
+# Author: James Brister <brister@vix.com> -- berkeley-unix --
+# Start Date: Sat, 24 Aug 1996 22:08:19 +0200
+# Project: INN
+# File: innshellvars.pl
+# RCSId: $Id: innshellvars.pl.in 7466 2005-12-12 03:04:08Z eagle $
+# Description: Set up any and all variables that an INN perl script
+# might need.
+
+package inn ;
+
+eval `@prefix@/bin/innconfval -p`;
+
+$newshome = $pathnews;
+$newslib = "@LIBDIR@";
+$spooldir = $pathspool;
+$most_logs = $pathlog;
+
+$errlog = "${most_logs}/errlog" ;
+$log = "${most_logs}/news" ;
+
+$spool = $patharticles;
+$overviewdir = $pathoverview;
+$archivedir = $patharchive;
+$badnews = "$pathincoming/bad";
+$spoolnews = $pathincoming;
+$batch = $pathoutgoing;
+$incoming = $pathincoming;
+
+$locks = $pathrun;
+$newsbin = $pathbin;
+$innddir = $pathrun;
+$newsetc = $pathetc;
+$newslbin = "$pathnews/local";
+
+$active = "${pathdb}/active" ;
+$activetimes = "${pathdb}/active.times" ;
+$ctlfile = "${newsetc}/control.ctl" ;
+$ctlwatch = "${newsetc}/innwatch.ctl" ;
+$history = "${pathdb}/history" ;
+$newactive = "${pathdb}/active.tmp" ;
+$newsfeeds = "${newsetc}/newsfeeds" ;
+$newsgroups = "${pathdb}/newsgroups" ;
+$oldactive = "${pathdb}/active.old" ;
+$path_motd = "${newsetc}/motd.news" ;
+$localgroups = "$newsetc/localgroups" ;
+$expirectl = "${newsetc}/expire.ctl" ;
+
+$controlprogs = $pathcontrol;
+$inews = "${newsbin}/inews" ;
+$innconfval = "${newsbin}/innconfval" ;
+$innd = "${newsbin}/innd" ;
+$inndstart = "${newsbin}/inndstart" ;
+$innwatch = "${newsbin}/innwatch" ;
+$rnews = "${newsbin}/rnews" ;
+$perl_startup_innd = "$pathfilter/startup_innd.pl" ;
+$perl_filter_innd = "$pathfilter/filter_innd.pl" ;
+$perl_filter_nnrpd = "$pathfilter/filter_nnrpd.pl" ;
+$python_filter_innd = "$pathfilter/filter_innd.py" ;
+$path_python_inn_module ="$pathfilter/INN.py" ;
+$path_tcl_startup = "$pathfilter/startup.tcl" ;
+$path_tcl_filter = "$pathfilter/filter.tcl" ;
+
+$daily = "${locks}/LOCK.news.daily" ;
+
+$newscontrol = "${innddir}/control" ;
+$nntpconnect = "${innddir}/nntpin" ;
+$serverpid = "${innddir}/innd.pid" ;
+$innwstatus = "${innddir}/innwatch.status" ;
+$watchpid = "${innddir}/innwatch.pid" ;
+
+$awk = "@_PATH_AWK@" ;
+$sed = "@_PATH_SED@" ;
+$inndf = "${newsbin}/inndf" ;
+$egrep = "@_PATH_EGREP@" ;
+$gpgv = "@PATH_GPGV@" ;
+$perl = "@_PATH_PERL@" ;
+$pgp = "@_PATH_PGP@" ;
+$sort = "@_PATH_SORT@" ;
+$getftp = "@GETFTP@" ;
+$uux = "@_PATH_UUX@" ;
+
+$compress = "@COMPRESS@" ;
+$gzip = "@GZIP@" ;
+$uncompress = "@UNCOMPRESS@" ;
+$log_compress = "@LOG_COMPRESS@" ;
+$z = "@LOG_COMPRESSEXT@" ;
+
+if ($ovmethod && $ovmethod eq "ovdb") {
+ $ENV{'DB_HOME'} = $pathoverview;
+}
+
+($tempsock = "${innddir}/ctlinndXXXXXX") =~ s!.*/(.*)XXXXXX$!$1*! ;
+($tempsockdir = "${innddir}/ctlinndXXXXXX") =~ s!/[^/]*$!! ;
+
+$have_uustat = ("@HAVE_UUSTAT@" eq "DO" ? 1 : 0) ;
+
+$newsmaster = '@NEWSMASTER@' ;
+$newsuser = '@NEWSUSER@' ;
+$newsgroup = '@NEWSGRP@' ;
+
+$ENV{'TMPDIR'} = $pathtmp;
+$tmpdir = $pathtmp;
+$spooltemp = $pathtmp;
+
+$umask = @NEWSUMASK@ ;
+
+$syslog_facility = lc("@SYSLOG_FACILITY@");
+$syslog_facility =~ s/log_//;
+
+$ENV{'PATH'} ||= '';
+$ENV{'PATH'} = "${newslbin}:${newsbin}:$ENV{'PATH'}:/bin:/usr/bin:/usr/ucb" ;
+
+$ENV{'HOME'} = ${pathnews};
+
+1 ;
--- /dev/null
+# -*- tcl -*-
+#
+# Author: James Brister <brister@vix.com> -- berkeley-unix --
+# Start Date: Sat, 24 Aug 1996 23:45:34 +0200
+# Project: INN
+# File: innshellvars.tcl
+# RCSId: $Id: innshellvars.tcl.in 7466 2005-12-12 03:04:08Z eagle $
+# Description: Set up any and all variables that an INN tcl script
+# might need.
+
+eval `@prefix@/bin/innconfval -t`
+
+set inn_newshome "$inn_pathnews"
+set inn_newslib "@LIBDIR@"
+set inn_spooldir "$inn_pathspool"
+set inn_most_logs "$inn_pathlog"
+
+set inn_errlog "${inn_most_logs}/errlog"
+set inn_log "${inn_most_logs}/news"
+
+set inn_batch "${inn_pathoutgoing}"
+set inn_incoming "${inn_pathincoming}"
+set inn_spool "${inn_patharticles}"
+set inn_overviewdir "${inn_pathoverview}"
+set inn_archivedir "${inn_patharchive}"
+set inn_badnews "${inn_pathincoming}/bad"
+set inn_spoolnews "${inn_pathincoming}"
+
+set inn_newslbin "${inn_newshome}/local"
+set inn_innddir "${inn_pathrun}"
+set inn_locks "${inn_pathrun}"
+set inn_newsbin "${inn_pathbin}"
+set inn_newsetc "${inn_newshome}/etc"
+
+set inn_active "${inn_pathdb}/active"
+set inn_activetimes "${inn_pathdb}/active.times"
+set inn_ctlfile "${inn_newsetc}/control.ctl"
+set inn_ctlwatch "${inn_newsetc}/innwatch.ctl"
+set inn_history "${inn_pathdb}/history"
+set inn_newactive "${inn_pathdb}/active.tmp"
+set inn_newsfeeds "${inn_newsetc}/newsfeeds"
+set inn_newsgroups "${inn_pathdb}/newsgroups"
+set inn_oldactive "${inn_pathdb}/active.old"
+set inn_localgroups "${inn_newsetc}/localgroups"
+set inn_expirectl "${inn_newsetc}/expire.ctl"
+set inn_path_motd "${inn_newsetc}/motd.news"
+
+set inn_daily "${inn_locks}/locks/LOCK.news.daily"
+
+set inn_inews "${inn_newsbin}/inews"
+set inn_innconfval "${inn_newsbin}/innconfval"
+set inn_innd "${inn_newsbin}/innd"
+set inn_inndstart "${inn_newsbin}/inndstart"
+set inn_innwatch "${inn_newsbin}/innwatch"
+set inn_rnews "${inn_newsbin}/rnews"
+set inn_controlprogs "${inn_pathcontrol}/control"
+set inn_perl_startup_innd "${inn_pathfilter}/startup_innd.pl"
+set inn_perl_filter_innd "${inn_pathfilter}/filter_innd.pl"
+set inn_perl_filter_nnrpd "${inn_pathfilter}/filter_nnrpd.pl"
+set inn_python_filter_innd "${pathfilter}/filter_innd.py"
+set inn_path_python_inn_module "${pathfilter}/INN.py"
+set inn_path_tcl_startup "${inn_pathfilter}/startup.tcl"
+set inn_path_tcl_filter "${inn_pathfilter}/filter.tcl"
+
+set inn_newscontrol "${inn_innddir}/control"
+set inn_nntpconnect "${inn_innddir}/nntpin"
+set inn_innwstatus "${inn_innddir}/innwatch.status"
+set inn_watchpid "${inn_innddir}/innwatch.pid"
+set inn_serverpid "${inn_innddir}/innd.pid"
+
+set inn_awk "@_PATH_AWK@"
+set inn_sed "@_PATH_SED@"
+set inn_perl "@_PATH_PERL@"
+set inn_gpgv "@PATH_GPGV@"
+set inn_pgp "@_PATH_PGP@"
+set inn_inndf "${inn_newsbin}/inndf"
+set inn_egrep "@_PATH_EGREP@"
+set inn_sort "@_PATH_SORT@"
+set inn_getftp "@GETFTP@"
+set inn_uux "@_PATH_UUX@"
+
+set inn_compress "@COMPRESS@"
+set inn_gzip "@GZIP@"
+set inn_uncompress "@UNCOMPRESS@"
+set inn_log_compress "@LOG_COMPRESS@"
+set inn_z "@LOG_COMPRESSEXT@"
+
+set inn_tempsock [ eval exec basename ${inn_innddir}/ctlinndXXXXXX | $inn_sed -e {s/XXXXXX$/*/} ]
+set inn_tempsockdir [ exec echo ${inn_innddir}/ctlinndXXXXXX | $inn_sed -e {s@/[^/]*$@@} ]
+
+set inn_have_uustat [ expr { "@HAVE_UUSTAT@" == "DO" ? 1 : 0 } ]
+
+set inn_newsmaster "@NEWSMASTER@"
+set inn_newsuser "@NEWSUSER@"
+set inn_newsgroup "@NEWSGRP@"
+
+set env(TMPDIR) "$inn_pathtmp"
+set inn_tmpdir "$inn_pathtmp"
+set inn_spooltemp "$inn_pathtmp"
+
+scan "@NEWSUMASK@" "%o" inn_umask
+
+set env(PATH) "$inn_newslbin:$inn_newsbin:$env(PATH):/bin:/usr/bin:/usr/ucb"
+
+set env(HOME) "$inn_pathnews"
--- /dev/null
+#! /bin/sh
+# fixscript will replace this line with code to load innshellvars
+
+## $Revision: 6994 $
+## Display status of INN.
+## Written by Landon Curt Noll <chongo@toad.com>.
+
+SYSLOG_CRIT=news.crit
+SYSLOG_ERR=news.err
+SYSLOG_NOTICE=news.notice
+SYSLOGS="${SYSLOG_CRIT} ${SYSLOG_ERR} ${SYSLOG_NOTICE}"
+
+## Set up the list of log files.
+LOGS="${SYSLOGS}"
+if [ -f "${MOST_LOGS}/`basename ${ERRLOG}`" ]; then
+ LOGS="${LOGS} `basename ${ERRLOG}`"
+else
+ LOGS="${LOGS} ${ERRLOG}"
+fi
+if [ -f "${MOST_LOGS}/`basename ${LOG}`" ]; then
+ LOGS="${LOGS} `basename ${LOG}`"
+else
+ LOGS="${LOGS} ${LOG}"
+fi
+
+## Show INND status.
+echo 'Server status:'
+ctlinnd mode 2>&1
+
+## Show disk usage. You might have to change this.
+echo ''
+echo 'Disk usage:'
+${INNDF} ${SPOOL} ${OVERVIEWDIR} ${PATHETC} ${INCOMING} ${BATCH} \
+ ${PATHDB} ${MOST_LOGS} | sort -u
+
+## Show size of batch files.
+echo ''
+echo 'Batch file sizes:'
+( cd ${BATCH}; ls -Cs | sed 1d )
+
+## Show size of log files.
+echo ''
+echo 'Log file sizes:'
+( cd ${MOST_LOGS}; ls -Cs ${LOGS} *.log 2>&1 )
+
+## Show the lock files
+echo ''
+( cd ${LOCKS}
+ set -$- LOCK.*
+ if [ -f "$1" ]; then
+ echo 'Lock files:'
+ ls -C LOCK.* 2>&1
+ else
+ echo 'Innwatch is not running'
+ fi
+)
+
+echo ''
+echo 'Server connections:'
+ctlinnd -t60 name '' 2>&1 \
+ | ${PERL} -ne '
+ next if m/(^((rem|local)conn|control)|:proc|:file):/;
+ s/^(\S+):(\d+):.*:.*:.*$/${1}:${2}/;
+ m/^(\S+):(\d+)$/;
+ $c{$1} = [] unless $c{$1};
+ @l = @{$c{$1}};
+ push @l, $2;
+ $c{$1} = [@l];
+ $m++;
+
+ END {
+ $n = 0;
+ foreach $f (sort {$#{$c{$b}} <=> $#{$c{$a}}} keys %c) {
+ printf "%-35.35s %3d (%s", $f, 1 + $#{$c{$f}}, "@{$c{$f}})\n";
+ $n++;
+ }
+ printf "\n%-35s %3d\n", "TOTAL: $n", $m;
+ }'
+
--- /dev/null
+#! /usr/bin/perl
+
+## $Id: innupgrade.in 6458 2003-09-03 02:58:51Z rra $
+##
+## Convert INN configuration files to the current syntax.
+##
+## Intended to be run during a major version upgrade, this script tries to
+## convert existing INN configuration files to the syntax expected by the
+## current version, if that's changed.
+##
+## Note that this script cannot use innshellvars.pl, since loading that file
+## requires innconfval be able to parse inn.conf, and until this script runs,
+## inn.conf may not be valid.
+##
+## Currently handles the following conversions:
+##
+## * Clean up inn.conf for the new parser in INN 2.4.
+## * Add the hismethod parameter to inn.conf if not found.
+
+require 5.003;
+
+use strict;
+use vars qw(%FIXES);
+use subs qw(fix_inn_conf);
+
+use Getopt::Long qw(GetOptions);
+
+# The mappings of file names to fixes.
+%FIXES = ('inn.conf' => \&fix_inn_conf);
+
+# Clean up inn.conf for the new parser in INN 2.4. Null keys (keys without
+# values) need to be commented out since they're no longer allowed (don't just
+# remove them entirely since people may want them there as examples), and
+# string values must be quoted if they contain any special characters. Takes
+# a reference to an array containing the contents of the file, a reference to
+# an array into which the output should be put, and the file name for error
+# reporting.
+sub fix_inn_conf {
+ my ($input, $output, $file) = @_;
+ my $line = 0;
+ my ($raw, $hismethod);
+ local $_;
+ for $raw (@$input) {
+ $_ = $raw;
+ $line++;
+ if (/^\s*\#/ || /^\s*$/) {
+ push (@$output, $_);
+ next;
+ }
+ chomp;
+ unless (/^(\s*)(\S+):(\s*)(.*)/) {
+ warn "$file:$line: cannot parse line: $_\n";
+ push (@$output, $_);
+ next;
+ }
+ my ($indent, $key, $space, $value) = ($1, $2, $3, $4);
+ if ($value eq '') {
+ push (@$output, "#$_\n");
+ next;
+ }
+ $hismethod = 1 if $key eq 'hismethod';
+ $value =~ s/\s+$//;
+ if ($value =~ /[\s;\"<>\[\]\\{}]/ && $value !~ /^\".*\"\s*$/) {
+ $value =~ s/([\"\\])/\\$1/g;
+ $value = '"' . $value . '"';
+ }
+ push (@$output, "$indent$key:$space$value\n");
+ }
+
+ # Add a setting of hismethod if one wasn't present in the original file.
+ unless ($hismethod) {
+ push (@$output, "\n# Added by innupgrade\nhismethod: hisv6\n");
+ }
+}
+
+# Upgrade a particular file. Open the file, read it into an array, and then
+# run the fix function on it. If the fix function generates different output
+# than the current contents of the file, change the file.
+sub upgrade_file {
+ my ($file, $function) = @_;
+ open (INPUT, $file) or die "$file: cannot open: $!\n";
+ my @input = <INPUT>;
+ close INPUT;
+ my @output;
+ &$function (\@input, \@output, $file);
+ if (join ('', @input) ne join ('', @output)) {
+ if (-e "$file.OLD") {
+ if (-t STDIN) {
+ print "$file.OLD already exists, overwrite (y/N)? ";
+ my $answer = <STDIN>;
+ if ($answer !~ /y/i) {
+ die "$file: backup $file.OLD already exists, aborting\n";
+ }
+ } else {
+ die "$file: backup $file.OLD already exists\n";
+ }
+ }
+ print "Updating $file, old version saved as $file.OLD\n";
+ my ($user, $group) = (stat $file)[4,5];
+ open (OUTPUT, "> $file.new.$$")
+ or die "$file: cannot create $file.new.$$: $!\n";
+ print OUTPUT @output;
+ close OUTPUT or die "$file: cannot flush new file: $!\n";
+ unless (link ($file, "$file.OLD")) {
+ rename ($file, "$file.OLD")
+ or die "$file: cannot rename to $file.OLD: $!\n";
+ }
+ if ($> == 0) {
+ if (defined ($user) && defined ($group)) {
+ chown ($user, $group, "$file.new.$$")
+ or warn "$file: cannot chown $file.new.$$: $!\n";
+ } else {
+ warn "$file: cannot find owner and group of $file\n";
+ }
+ }
+ rename ("$file.new.$$", $file)
+ or die "$file: cannot replace with $file.new.$$: $!\n";
+ }
+}
+
+# Upgrade a directory. Scan the directory for files that have upgrade rules
+# defined and for each one of those, try running the upgrade rule.
+sub upgrade_directory {
+ my $directory = shift;
+ chdir $directory or die "Can't chdir to $directory: $!\n";
+ opendir (DIR, ".") or die "Can't opendir $directory: $!\n";
+ for (readdir DIR) {
+ if ($FIXES{$_}) {
+ upgrade_file ($_, $FIXES{$_});
+ }
+ }
+ closedir DIR;
+}
+
+
+# The main routine. Parse command-line options to figure out what we're
+# doing.
+my ($file, $type);
+Getopt::Long::config ('bundling');
+GetOptions ('file|f=s' => \$file,
+ 'type|t=s' => \$type) or exit 1;
+if ($file) {
+ my $basename = $file;
+ $basename =~ s%.*/%%;
+ $type ||= $basename;
+ if (!$FIXES{$type}) { die "No upgrade rules defined for $basename\n" }
+ upgrade_file ($file, $FIXES{$type});
+} else {
+ if (@ARGV != 1) { die "Usage: innupgrade <directory>\n" }
+ my $directory = shift;
+ upgrade_directory ($directory);
+}
--- /dev/null
+#! /bin/sh
+# fixscript will replace this line with code to load innshellvars
+
+## $Revision: 2677 $
+## Watch the state of the system relative to the news subsystem.
+## As controlled by the control file, when space or inodes are almost
+## exhausted innd is throttled, or paused, similarly if the load is
+## too high - when conditions revert to normal, innd is restarted.
+## No logging is done here, watch for syslog reports from innd.
+## Written by Mike Cooper <mcooper@usc.edu>.
+## Extensively modified by <kre@munnari.oz.au>.
+## Watch a log file and send mail when it gets new output by
+## Steve Groom <stevo@elroy.Jpl.Nasa.Gov>
+## Steve's extensions merged in innwatch by
+## <Christophe.Wolfhugel@grasp.insa-lyon.fr>
+
+PROGNAME=innwatch
+LOCK=${LOCKS}/LOCK.${PROGNAME}
+DAILY=${LOCKS}/LOCK.news.daily
+## Where to put the timestamp file (directory and filename).
+TIMESTAMP=${LOCKS}/${PROGNAME}.time
+
+## Logfile to watch. Comment out if no logwatch.
+LOGFILE=${MOST_LOGS}/news.crit
+
+## Default value in case there is no definition in inn.conf
+: ${INNWATCHPAUSELOAD:=1500}
+: ${INNWATCHHILOAD:=2000}
+: ${INNWATCHLOLOAD:=1000}
+: ${INNWATCHSPOOLSPACE:=8000}
+: ${INNWATCHBATCHSPACE:=800}
+: ${INNWATCHLIBSPACE:=25000}
+: ${INNWATCHSPOOLNODES:=200}
+: ${INNWATCHSLEEPTIME:=600}
+
+## Parse JCL.
+while [ $# -gt 0 ] ; do
+ case X"$1" in
+ X-f)
+ FILE=$2
+ shift
+ ;;
+ X-f*)
+ FILE=`expr "$1" : '-s\(.*\)'`
+ ;;
+ X-l)
+ LOGFILE=$2
+ shift
+ ;;
+ X-l*)
+ LOGFILE=`expr "$1" : '-s\(.*\)'`
+ ;;
+ X-t)
+ INNWATCHSLEEPTIME=$2
+ shift
+ ;;
+ X-t*)
+ INNWATCHSLEEPTIME=`expr "$1" : '-t\(.*\)'`
+ ;;
+ X--)
+ shift
+ break
+ ;;
+ X-*)
+ echo "${PROGNAME}: Unknown flag $1" 1>&2
+ exit 1
+ ;;
+ *)
+ break
+ ;;
+ esac
+ shift
+done
+
+## Process arguments.
+if [ $# -ne 0 ] ; then
+ echo "Usage: ${PROGNAME} [flags]" 1>&2
+ exit 1
+fi
+
+trap '' 2
+
+## Anyone else there?
+shlock -p $$ -f ${LOCK} || {
+ echo "${PROGNAME}: [$$] locked by [`cat ${LOCK}`]"
+ exit 0
+}
+
+trap 'rm -f ${LOCK} ${WATCHPID} ; exit 1' 1 3 15
+echo "$$" > ${WATCHPID}
+
+## The reason why we turned INND off, and its, and our current state.
+REASON=''
+INND=''
+STATE=''
+
+trap '(
+ echo "${PROGNAME} waiting for INND to start (pid: $$)"
+ date
+ ) >${INNWSTATUS}' 2
+
+## We need to remember the process ID of innd, in case one exits
+## But we need to wait for innd to start before we can do that
+while PID=`cat ${SERVERPID} 2>/dev/null`; test -z "${PID}"; do
+ sleep ${INNWATCHSLEEPTIME}
+done
+
+trap '(
+ if [ -z "${STATE}" ]; then
+ echo "${PROGNAME} state RUN interval ${INNWATCHSLEEPTIME} pid $$"
+ else
+ echo "${PROGNAME} state ${STATE} interval ${INNWATCHSLEEPTIME} pid $$"
+ fi
+ if [ -z "${INND}" ]; then
+ X=GO
+ else
+ X="${INND}"
+ fi
+ test -n "${REASON}" && X="${X}: ${REASON}"
+ echo "INND state ${X}"
+ date
+ ) >${INNWSTATUS}' 2
+
+cd ${SPOOL}
+
+NEXTSLEEP=1
+HASEXITED=false
+
+while { sleep ${NEXTSLEEP} & wait; } ; : ; do
+ NEXTSLEEP=${INNWATCHSLEEPTIME}
+
+ ## If news.daily is running, idle: we don't want to change the
+ ## status of anything while news.daily may be depending on what we
+ ## have done.
+ test -f "${DAILY}" && continue
+
+ ## Check to see if INND is running.
+ ## Notify NEWSMASTER if it has stopped or just restarted.
+ if ctlinnd -s -t 120 mode 2>/dev/null ; then
+ ${HASEXITED} && {
+ HASEXITED=false
+ ${MAILCMD} -s "INND is now running" ${NEWSMASTER} </dev/null
+ }
+ else
+ ${HASEXITED} || {
+ HASEXITED=true
+ ${MAILCMD} -s "INND is NOT running" ${NEWSMASTER} </dev/null
+ }
+ continue
+ fi
+
+ ## If innd has exited & restarted, put the new one into the
+ ## same state the old one was in
+
+ nPID=`cat ${SERVERPID} 2>/dev/null`
+ test -n "${nPID}" -a "${PID}" -ne "${nPID}" && {
+ test -n "${INND}" -a "${INND}" != go && ctlinnd -s "${INND}" "${REASON}"
+ PID="${nPID}"
+ }
+
+ VALUE=0
+ PREVEXP=''
+
+ exec 3<&0
+ exec 0<${CTLWATCH}
+
+ LINE=0
+ while read line ; do
+ LINE=`expr ${LINE} + 1`
+ test -z "$line" && continue
+
+ ## The first character on the line is the field delimiter,
+ ## except '#' which marks the line as a comment
+ delim=`expr "${line}" : '\(.\).*'`
+ test "X${delim}" = 'X#' && continue
+
+ ## Parse the line into seven fields, and assign them to local vars.
+ ## You're welcome to work out what's going on with quoting in
+ ## the next few lines if you feel inclined.
+ eval `trap '' 2; echo "${line}" \
+ | ${SED} -e "s/'/'\"'\"'/g" \
+ -e "s/[ ]*\\\\${delim}[ ]*/\\\\${delim}/g" \
+ | ${AWK} -F"${delim}" '{ print "LAB='"'"'" $2 "'"'"'", \
+ "CND='"'"'" $3 "'"'"'", \
+ "EXP='"'"'" $4 "'"'"'", \
+ "TST='"'"'" $5 "'"'"'", \
+ "LIM=" $6, \
+ "CMD='"'"'" $7 "'"'"'", \
+ "CMT='"'"'" $8 "'"'"'" }'`
+
+ ## If there's no label, the label is the line number.
+ test -z "${LAB}" && LAB=${LINE}
+
+ ## Should we act on this line? We will if one (or more) of the
+ ## specified conditions is satisfied.
+ for X in a b; do # meaningless trash because we have no goto
+ if [ -z "${CND}" ]; then
+ X=-
+ else
+ X="${CND}"
+ fi
+ set -$- X ${X}; shift
+ for cnd
+ do
+ case "${cnd}" in
+ -)
+ test -n "${STATE}" -a "X${STATE}" != "X${LAB}" && continue
+ ;;
+ +)
+ test -n "${STATE}" && continue
+ ;;
+ '*')
+ ;;
+ -*)
+ test "X-${STATE}" = "X${cnd}" && continue
+ ;;
+ *)
+ test "X${STATE}" != "X${cnd}" && continue;
+ ;;
+ esac
+ break 2; # OK, continue with this line
+ done
+ continue 2; # No, skip it.
+ done
+
+ ## Evaluate the expression, if there is one, and if that works.
+ if [ -z "${EXP}" -o "${EXP}" = "${PREVEXP}" ] \
+ || { PREVEXP="${EXP}"; VALUE=`trap '' 2;eval "${EXP}"`; }; then
+ ## If innd is running, and test "succeeds", stop it.
+ case "${CMD}" in
+ throttle|pause)
+ OK=n
+ ;;
+ *)
+ OK=y
+ ;;
+ esac
+
+ if [ \( -z "${STATE}" -o "${STATE}" != "${LAB}" -o "${OK}" = y \) \
+ -a "${VALUE}" "-${TST}" "${LIM}" ] ; then
+ R="${CMT} [${PROGNAME}:${LAB}] ${VALUE} ${TST} ${LIM}"
+ O=
+ case "${CMD}" in
+ throttle)
+ case "${STATE}" in
+ ''|go)
+ REASON="${R}"
+ ;;
+ *)
+ ;;
+ esac
+ O="${LAB}"
+ ARG="${REASON}"
+ ;;
+ pause)
+ O="${LAB}"
+ REASON="${R}"
+ ARG="${REASON}"
+ ;;
+ shutdown)
+ ARG="${R}"
+ ;;
+ flush)
+ ARG=''
+ O="${STATE}"
+ ARG="${REASON}"
+ ;;
+ go)
+ ARG="${REASON}"
+ NEXTSLEEP=1
+ REASON=''
+ ;;
+ exit)
+ exit 0
+ ;;
+ *)
+ break
+ ;;
+ esac
+
+ ctlinnd -s "${CMD}" "${ARG}" && STATE="${O}" && INND="${CMD}"
+ break
+
+ ## Otherwise, if innd is not running, and reverse test succeeds
+ ## restart it.
+ elif [ "${STATE}" = "${LAB}" -a \
+ \( "${CMD}" = "throttle" -o "${CMD}" = pause \) -a \
+ ! "${VALUE}" "-${TST}" "${LIM}" ] ; then
+ ctlinnd -s go "${REASON}"
+ STATE=''
+ REASON=''
+ INND=''
+
+ ## If we have started innd, run all tests again quickly in
+ ## case there is some other condition that should stop it.
+ NEXTSLEEP=1
+ break
+ fi
+ fi
+
+ done
+
+ exec 0<&3
+ exec 3<&-
+
+ if [ -n "${LOGFILE}" -a -f "${LOGFILE}" ]; then
+ if [ ! -f ${TIMESTAMP} ]; then
+ DOIT=${LOGFILE}
+ else
+ # use ls to print most recently modified file first.
+ # If that's ${LOGFILE}, it's changed since the last pass.
+ DOIT="`ls -t ${TIMESTAMP} ${LOGFILE} | ${SED} -e 1q | grep ${LOGFILE}`"
+ fi
+
+ # If the file has been modified more recently than the timestamp,
+ # and the file has length greater than 0, send the warning.
+ if [ -n "${DOIT}" -a -s ${LOGFILE} ]; then
+ date >${TIMESTAMP}
+ (
+ ls -l ${LOGFILE}
+ echo "-----"
+ ctlinnd -t120 mode
+ echo "-----"
+ cat ${LOGFILE}
+ ) 2>&1 \
+ | sed -e 's/^~/~~/' \
+ | ${MAILCMD} -s "${PROGNAME} warning: messages in ${LOGFILE}" \
+ ${NEWSMASTER}
+ fi
+ fi
+
+done
+
+rm -f ${LOCK}
--- /dev/null
+#!@_PATH_SH@
+## $Revision: 5999 $
+## Daily news maintenance.
+## Optional arguments:
+## expdir=xxx Directory in which to build new history file
+## tmpdir=xxx Working directory for `sort' and such
+## expirectl=xxx Use xxx as expire.ctl file
+## flags=xxx Pass xxx flags to expire
+## lowmark Create and use a lowmark file
+## noexpire Do not expire
+## noexplog Do not log expire output
+## nologs Do not scan logfiles
+## nomail Do not capture and mail output
+## norenumber Do not renumber the active file
+## norm Do not remove certain old files
+## norotate Do not rotate logfiles
+## nostat Do not run innstat
+## notdaily Not a daily run, and therefore implies nologs.
+## delayrm Delay unlink files, then do it quicker (expire -z)
+## /full/path Path to a program to run before expiring
+
+. @LIBDIR@/innshellvars
+
+EXPLOG=${MOST_LOGS}/expire.log
+INNSTAT=${PATHBIN}/innstat
+HOSTNAME=`hostname`
+DATE=`date`
+TAGGEDHASH=@DO_DBZ_TAGGED_HASH@
+SORT=sort
+
+## If your expire does not pause or throttle innd, enable this next line:
+#MESSAGE="Expiration script $$"
+## Renumber all at once, or in steps? Set to the delay if steps (that
+## may take a long time!).
+RENUMBER=
+
+PROGNAME=news.daily
+LOCK=${LOCKS}/LOCK.${PROGNAME}
+
+## Set defaults.
+DAILY=true
+DOEXPIRE=true
+DOEXPLOG=true
+DOEXPIREOVER=false
+DOLOGS=true
+DOMAIL=true
+DORENUMBER=true
+DORM=true
+DOSTAT=true
+EXPIRECTL=
+EXPDIR=
+EXPIREFLAGS="-v1"
+EXPIREOVER=expireover
+LOWMARKFILE=
+RMFILE=
+
+HISTDIR="`dirname ${HISTORY}`"
+TOUT=600 # timeout value for ctlinnd commands
+if [ -z "${HISTDIR}" -o X"." = X"${HISTDIR}" -o ! -d "${HISTDIR}" ]; then
+ if [ -d "${PATHETC}/hist" ]; then
+ HISTDIR="${PATHETC}/hist"
+ else
+ HISTDIR="${PATHETC}"
+ fi
+fi
+if [ "${ENABLEOVERVIEW}" = "true" ]; then
+ DOEXPIREOVER=true
+ RMFILE=${MOST_LOGS}/expire.rm
+fi
+
+EXPIREOVERFLAGS=
+PROGRAMS=
+POSTPROGRAMS=
+REASON=
+SCANARG=
+DOGROUPBASEEXPIRY=false
+
+if [ "${GROUPBASEEXPIRY}" = "true" ]; then
+ if [ "${ENABLEOVERVIEW}" = "true" ]; then
+ DOGROUPBASEEXPIRY=true
+ else
+ DOEXPIREOVER=false
+ RMFILE=
+ fi
+fi
+
+## Parse JCL.
+for I
+do
+ case "X$I" in
+ Xdelayrm)
+ RMFILE=${MOST_LOGS}/expire.rm
+ ;;
+ Xexpctl=*)
+ EXPIRECTL=`expr "${I}" : 'expctl=\(.*\)'`
+ case ${EXPIRECTL} in
+ /*)
+ ;;
+ *)
+ EXPIRECTL=`/bin/pwd`/${EXPIRECTL}
+ ;;
+ esac
+ ;;
+ Xexpdir=*)
+ EXPDIR=`expr "${I}" : 'expdir=\(.*\)'`
+ ;;
+ Xtmpdir=*)
+ TMPDIR=`expr "${I}" : 'tmpdir=\(.*\)'`
+ ;;
+ Xexpireover)
+ DOEXPIREOVER=true
+ RMFILE=${MOST_LOGS}/expire.rm
+ ;;
+ Xexpireoverflags=*)
+ EXPIREOVERFLAGS=`expr "${I}" : 'expireoverflags=\(.*\)'`
+ ;;
+ Xflags=*)
+ EXPIREFLAGS=`expr "${I}" : 'flags=\(.*\)'`
+ ;;
+ Xlowmark)
+ LOWMARKFILE=${MOST_LOGS}/expire.lowmark
+ DORENUMBER=false
+ ;;
+ Xnotdaily)
+ DAILY=false
+ DOLOGS=false
+ ;;
+ Xnoexpire)
+ DOEXPIRE=false
+ ;;
+ Xnoexpireover)
+ DOEXPIREOVER=false
+ RMFILE=
+ ;;
+ Xnoexplog)
+ DOEXPLOG=false
+ ;;
+ Xnologs)
+ DOLOGS=false
+ ;;
+ Xnomail)
+ DOMAIL=false
+ MAIL="cut -c 1-1022 | ${SED} -e 's/^~/~~/'"
+ ;;
+ Xnonn)
+ # Ignore this.
+ ;;
+ Xnorenumber)
+ DORENUMBER=false
+ ;;
+ Xnorm)
+ DORM=false
+ ;;
+ Xnorotate)
+ SCANARG="${SCANARG} norotate"
+ ;;
+ Xnostat)
+ DOSTAT=false
+ ;;
+ X/*)
+ PROGRAMS="${PROGRAMS} ${I}"
+ ;;
+ Xpostexec=*)
+ POSTEXEC=`expr "${I}" : 'postexec=\(.*\)'`
+ POSTPROGRAMS="${POSTPROGRAMS} ${POSTEXEC}"
+ ;;
+ *)
+ echo "Unknown flag ${I}" 1>&2
+ exit 1
+ ;;
+ esac
+done
+
+##
+## Setup mail subject and command
+##
+if ${DAILY} ; then
+ MAILSUBJ="${HOSTNAME} Daily Usenet report for ${DATE}"
+else
+ MAILSUBJ="${HOSTNAME} intermediate usenet report for ${DATE}"
+fi
+MAIL="cut -c 1-1022 | ${SED} -e 's/^~/~~/' | \
+ ${MAILCMD} -s '$MAILSUBJ' ${NEWSMASTER}"
+
+#
+# Sanity check. Shouldn't we just bail out here with an error
+# instead of trying to patch things up?
+#
+${DOEXPIRE} || {
+ EXPDIR=
+ RMFILE=
+}
+
+test -n "${EXPDIR}" && {
+ test -z "${REASON}" && REASON="Expiring $$ on ${EXPDIR}"
+ EXPIREFLAGS="${EXPIREFLAGS} '-d${EXPDIR}' '-r${REASON}'"
+}
+
+test -n "${RMFILE}" && {
+ if ${DOGROUPBASEEXPIRY} ; then
+ EXPIREOVERFLAGS="${EXPIREOVERFLAGS} -z${RMFILE}"
+ else
+ EXPIREFLAGS="${EXPIREFLAGS} -z${RMFILE}"
+ fi
+ }
+
+test -n "${LOWMARKFILE}" && {
+ EXPIREOVERFLAGS="${EXPIREOVERFLAGS} -Z${LOWMARKFILE}"
+}
+
+
+if ${DOMAIL} ; then
+ ## Try to get a temporary file.
+ TEMP=${TMPDIR}/doex$$
+ test -f ${TEMP} && {
+ echo "Temporary file ${TEMP} exists" | eval ${MAIL}
+ exit 1
+ }
+ touch ${TEMP}
+ chmod 0660 ${TEMP}
+ exec 3>&1 >${TEMP} 2>&1
+fi
+
+cd ${PATHETC}
+
+## Show the status of the news system.
+${DOSTAT} && {
+ ${INNSTAT}
+ echo ''
+}
+
+## Lock out others.
+trap 'rm -f ${LOCK} ; exit 1' 1 2 3 15
+shlock -p $$ -f ${LOCK} || {
+ ( echo "$0: Locked by `cat ${LOCK}`"; ${INNSTAT} ) | eval ${MAIL}
+ exit 1
+}
+
+## Run any user programs.
+if [ -n "${PROGRAMS}" ] ; then
+ for P in ${PROGRAMS} ; do
+ echo ''
+ echo "${P}:"
+ eval ${P}
+ done
+fi
+
+${DOLOGS} && {
+ echo ''
+ scanlogs ${SCANARG}
+}
+
+# group-based expiry (delete expired overview first)
+if [ ${DOGROUPBASEEXPIRY} = true -a ${DOEXPIREOVER} = true ] ; then
+ if ${DOEXPLOG}; then
+ echo "${EXPIREOVER} start `date`: (${EXPIREOVERFLAGS})" >>${EXPLOG}
+ fi
+ ( cd ${HISTDIR} ; exec 2>&1 ; eval ${EXPIREOVER} "${EXPIREOVERFLAGS}" ) \
+ >>${EXPLOG}
+ if ${DOEXPLOG}; then
+ echo "${EXPIREOVER} end `date`" >>${EXPLOG}
+ fi
+ test -n "${LOWMARKFILE}" && {
+ echo "lowmarkrenumber begin `date`: (${LOWMARKFILE})" >>${EXPLOG}
+ ctlinnd -s -t`wc -l <${ACTIVE}` lowmark ${LOWMARKFILE} 2>&1
+ echo "lowmarkrenumber end `date`" >>${EXPLOG}
+ rm -f ${MOST_LOGS}/expire.lastlowmark
+ mv ${LOWMARKFILE} ${MOST_LOGS}/expire.lastlowmark
+ }
+ test -n "${RMFILE}" -a -s "${RMFILE}" && {
+ mv ${RMFILE} ${RMFILE}.$$ && RMFILE=${RMFILE}.$$
+
+ test -n "${TMPDIR}" && SORT="${SORT} -T ${TMPDIR}"
+ ${SORT} -u -o ${RMFILE} ${RMFILE}
+ if ${DOEXPLOG}; then
+ echo " expirerm start `date`" >>${EXPLOG}
+ fi
+ expirerm ${RMFILE}
+ if ${DOEXPLOG}; then
+ echo " expirerm end `date`" >>${EXPLOG}
+ fi
+ }
+ DOEXPIREOVER=false
+fi
+
+## The heart of the matter: prologs, expire, epilogs.
+if ${DOEXPIRE} ; then
+
+ ## Wait to be fairly certain innwatch is not in the middle of a pass
+ ## Since we're locked, innwatch will pause now till we're done
+ sleep 30
+
+ ## See if we're throttled for lack of space.
+ SERVERMODE=`ctlinnd -t $TOUT mode 2>/dev/null | ${SED} 1q`
+ case "${SERVERMODE}" in
+ 'Server paused'*'[innwatch:'*)
+ ## If paused, by innwatch, then turn pause into throttle
+ ## as we're going to stay that way for a while now
+ ctlinnd -t $TOUT -s throttle \
+ "`expr \"${SERVERMODE}\" : 'Server paused \(.*\)'`" || {
+ ( echo "$0: Cannot throttle while innwatch paused";
+ ${INNSTAT} ) | eval ${MAIL}
+ exit 1
+ }
+ esac
+ case "${SERVERMODE}" in
+ *space*" -- throttling")
+ echo "${SERVERMODE} -- trying to recover"
+ THROTTLED=true
+ EXPIREFLAGS="${EXPIREFLAGS} -n"
+ MESSAGE=
+ ;;
+ *"[innwatch:"*)
+ echo "${SERVERMODE} -- pressing on"
+ THROTTLED=false
+ EXPIREFLAGS="${EXPIREFLAGS} -n"
+ MESSAGE=
+ DORENUMBER=false
+ ;;
+ *)
+ THROTTLED=false
+ ;;
+ esac
+
+ ## Throttle server if we need to.
+ if [ -n "${MESSAGE}" ] ; then
+ ctlinnd -t $TOUT -s -t120 throttle "${MESSAGE}" 2>&1 || {
+ ( echo "$0: Cannot throttle news"; ${INNSTAT} ) | eval ${MAIL}
+ exit 1
+ }
+ fi
+
+ #
+ # Get rid of an old expire RMFILE since news.daily locks itself and
+ # we would not get here if another instance were still running.
+ #
+ if [ -n "${RMFILE}" ] ; then
+ rm -f ${RMFILE}
+ fi
+
+ ## Actual expire the articles (finally!).
+ test -n "${EXPIRECTL}" && EXPIREFLAGS="${EXPIREFLAGS} ${EXPIRECTL}"
+ if ${DOEXPLOG}; then
+ echo "expire begin `date`: (${EXPIREFLAGS})" >>${EXPLOG}
+ ( cd ${HISTDIR}; exec 2>&1 ; eval expire "${EXPIREFLAGS}" ) \
+ | ${SED} -e '/No such file or directory/d' \
+ -e 's/^/ /' >>${EXPLOG}
+ echo "expire end `date`" >>${EXPLOG}
+ else
+ eval expire "${EXPIREFLAGS}" 2>&1 | grep -v 'No such file or directory'
+ fi
+
+ ## If built on another filesystem, move history files.
+ if [ -n "${EXPDIR}" ] ; then
+ if [ ! -f ${EXPDIR}/history.n -o ! -f ${EXPDIR}/history.n.done ] ; then
+ ( echo "$0: No new history files"; ${INNSTAT} ) | eval ${MAIL}
+ exit 1
+ fi
+ cp /dev/null ${HISTORY}
+ mv -f ${EXPDIR}/history.n ${HISTORY}
+ mv -f ${EXPDIR}/history.n.dir ${HISTORY}.dir
+ if [ X${TAGGEDHASH} = XDO ] ; then
+ mv -f ${EXPDIR}/history.n.pag ${HISTORY}.pag
+ else
+ mv -f ${EXPDIR}/history.n.index ${HISTORY}.index
+ mv -f ${EXPDIR}/history.n.hash ${HISTORY}.hash
+ fi
+ rm -f ${EXPDIR}/history.n.done
+
+ case "${EXPIREFLAGS}" in
+ *-n*)
+ ;;
+ *)
+ MESSAGE="${REASON}"
+ ;;
+ esac
+ fi
+
+ ## Restart the server if we need to.
+ if ${THROTTLED} || test -n "${MESSAGE}" ; then
+ ctlinnd -t "$TOUT" -s go "${MESSAGE}" 2>&1 || {
+ ( echo "$0: Cannot unthrottle news"; ${INNSTAT} ) | eval ${MAIL}
+ exit 1
+ }
+ fi
+ if ${DOEXPLOG}; then
+ echo " all done `date`" >>${EXPLOG}
+ fi
+fi
+
+## Remove old sockets.
+${DORM} &&
+ find ${TEMPSOCKDIR} -name "${TEMPSOCK}" -mtime +2 -exec rm -f '{}' ';'
+
+## Did we became throttled during the run?
+SERVERMODE=`ctlinnd mode 2>/dev/null | ${SED} 1q`
+case "${SERVERMODE}" in
+*space*" -- throttling")
+ ## We did, try to unthrottle the server.
+ echo "${SERVERMODE} -- trying to recover"
+ ctlinnd -s go ""
+ ;;
+esac
+
+## Release the lock now, everything below this must be able to withstand
+## simultaneous news.daily's running. We do this so innwatch can start
+## monitoring again asap after the expire is done -- removing the
+## articles isn't critical, nor is the renumber.
+rm ${LOCK}
+
+## Remove the articles that are supposed to go
+if [ ${DOEXPIRE} = true -a ${DOGROUPBASEEXPIRY} != true ] ; then
+ test -n "${RMFILE}" -a -s "${RMFILE}" && {
+ mv ${RMFILE} ${RMFILE}.$$ && RMFILE=${RMFILE}.$$
+
+ test -n "${TMPDIR}" && SORT="${SORT} -T ${TMPDIR}"
+ ${SORT} -u -o ${RMFILE} ${RMFILE}
+ if ${DOEXPLOG}; then
+ echo " expirerm start `date`" >>${EXPLOG}
+ fi
+ expirerm ${RMFILE}
+ if ${DOEXPLOG}; then
+ echo " expirerm end `date`" >>${EXPLOG}
+ fi
+ ${DOEXPIREOVER} && {
+ if ${DOEXPLOG}; then
+ echo " ${EXPIREOVER} start `date`" >>${EXPLOG}
+ fi
+ ( cd ${HISTDIR} ; eval ${EXPIREOVER} "${EXPIREOVERFLAGS}" 2>&1 ) | \
+ grep -v 'No such file or directory'
+ DOEXPIREOVER=false
+ if ${DOEXPLOG}; then
+ echo " ${EXPIREOVER} end `date`" >>${EXPLOG}
+ fi
+ }
+ test -n "${LOWMARKFILE}" && {
+ echo "lowmarkrenumber begin `date`: (${LOWMARKFILE})" >>${EXPLOG}
+ ctlinnd -s -t`wc -l <${ACTIVE}` lowmark ${LOWMARKFILE} 2>&1
+ echo "lowmarkrenumber end `date`" >>${EXPLOG}
+ rm -f ${MOST_LOGS}/expire.lastlowmark
+ mv ${LOWMARKFILE} ${MOST_LOGS}/expire.lastlowmark
+ }
+ }
+ # expire overview lines for files we just removed
+ if ${DOEXPIREOVER}; then
+ if ${DOEXPLOG}; then
+ echo " ${EXPIREOVER} start `date`" >>${EXPLOG}
+ fi
+ ( cd ${HISTDIR} ; eval ${EXPIREOVER} "${EXPIREOVERFLAGS}" 2>&1 ) | \
+ grep -v 'No such file or directory'
+ DOEXPIREOVER=false
+ if ${DOEXPLOG}; then
+ echo " ${EXPIREOVER} end `date`" >>${EXPLOG}
+ fi
+ test -n "${LOWMARKFILE}" && {
+ echo "lowmarkrenumber begin `date`: (${LOWMARKFILE})" >>${EXPLOG}
+ ctlinnd -s -t`wc -l <${ACTIVE}` lowmark ${LOWMARKFILE} 2>&1
+ echo "lowmarkrenumber end `date`" >>${EXPLOG}
+ rm -f ${MOST_LOGS}/expire.lastlowmark
+ mv ${LOWMARKFILE} ${MOST_LOGS}/expire.lastlowmark
+ }
+ fi
+fi
+# in case noexpire expireover
+if ${DOEXPIREOVER}; then
+ if ${DOEXPLOG}; then
+ echo " ${EXPIREOVER} start `date`" >>${EXPLOG}
+ fi
+ ( cd ${HISTDIR} ; eval ${EXPIREOVER} "${EXPIREOVERFLAGS}" 2>&1 ) | \
+ grep -v 'No such file or directory'
+ if ${DOEXPLOG}; then
+ echo " ${EXPIREOVER} end `date`" >>${EXPLOG}
+ fi
+ test -n "${LOWMARKFILE}" && {
+ echo "lowmarkrenumber begin `date`: (${LOWMARKFILE})" >>${EXPLOG}
+ ctlinnd -s -t`wc -l <${ACTIVE}` lowmark ${LOWMARKFILE} 2>&1
+ echo "lowmarkrenumber end `date`" >>${EXPLOG}
+ rm -f ${MOST_LOGS}/expire.lastlowmark
+ mv ${LOWMARKFILE} ${MOST_LOGS}/expire.lastlowmark
+ }
+fi
+
+## Renumber the active file.
+if ${DORENUMBER} ; then
+ echo ''
+ echo 'Renumbering active file.'
+ if [ -z "${RENUMBER}" ] ;then
+ ctlinnd -s -t`wc -l <${ACTIVE}` renumber '' 2>&1
+ else
+ while read GROUP hi lo flag ; do
+ ctlinnd -s renumber ${GROUP} 2>&1
+ sleep ${RENUMBER}
+ done <${ACTIVE}
+ fi
+fi
+
+## Display expire log messages
+if ${DOMAIL} ; then
+ if [ -s ${EXPLOG} ] ; then
+ echo Expire messages:
+ cat ${EXPLOG}
+ echo ---------
+ echo ''
+ fi
+fi
+
+## Show the status of the news system after expiration.
+${DOSTAT} && {
+ echo 'Post expiration status:' ; echo ''
+ ${INNSTAT}
+ echo ''
+}
+
+## Mail the report.
+if ${DOMAIL} ; then
+ # Stop using the temp file, and mail captured output.
+ exec 1>&3 2>&1 3>&-
+ MAIL="${MAILCMD} -s \"${MAILSUBJ}\" ${NEWSMASTER}"
+ test -s ${TEMP} && cat ${TEMP} | ${SED} -e 's/^~/~~/' | eval ${MAIL}
+ rm -f ${TEMP}
+fi
+
+## Run any user programs.
+if [ -n "${POSTPROGRAMS}" ] ; then
+ for P in ${POSTPROGRAMS} ; do
+ echo ''
+ echo "${P}:"
+ eval ${P}
+ done
+fi
+
+## All done
+if ${DAILY} ; then
+ date >${PATHDB}/.news.daily
+fi
+${RNEWS} -U
+exit 0
--- /dev/null
+#! /bin/sh
+# fixscript will replace this line with code to load innshellvars
+
+## $Revision: 7435 $
+## News boot script. Runs as "news" user. Requires inndstart be
+## setuid root. Run from rc.whatever as:
+## su news -c /path/to/rc.news >/dev/console
+##
+## Or to stop INN:
+## su news -c '/path/to/rc.news stop'
+
+waitforpid()
+{
+ i=12
+ while [ $i -gt 0 ];
+ do
+ kill -0 $1 2>/dev/null || break
+ sleep 5
+ printf "."
+ i=`expr $i - 1`
+ done
+ printf "\n"
+}
+
+case X"$1" in
+Xstop)
+ # Stop cnfsstat (if running)
+ if [ -f ${PATHRUN}/cnfsstat.pid ]; then
+ kill `cat ${PATHRUN}/cnfsstat.pid`
+ rm -f ${PATHRUN}/cnfsstat.pid
+ fi
+
+ # Stop innwatch (if running)
+ if [ -f $WATCHPID ]; then
+ kill `cat $WATCHPID`
+ rm -f $WATCHPID
+ fi
+
+ printf "Stopping innd: "
+ ${PATHBIN}/ctlinnd throttle 'rc.news stop'
+ ${PATHBIN}/ctlinnd shutdown 'rc.news stop'
+
+ # wait for innd to exit (up to 60 sec)
+ pid=`cat ${SERVERPID} 2>/dev/null`
+ if [ "$pid" != "" ]; then
+ waitforpid $pid
+ else
+ printf "\n"
+ fi
+
+ # Turn off ovdb support procs, and close the DB environment
+ if [ "$OVMETHOD" = "ovdb" -a -f ${PATHRUN}/ovdb_server.pid ]; then
+ pid=`cat ${PATHRUN}/ovdb_server.pid 2>/dev/null`
+ if [ "$pid" != "" ]; then
+ printf "Stopping ovdb_server: "
+ kill $pid
+ waitforpid $pid
+ fi
+ fi
+ if [ "$OVMETHOD" = "ovdb" -a -f ${PATHRUN}/ovdb_monitor.pid ]; then
+ pid=`cat ${PATHRUN}/ovdb_monitor.pid 2>/dev/null`
+ if [ "$pid" != "" ]; then
+ printf "Stopping ovdb_monitor: "
+ kill $pid
+ waitforpid $pid
+ fi
+ fi
+
+ if [ -f ${PATHBIN}/rc.news.local ]; then
+ ${PATHBIN}/rc.news.local stop
+ fi
+
+ # Delete all of the PID files that we know about to avoid having them
+ # stick around after a fresh start.
+ rm -f ${PATHRUN}/cnfsstat.pid $WATCHPID $SERVERPID
+ rm -f ${PATHRUN}/ovdb_server.pid ${PATHRUN}/ovdb_monitor.pid
+
+ exit 0
+;;
+esac
+
+## Pick ${INND} or ${INNDSTART}
+WHAT=${INNDSTART}
+
+MAIL="${MAILCMD} -s 'Boot-time Usenet warning on `hostname`' ${NEWSMASTER}"
+
+## RFLAG is set below; set INNFLAGS in inn.conf(5)
+RFLAG=""
+
+## Clean shutdown or already running?
+if [ -f ${SERVERPID} ] ; then
+ if kill -0 `cat ${SERVERPID}` 2>/dev/null; then
+ echo 'INND is running'
+ exit 0
+ fi
+ echo 'INND: PID file exists -- unclean shutdown!'
+ RFLAG="-r"
+fi
+
+if [ ! -f ${PATHDB}/.news.daily ] ; then
+ case `find ${PATHBIN}/innd -mtime +1 -print 2>/dev/null` in
+ "")
+ ;;
+ *)
+ echo 'No .news.daily file; need to run news.daily?' | eval ${MAIL}
+ ;;
+ esac
+else
+ case `find ${PATHDB}/.news.daily -mtime +1 -print 2>/dev/null` in
+ "")
+ ;;
+ *)
+ echo 'Old .news.daily file; need to run news.daily?' | eval ${MAIL}
+ ;;
+ esac
+fi
+
+## Active file recovery.
+if [ ! -s ${ACTIVE} ] ; then
+ if [ -s ${NEWACTIVE} ] ; then
+ mv ${NEWACTIVE} ${ACTIVE}
+ else
+ if [ -s ${OLDACTIVE} ] ; then
+ cp ${OLDACTIVE} ${ACTIVE}
+ else
+ echo 'INND: No active file!'
+ exit 1
+ fi
+ fi
+ RFLAG="-r"
+ # You might want to rebuild the DBZ database, too:
+ #cd ${PATHDB} \
+ # && makehistory -r \
+ # && mv history.n.dir history.dir \
+ # && mv history.n.index history.index \
+ # && mv history.n.hash history.hash
+fi
+
+## Remove temporary batchfiles and lock files.
+( cd ${BATCH} && rm -f bch* )
+( cd ${LOCKS} && rm -f LOCK* )
+( cd ${TEMPSOCKDIR} && rm -f ${TEMPSOCK} )
+rm -f ${NEWSCONTROL} ${NNTPCONNECT} ${SERVERPID}
+
+## Initialize ovdb. Must be done before starting innd.
+if [ "$OVMETHOD" = "ovdb" ]; then
+ echo 'Starting ovdb.'
+ ovdb_init || {
+ echo "Can't initialize ovdb (check news.err for OVDB messages)"
+ exit 1
+ }
+ sleep 1
+fi
+
+## Start the show.
+echo 'Starting innd.'
+eval ${WHAT} ${RFLAG} ${INNFLAGS}
+
+# Gee, looks like lisp, doesn't it?
+${DOINNWATCH} && {
+ echo "Scheduled start of ${INNWATCH}."
+ ( sleep 60 ; ${INNWATCH} ) > /dev/null &
+}
+
+${DOCNFSSTAT} && {
+ echo "Scheduled start of cnfsstat."
+ ( sleep 60 ; ${PATHBIN}/cnfsstat -s -l -P ) > /dev/null &
+}
+
+RMFILE=${MOST_LOGS}/expire.rm
+for F in ${RMFILE} ${RMFILE}.*; do
+ if [ -f $F -a -s $F ] ; then
+ echo "Removing articles from pre-downtime expire run (${F})."
+ (
+ echo 'System shut down during expire.' \
+ 'Unlinking articles listed in'
+ echo ${F}
+ ) | eval ${MAIL}
+ ${PATHBIN}/expirerm ${F}
+ fi
+done &
+
+# Run any additional local startup commands.
+if [ -f ${PATHBIN}/rc.news.local ] ; then
+ ${PATHBIN}/rc.news.local start
+fi
--- /dev/null
+#! /bin/sh
+# fixscript will replace this line with code to load innshellvars
+
+## $Revision: 7770 $
+## Summarize INN log files.
+## Optional arguments:
+## norotate Do not rotate logfiles
+
+## If you get an error from this line:
+## sh -c 'egrep "`ls /etc`" /dev/null'
+## then get a better egrep, like the FSF one.
+
+## Directory where old log files are kept.
+OLD=${MOST_LOGS}/OLD
+## If you want to archive the active file, enable this line.
+ACTIVEFILE=${ACTIVE}
+## Number of lines of error in each category to report.
+TOP=${TOP-20}
+## NN log file.
+NN=${PATHETC}/nn/Log
+
+## Where these programs, if used, write their logs.
+## We also have to find innfeed's log file.
+INNFEEDLOG=`${AWK} '{gsub(/:|#/, " & ")} {if ($1 == "log-file" && $2 == ":") print $3}' ${PATHETC}/innfeed.conf`
+INNFEED=
+for F in "${INNFEEDLOG}" ; do
+ test -f "${MOST_LOGS}/${F}" && INNFEED="${INNFEED} ${MOST_LOGS}/${F}"
+done
+test -z "${INNFEED}" && test -f ${MOST_LOGS}/innfeed.log && INNFEED="${MOST_LOGS}/innfeed.log"
+
+
+## Where nntpsend, if used, writes its log information.
+NNTPSEND=${MOST_LOGS}/nntpsend.log
+SENDNNTP=${MOST_LOGS}/send-nntp.log
+SENDUUCP=${MOST_LOGS}/send-uucp.log
+LIVEFILES="${INNFEED} ${NNTPSEND} ${SENDNNTP} ${SENDUUCP}"
+## Where news.daily places expire output, unless noexplog was used.
+EXPLOG=${MOST_LOGS}/expire.log
+
+## If you divide your news syslog into separate files, list them here.
+SYSLOG_CRIT=${MOST_LOGS}/news.crit
+SYSLOG_ERR=${MOST_LOGS}/news.err
+SYSLOG_NOTICE=${MOST_LOGS}/news.notice
+SYSLOGS="${SYSLOG_CRIT} ${SYSLOG_ERR} ${SYSLOG_NOTICE}"
+
+## Where tally control and unwanted processors are found.
+TALLY_CONTROL=${PATHBIN}/tally.control
+TALLY_UNWANTED=${PATHBIN}/tally.unwanted
+UNWANTED_LOG=${MOST_LOGS}/unwanted.log
+CONTROL_LOG=${MOST_LOGS}/control.log
+CONTROL_DATA=
+test -f ${MOST_LOGS}/newgroup.log && CONTROL_DATA=${MOST_LOGS}/newgroup.log
+test -f ${MOST_LOGS}/rmgroup.log \
+ && CONTROL_DATA="${CONTROL_DATA} ${MOST_LOGS}/rmgroup.log"
+
+## Build up the list of log files to process.
+LOGS="${ERRLOG} ${EXPLOG} ${LOG} ${ACTIVEFILE} ${SYSLOGS} ${UNWANTED_LOG}"
+
+for F in ${LIVEFILES} ; do
+ test -n "${F}" -a -f "${F}" && LOGS="${LOGS} ${F}"
+done
+
+test -n "${CONTROL_DATA}" && LOGS="${LOGS} ${CONTROL_LOG}"
+for F in checkgroups default ihave newgroup rmgroup sendme sendsys \
+ senduuname version miscctl badcontrol failedpgp badpgp; do
+ test -f ${MOST_LOGS}/${F}.log && LOGS="${LOGS} ${MOST_LOGS}/${F}.log"
+done
+
+PROGNAME=scanlogs
+LOCK=${LOCKS}/LOCK.${PROGNAME}
+
+## Set defaults.
+ROTATE=true
+
+## Parse JCL.
+for I
+do
+ case "X${I}" in
+ Xnonn)
+ # Ignore this.
+ ;;
+ Xnorotate)
+ ROTATE=false
+ ;;
+ *)
+ echo "Unknown flag ${I}" 1>&2
+ exit 1
+ ;;
+ esac
+done
+
+## Make sure every log exists.
+for F in ${LOGS} ; do
+ test ! -f ${F} && touch ${F}
+done
+
+## Temporary files.
+T=${TMPDIR}/scan$$
+PROBS=${TMPDIR}/scanlog$$
+trap "rm -f ${T} ${PROBS}; exit 0" 0 1 2 3 15
+
+## Rotate the logs?
+if ${ROTATE} ; then
+ ## Lock out others.
+ shlock -p $$ -f ${LOCK} || {
+ echo "$0: Locked by `cat ${LOCK}`"
+ exit 1
+ }
+ trap "rm -f ${T} ${PROBS} ${LOCK}; exit 0" 1 2 3 15
+
+ HERE=`pwd`
+ cd ${MOST_LOGS}
+ test ! -d ${OLD} && mkdir ${OLD}
+
+ ctlinnd -s logmode
+ PAUSED=false
+ ctlinnd -s pause "Flushing log and syslog files" 2>&1 && PAUSED=true
+ OUTPUT=`ctlinnd flushlogs 2>&1`
+ if [ "$OUTPUT" != "Ok" -a "$OUTPUT" != "In debug mode" ]; then
+ echo "$OUTPUT"
+ echo 'Cannot flush logs.'
+ rm -f ${LOCK}
+ exit 1
+ fi
+
+ ## Make sure these .old files exist, in case innd is down.
+ for F in ${LOG} ${ERRLOG} ${EXPLOG} ; do
+ if [ ! -f ${F}.old ]; then
+ rm -f ${F}.old
+ cp ${F} ${F}.old
+ cat /dev/null >${F}
+ fi
+ done
+
+ ## Copy syslog files, truncating old inode since syslog has it open.
+ for F in ${SYSLOGS}; do
+ rm -f ${F}.old
+ cp ${F} ${F}.old
+ cat /dev/null >${F}
+ done
+ ctlinnd -s logmode
+
+ ## Make a copy of the active file.
+ if [ -n ${ACTIVEFILE} ] ; then
+ BASE=`basename ${ACTIVEFILE}`
+ rm -f ${OLD}/${BASE}.old
+ cp ${ACTIVEFILE} ${OLD}/${BASE}.old
+ fi
+
+ ## These are live files, so use link rather than copy.
+ for F in ${LIVEFILES} ; do
+ if [ -f ${F} ]; then
+ rm -f ${F}.old ${F}.new
+ ln ${F} ${F}.old
+ touch ${F}.new
+ chmod 0660 ${F}.new
+ mv ${F}.new ${F}
+ fi
+ done
+
+ ## Tally control messages if we logged them.
+ test -n "${CONTROL_DATA}" && cat ${CONTROL_DATA} | ${TALLY_CONTROL}
+
+ ${PAUSED} && ctlinnd -s go "Flushing log and syslog files" 2>&1
+
+ cd ${OLD}
+ for F in ${LOGS}; do
+ ## Process the current (just-flushed) log
+ BASE=`basename ${F}`
+ rm -f ${OLD}/${BASE}
+ case ${F} in
+ ${SYSLOG_CRIT}|${ERRLOG}|${EXPLOG}|${LOG}|${SYSLOG_NOTICE})
+ ## Make a link that can be deleted (since if not rotating
+ ## we delete the copy that is made in ${TMPDIR}).
+ mv ${F}.old ${OLD}/${BASE}
+ rm -f ${OLD}/${BASE}.0
+ ln ${OLD}/${BASE} ${OLD}/${BASE}.0
+ ;;
+ ${ACTIVEFILE})
+ mv ${BASE}.old ${OLD}/${BASE}
+ ;;
+ ${SYSLOG_ERR})
+ mv ${F}.old ${OLD}/${BASE}
+ ;;
+ ${UNWANTED_LOG})
+ ## Rotate and compress the file.
+ BASE=`basename ${F}`
+ if [ ! -f ${BASE} -a -f ../${BASE} ]; then
+ cp ../${BASE} ${BASE}
+ chmod 0440 ${BASE}
+ fi
+ if [ -f ${BASE} ]; then
+ ${LOG_COMPRESS} <${BASE} >${BASE}.0${Z} && rm -f ${BASE}
+ chmod 0440 ${BASE}.0${Z}
+
+ ## Do rotation.
+ if [ X${LOGCYCLES} = X ]; then
+ LOGCYCLES=3
+ fi
+ EXT=${LOGCYCLES}
+ rm -f ${BASE}.${LOGCYCLES}${Z}
+ while [ ${EXT} -gt 0 ] ; do
+ NEXT=${EXT}
+ EXT=`expr ${EXT} - 1`
+ test -f ${BASE}.${EXT}${Z} \
+ && rm -f ${BASE}.${NEXT}${Z} \
+ && mv ${BASE}.${EXT}${Z} ${BASE}.${NEXT}${Z}
+ done
+ fi
+ ## Innreport assumes where unwanted.log exists, so leave it
+ ## and process later.
+ ;;
+ *)
+ if [ -f ${F}.old ]; then
+ mv ${F}.old ${OLD}/${BASE}
+ else
+ rm -f ${OLD}/${BASE} ${F}.new
+ touch ${F}.new
+ chmod 0660 ${F}.new
+ ln ${F} ${F}.old
+ mv ${F}.new ${F}
+ mv ${F}.old ${OLD}/${BASE}
+ fi
+ ;;
+ esac
+ done
+ cd ${HERE}
+else
+ ## Don't use the real OLD directory, instead use TMPDIR
+ OLD=${TMPDIR}
+
+ ## Make a snapshot of what we need for below.
+ ctlinnd -s pause "Snapshot log and syslog files" 2>&1
+ for F in ${SYSLOG_CRIT} ${ERRLOG} ${EXPLOG} ${LOG} ${SYSLOG_NOTICE} ; do
+ BASE=`basename ${F}`
+ rm -f ${OLD}/${BASE}.0
+ cp ${F} ${OLD}/${BASE}.0
+ done
+ ctlinnd -s go "Snapshot log and syslog files" 2>&1
+fi
+
+##
+## We now (finally!) have copies of the log files where we need them.
+##
+
+## Display syslog critical messages.
+BASE=`basename ${SYSLOG_CRIT}`
+OLD_SYSLOG=${OLD}/${BASE}.0
+if [ -s ${OLD_SYSLOG} ] ; then
+ echo Syslog critical messages:
+ cat ${OLD_SYSLOG}
+ echo ---------
+ echo ''
+fi
+rm -f ${OLD_SYSLOG}
+
+## Display error log.
+BASE=`basename ${ERRLOG}`
+OLD_ERRLOG=${OLD}/${BASE}.0
+if [ -s ${OLD_ERRLOG} ] ; then
+ echo Error log:
+ cat ${OLD_ERRLOG}
+ echo ---------
+ echo ''
+fi
+rm -f ${OLD_ERRLOG}
+
+## Scan for various problems in articles we were offered or sent...
+BASE=`basename ${LOG}`
+OLD_LOG=${OLD}/${BASE}.0
+
+## and summarize syslog information.
+BASE=`basename ${SYSLOG_NOTICE}`
+OLD_SYSLOG=${OLD}/${BASE}.0
+if [ -s ${OLD_SYSLOG} -o -s ${OLD_LOG} ] ; then
+ ${PATHBIN}/innreport -f ${PATHETC}/innreport.conf ${OLD_SYSLOG} ${OLD_LOG}
+ echo ---------
+ echo ''
+fi
+rm -f ${OLD_LOG} ${OLD_SYSLOG}
+if ${ROTATE} ; then
+ BASE=`basename ${UNWANTED_LOG}`
+ if [ -f ${UNWANTED_LOG}.old ]; then
+ mv ${UNWANTED_LOG}.old ${OLD}/${BASE}
+ else
+ rm -f ${OLD}/${BASE}
+ cp ${UNWANTED_LOG} ${OLD}/${BASE}
+ chmod 0660 ${OLD}/${BASE}
+ fi
+fi
+
+OLD_SYSLOG=${OLD}/${EXPLOG}.0
+rm -f ${EXPLOG}
+
+## Compress and rotate the logs.
+if ${ROTATE} ; then
+ cd ${OLD}
+ if [ X${LOGCYCLES} = X ]; then
+ LOGCYCLES=3
+ fi
+ for F in ${LOGS} ; do
+ ## Skip if it's unwanted.log, since it's already rotated
+ if [ ${F} = ${UNWANTED_LOG} ]; then
+ continue
+ fi
+ ## Skip if file doesn't exist.
+ BASE=`basename ${F}`
+ test -f ${BASE} || continue
+
+ ## Compress the file.
+ ${LOG_COMPRESS} <${BASE} >${BASE}.0${Z} && rm -f ${BASE}
+ chmod 0440 ${BASE}.0${Z}
+
+ ## Do rotation.
+ EXT=${LOGCYCLES}
+ rm -f ${BASE}.${LOGCYCLES}${Z}
+ while [ ${EXT} -gt 0 ] ; do
+ NEXT=${EXT}
+ EXT=`expr ${EXT} - 1`
+ test -f ${BASE}.${EXT}${Z} \
+ && rm -f ${BASE}.${NEXT}${Z} \
+ && mv ${BASE}.${EXT}${Z} ${BASE}.${NEXT}${Z}
+ done
+ done
+
+ ## Remove lock.
+ rm -f ${LOCK}
+fi
+
+## All done.
+exit 0
--- /dev/null
+#! /usr/bin/perl -w
+
+# simpleftp - Rudimentary FTP client.
+#
+# Author: David Lawrence <tale@isc.org>.
+# Rewritten by Julien Elie <julien@trigofacile.com> to use Net::FTP.
+#
+# Fetch files to the local machine based on URLs on the command line.
+# INN's configure searches for ncftp, wget, linx, et caetera,
+# but they're all system add-ons, so this is provided.
+#
+# Perl 5 is already required by other parts of INN; it only took a half hour
+# to write this, so this was the easiest way to go for a backup plan.
+#
+# This script is nowhere near as flexible as libwww. If you really need
+# that kind of power, get libwww and use it. This is just sufficient for what
+# INN needed.
+
+use strict;
+use Net::FTP;
+use Sys::Hostname;
+
+$0 =~ s(.*/)();
+
+my $usage = "Usage: $0 ftp-URL ...\n";
+
+@ARGV
+ or die $usage;
+
+for (@ARGV) {
+ m(^ftp://)
+ or die $usage;
+}
+
+my ($lasthost, $ftp);
+
+# This will keep track of how many _failed_.
+my $exit = @ARGV;
+
+for (@ARGV) {
+ my ($host, $path) = m%^ftp://([^/]+)(/.+)%;
+ my $user = 'anonymous';
+ my $pass = (getpwuid($<))[0] . '@' . hostname;
+ my $port = 21;
+
+ unless (defined $host && defined $path) {
+ warn "$0: bad URL: $_\n";
+ next;
+ }
+
+ if ($host =~ /(.*):(.*)\@(.*)/) {
+ $user = $1;
+ $pass = $2;
+ $host = $3;
+ }
+
+ if ($host =~ /(.*):(.*)/) {
+ $port = $1;
+ $host = $2;
+ }
+
+ if (defined $lasthost && $host ne $lasthost) {
+ $ftp->quit;
+ $lasthost = undef;
+ }
+
+ unless (defined $lasthost) {
+ $ftp = Net::FTP->new($host, Port => $port)
+ or next;
+ $ftp->login($user, $pass)
+ or next;
+ }
+
+ my $localfile = $path;
+ $path =~ s([^/]+$)();
+ $localfile =~ s(.*/)();
+
+ $ftp->cwd($path)
+ or next;
+ $ftp->get($localfile)
+ or next;
+
+ $exit--;
+ $lasthost = $host;
+}
+
+$ftp->quit
+ if defined $lasthost;
+
+exit $exit;
--- /dev/null
+#! /bin/sh
+# fixscript will replace this line with code to load innshellvars
+
+## $Revision: 2677 $
+## Tally/update the newgroup/rmgroup control log.
+## Merge in a log that contains newgroup/rmgroup control messages so that
+## the "control.log" file is updated to contain the new counts of how
+## often each group has been newgroup'd or rmgroup'd. This is run by
+## scanlogs, which prepares this from the control-message handlers if
+## control.ctl specifies logging.
+
+CONTROL=${MOST_LOGS}/control.log
+CONTROL_NEW=${CONTROL}.new
+CONTROL_OLD=${CONTROL}.old
+
+PROGNAME=`basename $0`
+LOCK=${LOCKS}/LOCK.${PROGNAME}
+
+## Lock.
+trap 'rm -f ${LOCK} ; exit 1' 1 2 3 15
+shlock -f ${LOCK} -p $$ || {
+ echo "$0: cannot lock ${LOCK}" 1>&2
+ exit 1
+}
+
+## Prepare the files.
+if [ ! -f ${CONTROL} ]; then
+ touch ${CONTROL}
+ chmod 0660 ${CONTROL}
+fi
+rm -f ${CONTROL_NEW} ${CONTROL_OLD}
+ln ${CONTROL} ${CONTROL_OLD}
+touch ${CONTROL_NEW}
+chmod 0660 ${CONTROL_NEW}
+
+## Grab the data.
+${SED} -n -e 's/[ ][ ]*/ /g' -e 's/^ \(Control:.*\)$/1 \1/p' \
+ | cat - ${CONTROL} \
+ | ${SED} -e 's/ /#/g' -e 's/\([0-9][0-9]*\)#\(.*\)/\1 \2/' \
+ | ${AWK} 'BEGIN {
+ ctl[0]=0;
+ }
+ {
+ ctl[$2] += $1;
+ }
+ END {
+ for (line in ctl) {
+ if (line != 0) {
+ print ctl[line], line;
+ }
+ }
+ }' \
+ | tr '#' ' ' \
+ | sort -n -r >${CONTROL_NEW}
+mv -f ${CONTROL_NEW} ${CONTROL}
+
+## All done.
+rm -f ${LOCK}
+exit 0
--- /dev/null
+#! /bin/sh
+# fixscript will replace this line with code to load innshellvars
+
+## $Revision: 2677 $
+## Write a log file entry, by either mailing it or writing it safely.
+## Usage:
+## writelog name text... <input
+## where
+## name is 'mail' to mail it, or filename to append to.
+
+MAXTRY=60
+
+## Parse arguments.
+if [ $# -lt 2 ] ; then
+ echo "usage: $0 'logfile|mail' message ..." 1>&2
+ exit 1
+fi
+LOGFILE="$1"
+shift
+MESSAGE="$@"
+
+## Handle the easy cases.
+case "X${LOGFILE}" in
+X/dev/null)
+ exit 0
+ ;;
+Xmail)
+ sed -e 's/^~/~~/' | ${MAILCMD} -s "${MESSAGE}" ${NEWSMASTER}
+ exit 0
+ ;;
+esac
+
+## We're sending to a file.
+LOCK=${LOCKS}/LOCK.`basename ${LOGFILE}`
+
+## Remember our PID, in case while is a sub-shell.
+PID=$$
+TRY=0
+
+export LOCK MAXTRY PID LOGFILE ARTICLE MESSAGE TRY
+while [ ${TRY} -lt ${MAXTRY} ]; do
+ shlock -p ${PID} -f ${LOCK} && break
+ sleep 2
+ TRY=`expr ${TRY} + 1`
+done
+
+## If we got the lock, update the file; otherwise, give up.
+if [ ${TRY} -lt ${MAXTRY} ]; then
+ echo "${MESSAGE}" >>${LOGFILE}
+ ${SED} -e 's/^/ /' >>${LOGFILE}
+ echo "" >>${LOGFILE}
+ rm -f ${LOCK}
+else
+ ## This goes to errlog, usually.
+ echo "$0: Cannot grab lock ${LOCK}, held by:" `cat ${LOCK}` 1>&2
+fi
--- /dev/null
+## $Revision: 7907 $
+include ../Makefile.global
+top = ..
+
+## If you want to do ctlinnd pause/reload/go, uncomment these lines.
+#PAUSE = pause
+#RELOAD_AND_GO = reload go
+DIFF="diff"
+
+# Added a default rule for ".csh" because Digital UNIX has a builtin
+# rule which would overwite the innshellvars file.
+.csh:
+
+CTLINND = ${PATHBIN}/ctlinnd
+FILTBIN = ${PATHFILTER}
+PATH_PERL_STARTUP_INND = ${PATHFILTER}/startup_innd.pl
+PATH_PERL_FILTER_INND = ${PATHFILTER}/filter_innd.pl
+PATH_PERL_FILTER_NNRPD = ${PATHFILTER}/filter_nnrpd.pl
+PATH_TCL_STARTUP = ${PATHFILTER}/startup.tcl
+PATH_TCL_FILTER = ${PATHFILTER}/filter.tcl
+PATH_PYTHON_FILTER_INND = ${PATHFILTER}/filter_innd.py
+PATH_PYTHON_INN_MODULE = ${PATHFILTER}/INN.py
+PATH_PYTHON_NNRPD_MODULE= ${PATHFILTER}/nnrpd.py
+PATH_NNRPAUTH = ${PATHFILTER}/nnrpd_auth.pl
+PATH_NNRPYAUTH = ${PATHFILTER}/nnrpd_auth.py
+PATH_NNRPACCESS = ${PATHFILTER}/nnrpd_access.pl
+PATH_NNRPYACCESS = ${PATHFILTER}/nnrpd_access.py
+PATH_NNRPYDYNAMIC = ${PATHFILTER}/nnrpd_dynamic.py
+
+PATH_CONFIG = ${PATHETC}/inn.conf
+PATH_CONTROLCTL = ${PATHETC}/control.ctl
+PATH_EXPIRECTL = ${PATHETC}/expire.ctl
+PATH_INNDHOSTS = ${PATHETC}/incoming.conf
+PATH_MODERATORS = ${PATHETC}/moderators
+PATH_DISTPATS = ${PATHETC}/distrib.pats
+PATH_NEWSFEEDS = ${PATHETC}/newsfeeds
+PATH_READERSCONF = ${PATHETC}/readers.conf
+PATH_NNRPDTRACK = ${PATHETC}/nnrpd.track
+PATH_SCHEMA = ${PATHETC}/overview.fmt
+PATH_NNTPPASS = ${PATHETC}/passwd.nntp
+PATH_CTLWATCH = ${PATHETC}/innwatch.ctl
+PATH_ACTSYNC_IGN = ${PATHETC}/actsync.ign
+PATH_ACTSYNC_CFG = ${PATHETC}/actsync.cfg
+PATH_MOTD = ${PATHETC}/motd.news
+PATH_STORAGECONF = ${PATHETC}/storage.conf
+PATH_CYCBUFFCONFIG = ${PATHETC}/cycbuff.conf
+PATH_INNFEEDCTL = ${PATHETC}/innfeed.conf
+PATH_BUFFINDEXED = ${PATHETC}/buffindexed.conf
+PATH_RADIUS_CONF = ${PATHETC}/radius.conf
+PATH_OVDB_CONF = ${PATHETC}/ovdb.conf
+PATH_SASL_CONF = ${PATHETC}/sasl.conf
+PATH_SUBSCRIPTIONS = ${PATHETC}/subscriptions
+
+PATH_ACTIVE = ${PATHDB}/active
+PATH_ACTIVE_TIMES = ${PATHDB}/active.times
+PATH_HISTORY = ${PATHDB}/history
+PATH_NEWSGROUPS = ${PATHDB}/newsgroups
+
+## Scripts from above, plus site-specific config files.
+REST = \
+ newsfeeds incoming.conf nnrpd.track passwd.nntp \
+ inn.conf moderators innreport.conf \
+ control.ctl expire.ctl nntpsend.ctl overview.fmt \
+ innwatch.ctl distrib.pats actsync.cfg actsync.ign \
+ motd.news storage.conf cycbuff.conf buffindexed.conf \
+ innfeed.conf startup_innd.pl filter_innd.pl filter_nnrpd.pl \
+ filter_innd.py INN.py nnrpd.py \
+ startup.tcl filter.tcl nnrpd_auth.pl nnrpd_access.pl \
+ nnrpd_access.py nnrpd_dynamic.py \
+ news2mail.cf readers.conf \
+ radius.conf nnrpd_auth.py ovdb.conf sasl.conf active.minimal \
+ newsgroups.minimal subscriptions
+
+ALL = $(MOST) $(REST)
+
+REST_INSTALLED = \
+ $D$(PATH_NEWSFEEDS) $D$(PATH_INNDHOSTS) \
+ $D$(PATH_NNRPDTRACK) $D$(PATH_NNTPPASS) \
+ $D$(PATH_CONFIG) $D$(PATH_MODERATORS) \
+ $D$(PATH_CONTROLCTL) $D$(PATH_EXPIRECTL) $D$(PATHETC)/nntpsend.ctl \
+ $D$(PATHETC)/innreport.conf \
+ $D$(PATH_CTLWATCH) $D$(PATH_DISTPATS) $D$(PATH_SCHEMA) \
+ $D$(PATH_ACTSYNC_CFG) $D$(PATH_ACTSYNC_IGN) \
+ $D$(PATH_MOTD) $D$(PATH_STORAGECONF) \
+ $D$(PATH_OVERVIEWCTL) $D$(PATH_CYCBUFFCONFIG) $D$(PATH_BUFFINDEXED) \
+ $D$(PATH_INNFEEDCTL) $D$(PATH_PERL_STARTUP_INND) \
+ $D$(PATH_PERL_FILTER_INND) $D$(PATH_PERL_FILTER_NNRPD) \
+ $D$(PATH_PYTHON_FILTER_INND) $D$(PATH_PYTHON_INN_MODULE) \
+ $D$(PATH_TCL_STARTUP) $D$(PATH_TCL_FILTER) $D$(PATH_PYTHON_NNRPD_MODULE) \
+ $D$(PATH_NNRPAUTH) $D$(PATHETC)/news2mail.cf $D$(PATH_READERSCONF) \
+ $D$(PATH_RADIUS_CONF) $D$(PATH_NNRPYAUTH) $D$(PATH_OVDB_CONF) \
+ $D$(PATH_NNRPYACCESS) $D$(PATH_NNRPYDYNAMIC) \
+ $D$(PATH_SASL_CONF) $D$(PATH_SUBSCRIPTIONS) $D$(PATH_NNRPACCESS)
+
+ALL_INSTALLED = $(MOST_INSTALLED) $(REST_INSTALLED)
+
+SPECIAL = $D$(PATH_ACTIVE) $D$(PATH_ACTIVE_TIMES) \
+ $D$(PATH_NEWSGROUPS) $D$(PATH_HISTORY)
+
+## Get new versions of everything from samples directory.
+all: $(P) $(ALL) config
+
+## Get only scripts, not per-host config files.
+most: $(MOST)
+
+## Show changes between files here and ones in samples.
+diff:
+ @$(MAKE) COPY=-${DIFF} all
+
+## Show changes between files here and installed versions.
+diff-installed:
+ @$(MAKE) COPY_RPRI=-${DIFF} COPY_RPUB=-${DIFF} COPY_XPRI=-${DIFF} COPY_XPUB=-${DIFF} $(ALL_INSTALLED)
+
+## Show what would be copied from samples directory.
+what:
+ @$(MAKE) -s 'COPY=@echo' $(ALL) | ${AWK} 'NF==2 { print $$2; }'
+
+config: $(ALL)
+ date >config
+
+## Don't use parallel rules -- we want this to be viewed carefully.
+install: all $(PAUSE) install-config $(RELOAD_AND_GO)
+reload-install: all pause install-config reload go
+install-config: update $(REST_INSTALLED) $(SPECIAL)
+
+## Install scripts, not per-host config files.
+update: all $(MOST_INSTALLED)
+ @echo "" ; echo inn.conf in site directory may have newly added parameters
+ @echo which installed inn.conf does not have. Check those parameters
+ @echo before you run innd. ; echo ""
+ date >update
+
+## Special rules for files that sould never be overwritten if they are
+## already installed. These are used only for the initial install of a
+## brand new server.
+$D$(PATH_ACTIVE): ; $(CP_DATA) active.minimal $@
+$D$(PATH_NEWSGROUPS): ; $(CP_DATA) newsgroups.minimal $@
+$D$(PATH_ACTIVE_TIMES):
+ touch $@
+ chown $(NEWSUSER) $@
+ chgrp $(NEWSGROUP) $@
+ chmod $(FILEMODE) $@
+$D$(PATH_HISTORY):
+ touch $@
+ chown $(NEWSUSER) $@
+ chgrp $(NEWSGROUP) $@
+ chmod $(FILEMODE) $@
+ $(PATHBIN)/makedbz -i -o
+
+## Remove files that are unchanged from the release version.
+clean:
+ @-for I in $(ALL) ; do \
+ cmp -s $$I ../samples/$$I && echo rm -f $$I && rm -f $$I ; \
+ done
+
+clobber distclean:
+ rm -f $(ALL) tags profiled config update
+
+tags ctags:
+ cp /dev/null tags
+
+profiled:
+ cp /dev/null profiled
+
+depend:
+
+## Commands to make private or public, read or executable files.
+COPY_RPRI = $(CP_RPRI)
+COPY_RPUB = $(CP_RPUB)
+COPY_XPRI = $(CP_XPRI)
+COPY_XPUB = $(CP_XPUB)
+
+## Order: innd, control, expire, inews, sending, misc
+$D$(PATH_INNDHOSTS): incoming.conf ; $(COPY_RPRI) $? $@
+$D$(PATH_NEWSFEEDS): newsfeeds ; $(COPY_RPUB) $? $@
+$D$(PATH_READERSCONF): readers.conf ; $(COPY_RPUB) $? $@
+$D$(PATH_RADIUS_CONF): radius.conf ; $(COPY_RPRI) $? $@
+$D$(PATH_NNRPDTRACK): nnrpd.track ; $(COPY_RPUB) $? $@
+$D$(PATH_SCHEMA): overview.fmt ; $(COPY_RPUB) $? $@
+$D$(PATH_CONTROLCTL): control.ctl ; $(COPY_RPUB) $? $@
+$D$(PATH_CTLWATCH): innwatch.ctl ; $(COPY_RPUB) $? $@
+$D$(PATH_EXPIRECTL): expire.ctl ; $(COPY_RPUB) $? $@
+$D$(PATH_CONFIG): inn.conf ; $(COPY_RPUB) $? $@
+$D$(PATH_MODERATORS): moderators ; $(COPY_RPUB) $? $@
+$D$(PATH_DISTPATS): distrib.pats ; $(COPY_RPUB) $? $@
+$D$(PATH_NNTPPASS): passwd.nntp ; $(COPY_RPRI) $? $@
+$D$(PATHETC)/nntpsend.ctl: nntpsend.ctl ; $(COPY_RPUB) $? $@
+$D$(PATHETC)/news2mail.cf: news2mail.cf ; $(COPY_RPUB) $? $@
+$D$(PATHETC)/innreport.conf: innreport.conf ; $(COPY_RPUB) $? $@
+$D$(PATH_STORAGECONF): storage.conf ; $(COPY_RPUB) $? $@
+$D$(PATH_CYCBUFFCONFIG): cycbuff.conf ; $(COPY_RPUB) $? $@
+$D$(PATH_BUFFINDEXED): buffindexed.conf ; $(COPY_RPUB) $? $@
+$D$(PATH_OVDB_CONF): ovdb.conf ; $(COPY_RPUB) $? $@
+$D$(PATH_PERL_STARTUP_INND): startup_innd.pl ; $(COPY_RPUB) $? $@
+$D$(PATH_PERL_FILTER_INND): filter_innd.pl ; $(COPY_RPUB) $? $@
+$D$(PATH_PERL_FILTER_NNRPD): filter_nnrpd.pl ; $(COPY_RPUB) $? $@
+$D$(PATH_PYTHON_FILTER_INND): filter_innd.py ; $(COPY_RPUB) $? $@
+$D$(PATH_PYTHON_INN_MODULE): INN.py ; $(COPY_RPUB) $? $@
+$D$(PATH_PYTHON_NNRPD_MODULE): nnrpd.py ; $(COPY_RPUB) $? $@
+$D$(PATH_TCL_STARTUP): startup.tcl ; $(COPY_RPUB) $? $@
+$D$(PATH_TCL_FILTER): filter.tcl ; $(COPY_RPUB) $? $@
+$D$(PATH_NNRPAUTH): nnrpd_auth.pl ; $(COPY_RPUB) $? $@
+$D$(PATH_NNRPACCESS): nnrpd_access.pl ; $(COPY_RPUB) $? $@
+$D$(PATH_NNRPYAUTH): nnrpd_auth.py ; $(COPY_RPUB) $? $@
+$D$(PATH_NNRPYACCESS): nnrpd_access.py ; $(COPY_RPUB) $? $@
+$D$(PATH_NNRPYDYNAMIC): nnrpd_dynamic.py ; $(COPY_RPUB) $? $@
+$D$(PATH_ACTSYNC_CFG): actsync.cfg ; $(COPY_RPUB) $? $@
+$D$(PATH_ACTSYNC_IGN): actsync.ign ; $(COPY_RPUB) $? $@
+$D$(PATH_MOTD): motd.news ; $(COPY_RPUB) $? $@
+$D$(PATH_INNFEEDCTL): innfeed.conf ; $(COPY_RPRI) $? $@
+$D$(PATH_SASL_CONF): sasl.conf ; $(COPY_RPUB) $? $@
+$D$(PATH_SUBSCRIPTIONS): subscriptions ; $(COPY_RPUB) $? $@
+
+REASON = 'Installing site config files from site/Makefile'
+go pause:
+ -${CTLINND} $@ $(REASON)
+reload:
+ -${CTLINND} reload all $(REASON)
+
+## Use this to just replace any changed files you might have made. Only
+## do this after you've examined the output of "make -n"!
+replace:
+ $(MAKE) COPY=cp all
+
+## Get files from the samples directory.
+COPY = $(SHELL) ./getsafe.sh
+actsync.cfg: ../samples/actsync.cfg ; $(COPY) $? $@
+actsync.ign: ../samples/actsync.ign ; $(COPY) $? $@
+control.ctl: ../samples/control.ctl ; $(COPY) $? $@
+expire.ctl: ../samples/expire.ctl ; $(COPY) $? $@
+filter.tcl: ../samples/filter.tcl ; $(COPY) $? $@
+nnrpd_auth.pl: ../samples/nnrpd_auth.pl ; $(COPY) $? $@
+nnrpd_access.pl: ../samples/nnrpd_access.pl ; $(COPY) $? $@
+nnrpd_auth.py: ../samples/nnrpd_auth.py ; $(COPY) $? $@
+nnrpd_access.py: ../samples/nnrpd_access.py ; $(COPY) $? $@
+nnrpd_dynamic.py: ../samples/nnrpd_dynamic.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) $? $@
+INN.py: ../samples/INN.py ; $(COPY) $? $@
+nnrpd.py: ../samples/nnrpd.py ; $(COPY) $? $@
+incoming.conf: ../samples/incoming.conf ; $(COPY) $? $@
+inn.conf: ../samples/inn.conf ; $(COPY) $? $@
+innreport.conf: ../samples/innreport.conf ; $(COPY) $? $@
+storage.conf: ../samples/storage.conf ; $(COPY) $? $@
+cycbuff.conf: ../samples/cycbuff.conf ; $(COPY) $? $@
+buffindexed.conf: ../samples/buffindexed.conf ; $(COPY) $? $@
+ovdb.conf: ../samples/ovdb.conf ; $(COPY) $? $@
+innwatch.ctl: ../samples/innwatch.ctl ; $(COPY) $? $@
+innfeed.conf: ../samples/innfeed.conf ; $(COPY) $? $@
+moderators: ../samples/moderators ; $(COPY) $? $@
+distrib.pats: ../samples/distrib.pats ; $(COPY) $? $@
+motd.news: ../samples/motd.news ; $(COPY) $? $@
+news2mail.cf: ../samples/news2mail.cf ; $(COPY) $? $@
+newsfeeds: ../samples/newsfeeds ; $(COPY) $? $@
+nnrpd.track: ../samples/nnrpd.track ; $(COPY) $? $@
+nntpsend.ctl: ../samples/nntpsend.ctl ; $(COPY) $? $@
+overview.fmt: ../samples/overview.fmt ; $(COPY) $? $@
+parsecontrol: ../samples/parsecontrol ; $(COPY) $? $@
+passwd.nntp: ../samples/passwd.nntp ; $(COPY) $? $@
+readers.conf: ../samples/readers.conf ; $(COPY) $? $@
+radius.conf: ../samples/radius.conf ; $(COPY) $? $@
+startup.tcl: ../samples/startup.tcl ; $(COPY) $? $@
+startup_innd.pl: ../samples/startup_innd.pl ; $(COPY) $? $@
+subscriptions: ../samples/subscriptions ; $(COPY) $? $@
+sasl.conf: ../samples/sasl.conf ; $(COPY) $? $@
+active.minimal: ../samples/active.minimal ; $(COPY) $? $@
+newsgroups.minimal: ../samples/newsgroups.minimal ; $(COPY) $? $@
--- /dev/null
+#! /bin/sh
+## $Revision: 847 $
+##
+## Safely get a file from the samples directory. Usage:
+## getsafe <sample> <localfile>
+case $# in
+2)
+ ;;
+*)
+ echo "Can't get INN sample file: wrong number of arguments." 1>&2
+ exit 1
+ ;;
+esac
+
+SRC=$1
+DEST=$2
+
+## Try RCS.
+if [ -f RCS/${DEST},v ] ; then
+ echo "Note: ${SRC} has changed; please compare."
+ test -f ${DEST} && exit 0
+ exec co -q ${DEST}
+fi
+
+## Try SCCS.
+if [ -f SCCS/s.${DEST} ] ; then
+ echo "Note: ${SRC} has changed; please compare."
+ test -f ${DEST} && exit 0
+ exec sccs get -s ${DEST}
+fi
+
+## File exist locally?
+if [ -f ${DEST} ] ; then
+ cmp ${SRC} ${DEST}
+ if [ $? -eq 0 ] ; then
+ touch ${DEST}
+ exit 0
+ fi
+ echo "${SRC} has changed; please update ${DEST}"
+ exit 1
+fi
+
+echo Using sample version of ${DEST}
+cp ${SRC} ${DEST}
+
+exit 0
--- /dev/null
+# This file is automatically generated by buildconfig
+
+METHOD_SOURCES = buffindexed/buffindexed.c cnfs/cnfs.c ovdb/ovdb.c \
+ timecaf/caf.c timecaf/timecaf.c timehash/timehash.c \
+ tradindexed/tdx-cache.c tradindexed/tdx-data.c \
+ tradindexed/tdx-group.c tradindexed/tradindexed.c \
+ tradspool/tradspool.c trash/trash.c
+EXTRA_SOURCES = tradindexed/tdx-util.c
+PROGRAMS = tradindexed/tdx-util
+
+
+## Included from buffindexed/ovmethod.mk
+
+# This rule requires a compiler that supports -o with -c. Since it's normally
+# used by developers, that should be acceptable.
+buffindexed/buffindexed_d.o: buffindexed/buffindexed.c
+ $(CC) $(CFLAGS) -DBUFF_DEBUG -c -o $@ buffindexed/buffindexed.c
+
+buffindexed/debug: buffindexed/buffindexed_d.o libstorage.$(EXTLIB) $(LIBHIST)
+ $(LIBLD) $(LDFLAGS) -o $@ buffindexed/buffindexed_d.o \
+ $(LIBSTORAGE) $(LIBHIST) $(LIBINN) $(EXTSTORAGELIBS) $(LIBS)
+
+
+## Included from tradindexed/ovmethod.mk
+
+tradindexed/tdx-util.o: tradindexed/tdx-util.c
+ $(CC) $(CFLAGS) -c -o $@ tradindexed/tdx-util.c
+
+tradindexed/tdx-util: tradindexed/tdx-util.o libstorage.$(EXTLIB) $(LIBHIST)
+ $(LIBLD) $(LDFLAGS) -o $@ tradindexed/tdx-util.o \
+ $(LIBSTORAGE) $(LIBHIST) $(LIBINN) $(EXTSTORAGELIBS) $(LIBS)
--- /dev/null
+## $Id: Makefile 7727 2008-04-06 07:59:46Z iulius $
+
+include ../Makefile.global
+
+top = ..
+CFLAGS = $(GCFLAGS) -I. $(BERKELEY_DB_CFLAGS)
+
+SOURCES = expire.c interface.c methods.c ov.c overdata.c ovmethods.c \
+ $(METHOD_SOURCES)
+OBJECTS = $(SOURCES:.c=.o)
+LOBJECTS = $(OBJECTS:.o=.lo)
+
+.SUFFIXES: .lo
+
+all: library programs
+
+# Included here after the all target, since additional rules are defined in
+# Make.methods to be sure that we recurse properly to build the methods.
+include Make.methods
+
+warnings:
+ $(MAKE) COPT='$(WARNINGS)' all
+
+install: all
+ $(LI_XPUB) libstorage.$(EXTLIB) $D$(PATHLIB)/libstorage.$(EXTLIB)
+ for F in $(PROGRAMS) ; do \
+ $(LI_XPRI) $$F $D$(PATHBIN)/`basename $$F` ; \
+ done
+
+library: libstorage.$(EXTLIB)
+
+programs: $(PROGRAMS)
+
+clobber clean distclean:
+ rm -f *.o *.lo */*.o */*.lo libstorage.la libstorage.a
+ rm -f $(PROGRAMS) libstorage_pure_*.a .pure
+ rm -f buildconfig methods.c methods.h ovmethods.c ovmethods.h
+ rm -f profiled libstorage$(PROFSUFFIX).a
+ rm -rf .libs */.libs
+
+tags ctags: $(SOURCES)
+ $(CTAGS) $(SOURCES) ../include/*.h ../include/inn/*.h
+
+$(FIXSCRIPT):
+ @echo Run configure before running make. See INSTALL for details.
+ @exit 1
+
+libstorage.la: $(OBJECTS) $(LIBINN)
+ $(LIBLD) $(LDFLAGS) -o $@ $(LOBJECTS) \
+ $(LIBINN) $(EXTSTORAGELIBS) $(LIBS) \
+ -rpath $(PATHLIB) -version-info 2:0:0
+
+libstorage.a: $(OBJECTS)
+ ar r $@ $(OBJECTS)
+ $(RANLIB) libstorage.a
+
+# Make.methods is included in the distribution tarball since some non-GNU
+# makes can't deal with including a non-existent file, so don't depend on
+# it. The dependencies aren't entirely accurate; you really want to re-run
+# buildconfig each time a new subdirectory is added to the directory. But
+# adding a dependency on . is a bit too non-portable for my taste and causes
+# too many rebuilds.
+Make.methods methods.h ovmethods.c ovmethods.h methods.c: buildconfig
+ ./buildconfig
+
+buildconfig: buildconfig.in $(FIXSCRIPT)
+ $(FIXSCRIPT) -i buildconfig.in
+
+.c.o .c.lo:
+ $(LIBCC) $(CFLAGS) $(CCOUTPUT)
+
+ovtest: ov.c libstorage.$(EXTLIB) $(LIBINN)
+ $(CC) $(CFLAGS) -D_TEST_ -o ovtest ov.c \
+ libstorage.$(EXTLIB) $(LIBINN) $(EXTSTORAGELIBS) $(LIBS)
+
+$(LIBINN): ; (cd ../lib ; $(MAKE))
+$(LIBHIST): ; (cd ../history ; $(MAKE))
+
+
+## Profiling. The rules are a bit brute-force, but good enough.
+
+profiled: libstorage$(PROFSUFFIX).a
+ date >$@
+
+libstorage$(PROFSUFFIX).a: $(SOURCES)
+ rm -f $(OBJECTS)
+ $(MAKEPROFILING) libstorage.a
+ mv libstorage.a libstorage$(PROFSUFFIX).a
+ $(RANLIB) libstorage$(PROFSUFFIX).a
+ rm -f $(OBJECTS)
+
+
+## Dependencies. Default list, below, is probably good enough.
+
+depend: Makefile $(SOURCES) $(EXTRA_SOURCES)
+ $(MAKEDEPEND) '$(CFLAGS)' $(SOURCES) $(EXTRA_SOURCES)
+
+# DO NOT DELETE THIS LINE -- make depend depends on it.
+expire.o: expire.c ../include/config.h ../include/inn/defines.h \
+ ../include/inn/system.h ../include/clibrary.h ../include/config.h \
+ ../include/inn/innconf.h ../include/inn/defines.h ../include/libinn.h \
+ ../include/ov.h ../include/storage.h ../include/inn/history.h \
+ ovinterface.h ../include/storage.h ../include/inn/history.h \
+ ../include/paths.h
+interface.o: interface.c ../include/config.h ../include/inn/defines.h \
+ ../include/inn/system.h ../include/clibrary.h ../include/config.h \
+ ../include/conffile.h ../include/inn/innconf.h ../include/inn/defines.h \
+ ../include/inn/wire.h interface.h ../include/storage.h \
+ ../include/libinn.h methods.h ../include/paths.h
+methods.o: methods.c interface.h ../include/config.h \
+ ../include/inn/defines.h ../include/inn/system.h ../include/storage.h \
+ ../include/config.h methods.h cnfs/cnfs.h timecaf/timecaf.h \
+ ../include/config.h interface.h timehash/timehash.h ../include/config.h \
+ interface.h tradspool/tradspool.h ../include/config.h interface.h \
+ trash/trash.h ../include/config.h interface.h
+ov.o: ov.c ../include/config.h ../include/inn/defines.h \
+ ../include/inn/system.h ../include/clibrary.h ../include/config.h \
+ ../include/inn/innconf.h ../include/inn/defines.h ../include/libinn.h \
+ ../include/ov.h ../include/storage.h ../include/inn/history.h \
+ ovinterface.h ../include/storage.h ../include/inn/history.h ovmethods.h
+overdata.o: overdata.c ../include/config.h ../include/inn/defines.h \
+ ../include/inn/system.h ../include/clibrary.h ../include/config.h \
+ ../include/inn/buffer.h ../include/inn/defines.h \
+ ../include/inn/innconf.h ../include/inn/messages.h ../include/inn/qio.h \
+ ../include/inn/wire.h ../include/inn/vector.h ../include/libinn.h \
+ ovinterface.h ../include/ov.h ../include/storage.h \
+ ../include/inn/history.h ../include/storage.h ../include/inn/history.h \
+ ../include/paths.h
+ovmethods.o: ovmethods.c ovinterface.h ../include/config.h \
+ ../include/inn/defines.h ../include/inn/system.h ../include/ov.h \
+ ../include/storage.h ../include/config.h ../include/inn/history.h \
+ ../include/inn/defines.h ../include/storage.h ../include/inn/history.h \
+ buffindexed/buffindexed.h ovdb/ovdb.h tradindexed/tradindexed.h \
+ ../include/config.h ../include/ov.h ../include/storage.h
+buffindexed/buffindexed.o: buffindexed/buffindexed.c ../include/config.h \
+ ../include/inn/defines.h ../include/inn/system.h ../include/clibrary.h \
+ ../include/config.h ../include/portable/mmap.h ../include/config.h \
+ ../include/inn/innconf.h ../include/inn/defines.h ../include/libinn.h \
+ ../include/ov.h ../include/storage.h ../include/inn/history.h \
+ ../include/paths.h ovinterface.h ../include/config.h ../include/ov.h \
+ ../include/storage.h ../include/inn/history.h ../include/storage.h \
+ buffindexed/buffindexed.h
+cnfs/cnfs.o: cnfs/cnfs.c ../include/config.h ../include/inn/defines.h \
+ ../include/inn/system.h ../include/clibrary.h ../include/config.h \
+ ../include/portable/mmap.h ../include/config.h \
+ ../include/portable/time.h ../include/inn/innconf.h \
+ ../include/inn/defines.h interface.h ../include/config.h \
+ ../include/storage.h ../include/libinn.h methods.h interface.h \
+ ../include/paths.h ../include/inn/wire.h ../include/inn/mmap.h \
+ cnfs/cnfs.h cnfs/cnfs-private.h
+ovdb/ovdb.o: ovdb/ovdb.c ../include/config.h ../include/inn/defines.h \
+ ../include/inn/system.h ../include/clibrary.h ../include/config.h \
+ ../include/portable/socket.h ../include/config.h \
+ ../include/portable/time.h ../include/conffile.h \
+ ../include/inn/innconf.h ../include/inn/defines.h \
+ ../include/inn/messages.h ../include/libinn.h ../include/paths.h \
+ ../include/storage.h ../include/ov.h ../include/storage.h \
+ ../include/inn/history.h ovinterface.h ../include/config.h \
+ ../include/ov.h ../include/storage.h ../include/inn/history.h \
+ ovdb/ovdb.h ovdb/ovdb-private.h
+timecaf/caf.o: timecaf/caf.c ../include/config.h ../include/inn/defines.h \
+ ../include/inn/system.h ../include/clibrary.h ../include/config.h \
+ ../include/libinn.h timecaf/caf.h
+timecaf/timecaf.o: timecaf/timecaf.c ../include/config.h ../include/inn/defines.h \
+ ../include/inn/system.h ../include/clibrary.h ../include/config.h \
+ ../include/portable/mmap.h ../include/config.h timecaf/caf.h \
+ ../include/inn/innconf.h ../include/inn/defines.h ../include/inn/wire.h \
+ ../include/libinn.h methods.h interface.h ../include/config.h \
+ ../include/storage.h timecaf/timecaf.h interface.h ../include/paths.h
+timehash/timehash.o: timehash/timehash.c ../include/config.h \
+ ../include/inn/defines.h ../include/inn/system.h ../include/clibrary.h \
+ ../include/config.h ../include/portable/mmap.h ../include/config.h \
+ ../include/inn/innconf.h ../include/inn/defines.h ../include/inn/wire.h \
+ ../include/libinn.h methods.h interface.h ../include/config.h \
+ ../include/storage.h ../include/paths.h timehash/timehash.h interface.h
+tradindexed/tdx-cache.o: tradindexed/tdx-cache.c ../include/config.h \
+ ../include/inn/defines.h ../include/inn/system.h ../include/clibrary.h \
+ ../include/config.h ../include/inn/hashtab.h ../include/inn/defines.h \
+ ../include/inn/messages.h ../include/libinn.h ../include/storage.h \
+ tradindexed/tdx-private.h
+tradindexed/tdx-data.o: tradindexed/tdx-data.c ../include/config.h \
+ ../include/inn/defines.h ../include/inn/system.h ../include/clibrary.h \
+ ../include/config.h ../include/portable/mmap.h ../include/config.h \
+ ../include/inn/history.h ../include/inn/defines.h \
+ ../include/inn/innconf.h ../include/inn/messages.h \
+ ../include/inn/mmap.h ../include/libinn.h ../include/ov.h \
+ ../include/storage.h ../include/inn/history.h ovinterface.h \
+ ../include/config.h ../include/ov.h ../include/storage.h \
+ ../include/inn/history.h ../include/storage.h tradindexed/tdx-private.h \
+ tradindexed/tdx-structure.h
+tradindexed/tdx-group.o: tradindexed/tdx-group.c ../include/config.h \
+ ../include/inn/defines.h ../include/inn/system.h ../include/clibrary.h \
+ ../include/config.h ../include/portable/mmap.h ../include/config.h \
+ ../include/inn/hashtab.h ../include/inn/defines.h \
+ ../include/inn/innconf.h ../include/inn/messages.h \
+ ../include/inn/mmap.h ../include/inn/qio.h ../include/inn/vector.h \
+ ../include/libinn.h ../include/paths.h tradindexed/tdx-private.h \
+ ../include/storage.h tradindexed/tdx-structure.h
+tradindexed/tradindexed.o: tradindexed/tradindexed.c ../include/config.h \
+ ../include/inn/defines.h ../include/inn/system.h ../include/clibrary.h \
+ ../include/config.h ../include/inn/innconf.h ../include/inn/defines.h \
+ ../include/inn/messages.h ../include/libinn.h ../include/ov.h \
+ ../include/storage.h ../include/inn/history.h ../include/storage.h \
+ tradindexed/tdx-private.h tradindexed/tdx-structure.h \
+ tradindexed/tradindexed.h
+tradspool/tradspool.o: tradspool/tradspool.c ../include/config.h \
+ ../include/inn/defines.h ../include/inn/system.h ../include/clibrary.h \
+ ../include/config.h ../include/portable/mmap.h ../include/config.h \
+ ../include/inn/innconf.h ../include/inn/defines.h ../include/inn/qio.h \
+ ../include/inn/wire.h ../include/libinn.h ../include/paths.h methods.h \
+ interface.h ../include/config.h ../include/storage.h \
+ tradspool/tradspool.h interface.h
+trash/trash.o: trash/trash.c ../include/config.h ../include/inn/defines.h \
+ ../include/inn/system.h ../include/clibrary.h ../include/config.h \
+ ../include/libinn.h methods.h interface.h ../include/config.h \
+ ../include/storage.h trash/trash.h interface.h
+tradindexed/tdx-util.o: tradindexed/tdx-util.c ../include/config.h \
+ ../include/inn/defines.h ../include/inn/system.h ../include/clibrary.h \
+ ../include/config.h ../include/inn/buffer.h ../include/inn/defines.h \
+ ../include/inn/history.h ../include/inn/innconf.h \
+ ../include/inn/messages.h ../include/inn/vector.h ../include/libinn.h \
+ ../include/ov.h ../include/storage.h ../include/inn/history.h \
+ ovinterface.h ../include/config.h ../include/ov.h ../include/storage.h \
+ ../include/inn/history.h ../include/paths.h tradindexed/tdx-private.h \
+ ../include/storage.h tradindexed/tdx-structure.h
--- /dev/null
+/* $Id: buffindexed.c 7602 2007-02-10 22:19:49Z eagle $
+**
+** Overview buffer and index method.
+*/
+
+#include "config.h"
+#include "clibrary.h"
+#include "portable/mmap.h"
+#include <assert.h>
+#include <ctype.h>
+#include <errno.h>
+#include <fcntl.h>
+#if HAVE_LIMITS_H
+# include <limits.h>
+#endif
+#include <syslog.h>
+#include <sys/stat.h>
+#include <time.h>
+
+#include "inn/innconf.h"
+#include "libinn.h"
+#include "ov.h"
+#include "paths.h"
+#include "ovinterface.h"
+#include "storage.h"
+
+#include "buffindexed.h"
+
+#define OVBUFF_MAGIC "ovbuff"
+
+/* ovbuff header */
+#define OVBUFFMASIZ 8
+#define OVBUFFNASIZ 16
+#define OVBUFFLASIZ 16
+#define OVBUFFPASIZ 64
+
+#define OVMAXCYCBUFFNAME 8
+
+#define OV_HDR_PAGESIZE 16384
+#define OV_BLOCKSIZE 8192
+#define OV_BEFOREBITF (1 * OV_BLOCKSIZE)
+#define OV_FUDGE 1024
+
+/* ovblock pointer */
+typedef struct _OV {
+ unsigned int blocknum;
+ short index;
+} OV;
+
+/* ovbuff header */
+typedef struct {
+ char magic[OVBUFFMASIZ];
+ char path[OVBUFFPASIZ];
+ char indexa[OVBUFFLASIZ]; /* ASCII version of index */
+ char lena[OVBUFFLASIZ]; /* ASCII version of len */
+ char totala[OVBUFFLASIZ]; /* ASCII version of total */
+ char useda[OVBUFFLASIZ]; /* ASCII version of used */
+ char freea[OVBUFFLASIZ]; /* ASCII version of free */
+ char updateda[OVBUFFLASIZ]; /* ASCII version of updated */
+} OVBUFFHEAD;
+
+/* ovbuff info */
+typedef struct _OVBUFF {
+ unsigned int index; /* ovbuff index */
+ char path[OVBUFFPASIZ]; /* Path to file */
+ int magicver; /* Magic version number */
+ int fd; /* file descriptor for this
+ ovbuff */
+ off_t len; /* Length of writable area, in
+ bytes */
+ off_t base; /* Offset (relative to byte
+ 0 of file) to base block */
+ unsigned int freeblk; /* next free block number no
+ freeblk left if equals
+ totalblk */
+ unsigned int totalblk; /* number of total blocks */
+ unsigned int usedblk; /* number of used blocks */
+ time_t updated; /* Time of last update to
+ header */
+ void * bitfield; /* Bitfield for ovbuff block in
+ use */
+ bool needflush; /* true if OVBUFFHEAD is needed
+ to be flushed */
+ struct _OVBUFF *next; /* next ovbuff */
+ int nextchunk; /* next chunk */
+#ifdef OV_DEBUG
+ struct ov_trace_array *trace;
+#endif /* OV_DEBUG */
+} OVBUFF;
+
+typedef struct _OVINDEXHEAD {
+ OV next; /* next block */
+ ARTNUM low; /* lowest article number in the index */
+ ARTNUM high; /* highest article number in the index */
+} OVINDEXHEAD;
+
+typedef struct _OVINDEX {
+ ARTNUM artnum; /* article number */
+ unsigned int blocknum; /* overview data block number */
+ short index; /* overview data block index */
+ TOKEN token; /* token for this article */
+ off_t offset; /* offset from the top in the block */
+ int len; /* length of the data */
+ time_t arrived; /* arrived time of article */
+ time_t expires; /* expire time of article */
+} OVINDEX;
+
+#define OVINDEXMAX ((OV_BLOCKSIZE-sizeof(OVINDEXHEAD))/sizeof(OVINDEX))
+
+typedef struct _OVBLOCK {
+ OVINDEXHEAD ovindexhead; /* overview index header */
+ OVINDEX ovindex[OVINDEXMAX]; /* overview index */
+} OVBLOCK;
+
+typedef struct _OVBLKS {
+ OVBLOCK *ovblock;
+ void * addr;
+ int len;
+ OV indexov;
+} OVBLKS;
+
+/* Data structure for specifying a location in the group index */
+typedef struct {
+ int recno; /* Record number in group index */
+} GROUPLOC;
+
+#ifdef OV_DEBUG
+struct ov_trace {
+ time_t occupied;
+ time_t freed;
+ GROUPLOC gloc;
+};
+
+#define OV_TRACENUM 10
+struct ov_trace_array {
+ int max;
+ int cur;
+ struct ov_trace *ov_trace;
+};
+
+struct ov_name_table {
+ char *name;
+ int recno;
+ struct ov_name_table *next;
+};
+
+static struct ov_name_table *name_table = NULL;
+#endif /* OV_DEBUG */
+
+#define GROUPHEADERHASHSIZE (16 * 1024)
+#define GROUPHEADERMAGIC (~(0xf1f0f33d))
+
+typedef struct {
+ int magic;
+ GROUPLOC hash[GROUPHEADERHASHSIZE];
+ GROUPLOC freelist;
+} GROUPHEADER;
+
+/* The group is matched based on the MD5 of the grouname. This may prove to
+ be inadequate in the future, if so, the right thing to do is to is
+ probably just to add a SHA1 hash into here also. We get a really nice
+ benefit from this being fixed length, we should try to keep it that way.
+*/
+typedef struct {
+ HASH hash; /* MD5 hash of the group name */
+ HASH alias; /* If not empty then this is the hash of the
+ group that this group is an alias for */
+ ARTNUM high; /* High water mark in group */
+ ARTNUM low; /* Low water mark in group */
+ int count; /* Number of articles in group */
+ int flag; /* Posting/Moderation Status */
+ time_t expired; /* When last expiry */
+ time_t deleted; /* When this was deleted, 0 otherwise */
+ GROUPLOC next; /* Next block in this chain */
+ OV baseindex; /* base index buff */
+ OV curindex; /* current index buff */
+ int curindexoffset; /* current index offset for this ovbuff */
+ ARTNUM curhigh; /* High water mark in group */
+ ARTNUM curlow; /* Low water mark in group */
+ OV curdata; /* current offset for this ovbuff */
+ off_t curoffset; /* current offset for this ovbuff */
+} GROUPENTRY;
+
+typedef struct _GIBLIST {
+ OV ov;
+ struct _GIBLIST *next;
+} GIBLIST;
+
+typedef struct _GDB {
+ OV datablk;
+ void * addr;
+ void * data;
+ int len;
+ bool mmapped;
+ struct _GDB *next;
+} GROUPDATABLOCK;
+
+typedef struct {
+ char *group;
+ int lo;
+ int hi;
+ int cur;
+ bool needov;
+ GROUPLOC gloc;
+ int count;
+ GROUPDATABLOCK gdb; /* used for caching current block */
+} OVSEARCH;
+
+#define GROUPDATAHASHSIZE 25
+
+static GROUPDATABLOCK *groupdatablock[GROUPDATAHASHSIZE];
+
+typedef enum {PREPEND_BLK, APPEND_BLK} ADDINDEX;
+typedef enum {SRCH_FRWD, SRCH_BKWD} SRCH;
+
+#define _PATH_OVBUFFCONFIG "buffindexed.conf"
+
+static char LocalLogName[] = "buffindexed";
+static long pagesize = 0;
+static OVBUFF *ovbufftab;
+static int GROUPfd;
+static GROUPHEADER *GROUPheader = NULL;
+static GROUPENTRY *GROUPentries = NULL;
+static int GROUPcount = 0;
+static GROUPLOC GROUPemptyloc = { -1 };
+#define NULLINDEX (-1)
+static OV ovnull = { 0, NULLINDEX };
+typedef unsigned long ULONG;
+static ULONG onarray[64], offarray[64];
+static int longsize = sizeof(long);
+static bool Nospace;
+static bool Needunlink;
+static bool Cutofflow;
+static bool Cache;
+static OVSEARCH *Cachesearch;
+
+static int ovbuffmode;
+
+static GROUPLOC GROUPnewnode(void);
+static bool GROUPremapifneeded(GROUPLOC loc);
+static void GROUPLOCclear(GROUPLOC *loc);
+static bool GROUPLOCempty(GROUPLOC loc);
+static bool GROUPlockhash(enum inn_locktype type);
+static bool GROUPlock(GROUPLOC gloc, enum inn_locktype type);
+static off_t GROUPfilesize(int count);
+static bool GROUPexpand(int mode);
+static void *ovopensearch(char *group, int low, int high, bool needov);
+static void ovclosesearch(void *handle, bool freeblock);
+static OVINDEX *Gib;
+static GIBLIST *Giblist;
+static int Gibcount;
+
+#ifdef MMAP_MISSES_WRITES
+/* With HP/UX, you definitely do not want to mix mmap-accesses of
+ a file with read()s and write()s of the same file */
+static off_t mmapwrite(int fd, void *buf, off_t nbyte, off_t offset) {
+ int pagefudge, len;
+ off_t mmapoffset;
+ void * addr;
+
+ pagefudge = offset % pagesize;
+ mmapoffset = offset - pagefudge;
+ len = pagefudge + nbyte;
+
+ if ((addr = mmap(NULL, len, PROT_READ|PROT_WRITE, MAP_SHARED, fd, mmapoffset)) == MAP_FAILED) {
+ return -1;
+ }
+ memcpy(addr+pagefudge, buf, nbyte);
+ munmap(addr, len);
+ return nbyte;
+}
+#endif /* MMAP_MISSES_WRITES */
+
+static bool ovparse_part_line(char *l) {
+ char *p;
+ struct stat sb;
+ off_t len, base;
+ int tonextblock;
+ OVBUFF *ovbuff, *tmp = ovbufftab;
+
+ /* ovbuff partition name */
+ if ((p = strchr(l, ':')) == NULL || p - l <= 0 || p - l > OVMAXCYCBUFFNAME - 1) {
+ syslog(L_ERROR, "%s: bad index in line '%s'", LocalLogName, l);
+ return false;
+ }
+ *p = '\0';
+ ovbuff = xmalloc(sizeof(OVBUFF));
+ ovbuff->index = strtoul(l, NULL, 10);
+ for (; tmp != (OVBUFF *)NULL; tmp = tmp->next) {
+ if (tmp->index == ovbuff->index) {
+ syslog(L_ERROR, "%s: dupulicate index in line '%s'", LocalLogName, l);
+ free(ovbuff);
+ return false;
+ }
+ }
+ l = ++p;
+
+ /* Path to ovbuff partition */
+ if ((p = strchr(l, ':')) == NULL || p - l <= 0 || p - l > OVBUFFPASIZ - 1) {
+ syslog(L_ERROR, "%s: bad pathname in line '%s'", LocalLogName, l);
+ free(ovbuff);
+ return false;
+ }
+ *p = '\0';
+ memset(ovbuff->path, '\0', OVBUFFPASIZ);
+ strlcpy(ovbuff->path, l, OVBUFFPASIZ);
+ if (stat(ovbuff->path, &sb) < 0) {
+ syslog(L_ERROR, "%s: file '%s' does not exist, ignoring '%d'",
+ LocalLogName, ovbuff->path, ovbuff->index);
+ free(ovbuff);
+ return false;
+ }
+ l = ++p;
+
+ /* Length/size of symbolic partition in KB */
+ len = strtoul(l, NULL, 10) * (off_t) 1024;
+ /*
+ ** The minimum article offset will be the size of the bitfield itself,
+ ** len / (blocksize * 8), plus however many additional blocks the OVBUFFHEAD
+ ** external header occupies ... then round up to the next block.
+ */
+ base = len / (OV_BLOCKSIZE * 8) + OV_BEFOREBITF;
+ tonextblock = OV_HDR_PAGESIZE - (base & (OV_HDR_PAGESIZE - 1));
+ ovbuff->base = base + tonextblock;
+ if (S_ISREG(sb.st_mode) && (len != sb.st_size || ovbuff->base > sb.st_size)) {
+ if (len != sb.st_size)
+ syslog(L_NOTICE, "%s: length mismatch '%lu' for index '%d' (%lu bytes)",
+ LocalLogName, (unsigned long) len, ovbuff->index,
+ (unsigned long) sb.st_size);
+ if (ovbuff->base > sb.st_size)
+ syslog(L_NOTICE, "%s: length must be at least '%lu' for index '%d' (%lu bytes)",
+ LocalLogName, (unsigned long) ovbuff->base, ovbuff->index,
+ (unsigned long) sb.st_size);
+ free(ovbuff);
+ return false;
+ }
+ ovbuff->len = len;
+ ovbuff->fd = -1;
+ ovbuff->next = (OVBUFF *)NULL;
+ ovbuff->needflush = false;
+ ovbuff->bitfield = NULL;
+ ovbuff->nextchunk = 1;
+
+ if (ovbufftab == (OVBUFF *)NULL)
+ ovbufftab = ovbuff;
+ else {
+ for (tmp = ovbufftab; tmp->next != (OVBUFF *)NULL; tmp = tmp->next);
+ tmp->next = ovbuff;
+ }
+ return true;
+}
+
+/*
+** ovbuffread_config() -- Read the overview partition/file configuration file.
+*/
+
+static bool ovbuffread_config(void) {
+ char *path, *config, *from, *to, **ctab = (char **)NULL;
+ int ctab_free = 0; /* Index to next free slot in ctab */
+ int ctab_i;
+
+ path = concatpath(innconf->pathetc, _PATH_OVBUFFCONFIG);
+ config = ReadInFile(path, NULL);
+ if (config == NULL) {
+ syslog(L_ERROR, "%s: cannot read %s", LocalLogName, path);
+ free(config);
+ free(path);
+ return false;
+ }
+ free(path);
+ for (from = to = config; *from; ) {
+ if (*from == '#') { /* Comment line? */
+ while (*from && *from != '\n')
+ from++; /* Skip past it */
+ from++;
+ continue; /* Back to top of loop */
+ }
+ if (*from == '\n') { /* End or just a blank line? */
+ from++;
+ continue; /* Back to top of loop */
+ }
+ if (ctab_free == 0)
+ ctab = xmalloc(sizeof(char *));
+ else
+ ctab = xrealloc(ctab, (ctab_free + 1) * sizeof(char *));
+ /* If we're here, we've got the beginning of a real entry */
+ ctab[ctab_free++] = to = from;
+ while (1) {
+ if (*from && *from == '\\' && *(from + 1) == '\n') {
+ from += 2; /* Skip past backslash+newline */
+ while (*from && isspace((int)*from))
+ from++;
+ continue;
+ }
+ if (*from && *from != '\n')
+ *to++ = *from++;
+ if (*from == '\n') {
+ *to++ = '\0';
+ from++;
+ break;
+ }
+ if (! *from)
+ break;
+ }
+ }
+ for (ctab_i = 0; ctab_i < ctab_free; ctab_i++) {
+ if (!ovparse_part_line(ctab[ctab_i])) {
+ free(config);
+ free(ctab);
+ return false;
+ }
+ }
+ free(config);
+ free(ctab);
+ if (ovbufftab == (OVBUFF *)NULL) {
+ syslog(L_ERROR, "%s: no buffindexed defined", LocalLogName);
+ return false;
+ }
+ return true;
+}
+
+static char hextbl[] = {'0', '1', '2', '3', '4', '5', '6', '7', '8', '9',
+ 'a', 'b', 'c', 'd', 'e', 'f'};
+
+static char *offt2hex(off_t offset, bool leadingzeros) {
+ static char buf[24];
+ char *p;
+
+ if (sizeof(off_t) <= 4) {
+ snprintf(buf, sizeof(buf), (leadingzeros) ? "%016lx" : "%lx", offset);
+ } else {
+ int i;
+
+ for (i = 0; i < OVBUFFLASIZ; i++)
+ buf[i] = '0'; /* Pad with zeros to start */
+ for (i = OVBUFFLASIZ - 1; i >= 0; i--) {
+ buf[i] = hextbl[offset & 0xf];
+ offset >>= 4;
+ }
+ }
+ if (!leadingzeros) {
+ for (p = buf; *p == '0'; p++)
+ ;
+ if (*p != '\0')
+ return p;
+ else
+ return p - 1; /* We converted a "0" and then bypassed all the zeros */
+ } else
+ return buf;
+}
+
+static off_t hex2offt(char *hex) {
+ if (sizeof(off_t) <= 4) {
+ unsigned long rpofft;
+
+ sscanf(hex, "%lx", &rpofft);
+ return rpofft;
+ } else {
+ char diff;
+ off_t n = 0;
+
+ for (; *hex != '\0'; hex++) {
+ if (*hex >= '0' && *hex <= '9')
+ diff = '0';
+ else if (*hex >= 'a' && *hex <= 'f')
+ diff = 'a' - 10;
+ else if (*hex >= 'A' && *hex <= 'F')
+ diff = 'A' - 10;
+ else {
+ /*
+ ** We used to have a syslog() message here, but the case
+ ** where we land here because of a ":" happens, er, often.
+ */
+ break;
+ }
+ n += (*hex - diff);
+ if (isalnum((int)*(hex + 1)))
+ n <<= 4;
+ }
+ return n;
+ }
+}
+
+static void ovreadhead(OVBUFF *ovbuff) {
+ OVBUFFHEAD rpx;
+ char buff[OVBUFFLASIZ+1];
+
+ memcpy(&rpx, ovbuff->bitfield, sizeof(OVBUFFHEAD));
+ strncpy(buff, rpx.useda, OVBUFFLASIZ);
+ buff[OVBUFFLASIZ] = '\0';
+ ovbuff->usedblk = (unsigned int)hex2offt((char *)buff);
+ strncpy(buff, rpx.freea, OVBUFFLASIZ);
+ buff[OVBUFFLASIZ] = '\0';
+ ovbuff->freeblk = (unsigned int)hex2offt((char *)buff);
+ return;
+}
+
+static void ovflushhead(OVBUFF *ovbuff) {
+ OVBUFFHEAD rpx;
+
+ if (!ovbuff->needflush)
+ return;
+ memset(&rpx, 0, sizeof(OVBUFFHEAD));
+ ovbuff->updated = time(NULL);
+ strncpy(rpx.magic, OVBUFF_MAGIC, strlen(OVBUFF_MAGIC));
+ strncpy(rpx.path, ovbuff->path, OVBUFFPASIZ);
+ /* Don't use sprintf() directly ... the terminating '\0' causes grief */
+ strncpy(rpx.indexa, offt2hex(ovbuff->index, true), OVBUFFLASIZ);
+ strncpy(rpx.lena, offt2hex(ovbuff->len, true), OVBUFFLASIZ);
+ strncpy(rpx.totala, offt2hex(ovbuff->totalblk, true), OVBUFFLASIZ);
+ strncpy(rpx.useda, offt2hex(ovbuff->usedblk, true), OVBUFFLASIZ);
+ strncpy(rpx.freea, offt2hex(ovbuff->freeblk, true), OVBUFFLASIZ);
+ strncpy(rpx.updateda, offt2hex(ovbuff->updated, true), OVBUFFLASIZ);
+ memcpy(ovbuff->bitfield, &rpx, sizeof(OVBUFFHEAD));
+ mmap_flush(ovbuff->bitfield, ovbuff->base);
+ ovbuff->needflush = false;
+ return;
+}
+
+static bool ovlock(OVBUFF *ovbuff, enum inn_locktype type) {
+ return inn_lock_range(ovbuff->fd, type, true, 0, sizeof(OVBUFFHEAD));
+}
+
+static bool ovbuffinit_disks(void) {
+ OVBUFF *ovbuff = ovbufftab;
+ char buf[64];
+ OVBUFFHEAD *rpx;
+ int i, fd;
+ off_t tmpo;
+
+ /*
+ ** Discover the state of our ovbuffs. If any of them are in icky shape,
+ ** duck shamelessly & return false.
+ */
+ for (; ovbuff != (OVBUFF *)NULL; ovbuff = ovbuff->next) {
+ if (ovbuff->fd < 0) {
+ if ((fd = open(ovbuff->path, ovbuffmode & OV_WRITE ? O_RDWR : O_RDONLY)) < 0) {
+ syslog(L_ERROR, "%s: ERROR opening '%s' : %m", LocalLogName, ovbuff->path);
+ return false;
+ } else {
+ close_on_exec(fd, true);
+ ovbuff->fd = fd;
+ }
+ }
+ if ((ovbuff->bitfield =
+ mmap(NULL, ovbuff->base, ovbuffmode & OV_WRITE ? (PROT_READ | PROT_WRITE) : PROT_READ,
+ MAP_SHARED, ovbuff->fd, (off_t) 0)) == MAP_FAILED) {
+ syslog(L_ERROR,
+ "%s: ovinitdisks: mmap for %s offset %d len %lu failed: %m",
+ LocalLogName, ovbuff->path, 0, (unsigned long) ovbuff->base);
+ return false;
+ }
+ rpx = (OVBUFFHEAD *)ovbuff->bitfield;
+ ovlock(ovbuff, INN_LOCK_WRITE);
+ if (strncmp(rpx->magic, OVBUFF_MAGIC, strlen(OVBUFF_MAGIC)) == 0) {
+ ovbuff->magicver = 1;
+ if (strncmp(rpx->path, ovbuff->path, OVBUFFPASIZ) != 0) {
+ syslog(L_ERROR, "%s: Path mismatch: read %s for buffindexed %s",
+ LocalLogName, rpx->path, ovbuff->path);
+ ovbuff->needflush = true;
+ }
+ strncpy(buf, rpx->indexa, OVBUFFLASIZ);
+ buf[OVBUFFLASIZ] = '\0';
+ i = hex2offt(buf);
+ if (i != ovbuff->index) {
+ syslog(L_ERROR, "%s: Mismatch: index '%d' for buffindexed %s",
+ LocalLogName, i, ovbuff->path);
+ ovlock(ovbuff, INN_LOCK_UNLOCK);
+ return false;
+ }
+ strncpy(buf, rpx->lena, OVBUFFLASIZ);
+ buf[OVBUFFLASIZ] = '\0';
+ tmpo = hex2offt(buf);
+ if (tmpo != ovbuff->len) {
+ syslog(L_ERROR, "%s: Mismatch: read 0x%s length for buffindexed %s",
+ LocalLogName, offt2hex(tmpo, false), ovbuff->path);
+ ovlock(ovbuff, INN_LOCK_UNLOCK);
+ return false;
+ }
+ strncpy(buf, rpx->totala, OVBUFFLASIZ);
+ buf[OVBUFFLASIZ] = '\0';
+ ovbuff->totalblk = hex2offt(buf);
+ strncpy(buf, rpx->useda, OVBUFFLASIZ);
+ buf[OVBUFFLASIZ] = '\0';
+ ovbuff->usedblk = hex2offt(buf);
+ strncpy(buf, rpx->freea, OVBUFFLASIZ);
+ buf[OVBUFFLASIZ] = '\0';
+ ovbuff->freeblk = hex2offt(buf);
+ ovflushhead(ovbuff);
+ Needunlink = false;
+ } else {
+ ovbuff->totalblk = (ovbuff->len - ovbuff->base)/OV_BLOCKSIZE;
+ if (ovbuff->totalblk < 1) {
+ syslog(L_ERROR, "%s: too small length '%lu' for buffindexed %s",
+ LocalLogName, (unsigned long) ovbuff->len, ovbuff->path);
+ ovlock(ovbuff, INN_LOCK_UNLOCK);
+ return false;
+ }
+ ovbuff->magicver = 1;
+ ovbuff->usedblk = 0;
+ ovbuff->freeblk = 0;
+ ovbuff->updated = 0;
+ ovbuff->needflush = true;
+ syslog(L_NOTICE,
+ "%s: No magic cookie found for buffindexed %d, initializing",
+ LocalLogName, ovbuff->index);
+ ovflushhead(ovbuff);
+ }
+#ifdef OV_DEBUG
+ ovbuff->trace = xcalloc(ovbuff->totalblk, sizeof(ov_trace_array));
+#endif /* OV_DEBUG */
+ ovlock(ovbuff, INN_LOCK_UNLOCK);
+ }
+ return true;
+}
+
+static int ovusedblock(OVBUFF *ovbuff, int blocknum, bool set_operation, bool setbitvalue) {
+ off_t longoffset;
+ int bitoffset; /* From the 'left' side of the long */
+ ULONG bitlong, mask;
+
+ longoffset = blocknum / (sizeof(long) * 8);
+ bitoffset = blocknum % (sizeof(long) * 8);
+ bitlong = *((ULONG *) ovbuff->bitfield + (OV_BEFOREBITF / sizeof(long))
+ + longoffset);
+ if (set_operation) {
+ if (setbitvalue) {
+ mask = onarray[bitoffset];
+ bitlong |= mask;
+ } else {
+ mask = offarray[bitoffset];
+ bitlong &= mask;
+ }
+ *((ULONG *) ovbuff->bitfield + (OV_BEFOREBITF / sizeof(long))
+ + longoffset) = bitlong;
+ return 2; /* XXX Clean up return semantics */
+ }
+ /* It's a read operation */
+ mask = onarray[bitoffset];
+ /* return bitlong & mask; doesn't work if sizeof(ulong) > sizeof(int) */
+ if ( bitlong & mask ) return 1; else return 0;
+}
+
+static void ovnextblock(OVBUFF *ovbuff) {
+ int i, j, last, lastbit, left;
+ ULONG mask = 0x80000000;
+ ULONG *table;
+
+ last = ovbuff->totalblk/(sizeof(long) * 8);
+ if ((left = ovbuff->totalblk % (sizeof(long) * 8)) != 0) {
+ last++;
+ }
+ table = ((ULONG *) ovbuff->bitfield + (OV_BEFOREBITF / sizeof(long)));
+ for (i = ovbuff->nextchunk ; i < last ; i++) {
+ if (i == last - 1 && left != 0) {
+ for (j = 1 ; j < left ; j++) {
+ mask |= mask >> 1;
+ }
+ if ((table[i] & mask) != mask)
+ break;
+ } else {
+ if ((table[i] ^ ~0) != 0)
+ break;
+ }
+ }
+ if (i == last) {
+ for (i = 0 ; i < ovbuff->nextchunk ; i++) {
+ if ((table[i] ^ ~0) != 0)
+ break;
+ }
+ if (i == ovbuff->nextchunk) {
+ ovbuff->freeblk = ovbuff->totalblk;
+ return;
+ }
+ }
+ if ((i - 1) >= 0 && (last - 1 == i) && left != 0) {
+ lastbit = left;
+ } else {
+ lastbit = sizeof(long) * 8;
+ }
+ for (j = 0 ; j < lastbit ; j++) {
+ if ((table[i] & onarray[j]) == 0)
+ break;
+ }
+ if (j == lastbit) {
+ ovbuff->freeblk = ovbuff->totalblk;
+ return;
+ }
+ ovbuff->freeblk = i * sizeof(long) * 8 + j;
+ ovbuff->nextchunk = i + 1;
+ if (i == last)
+ ovbuff->nextchunk = 0;
+ return;
+}
+
+static OVBUFF *getovbuff(OV ov) {
+ OVBUFF *ovbuff = ovbufftab;
+ for (; ovbuff != (OVBUFF *)NULL; ovbuff = ovbuff->next) {
+ if (ovbuff->index == ov.index)
+ return ovbuff;
+ }
+ return NULL;
+}
+
+#ifdef OV_DEBUG
+static OV ovblocknew(GROUPENTRY *ge) {
+#else
+static OV ovblocknew(void) {
+#endif /* OV_DEBUG */
+ static OVBUFF *ovbuffnext = NULL;
+ OVBUFF *ovbuff;
+ OV ov;
+#ifdef OV_DEBUG
+ int recno;
+ struct ov_trace_array *trace;
+#endif /* OV_DEBUG */
+
+ if (ovbuffnext == NULL)
+ ovbuffnext = ovbufftab;
+ for (ovbuff = ovbuffnext ; ovbuff != (OVBUFF *)NULL ; ovbuff = ovbuff->next) {
+ ovlock(ovbuff, INN_LOCK_WRITE);
+ ovreadhead(ovbuff);
+ if (ovbuff->totalblk != ovbuff->usedblk && ovbuff->freeblk == ovbuff->totalblk) {
+ ovnextblock(ovbuff);
+ }
+ if (ovbuff->totalblk == ovbuff->usedblk || ovbuff->freeblk == ovbuff->totalblk) {
+ /* no space left for this ovbuff */
+ ovlock(ovbuff, INN_LOCK_UNLOCK);
+ continue;
+ }
+ break;
+ }
+ if (ovbuff == NULL) {
+ for (ovbuff = ovbufftab ; ovbuff != ovbuffnext ; ovbuff = ovbuff->next) {
+ ovlock(ovbuff, INN_LOCK_WRITE);
+ ovreadhead(ovbuff);
+ if (ovbuff->totalblk == ovbuff->usedblk || ovbuff->freeblk == ovbuff->totalblk) {
+ /* no space left for this ovbuff */
+ ovlock(ovbuff, INN_LOCK_UNLOCK);
+ continue;
+ }
+ break;
+ }
+ if (ovbuff == ovbuffnext) {
+ Nospace = true;
+ return ovnull;
+ }
+ }
+#ifdef OV_DEBUG
+ recno = ((char *)ge - (char *)&GROUPentries[0])/sizeof(GROUPENTRY);
+ if (ovusedblock(ovbuff, ovbuff->freeblk, false, true)) {
+ syslog(L_FATAL, "%s: 0x%08x trying to occupy new block(%d, %d), but already occupied", LocalLogName, recno, ovbuff->index, ovbuff->freeblk);
+ buffindexed_close();
+ abort();
+ }
+ trace = &ovbuff->trace[ovbuff->freeblk];
+ if (trace->ov_trace == NULL) {
+ trace->ov_trace = xcalloc(OV_TRACENUM, sizeof(struct ov_trace));
+ trace->max = OV_TRACENUM;
+ } else if (trace->cur + 1 == trace->max) {
+ trace->max += OV_TRACENUM;
+ trace->ov_trace = xrealloc(trace->ov_trace, trace->max * sizeof(struct ov_trace));
+ memset(&trace->ov_trace[trace->cur], '\0', sizeof(struct ov_trace) * (trace->max - trace->cur));
+ }
+ if (trace->ov_trace[trace->cur].occupied != 0) {
+ trace->cur++;
+ }
+ trace->ov_trace[trace->cur].gloc.recno = recno;
+ trace->ov_trace[trace->cur].occupied = time(NULL);
+#endif /* OV_DEBUG */
+ ov.index = ovbuff->index;
+ ov.blocknum = ovbuff->freeblk;
+ ovusedblock(ovbuff, ov.blocknum, true, true);
+ ovnextblock(ovbuff);
+ ovbuff->usedblk++;
+ ovbuff->needflush = true;
+ ovflushhead(ovbuff);
+ ovlock(ovbuff, INN_LOCK_UNLOCK);
+ ovbuffnext = ovbuff->next;
+ if (ovbuffnext == NULL)
+ ovbuffnext = ovbufftab;
+ return ov;
+}
+
+#ifdef OV_DEBUG
+static void ovblockfree(OV ov, GROUPENTRY *ge) {
+#else
+static void ovblockfree(OV ov) {
+#endif /* OV_DEBUG */
+ OVBUFF *ovbuff;
+#ifdef OV_DEBUG
+ int recno;
+ struct ov_trace_array *trace;
+#endif /* OV_DEBUG */
+
+ if (ov.index == NULLINDEX)
+ return;
+ if ((ovbuff = getovbuff(ov)) == NULL)
+ return;
+ ovlock(ovbuff, INN_LOCK_WRITE);
+#ifdef OV_DEBUG
+ recno = ((char *)ge - (char *)&GROUPentries[0])/sizeof(GROUPENTRY);
+ if (!ovusedblock(ovbuff, ov.blocknum, false, false)) {
+ syslog(L_FATAL, "%s: 0x%08x trying to free block(%d, %d), but already freed", LocalLogName, recno, ov.index, ov.blocknum);
+ buffindexed_close();
+ abort();
+ }
+ trace = &ovbuff->trace[ov.blocknum];
+ if (trace->ov_trace == NULL) {
+ trace->ov_trace = xcalloc(OV_TRACENUM, sizeof(struct ov_trace));
+ trace->max = OV_TRACENUM;
+ } else if (trace->cur + 1 == trace->max) {
+ trace->max += OV_TRACENUM;
+ trace->ov_trace = xrealloc(trace->ov_trace, trace->max * sizeof(struct ov_trace));
+ memset(&trace->ov_trace[trace->cur], '\0', sizeof(struct ov_trace) * (trace->max - trace->cur));
+ }
+ if (trace->ov_trace[trace->cur].freed != 0) {
+ trace->cur++;
+ }
+ trace->ov_trace[trace->cur].freed = time(NULL);
+ trace->ov_trace[trace->cur].gloc.recno = recno;
+ trace->cur++;
+#endif /* OV_DEBUG */
+ ovusedblock(ovbuff, ov.blocknum, true, false);
+ ovreadhead(ovbuff);
+ if (ovbuff->freeblk == ovbuff->totalblk)
+ ovbuff->freeblk = ov.blocknum;
+ ovbuff->usedblk--;
+ ovbuff->needflush = true;
+ ovflushhead(ovbuff);
+ ovlock(ovbuff, INN_LOCK_UNLOCK);
+ return;
+}
+
+bool buffindexed_open(int mode) {
+ char *groupfn;
+ struct stat sb;
+ int i, flag = 0;
+ static int uninitialized = 1;
+ ULONG on, off;
+
+ if (uninitialized) {
+ on = 1;
+ off = on;
+ off ^= ULONG_MAX;
+ for (i = (longsize * 8) - 1; i >= 0; i--) {
+ onarray[i] = on;
+ offarray[i] = off;
+ on <<= 1;
+ off = on;
+ off ^= ULONG_MAX;
+ }
+ uninitialized = 0;
+ }
+ ovbuffmode = mode;
+ if (pagesize == 0) {
+ pagesize = getpagesize();
+ if (pagesize == -1) {
+ syslog(L_ERROR, "%s: getpagesize failed: %m", LocalLogName);
+ pagesize = 0;
+ return false;
+ }
+ if ((pagesize > OV_HDR_PAGESIZE) || (OV_HDR_PAGESIZE % pagesize)) {
+ syslog(L_ERROR, "%s: OV_HDR_PAGESIZE (%d) is not a multiple of pagesize (%ld)", LocalLogName, OV_HDR_PAGESIZE, pagesize);
+ return false;
+ }
+ }
+ memset(&groupdatablock, '\0', sizeof(groupdatablock));
+ if (!ovbuffread_config()) {
+ return false;
+ }
+ Needunlink = true;
+ if (!ovbuffinit_disks()) {
+ return false;
+ }
+
+ groupfn = concatpath(innconf->pathdb, "group.index");
+ if (Needunlink && unlink(groupfn) == 0) {
+ syslog(L_NOTICE, "%s: all buffers are brandnew, unlink '%s'", LocalLogName, groupfn);
+ }
+ GROUPfd = open(groupfn, ovbuffmode & OV_WRITE ? O_RDWR | O_CREAT : O_RDONLY, 0660);
+ if (GROUPfd < 0) {
+ syslog(L_FATAL, "%s: Could not create %s: %m", LocalLogName, groupfn);
+ free(groupfn);
+ return false;
+ }
+
+ if (fstat(GROUPfd, &sb) < 0) {
+ syslog(L_FATAL, "%s: Could not fstat %s: %m", LocalLogName, groupfn);
+ free(groupfn);
+ close(GROUPfd);
+ return false;
+ }
+ if (sb.st_size > sizeof(GROUPHEADER)) {
+ if (mode & OV_READ)
+ flag |= PROT_READ;
+ if (mode & OV_WRITE) {
+ /*
+ * Note: below mapping of groupheader won't work unless we have
+ * both PROT_READ and PROT_WRITE perms.
+ */
+ flag |= PROT_WRITE|PROT_READ;
+ }
+ GROUPcount = (sb.st_size - sizeof(GROUPHEADER)) / sizeof(GROUPENTRY);
+ if ((GROUPheader = (GROUPHEADER *)mmap(0, GROUPfilesize(GROUPcount), flag,
+ MAP_SHARED, GROUPfd, 0)) == (GROUPHEADER *) -1) {
+ syslog(L_FATAL, "%s: Could not mmap %s in buffindexed_open: %m", LocalLogName, groupfn);
+ free(groupfn);
+ close(GROUPfd);
+ return false;
+ }
+ GROUPentries = (GROUPENTRY *)((char *)GROUPheader + sizeof(GROUPHEADER));
+ } else {
+ GROUPcount = 0;
+ if (!GROUPexpand(mode)) {
+ free(groupfn);
+ close(GROUPfd);
+ return false;
+ }
+ }
+ close_on_exec(GROUPfd, true);
+
+ free(groupfn);
+ Cutofflow = false;
+
+ return true;
+}
+
+static GROUPLOC GROUPfind(char *group, bool Ignoredeleted) {
+ HASH grouphash;
+ unsigned int i;
+ GROUPLOC loc;
+
+ grouphash = Hash(group, strlen(group));
+ memcpy(&i, &grouphash, sizeof(i));
+
+ loc = GROUPheader->hash[i % GROUPHEADERHASHSIZE];
+ GROUPremapifneeded(loc);
+
+ while (!GROUPLOCempty(loc)) {
+ if (GROUPentries[loc.recno].deleted == 0 || Ignoredeleted) {
+ if (memcmp(&grouphash, &GROUPentries[loc.recno].hash, sizeof(HASH)) == 0) {
+ return loc;
+ }
+ }
+ loc = GROUPentries[loc.recno].next;
+ }
+ return GROUPemptyloc;
+}
+
+bool buffindexed_groupstats(char *group, int *lo, int *hi, int *count, int *flag) {
+ GROUPLOC gloc;
+
+ gloc = GROUPfind(group, false);
+ if (GROUPLOCempty(gloc)) {
+ return false;
+ }
+ GROUPlock(gloc, INN_LOCK_READ);
+ if (lo != NULL)
+ *lo = GROUPentries[gloc.recno].low;
+ if (hi != NULL)
+ *hi = GROUPentries[gloc.recno].high;
+ if (count != NULL)
+ *count = GROUPentries[gloc.recno].count;
+ if (flag != NULL)
+ *flag = GROUPentries[gloc.recno].flag;
+ GROUPlock(gloc, INN_LOCK_UNLOCK);
+ return true;
+}
+
+static void setinitialge(GROUPENTRY *ge, HASH grouphash, char *flag, GROUPLOC next, ARTNUM lo, ARTNUM hi) {
+ ge->hash = grouphash;
+ if (lo != 0)
+ ge->low = lo;
+ ge->high = hi;
+ ge->expired = ge->deleted = ge->count = 0;
+ ge->flag = *flag;
+ ge->baseindex = ge->curindex = ge->curdata = ovnull;
+ ge->curindexoffset = ge->curoffset = 0;
+ ge->next = next;
+}
+
+bool buffindexed_groupadd(char *group, ARTNUM lo, ARTNUM hi, char *flag) {
+ unsigned int i;
+ HASH grouphash;
+ GROUPLOC gloc;
+ GROUPENTRY *ge;
+#ifdef OV_DEBUG
+ struct ov_name_table *ntp;
+#endif /* OV_DEBUG */
+
+ gloc = GROUPfind(group, true);
+ if (!GROUPLOCempty(gloc)) {
+ ge = &GROUPentries[gloc.recno];
+ if (GROUPentries[gloc.recno].deleted != 0) {
+ grouphash = Hash(group, strlen(group));
+ setinitialge(ge, grouphash, flag, ge->next, lo, hi);
+ } else {
+ ge->flag = *flag;
+ }
+ return true;
+ }
+ grouphash = Hash(group, strlen(group));
+ memcpy(&i, &grouphash, sizeof(i));
+ i = i % GROUPHEADERHASHSIZE;
+ GROUPlockhash(INN_LOCK_WRITE);
+ gloc = GROUPnewnode();
+ ge = &GROUPentries[gloc.recno];
+ setinitialge(ge, grouphash, flag, GROUPheader->hash[i], lo, hi);
+ GROUPheader->hash[i] = gloc;
+#ifdef OV_DEBUG
+ ntp = xmalloc(sizeof(struct ov_name_table));
+ memset(ntp, '\0', sizeof(struct ov_name_table));
+ ntp->name = xstrdup(group);
+ ntp->recno = gloc.recno;
+ if (name_table == NULL)
+ name_table = ntp;
+ else {
+ ntp->next = name_table;
+ name_table = ntp;
+ }
+#endif /* OV_DEBUG */
+ GROUPlockhash(INN_LOCK_UNLOCK);
+ return true;
+}
+
+static off_t GROUPfilesize(int count) {
+ return ((off_t) count * sizeof(GROUPENTRY)) + sizeof(GROUPHEADER);
+}
+
+/* Check if the given GROUPLOC refers to GROUPENTRY that we don't have mmap'ed,
+** if so then see if the file has been grown by another writer and remmap
+*/
+static bool GROUPremapifneeded(GROUPLOC loc) {
+ struct stat sb;
+
+ if (loc.recno < GROUPcount)
+ return true;
+
+ if (fstat(GROUPfd, &sb) < 0)
+ return false;
+
+ if (GROUPfilesize(GROUPcount) >= sb.st_size)
+ return true;
+
+ if (GROUPheader) {
+ if (munmap((void *)GROUPheader, GROUPfilesize(GROUPcount)) < 0) {
+ syslog(L_FATAL, "%s: Could not munmap group.index in GROUPremapifneeded: %m", LocalLogName);
+ return false;
+ }
+ }
+
+ GROUPcount = (sb.st_size - sizeof(GROUPHEADER)) / sizeof(GROUPENTRY);
+ GROUPheader = (GROUPHEADER *)mmap(0, GROUPfilesize(GROUPcount),
+ PROT_READ | PROT_WRITE, MAP_SHARED, GROUPfd, 0);
+ if (GROUPheader == (GROUPHEADER *) -1) {
+ syslog(L_FATAL, "%s: Could not mmap group.index in GROUPremapifneeded: %m", LocalLogName);
+ return false;
+ }
+ GROUPentries = (GROUPENTRY *)((char *)GROUPheader + sizeof(GROUPHEADER));
+ return true;
+}
+
+/* This function does not need to lock because it's callers are expected to do so */
+static bool GROUPexpand(int mode) {
+ int i;
+ int flag = 0;
+
+ if (GROUPheader) {
+ if (munmap((void *)GROUPheader, GROUPfilesize(GROUPcount)) < 0) {
+ syslog(L_FATAL, "%s: Could not munmap group.index in GROUPexpand: %m", LocalLogName);
+ return false;
+ }
+ }
+ GROUPcount += 1024;
+ if (ftruncate(GROUPfd, GROUPfilesize(GROUPcount)) < 0) {
+ syslog(L_FATAL, "%s: Could not extend group.index: %m", LocalLogName);
+ return false;
+ }
+ if (mode & OV_READ)
+ flag |= PROT_READ;
+ if (mode & OV_WRITE) {
+ /*
+ * Note: below check of magic won't work unless we have both PROT_READ
+ * and PROT_WRITE perms.
+ */
+ flag |= PROT_WRITE|PROT_READ;
+ }
+ GROUPheader = (GROUPHEADER *)mmap(0, GROUPfilesize(GROUPcount),
+ flag, MAP_SHARED, GROUPfd, 0);
+ if (GROUPheader == (GROUPHEADER *) -1) {
+ syslog(L_FATAL, "%s: Could not mmap group.index in GROUPexpand: %m", LocalLogName);
+ return false;
+ }
+ GROUPentries = (GROUPENTRY *)((char *)GROUPheader + sizeof(GROUPHEADER));
+ if (GROUPheader->magic != GROUPHEADERMAGIC) {
+ GROUPheader->magic = GROUPHEADERMAGIC;
+ GROUPLOCclear(&GROUPheader->freelist);
+ for (i = 0; i < GROUPHEADERHASHSIZE; i++)
+ GROUPLOCclear(&GROUPheader->hash[i]);
+ }
+ /* Walk the new entries from the back to the front, adding them to the freelist */
+ for (i = GROUPcount - 1; (GROUPcount - 1024) <= i; i--) {
+ GROUPentries[i].next = GROUPheader->freelist;
+ GROUPheader->freelist.recno = i;
+ }
+ return true;
+}
+
+static GROUPLOC GROUPnewnode(void) {
+ GROUPLOC loc;
+
+ /* If we didn't find any free space, then make some */
+ if (GROUPLOCempty(GROUPheader->freelist)) {
+ if (!GROUPexpand(ovbuffmode)) {
+ return GROUPemptyloc;
+ }
+ }
+ assert(!GROUPLOCempty(GROUPheader->freelist));
+ loc = GROUPheader->freelist;
+ GROUPheader->freelist = GROUPentries[GROUPheader->freelist.recno].next;
+ return loc;
+}
+
+bool buffindexed_groupdel(char *group) {
+ GROUPLOC gloc;
+ GROUPENTRY *ge;
+
+ gloc = GROUPfind(group, false);
+ if (GROUPLOCempty(gloc)) {
+ return true;
+ }
+ GROUPlock(gloc, INN_LOCK_WRITE);
+ ge = &GROUPentries[gloc.recno];
+ ge->deleted = time(NULL);
+ HashClear(&ge->hash);
+ GROUPlock(gloc, INN_LOCK_UNLOCK);
+ return true;
+}
+
+static void GROUPLOCclear(GROUPLOC *loc) {
+ loc->recno = -1;
+}
+
+static bool GROUPLOCempty(GROUPLOC loc) {
+ return (loc.recno < 0);
+}
+
+static bool GROUPlockhash(enum inn_locktype type) {
+ return inn_lock_range(GROUPfd, type, true, 0, sizeof(GROUPHEADER));
+}
+
+static bool GROUPlock(GROUPLOC gloc, enum inn_locktype type) {
+ return inn_lock_range(GROUPfd,
+ type,
+ true,
+ sizeof(GROUPHEADER) + (sizeof(GROUPENTRY) * gloc.recno),
+ sizeof(GROUPENTRY));
+}
+
+#ifdef OV_DEBUG
+static bool ovsetcurindexblock(GROUPENTRY *ge, GROUPENTRY *georig) {
+#else
+static bool ovsetcurindexblock(GROUPENTRY *ge) {
+#endif /* OV_DEBUG */
+ OVBUFF *ovbuff;
+ OV ov;
+ OVINDEXHEAD ovindexhead;
+
+ /* there is no index */
+#ifdef OV_DEBUG
+ ov = ovblocknew(georig ? georig : ge);
+#else
+ ov = ovblocknew();
+#endif /* OV_DEBUG */
+ if (ov.index == NULLINDEX) {
+ syslog(L_ERROR, "%s: ovsetcurindexblock could not get new block", LocalLogName);
+ return false;
+ }
+ if ((ovbuff = getovbuff(ov)) == NULL) {
+ syslog(L_ERROR, "%s: ovsetcurindexblock could not get ovbuff block for new, %d, %d", LocalLogName, ov.index, ov.blocknum);
+ return false;
+ }
+ ovindexhead.next = ovnull;
+ ovindexhead.low = 0;
+ ovindexhead.high = 0;
+#ifdef MMAP_MISSES_WRITES
+ if (mmapwrite(ovbuff->fd, &ovindexhead, sizeof(OVINDEXHEAD), ovbuff->base + ov.blocknum * OV_BLOCKSIZE) != sizeof(OVINDEXHEAD)) {
+#else
+ if (pwrite(ovbuff->fd, &ovindexhead, sizeof(OVINDEXHEAD), ovbuff->base + ov.blocknum * OV_BLOCKSIZE) != sizeof(OVINDEXHEAD)) {
+#endif /* MMAP_MISSES_WRITES */
+ syslog(L_ERROR, "%s: could not write index record index '%d', blocknum '%d': %m", LocalLogName, ge->curindex.index, ge->curindex.blocknum);
+ return true;
+ }
+ if (ge->baseindex.index == NULLINDEX) {
+ ge->baseindex = ov;
+ } else {
+ if ((ovbuff = getovbuff(ge->curindex)) == NULL)
+ return false;
+#ifdef OV_DEBUG
+ if (!ovusedblock(ovbuff, ge->curindex.blocknum, false, false)) {
+ syslog(L_FATAL, "%s: block(%d, %d) not occupied (index)", LocalLogName, ovbuff->index, ge->curindex.blocknum);
+ abort();
+ }
+#endif /* OV_DEBUG */
+ ovindexhead.next = ov;
+ ovindexhead.low = ge->curlow;
+ ovindexhead.high = ge->curhigh;
+#ifdef MMAP_MISSES_WRITES
+ if (mmapwrite(ovbuff->fd, &ovindexhead, sizeof(OVINDEXHEAD), ovbuff->base + ge->curindex.blocknum * OV_BLOCKSIZE) != sizeof(OVINDEXHEAD)) {
+#else
+ if (pwrite(ovbuff->fd, &ovindexhead, sizeof(OVINDEXHEAD), ovbuff->base + ge->curindex.blocknum * OV_BLOCKSIZE) != sizeof(OVINDEXHEAD)) {
+#endif /* MMAP_MISSES_WRITES */
+ syslog(L_ERROR, "%s: could not write index record index '%d', blocknum '%d': %m", LocalLogName, ge->curindex.index, ge->curindex.blocknum);
+ return false;
+ }
+ }
+ ge->curindex = ov;
+ ge->curindexoffset = 0;
+ ge->curlow = 0;
+ ge->curhigh = 0;
+ return true;
+}
+
+#ifdef OV_DEBUG
+static bool ovaddrec(GROUPENTRY *ge, ARTNUM artnum, TOKEN token, char *data, int len, time_t arrived, time_t expires, GROUPENTRY *georig) {
+#else
+static bool ovaddrec(GROUPENTRY *ge, ARTNUM artnum, TOKEN token, char *data, int len, time_t arrived, time_t expires) {
+#endif /* OV_DEBUG */
+ OV ov;
+ OVINDEX ie;
+ OVBUFF *ovbuff;
+ OVINDEXHEAD ovindexhead;
+ bool needupdate = false;
+#ifdef OV_DEBUG
+ int recno;
+#endif /* OV_DEBUG */
+
+ Nospace = false;
+ if (OV_BLOCKSIZE < len) {
+ syslog(L_ERROR, "%s: overview data must be under %d (%d)", LocalLogName, OV_BLOCKSIZE, len);
+ return false;
+ }
+ if (ge->curdata.index == NULLINDEX) {
+ /* no data block allocated */
+#ifdef OV_DEBUG
+ ov = ovblocknew(georig ? georig : ge);
+#else
+ ov = ovblocknew();
+#endif /* OV_DEBUG */
+ if (ov.index == NULLINDEX) {
+ syslog(L_ERROR, "%s: ovaddrec could not get new block", LocalLogName);
+ return false;
+ }
+ if ((ovbuff = getovbuff(ov)) == NULL) {
+ syslog(L_ERROR, "%s: ovaddrec could not get ovbuff block for new, %d, %d, %ld", LocalLogName, ov.index, ov.blocknum, artnum);
+ return false;
+ }
+ ge->curdata = ov;
+ ge->curoffset = 0;
+ } else if ((ovbuff = getovbuff(ge->curdata)) == NULL)
+ return false;
+ else if (OV_BLOCKSIZE - ge->curoffset < len) {
+ /* too short to store data, allocate new block */
+#ifdef OV_DEBUG
+ ov = ovblocknew(georig ? georig : ge);
+#else
+ ov = ovblocknew();
+#endif /* OV_DEBUG */
+ if (ov.index == NULLINDEX) {
+ syslog(L_ERROR, "%s: ovaddrec could not get new block", LocalLogName);
+ return false;
+ }
+ if ((ovbuff = getovbuff(ov)) == NULL) {
+ syslog(L_ERROR, "%s: ovaddrec could not get ovbuff block for new, %d, %d, %ld", LocalLogName, ov.index, ov.blocknum, artnum);
+ return false;
+ }
+ ge->curdata = ov;
+ ge->curoffset = 0;
+ }
+#ifdef OV_DEBUG
+ if (!ovusedblock(ovbuff, ge->curdata.blocknum, false, false)) {
+ syslog(L_FATAL, "%s: block(%d, %d) not occupied", LocalLogName, ovbuff->index, ge->curdata.blocknum);
+ buffindexed_close();
+ abort();
+ }
+#endif /* OV_DEBUG */
+#ifdef MMAP_MISSES_WRITES
+ if (mmapwrite(ovbuff->fd, data, len, ovbuff->base + ge->curdata.blocknum * OV_BLOCKSIZE + ge->curoffset) != len) {
+#else
+ if (pwrite(ovbuff->fd, data, len, ovbuff->base + ge->curdata.blocknum * OV_BLOCKSIZE + ge->curoffset) != len) {
+#endif /* MMAP_MISSES_WRITES */
+ syslog(L_ERROR, "%s: could not append overview record index '%d', blocknum '%d': %m", LocalLogName, ge->curdata.index, ge->curdata.blocknum);
+ return false;
+ }
+ memset(&ie, '\0', sizeof(ie));
+ ie.artnum = artnum;
+ ie.len = len;
+ ie.index = ge->curdata.index;
+ ie.blocknum = ge->curdata.blocknum;
+ ie.offset = ge->curoffset;
+ ie.token = token;
+ ie.arrived = arrived;
+ ie.expires = expires;
+
+ if (ge->baseindex.index == NULLINDEX || ge->curindexoffset == OVINDEXMAX) {
+#ifdef OV_DEBUG
+ if (!ovsetcurindexblock(ge, georig)) {
+#else
+ if (!ovsetcurindexblock(ge)) {
+#endif /* OV_DEBUG */
+ syslog(L_ERROR, "%s: could not set current index", LocalLogName);
+ return false;
+ }
+ }
+ if ((ovbuff = getovbuff(ge->curindex)) == NULL)
+ return false;
+#ifdef OV_DEBUG
+ if (!ovusedblock(ovbuff, ge->curindex.blocknum, false, false)) {
+ syslog(L_FATAL, "%s: block(%d, %d) not occupied (index)", LocalLogName, ovbuff->index, ge->curindex.blocknum);
+ buffindexed_close();
+ abort();
+ }
+#endif /* OV_DEBUG */
+#ifdef MMAP_MISSES_WRITES
+ if (mmapwrite(ovbuff->fd, &ie, sizeof(ie), ovbuff->base + ge->curindex.blocknum * OV_BLOCKSIZE + sizeof(OVINDEXHEAD) + sizeof(ie) * ge->curindexoffset) != sizeof(ie)) {
+#else
+ if (pwrite(ovbuff->fd, &ie, sizeof(ie), ovbuff->base + ge->curindex.blocknum * OV_BLOCKSIZE + sizeof(OVINDEXHEAD) + sizeof(ie) * ge->curindexoffset) != sizeof(ie)) {
+#endif /* MMAP_MISSES_WRITES */
+ syslog(L_ERROR, "%s: could not write index record index '%d', blocknum '%d': %m", LocalLogName, ge->curindex.index, ge->curindex.blocknum);
+ return true;
+ }
+ if ((ge->curlow <= 0) || (ge->curlow > artnum)) {
+ ge->curlow = artnum;
+ needupdate = true;
+ }
+ if ((ge->curhigh <= 0) || (ge->curhigh < artnum)) {
+ ge->curhigh = artnum;
+ needupdate = true;
+ }
+ if (needupdate) {
+ ovindexhead.next = ovnull;
+ ovindexhead.low = ge->curlow;
+ ovindexhead.high = ge->curhigh;
+#ifdef MMAP_MISSES_WRITES
+ if (mmapwrite(ovbuff->fd, &ovindexhead, sizeof(OVINDEXHEAD), ovbuff->base + ge->curindex.blocknum * OV_BLOCKSIZE) != sizeof(OVINDEXHEAD)) {
+#else
+ if (pwrite(ovbuff->fd, &ovindexhead, sizeof(OVINDEXHEAD), ovbuff->base + ge->curindex.blocknum * OV_BLOCKSIZE) != sizeof(OVINDEXHEAD)) {
+#endif /* MMAP_MISSES_WRITES */
+ syslog(L_ERROR, "%s: could not write index record index '%d', blocknum '%d': %m", LocalLogName, ge->curindex.index, ge->curindex.blocknum);
+ return true;
+ }
+ }
+ if ((ge->low <= 0) || (ge->low > artnum))
+ ge->low = artnum;
+ if ((ge->high <= 0) || (ge->high < artnum))
+ ge->high = artnum;
+ ge->curindexoffset++;
+ ge->curoffset += len;
+ ge->count++;
+ return true;
+}
+
+bool buffindexed_add(char *group, ARTNUM artnum, TOKEN token, char *data, int len, time_t arrived, time_t expires) {
+ GROUPLOC gloc;
+ GROUPENTRY *ge;
+
+ if (len > OV_BLOCKSIZE) {
+ syslog(L_ERROR, "%s: overview data is too large %d", LocalLogName, len);
+ return true;
+ }
+
+ gloc = GROUPfind(group, false);
+ if (GROUPLOCempty(gloc)) {
+ return true;
+ }
+ GROUPlock(gloc, INN_LOCK_WRITE);
+ /* prepend block(s) if needed. */
+ ge = &GROUPentries[gloc.recno];
+ if (Cutofflow && ge->low > artnum) {
+ GROUPlock(gloc, INN_LOCK_UNLOCK);
+ return true;
+ }
+#ifdef OV_DEBUG
+ if (!ovaddrec(ge, artnum, token, data, len, arrived, expires, NULL)) {
+#else
+ if (!ovaddrec(ge, artnum, token, data, len, arrived, expires)) {
+#endif /* OV_DEBUG */
+ if (Nospace) {
+ GROUPlock(gloc, INN_LOCK_UNLOCK);
+ syslog(L_ERROR, "%s: no space left for buffer, adding '%s'", LocalLogName, group);
+ return false;
+ }
+ syslog(L_ERROR, "%s: could not add overview for '%s'", LocalLogName, group);
+ }
+ GROUPlock(gloc, INN_LOCK_UNLOCK);
+
+ return true;
+}
+
+bool buffindexed_cancel(TOKEN token UNUSED) {
+ return true;
+}
+
+#ifdef OV_DEBUG
+static void freegroupblock(GROUPENTRY *ge) {
+#else
+static void freegroupblock(void) {
+#endif /* OV_DEBUG */
+ GROUPDATABLOCK *gdb;
+ int i;
+ GIBLIST *giblist;
+
+ for (giblist = Giblist ; giblist != NULL ; giblist = giblist->next) {
+#ifdef OV_DEBUG
+ ovblockfree(giblist->ov, ge);
+#else
+ ovblockfree(giblist->ov);
+#endif /* OV_DEBUG */
+ }
+ for (i = 0 ; i < GROUPDATAHASHSIZE ; i++) {
+ for (gdb = groupdatablock[i] ; gdb != NULL ; gdb = gdb->next) {
+#ifdef OV_DEBUG
+ ovblockfree(gdb->datablk, ge);
+#else
+ ovblockfree(gdb->datablk);
+#endif /* OV_DEBUG */
+ }
+ }
+}
+
+static void ovgroupunmap(void) {
+ GROUPDATABLOCK *gdb, *gdbnext;
+ int i;
+ GIBLIST *giblist, *giblistnext;
+
+ for (i = 0 ; i < GROUPDATAHASHSIZE ; i++) {
+ for (gdb = groupdatablock[i] ; gdb != NULL ; gdb = gdbnext) {
+ gdbnext = gdb->next;
+ free(gdb);
+ }
+ groupdatablock[i] = NULL;
+ }
+ for (giblist = Giblist ; giblist != NULL ; giblist = giblistnext) {
+ giblistnext = giblist->next;
+ free(giblist);
+ }
+ Giblist = NULL;
+ if (!Cache && (Gib != NULL)) {
+ free(Gib);
+ Gib = NULL;
+ if (Cachesearch != NULL) {
+ free(Cachesearch->group);
+ free(Cachesearch);
+ Cachesearch = NULL;
+ }
+ }
+}
+
+static void insertgdb(OV *ov, GROUPDATABLOCK *gdb) {
+ gdb->next = groupdatablock[(ov->index + ov->blocknum) % GROUPDATAHASHSIZE];
+ groupdatablock[(ov->index + ov->blocknum) % GROUPDATAHASHSIZE] = gdb;
+ return;
+}
+
+static GROUPDATABLOCK *searchgdb(OV *ov) {
+ GROUPDATABLOCK *gdb;
+
+ gdb = groupdatablock[(ov->index + ov->blocknum) % GROUPDATAHASHSIZE];
+ for (; gdb != NULL ; gdb = gdb->next) {
+ if (ov->index == gdb->datablk.index && ov->blocknum == gdb->datablk.blocknum)
+ break;
+ }
+ return gdb;
+}
+
+static int INDEXcompare(const void *p1, const void *p2) {
+ OVINDEX *oi1;
+ OVINDEX *oi2;
+
+ oi1 = (OVINDEX *)p1;
+ oi2 = (OVINDEX *)p2;
+ return oi1->artnum - oi2->artnum;
+}
+
+static bool ovgroupmmap(GROUPENTRY *ge, int low, int high, bool needov) {
+ OV ov = ge->baseindex;
+ OVBUFF *ovbuff;
+ GROUPDATABLOCK *gdb;
+ int pagefudge, limit, i, count, len;
+ off_t offset, mmapoffset;
+ OVBLOCK *ovblock;
+ void * addr;
+ GIBLIST *giblist;
+
+ if (high - low < 0) {
+ Gibcount = 0;
+ return true;
+ }
+ Gibcount = ge->count;
+ if (Gibcount == 0)
+ return true;
+ Gib = xmalloc(Gibcount * sizeof(OVINDEX));
+ count = 0;
+ while (ov.index != NULLINDEX) {
+ ovbuff = getovbuff(ov);
+ if (ovbuff == NULL) {
+ syslog(L_ERROR, "%s: ovgroupmmap ovbuff is null(ovindex is %d, ovblock is %d", LocalLogName, ov.index, ov.blocknum);
+ ovgroupunmap();
+ return false;
+ }
+ offset = ovbuff->base + (ov.blocknum * OV_BLOCKSIZE);
+ pagefudge = offset % pagesize;
+ mmapoffset = offset - pagefudge;
+ len = pagefudge + OV_BLOCKSIZE;
+ if ((addr = mmap(NULL, len, PROT_READ, MAP_SHARED, ovbuff->fd, mmapoffset)) == MAP_FAILED) {
+ syslog(L_ERROR, "%s: ovgroupmmap could not mmap index block: %m", LocalLogName);
+ ovgroupunmap();
+ return false;
+ }
+ ovblock = (OVBLOCK *)((char *)addr + pagefudge);
+ if (ov.index == ge->curindex.index && ov.blocknum == ge->curindex.blocknum) {
+ limit = ge->curindexoffset;
+ } else {
+ limit = OVINDEXMAX;
+ }
+ for (i = 0 ; i < limit ; i++) {
+ if (Gibcount == count) {
+ Gibcount += OV_FUDGE;
+ Gib = xrealloc(Gib, Gibcount * sizeof(OVINDEX));
+ }
+ Gib[count++] = ovblock->ovindex[i];
+ }
+ giblist = xmalloc(sizeof(GIBLIST));
+ giblist->ov = ov;
+ giblist->next = Giblist;
+ Giblist = giblist;
+ ov = ovblock->ovindexhead.next;
+ munmap(addr, len);
+ }
+ Gibcount = count;
+ qsort(Gib, Gibcount, sizeof(OVINDEX), INDEXcompare);
+ /* Remove duplicates. */
+ for (i = 0; i < Gibcount - 1; i++) {
+ if (Gib[i].artnum == Gib[i+1].artnum) {
+ /* lower position is removed */
+ Gib[i].artnum = 0;
+ }
+ }
+ if (!needov)
+ return true;
+ count = 0;
+ for (i = 0 ; i < Gibcount ; i++) {
+ if (Gib[i].artnum == 0 || Gib[i].artnum < low || Gib[i].artnum > high)
+ continue;
+ ov.index = Gib[i].index;
+ ov.blocknum = Gib[i].blocknum;
+ gdb = searchgdb(&ov);
+ if (gdb != NULL)
+ continue;
+ ovbuff = getovbuff(ov);
+ if (ovbuff == NULL)
+ continue;
+ gdb = xmalloc(sizeof(GROUPDATABLOCK));
+ gdb->datablk = ov;
+ gdb->next = NULL;
+ gdb->mmapped = false;
+ insertgdb(&ov, gdb);
+ count++;
+ }
+ if (count == 0)
+ return true;
+ if (count * OV_BLOCKSIZE > innconf->keepmmappedthreshold * 1024)
+ /* large retrieval, mmap is done in ovsearch() */
+ return true;
+ for (i = 0 ; i < GROUPDATAHASHSIZE ; i++) {
+ for (gdb = groupdatablock[i] ; gdb != NULL ; gdb = gdb->next) {
+ ov = gdb->datablk;
+ ovbuff = getovbuff(ov);
+ offset = ovbuff->base + (ov.blocknum * OV_BLOCKSIZE);
+ pagefudge = offset % pagesize;
+ mmapoffset = offset - pagefudge;
+ gdb->len = pagefudge + OV_BLOCKSIZE;
+ if ((gdb->addr = mmap(NULL, gdb->len, PROT_READ, MAP_SHARED, ovbuff->fd, mmapoffset)) == MAP_FAILED) {
+ syslog(L_ERROR, "%s: ovgroupmmap could not mmap data block: %m", LocalLogName);
+ free(gdb);
+ ovgroupunmap();
+ return false;
+ }
+ gdb->data = (char *)gdb->addr + pagefudge;
+ gdb->mmapped = true;
+ }
+ }
+ return true;
+}
+
+static void *ovopensearch(char *group, int low, int high, bool needov) {
+ GROUPLOC gloc;
+ GROUPENTRY *ge;
+ OVSEARCH *search;
+
+ gloc = GROUPfind(group, false);
+ if (GROUPLOCempty(gloc))
+ return NULL;
+
+ ge = &GROUPentries[gloc.recno];
+ if (low < ge->low)
+ low = ge->low;
+ if (high > ge->high)
+ high = ge->high;
+
+ if (!ovgroupmmap(ge, low, high, needov)) {
+ return NULL;
+ }
+
+ search = xmalloc(sizeof(OVSEARCH));
+ search->hi = high;
+ search->lo = low;
+ search->cur = 0;
+ search->group = xstrdup(group);
+ search->needov = needov;
+ search->gloc = gloc;
+ search->count = ge->count;
+ search->gdb.mmapped = false;
+ return (void *)search;
+}
+
+void *buffindexed_opensearch(char *group, int low, int high) {
+ GROUPLOC gloc;
+ void *handle;
+
+ if (Gib != NULL) {
+ free(Gib);
+ Gib = NULL;
+ if (Cachesearch != NULL) {
+ free(Cachesearch->group);
+ free(Cachesearch);
+ Cachesearch = NULL;
+ }
+ }
+ gloc = GROUPfind(group, false);
+ if (GROUPLOCempty(gloc)) {
+ return NULL;
+ }
+ GROUPlock(gloc, INN_LOCK_WRITE);
+ if ((handle = ovopensearch(group, low, high, true)) == NULL)
+ GROUPlock(gloc, INN_LOCK_UNLOCK);
+ return(handle);
+}
+
+static bool ovsearch(void *handle, ARTNUM *artnum, char **data, int *len, TOKEN *token, time_t *arrived, time_t *expires) {
+ OVSEARCH *search = (OVSEARCH *)handle;
+ OV srchov;
+ GROUPDATABLOCK *gdb;
+ off_t offset, mmapoffset;
+ OVBUFF *ovbuff;
+ int pagefudge;
+ bool newblock;
+
+ if (search->cur == Gibcount) {
+ return false;
+ }
+ while (Gib[search->cur].artnum == 0 || Gib[search->cur].artnum < search->lo) {
+ search->cur++;
+ if (search->cur == Gibcount)
+ return false;
+ }
+ if (Gib[search->cur].artnum > search->hi)
+ return false;
+
+ if (search->needov) {
+ if (Gib[search->cur].index == NULLINDEX) {
+ if (len)
+ *len = 0;
+ if (artnum)
+ *artnum = Gib[search->cur].artnum;
+ } else {
+ if (artnum)
+ *artnum = Gib[search->cur].artnum;
+ if (len)
+ *len = Gib[search->cur].len;
+ if (arrived)
+ *arrived = Gib[search->cur].arrived;
+ if (expires)
+ *expires = Gib[search->cur].expires;
+ if (data) {
+ srchov.index = Gib[search->cur].index;
+ srchov.blocknum = Gib[search->cur].blocknum;
+ gdb = searchgdb(&srchov);
+ if (gdb == NULL) {
+ if (len)
+ *len = 0;
+ search->cur++;
+ return true;
+ }
+ if (!gdb->mmapped) {
+ /* block needs to be mmapped */
+ if (search->gdb.mmapped) {
+ /* check previous mmapped area */
+ if (search->gdb.datablk.blocknum != srchov.blocknum || search->gdb.datablk.index != srchov.index) {
+ /* different one, release previous one */
+ munmap(search->gdb.addr, search->gdb.len);
+ newblock = true;
+ } else
+ newblock = false;
+ } else
+ newblock = true;
+ if (newblock) {
+ search->gdb.datablk.blocknum = srchov.blocknum;
+ search->gdb.datablk.index = srchov.index;
+ ovbuff = getovbuff(srchov);
+ offset = ovbuff->base + (srchov.blocknum * OV_BLOCKSIZE);
+ pagefudge = offset % pagesize;
+ mmapoffset = offset - pagefudge;
+ search->gdb.len = pagefudge + OV_BLOCKSIZE;
+ if ((search->gdb.addr = mmap(NULL, search->gdb.len, PROT_READ, MAP_SHARED, ovbuff->fd, mmapoffset)) == MAP_FAILED) {
+ syslog(L_ERROR, "%s: ovsearch could not mmap data block: %m", LocalLogName);
+ return false;
+ }
+ gdb->data = search->gdb.data = (char *)search->gdb.addr + pagefudge;
+ search->gdb.mmapped = true;
+ }
+ }
+ *data = (char *)gdb->data + Gib[search->cur].offset;
+ }
+ }
+ }
+ if (token) {
+ if (Gib[search->cur].index == NULLINDEX && !search->needov) {
+ search->cur++;
+ return false;
+ }
+ *token = Gib[search->cur].token;
+ }
+ search->cur++;
+ return true;
+}
+
+bool buffindexed_search(void *handle, ARTNUM *artnum, char **data, int *len, TOKEN *token, time_t *arrived) {
+ return(ovsearch(handle, artnum, data, len, token, arrived, NULL));
+}
+
+static void ovclosesearch(void *handle, bool freeblock) {
+ OVSEARCH *search = (OVSEARCH *)handle;
+ GROUPDATABLOCK *gdb;
+ int i;
+#ifdef OV_DEBUG
+ GROUPENTRY *ge;
+ GROUPLOC gloc;
+#endif /* OV_DEBUG */
+
+ for (i = 0 ; i < GROUPDATAHASHSIZE ; i++) {
+ for (gdb = groupdatablock[i] ; gdb != NULL ; gdb = gdb->next) {
+ if (gdb->mmapped)
+ munmap(gdb->addr, gdb->len);
+ }
+ }
+ if (search->gdb.mmapped)
+ munmap(search->gdb.addr, search->gdb.len);
+ if (freeblock) {
+#ifdef OV_DEBUG
+ gloc = GROUPfind(search->group, false);
+ if (!GROUPLOCempty(gloc)) {
+ ge = &GROUPentries[gloc.recno];
+ freegroupblock(ge);
+ }
+#else
+ freegroupblock();
+#endif /* OV_DEBUG */
+ }
+ ovgroupunmap();
+ if (Cache) {
+ Cachesearch = search;
+ } else {
+ free(search->group);
+ free(search);
+ }
+ return;
+}
+
+void buffindexed_closesearch(void *handle) {
+ OVSEARCH *search = (OVSEARCH *)handle;
+ GROUPLOC gloc;
+
+ gloc = search->gloc;
+ ovclosesearch(handle, false);
+ GROUPlock(gloc, INN_LOCK_UNLOCK);
+}
+
+/* get token from sorted index */
+static bool gettoken(ARTNUM artnum, TOKEN *token) {
+ int i, j, offset, limit;
+ offset = 0;
+ limit = Gibcount;
+ for (i = (limit - offset) / 2 ; i > 0 ; i = (limit - offset) / 2) {
+ if (Gib[offset + i].artnum == artnum) {
+ *token = Gib[offset + i].token;
+ return true;
+ } else if (Gib[offset + i].artnum == 0) {
+ /* case for duplicated index */
+ for (j = offset + i - 1; j >= offset ; j --) {
+ if (Gib[j].artnum != 0)
+ break;
+ }
+ if (j < offset) {
+ /* article not found */
+ return false;
+ }
+ if (Gib[j].artnum == artnum) {
+ *token = Gib[j].token;
+ return true;
+ } else if (Gib[j].artnum < artnum) {
+ /* limit is not changed */
+ offset += i + 1;
+ } else {
+ /* offset is not changed */
+ limit = j;
+ }
+ } else if (Gib[offset + i].artnum < artnum) {
+ /* limit is unchanged */
+ offset += i + 1;
+ } else {
+ /* offset is unchanged */
+ limit = offset + i;
+ }
+ }
+ /* i == 0 */
+ if (Gib[offset].artnum != artnum) {
+ /* article not found */
+ return false;
+ }
+ *token = Gib[offset].token;
+ return true;
+}
+
+bool buffindexed_getartinfo(char *group, ARTNUM artnum, TOKEN *token) {
+ GROUPLOC gloc;
+ void *handle;
+ bool retval, grouplocked = false;
+
+ if (Gib != NULL) {
+ if (Cachesearch != NULL && strcmp(Cachesearch->group, group) != 0) {
+ free(Gib);
+ Gib = NULL;
+ free(Cachesearch->group);
+ free(Cachesearch);
+ Cachesearch = NULL;
+ } else {
+ if (gettoken(artnum, token))
+ return true;
+ else {
+ /* examine to see if overview index are increased */
+ gloc = GROUPfind(group, false);
+ if (GROUPLOCempty(gloc)) {
+ return false;
+ }
+ GROUPlock(gloc, INN_LOCK_WRITE);
+ if ((Cachesearch != NULL) && (GROUPentries[gloc.recno].count == Cachesearch->count)) {
+ /* no new overview data is stored */
+ GROUPlock(gloc, INN_LOCK_UNLOCK);
+ return false;
+ } else {
+ grouplocked = true;
+ free(Gib);
+ Gib = NULL;
+ if (Cachesearch != NULL) {
+ free(Cachesearch->group);
+ free(Cachesearch);
+ Cachesearch = NULL;
+ }
+ }
+ }
+ }
+ }
+ if (!grouplocked) {
+ gloc = GROUPfind(group, false);
+ if (GROUPLOCempty(gloc)) {
+ return false;
+ }
+ GROUPlock(gloc, INN_LOCK_WRITE);
+ }
+ if (!(handle = ovopensearch(group, artnum, artnum, false))) {
+ GROUPlock(gloc, INN_LOCK_UNLOCK);
+ return false;
+ }
+ retval = buffindexed_search(handle, NULL, NULL, NULL, token, NULL);
+ ovclosesearch(handle, false);
+ GROUPlock(gloc, INN_LOCK_UNLOCK);
+ return retval;
+}
+
+bool buffindexed_expiregroup(char *group, int *lo, struct history *h) {
+ void *handle;
+ GROUPENTRY newge, *ge;
+ GROUPLOC gloc, next;
+ char *data;
+ int i, j, len;
+ TOKEN token;
+ ARTNUM artnum, low, high;
+ ARTHANDLE *ah;
+ char flag;
+ HASH hash;
+ time_t arrived, expires;
+
+ if (group == NULL) {
+ for (i = 0 ; i < GROUPheader->freelist.recno ; i++) {
+ gloc.recno = i;
+ GROUPlock(gloc, INN_LOCK_WRITE);
+ ge = &GROUPentries[gloc.recno];
+ if (ge->expired >= OVrealnow || ge->count == 0) {
+ GROUPlock(gloc, INN_LOCK_UNLOCK);
+ continue;
+ }
+ if (!ovgroupmmap(ge, ge->low, ge->high, true)) {
+ GROUPlock(gloc, INN_LOCK_UNLOCK);
+ syslog(L_ERROR, "%s: could not mmap overview for hidden groups(%d)", LocalLogName, i);
+ continue;
+ }
+ for (j = 0 ; j < Gibcount ; j++) {
+ if (Gib[j].artnum == 0)
+ continue;
+ /* this may be duplicated, but ignore it in this case */
+ OVEXPremove(Gib[j].token, true, NULL, 0);
+ }
+#ifdef OV_DEBUG
+ freegroupblock(ge);
+#else
+ freegroupblock();
+#endif
+ ovgroupunmap();
+ ge->expired = time(NULL);
+ ge->count = 0;
+ GROUPlock(gloc, INN_LOCK_UNLOCK);
+ }
+ return true;
+ }
+ gloc = GROUPfind(group, false);
+ if (GROUPLOCempty(gloc)) {
+ return false;
+ }
+ GROUPlock(gloc, INN_LOCK_WRITE);
+ ge = &GROUPentries[gloc.recno];
+ if (ge->count == 0) {
+ if (lo)
+ *lo = ge->low;
+ ge->expired = time(NULL);
+ GROUPlock(gloc, INN_LOCK_UNLOCK);
+ return true;
+ }
+ flag = ge->flag;
+ hash = ge->hash;
+ next = ge->next;
+ low = ge->low;
+ high = ge->high;
+
+ newge.low = 0;
+ setinitialge(&newge, hash, &flag, next, 0, high);
+ if ((handle = ovopensearch(group, low, high, true)) == NULL) {
+ ge->expired = time(NULL);
+ GROUPlock(gloc, INN_LOCK_UNLOCK);
+ syslog(L_ERROR, "%s: could not open overview for '%s'", LocalLogName, group);
+ return false;
+ }
+ while (ovsearch(handle, &artnum, &data, &len, &token, &arrived, &expires)) {
+ ah = NULL;
+ if (len == 0)
+ continue;
+ if (!SMprobe(EXPENSIVESTAT, &token, NULL) || OVstatall) {
+ if ((ah = SMretrieve(token, RETR_STAT)) == NULL)
+ continue;
+ SMfreearticle(ah);
+ } else {
+ if (!OVhisthasmsgid(h, data))
+ continue;
+ }
+ if (innconf->groupbaseexpiry && OVgroupbasedexpire(token, group, data, len, arrived, expires))
+ continue;
+#ifdef OV_DEBUG
+ if (!ovaddrec(&newge, artnum, token, data, len, arrived, expires, ge)) {
+#else
+ if (!ovaddrec(&newge, artnum, token, data, len, arrived, expires)) {
+#endif /* OV_DEBUG */
+ ovclosesearch(handle, true);
+ ge->expired = time(NULL);
+ GROUPlock(gloc, INN_LOCK_UNLOCK);
+ syslog(L_ERROR, "%s: could not add new overview for '%s'", LocalLogName, group);
+ return false;
+ }
+ }
+ if (newge.low == 0)
+ /* no article for the group */
+ newge.low = newge.high;
+ *ge = newge;
+ if (lo) {
+ if (ge->count == 0)
+ /* lomark should be himark + 1, if no article for the group */
+ *lo = ge->low + 1;
+ else
+ *lo = ge->low;
+ }
+ ovclosesearch(handle, true);
+ ge->expired = time(NULL);
+ GROUPlock(gloc, INN_LOCK_UNLOCK);
+ return true;
+}
+
+bool buffindexed_ctl(OVCTLTYPE type, void *val) {
+ int total, used, *i, j;
+ OVBUFF *ovbuff = ovbufftab;
+ OVSORTTYPE *sorttype;
+ bool *boolval;
+ GROUPDATABLOCK *gdb;
+
+ switch (type) {
+ case OVSPACE:
+ for (total = used = 0 ; ovbuff != (OVBUFF *)NULL ; ovbuff = ovbuff->next) {
+ ovlock(ovbuff, INN_LOCK_READ);
+ ovreadhead(ovbuff);
+ total += ovbuff->totalblk;
+ used += ovbuff->usedblk;
+ ovlock(ovbuff, INN_LOCK_UNLOCK);
+ }
+ i = (int *)val;
+ *i = (used * 100) / total;
+ return true;
+ case OVSORT:
+ sorttype = (OVSORTTYPE *)val;
+ *sorttype = OVNOSORT;
+ return true;
+ case OVCUTOFFLOW:
+ Cutofflow = *(bool *)val;
+ return true;
+ case OVSTATICSEARCH:
+ i = (int *)val;
+ *i = true;
+ for (j = 0 ; j < GROUPDATAHASHSIZE ; j++) {
+ for (gdb = groupdatablock[j] ; gdb != NULL ; gdb = gdb->next) {
+ if (gdb->mmapped) {
+ *i = false;
+ return true;
+ }
+ }
+ }
+ return true;
+ case OVCACHEKEEP:
+ Cache = *(bool *)val;
+ return true;
+ case OVCACHEFREE:
+ boolval = (bool *)val;
+ *boolval = true;
+ if (Gib != NULL) {
+ free(Gib);
+ Gib = NULL;
+ if (Cachesearch != NULL) {
+ free(Cachesearch->group);
+ free(Cachesearch);
+ Cachesearch = NULL;
+ }
+ }
+ return true;
+ default:
+ return false;
+ }
+}
+
+void buffindexed_close(void) {
+ struct stat sb;
+ OVBUFF *ovbuffnext, *ovbuff = ovbufftab;
+#ifdef OV_DEBUG
+ FILE *F = NULL;
+ pid_t pid;
+ char *path = NULL;
+ int i,j;
+ struct ov_trace_array *trace;
+ struct ov_name_table *ntp;
+ size_t length;
+#endif /* OV_DEBUG */
+
+#ifdef OV_DEBUG
+ for (; ovbuff != (OVBUFF *)NULL; ovbuff = ovbuff->next) {
+ for (i = 0 ; i < ovbuff->totalblk ; i++) {
+ trace = &ovbuff->trace[i];
+ if (trace->ov_trace == NULL)
+ continue;
+ for (j = 0 ; j <= trace->cur && j < trace->max ; j++) {
+ if (trace->ov_trace[j].occupied != 0 ||
+ trace->ov_trace[j].freed != 0) {
+ if (F == NULL) {
+ length = strlen(innconf->pathtmp) + 11;
+ path = xmalloc(length);
+ pid = getpid();
+ snprintf(path, length, "%s/%d", innconf->pathtmp, pid);
+ if ((F = fopen(path, "w")) == NULL) {
+ syslog(L_ERROR, "%s: could not open %s: %m", LocalLogName, path);
+ break;
+ }
+ }
+ fprintf(F, "%d: % 6d, % 2d: 0x%08x, % 10d, % 10d\n", ovbuff->index, i, j,
+ trace->ov_trace[j].gloc.recno,
+ trace->ov_trace[j].occupied,
+ trace->ov_trace[j].freed);
+ }
+ }
+ }
+ }
+ if ((ntp = name_table) != NULL) {
+ if (F == NULL) {
+ length = strlen(innconf->pathtmp) + 11;
+ path = xmalloc(length);
+ pid = getpid();
+ sprintf(path, length, "%s/%d", innconf->pathtmp, pid);
+ if ((F = fopen(path, "w")) == NULL) {
+ syslog(L_ERROR, "%s: could not open %s: %m", LocalLogName, path);
+ }
+ }
+ if (F != NULL) {
+ while(ntp) {
+ fprintf(F, "0x%08x: %s\n", ntp->recno, ntp->name);
+ ntp = ntp->next;
+ }
+ }
+ }
+ if (F != NULL)
+ fclose(F);
+ if (path != NULL)
+ free(path);
+#endif /* OV_DEBUG */
+ if (Gib != NULL) {
+ free(Gib);
+ Gib = NULL;
+ if (Cachesearch != NULL) {
+ free(Cachesearch->group);
+ free(Cachesearch);
+ Cachesearch = NULL;
+ }
+ }
+ if (fstat(GROUPfd, &sb) < 0)
+ return;
+ close(GROUPfd);
+
+ if (GROUPheader) {
+ if (munmap((void *)GROUPheader, GROUPfilesize(GROUPcount)) < 0) {
+ syslog(L_FATAL, "%s: could not munmap group.index in buffindexed_close: %m", LocalLogName);
+ return;
+ }
+ }
+ for (; ovbuff != (OVBUFF *)NULL; ovbuff = ovbuffnext) {
+ ovbuffnext = ovbuff->next;
+ free(ovbuff);
+ }
+ ovbufftab = NULL;
+}
+
+#ifdef BUFF_DEBUG
+static int countgdb(void) {
+ int i, count = 0;
+ GROUPDATABLOCK *gdb;
+
+ for (i = 0 ; i < GROUPDATAHASHSIZE ; i++) {
+ for (gdb = groupdatablock[i] ; gdb != NULL ; gdb = gdb->next)
+ count++;
+ }
+ return count;
+}
+
+main(int argc, char **argv) {
+ char *group, flag[2], buff[OV_BLOCKSIZE];
+ int lo, hi, count, flags, i;
+ OVSEARCH *search;
+ OVBLOCK *ovblock;
+ OVINDEX *ovindex;
+ OVBUFF *ovbuff;
+ GROUPENTRY *ge;
+ GROUPLOC gloc;
+ GIBLIST *giblist;
+
+ if (argc != 2) {
+ fprintf(stderr, "only one argument can be specified\n");
+ exit(1);
+ }
+ /* if innconf isn't already read in, do so. */
+ if (innconf == NULL) {
+ if (!innconf_read(NULL)) {
+ fprintf(stderr, "reading inn.conf failed\n");
+ exit(1);
+ }
+ }
+ if (!buffindexed_open(OV_READ)) {
+ fprintf(stderr, "buffindexed_open failed\n");
+ exit(1);
+ }
+ fprintf(stdout, "GROUPheader->freelist.recno is %d(0x%08x)\n", GROUPheader->freelist.recno, GROUPheader->freelist.recno);
+ group = argv[1];
+ if (isdigit(*group)) {
+ gloc.recno = atoi(group);
+ ge = &GROUPentries[gloc.recno];
+ fprintf(stdout, "left articles are %d for %d, last expiry is %d\n", ge->count, gloc.recno, ge->expired);
+ if (ge->count == 0) {
+ GROUPlock(gloc, INN_LOCK_UNLOCK);
+ exit(0);
+ }
+ if (!ovgroupmmap(ge, ge->low, ge->high, true)) {
+ fprintf(stderr, "ovgroupmmap failed\n");
+ GROUPlock(gloc, INN_LOCK_UNLOCK);
+ }
+ for (giblist = Giblist, i = 0 ; giblist != NULL ; giblist = giblist->next, i++);
+ fprintf(stdout, "%d index block(s)\n", i);
+ fprintf(stdout, "%d data block(s)\n", countgdb());
+ for (giblist = Giblist ; giblist != NULL ; giblist = giblist->next) {
+ fprintf(stdout, " % 8d(% 5d)\n", giblist->ov.blocknum, giblist->ov.index);
+ }
+ for (i = 0 ; i < Gibcount ; i++) {
+ if (Gib[i].artnum == 0)
+ continue;
+ if (Gib[i].index == NULLINDEX)
+ fprintf(stdout, " %d empty\n");
+ else {
+ fprintf(stdout, " %d %d\n", Gib[i].offset, Gib[i].len);
+ }
+ }
+ ovgroupunmap();
+ GROUPlock(gloc, INN_LOCK_UNLOCK);
+ exit(0);
+ }
+ gloc = GROUPfind(group, false);
+ if (GROUPLOCempty(gloc)) {
+ fprintf(stderr, "gloc is null\n");
+ }
+ GROUPlock(gloc, INN_LOCK_READ);
+ ge = &GROUPentries[gloc.recno];
+ fprintf(stdout, "base %d(%d), cur %d(%d), expired at %s\n", ge->baseindex.blocknum, ge->baseindex.index, ge->curindex.blocknum, ge->curindex.index, ge->expired == 0 ? "none\n" : ctime(&ge->expired));
+ if (!buffindexed_groupstats(group, &lo, &hi, &count, &flags)) {
+ fprintf(stderr, "buffindexed_groupstats failed for group %s\n", group);
+ exit(1);
+ }
+ flag[0] = (char)flags;
+ flag[1] = '\0';
+ fprintf(stdout, "%s: low is %d, high is %d, count is %d, flag is '%s'\n", group, lo, hi, count, flag);
+ if ((search = (OVSEARCH *)ovopensearch(group, lo, hi, true)) == NULL) {
+ fprintf(stderr, "ovopensearch failed for group %s\n", group);
+ exit(1);
+ }
+ fprintf(stdout, " gloc is %d(0x%08x)\n", search->gloc.recno, search->gloc.recno);
+ for (giblist = Giblist, i = 0 ; giblist != NULL ; giblist = giblist->next, i++);
+ fprintf(stdout, "%d index block(s)\n", i);
+ fprintf(stdout, "%d data block(s)\n", countgdb());
+ for (giblist = Giblist ; giblist != NULL ; giblist = giblist->next) {
+ fprintf(stdout, " % 8d(% 5d)\n", giblist->ov.blocknum, giblist->ov.index);
+ }
+ for (i = 0 ; i < Gibcount ; i++) {
+ if (Gib[i].artnum == 0)
+ continue;
+ if (Gib[i].index == NULLINDEX)
+ fprintf(stdout, " %d empty\n");
+ else {
+ fprintf(stdout, " %d %d\n", Gib[i].offset, Gib[i].len);
+ }
+ }
+ {
+ ARTNUM artnum;
+ char *data;
+ int len;
+ TOKEN token;
+ while (buffindexed_search((void *)search, &artnum, &data, &len, &token, NULL)) {
+ if (len == 0)
+ fprintf(stdout, "%d: len is 0\n", artnum);
+ else {
+ memcpy(buff, data, len);
+ buff[len] = '\0';
+ fprintf(stdout, "%d: %s\n", artnum, buff);
+ }
+ }
+ }
+}
+#endif /* BUFF_DEBUG */
--- /dev/null
+#ifndef _BUFFINDEXED_H_
+#define _BUFFINDEXED_H_
+
+#ifdef __cplusplus
+extern "C" {
+#endif /* __cplusplus */
+
+bool buffindexed_open(int mode);
+bool buffindexed_groupstats(char *group, int *lo, int *hi, int *count, int *flag);
+bool buffindexed_groupadd(char *group, ARTNUM lo, ARTNUM hi, char *flag);
+bool buffindexed_groupdel(char *group);
+bool buffindexed_add(char *group, ARTNUM artnum, TOKEN token, char *data, int len, time_t arrived, time_t expires);
+bool buffindexed_cancel(TOKEN token);
+void *buffindexed_opensearch(char *group, int low, int high);
+bool buffindexed_search(void *handle, ARTNUM *artnum, char **data, int *len, TOKEN *token, time_t *arrived);
+void buffindexed_closesearch(void *handle);
+bool buffindexed_getartinfo(char *group, ARTNUM artnum, TOKEN *token);
+bool buffindexed_expiregroup(char *group, int *lo, struct history *h);
+bool buffindexed_ctl(OVCTLTYPE type, void *val);
+void buffindexed_close(void);
+
+#ifdef __cplusplus
+}
+#endif /* __cplusplus */
+
+#endif /* _BUFFINDEXED_H_ */
--- /dev/null
+name = buffindexed
+number = 3
+sources = buffindexed.c
--- /dev/null
+# This rule requires a compiler that supports -o with -c. Since it's normally
+# used by developers, that should be acceptable.
+buffindexed/buffindexed_d.o: buffindexed/buffindexed.c
+ $(CC) $(CFLAGS) -DBUFF_DEBUG -c -o $@ buffindexed/buffindexed.c
+
+buffindexed/debug: buffindexed/buffindexed_d.o libstorage.$(EXTLIB) $(LIBHIST)
+ $(LIBLD) $(LDFLAGS) -o $@ buffindexed/buffindexed_d.o \
+ $(LIBSTORAGE) $(LIBHIST) $(LIBINN) $(EXTSTORAGELIBS) $(LIBS)
--- /dev/null
+#! /usr/bin/perl
+
+## $Id: buildconfig.in 6806 2004-05-18 01:18:57Z rra $
+##
+## Generate linkage code and makefiles for storage and overview methods.
+##
+## Goes through all subdirectories of the current directory and finds
+## directories that are either storage methods or overview methods. Builds
+## methods.[ch] and ovmethods.[ch] as well as makefile stubs.
+
+require 5.003;
+
+use strict;
+use vars qw(@OVERVIEW @STORAGE);
+
+# Storage API functions.
+@STORAGE = qw(init store retrieve next freearticle cancel ctl flushcacheddata
+ printfiles shutdown);
+
+# Overview API functions.
+@OVERVIEW = qw(open groupstats groupadd groupdel add cancel opensearch search
+ closesearch getartinfo expiregroup ctl close);
+
+# Used to make heredocs more readable.
+sub unquote { my ($string) = @_; $string =~ s/^:( {0,7}|\t)//gm; $string }
+
+# Parse a method.config file for a storage method, putting information about
+# that storage method into the given hash ref.
+sub parse_config {
+ my ($dir, $file, $config) = @_;
+ local $_;
+ $$config{sources} ||= [];
+ $$config{extra} ||= [];
+ $$config{programs} ||= [];
+ $$config{makefiles} ||= [];
+ open (CONFIG, "$dir/$file") or die "Can't open $dir/$file: $!\n";
+ while (<CONFIG>) {
+ s/^\s+//;
+ s/\s+$//;
+ if (/^name\s*=\s*(\S+)$/) {
+ my $method = $1;
+ die "$dir/$file: $method has already been defined\n"
+ if (defined $$config{method}{$method});
+ $$config{method}{$method} = $dir;
+ } elsif (/^number\s*=\s*(\d+)$/) {
+ my $number = $1;
+ if (defined $$config{number}{$number}) {
+ die "$dir/$file: method number $number was already "
+ . "allocated in $$config{number}{$number}\n";
+ }
+ $$config{number}{$dir} = $number;
+ } elsif (/^sources\s*=\s*(.*)/) {
+ my $sources = $1;
+ my @sources = split (' ', $sources);
+ push (@{ $$config{sources} }, map { "$dir/$_" } @sources);
+ } elsif (/^extra-sources\s*=\s*(.*)/) {
+ my $extra = $1;
+ my @extra = split (' ', $extra);
+ push (@{ $$config{extra} }, map { "$dir/$_" } @extra);
+ } elsif (/^programs\s*=\s*(.*)/) {
+ my $programs = $1;
+ my @programs = split (' ', $programs);
+ push (@{ $$config{programs} }, map { "$dir/$_" } @programs);
+ } else {
+ warn "$dir/$file: ignoring unknown line: $_\n";
+ }
+ }
+
+ # If there is a makefile fragment in the directory, note it.
+ if (-f "$dir/method.mk") {
+ push (@{ $$config{makefiles} }, "$dir/method.mk");
+ } elsif (-f "$dir/ovmethod.mk") {
+ push (@{ $$config{makefiles} }, "$dir/ovmethod.mk");
+ }
+}
+
+# Write out include directives for a list of files.
+sub write_includes {
+ my ($fh, $config) = @_;
+ my $method;
+ for $method (sort keys %{ $$config{method} }) {
+ my $path = $$config{method}{$method};
+ print $fh qq(\#include "$path/$method.h"\n);
+ }
+}
+
+# Write out the method struct.
+sub write_methods {
+ my ($fh, $config, $prefix, @funcs) = @_;
+ my ($notfirst, $method);
+ for $method (sort keys %{ $$config{method} }) {
+ print $fh "\n},\n" if $notfirst;
+ print $fh qq(\{\n "$method");
+ print $fh ', ', $prefix, '_', uc ($method) if $prefix;
+ for (@funcs) {
+ print $fh ",\n ${method}_$_";
+ }
+ $notfirst++;
+ }
+ print $fh "\n}\n};\n\n";
+}
+
+# Write out the constant defines for methods.
+sub write_constants {
+ my ($fh, $config, $prefix) = @_;
+ my $method;
+ for $method (sort keys %{ $$config{method} }) {
+ printf $fh "#define ${prefix}_%-30s%d\n", uc ($method),
+ $$config{number}{$$config{method}{$method}};
+ }
+}
+
+# Write out methods.c and methods.h for the interface to the storage
+# methods.
+sub write_storage {
+ my $storage = shift;
+ open (DEF, '> methods.c.new') or die "Can't create methods.c.new: $!\n";
+ print DEF unquote (<<'EOE');
+: /* This file is automatically generated by buildconfig. */
+:
+: #include "interface.h"
+: #include "methods.h"
+EOE
+ my $method;
+ write_includes (\*DEF, $storage);
+ print DEF "\nSTORAGE_METHOD storage_methods[",
+ scalar (keys %{ $$storage{method} }), "] = {\n";
+ write_methods (\*DEF, $storage, 'TOKEN', @STORAGE);
+ close DEF;
+ rename ('methods.c.new', 'methods.c');
+
+ open (H, '> methods.h.new') or die "Can't open methods.h.new: $!\n";
+ print H unquote (<<'EOE');
+: /* This file is automatically generated by buildconfig */
+:
+: #ifndef METHODS_H
+: #define METHODS_H 1
+:
+: #include "interface.h"
+:
+EOE
+ print H '#define NUM_STORAGE_METHODS ',
+ scalar (keys %{ $$storage{method} }), "\n\n";
+ write_constants (\*H, $storage, 'TOKEN');
+ print H unquote (<<'EOE');
+:
+: extern STORAGE_METHOD storage_methods[NUM_STORAGE_METHODS];
+:
+: #endif /* METHODS_H */
+EOE
+ close H;
+ rename ('methods.h.new', 'methods.h');
+}
+
+# Write out ovmethods.c and ovmethods.h for the interface to the overview
+# methods.
+sub write_overview {
+ my $overview = shift;
+ open (DEF, '> ovmethods.c.new')
+ or die "Can't create ovmethods.c.new: $!\n";
+ print DEF unquote (<<'EOE');
+: /* This file is automatically generated by buildconfig. */
+:
+: #include "ovinterface.h"
+EOE
+ write_includes (\*DEF, $overview);
+ print DEF "\nOV_METHOD ov_methods[",
+ scalar (keys %{ $$overview{method} }), "] = {\n";
+ write_methods (\*DEF, $overview, undef, @OVERVIEW);
+ close DEF;
+ rename ('ovmethods.c.new', 'ovmethods.c');
+
+ open (H, '> ovmethods.h.new') or die "Can't open ovmethods.h.new: $!\n";
+ print H unquote (<<'EOE');
+: /* This file is automatically generated by buildconfig */
+:
+: #ifndef OVMETHODS_H
+: #define OVMETHODS_H 1
+:
+: #include "ovinterface.h"
+:
+EOE
+ print H '#define NUM_OV_METHODS ',
+ scalar (keys %{ $$overview{method} }), "\n";
+ print H unquote (<<'EOE');
+:
+: extern OV_METHOD ov_methods[NUM_OV_METHODS];
+:
+: #endif /* OVMETHODS_H */
+EOE
+ close H;
+ rename ('ovmethods.h.new', 'ovmethods.h');
+}
+
+# Return a string setting a makefile variable. Tab over the = properly and
+# wrap to fit our coding standards.
+sub makefile_var {
+ my ($variable, @values) = @_;
+ my $output;
+ $output = sprintf ("%-15s =", $variable);
+ my $column = 17;
+ for (@values) {
+ if ($column > 17 && 77 - $column < length ($_)) {
+ $output .= " \\\n" . ' ' x 17;
+ $column = 17;
+ }
+ $output .= " $_";
+ $column += 1 + length ($_);
+ }
+ $output .= "\n";
+ return $output;
+}
+
+# Write out the makefile fragment for overview and storage methods.
+sub write_makefile {
+ my ($dirs, $sources, $extra, $programs, $makefiles) = @_;
+ open (MAKE, '> Make.methods.new')
+ or die "Can't create Make.methods.new: $!\n";
+ print MAKE "# This file is automatically generated by buildconfig\n\n";
+ print MAKE makefile_var ('METHOD_SOURCES', @$sources);
+ print MAKE makefile_var ('EXTRA_SOURCES', @$extra);
+ print MAKE makefile_var ('PROGRAMS', @$programs);
+ for (@$makefiles) {
+ print MAKE "\n\n## Included from $_\n\n";
+ open (FRAG, $_) or die "Can't open $_: $!\n";
+ print MAKE <FRAG>;
+ close FRAG;
+ }
+ rename ('Make.methods.new', 'Make.methods');
+}
+
+my ($dir, %storage, %overview);
+if (!-d 'cnfs') {
+ if (-d 'storage/cnfs') {
+ chdir 'storage' or die "Can't chdir to storage: $!\n";
+ } else {
+ die "Can't find storage directory (looking for storage/cnfs)\n";
+ }
+}
+opendir (D, ".") or die "Can't open current directory: $!\n";
+my @dirs = sort readdir D;
+for $dir (@dirs) {
+ if (-e "$dir/method.config") {
+ parse_config ($dir, 'method.config', \%storage);
+ }
+ if (-e "$dir/ovmethod.config") {
+ parse_config ($dir, 'ovmethod.config', \%overview);
+ }
+}
+write_storage (\%storage);
+write_overview (\%overview);
+@dirs = (sort values %{ $storage{method} },
+ sort values %{ $overview{method} });
+my @sources = (sort @{ $storage{sources} }, sort @{ $overview{sources} });
+my @extra = (sort @{ $storage{extra} }, sort @{ $overview{extra} });
+my @programs = sort (@{ $storage{programs} }, @{ $overview{programs} });
+my @makefiles = sort (@{ $storage{makefiles} }, @{ $overview{makefiles} });
+write_makefile (\@dirs, \@sources, \@extra, \@programs, \@makefiles);
--- /dev/null
+/* $Id: cnfs-private.h 5975 2002-12-11 04:51:43Z rra $
+**
+** CNFS disk/file mode header file.
+*/
+
+#ifndef CNFS_PRIVATE_H
+#define CNFS_PRIVATE_H 1
+
+#include <sys/types.h>
+#include <unistd.h>
+
+#define _PATH_CYCBUFFCONFIG "cycbuff.conf"
+
+/* Page boundary on which to mmap() the CNFS article usage header. Should be
+ a multiple of the pagesize for all the architectures you expect might need
+ access to your CNFS buffer. If you don't expect to share your buffer
+ across several platforms, you can use 'pagesize' here. */
+#define CNFS_HDR_PAGESIZE 16384
+
+#define CNFS_MAGICV1 "Cycbuff" /* CNFSMASIZ bytes */
+#define CNFS_MAGICV2 "CBuf1" /* CNFSMASIZ bytes */
+#define CNFS_MAGICV3 "CBuf3" /* CNFSMASIZ bytes */
+#define CNFS_BLOCKSIZE 512 /* Unit block size we'll work with */
+
+/* Amount of data stored at beginning of CYCBUFF before the bitfield */
+#define CNFS_BEFOREBITF (1 * CNFS_BLOCKSIZE)
+
+struct metacycbuff; /* Definition comes below */
+
+#define CNFSMAXCYCBUFFNAME 8
+#define CNFSMASIZ 8
+#define CNFSNASIZ 16 /* Effective size is 9, not 16 */
+#define CNFSPASIZ 64
+#define CNFSLASIZ 16 /* Match length of ASCII hex off_t
+ representation */
+
+typedef struct _CYCBUFF {
+ char name[CNFSNASIZ];/* Symbolic name */
+ char path[CNFSPASIZ];/* Path to file */
+ off_t len; /* Length of writable area, in bytes */
+ off_t free; /* Offset (relative to byte 0 of file) to first
+ freely available byte */
+ time_t updated; /* Time of last update to header */
+ int fd; /* file descriptor for this cycbuff */
+ uint32_t cyclenum; /* Number of current cycle, 0 = invalid */
+ int magicver; /* Magic version number */
+ void * bitfield; /* Bitfield for article in use */
+ off_t minartoffset; /* The minimum offset allowed for article
+ storage */
+ bool needflush; /* true if CYCBUFFEXTERN is needed to be
+ flushed */
+ struct _CYCBUFF *next;
+ bool currentbuff; /* true if this cycbuff is currently used */
+ char metaname[CNFSNASIZ];/* Symbolic name of meta */
+ int order; /* Order in meta, start from 1 not 0 */
+} CYCBUFF;
+
+/*
+** A structure suitable for thwapping onto disk in a quasi-portable way.
+** We assume that sizeof(CYCBUFFEXTERN) < CNFS_BLOCKSIZE.
+*/
+typedef struct {
+ char magic[CNFSMASIZ];
+ char name[CNFSNASIZ];
+ char path[CNFSPASIZ];
+ char lena[CNFSLASIZ]; /* ASCII version of len */
+ char freea[CNFSLASIZ]; /* ASCII version of free */
+ char updateda[CNFSLASIZ]; /* ASCII version of updated */
+ char cyclenuma[CNFSLASIZ]; /* ASCII version of cyclenum */
+ char metaname[CNFSNASIZ];
+ char orderinmeta[CNFSLASIZ];
+ char currentbuff[CNFSMASIZ];
+} CYCBUFFEXTERN;
+
+#define METACYCBUFF_UPDATE 25
+#define REFRESH_INTERVAL 30
+
+typedef enum {INTERLEAVE, SEQUENTIAL} METAMODE;
+
+typedef struct metacycbuff {
+ char *name; /* Symbolic name of the pool */
+ int count; /* Number of files/devs in this pool */
+ CYCBUFF **members; /* Member cycbuffs */
+ int memb_next; /* Index to next member to write onto */
+ unsigned long write_count; /* Number of writes since last header flush */
+ struct metacycbuff *next;
+ METAMODE metamode;
+} METACYCBUFF;
+
+typedef struct _CNFSEXPIRERULES {
+ STORAGECLASS class;
+ METACYCBUFF *dest;
+ struct _CNFSEXPIRERULES *next;
+} CNFSEXPIRERULES;
+
+typedef struct {
+ long size; /* Size of the article */
+ time_t arrived; /* This is the time when article arrived */
+ STORAGECLASS class; /* storage class */
+} CNFSARTHEADER;
+
+/* uncomment below for old cnfs spool */
+/* #ifdef OLD_CNFS */
+typedef struct {
+ long zottf; /* This should always be 0x01234*/
+ long size; /* Size of the article */
+ char m_id[64]; /* We'll only store up to 63 bytes of the
+ Message-ID, that should be good enough */
+} oldCNFSARTHEADER;
+
+#endif /* !CNFS_PRIVATE_H */
--- /dev/null
+/* $Id: cnfs.c 7412 2005-10-09 03:44:35Z eagle $
+**
+** Cyclic News File System.
+*/
+
+#include "config.h"
+#include "clibrary.h"
+#include "portable/mmap.h"
+#include "portable/time.h"
+#include <ctype.h>
+#include <errno.h>
+#include <fcntl.h>
+#if HAVE_LIMITS_H
+# include <limits.h>
+#endif
+#include <netinet/in.h>
+#include <syslog.h>
+#include <sys/stat.h>
+#include <sys/uio.h>
+
+#include "inn/innconf.h"
+#include "interface.h"
+#include "libinn.h"
+#include "methods.h"
+#include "paths.h"
+#include "inn/wire.h"
+#include "inn/mmap.h"
+
+#include "cnfs.h"
+#include "cnfs-private.h"
+
+/* Temporary until cnfs_mapcntl is handled like mapcntl. Make MS_ASYNC
+ disappear on platforms that don't have it. */
+#ifndef MS_ASYNC
+# define MS_ASYNC 0
+#endif
+
+/* We can give a more descriptive error below about not having largefile support if the platform has EOVERFLOW; on other platforms some other
+ * errno will be used and so we won't know when to give the descriptive
+ * error. Oh well. */
+#ifndef EOVERFLOW
+# define EOVERFLOW 0
+#endif
+
+typedef struct {
+ /**** Stuff to be cleaned up when we're done with the article */
+ char *base; /* Base of mmap()ed art */
+ int len; /* Length of article (and thus
+ mmap()ed art */
+ CYCBUFF *cycbuff; /* pointer to current CYCBUFF */
+ off_t offset; /* offset to current article */
+ bool rollover; /* true if the search is rollovered */
+} PRIV_CNFS;
+
+static char LocalLogName[] = "CNFS-sm";
+static CYCBUFF *cycbufftab = (CYCBUFF *)NULL;
+static METACYCBUFF *metacycbufftab = (METACYCBUFF *)NULL;
+static CNFSEXPIRERULES *metaexprulestab = (CNFSEXPIRERULES *)NULL;
+static long pagesize = 0;
+static int metabuff_update = METACYCBUFF_UPDATE;
+static int refresh_interval = REFRESH_INTERVAL;
+
+static TOKEN CNFSMakeToken(char *cycbuffname, off_t offset,
+ uint32_t cycnum, STORAGECLASS class) {
+ TOKEN token;
+ int32_t int32;
+
+ /*
+ ** XXX We'll assume that TOKENSIZE is 16 bytes and that we divvy it
+ ** up as: 8 bytes for cycbuffname, 4 bytes for offset, 4 bytes
+ ** for cycnum. See also: CNFSBreakToken() for hard-coded constants.
+ */
+ token.type = TOKEN_CNFS;
+ token.class = class;
+ memcpy(token.token, cycbuffname, CNFSMAXCYCBUFFNAME);
+ int32 = htonl(offset / CNFS_BLOCKSIZE);
+ memcpy(&token.token[8], &int32, sizeof(int32));
+ int32 = htonl(cycnum);
+ memcpy(&token.token[12], &int32, sizeof(int32));
+ return token;
+}
+
+/*
+** NOTE: We assume that cycbuffname is 9 bytes long.
+*/
+
+static bool CNFSBreakToken(TOKEN token, char *cycbuffname,
+ off_t *offset, uint32_t *cycnum) {
+ int32_t int32;
+
+ if (cycbuffname == NULL || offset == NULL || cycnum == NULL) {
+ syslog(L_ERROR, "%s: BreakToken: invalid argument: %s",
+ LocalLogName, cycbuffname);
+ SMseterror(SMERR_INTERNAL, "BreakToken: invalid argument");
+ return false;
+ }
+ memcpy(cycbuffname, token.token, CNFSMAXCYCBUFFNAME);
+ *(cycbuffname + CNFSMAXCYCBUFFNAME) = '\0'; /* Just to be paranoid */
+ memcpy(&int32, &token.token[8], sizeof(int32));
+ *offset = (off_t)ntohl(int32) * (off_t)CNFS_BLOCKSIZE;
+ memcpy(&int32, &token.token[12], sizeof(int32));
+ *cycnum = ntohl(int32);
+ return true;
+}
+
+static char hextbl[] = {'0', '1', '2', '3', '4', '5', '6', '7', '8', '9',
+ 'a', 'b', 'c', 'd', 'e', 'f'};
+
+/*
+** CNFSofft2hex -- Given an argument of type off_t, return
+** a static ASCII string representing its value in hexadecimal.
+**
+** If "leadingzeros" is true, the number returned will have leading 0's.
+*/
+
+static char * CNFSofft2hex(off_t offset, bool leadingzeros) {
+ static char buf[24];
+ char *p;
+
+ if (sizeof(off_t) <= 4) {
+ snprintf(buf, sizeof(buf), (leadingzeros) ? "%016lx" : "%lx", offset);
+ } else {
+ int i;
+
+ for (i = 0; i < CNFSLASIZ; i++)
+ buf[i] = '0'; /* Pad with zeros to start */
+ for (i = CNFSLASIZ - 1; i >= 0; i--) {
+ buf[i] = hextbl[offset & 0xf];
+ offset >>= 4;
+ }
+ }
+ if (! leadingzeros) {
+ for (p = buf; *p == '0'; p++)
+ ;
+ if (*p != '\0')
+ return p;
+ else
+ return p - 1; /* We converted a "0" and then bypassed all
+ the zeros */
+ } else
+ return buf;
+}
+
+/*
+** CNFShex2offt -- Given an ASCII string containing a hexadecimal representation
+** of a off_t, return a off_t.
+*/
+
+static off_t CNFShex2offt(char *hex) {
+ if (sizeof(off_t) <= 4) {
+ unsigned long rpofft;
+ /* I'm lazy */
+ sscanf(hex, "%lx", &rpofft);
+ return rpofft;
+ } else {
+ char diff;
+ off_t n = 0;
+
+ for (; *hex != '\0'; hex++) {
+ if (*hex >= '0' && *hex <= '9')
+ diff = '0';
+ else if (*hex >= 'a' && *hex <= 'f')
+ diff = 'a' - 10;
+ else if (*hex >= 'A' && *hex <= 'F')
+ diff = 'A' - 10;
+ else {
+ /*
+ ** We used to have a syslog() message here, but the case
+ ** where we land here because of a ":" happens, er, often.
+ */
+ break;
+ }
+ n += (*hex - diff);
+ if (isalnum((int)*(hex + 1)))
+ n <<= 4;
+ }
+ return n;
+ }
+}
+
+static bool CNFSflushhead(CYCBUFF *cycbuff) {
+ CYCBUFFEXTERN rpx;
+
+ if (!cycbuff->needflush)
+ return true;
+ if (!SMopenmode) {
+ syslog(L_ERROR, "%s: CNFSflushhead: attempted flush whilst read only",
+ LocalLogName);
+ return false;
+ }
+ memset(&rpx, 0, sizeof(CYCBUFFEXTERN));
+ if (cycbuff->magicver == 3) {
+ cycbuff->updated = time(NULL);
+ strncpy(rpx.magic, CNFS_MAGICV3, strlen(CNFS_MAGICV3));
+ strncpy(rpx.name, cycbuff->name, CNFSNASIZ);
+ strncpy(rpx.path, cycbuff->path, CNFSPASIZ);
+ /* Don't use sprintf() directly ... the terminating '\0' causes grief */
+ strncpy(rpx.lena, CNFSofft2hex(cycbuff->len, true), CNFSLASIZ);
+ strncpy(rpx.freea, CNFSofft2hex(cycbuff->free, true), CNFSLASIZ);
+ strncpy(rpx.cyclenuma, CNFSofft2hex(cycbuff->cyclenum, true), CNFSLASIZ);
+ strncpy(rpx.updateda, CNFSofft2hex(cycbuff->updated, true), CNFSLASIZ);
+ strncpy(rpx.metaname, cycbuff->metaname, CNFSNASIZ);
+ strncpy(rpx.orderinmeta, CNFSofft2hex(cycbuff->order, true), CNFSLASIZ);
+ if (cycbuff->currentbuff) {
+ strncpy(rpx.currentbuff, "TRUE", CNFSMASIZ);
+ } else {
+ strncpy(rpx.currentbuff, "FALSE", CNFSMASIZ);
+ }
+ memcpy(cycbuff->bitfield, &rpx, sizeof(CYCBUFFEXTERN));
+ msync(cycbuff->bitfield, cycbuff->minartoffset, MS_ASYNC);
+ cycbuff->needflush = false;
+ } else {
+ syslog(L_ERROR, "%s: CNFSflushhead: bogus magicver for %s: %d",
+ LocalLogName, cycbuff->name, cycbuff->magicver);
+ return false;
+ }
+ return true;
+}
+
+static void CNFSshutdowncycbuff(CYCBUFF *cycbuff) {
+ if (cycbuff == (CYCBUFF *)NULL)
+ return;
+ if (cycbuff->needflush) {
+ syslog(L_NOTICE, "%s: CNFSshutdowncycbuff: flushing %s", LocalLogName, cycbuff->name);
+ CNFSflushhead(cycbuff);
+ }
+ if (cycbuff->bitfield != NULL) {
+ munmap(cycbuff->bitfield, cycbuff->minartoffset);
+ cycbuff->bitfield = NULL;
+ }
+ if (cycbuff->fd >= 0)
+ close(cycbuff->fd);
+ cycbuff->fd = -1;
+}
+
+static void CNFScleancycbuff(void) {
+ CYCBUFF *cycbuff, *nextcycbuff;
+
+ for (cycbuff = cycbufftab; cycbuff != (CYCBUFF *)NULL;) {
+ CNFSshutdowncycbuff(cycbuff);
+ nextcycbuff = cycbuff->next;
+ free(cycbuff);
+ cycbuff = nextcycbuff;
+ }
+ cycbufftab = (CYCBUFF *)NULL;
+}
+
+static void CNFScleanmetacycbuff(void) {
+ METACYCBUFF *metacycbuff, *nextmetacycbuff;
+
+ for (metacycbuff = metacycbufftab; metacycbuff != (METACYCBUFF *)NULL;) {
+ nextmetacycbuff = metacycbuff->next;
+ free(metacycbuff->members);
+ free(metacycbuff->name);
+ free(metacycbuff);
+ metacycbuff = nextmetacycbuff;
+ }
+ metacycbufftab = (METACYCBUFF *)NULL;
+}
+
+static void CNFScleanexpirerule(void) {
+ CNFSEXPIRERULES *metaexprule, *nextmetaexprule;
+
+ for (metaexprule = metaexprulestab; metaexprule != (CNFSEXPIRERULES *)NULL;) {
+ nextmetaexprule = metaexprule->next;
+ free(metaexprule);
+ metaexprule = nextmetaexprule;
+ }
+ metaexprulestab = (CNFSEXPIRERULES *)NULL;
+}
+
+static CYCBUFF *CNFSgetcycbuffbyname(char *name) {
+ CYCBUFF *cycbuff;
+
+ if (name == NULL)
+ return NULL;
+ for (cycbuff = cycbufftab; cycbuff != (CYCBUFF *)NULL; cycbuff = cycbuff->next)
+ if (strcmp(name, cycbuff->name) == 0)
+ return cycbuff;
+ return NULL;
+}
+
+static METACYCBUFF *CNFSgetmetacycbuffbyname(char *name) {
+ METACYCBUFF *metacycbuff;
+
+ if (name == NULL)
+ return NULL;
+ for (metacycbuff = metacycbufftab; metacycbuff != (METACYCBUFF *)NULL; metacycbuff = metacycbuff->next)
+ if (strcmp(name, metacycbuff->name) == 0)
+ return metacycbuff;
+ return NULL;
+}
+
+static void CNFSflushallheads(void) {
+ CYCBUFF *cycbuff;
+
+ for (cycbuff = cycbufftab; cycbuff != (CYCBUFF *)NULL; cycbuff = cycbuff->next) {
+ if (cycbuff->needflush)
+ syslog(L_NOTICE, "%s: CNFSflushallheads: flushing %s", LocalLogName, cycbuff->name);
+ CNFSflushhead(cycbuff);
+ }
+}
+
+/*
+** CNFSReadFreeAndCycle() -- Read from disk the current values of CYCBUFF's
+** free pointer and cycle number. Return 1 on success, 0 otherwise.
+*/
+
+static void CNFSReadFreeAndCycle(CYCBUFF *cycbuff) {
+ CYCBUFFEXTERN rpx;
+ char buf[64];
+
+ memcpy(&rpx, cycbuff->bitfield, sizeof(CYCBUFFEXTERN));
+ /* Sanity checks are not needed since CNFSinit_disks() has already done. */
+ strncpy(buf, rpx.freea, CNFSLASIZ);
+ buf[CNFSLASIZ] = '\0';
+ cycbuff->free = CNFShex2offt(buf);
+ strncpy(buf, rpx.updateda, CNFSLASIZ);
+ buf[CNFSLASIZ] = '\0';
+ cycbuff->updated = CNFShex2offt(buf);
+ strncpy(buf, rpx.cyclenuma, CNFSLASIZ);
+ buf[CNFSLASIZ] = '\0';
+ cycbuff->cyclenum = CNFShex2offt(buf);
+ return;
+}
+
+static bool CNFSparse_part_line(char *l) {
+ char *p;
+ struct stat sb;
+ off_t len, minartoffset;
+ int tonextblock;
+ CYCBUFF *cycbuff, *tmp;
+
+ /* Symbolic cnfs partition name */
+ if ((p = strchr(l, ':')) == NULL || p - l <= 0 || p - l > CNFSMAXCYCBUFFNAME - 1) {
+ syslog(L_ERROR, "%s: bad cycbuff name in line '%s'", LocalLogName, l);
+ return false;
+ }
+ *p = '\0';
+ if (CNFSgetcycbuffbyname(l) != NULL) {
+ *p = ':';
+ syslog(L_ERROR, "%s: duplicate cycbuff name in line '%s'", LocalLogName, l);
+ return false;
+ }
+ cycbuff = xmalloc(sizeof(CYCBUFF));
+ memset(cycbuff->name, '\0', CNFSNASIZ);
+ strlcpy(cycbuff->name, l, CNFSNASIZ);
+ l = ++p;
+
+ /* Path to cnfs partition */
+ if ((p = strchr(l, ':')) == NULL || p - l <= 0 || p - l > CNFSPASIZ - 1) {
+ syslog(L_ERROR, "%s: bad pathname in line '%s'", LocalLogName, l);
+ free(cycbuff);
+ return false;
+ }
+ *p = '\0';
+ memset(cycbuff->path, '\0', CNFSPASIZ);
+ strlcpy(cycbuff->path, l, CNFSPASIZ);
+ if (stat(cycbuff->path, &sb) < 0) {
+ if (errno == EOVERFLOW) {
+ syslog(L_ERROR, "%s: file '%s' : %s, ignoring '%s' cycbuff",
+ LocalLogName, cycbuff->path,
+ "Overflow (probably >2GB without largefile support)",
+ cycbuff->name);
+ } else {
+ syslog(L_ERROR, "%s: file '%s' : %m, ignoring '%s' cycbuff",
+ LocalLogName, cycbuff->path, cycbuff->name);
+ }
+ free(cycbuff);
+ return false;
+ }
+ l = ++p;
+
+ /* Length/size of symbolic partition */
+ len = strtoul(l, NULL, 10) * (off_t)1024; /* This value in KB in decimal */
+ if (S_ISREG(sb.st_mode) && len != sb.st_size) {
+ if (sizeof(CYCBUFFEXTERN) > (size_t) sb.st_size) {
+ syslog(L_NOTICE, "%s: length must be at least '%lu' for '%s' cycbuff(%lu bytes)",
+ LocalLogName, (unsigned long) sizeof(CYCBUFFEXTERN), cycbuff->name,
+ (unsigned long) sb.st_size);
+ free(cycbuff);
+ return false;
+ }
+ }
+ cycbuff->len = len;
+ cycbuff->fd = -1;
+ cycbuff->next = (CYCBUFF *)NULL;
+ cycbuff->needflush = false;
+ cycbuff->bitfield = NULL;
+ /*
+ ** The minimum article offset will be the size of the bitfield itself,
+ ** len / (blocksize * 8), plus however many additional blocks the CYCBUFF
+ ** external header occupies ... then round up to the next block.
+ */
+ minartoffset =
+ cycbuff->len / (CNFS_BLOCKSIZE * 8) + CNFS_BEFOREBITF;
+ tonextblock = CNFS_HDR_PAGESIZE - (minartoffset & (CNFS_HDR_PAGESIZE - 1));
+ cycbuff->minartoffset = minartoffset + tonextblock;
+
+ if (cycbufftab == (CYCBUFF *)NULL)
+ cycbufftab = cycbuff;
+ else {
+ for (tmp = cycbufftab; tmp->next != (CYCBUFF *)NULL; tmp = tmp->next);
+ tmp->next = cycbuff;
+ }
+ /* Done! */
+ return true;
+}
+
+static bool CNFSparse_metapart_line(char *l) {
+ char *p, *cycbuff, *q = l;
+ CYCBUFF *rp;
+ METACYCBUFF *metacycbuff, *tmp;
+
+ /* Symbolic metacycbuff name */
+ if ((p = strchr(l, ':')) == NULL || p - l <= 0) {
+ syslog(L_ERROR, "%s: bad partition name in line '%s'", LocalLogName, l);
+ return false;
+ }
+ *p = '\0';
+ if (CNFSgetmetacycbuffbyname(l) != NULL) {
+ *p = ':';
+ syslog(L_ERROR, "%s: duplicate metabuff name in line '%s'", LocalLogName, l);
+ return false;
+ }
+ metacycbuff = xmalloc(sizeof(METACYCBUFF));
+ metacycbuff->members = (CYCBUFF **)NULL;
+ metacycbuff->count = 0;
+ metacycbuff->name = xstrdup(l);
+ metacycbuff->next = (METACYCBUFF *)NULL;
+ metacycbuff->metamode = INTERLEAVE;
+ l = ++p;
+
+ if ((p = strchr(l, ':')) != NULL) {
+ if (p - l <= 0) {
+ syslog(L_ERROR, "%s: bad mode in line '%s'", LocalLogName, q);
+ return false;
+ }
+ if (strcmp(++p, "INTERLEAVE") == 0)
+ metacycbuff->metamode = INTERLEAVE;
+ else if (strcmp(p, "SEQUENTIAL") == 0)
+ metacycbuff->metamode = SEQUENTIAL;
+ else {
+ syslog(L_ERROR, "%s: unknown mode in line '%s'", LocalLogName, q);
+ return false;
+ }
+ *--p = '\0';
+ }
+ /* Cycbuff list */
+ while ((p = strchr(l, ',')) != NULL && p - l > 0) {
+ *p = '\0';
+ cycbuff = l;
+ l = ++p;
+ if ((rp = CNFSgetcycbuffbyname(cycbuff)) == NULL) {
+ syslog(L_ERROR, "%s: bogus cycbuff '%s' (metacycbuff '%s')",
+ LocalLogName, cycbuff, metacycbuff->name);
+ free(metacycbuff->members);
+ free(metacycbuff->name);
+ free(metacycbuff);
+ return false;
+ }
+ if (metacycbuff->count == 0)
+ metacycbuff->members = xmalloc(sizeof(CYCBUFF *));
+ else
+ metacycbuff->members = xrealloc(metacycbuff->members, (metacycbuff->count + 1) * sizeof(CYCBUFF *));
+ metacycbuff->members[metacycbuff->count++] = rp;
+ }
+ /* Gotta deal with the last cycbuff on the list */
+ cycbuff = l;
+ if ((rp = CNFSgetcycbuffbyname(cycbuff)) == NULL) {
+ syslog(L_ERROR, "%s: bogus cycbuff '%s' (metacycbuff '%s')",
+ LocalLogName, cycbuff, metacycbuff->name);
+ free(metacycbuff->members);
+ free(metacycbuff->name);
+ free(metacycbuff);
+ return false;
+ } else {
+ if (metacycbuff->count == 0)
+ metacycbuff->members = xmalloc(sizeof(CYCBUFF *));
+ else
+ metacycbuff->members = xrealloc(metacycbuff->members, (metacycbuff->count + 1) * sizeof(CYCBUFF *));
+ metacycbuff->members[metacycbuff->count++] = rp;
+ }
+
+ if (metacycbuff->count == 0) {
+ syslog(L_ERROR, "%s: no cycbuffs assigned to cycbuff '%s'",
+ LocalLogName, metacycbuff->name);
+ free(metacycbuff->name);
+ free(metacycbuff);
+ return false;
+ }
+ if (metacycbufftab == (METACYCBUFF *)NULL)
+ metacycbufftab = metacycbuff;
+ else {
+ for (tmp = metacycbufftab; tmp->next != (METACYCBUFF *)NULL; tmp = tmp->next);
+ tmp->next = metacycbuff;
+ }
+ /* DONE! */
+ return true;
+}
+
+static bool CNFSparse_groups_line(void) {
+ METACYCBUFF *mrp;
+ STORAGE_SUB *sub = (STORAGE_SUB *)NULL;
+ CNFSEXPIRERULES *metaexprule, *tmp;
+
+ sub = SMGetConfig(TOKEN_CNFS, sub);
+ for (;sub != (STORAGE_SUB *)NULL; sub = SMGetConfig(TOKEN_CNFS, sub)) {
+ if (sub->options == (char *)NULL) {
+ syslog(L_ERROR, "%s: storage.conf options field is missing",
+ LocalLogName);
+ CNFScleanexpirerule();
+ return false;
+ }
+ if ((mrp = CNFSgetmetacycbuffbyname(sub->options)) == NULL) {
+ syslog(L_ERROR, "%s: storage.conf options field '%s' undefined",
+ LocalLogName, sub->options);
+ CNFScleanexpirerule();
+ return false;
+ }
+ metaexprule = xmalloc(sizeof(CNFSEXPIRERULES));
+ metaexprule->class = sub->class;
+ metaexprule->dest = mrp;
+ metaexprule->next = (CNFSEXPIRERULES *)NULL;
+ if (metaexprulestab == (CNFSEXPIRERULES *)NULL)
+ metaexprulestab = metaexprule;
+ else {
+ for (tmp = metaexprulestab; tmp->next != (CNFSEXPIRERULES *)NULL; tmp = tmp->next);
+ tmp->next = metaexprule;
+ }
+ }
+ /* DONE! */
+ return true;
+}
+
+/*
+** CNFSinit_disks -- Finish initializing cycbufftab
+** Called by "innd" only -- we open (and keep) a read/write
+** file descriptor for each CYCBUFF.
+**
+** Calling this function repeatedly shouldn't cause any harm
+** speed-wise or bug-wise, as long as the caller is accessing the
+** CYCBUFFs _read-only_. If innd calls this function repeatedly,
+** bad things will happen.
+*/
+
+static bool CNFSinit_disks(CYCBUFF *cycbuff) {
+ char buf[64];
+ CYCBUFFEXTERN *rpx;
+ int fd;
+ off_t tmpo;
+ bool oneshot;
+
+ /*
+ ** Discover the state of our cycbuffs. If any of them are in icky shape,
+ ** duck shamelessly & return false.
+ */
+
+ if (cycbuff != (CYCBUFF *)NULL)
+ oneshot = true;
+ else {
+ oneshot = false;
+ cycbuff = cycbufftab;
+ }
+ for (; cycbuff != (CYCBUFF *)NULL; cycbuff = cycbuff->next) {
+ if (strcmp(cycbuff->path, "/dev/null") == 0) {
+ syslog(L_ERROR, "%s: ERROR opening '%s' is not available",
+ LocalLogName, cycbuff->path);
+ return false;
+ }
+ if (cycbuff->fd < 0) {
+ if ((fd = open(cycbuff->path, SMopenmode ? O_RDWR : O_RDONLY)) < 0) {
+ syslog(L_ERROR, "%s: ERROR opening '%s' O_RDONLY : %m",
+ LocalLogName, cycbuff->path);
+ return false;
+ } else {
+ close_on_exec(fd, true);
+ cycbuff->fd = fd;
+ }
+ }
+ errno = 0;
+ cycbuff->bitfield = mmap(NULL, cycbuff->minartoffset,
+ SMopenmode ? (PROT_READ | PROT_WRITE) : PROT_READ,
+ MAP_SHARED, cycbuff->fd, 0);
+ if (cycbuff->bitfield == MAP_FAILED || errno != 0) {
+ syslog(L_ERROR,
+ "%s: CNFSinitdisks: mmap for %s offset %d len %ld failed: %m",
+ LocalLogName, cycbuff->path, 0, (long) cycbuff->minartoffset);
+ cycbuff->bitfield = NULL;
+ return false;
+ }
+
+ /*
+ ** Much of this checking from previous revisions is (probably) bogus
+ ** & buggy & particularly icky & unupdated. Use at your own risk. :-)
+ */
+ rpx = (CYCBUFFEXTERN *)cycbuff->bitfield;
+ if (strncmp(rpx->magic, CNFS_MAGICV3, strlen(CNFS_MAGICV3)) == 0) {
+ cycbuff->magicver = 3;
+ if (strncmp(rpx->name, cycbuff->name, CNFSNASIZ) != 0) {
+ syslog(L_ERROR, "%s: Mismatch 3: read %s for cycbuff %s", LocalLogName,
+ rpx->name, cycbuff->name);
+ return false;
+ }
+ if (strncmp(rpx->path, cycbuff->path, CNFSPASIZ) != 0) {
+ syslog(L_ERROR, "%s: Path mismatch: read %s for cycbuff %s",
+ LocalLogName, rpx->path, cycbuff->path);
+ }
+ strncpy(buf, rpx->lena, CNFSLASIZ);
+ buf[CNFSLASIZ] = '\0';
+ tmpo = CNFShex2offt(buf);
+ if (tmpo != cycbuff->len) {
+ syslog(L_ERROR, "%s: Mismatch: read 0x%s length for cycbuff %s",
+ LocalLogName, CNFSofft2hex(tmpo, false), cycbuff->path);
+ return false;
+ }
+ strncpy(buf, rpx->freea, CNFSLASIZ);
+ buf[CNFSLASIZ] = '\0';
+ cycbuff->free = CNFShex2offt(buf);
+ strncpy(buf, rpx->updateda, CNFSLASIZ);
+ buf[CNFSLASIZ] = '\0';
+ cycbuff->updated = CNFShex2offt(buf);
+ strncpy(buf, rpx->cyclenuma, CNFSLASIZ);
+ buf[CNFSLASIZ] = '\0';
+ cycbuff->cyclenum = CNFShex2offt(buf);
+ strncpy(cycbuff->metaname, rpx->metaname, CNFSLASIZ);
+ strncpy(buf, rpx->orderinmeta, CNFSLASIZ);
+ cycbuff->order = CNFShex2offt(buf);
+ if (strncmp(rpx->currentbuff, "TRUE", CNFSMASIZ) == 0) {
+ cycbuff->currentbuff = true;
+ } else
+ cycbuff->currentbuff = false;
+ } else {
+ syslog(L_NOTICE,
+ "%s: No magic cookie found for cycbuff %s, initializing",
+ LocalLogName, cycbuff->name);
+ cycbuff->magicver = 3;
+ cycbuff->free = cycbuff->minartoffset;
+ cycbuff->updated = 0;
+ cycbuff->cyclenum = 1;
+ cycbuff->currentbuff = true;
+ cycbuff->order = 0; /* to indicate this is newly added cycbuff */
+ cycbuff->needflush = true;
+ memset(cycbuff->metaname, '\0', CNFSLASIZ);
+ if (!CNFSflushhead(cycbuff))
+ return false;
+ }
+ if (oneshot)
+ break;
+ }
+ return true;
+}
+
+static bool CNFS_setcurrent(METACYCBUFF *metacycbuff) {
+ CYCBUFF *cycbuff;
+ int i, currentcycbuff = 0, order = -1;
+ bool foundcurrent = false;
+ for (i = 0 ; i < metacycbuff->count ; i++) {
+ cycbuff = metacycbuff->members[i];
+ if (strncmp(cycbuff->metaname, metacycbuff->name, CNFSNASIZ) != 0) {
+ /* this cycbuff is moved from other metacycbuff , or is new */
+ cycbuff->order = i + 1;
+ cycbuff->currentbuff = false;
+ strncpy(cycbuff->metaname, metacycbuff->name, CNFSLASIZ);
+ cycbuff->needflush = true;
+ continue;
+ }
+ if (foundcurrent == false && cycbuff->currentbuff == true) {
+ currentcycbuff = i;
+ foundcurrent = true;
+ }
+ if (foundcurrent == false || order == -1 || order > cycbuff->order) {
+ /* this cycbuff is a candidate for current cycbuff */
+ currentcycbuff = i;
+ order = cycbuff->order;
+ }
+ if (cycbuff->order != i + 1) {
+ /* cycbuff order seems to be changed */
+ cycbuff->order = i + 1;
+ cycbuff->needflush = true;
+ }
+ }
+ /* If no current cycbuff found (say, all our cycbuffs are new) default to 0 */
+ if (foundcurrent == false) {
+ currentcycbuff = 0;
+ }
+ for (i = 0 ; i < metacycbuff->count ; i++) {
+ cycbuff = metacycbuff->members[i];
+ if (currentcycbuff == i && cycbuff->currentbuff == false) {
+ cycbuff->currentbuff = true;
+ cycbuff->needflush = true;
+ }
+ if (currentcycbuff != i && cycbuff->currentbuff == true) {
+ cycbuff->currentbuff = false;
+ cycbuff->needflush = true;
+ }
+ if (cycbuff->needflush == true && !CNFSflushhead(cycbuff))
+ return false;
+ }
+ metacycbuff->memb_next = currentcycbuff;
+ return true;
+}
+
+/*
+** CNFSread_config() -- Read the cnfs partition/file configuration file.
+**
+** Oh, for the want of Perl! My parser probably shows that I don't use
+** C all that often anymore....
+*/
+
+static bool CNFSread_config(void) {
+ char *path, *config, *from, *to, **ctab = (char **)NULL;
+ int ctab_free = 0; /* Index to next free slot in ctab */
+ int ctab_i;
+ bool metacycbufffound = false;
+ bool cycbuffupdatefound = false;
+ bool refreshintervalfound = false;
+ int update, refresh;
+
+ path = concatpath(innconf->pathetc, _PATH_CYCBUFFCONFIG);
+ config = ReadInFile(path, NULL);
+ if (config == NULL) {
+ syslog(L_ERROR, "%s: cannot read %s", LocalLogName, path);
+ free(config);
+ free(path);
+ return false;
+ }
+ free(path);
+ for (from = to = config; *from; ) {
+ if (*from == '#') { /* Comment line? */
+ while (*from && *from != '\n')
+ from++; /* Skip past it */
+ from++;
+ continue; /* Back to top of loop */
+ }
+ if (*from == '\n') { /* End or just a blank line? */
+ from++;
+ continue; /* Back to top of loop */
+ }
+ if (ctab_free == 0)
+ ctab = xmalloc(sizeof(char *));
+ else
+ ctab = xrealloc(ctab, (ctab_free + 1) * sizeof(char *));
+ /* If we're here, we've got the beginning of a real entry */
+ ctab[ctab_free++] = to = from;
+ while (1) {
+ if (*from && *from == '\\' && *(from + 1) == '\n') {
+ from += 2; /* Skip past backslash+newline */
+ while (*from && isspace((int)*from))
+ from++;
+ continue;
+ }
+ if (*from && *from != '\n')
+ *to++ = *from++;
+ if (*from == '\n') {
+ *to++ = '\0';
+ from++;
+ break;
+ }
+ if (! *from)
+ break;
+ }
+ }
+
+ for (ctab_i = 0; ctab_i < ctab_free; ctab_i++) {
+ if (strncmp(ctab[ctab_i], "cycbuff:", 8) == 0) {
+ if (metacycbufffound) {
+ syslog(L_ERROR, "%s: all cycbuff entries shoud be before metacycbuff entries", LocalLogName);
+ free(config);
+ free(ctab);
+ return false;
+ }
+ if (!CNFSparse_part_line(ctab[ctab_i] + 8)) {
+ free(config);
+ free(ctab);
+ return false;
+ }
+ } else if (strncmp(ctab[ctab_i], "metacycbuff:", 12) == 0) {
+ metacycbufffound = true;
+ if (!CNFSparse_metapart_line(ctab[ctab_i] + 12)) {
+ free(config);
+ free(ctab);
+ return false;
+ }
+ } else if (strncmp(ctab[ctab_i], "cycbuffupdate:", 14) == 0) {
+ if (cycbuffupdatefound) {
+ syslog(L_ERROR, "%s: duplicate cycbuffupdate entries", LocalLogName);
+ free(config);
+ free(ctab);
+ return false;
+ }
+ cycbuffupdatefound = true;
+ update = atoi(ctab[ctab_i] + 14);
+ if (update < 0) {
+ syslog(L_ERROR, "%s: invalid cycbuffupdate", LocalLogName);
+ free(config);
+ free(ctab);
+ return false;
+ }
+ if (update == 0)
+ metabuff_update = METACYCBUFF_UPDATE;
+ else
+ metabuff_update = update;
+ } else if (strncmp(ctab[ctab_i], "refreshinterval:", 16) == 0) {
+ if (refreshintervalfound) {
+ syslog(L_ERROR, "%s: duplicate refreshinterval entries", LocalLogName);
+ free(config);
+ free(ctab);
+ return false;
+ }
+ refreshintervalfound = true;
+ refresh = atoi(ctab[ctab_i] + 16);
+ if (refresh < 0) {
+ syslog(L_ERROR, "%s: invalid refreshinterval", LocalLogName);
+ free(config);
+ free(ctab);
+ return false;
+ }
+ if (refresh == 0)
+ refresh_interval = REFRESH_INTERVAL;
+ else
+ refresh_interval = refresh;
+ } else {
+ syslog(L_ERROR, "%s: Bogus metacycbuff config line '%s' ignored",
+ LocalLogName, ctab[ctab_i]);
+ }
+ }
+ free(config);
+ free(ctab);
+ if (!CNFSparse_groups_line()) {
+ return false;
+ }
+ if (cycbufftab == (CYCBUFF *)NULL) {
+ syslog(L_ERROR, "%s: zero cycbuffs defined", LocalLogName);
+ return false;
+ }
+ if (metacycbufftab == (METACYCBUFF *)NULL) {
+ syslog(L_ERROR, "%s: zero metacycbuffs defined", LocalLogName);
+ return false;
+ }
+ return true;
+}
+
+/* Figure out what page an address is in and flush those pages */
+static void
+cnfs_mapcntl(void *p, size_t length, int flags)
+{
+ char *start, *end;
+
+ start = (char *)((size_t)p & ~(size_t)(pagesize - 1));
+ end = (char *)((size_t)((char *)p + length + pagesize) &
+ ~(size_t)(pagesize - 1));
+ if (flags == MS_INVALIDATE) {
+ msync(start, end - start, flags);
+ } else {
+ static char *sstart, *send;
+
+ /* Don't thrash the system with msync()s - keep the last value
+ * and check each time, only if the pages which we should
+ * flush change actually flush the previous ones. Calling
+ * cnfs_mapcntl(NULL, 0, MS_ASYNC) then flushes the final
+ * piece */
+ if (start != sstart || end != send) {
+ if (sstart != NULL && send != NULL) {
+ msync(sstart, send - sstart, flags);
+ }
+ sstart = start;
+ send = send;
+ }
+ }
+}
+
+/*
+** Bit arithmetic by brute force.
+**
+** XXXYYYXXX WARNING: the code below is not endian-neutral!
+*/
+
+typedef unsigned long ULONG;
+
+static int CNFSUsedBlock(CYCBUFF *cycbuff, off_t offset,
+ bool set_operation, bool setbitvalue) {
+ off_t blocknum;
+ off_t longoffset;
+ int bitoffset; /* From the 'left' side of the long */
+ static int uninitialized = 1;
+ static int longsize = sizeof(long);
+ int i;
+ ULONG bitlong, on, off, mask;
+ static ULONG onarray[64], offarray[64];
+ ULONG *where;
+
+ if (uninitialized) {
+ on = 1;
+ off = on;
+ off ^= ULONG_MAX;
+ for (i = (longsize * 8) - 1; i >= 0; i--) {
+ onarray[i] = on;
+ offarray[i] = off;
+ on <<= 1;
+ off = on;
+ off ^= ULONG_MAX;
+ }
+ uninitialized = 0;
+ }
+
+ /* We allow bit-setting under minartoffset, but it better be false */
+ if ((offset < cycbuff->minartoffset && setbitvalue) ||
+ offset > cycbuff->len) {
+ char bufoff[64], bufmin[64], bufmax[64];
+ SMseterror(SMERR_INTERNAL, NULL);
+ strlcpy(bufoff, CNFSofft2hex(offset, false), sizeof(bufoff));
+ strlcpy(bufmin, CNFSofft2hex(cycbuff->minartoffset, false),
+ sizeof(bufmin));
+ strlcpy(bufmax, CNFSofft2hex(cycbuff->len, false), sizeof(bufmax));
+ syslog(L_ERROR,
+ "%s: CNFSUsedBlock: invalid offset %s, min = %s, max = %s",
+ LocalLogName, bufoff, bufmin, bufmax);
+ return 0;
+ }
+ if (offset % CNFS_BLOCKSIZE != 0) {
+ SMseterror(SMERR_INTERNAL, NULL);
+ syslog(L_ERROR,
+ "%s: CNFSsetusedbitbyrp: offset %s not on %d-byte block boundary",
+ LocalLogName, CNFSofft2hex(offset, false), CNFS_BLOCKSIZE);
+ return 0;
+ }
+ blocknum = offset / CNFS_BLOCKSIZE;
+ longoffset = blocknum / (longsize * 8);
+ bitoffset = blocknum % (longsize * 8);
+ where = (ULONG *)cycbuff->bitfield + (CNFS_BEFOREBITF / longsize)
+ + longoffset;
+ bitlong = *where;
+ if (set_operation) {
+ if (setbitvalue) {
+ mask = onarray[bitoffset];
+ bitlong |= mask;
+ } else {
+ mask = offarray[bitoffset];
+ bitlong &= mask;
+ }
+ *where = bitlong;
+ if (innconf->nfswriter) {
+ cnfs_mapcntl(where, sizeof *where, MS_ASYNC);
+ }
+ return 2; /* XXX Clean up return semantics */
+ }
+ /* It's a read operation */
+ mask = onarray[bitoffset];
+
+/*
+ * return bitlong & mask; doesn't work if sizeof(ulong) > sizeof(int)
+ */
+ if ( bitlong & mask ) return 1; else return 0;
+
+}
+
+static int CNFSArtMayBeHere(CYCBUFF *cycbuff, off_t offset, uint32_t cycnum) {
+ static time_t lastupdate = 0;
+ CYCBUFF *tmp;
+
+ if (SMpreopen && !SMopenmode) {
+ if ((time(NULL) - lastupdate) > refresh_interval) { /* XXX Changed to refresh every 30sec - cmo*/
+ for (tmp = cycbufftab; tmp != (CYCBUFF *)NULL; tmp = tmp->next) {
+ CNFSReadFreeAndCycle(tmp);
+ }
+ lastupdate = time(NULL);
+ } else if (cycnum == cycbuff->cyclenum + 1) { /* rollover ? */
+ CNFSReadFreeAndCycle(cycbuff);
+ }
+ }
+ /*
+ ** The current cycle number may have advanced since the last time we
+ ** checked it, so use a ">=" check instead of "==". Our intent is
+ ** avoid a false negative response, *not* a false positive response.
+ */
+ if (! (cycnum == cycbuff->cyclenum ||
+ (cycnum == cycbuff->cyclenum - 1 && offset > cycbuff->free) ||
+ (cycnum + 1 == 0 && cycbuff->cyclenum == 2 && offset > cycbuff->free))) {
+ /* We've been overwritten */
+ return 0;
+ }
+ return CNFSUsedBlock(cycbuff, offset, false, false);
+}
+
+bool cnfs_init(SMATTRIBUTE *attr) {
+ METACYCBUFF *metacycbuff;
+ CYCBUFF *cycbuff;
+
+ if (attr == NULL) {
+ syslog(L_ERROR, "%s: attr is NULL", LocalLogName);
+ SMseterror(SMERR_INTERNAL, "attr is NULL");
+ return false;
+ }
+ attr->selfexpire = true;
+ attr->expensivestat = false;
+ if (innconf == NULL) {
+ if (!innconf_read(NULL)) {
+ syslog(L_ERROR, "%s: innconf_read failed", LocalLogName);
+ SMseterror(SMERR_INTERNAL, "ReadInnConf() failed");
+ return false;
+ }
+ }
+ if (pagesize == 0) {
+ pagesize = getpagesize();
+ if (pagesize == -1) {
+ syslog(L_ERROR, "%s: getpagesize failed: %m", LocalLogName);
+ SMseterror(SMERR_INTERNAL, "getpagesize failed");
+ pagesize = 0;
+ return false;
+ }
+ if ((pagesize > CNFS_HDR_PAGESIZE) || (CNFS_HDR_PAGESIZE % pagesize)) {
+ syslog(L_ERROR, "%s: CNFS_HDR_PAGESIZE (%d) is not a multiple of pagesize (%ld)", LocalLogName, CNFS_HDR_PAGESIZE, pagesize);
+ SMseterror(SMERR_INTERNAL, "CNFS_HDR_PAGESIZE not multiple of pagesize");
+ return false;
+ }
+ }
+ if (STORAGE_TOKEN_LENGTH < 16) {
+ syslog(L_ERROR, "%s: token length is less than 16 bytes", LocalLogName);
+ SMseterror(SMERR_TOKENSHORT, NULL);
+ return false;
+ }
+
+ if (!CNFSread_config()) {
+ CNFScleancycbuff();
+ CNFScleanmetacycbuff();
+ CNFScleanexpirerule();
+ SMseterror(SMERR_INTERNAL, NULL);
+ return false;
+ }
+ if (!CNFSinit_disks(NULL)) {
+ CNFScleancycbuff();
+ CNFScleanmetacycbuff();
+ CNFScleanexpirerule();
+ SMseterror(SMERR_INTERNAL, NULL);
+ return false;
+ }
+ for (metacycbuff = metacycbufftab; metacycbuff != (METACYCBUFF *)NULL; metacycbuff = metacycbuff->next) {
+ metacycbuff->memb_next = 0;
+ metacycbuff->write_count = 0; /* Let's not forget this */
+ if (metacycbuff->metamode == SEQUENTIAL)
+ /* mark current cycbuff */
+ if (CNFS_setcurrent(metacycbuff) == false) {
+ CNFScleancycbuff();
+ CNFScleanmetacycbuff();
+ CNFScleanexpirerule();
+ SMseterror(SMERR_INTERNAL, NULL);
+ return false;
+ }
+ }
+ if (!SMpreopen) {
+ for (cycbuff = cycbufftab; cycbuff != (CYCBUFF *)NULL; cycbuff = cycbuff->next) {
+ CNFSshutdowncycbuff(cycbuff);
+ }
+ }
+ return true;
+}
+
+TOKEN cnfs_store(const ARTHANDLE article, const STORAGECLASS class) {
+ TOKEN token;
+ CYCBUFF *cycbuff = NULL;
+ METACYCBUFF *metacycbuff = NULL;
+ int i;
+ static char buf[1024];
+ static char alignbuf[CNFS_BLOCKSIZE];
+ char *artcycbuffname;
+ off_t artoffset, middle;
+ uint32_t artcyclenum;
+ CNFSARTHEADER cah;
+ static struct iovec *iov;
+ static int iovcnt;
+ int tonextblock;
+ CNFSEXPIRERULES *metaexprule;
+ off_t left;
+ size_t totlen;
+
+ for (metaexprule = metaexprulestab; metaexprule != (CNFSEXPIRERULES *)NULL; metaexprule = metaexprule->next) {
+ if (metaexprule->class == class)
+ break;
+ }
+ if (metaexprule == (CNFSEXPIRERULES *)NULL) {
+ SMseterror(SMERR_INTERNAL, "no rules match");
+ syslog(L_ERROR, "%s: no matches for group '%s'",
+ LocalLogName, buf);
+ token.type = TOKEN_EMPTY;
+ return token;
+ }
+ metacycbuff = metaexprule->dest;
+
+ cycbuff = metacycbuff->members[metacycbuff->memb_next];
+ if (cycbuff == NULL) {
+ SMseterror(SMERR_INTERNAL, "no cycbuff found");
+ syslog(L_ERROR, "%s: no cycbuff found for %d", LocalLogName, metacycbuff->memb_next);
+ token.type = TOKEN_EMPTY;
+ return token;
+ } else if (!SMpreopen && !CNFSinit_disks(cycbuff)) {
+ SMseterror(SMERR_INTERNAL, "cycbuff initialization fail");
+ syslog(L_ERROR, "%s: cycbuff '%s' initialization fail", LocalLogName, cycbuff->name);
+ token.type = TOKEN_EMPTY;
+ return token;
+ }
+
+ /* cycbuff->free should have already been aligned by the last write, but
+ realign it just to be sure. */
+ tonextblock = CNFS_BLOCKSIZE - (cycbuff->free & (CNFS_BLOCKSIZE - 1));
+ if (tonextblock != CNFS_BLOCKSIZE)
+ cycbuff->free += tonextblock;
+
+ /* Article too big? */
+ if (cycbuff->len - cycbuff->free < CNFS_BLOCKSIZE + 1)
+ left = 0;
+ else
+ left = cycbuff->len - cycbuff->free - CNFS_BLOCKSIZE - 1;
+ if (article.len > left) {
+ for (middle = cycbuff->free ;middle < cycbuff->len - CNFS_BLOCKSIZE - 1;
+ middle += CNFS_BLOCKSIZE) {
+ CNFSUsedBlock(cycbuff, middle, true, false);
+ }
+ if (innconf->nfswriter) {
+ cnfs_mapcntl(NULL, 0, MS_ASYNC);
+ }
+ cycbuff->free = cycbuff->minartoffset;
+ cycbuff->cyclenum++;
+ if (cycbuff->cyclenum == 0)
+ cycbuff->cyclenum += 2; /* cnfs_next() needs this */
+ cycbuff->needflush = true;
+ if (metacycbuff->metamode == INTERLEAVE) {
+ CNFSflushhead(cycbuff); /* Flush, just for giggles */
+ syslog(L_NOTICE, "%s: cycbuff %s rollover to cycle 0x%x... remain calm",
+ LocalLogName, cycbuff->name, cycbuff->cyclenum);
+ } else {
+ /* SEQUENTIAL */
+ cycbuff->currentbuff = false;
+ CNFSflushhead(cycbuff); /* Flush, just for giggles */
+ if (!SMpreopen) CNFSshutdowncycbuff(cycbuff);
+ metacycbuff->memb_next = (metacycbuff->memb_next + 1) % metacycbuff->count;
+ cycbuff = metacycbuff->members[metacycbuff->memb_next];
+ if (!SMpreopen && !CNFSinit_disks(cycbuff)) {
+ SMseterror(SMERR_INTERNAL, "cycbuff initialization fail");
+ syslog(L_ERROR, "%s: cycbuff '%s' initialization fail", LocalLogName, cycbuff->name);
+ token.type = TOKEN_EMPTY;
+ return token;
+ }
+ syslog(L_NOTICE, "%s: metacycbuff %s cycbuff is moved to %s remain calm",
+ LocalLogName, metacycbuff->name, cycbuff->name);
+ cycbuff->currentbuff = true;
+ cycbuff->needflush = true;
+ CNFSflushhead(cycbuff); /* Flush, just for giggles */
+ }
+ }
+
+ /* Ah, at least we know all three important data */
+ artcycbuffname = cycbuff->name;
+ artoffset = cycbuff->free;
+ artcyclenum = cycbuff->cyclenum;
+
+ memset(&cah, 0, sizeof(cah));
+ cah.size = htonl(article.len);
+ if (article.arrived == (time_t)0)
+ cah.arrived = htonl(time(NULL));
+ else
+ cah.arrived = htonl(article.arrived);
+ cah.class = class;
+
+ if (lseek(cycbuff->fd, artoffset, SEEK_SET) < 0) {
+ SMseterror(SMERR_INTERNAL, "lseek failed");
+ syslog(L_ERROR, "%s: lseek failed for '%s' offset 0x%s: %m",
+ LocalLogName, cycbuff->name, CNFSofft2hex(artoffset, false));
+ token.type = TOKEN_EMPTY;
+ if (!SMpreopen) CNFSshutdowncycbuff(cycbuff);
+ return token;
+ }
+ if (iovcnt == 0) {
+ iov = xmalloc((article.iovcnt + 2) * sizeof(struct iovec));
+ iovcnt = article.iovcnt + 2;
+ } else if (iovcnt < article.iovcnt + 2) {
+ iov = xrealloc(iov, (article.iovcnt + 2) * sizeof(struct iovec));
+ iovcnt = article.iovcnt + 2;
+ }
+ iov[0].iov_base = (char *) &cah;
+ iov[0].iov_len = sizeof(cah);
+ totlen = iov[0].iov_len;
+ for (i = 1; i <= article.iovcnt; i++) {
+ iov[i].iov_base = article.iov[i-1].iov_base;
+ iov[i].iov_len = article.iov[i-1].iov_len;
+ totlen += iov[i].iov_len;
+ }
+ if ((totlen & (CNFS_BLOCKSIZE - 1)) != 0) {
+ /* Want to xwritev an exact multiple of CNFS_BLOCKSIZE */
+ iov[i].iov_base = alignbuf;
+ iov[i].iov_len = CNFS_BLOCKSIZE - (totlen & (CNFS_BLOCKSIZE - 1));
+ totlen += iov[i].iov_len;
+ i++;
+ }
+ if (xwritev(cycbuff->fd, iov, i) < 0) {
+ SMseterror(SMERR_INTERNAL, "cnfs_store() xwritev() failed");
+ syslog(L_ERROR,
+ "%s: cnfs_store xwritev failed for '%s' offset 0x%s: %m",
+ LocalLogName, artcycbuffname, CNFSofft2hex(artoffset, false));
+ token.type = TOKEN_EMPTY;
+ if (!SMpreopen) CNFSshutdowncycbuff(cycbuff);
+ return token;
+ }
+ cycbuff->needflush = true;
+
+ /* Now that the article is written, advance the free pointer & flush */
+ cycbuff->free += totlen;
+
+ /*
+ ** If cycbuff->free > cycbuff->len, don't worry. The next cnfs_store()
+ ** will detect the situation & wrap around correctly.
+ */
+ if (metacycbuff->metamode == INTERLEAVE)
+ metacycbuff->memb_next = (metacycbuff->memb_next + 1) % metacycbuff->count;
+ if (++metacycbuff->write_count % metabuff_update == 0) {
+ for (i = 0; i < metacycbuff->count; i++) {
+ CNFSflushhead(metacycbuff->members[i]);
+ }
+ }
+ CNFSUsedBlock(cycbuff, artoffset, true, true);
+ for (middle = artoffset + CNFS_BLOCKSIZE; middle < cycbuff->free;
+ middle += CNFS_BLOCKSIZE) {
+ CNFSUsedBlock(cycbuff, middle, true, false);
+ }
+ if (innconf->nfswriter) {
+ cnfs_mapcntl(NULL, 0, MS_ASYNC);
+ }
+ if (!SMpreopen) CNFSshutdowncycbuff(cycbuff);
+ return CNFSMakeToken(artcycbuffname, artoffset, artcyclenum, class);
+}
+
+ARTHANDLE *cnfs_retrieve(const TOKEN token, const RETRTYPE amount) {
+ char cycbuffname[9];
+ off_t offset;
+ uint32_t cycnum;
+ CYCBUFF *cycbuff;
+ ARTHANDLE *art;
+ CNFSARTHEADER cah;
+ PRIV_CNFS *private;
+ char *p;
+ long pagefudge;
+ off_t mmapoffset;
+ static TOKEN ret_token;
+ static bool nomessage = false;
+ int plusoffset = 0;
+
+ if (token.type != TOKEN_CNFS) {
+ SMseterror(SMERR_INTERNAL, NULL);
+ return NULL;
+ }
+ if (! CNFSBreakToken(token, cycbuffname, &offset, &cycnum)) {
+ /* SMseterror() should have already been called */
+ return NULL;
+ }
+ if ((cycbuff = CNFSgetcycbuffbyname(cycbuffname)) == NULL) {
+ SMseterror(SMERR_NOENT, NULL);
+ if (!nomessage) {
+ syslog(L_ERROR, "%s: cnfs_retrieve: token %s: bogus cycbuff name: %s:0x%s:%d",
+ LocalLogName, TokenToText(token), cycbuffname, CNFSofft2hex(offset, false), cycnum);
+ nomessage = true;
+ }
+ return NULL;
+ }
+ if (!SMpreopen && !CNFSinit_disks(cycbuff)) {
+ SMseterror(SMERR_INTERNAL, "cycbuff initialization fail");
+ syslog(L_ERROR, "%s: cycbuff '%s' initialization fail", LocalLogName, cycbuff->name);
+ return NULL;
+ }
+ if (! CNFSArtMayBeHere(cycbuff, offset, cycnum)) {
+ SMseterror(SMERR_NOENT, NULL);
+ if (!SMpreopen) CNFSshutdowncycbuff(cycbuff);
+ return NULL;
+ }
+
+ art = xmalloc(sizeof(ARTHANDLE));
+ art->type = TOKEN_CNFS;
+ if (amount == RETR_STAT) {
+ art->data = NULL;
+ art->len = 0;
+ art->private = NULL;
+ ret_token = token;
+ art->token = &ret_token;
+ if (!SMpreopen) CNFSshutdowncycbuff(cycbuff);
+ return art;
+ }
+ /*
+ ** Because we don't know the length of the article (yet), we'll
+ ** just mmap() a chunk of memory which is guaranteed to be larger
+ ** than the largest article can be.
+ ** XXX Because the max article size can be changed, we could get into hot
+ ** XXX water here. So, to be safe, we double MAX_ART_SIZE and add enough
+ ** XXX extra for the pagesize fudge factor and CNFSARTHEADER structure.
+ */
+ if (pread(cycbuff->fd, &cah, sizeof(cah), offset) != sizeof(cah)) {
+ SMseterror(SMERR_UNDEFINED, "read failed");
+ syslog(L_ERROR, "%s: could not read token %s %s:0x%s:%d: %m",
+ LocalLogName, TokenToText(token), cycbuffname, CNFSofft2hex(offset, false), cycnum);
+ free(art);
+ if (!SMpreopen) CNFSshutdowncycbuff(cycbuff);
+ return NULL;
+ }
+#ifdef OLD_CNFS
+ if(cah.size == htonl(0x1234) && ntohl(cah.arrived) < time(NULL)-10*365*24*3600) {
+ oldCNFSARTHEADER cahh;
+ *(CNFSARTHEADER *)&cahh = cah;
+ if(pread(cycbuff->fd, ((char *)&cahh)+sizeof(CNFSARTHEADER), sizeof(oldCNFSARTHEADER)-sizeof(CNFSARTHEADER), offset+sizeof(cah)) != sizeof(oldCNFSARTHEADER)-sizeof(CNFSARTHEADER)) {
+ SMseterror(SMERR_UNDEFINED, "read2 failed");
+ syslog(L_ERROR, "%s: could not read2 token %s %s:0x%s:%ld: %m",
+ LocalLogName, TokenToText(token), cycbuffname,
+ CNFSofft2hex(offset, false), cycnum);
+ free(art);
+ if (!SMpreopen) CNFSshutdowncycbuff(cycbuff);
+ return NULL;
+ }
+ cah.size = cahh.size;
+ cah.arrived = htonl(time(NULL));
+ cah.class = 0;
+ plusoffset = sizeof(oldCNFSARTHEADER)-sizeof(CNFSARTHEADER);
+ }
+#endif /* OLD_CNFS */
+ if (offset > cycbuff->len - CNFS_BLOCKSIZE - (off_t) ntohl(cah.size) - 1) {
+ if (!SMpreopen) {
+ SMseterror(SMERR_UNDEFINED, "CNFSARTHEADER size overflow");
+ syslog(L_ERROR, "%s: could not match article size token %s %s:0x%s:%d: %d",
+ LocalLogName, TokenToText(token), cycbuffname, CNFSofft2hex(offset, false), cycnum, ntohl(cah.size));
+ free(art);
+ CNFSshutdowncycbuff(cycbuff);
+ return NULL;
+ }
+ CNFSReadFreeAndCycle(cycbuff);
+ if (offset > cycbuff->len - CNFS_BLOCKSIZE - (off_t) ntohl(cah.size) - 1) {
+ SMseterror(SMERR_UNDEFINED, "CNFSARTHEADER size overflow");
+ syslog(L_ERROR, "%s: could not match article size token %s %s:0x%s:%d: %d",
+ LocalLogName, TokenToText(token), cycbuffname, CNFSofft2hex(offset, false), cycnum, ntohl(cah.size));
+ free(art);
+ return NULL;
+ }
+ }
+ /* checking the bitmap to ensure cah.size is not broken was dropped */
+ if (innconf->cnfscheckfudgesize != 0 && innconf->maxartsize != 0 &&
+ (ntohl(cah.size) > (size_t) innconf->maxartsize + innconf->cnfscheckfudgesize)) {
+ char buf1[24];
+ strlcpy(buf1, CNFSofft2hex(cycbuff->free, false), sizeof(buf1));
+ SMseterror(SMERR_UNDEFINED, "CNFSARTHEADER fudge size overflow");
+ syslog(L_ERROR, "%s: fudge size overflows bitmaps %s %s:0x%s: %u",
+ LocalLogName, TokenToText(token), cycbuffname, buf1, ntohl(cah.size));
+ if (!SMpreopen) CNFSshutdowncycbuff(cycbuff);
+ free(art);
+ return NULL;
+ }
+ private = xmalloc(sizeof(PRIV_CNFS));
+ art->private = (void *)private;
+ art->arrived = ntohl(cah.arrived);
+ offset += sizeof(cah) + plusoffset;
+ if (innconf->articlemmap) {
+ pagefudge = offset % pagesize;
+ mmapoffset = offset - pagefudge;
+ private->len = pagefudge + ntohl(cah.size);
+ if ((private->base = mmap(NULL, private->len, PROT_READ,
+ MAP_SHARED, cycbuff->fd, mmapoffset)) == MAP_FAILED) {
+ SMseterror(SMERR_UNDEFINED, "mmap failed");
+ syslog(L_ERROR, "%s: could not mmap token %s %s:0x%s:%d: %m",
+ LocalLogName, TokenToText(token), cycbuffname, CNFSofft2hex(offset, false), cycnum);
+ free(art->private);
+ free(art);
+ if (!SMpreopen) CNFSshutdowncycbuff(cycbuff);
+ return NULL;
+ }
+ mmap_invalidate(private->base, private->len);
+ if (amount == RETR_ALL)
+ madvise(private->base, private->len, MADV_WILLNEED);
+ else
+ madvise(private->base, private->len, MADV_SEQUENTIAL);
+ } else {
+ private->base = xmalloc(ntohl(cah.size));
+ pagefudge = 0;
+ if (pread(cycbuff->fd, private->base, ntohl(cah.size), offset) < 0) {
+ SMseterror(SMERR_UNDEFINED, "read failed");
+ syslog(L_ERROR, "%s: could not read token %s %s:0x%s:%d: %m",
+ LocalLogName, TokenToText(token), cycbuffname, CNFSofft2hex(offset, false), cycnum);
+ free(private->base);
+ free(art->private);
+ free(art);
+ if (!SMpreopen) CNFSshutdowncycbuff(cycbuff);
+ return NULL;
+ }
+ }
+ ret_token = token;
+ art->token = &ret_token;
+ art->len = ntohl(cah.size);
+ if (amount == RETR_ALL) {
+ art->data = innconf->articlemmap ? private->base + pagefudge : private->base;
+ if (!SMpreopen) CNFSshutdowncycbuff(cycbuff);
+ return art;
+ }
+ if ((p = wire_findbody(innconf->articlemmap ? private->base + pagefudge : private->base, art->len)) == NULL) {
+ SMseterror(SMERR_NOBODY, NULL);
+ if (innconf->articlemmap)
+ munmap(private->base, private->len);
+ else
+ free(private->base);
+ free(art->private);
+ free(art);
+ if (!SMpreopen) CNFSshutdowncycbuff(cycbuff);
+ return NULL;
+ }
+ if (amount == RETR_HEAD) {
+ if (innconf->articlemmap) {
+ art->data = private->base + pagefudge;
+ art->len = p - private->base - pagefudge;
+ } else {
+ art->data = private->base;
+ art->len = p - private->base;
+ }
+ if (!SMpreopen) CNFSshutdowncycbuff(cycbuff);
+ return art;
+ }
+ if (amount == RETR_BODY) {
+ art->data = p;
+ if (innconf->articlemmap)
+ art->len = art->len - (p - private->base - pagefudge);
+ else
+ art->len = art->len - (p - private->base);
+ if (!SMpreopen) CNFSshutdowncycbuff(cycbuff);
+ return art;
+ }
+ SMseterror(SMERR_UNDEFINED, "Invalid retrieve request");
+ if (innconf->articlemmap)
+ munmap(private->base, private->len);
+ else
+ free(private->base);
+ free(art->private);
+ free(art);
+ if (!SMpreopen) CNFSshutdowncycbuff(cycbuff);
+ return NULL;
+}
+
+void cnfs_freearticle(ARTHANDLE *article) {
+ PRIV_CNFS *private;
+
+ if (!article)
+ return;
+
+ if (article->private) {
+ private = (PRIV_CNFS *)article->private;
+ if (innconf->articlemmap)
+ munmap(private->base, private->len);
+ else
+ free(private->base);
+ free(private);
+ }
+ free(article);
+}
+
+bool cnfs_cancel(TOKEN token) {
+ char cycbuffname[9];
+ off_t offset;
+ uint32_t cycnum;
+ CYCBUFF *cycbuff;
+
+ if (token.type != TOKEN_CNFS) {
+ SMseterror(SMERR_INTERNAL, NULL);
+ return false;
+ }
+ if (! CNFSBreakToken(token, cycbuffname, &offset, &cycnum)) {
+ SMseterror(SMERR_INTERNAL, NULL);
+ /* SMseterror() should have already been called */
+ return false;
+ }
+ if ((cycbuff = CNFSgetcycbuffbyname(cycbuffname)) == NULL) {
+ SMseterror(SMERR_INTERNAL, "bogus cycbuff name");
+ return false;
+ }
+ if (!SMpreopen && !CNFSinit_disks(cycbuff)) {
+ SMseterror(SMERR_INTERNAL, "cycbuff initialization fail");
+ syslog(L_ERROR, "%s: cycbuff '%s' initialization fail", LocalLogName, cycbuff->name);
+ return false;
+ }
+ if (! (cycnum == cycbuff->cyclenum ||
+ (cycnum == cycbuff->cyclenum - 1 && offset > cycbuff->free) ||
+ (cycnum + 1 == 0 && cycbuff->cyclenum == 2 && offset > cycbuff->free))) {
+ SMseterror(SMERR_NOENT, NULL);
+ if (!SMpreopen) CNFSshutdowncycbuff(cycbuff);
+ return false;
+ }
+ if (CNFSUsedBlock(cycbuff, offset, false, false) == 0) {
+ SMseterror(SMERR_NOENT, NULL);
+ if (!SMpreopen) CNFSshutdowncycbuff(cycbuff);
+ return false;
+ }
+ CNFSUsedBlock(cycbuff, offset, true, false);
+ if (innconf->nfswriter) {
+ cnfs_mapcntl(NULL, 0, MS_ASYNC);
+ }
+ if (!SMpreopen) CNFSshutdowncycbuff(cycbuff);
+ return true;
+}
+
+ARTHANDLE *cnfs_next(const ARTHANDLE *article, const RETRTYPE amount) {
+ ARTHANDLE *art;
+ CYCBUFF *cycbuff;
+ PRIV_CNFS priv, *private;
+ off_t middle = 0, limit;
+ CNFSARTHEADER cah;
+ off_t offset;
+ long pagefudge, blockfudge;
+ static TOKEN token;
+ int tonextblock;
+ off_t mmapoffset;
+ char *p;
+ int plusoffset = 0;
+
+ if (article == (ARTHANDLE *)NULL) {
+ if ((cycbuff = cycbufftab) == (CYCBUFF *)NULL)
+ return (ARTHANDLE *)NULL;
+ priv.offset = 0;
+ priv.rollover = false;
+ } else {
+ priv = *(PRIV_CNFS *)article->private;
+ free(article->private);
+ free((void *)article);
+ if (innconf->articlemmap)
+ munmap(priv.base, priv.len);
+ else {
+ /* In the case we return art->data = NULL, we
+ * must not free an already stale pointer.
+ -mibsoft@mibsoftware.com
+ */
+ if (priv.base) {
+ free(priv.base);
+ priv.base = 0;
+ }
+ }
+ cycbuff = priv.cycbuff;
+ }
+
+ for (;cycbuff != (CYCBUFF *)NULL;
+ cycbuff = cycbuff->next,
+ priv.offset = 0) {
+
+ if (!SMpreopen && !CNFSinit_disks(cycbuff)) {
+ SMseterror(SMERR_INTERNAL, "cycbuff initialization fail");
+ continue;
+ }
+ if (priv.rollover && priv.offset >= cycbuff->free) {
+ priv.offset = 0;
+ if (!SMpreopen) CNFSshutdowncycbuff(cycbuff);
+ continue;
+ }
+ if (priv.offset == 0) {
+ if (cycbuff->cyclenum == 1) {
+ priv.offset = cycbuff->minartoffset;
+ priv.rollover = true;
+ } else {
+ priv.offset = cycbuff->free;
+ priv.rollover = false;
+ }
+ }
+ if (!priv.rollover) {
+ for (middle = priv.offset ;middle < cycbuff->len - CNFS_BLOCKSIZE - 1;
+ middle += CNFS_BLOCKSIZE) {
+ if (CNFSUsedBlock(cycbuff, middle, false, false) != 0)
+ break;
+ }
+ if (middle >= cycbuff->len - CNFS_BLOCKSIZE - 1) {
+ priv.rollover = true;
+ middle = cycbuff->minartoffset;
+ }
+ break;
+ } else {
+ for (middle = priv.offset ;middle < cycbuff->free;
+ middle += CNFS_BLOCKSIZE) {
+ if (CNFSUsedBlock(cycbuff, middle, false, false) != 0)
+ break;
+ }
+ if (middle >= cycbuff->free) {
+ middle = 0;
+ if (!SMpreopen) CNFSshutdowncycbuff(cycbuff);
+ continue;
+ } else
+ break;
+ }
+ }
+ if (cycbuff == (CYCBUFF *)NULL)
+ return (ARTHANDLE *)NULL;
+
+ offset = middle;
+ if (pread(cycbuff->fd, &cah, sizeof(cah), offset) != sizeof(cah)) {
+ if (!SMpreopen) CNFSshutdowncycbuff(cycbuff);
+ return (ARTHANDLE *)NULL;
+ }
+#ifdef OLD_CNFS
+ if(cah.size == htonl(0x1234) && ntohl(cah.arrived) < time(NULL)-10*365*24*3600) {
+ oldCNFSARTHEADER cahh;
+ *(CNFSARTHEADER *)&cahh = cah;
+ if(pread(cycbuff->fd, ((char *)&cahh)+sizeof(CNFSARTHEADER), sizeof(oldCNFSARTHEADER)-sizeof(CNFSARTHEADER), offset+sizeof(cah)) != sizeof(oldCNFSARTHEADER)-sizeof(CNFSARTHEADER)) {
+ if (!SMpreopen) CNFSshutdowncycbuff(cycbuff);
+ return (ARTHANDLE *)NULL;
+ }
+ cah.size = cahh.size;
+ cah.arrived = htonl(time(NULL));
+ cah.class = 0;
+ plusoffset = sizeof(oldCNFSARTHEADER)-sizeof(CNFSARTHEADER);
+ }
+#endif /* OLD_CNFS */
+ art = xmalloc(sizeof(ARTHANDLE));
+ private = xmalloc(sizeof(PRIV_CNFS));
+ art->private = (void *)private;
+ art->type = TOKEN_CNFS;
+ *private = priv;
+ private->cycbuff = cycbuff;
+ private->offset = middle;
+ if (cycbuff->len - cycbuff->free < (off_t) ntohl(cah.size) + CNFS_BLOCKSIZE + 1) {
+ private->offset += CNFS_BLOCKSIZE;
+ art->data = NULL;
+ art->len = 0;
+ art->token = NULL;
+ if (!SMpreopen) CNFSshutdowncycbuff(cycbuff);
+ return art;
+ }
+ /* check the bitmap to ensure cah.size is not broken */
+ blockfudge = (sizeof(cah) + plusoffset + ntohl(cah.size)) % CNFS_BLOCKSIZE;
+ limit = private->offset + sizeof(cah) + plusoffset + ntohl(cah.size) - blockfudge + CNFS_BLOCKSIZE;
+ if (offset < cycbuff->free) {
+ for (middle = offset + CNFS_BLOCKSIZE; (middle < cycbuff->free) && (middle < limit);
+ middle += CNFS_BLOCKSIZE) {
+ if (CNFSUsedBlock(cycbuff, middle, false, false) != 0)
+ /* Bitmap set. This article assumes to be broken */
+ break;
+ }
+ if ((middle > cycbuff->free) || (middle != limit)) {
+ private->offset = middle;
+ art->data = NULL;
+ art->len = 0;
+ art->token = NULL;
+ if (!SMpreopen) CNFSshutdowncycbuff(cycbuff);
+ return art;
+ }
+ } else {
+ for (middle = offset + CNFS_BLOCKSIZE; (middle < cycbuff->len) && (middle < limit);
+ middle += CNFS_BLOCKSIZE) {
+ if (CNFSUsedBlock(cycbuff, middle, false, false) != 0)
+ /* Bitmap set. This article assumes to be broken */
+ break;
+ }
+ if ((middle >= cycbuff->len) || (middle != limit)) {
+ private->offset = middle;
+ art->data = NULL;
+ art->len = 0;
+ art->token = NULL;
+ if (!SMpreopen) CNFSshutdowncycbuff(cycbuff);
+ return art;
+ }
+ }
+ if (innconf->cnfscheckfudgesize != 0 && innconf->maxartsize != 0 &&
+ ((off_t) ntohl(cah.size) > innconf->maxartsize + innconf->cnfscheckfudgesize)) {
+ art->data = NULL;
+ art->len = 0;
+ art->token = NULL;
+ private->base = 0;
+ if (!SMpreopen) CNFSshutdowncycbuff(cycbuff);
+ return art;
+ }
+
+ private->offset += (off_t) ntohl(cah.size) + sizeof(cah) + plusoffset;
+ tonextblock = CNFS_BLOCKSIZE - (private->offset & (CNFS_BLOCKSIZE - 1));
+ private->offset += (off_t) tonextblock;
+ art->arrived = ntohl(cah.arrived);
+ token = CNFSMakeToken(cycbuff->name, offset, (offset > cycbuff->free) ? cycbuff->cyclenum - 1 : cycbuff->cyclenum, cah.class);
+ art->token = &token;
+ offset += sizeof(cah) + plusoffset;
+ if (innconf->articlemmap) {
+ pagefudge = offset % pagesize;
+ mmapoffset = offset - pagefudge;
+ private->len = pagefudge + ntohl(cah.size);
+ if ((private->base = mmap(0, private->len, PROT_READ,
+ MAP_SHARED, cycbuff->fd, mmapoffset)) == MAP_FAILED) {
+ art->data = NULL;
+ art->len = 0;
+ art->token = NULL;
+ if (!SMpreopen) CNFSshutdowncycbuff(cycbuff);
+ return art;
+ }
+ mmap_invalidate(private->base, private->len);
+ madvise(private->base, private->len, MADV_SEQUENTIAL);
+ } else {
+ private->base = xmalloc(ntohl(cah.size));
+ pagefudge = 0;
+ if (pread(cycbuff->fd, private->base, ntohl(cah.size), offset) < 0) {
+ art->data = NULL;
+ art->len = 0;
+ art->token = NULL;
+ if (!SMpreopen) CNFSshutdowncycbuff(cycbuff);
+ free(private->base);
+ private->base = 0;
+ return art;
+ }
+ }
+ art->len = ntohl(cah.size);
+ if (amount == RETR_ALL) {
+ art->data = innconf->articlemmap ? private->base + pagefudge : private->base;
+ if (!SMpreopen) CNFSshutdowncycbuff(cycbuff);
+ return art;
+ }
+ if ((p = wire_findbody(innconf->articlemmap ? private->base + pagefudge : private->base, art->len)) == NULL) {
+ art->data = NULL;
+ art->len = 0;
+ art->token = NULL;
+ if (!SMpreopen) CNFSshutdowncycbuff(cycbuff);
+ return art;
+ }
+ if (amount == RETR_HEAD) {
+ if (innconf->articlemmap) {
+ art->data = private->base + pagefudge;
+ art->len = p - private->base - pagefudge;
+ } else {
+ art->data = private->base;
+ art->len = p - private->base;
+ }
+ if (!SMpreopen) CNFSshutdowncycbuff(cycbuff);
+ return art;
+ }
+ if (amount == RETR_BODY) {
+ art->data = p;
+ if (innconf->articlemmap)
+ art->len = art->len - (p - private->base - pagefudge);
+ else
+ art->len = art->len - (p - private->base);
+ if (!SMpreopen) CNFSshutdowncycbuff(cycbuff);
+ return art;
+ }
+ art->data = NULL;
+ art->len = 0;
+ art->token = NULL;
+ if (!SMpreopen) CNFSshutdowncycbuff(cycbuff);
+ return art;
+}
+
+bool cnfs_ctl(PROBETYPE type, TOKEN *token UNUSED, void *value) {
+ struct artngnum *ann;
+
+ switch (type) {
+ case SMARTNGNUM:
+ if ((ann = (struct artngnum *)value) == NULL)
+ return false;
+ /* make SMprobe() call cnfs_retrieve() */
+ ann->artnum = 0;
+ return true;
+ default:
+ return false;
+ }
+}
+
+bool cnfs_flushcacheddata(FLUSHTYPE type) {
+ if (type == SM_ALL || type == SM_HEAD)
+ CNFSflushallheads();
+ return true;
+}
+
+void
+cnfs_printfiles(FILE *file, TOKEN token, char **xref UNUSED,
+ int ngroups UNUSED)
+{
+ fprintf(file, "%s\n", TokenToText(token));
+}
+
+void cnfs_shutdown(void) {
+ CNFScleancycbuff();
+ CNFScleanmetacycbuff();
+ CNFScleanexpirerule();
+}
--- /dev/null
+/* $Id: cnfs.h 4266 2001-01-04 06:01:36Z rra $
+**
+** cyclic news file system header
+*/
+
+#ifndef __CNFS_H__
+#define __CNFS_H__
+
+bool cnfs_init(SMATTRIBUTE *attr);
+TOKEN cnfs_store(const ARTHANDLE article, const STORAGECLASS class);
+ARTHANDLE *cnfs_retrieve(const TOKEN token, const RETRTYPE amount);
+ARTHANDLE *cnfs_next(const ARTHANDLE *article, const RETRTYPE amount);
+void cnfs_freearticle(ARTHANDLE *article);
+bool cnfs_cancel(TOKEN token);
+bool cnfs_ctl(PROBETYPE type, TOKEN *token, void *value);
+bool cnfs_flushcacheddata(FLUSHTYPE type);
+void cnfs_printfiles(FILE *file, TOKEN token, char **xref, int ngroups);
+void cnfs_shutdown(void);
+
+#endif
--- /dev/null
+name = cnfs
+number = 3
+sources = cnfs.c
--- /dev/null
+/* $Id: expire.c 6775 2004-05-17 06:23:42Z rra $
+**
+** Code for overview-driven expiration.
+**
+** In order to expire on a per-newsgroup (instead of per-storage-class)
+** basis, one has to use overview-driven expiration. This contains all of
+** the code to do that. It provides OVgroupbasedexpire, OVhisthasmsgid, and
+** OVgroupmatch for the use of various overview methods.
+*/
+
+#include "config.h"
+#include "clibrary.h"
+#include <ctype.h>
+#include <errno.h>
+
+#include "inn/innconf.h"
+#include "libinn.h"
+#include "ov.h"
+#include "ovinterface.h"
+#include "paths.h"
+#include "storage.h"
+
+enum KRP {Keep, Remove, Poison};
+
+/* Statistics */
+static long EXPprocessed;
+static long EXPunlinked;
+static long EXPoverindexdrop;
+
+#define NGH_HASH(Name, p, j) \
+ for (p = Name, j = 0; *p; ) j = (j << 5) + j + *p++
+#define NGH_SIZE 2048
+#define NGH_BUCKET(j) &NGHtable[j & (NGH_SIZE - 1)]
+
+#define OVFMT_UNINIT -2
+#define OVFMT_NODATE -1
+#define OVFMT_NOXREF -1
+
+static int Dateindex = OVFMT_UNINIT;
+static int Xrefindex = OVFMT_UNINIT;
+static int Messageidindex = OVFMT_UNINIT;
+
+typedef struct _NEWSGROUP {
+ char *Name;
+ char *Rest;
+ unsigned long Last;
+ unsigned long Lastpurged;
+ /* These fields are new. */
+ time_t Keep;
+ time_t Default;
+ time_t Purge;
+ /* X flag => remove entire article when it expires in this group */
+ bool Poison;
+} NEWSGROUP;
+
+typedef struct _NGHASH {
+ int Size;
+ int Used;
+ NEWSGROUP **Groups;
+} NGHASH;
+
+#define MAGIC_TIME 49710.
+
+typedef struct _BADGROUP {
+ struct _BADGROUP *Next;
+ char *Name;
+} BADGROUP;
+
+/*
+** Information about the schema of the news overview files.
+*/
+typedef struct _ARTOVERFIELD {
+ char *Header;
+ int Length;
+ bool HasHeader;
+ bool NeedsHeader;
+} ARTOVERFIELD;
+
+static BADGROUP *EXPbadgroups;
+static int nGroups;
+static NEWSGROUP *Groups;
+static NEWSGROUP EXPdefault;
+static NGHASH NGHtable[NGH_SIZE];
+
+static char **arts;
+static enum KRP *krps;
+
+static ARTOVERFIELD * ARTfields;
+static int ARTfieldsize;
+static bool ReadOverviewfmt = false;
+
+
+/* FIXME: The following variables are shared between this file and ov.c.
+ This should be cleaned up with a better internal interface. */
+extern time_t OVnow;
+extern char * ACTIVE;
+extern FILE * EXPunlinkfile;
+extern bool OVignoreselfexpire;
+extern bool OVusepost;
+extern bool OVkeep;
+extern bool OVearliest;
+extern bool OVquiet;
+extern int OVnumpatterns;
+extern char ** OVpatterns;
+
+
+/*
+** Hash a newsgroup and see if we get it.
+*/
+static NEWSGROUP *
+NGfind(char *Name)
+{
+ char *p;
+ int i;
+ unsigned int j;
+ NEWSGROUP **ngp;
+ char c;
+ NGHASH *htp;
+
+ NGH_HASH(Name, p, j);
+ htp = NGH_BUCKET(j);
+ for (c = *Name, ngp = htp->Groups, i = htp->Used; --i >= 0; ngp++)
+ if (c == ngp[0]->Name[0] && strcmp(Name, ngp[0]->Name) == 0)
+ return ngp[0];
+ return NULL;
+}
+
+/*
+** Sorting predicate to put newsgroups in rough order of their activity.
+*/
+static int
+NGcompare(const void *p1, const void *p2)
+{
+ const NEWSGROUP * const * ng1 = p1;
+ const NEWSGROUP * const * ng2 = p2;
+
+ return ng1[0]->Last - ng2[0]->Last;
+}
+
+/*
+** Split a line at a specified field separator into a vector and return
+** the number of fields found, or -1 on error.
+*/
+static int
+EXPsplit(char *p, char sep, char **argv, int count)
+{
+ int i;
+
+ if (!p)
+ return 0;
+
+ while (*p == sep)
+ ++p;
+
+ if (!*p)
+ return 0;
+
+ if (!p)
+ return 0;
+
+ while (*p == sep)
+ ++p;
+
+ if (!*p)
+ return 0;
+
+ for (i = 1, *argv++ = p; *p; )
+ if (*p++ == sep) {
+ p[-1] = '\0';
+ for (; *p == sep; p++);
+ if (!*p)
+ return i;
+ if (++i == count)
+ /* Overflow. */
+ return -1;
+ *argv++ = p;
+ }
+ return i;
+}
+
+/*
+** Build the newsgroup structures from the active file.
+*/
+static void
+BuildGroups(char *active)
+{
+ NGHASH *htp;
+ NEWSGROUP *ngp;
+ char *p;
+ char *q;
+ int i;
+ unsigned j;
+ int lines;
+ int NGHbuckets;
+ char *fields[5];
+
+ /* Count the number of groups. */
+ for (p = active, i = 0; (p = strchr(p, '\n')) != NULL; p++, i++)
+ continue;
+ nGroups = i;
+ Groups = xmalloc(i * sizeof(NEWSGROUP));
+
+ /* Set up the default hash buckets. */
+ NGHbuckets = i / NGH_SIZE;
+ if (NGHbuckets == 0)
+ NGHbuckets = 1;
+ for (i = NGH_SIZE, htp = NGHtable; --i >= 0; htp++) {
+ htp->Size = NGHbuckets;
+ htp->Groups = xmalloc(htp->Size * sizeof(NEWSGROUP *));
+ htp->Used = 0;
+ }
+
+ /* Fill in the array. */
+ lines = 0;
+ for (p = active, ngp = Groups, i = nGroups; --i >= 0; ngp++, p = q + 1) {
+ lines++;
+ if ((q = strchr(p, '\n')) == NULL) {
+ fprintf(stderr, "%s: line %d missing newline\n", ACTIVE, lines);
+ exit(1);
+ }
+ if (*p == '.')
+ continue;
+ *q = '\0';
+ if (EXPsplit(p, ' ', fields, ARRAY_SIZE(fields)) != 4) {
+ fprintf(stderr, "%s: line %d wrong number of fields\n", ACTIVE, lines);
+ exit(1);
+ }
+ ngp->Name = fields[0];
+ ngp->Last = atol(fields[1]);
+ ngp->Rest = fields[3];
+
+ /* Find the right bucket for the group, make sure there is room. */
+ NGH_HASH(ngp->Name, p, j);
+ htp = NGH_BUCKET(j);
+ if (htp->Used >= htp->Size) {
+ htp->Size += NGHbuckets;
+ htp->Groups = xrealloc(htp->Groups, htp->Size * sizeof(NEWSGROUP *));
+ }
+ htp->Groups[htp->Used++] = ngp;
+ }
+
+ /* Sort each hash bucket. */
+ for (i = NGH_SIZE, htp = NGHtable; --i >= 0; htp++)
+ if (htp->Used > 1)
+ qsort(htp->Groups, htp->Used, sizeof htp->Groups[0], NGcompare);
+
+ /* Ok, now change our use of the Last field. Set them all to maxint. */
+ for (i = NGH_SIZE, htp = NGHtable; --i >= 0; htp++) {
+ NEWSGROUP **ngpa;
+ int k;
+
+ for (ngpa = htp->Groups, k = htp->Used; --k >= 0; ngpa++) {
+ ngpa[0]->Last = ~(unsigned long) 0;
+ ngpa[0]->Lastpurged = 0;
+ }
+ }
+}
+
+/*
+** Parse a number field converting it into a "when did this start?".
+** This makes the "keep it" tests fast, but inverts the logic of
+** just about everything you expect. Print a message and return false
+** on error.
+*/
+static bool
+EXPgetnum(int line, char *word, time_t *v, const char *name)
+{
+ char *p;
+ bool SawDot;
+ double d;
+
+ if (strcasecmp(word, "never") == 0) {
+ *v = (time_t)0;
+ return true;
+ }
+
+ /* Check the number. We don't have strtod yet. */
+ for (p = word; ISWHITE(*p); p++)
+ continue;
+ if (*p == '+' || *p == '-')
+ p++;
+ for (SawDot = false; *p; p++)
+ if (*p == '.') {
+ if (SawDot)
+ break;
+ SawDot = true;
+ }
+ else if (!CTYPE(isdigit, (int)*p))
+ break;
+ if (*p) {
+ fprintf(stderr, "Line %d, bad `%c' character in %s field\n",
+ line, *p, name);
+ return false;
+ }
+ d = atof(word);
+ if (d > MAGIC_TIME)
+ *v = (time_t)0;
+ else
+ *v = OVnow - (time_t)(d * 86400.);
+ return true;
+}
+
+/*
+** Set the expiration fields for all groups that match this pattern.
+*/
+static void
+EXPmatch(char *p, NEWSGROUP *v, char mod)
+{
+ NEWSGROUP *ngp;
+ int i;
+ bool negate;
+
+ negate = *p == '!';
+ if (negate)
+ p++;
+ for (ngp = Groups, i = nGroups; --i >= 0; ngp++)
+ if (negate ? !uwildmat(ngp->Name, p) : uwildmat(ngp->Name, p))
+ if (mod == 'a'
+ || (mod == 'm' && ngp->Rest[0] == NF_FLAG_MODERATED)
+ || (mod == 'u' && ngp->Rest[0] != NF_FLAG_MODERATED)) {
+ ngp->Keep = v->Keep;
+ ngp->Default = v->Default;
+ ngp->Purge = v->Purge;
+ ngp->Poison = v->Poison;
+ }
+}
+
+/*
+** Parse the expiration control file. Return true if okay.
+*/
+static bool
+EXPreadfile(FILE *F)
+{
+ char *p;
+ int i;
+ int j;
+ int k;
+ char mod;
+ NEWSGROUP v;
+ bool SawDefault;
+ char buff[BUFSIZ];
+ char *fields[7];
+ char **patterns;
+
+ /* Scan all lines. */
+ SawDefault = false;
+ patterns = xmalloc(nGroups * sizeof(char *));
+
+ for (i = 1; fgets(buff, sizeof buff, F) != NULL; i++) {
+ if ((p = strchr(buff, '\n')) == NULL) {
+ fprintf(stderr, "Line %d too long\n", i);
+ free(patterns);
+ return false;
+ }
+ *p = '\0';
+ p = strchr(buff, '#');
+ if (p)
+ *p = '\0';
+ else
+ p = buff + strlen(buff);
+ while (--p >= buff) {
+ if (isspace((int)*p))
+ *p = '\0';
+ else
+ break;
+ }
+ if (buff[0] == '\0')
+ continue;
+ if ((j = EXPsplit(buff, ':', fields, ARRAY_SIZE(fields))) == -1) {
+ fprintf(stderr, "Line %d too many fields\n", i);
+ free(patterns);
+ return false;
+ }
+
+ /* Expired-article remember line? */
+ if (strcmp(fields[0], "/remember/") == 0) {
+ continue;
+ }
+
+ /* Regular expiration line -- right number of fields? */
+ if (j != 5) {
+ fprintf(stderr, "Line %d bad format\n", i);
+ free(patterns);
+ return false;
+ }
+
+ /* Parse the fields. */
+ if (strchr(fields[1], 'M') != NULL)
+ mod = 'm';
+ else if (strchr(fields[1], 'U') != NULL)
+ mod = 'u';
+ else if (strchr(fields[1], 'A') != NULL)
+ mod = 'a';
+ else {
+ fprintf(stderr, "Line %d bad modflag\n", i);
+ free(patterns);
+ return false;
+ }
+ v.Poison = (strchr(fields[1], 'X') != NULL);
+ if (!EXPgetnum(i, fields[2], &v.Keep, "keep")
+ || !EXPgetnum(i, fields[3], &v.Default, "default")
+ || !EXPgetnum(i, fields[4], &v.Purge, "purge")) {
+ free(patterns);
+ return false;
+ }
+ /* These were turned into offsets, so the test is the opposite
+ * of what you think it should be. If Purge isn't forever,
+ * make sure it's greater then the other two fields. */
+ if (v.Purge) {
+ /* Some value not forever; make sure other values are in range. */
+ if (v.Keep && v.Keep < v.Purge) {
+ fprintf(stderr, "Line %d keep>purge\n", i);
+ free(patterns);
+ return false;
+ }
+ if (v.Default && v.Default < v.Purge) {
+ fprintf(stderr, "Line %d default>purge\n", i);
+ free(patterns);
+ return false;
+ }
+ }
+
+ /* Is this the default line? */
+ if (fields[0][0] == '*' && fields[0][1] == '\0' && mod == 'a') {
+ if (SawDefault) {
+ fprintf(stderr, "Line %d duplicate default\n", i);
+ free(patterns);
+ return false;
+ }
+ EXPdefault.Keep = v.Keep;
+ EXPdefault.Default = v.Default;
+ EXPdefault.Purge = v.Purge;
+ EXPdefault.Poison = v.Poison;
+ SawDefault = true;
+ }
+
+ /* Assign to all groups that match the pattern and flags. */
+ if ((j = EXPsplit(fields[0], ',', patterns, nGroups)) == -1) {
+ fprintf(stderr, "Line %d too many patterns\n", i);
+ free(patterns);
+ return false;
+ }
+ for (k = 0; k < j; k++)
+ EXPmatch(patterns[k], &v, mod);
+ }
+ free(patterns);
+
+ return true;
+}
+
+/*
+** Handle a newsgroup that isn't in the active file.
+*/
+static NEWSGROUP *
+EXPnotfound(char *Entry)
+{
+ static NEWSGROUP Removeit;
+ BADGROUP *bg;
+
+ /* See if we already know about this group. */
+ for (bg = EXPbadgroups; bg; bg = bg->Next)
+ if (strcmp(Entry, bg->Name) == 0)
+ break;
+ if (bg == NULL) {
+ bg = xmalloc(sizeof(BADGROUP));
+ bg->Name = xstrdup(Entry);
+ bg->Next = EXPbadgroups;
+ EXPbadgroups = bg;
+ }
+ /* remove it all now. */
+ if (Removeit.Keep == 0) {
+ Removeit.Keep = OVnow;
+ Removeit.Default = OVnow;
+ Removeit.Purge = OVnow;
+ }
+ return &Removeit;
+}
+
+/*
+** Should we keep the specified article?
+*/
+static enum KRP
+EXPkeepit(char *Entry, time_t when, time_t expires)
+{
+ NEWSGROUP *ngp;
+ enum KRP retval = Remove;
+
+ if ((ngp = NGfind(Entry)) == NULL)
+ ngp = EXPnotfound(Entry);
+
+ /* Bad posting date? */
+ if (when > OVrealnow + 86400) {
+ /* Yes -- force the article to go right now. */
+ when = expires ? ngp->Purge : ngp->Default;
+ }
+
+ /* If no expiration, make sure it wasn't posted before the default. */
+ if (expires == 0) {
+ if (when >= ngp->Default)
+ retval = Keep;
+
+ /* Make sure it's not posted before the purge cut-off and
+ * that it's not due to expire. */
+ } else {
+ if (when >= ngp->Purge && (expires >= OVnow || when >= ngp->Keep))
+ retval = Keep;
+ }
+ if (retval == Keep) {
+ return Keep;
+ } else {
+ return ngp->Poison ? Poison : Remove;
+ }
+}
+
+/*
+** An article can be removed. Either print a note, or actually remove it.
+** Takes in the Xref information so that it can pass this to the storage
+** API callback used to generate the list of files to remove.
+*/
+void
+OVEXPremove(TOKEN token, bool deletedgroups, char **xref, int ngroups)
+{
+ EXPunlinked++;
+ if (deletedgroups) {
+ EXPprocessed++;
+ EXPoverindexdrop++;
+ }
+ if (EXPunlinkfile && xref != NULL) {
+ SMprintfiles(EXPunlinkfile, token, xref, ngroups);
+ if (!ferror(EXPunlinkfile))
+ return;
+ fprintf(stderr, "Can't write to -z file, %s\n", strerror(errno));
+ fprintf(stderr, "(Will ignore it for rest of run.)\n");
+ fclose(EXPunlinkfile);
+ EXPunlinkfile = NULL;
+ }
+ if (!SMcancel(token) && SMerrno != SMERR_NOENT && SMerrno != SMERR_UNINIT)
+ fprintf(stderr, "Can't unlink %s: %s\n", TokenToText(token),
+ SMerrorstr);
+}
+
+/*
+** Read the overview schema.
+*/
+static void
+ARTreadschema(void)
+{
+ FILE *F;
+ char *p;
+ char *path;
+ ARTOVERFIELD *fp;
+ int i;
+ char buff[SMBUF];
+ bool foundxref = false;
+ bool foundxreffull = false;
+
+ /* Open file, count lines. */
+ path = concatpath(innconf->pathetc, _PATH_SCHEMA);
+ F = fopen(path, "r");
+ if (F == NULL)
+ return;
+ for (i = 0; fgets(buff, sizeof buff, F) != NULL; i++)
+ continue;
+ fseeko(F, 0, SEEK_SET);
+ ARTfields = xmalloc((i + 1) * sizeof(ARTOVERFIELD));
+
+ /* Parse each field. */
+ for (fp = ARTfields; fgets(buff, sizeof buff, F) != NULL; ) {
+ /* Ignore blank and comment lines. */
+ if ((p = strchr(buff, '\n')) != NULL)
+ *p = '\0';
+ if ((p = strchr(buff, '#')) != NULL)
+ *p = '\0';
+ if (buff[0] == '\0')
+ continue;
+ if ((p = strchr(buff, ':')) != NULL) {
+ *p++ = '\0';
+ fp->NeedsHeader = (strcmp(p, "full") == 0);
+ }
+ else
+ fp->NeedsHeader = false;
+ fp->HasHeader = false;
+ fp->Header = xstrdup(buff);
+ fp->Length = strlen(buff);
+ if (strcasecmp(buff, "Xref") == 0) {
+ foundxref = true;
+ foundxreffull = fp->NeedsHeader;
+ }
+ fp++;
+ }
+ ARTfieldsize = fp - ARTfields;
+ fclose(F);
+ if (!foundxref || !foundxreffull) {
+ fprintf(stderr, "'Xref:full' must be included in %s", path);
+ exit(1);
+ }
+ free(path);
+}
+
+/*
+** Return a field from the overview line or NULL on error. Return a copy
+** since we might be re-using the line later.
+*/
+static char *
+OVERGetHeader(const char *p, int field)
+{
+ static char *buff;
+ static int buffsize;
+ int i;
+ ARTOVERFIELD *fp;
+ char *next;
+
+ fp = &ARTfields[field];
+
+ /* Skip leading headers. */
+ for (; field-- >= 0 && *p; p++)
+ if ((p = strchr(p, '\t')) == NULL)
+ return NULL;
+ if (*p == '\0')
+ return NULL;
+
+ if (fp->HasHeader)
+ p += fp->Length + 2;
+
+ if (fp->NeedsHeader) { /* find an exact match */
+ while (strncmp(fp->Header, p, fp->Length) != 0) {
+ if ((p = strchr(p, '\t')) == NULL)
+ return NULL;
+ p++;
+ }
+ p += fp->Length + 2;
+ }
+
+ /* Figure out length; get space. */
+ if ((next = strpbrk(p, "\n\r\t")) != NULL) {
+ i = next - p;
+ } else {
+ i = strlen(p);
+ }
+ if (buffsize == 0) {
+ buffsize = i;
+ buff = xmalloc(buffsize + 1);
+ }
+ else if (buffsize < i) {
+ buffsize = i;
+ buff = xrealloc(buff, buffsize + 1);
+ }
+
+ strncpy(buff, p, i);
+ buff[i] = '\0';
+ return buff;
+}
+
+/*
+** Read overview.fmt and find index for headers
+*/
+static void
+OVfindheaderindex(void)
+{
+ FILE *F;
+ char *active;
+ char *path;
+ int i;
+
+ if (ReadOverviewfmt)
+ return;
+ if (innconf->groupbaseexpiry) {
+ ACTIVE = concatpath(innconf->pathdb, _PATH_ACTIVE);
+ if ((active = ReadInFile(ACTIVE, (struct stat *)NULL)) == NULL) {
+ fprintf(stderr, "Can't read %s, %s\n",
+ ACTIVE, strerror(errno));
+ exit(1);
+ }
+ BuildGroups(active);
+ arts = xmalloc(nGroups * sizeof(char *));
+ krps = xmalloc(nGroups * sizeof(enum KRP));
+ path = concatpath(innconf->pathetc, _PATH_EXPIRECTL);
+ F = fopen(path, "r");
+ free(path);
+ if (!EXPreadfile(F)) {
+ fclose(F);
+ fprintf(stderr, "Format error in expire.ctl\n");
+ exit(1);
+ }
+ fclose(F);
+ }
+ ARTreadschema();
+ if (Dateindex == OVFMT_UNINIT) {
+ for (Dateindex = OVFMT_NODATE, i = 0; i < ARTfieldsize; i++) {
+ if (strcasecmp(ARTfields[i].Header, "Date") == 0) {
+ Dateindex = i;
+ } else if (strcasecmp(ARTfields[i].Header, "Xref") == 0) {
+ Xrefindex = i;
+ } else if (strcasecmp(ARTfields[i].Header, "Message-ID") == 0) {
+ Messageidindex = i;
+ }
+ }
+ }
+ ReadOverviewfmt = true;
+ return;
+}
+
+/*
+** Do the work of expiring one line. Assumes article still exists in the
+** spool. Returns true if article should be purged, or return false.
+*/
+bool
+OVgroupbasedexpire(TOKEN token, const char *group, const char *data,
+ int len UNUSED, time_t arrived, time_t expires)
+{
+ static char *Group = NULL;
+ char *p;
+ int i;
+ int count;
+ time_t when;
+ bool poisoned;
+ bool keeper;
+ bool delete;
+ bool purge;
+ char *Xref;
+
+ if (SMprobe(SELFEXPIRE, &token, NULL)) {
+ if (!OVignoreselfexpire)
+ /* this article should be kept */
+ return false;
+ }
+ if (!ReadOverviewfmt) {
+ OVfindheaderindex();
+ }
+
+ if (OVusepost) {
+ if ((p = OVERGetHeader(data, Dateindex)) == NULL) {
+ EXPoverindexdrop++;
+ return true;
+ }
+ if ((when = parsedate(p, NULL)) == -1) {
+ EXPoverindexdrop++;
+ return true;
+ }
+ } else {
+ when = arrived;
+ }
+ if ((Xref = OVERGetHeader(data, Xrefindex)) == NULL) {
+ if (Group != NULL) {
+ free(Group);
+ }
+ Group = concat(group, ":", (char *) 0);
+ Xref = Group;
+ } else {
+ if ((Xref = strchr(Xref, ' ')) == NULL) {
+ EXPoverindexdrop++;
+ return true;
+ }
+ for (Xref++; *Xref == ' '; Xref++)
+ ;
+ }
+ if ((count = EXPsplit(Xref, ' ', arts, nGroups)) == -1) {
+ EXPoverindexdrop++;
+ return true;
+ }
+
+ /* arts is now an array of strings, each of which is a group name, a
+ colon, and an article number. EXPkeepit wants just pure group names,
+ so replace the colons with nuls (deleting the overview entry if it
+ isn't in the expected form). */
+ for (i = 0; i < count; i++) {
+ p = strchr(arts[i], ':');
+ if (p == NULL) {
+ fflush(stdout);
+ fprintf(stderr, "Bad entry, \"%s\"\n", arts[i]);
+ EXPoverindexdrop++;
+ return true;
+ }
+ *p = '\0';
+ }
+
+ /* First check all postings */
+ poisoned = false;
+ keeper = false;
+ delete = false;
+ purge = true;
+ for (i = 0; i < count; ++i) {
+ if ((krps[i] = EXPkeepit(arts[i], when, expires)) == Poison)
+ poisoned = true;
+ if (OVkeep && (krps[i] == Keep))
+ keeper = true;
+ if ((krps[i] == Remove) && strcmp(group, arts[i]) == 0)
+ delete = true;
+ if ((krps[i] == Keep))
+ purge = false;
+ }
+ EXPprocessed++;
+
+ if (OVearliest) {
+ if (delete || poisoned || token.type == TOKEN_EMPTY) {
+ /* delete article if this is first entry */
+ if (strcmp(group, arts[0]) == 0) {
+ for (i = 0; i < count; i++)
+ arts[i][strlen(arts[i])] = ':';
+ OVEXPremove(token, false, arts, count);
+ }
+ EXPoverindexdrop++;
+ return true;
+ }
+ } else { /* not earliest mode */
+ if ((!keeper && delete) || token.type == TOKEN_EMPTY) {
+ /* delete article if purge is set, indicating that it has
+ expired out of every group to which it was posted */
+ if (purge) {
+ for (i = 0; i < count; i++)
+ arts[i][strlen(arts[i])] = ':';
+ OVEXPremove(token, false, arts, count);
+ }
+ EXPoverindexdrop++;
+ return true;
+ }
+ }
+
+ /* this article should be kept */
+ return false;
+}
+
+bool
+OVhisthasmsgid(struct history *h, const char *data)
+{
+ char *p;
+
+ if (!ReadOverviewfmt) {
+ OVfindheaderindex();
+ }
+ if ((p = OVERGetHeader(data, Messageidindex)) == NULL)
+ return false;
+ return HISlookup(h, p, NULL, NULL, NULL, NULL);
+}
+
+bool
+OVgroupmatch(const char *group)
+{
+ int i;
+ bool wanted = false;
+
+ if (OVnumpatterns == 0 || group == NULL)
+ return true;
+ for (i = 0; i < OVnumpatterns; i++) {
+ switch (OVpatterns[i][0]) {
+ case '!':
+ if (!wanted && uwildmat(group, &OVpatterns[i][1]))
+ break;
+ case '@':
+ if (uwildmat(group, &OVpatterns[i][1])) {
+ return false;
+ }
+ break;
+ default:
+ if (uwildmat(group, OVpatterns[i]))
+ wanted = true;
+ }
+ }
+ return wanted;
+}
+
+void
+OVEXPcleanup(void)
+{
+ int i;
+ BADGROUP *bg, *bgnext;
+ ARTOVERFIELD *fp;
+ NGHASH *htp;
+
+ if (EXPprocessed != 0) {
+ if (!OVquiet) {
+ printf(" Article lines processed %8ld\n", EXPprocessed);
+ printf(" Articles dropped %8ld\n", EXPunlinked);
+ printf(" Overview index dropped %8ld\n", EXPoverindexdrop);
+ }
+ EXPprocessed = EXPunlinked = EXPoverindexdrop = 0;
+ }
+ if (innconf->ovgrouppat != NULL) {
+ for (i = 0 ; i < OVnumpatterns ; i++)
+ free(OVpatterns[i]);
+ free(OVpatterns);
+ }
+ for (bg = EXPbadgroups; bg; bg = bgnext) {
+ bgnext = bg->Next;
+ free(bg->Name);
+ free(bg);
+ }
+ for (fp = ARTfields, i = 0; i < ARTfieldsize ; i++, fp++) {
+ free(fp->Header);
+ }
+ free(ARTfields);
+ if (ACTIVE != NULL) {
+ free(ACTIVE);
+ ACTIVE = NULL;
+ }
+ if (Groups != NULL) {
+ free(Groups);
+ Groups = NULL;
+ }
+ for (i = 0, htp = NGHtable ; i < NGH_SIZE ; i++, htp++) {
+ if (htp->Groups != NULL) {
+ free(htp->Groups);
+ htp->Groups = NULL;
+ }
+ }
+}
--- /dev/null
+/* $Id: interface.c 7277 2005-06-07 04:40:16Z eagle $
+**
+** Storage Manager interface
+*/
+#include "config.h"
+#include "clibrary.h"
+#include <ctype.h>
+#include <errno.h>
+#include <syslog.h>
+#include <time.h>
+
+#include "conffile.h"
+#include "inn/innconf.h"
+#include "inn/wire.h"
+#include "interface.h"
+#include "libinn.h"
+#include "methods.h"
+#include "paths.h"
+
+typedef enum {INIT_NO, INIT_DONE, INIT_FAIL} INITTYPE;
+typedef struct {
+ INITTYPE initialized;
+ bool configured;
+ bool selfexpire;
+ bool expensivestat;
+} METHOD_DATA;
+
+METHOD_DATA method_data[NUM_STORAGE_METHODS];
+
+static STORAGE_SUB *subscriptions = NULL;
+static unsigned int typetoindex[256];
+int SMerrno;
+char *SMerrorstr = NULL;
+static bool ErrorAlloc = false;
+static bool Initialized = false;
+bool SMopenmode = false;
+bool SMpreopen = false;
+
+/*
+** Checks to see if the token is valid
+*/
+bool IsToken(const char *text) {
+ const char *p;
+
+ if (!text)
+ return false;
+
+ if (strlen(text) != (sizeof(TOKEN) * 2) + 2)
+ return false;
+
+ if (text[0] != '@')
+ return false;
+
+ if (text[(sizeof(TOKEN) * 2) + 1] != '@')
+ return false;
+
+ for (p = text + 1; *p != '@'; p++)
+ if (!isxdigit((int)*p))
+ return false;
+
+ return true;
+}
+
+/*
+** Converts a token to a textual representation for error messages
+** and the like.
+*/
+char *
+TokenToText(const TOKEN token)
+{
+ static const char hex[] = "0123456789ABCDEF";
+ static char result[(sizeof(TOKEN) * 2) + 3];
+ const char *p;
+ char *q;
+ size_t i;
+
+
+ result[0] = '@';
+ for (q = result + 1, p = (const char *) &token, i = 0; i < sizeof(TOKEN);
+ i++, p++) {
+ *q++ = hex[(*p & 0xF0) >> 4];
+ *q++ = hex[*p & 0x0F];
+ }
+ *q++ = '@';
+ *q++ = '\0';
+ return result;
+
+}
+
+/*
+** Converts a hex digit and converts it to a int
+*/
+static int hextodec(const int c) {
+ return isdigit(c) ? (c - '0') : ((c - 'A') + 10);
+}
+
+/*
+** Converts a textual representation of a token back to a native
+** representation
+*/
+TOKEN TextToToken(const char *text) {
+ const char *p;
+ char *q;
+ int i;
+ TOKEN token;
+
+ if (text[0] == '@')
+ p = &text[1];
+ else
+ p = text;
+
+ for (q = (char *)&token, i = 0; i != sizeof(TOKEN); i++) {
+ q[i] = (hextodec(*p) << 4) + hextodec(*(p + 1));
+ p += 2;
+ }
+ return token;
+}
+
+/*
+** Given an article and length in non-wire format, return a malloced region
+** containing the article in wire format. Set *newlen to the length of the
+** new article.
+*/
+char *
+ToWireFmt(const char *article, size_t len, size_t *newlen)
+{
+ size_t bytes;
+ char *newart;
+ const char *p;
+ char *dest;
+ bool atstartofline=true;
+
+ /* First go thru article and count number of bytes we need. */
+ for (bytes = 0, p=article ; p < &article[len] ; ++p) {
+ if (*p == '.' && atstartofline) ++bytes; /* 1 byte for escaping . */
+ ++bytes;
+ if (*p == '\n') {
+ ++bytes; /* need another byte for CR */
+ atstartofline = true; /* next char starts new line */
+ } else {
+ atstartofline = false;
+ }
+ }
+ bytes += 3; /* for .\r\n */
+ newart = xmalloc(bytes + 1);
+ *newlen = bytes;
+
+ /* now copy the article, making changes */
+ atstartofline = true;
+ for (p=article, dest=newart ; p < &article[len] ; ++p) {
+ if (*p == '\n') {
+ *dest++ = '\r';
+ *dest++ = '\n';
+ atstartofline = true;
+ } else {
+ if (atstartofline && *p == '.') *dest++ = '.'; /* add extra . */
+ *dest++ = *p;
+ atstartofline = false;
+ }
+ }
+ *dest++ = '.';
+ *dest++ = '\r';
+ *dest++ = '\n';
+ *dest = '\0';
+ return newart;
+}
+
+char *
+FromWireFmt(const char *article, size_t len, size_t *newlen)
+{
+ size_t bytes;
+ char *newart;
+ const char *p;
+ char *dest;
+ bool atstartofline = true;
+
+ /* First go thru article and count number of bytes we need */
+ for (bytes = 0, p=article ; p < &article[len] ; ) {
+ /* check for terminating .\r\n and if so break */
+ if (p == &article[len-3] && *p == '.' && p[1] == '\r' && p[2] == '\n')
+ break;
+ /* check for .. at start-of-line */
+ if (atstartofline && p < &article[len-1] && *p == '.' && p[1] == '.') {
+ bytes++; /* only output 1 byte */
+ p+=2;
+ atstartofline = false;
+ } else if (p < &article[len-1] && *p == '\r' && p[1] == '\n') {
+ bytes++; /* \r\n counts as only one byte in output */
+ p += 2;
+ atstartofline = true;
+ } else {
+ bytes++;
+ p++;
+ atstartofline = false;
+ }
+ }
+ newart = xmalloc(bytes + 1);
+ *newlen = bytes;
+ for (p = article, dest = newart ; p < &article[len]; ) {
+ /* check for terminating .\r\n and if so break */
+ if (p == &article[len-3] && *p == '.' && p[1] == '\r' && p[2] == '\n')
+ break;
+ if (atstartofline && p < &article[len-1] && *p == '.' && p[1] == '.') {
+ *dest++ = '.';
+ p += 2;
+ atstartofline = false;
+ } else if (p < &article[len-1] && *p == '\r' && p[1] == '\n') {
+ *dest++ = '\n';
+ p += 2;
+ atstartofline = true;
+ } else {
+ *dest++ = *p++;
+ atstartofline = false;
+ }
+ }
+ *dest = '\0';
+ return newart;
+}
+
+/*
+** get Xref header without pathhost
+*/
+static char *
+GetXref(ARTHANDLE *art)
+{
+ const char *p, *p1;
+ const char *q;
+ char *buff;
+ bool Nocr = false;
+
+ p = wire_findheader(art->data, art->len, "xref");
+ if (p == NULL)
+ return NULL;
+ q = p;
+ for (p1 = NULL; p < art->data + art->len; p++) {
+ if (p1 != (char *)NULL && *p1 == '\r' && *p == '\n') {
+ Nocr = false;
+ break;
+ }
+ if (*p == '\n') {
+ Nocr = true;
+ break;
+ }
+ p1 = p;
+ }
+ if (p >= art->data + art->len)
+ return NULL;
+ if (!Nocr)
+ p = p1;
+ /* skip pathhost */
+ for (; (*q == ' ') && (q < p); q++);
+ if (q == p)
+ return NULL;
+ if ((q = memchr(q, ' ', p - q)) == NULL)
+ return NULL;
+ for (q++; (*q == ' ') && (q < p); q++);
+ if (q == p)
+ return NULL;
+ buff = xmalloc(p - q + 1);
+ memcpy(buff, q, p - q);
+ buff[p - q] = '\0';
+ return buff;
+}
+
+/*
+** Split newsgroup and returns artnum
+** or 0 if there are no newsgroup.
+*/
+static ARTNUM GetGroups(char *Xref) {
+ char *p;
+
+ if ((p = strchr(Xref, ':')) == NULL)
+ return 0;
+ *p++ = '\0';
+ return ((ARTNUM)atoi(p));
+}
+
+STORAGE_SUB *SMGetConfig(STORAGETYPE type, STORAGE_SUB *sub) {
+ if (sub == (STORAGE_SUB *)NULL)
+ sub = subscriptions;
+ else
+ sub = sub->next;
+ for (;sub != NULL; sub = sub->next) {
+ if (sub->type == type) {
+ return sub;
+ }
+ }
+ return (STORAGE_SUB *)NULL;
+}
+
+static time_t ParseTime(char *tmbuf)
+{
+ char *startnum;
+ time_t ret;
+ int tmp;
+
+ ret = 0;
+ startnum = tmbuf;
+ while (*tmbuf) {
+ if (!isdigit((int)*tmbuf)) {
+ tmp = atol(startnum);
+ switch (*tmbuf) {
+ case 'M':
+ ret += tmp*60*60*24*31;
+ break;
+ case 'd':
+ ret += tmp*60*60*24;
+ break;
+ case 'h':
+ ret += tmp*60*60;
+ break;
+ case 'm':
+ ret += tmp*60;
+ break;
+ case 's':
+ ret += tmp;
+ break;
+ default:
+ return(0);
+ }
+ startnum = tmbuf+1;
+ }
+ tmbuf++;
+ }
+ return(ret);
+}
+
+#define SMlbrace 1
+#define SMrbrace 2
+#define SMmethod 10
+#define SMgroups 11
+#define SMsize 12
+#define SMclass 13
+#define SMexpire 14
+#define SMoptions 15
+#define SMexactmatch 16
+
+static CONFTOKEN smtoks[] = {
+ { SMlbrace, "{" },
+ { SMrbrace, "}" },
+ { SMmethod, "method" },
+ { SMgroups, "newsgroups:" },
+ { SMsize, "size:" },
+ { SMclass, "class:" },
+ { SMexpire, "expires:" },
+ { SMoptions, "options:" },
+ { SMexactmatch, "exactmatch:" },
+ { 0, 0 }
+};
+
+/* Open the config file and parse it, generating the policy data */
+static bool
+SMreadconfig(void)
+{
+ CONFFILE *f;
+ CONFTOKEN *tok;
+ int type;
+ int i;
+ char *p;
+ char *q;
+ char *path;
+ char *method = NULL;
+ char *pattern = NULL;
+ size_t minsize = 0;
+ size_t maxsize = 0;
+ time_t minexpire = 0;
+ time_t maxexpire = 0;
+ int class = 0;
+ STORAGE_SUB *sub = NULL;
+ STORAGE_SUB *prev = NULL;
+ char *options = 0;
+ int inbrace;
+ bool exactmatch = false;
+
+ /* if innconf isn't already read in, do so. */
+ if (innconf == NULL) {
+ if (!innconf_read(NULL)) {
+ SMseterror(SMERR_INTERNAL, "ReadInnConf() failed");
+ return false;
+ }
+ }
+
+ for (i = 0; i < NUM_STORAGE_METHODS; i++) {
+ method_data[i].initialized = INIT_NO;
+ method_data[i].configured = false;
+ }
+ path = concatpath(innconf->pathetc, _PATH_STORAGECTL);
+ f = CONFfopen(path);
+ if (f == NULL) {
+ SMseterror(SMERR_UNDEFINED, NULL);
+ syslog(L_ERROR, "SM Could not open %s: %m", path);
+ free(path);
+ return false;
+ }
+ free(path);
+
+ inbrace = 0;
+ while ((tok = CONFgettoken(smtoks, f)) != NULL) {
+ if (!inbrace) {
+ if (tok->type != SMmethod) {
+ SMseterror(SMERR_CONFIG, "Expected 'method' keyword");
+ syslog(L_ERROR, "SM expected 'method' keyword, line %d", f->lineno);
+ return false;
+ }
+ if ((tok = CONFgettoken(0, f)) == NULL) {
+ SMseterror(SMERR_CONFIG, "Expected method name");
+ syslog(L_ERROR, "SM expected method name, line %d", f->lineno);
+ return false;
+ }
+ method = xstrdup(tok->name);
+ if ((tok = CONFgettoken(smtoks, f)) == NULL || tok->type != SMlbrace) {
+ SMseterror(SMERR_CONFIG, "Expected '{'");
+ syslog(L_ERROR, "SM Expected '{', line %d", f->lineno);
+ return false;
+ }
+ inbrace = 1;
+ /* initialize various params to defaults. */
+ minsize = 0;
+ maxsize = 0; /* zero means no limit */
+ class = 0;
+ pattern = NULL;
+ options = NULL;
+ minexpire = 0;
+ maxexpire = 0;
+ exactmatch = false;
+
+ } else {
+ type = tok->type;
+ if (type == SMrbrace)
+ inbrace = 0;
+ else {
+ if ((tok = CONFgettoken(0, f)) == NULL) {
+ SMseterror(SMERR_CONFIG, "Keyword with no value");
+ syslog(L_ERROR, "SM keyword with no value, line %d", f->lineno);
+ return false;
+ }
+ p = tok->name;
+ switch(type) {
+ case SMgroups:
+ if (pattern)
+ free(pattern);
+ pattern = xstrdup(tok->name);
+ break;
+ case SMsize:
+ minsize = strtoul(p, NULL, 10);
+ if ((p = strchr(p, ',')) != NULL) {
+ p++;
+ maxsize = strtoul(p, NULL, 10);
+ }
+ break;
+ case SMclass:
+ class = atoi(p);
+ if (class > NUM_STORAGE_CLASSES) {
+ SMseterror(SMERR_CONFIG, "Storage class too large");
+ warn("SM: storage class larger than %d, line %d",
+ NUM_STORAGE_CLASSES, f->lineno);
+ return false;
+ }
+ break;
+ case SMexpire:
+ q = strchr(p, ',');
+ if (q)
+ *q++ = 0;
+ minexpire = ParseTime(p);
+ if (q)
+ maxexpire = ParseTime(q);
+ break;
+ case SMoptions:
+ if (options)
+ free(options);
+ options = xstrdup(p);
+ break;
+ case SMexactmatch:
+ if (strcasecmp(p, "true") == 0
+ || strcasecmp(p, "yes") == 0
+ || strcasecmp(p, "on") == 0)
+ exactmatch = true;
+ break;
+ default:
+ SMseterror(SMERR_CONFIG, "Unknown keyword in method declaration");
+ syslog(L_ERROR, "SM Unknown keyword in method declaration, line %d: %s", f->lineno, tok->name);
+ free(method);
+ return false;
+ break;
+ }
+ }
+ }
+ if (!inbrace) {
+ /* just finished a declaration */
+ sub = xmalloc(sizeof(STORAGE_SUB));
+ sub->type = TOKEN_EMPTY;
+ for (i = 0; i < NUM_STORAGE_METHODS; i++) {
+ if (!strcasecmp(method, storage_methods[i].name)) {
+ sub->type = storage_methods[i].type;
+ method_data[i].configured = true;
+ break;
+ }
+ }
+ if (sub->type == TOKEN_EMPTY) {
+ SMseterror(SMERR_CONFIG, "Invalid storage method name");
+ syslog(L_ERROR, "SM no configured storage methods are named '%s'", method);
+ free(options);
+ free(sub);
+ return false;
+ }
+ if (!pattern) {
+ SMseterror(SMERR_CONFIG, "pattern not defined");
+ syslog(L_ERROR, "SM no pattern defined");
+ free(options);
+ free(sub);
+ return false;
+ }
+ sub->pattern = pattern;
+ sub->minsize = minsize;
+ sub->maxsize = maxsize;
+ sub->class = class;
+ sub->options = options;
+ sub->minexpire = minexpire;
+ sub->maxexpire = maxexpire;
+ sub->exactmatch = exactmatch;
+
+ free(method);
+ method = 0;
+
+ if (!prev)
+ subscriptions = sub;
+ if (prev)
+ prev->next = sub;
+ prev = sub;
+ sub->next = NULL;
+ }
+ }
+
+ CONFfclose(f);
+
+ return true;
+}
+
+/*
+** setup storage api environment (open mode etc.)
+*/
+bool SMsetup(SMSETUP type, void *value) {
+ if (Initialized)
+ return false;
+ switch (type) {
+ case SM_RDWR:
+ SMopenmode = *(bool *)value;
+ break;
+ case SM_PREOPEN:
+ SMpreopen = *(bool *)value;
+ break;
+ default:
+ return false;
+ }
+ return true;
+}
+
+/*
+** Calls the setup function for all of the configured methods and returns
+** true if they all initialize ok, false if they don't
+*/
+bool SMinit(void) {
+ int i;
+ bool allok = true;
+ static bool once = false;
+ SMATTRIBUTE smattr;
+
+ if (Initialized)
+ return true;
+
+ Initialized = true;
+
+ if (!SMreadconfig()) {
+ SMshutdown();
+ Initialized = false;
+ return false;
+ }
+
+ for (i = 0; i < NUM_STORAGE_METHODS; i++) {
+ if (method_data[i].configured) {
+ if (method_data[i].configured && storage_methods[i].init(&smattr)) {
+ method_data[i].initialized = INIT_DONE;
+ method_data[i].selfexpire = smattr.selfexpire;
+ method_data[i].expensivestat = smattr.expensivestat;
+ } else {
+ method_data[i].initialized = INIT_FAIL;
+ method_data[i].selfexpire = false;
+ method_data[i].expensivestat = true;
+ syslog(L_ERROR, "SM storage method '%s' failed initialization", storage_methods[i].name);
+ allok = false;
+ }
+ }
+ typetoindex[storage_methods[i].type] = i;
+ }
+ if (!allok) {
+ SMshutdown();
+ Initialized = false;
+ SMseterror(SMERR_UNDEFINED, "one or more storage methods failed initialization");
+ syslog(L_ERROR, "SM one or more storage methods failed initialization");
+ return false;
+ }
+ if (!once && atexit(SMshutdown) < 0) {
+ SMshutdown();
+ Initialized = false;
+ SMseterror(SMERR_UNDEFINED, NULL);
+ return false;
+ }
+ once = true;
+ return true;
+}
+
+static bool InitMethod(STORAGETYPE method) {
+ SMATTRIBUTE smattr;
+
+ if (!Initialized)
+ if (!SMreadconfig()) {
+ Initialized = false;
+ return false;
+ }
+ Initialized = true;
+
+ if (method_data[method].initialized == INIT_DONE)
+ return true;
+
+ if (method_data[method].initialized == INIT_FAIL)
+ return false;
+
+ if (!method_data[method].configured) {
+ method_data[method].initialized = INIT_FAIL;
+ SMseterror(SMERR_UNDEFINED, "storage method is not configured.");
+ return false;
+ }
+ if (!storage_methods[method].init(&smattr)) {
+ method_data[method].initialized = INIT_FAIL;
+ method_data[method].selfexpire = false;
+ method_data[method].expensivestat = true;
+ SMseterror(SMERR_UNDEFINED, "Could not initialize storage method late.");
+ return false;
+ }
+ method_data[method].initialized = INIT_DONE;
+ method_data[method].selfexpire = smattr.selfexpire;
+ method_data[method].expensivestat = smattr.expensivestat;
+ return true;
+}
+
+static bool
+MatchGroups(const char *g, int len, const char *pattern, bool exactmatch)
+{
+ char *group, *groups, *q;
+ int i, lastwhite;
+ enum uwildmat matched;
+ bool wanted = false;
+
+ q = groups = xmalloc(len + 1);
+ for (lastwhite = -1, i = 0 ; i < len ; i++) {
+ /* trim white chars */
+ if (g[i] == '\r' || g[i] == '\n' || g[i] == ' ' || g[i] == '\t') {
+ if (lastwhite + 1 != i)
+ *q++ = ' ';
+ lastwhite = i;
+ } else
+ *q++ = g[i];
+ }
+ *q = '\0';
+
+ group = strtok(groups, " ,");
+ while (group != NULL) {
+ q = strchr(group, ':');
+ if (q != NULL)
+ *q = '\0';
+ matched = uwildmat_poison(group, pattern);
+ if (matched == UWILDMAT_POISON || (exactmatch && !matched)) {
+ free(groups);
+ return false;
+ }
+ if (matched == UWILDMAT_MATCH)
+ wanted = true;
+ group = strtok(NULL, " ,");
+ }
+
+ free(groups);
+ return wanted;
+}
+
+STORAGE_SUB *SMgetsub(const ARTHANDLE article) {
+ STORAGE_SUB *sub;
+
+ if (article.len == 0) {
+ SMseterror(SMERR_BADHANDLE, NULL);
+ return NULL;
+ }
+
+ if (article.groups == NULL)
+ return NULL;
+
+ for (sub = subscriptions; sub != NULL; sub = sub->next) {
+ if (!(method_data[typetoindex[sub->type]].initialized == INIT_FAIL) &&
+ (article.len >= sub->minsize) &&
+ (!sub->maxsize || (article.len <= sub->maxsize)) &&
+ (!sub->minexpire || article.expires >= sub->minexpire) &&
+ (!sub->maxexpire || (article.expires <= sub->maxexpire)) &&
+ MatchGroups(article.groups, article.groupslen, sub->pattern,
+ sub->exactmatch)) {
+ if (InitMethod(typetoindex[sub->type]))
+ return sub;
+ }
+ }
+ errno = 0;
+ SMseterror(SMERR_NOMATCH, "no matching entry in storage.conf");
+ return NULL;
+}
+
+TOKEN SMstore(const ARTHANDLE article) {
+ STORAGE_SUB *sub;
+ TOKEN result;
+
+ if (!SMopenmode) {
+ result.type = TOKEN_EMPTY;
+ SMseterror(SMERR_INTERNAL, "read only storage api");
+ return result;
+ }
+ result.type = TOKEN_EMPTY;
+ if ((sub = SMgetsub(article)) == NULL) {
+ return result;
+ }
+ return storage_methods[typetoindex[sub->type]].store(article, sub->class);
+}
+
+ARTHANDLE *SMretrieve(const TOKEN token, const RETRTYPE amount) {
+ ARTHANDLE *art;
+
+ if (method_data[typetoindex[token.type]].initialized == INIT_FAIL) {
+ SMseterror(SMERR_UNINIT, NULL);
+ return NULL;
+ }
+ if (method_data[typetoindex[token.type]].initialized == INIT_NO && !InitMethod(typetoindex[token.type])) {
+ syslog(L_ERROR, "SM could not find token type or method was not initialized (%d)",
+ token.type);
+ SMseterror(SMERR_UNINIT, NULL);
+ return NULL;
+ }
+ art = storage_methods[typetoindex[token.type]].retrieve(token, amount);
+ if (art)
+ art->nextmethod = 0;
+ return art;
+
+}
+
+ARTHANDLE *SMnext(const ARTHANDLE *article, const RETRTYPE amount) {
+ unsigned char i;
+ int start;
+ ARTHANDLE *newart;
+
+ if (article == NULL)
+ start = 0;
+ else
+ start= article->nextmethod;
+
+ if (method_data[start].initialized == INIT_FAIL) {
+ SMseterror(SMERR_UNINIT, NULL);
+ return NULL;
+ }
+ if (method_data[start].initialized == INIT_NO && method_data[start].configured
+ && !InitMethod(start)) {
+ SMseterror(SMERR_UNINIT, NULL);
+ return NULL;
+ }
+
+ for (i = start, newart = NULL; i < NUM_STORAGE_METHODS; i++) {
+ if (method_data[i].configured && (newart = storage_methods[i].next(article, amount)) != (ARTHANDLE *)NULL) {
+ newart->nextmethod = i;
+ break;
+ } else
+ article = NULL;
+ }
+
+ return newart;
+}
+
+void SMfreearticle(ARTHANDLE *article) {
+ if (method_data[typetoindex[article->type]].initialized == INIT_FAIL) {
+ return;
+ }
+ if (method_data[typetoindex[article->type]].initialized == INIT_NO && !InitMethod(typetoindex[article->type])) {
+ syslog(L_ERROR, "SM can't free article with uninitialized method");
+ return;
+ }
+ storage_methods[typetoindex[article->type]].freearticle(article);
+}
+
+bool SMcancel(TOKEN token) {
+ if (!SMopenmode) {
+ SMseterror(SMERR_INTERNAL, "read only storage api");
+ return false;
+ }
+ if (method_data[typetoindex[token.type]].initialized == INIT_FAIL) {
+ SMseterror(SMERR_UNINIT, NULL);
+ return false;
+ }
+ if (method_data[typetoindex[token.type]].initialized == INIT_NO && !InitMethod(typetoindex[token.type])) {
+ SMseterror(SMERR_UNINIT, NULL);
+ syslog(L_ERROR, "SM can't cancel article with uninitialized method");
+ return false;
+ }
+ return storage_methods[typetoindex[token.type]].cancel(token);
+}
+
+bool SMprobe(PROBETYPE type, TOKEN *token, void *value) {
+ struct artngnum *ann;
+ ARTHANDLE *art;
+
+ switch (type) {
+ case SELFEXPIRE:
+ return (method_data[typetoindex[token->type]].selfexpire);
+ case SMARTNGNUM:
+ if (method_data[typetoindex[token->type]].initialized == INIT_FAIL) {
+ SMseterror(SMERR_UNINIT, NULL);
+ return false;
+ }
+ if (method_data[typetoindex[token->type]].initialized == INIT_NO && !InitMethod(typetoindex[token->type])) {
+ SMseterror(SMERR_UNINIT, NULL);
+ syslog(L_ERROR, "SM can't cancel article with uninitialized method");
+ return false;
+ }
+ if ((ann = (struct artngnum *)value) == NULL)
+ return false;
+ ann->groupname = NULL;
+ if (storage_methods[typetoindex[token->type]].ctl(type, token, value)) {
+ if (ann->artnum != 0) {
+ /* set by storage method */
+ return true;
+ } else {
+ art = storage_methods[typetoindex[token->type]].retrieve(*token, RETR_HEAD);
+ if (art == NULL) {
+ if (ann->groupname != NULL)
+ free(ann->groupname);
+ storage_methods[typetoindex[token->type]].freearticle(art);
+ return false;
+ }
+ if ((ann->groupname = GetXref(art)) == NULL) {
+ if (ann->groupname != NULL)
+ free(ann->groupname);
+ storage_methods[typetoindex[token->type]].freearticle(art);
+ return false;
+ }
+ storage_methods[typetoindex[token->type]].freearticle(art);
+ if ((ann->artnum = GetGroups(ann->groupname)) == 0) {
+ if (ann->groupname != NULL)
+ free(ann->groupname);
+ return false;
+ }
+ return true;
+ }
+ } else {
+ return false;
+ }
+ case EXPENSIVESTAT:
+ return (method_data[typetoindex[token->type]].expensivestat);
+ default:
+ return false;
+ }
+}
+
+bool SMflushcacheddata(FLUSHTYPE type) {
+ int i;
+
+ for (i = 0; i < NUM_STORAGE_METHODS; i++) {
+ if (method_data[i].initialized == INIT_DONE &&
+ !storage_methods[i].flushcacheddata(type))
+ syslog(L_ERROR, "SM can't flush cached data method '%s'", storage_methods[i].name);
+ }
+ return true;
+}
+
+void SMprintfiles(FILE *file, TOKEN token, char **xref, int ngroups) {
+ if (method_data[typetoindex[token.type]].initialized == INIT_FAIL)
+ return;
+ if (method_data[typetoindex[token.type]].initialized == INIT_NO
+ && !InitMethod(typetoindex[token.type])) {
+ SMseterror(SMERR_UNINIT, NULL);
+ syslog(L_ERROR, "SM can't print files for article with uninitialized method");
+ return;
+ }
+ storage_methods[typetoindex[token.type]].printfiles(file, token, xref, ngroups);
+}
+
+void SMshutdown(void) {
+ int i;
+ STORAGE_SUB *old;
+
+ if (!Initialized)
+ return;
+
+ for (i = 0; i < NUM_STORAGE_METHODS; i++)
+ if (method_data[i].initialized == INIT_DONE) {
+ storage_methods[i].shutdown();
+ method_data[i].initialized = INIT_NO;
+ method_data[i].configured = false;
+ }
+ while (subscriptions) {
+ old = subscriptions;
+ subscriptions = subscriptions->next;
+ free(old->pattern);
+ free(old->options);
+ free(old);
+ }
+ Initialized = false;
+}
+
+void SMseterror(int errornum, char *error) {
+ if (ErrorAlloc)
+ free(SMerrorstr);
+
+ ErrorAlloc = false;
+
+ if ((errornum == SMERR_UNDEFINED) && (errno == ENOENT))
+ errornum = SMERR_NOENT;
+
+ SMerrno = errornum;
+
+ if (error == NULL) {
+ switch (SMerrno) {
+ case SMERR_UNDEFINED:
+ SMerrorstr = xstrdup(strerror(errno));
+ ErrorAlloc = true;
+ break;
+ case SMERR_INTERNAL:
+ SMerrorstr = "Internal error";
+ break;
+ case SMERR_NOENT:
+ SMerrorstr = "Token not found";
+ break;
+ case SMERR_TOKENSHORT:
+ SMerrorstr = "Configured token size too small";
+ break;
+ case SMERR_NOBODY:
+ SMerrorstr = "No article body found";
+ break;
+ case SMERR_UNINIT:
+ SMerrorstr = "Storage manager is not initialized";
+ break;
+ case SMERR_CONFIG:
+ SMerrorstr = "Error reading config file";
+ break;
+ case SMERR_BADHANDLE:
+ SMerrorstr = "Bad article handle";
+ break;
+ case SMERR_BADTOKEN:
+ SMerrorstr = "Bad token";
+ break;
+ case SMERR_NOMATCH:
+ SMerrorstr = "No matching entry in storage.conf";
+ break;
+ default:
+ SMerrorstr = "Undefined error";
+ }
+ } else {
+ SMerrorstr = xstrdup(error);
+ ErrorAlloc = true;
+ }
+}
+
--- /dev/null
+/* $Id: interface.h 5933 2002-12-07 09:47:17Z rra $
+**
+** Storage Manager interface header
+*/
+
+#ifndef __INTERFACE_H__
+#define __INTERFACE_H__
+
+#include "config.h"
+#include "storage.h"
+#include <stdio.h>
+
+typedef struct {
+ bool selfexpire;
+ bool expensivestat;
+} SMATTRIBUTE;
+
+typedef struct {
+ const char *name;
+ unsigned char type;
+ bool (*init)(SMATTRIBUTE *attr);
+ TOKEN (*store)(const ARTHANDLE article, const STORAGECLASS storageclass);
+ ARTHANDLE *(*retrieve)(const TOKEN token, const RETRTYPE amount);
+ ARTHANDLE *(*next)(const ARTHANDLE *article, const RETRTYPE amount);
+ void (*freearticle)(ARTHANDLE *article);
+ bool (*cancel)(TOKEN token);
+ bool (*ctl)(PROBETYPE type, TOKEN *token, void *value);
+ bool (*flushcacheddata)(FLUSHTYPE type);
+ void (*printfiles)(FILE *, TOKEN, char **xref, int ngroups);
+ void (*shutdown)(void);
+} STORAGE_METHOD;
+
+typedef struct __S_SUB__ {
+ int type; /* Index into storage_methods of the one to use */
+ size_t minsize; /* Minimum size to send to this method */
+ size_t maxsize; /* Maximum size to send to this method */
+ time_t minexpire; /* Minimum expire offset to send method */
+ time_t maxexpire; /* Maximum expire offset to send method */
+ int numpatterns; /* Number of patterns in patterns */
+ int class; /* Number of the storage class for this subscription */
+ char *pattern; /* Wildmat pattern to check against the
+ groups to determine if the article
+ should go to this method */
+ char *options; /* additional options specific to the
+ method */
+ bool exactmatch; /* all newsgroups to which article belongs
+ should match the patterns */
+ struct __S_SUB__ *next;
+} STORAGE_SUB;
+
+extern bool SMopenmode;
+extern bool SMpreopen;
+char *SMFindBody(char *article, int len);
+STORAGE_SUB *SMGetConfig(STORAGETYPE type, STORAGE_SUB *sub);
+STORAGE_SUB *SMgetsub(const ARTHANDLE article);
+void SMseterror(int errorno, char *error);
+
+#endif /* __INTERFACE_H__ */
--- /dev/null
+/* $Id: ov.c 6135 2003-01-19 01:15:40Z rra $
+**
+** The implementation of the overview API.
+**
+** This code handles calls to the overview API by passing them along to the
+** appropriate underlying overview method, as well as implementing those
+** portions of the overview subsystem that are independent of storage
+** method.
+*/
+
+#include "config.h"
+#include "clibrary.h"
+#include <assert.h>
+#include <ctype.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <sys/stat.h>
+#include <syslog.h>
+
+#include "inn/innconf.h"
+#include "libinn.h"
+#include "ov.h"
+#include "ovinterface.h"
+#include "ovmethods.h"
+
+/* FIXME: The following variables are shared between this file and expire.c.
+ This should be cleaned up with a better internal interface. */
+static bool OVdelayrm;
+static OV_METHOD ov;
+
+bool
+OVopen(int mode)
+{
+ int i;
+ bool val;
+ char *p;
+
+ if (ov.open)
+ /* already opened */
+ return true;
+
+ /* if innconf isn't already read in, do so. */
+ if (innconf == NULL)
+ if (!innconf_read(NULL))
+ return false;
+ if (!innconf->enableoverview) {
+ syslog(L_FATAL, "enableoverview is not true");
+ fprintf(stderr, "enableoverview is not true\n");
+ return false;
+ }
+ if (innconf->ovmethod == NULL) {
+ syslog(L_FATAL, "ovmethod is not defined");
+ fprintf(stderr, "ovmethod is not defined\n");
+ return false;
+ }
+ for (i=0;i<NUM_OV_METHODS;i++) {
+ if (!strcmp(innconf->ovmethod, ov_methods[i].name))
+ break;
+ }
+ if (i == NUM_OV_METHODS) {
+ syslog(L_FATAL, "%s is not found for ovmethod", innconf->ovmethod);
+ fprintf(stderr, "%s is not found for ovmethod\n", innconf->ovmethod);
+ return false;
+ }
+ ov = ov_methods[i];
+ val = (*ov.open)(mode);
+ if (atexit(OVclose) < 0) {
+ OVclose();
+ return false;
+ }
+ if (innconf->ovgrouppat != NULL) {
+ for (i = 1, p = innconf->ovgrouppat; *p && (p = strchr(p+1, ',')); i++);
+ OVnumpatterns = i;
+ OVpatterns = xmalloc(OVnumpatterns * sizeof(char *));
+ for (i = 0, p = strtok(innconf->ovgrouppat, ","); p != NULL && i <= OVnumpatterns ; i++, p = strtok(NULL, ","))
+ OVpatterns[i] = xstrdup(p);
+ if (i != OVnumpatterns) {
+ syslog(L_FATAL, "extra ',' in pattern");
+ fprintf(stderr, "extra ',' in pattern");
+ return false;
+ }
+ }
+ return val;
+}
+
+bool
+OVgroupstats(char *group, int *lo, int *hi, int *count, int *flag)
+{
+ if (!ov.open) {
+ /* must be opened */
+ syslog(L_ERROR, "ovopen must be called first");
+ fprintf(stderr, "ovopen must be called first");
+ return false;
+ }
+ return ((*ov.groupstats)(group, lo, hi, count, flag));
+}
+
+bool
+OVgroupadd(char *group, ARTNUM lo, ARTNUM hi, char *flag)
+{
+ /* lomark should never be changed in each ovmethod if lo is 0 */
+ if (!ov.open) {
+ /* must be opened */
+ syslog(L_ERROR, "ovopen must be called first");
+ fprintf(stderr, "ovopen must be called first");
+ return false;
+ }
+ return ((*ov.groupadd)(group, lo, hi, flag));
+}
+
+bool
+OVgroupdel(char *group)
+{
+ if (!ov.open) {
+ /* must be opened */
+ syslog(L_ERROR, "ovopen must be called first");
+ fprintf(stderr, "ovopen must be called first");
+ return false;
+ }
+ return ((*ov.groupdel)(group));
+}
+
+OVADDRESULT
+OVadd(TOKEN token, char *data, int len, time_t arrived, time_t expires)
+{
+ char *next, *nextcheck;
+ static char *xrefdata, *patcheck, *overdata;
+ char *xrefstart = NULL;
+ char *xrefend;
+ static int xrefdatalen = 0, overdatalen = 0;
+ bool found = false;
+ int xreflen;
+ int i;
+ char *group;
+ ARTNUM artnum;
+
+ if (!ov.open) {
+ /* must be opened */
+ syslog(L_ERROR, "ovopen must be called first");
+ fprintf(stderr, "ovopen must be called first");
+ return OVADDFAILED;
+ }
+
+ /*
+ * find last Xref: in the overview line. Note we need to find the *last*
+ * Xref:, since there have been corrupted articles on Usenet with Xref:
+ * fragments stuck in other header lines. The last Xref: is guaranteed
+ * to be from our server.
+ */
+
+ for (next = data; ((len - (next - data)) > 6 ) && ((next = memchr(next, 'X', len - (next - data))) != NULL); ) {
+ if (memcmp(next, "Xref: ", 6) == 0) {
+ found = true;
+ xrefstart = next;
+ }
+ next++;
+ }
+
+ if (!found)
+ return OVADDFAILED;
+
+ next = xrefstart;
+ for (i = 0; (i < 2) && (next < (data + len)); i++) {
+ if ((next = memchr(next, ' ', len - (next - data))) == NULL)
+ return OVADDFAILED;
+ next++;
+ }
+ xreflen = len - (next - data);
+
+ /*
+ * If there are other fields beyond Xref in overview, then
+ * we must find Xref's end, or data following is misinterpreted.
+ */
+ if ((xrefend = memchr(next, '\t', xreflen)) != NULL)
+ xreflen = xrefend - next;
+
+ if (xrefdatalen == 0) {
+ xrefdatalen = BIG_BUFFER;
+ xrefdata = xmalloc(xrefdatalen);
+ if (innconf->ovgrouppat != NULL)
+ patcheck = xmalloc(xrefdatalen);
+ }
+ if (xreflen > xrefdatalen) {
+ xrefdatalen = xreflen;
+ xrefdata = xrealloc(xrefdata, xrefdatalen + 1);
+ if (innconf->ovgrouppat != NULL)
+ patcheck = xrealloc(patcheck, xrefdatalen + 1);
+ }
+ if (overdatalen == 0) {
+ overdatalen = BIG_BUFFER;
+ overdata = xmalloc(overdatalen);
+ }
+ if (len + 16 > overdatalen) {
+ overdatalen = len + 16;
+ overdata = xrealloc(overdata, overdatalen);
+ }
+
+ if (innconf->ovgrouppat != NULL) {
+ memcpy(patcheck, next, xreflen);
+ patcheck[xreflen] = '\0';
+ for (group = patcheck; group && *group; group = memchr(nextcheck, ' ', xreflen - (nextcheck - patcheck))) {
+ while (isspace((int)*group))
+ group++;
+ if ((nextcheck = memchr(group, ':', xreflen - (patcheck - group))) == NULL)
+ return OVADDFAILED;
+ *nextcheck++ = '\0';
+ if (!OVgroupmatch(group)) {
+ if (!SMprobe(SELFEXPIRE, &token, NULL) && innconf->groupbaseexpiry)
+ /* this article will never be expired, since it does not
+ have self expiry function in stored method and
+ groupbaseexpiry is true */
+ return OVADDFAILED;
+ return OVADDGROUPNOMATCH;
+ }
+ }
+ }
+ memcpy(xrefdata, next, xreflen);
+ xrefdata[xreflen] = '\0';
+ for (group = xrefdata; group && *group; group = memchr(next, ' ', xreflen - (next - xrefdata))) {
+ /* Parse the xref part into group name and article number */
+ while (isspace((int)*group))
+ group++;
+ if ((next = memchr(group, ':', xreflen - (group - xrefdata))) == NULL)
+ return OVADDFAILED;
+ *next++ = '\0';
+ artnum = atoi(next);
+ if (artnum <= 0)
+ continue;
+
+ sprintf(overdata, "%ld\t", artnum);
+ i = strlen(overdata);
+ memcpy(overdata + i, data, len);
+ i += len;
+ memcpy(overdata + i, "\r\n", 2);
+ i += 2;
+
+ if(! (*ov.add)(group, artnum, token, overdata, i, arrived, expires))
+ return OVADDFAILED;
+ }
+
+ return OVADDCOMPLETED;
+}
+
+bool
+OVcancel(TOKEN token)
+{
+ if (!ov.open) {
+ /* must be opened */
+ syslog(L_ERROR, "ovopen must be called first");
+ fprintf(stderr, "ovopen must be called first");
+ return false;
+ }
+ return ((*ov.cancel)(token));
+}
+
+void *
+OVopensearch(char *group, int low, int high)
+{
+ if (!ov.open) {
+ /* must be opened */
+ syslog(L_ERROR, "ovopen must be called first");
+ fprintf(stderr, "ovopen must be called first");
+ return false;
+ }
+ return ((*ov.opensearch)(group, low, high));
+}
+
+bool
+OVsearch(void *handle, ARTNUM *artnum, char **data, int *len, TOKEN *token,
+ time_t *arrived)
+{
+ if (!ov.open) {
+ /* must be opened */
+ syslog(L_ERROR, "ovopen must be called first");
+ fprintf(stderr, "ovopen must be called first");
+ return false;
+ }
+ return ((*ov.search)(handle, artnum, data, len, token, arrived));
+}
+
+void
+OVclosesearch(void *handle)
+{
+ if (!ov.open) {
+ /* must be opened */
+ syslog(L_ERROR, "ovopen must be called first");
+ fprintf(stderr, "ovopen must be called first");
+ return;
+ }
+ (*ov.closesearch)(handle);
+ return;
+}
+
+bool
+OVgetartinfo(char *group, ARTNUM artnum, TOKEN *token)
+{
+ if (!ov.open) {
+ /* must be opened */
+ syslog(L_ERROR, "ovopen must be called first");
+ fprintf(stderr, "ovopen must be called first");
+ return false;
+ }
+ return ((*ov.getartinfo)(group, artnum, token));
+}
+
+bool
+OVexpiregroup(char *group, int *lo, struct history *h)
+{
+ if (!ov.open) {
+ /* must be opened */
+ syslog(L_ERROR, "ovopen must be called first");
+ fprintf(stderr, "ovopen must be called first");
+ return false;
+ }
+ return ((*ov.expiregroup)(group, lo, h));
+}
+
+bool
+OVctl(OVCTLTYPE type, void *val)
+{
+ if (!ov.open) {
+ /* must be opened */
+ syslog(L_ERROR, "ovopen must be called first");
+ fprintf(stderr, "ovopen must be called first");
+ return false;
+ }
+ switch (type) {
+ case OVGROUPBASEDEXPIRE:
+ if (!innconf->groupbaseexpiry) {
+ syslog(L_ERROR, "OVGROUPBASEDEXPIRE is not allowed if groupbaseexpiry if false");
+ fprintf(stderr, "OVGROUPBASEDEXPIRE is not allowed if groupbaseexpiry if false");
+ return false;
+ }
+ if (((OVGE *)val)->delayrm) {
+ if ((((OVGE *)val)->filename == NULL) || (strlen(((OVGE *)val)->filename) == 0)) {
+ syslog(L_ERROR, "file name must be specified");
+ fprintf(stderr, "file name must be specified");
+ return false;
+ }
+ if ((EXPunlinkfile = fopen(((OVGE *)val)->filename, "w")) == NULL) {
+ syslog(L_ERROR, "fopen: %s failed: %m", ((OVGE *)val)->filename);
+ fprintf(stderr, "fopen: %s failed: %s", ((OVGE *)val)->filename,
+ strerror(errno));
+ return false;
+ }
+ }
+ OVdelayrm = ((OVGE *)val)->delayrm;
+ OVusepost = ((OVGE *)val)->usepost;
+ OVrealnow = ((OVGE *)val)->now;
+ OVnow = ((OVGE *)val)->now + (time_t)((OVGE *)val)->timewarp;
+ OVquiet = ((OVGE *)val)->quiet;
+ OVkeep = ((OVGE *)val)->keep;
+ OVearliest = ((OVGE *)val)->earliest;
+ OVignoreselfexpire = ((OVGE *)val)->ignoreselfexpire;
+ return true;
+ case OVSTATALL:
+ OVstatall = *(bool *)val;
+ return true;
+ default:
+ return ((*ov.ctl)(type, val));
+ }
+}
+
+void
+OVclose(void)
+{
+ if (!ov.open)
+ return;
+ (*ov.close)();
+ memset(&ov, '\0', sizeof(ov));
+ OVEXPcleanup();
+}
--- /dev/null
+#ifdef USE_BERKELEY_DB
+
+#include <db.h>
+
+#if DB_VERSION_MAJOR == 2
+#if DB_VERSION_MINOR < 6
+#error "Need BerkeleyDB 2.6.x, 2.7.x, 3.x or 4.x"
+#endif
+#else
+#if DB_VERSION_MAJOR < 3 || DB_VERSION_MAJOR > 4
+#error "Need BerkeleyDB 2.6.x, 2.7.x, 3.x or 4.x"
+#endif
+#endif
+
+/*
+ * How data is stored:
+ *
+ * Each group is assigned an integer ID. The mapping between a group name
+ * and its ID is stored in the groupinfo DB. Overview data itself
+ * is stored in one or more btree DBs. The specific DB file that is used
+ * to store data for a certain group is chosen by taking the hash of the
+ * group name, copying the first bytes of the hash into an int, and then
+ * modding the int value to the number of DBs.
+ *
+ * Each group has one groupinfo structure in the groupinfo DB, whose key
+ * is the newsgroup name. The overview records for the group have a
+ * 'struct datakey' as their keys, which consists of the group ID (in
+ * native byteorder) followed by the article number in network byteorder.
+ * The reason for storing the article number in net byte order (big-endian)
+ * is that the keys will sort correctly using BerkeleyDB's default sort
+ * function (basically, a memcmp).
+ *
+ * The overview records consist of a 'struct ovdata' followed by the actual
+ * overview data. The struct ovdata contains the token and arrival time.
+ */
+
+struct ovdb_conf {
+ char *home; /* path to directory where db files are stored */
+ int txn_nosync; /* whether to pass DB_TXN_NOSYNC to db_appinit */
+ int numdbfiles;
+ size_t cachesize;
+ size_t pagesize;
+ int minkey;
+ int maxlocks;
+ int nocompact;
+ int readserver;
+ int numrsprocs;
+ int maxrsconn;
+ int useshm;
+ int shmkey;
+};
+
+typedef u_int32_t group_id_t;
+
+struct groupinfo {
+ ARTNUM low;
+ ARTNUM high;
+ int count;
+ int flag;
+ time_t expired; /* when this group was last touched by expiregroup */
+ group_id_t current_gid; /* group ID */
+ group_id_t new_gid; /* pending ID (expireover) */
+ int current_db; /* which DB file the records are in */
+ int new_db; /* pending DB file */
+ pid_t expiregrouppid; /* PID of expireover process */
+ int status;
+};
+#define GROUPINFO_DELETED 1
+#define GROUPINFO_EXPIRING (1<<1)
+#define GROUPINFO_MOVING (1<<2)
+#define GROUPINFO_MOVE_REQUESTED (1<<3) /*NYI*/
+
+struct datakey {
+ group_id_t groupnum; /* must be the first member of this struct */
+ u_int32_t artnum;
+};
+
+struct ovdata {
+ TOKEN token;
+ time_t arrived;
+ time_t expires;
+};
+
+
+#define DATA_VERSION 2
+
+extern struct ovdb_conf ovdb_conf;
+extern DB_ENV *OVDBenv;
+
+#define OVDB_ERR_NONE 0
+#define OVDB_ERR_SYSLOG 1 /* default */
+#define OVDB_ERR_STDERR 2
+extern int ovdb_errmode;
+
+void read_ovdb_conf(void);
+int ovdb_open_berkeleydb(int mode, int flags);
+void ovdb_close_berkeleydb(void);
+int ovdb_getgroupinfo(char *group, struct groupinfo *gi, int ignoredeleted, DB_TXN *tid, int getflags);
+
+#define OVDB_RECOVER 1
+#define OVDB_UPGRADE 2
+
+#define OVDB_LOCK_NORMAL 0
+#define OVDB_LOCK_ADMIN 1
+#define OVDB_LOCK_EXCLUSIVE 2
+
+bool ovdb_getlock(int mode);
+bool ovdb_releaselock(void);
+bool ovdb_check_pidfile(char *file);
+bool ovdb_check_user(void);
+
+#define OVDB_LOCKFN "ovdb.sem"
+#define OVDB_MONITOR_PIDFILE "ovdb_monitor.pid"
+#define OVDB_SERVER_PIDFILE "ovdb_server.pid"
+#define SPACES " "
+
+/* read server stuff */
+#define CMD_QUIT 0x01
+#define CMD_GROUPSTATS 0x02
+#define CMD_OPENSRCH 0x03
+#define CMD_SRCH 0x04
+#define CMD_CLOSESRCH 0x05
+#define CMD_ARTINFO 0x06
+#define CMD_MASK 0x0F
+#define RPLY_OK 0x00
+#define RPLY_ERROR 0x10
+#define OVDB_SERVER (1<<4)
+#define OVDB_SERVER_BANNER "ovdb read protocol 1"
+#define OVDB_SERVER_PORT 32323 /* only used if don't have unix domain sockets */
+#define OVDB_SERVER_SOCKET "ovdb.server"
+
+struct rs_cmd {
+ uint32_t what;
+ uint32_t grouplen;
+ uint32_t artlo;
+ uint32_t arthi;
+ void * handle;
+};
+
+struct rs_groupstats {
+ uint32_t status;
+ int lo;
+ int hi;
+ int count;
+ int flag;
+ uint32_t aliaslen;
+ /* char alias */
+};
+
+struct rs_opensrch {
+ uint32_t status;
+ void * handle;
+};
+
+struct rs_srch {
+ uint32_t status;
+ ARTNUM artnum;
+ TOKEN token;
+ time_t arrived;
+ int len;
+ /* char data */
+};
+
+struct rs_artinfo {
+ uint32_t status;
+ TOKEN token;
+};
+
+
+#if DB_VERSION_MAJOR == 2
+char *db_strerror(int err);
+
+#define TXN_START(label, tid) \
+label: { \
+ int txn_ret; \
+ txn_ret = txn_begin(OVDBenv->tx_info, NULL, &tid); \
+ if (txn_ret != 0) { \
+ syslog(L_ERROR, "OVDB: " #label " txn_begin: %s", db_strerror(ret)); \
+ tid = NULL; \
+ } \
+}
+
+#define TXN_RETRY(label, tid) \
+{ txn_abort(tid); goto label; }
+
+#define TXN_ABORT(label, tid) txn_abort(tid)
+#define TXN_COMMIT(label, tid) txn_commit(tid)
+
+#define TRYAGAIN EAGAIN
+
+#elif DB_VERSION_MAJOR == 3
+
+#define TXN_START(label, tid) \
+label: { \
+ int txn_ret; \
+ txn_ret = txn_begin(OVDBenv, NULL, &tid, 0); \
+ if (txn_ret != 0) { \
+ syslog(L_ERROR, "OVDB: " #label " txn_begin: %s", db_strerror(ret)); \
+ tid = NULL; \
+ } \
+}
+
+#define TXN_RETRY(label, tid) \
+{ txn_abort(tid); goto label; }
+
+#define TXN_ABORT(label, tid) txn_abort(tid)
+#define TXN_COMMIT(label, tid) txn_commit(tid, 0)
+
+#define TRYAGAIN DB_LOCK_DEADLOCK
+
+#else /* DB_VERSION_MAJOR == 4 */
+
+#define TXN_START(label, tid) \
+label: { \
+ int txn_ret; \
+ txn_ret = OVDBenv->txn_begin(OVDBenv, NULL, &tid, 0); \
+ if (txn_ret != 0) { \
+ syslog(L_ERROR, "OVDB: " #label " txn_begin: %s", db_strerror(ret)); \
+ tid = NULL; \
+ } \
+}
+
+#define TXN_RETRY(label, tid) \
+{ (tid)->abort(tid); goto label; }
+
+#define TXN_ABORT(label, tid) (tid)->abort(tid)
+#define TXN_COMMIT(label, tid) (tid)->commit(tid, 0)
+
+#define TRYAGAIN DB_LOCK_DEADLOCK
+
+#endif /* DB_VERSION_MAJOR == 4 */
+
+#endif /* USE_BERKELEY_DB */
--- /dev/null
+/*
+ * ovdb.c
+ * ovdb 2.00
+ * Overview storage using BerkeleyDB 2.x/3.x/4.x
+ *
+ * 2004-02-17 : Need to track search cursors, since it's possible that
+ * ovdb_closesearch does not get called. We now close
+ * any cursors still open in ovdb_close, or they'd be in
+ * the database indefinitely causing deadlocks.
+ * 2002-08-13 : Change BOOL to bool, remove portability to < 2.4.
+ * 2002-08-11 : Cleaned up use of sprintf and fixed a bunch of warnings.
+ * 2002-02-28 : Update getartinfo for the overview API change in 2.4. This
+ * breaks compatibility with INN 2.3.x....
+ * 2000-12-12 : Add support for BerkeleyDB DB_SYSTEM_MEM option, controlled
+ * : by ovdb.conf 'useshm' and 'shmkey'
+ * 2000-11-27 : Update for DB 3.2.x compatibility
+ * 2000-11-13 : New 'readserver' feature
+ * 2000-10-10 : ovdb_search now closes the cursor right after the last
+ * record is read.
+ * 2000-10-05 : artnum member of struct datakey changed from ARTNUM to u_int32_t.
+ * OS's where sizeof(long)==8 will have to rebuild their databases
+ * after this update.
+ * 2000-10-05 : from Dan Riley: struct datakey needs to be zero'd, for
+ * 64-bit OSs where the struct has internal padding bytes.
+ * 2000-09-29 : ovdb_expiregroup can now fix incorrect counts; use new
+ * inn/version.h so can have same ovdb.c for 2.3.0, 2.3.1, and 2.4
+ * 2000-09-28 : low mark in ovdb_expiregroup still wasn't right
+ * 2000-09-27 : Further improvements to ovdb_expiregroup: restructured the
+ * loop; now updates groupinfo as it goes along rather than
+ * counting records at the end, which prevents a possible
+ * deadlock.
+ * 2000-09-19 : *lo wasn't being set in ovdb_expiregroup
+ * 2000-09-15 : added ovdb_check_user(); tweaked some error msgs; fixed an
+ * improper use of RENEW
+ * 2000-08-28: New major release: version 2.00 (beta)
+ * + "groupsbyname" and "groupstats" databases replaced with "groupinfo".
+ * + ovdb_recover, ovdb_upgrade, and dbprocs are now deprecated; their
+ * functionality is now in ovdb_init and ovdb_monitor.
+ * + ovdb_init can upgrade a database from the old version of ovdb to
+ * work with this version.
+ * + Rewrote ovdb_expiregroup(); it can now re-write OV data rather
+ * than simply deleting old keys (which can leave 'holes' that result
+ * in inefficient disk-space use).
+ * + Add "nocompact" to ovdb.conf, which controls whether ovdb_expiregroup()
+ * rewrites OV data.
+ * + No longer needs the BerkeleyDB tools db_archive, db_checkpoint, and
+ * db_deadlock. That functionality is now in ovdb_monitor.
+ * + ovdb_open() won't succeed if ovdb_monitor is not running. This will
+ * prevent the problems that happen if the database is not regularly
+ * checkpointed and deadlock-tested.
+ * + Internal group IDs (32-bit ints) are now reused.
+ * + Add "maxlocks" to ovdb.conf, which will set the DB lk_max parameter.
+ * + Pull "test" code out into ovdb_stat. ovdb_stat will also provide
+ * functionality similar to the BerkeleyDB "db_stat" command.
+ * + Update docs: write man pages for the new ovdb_* commands; update
+ * ovdb.pod
+ *
+ * 2000-07-11 : fix possible alignment problem; add test code
+ * 2000-07-07 : bugfix: timestamp handling
+ * 2000-06-10 : Modified groupnum() interface; fix ovdb_add() to return false
+ * for certain groupnum() errors
+ * 2000-06-08 : Added BerkeleyDB 3.1.x compatibility
+ * 2000-04-09 : Tweak some default parameters; store aliased group info
+ * 2000-03-29 : Add DB_RMW flag to the 'get' of get-modify-put sequences
+ * 2000-02-17 : Update expire behavior to be consistent with current
+ * ov3 and buffindexed
+ * 2000-01-13 : Fix to make compatible with unmodified nnrpd/article.c
+ * 2000-01-04 : Added data versioning
+ * 1999-12-20 : Added BerkeleyDB 3.x compatibility
+ * 1999-12-06 : First Release -- H. Kehoe <hakehoe@avalon.net>
+ */
+
+#include "config.h"
+#include "clibrary.h"
+#include "portable/socket.h"
+#include "portable/time.h"
+#include <errno.h>
+#include <fcntl.h>
+#ifdef HAVE_LIMITS_H
+# include <limits.h>
+#endif
+#include <pwd.h>
+#include <signal.h>
+#ifdef HAVE_SYS_SELECT_H
+# include <sys/select.h>
+#endif
+#include <syslog.h>
+
+#include "conffile.h"
+#include "inn/innconf.h"
+#include "inn/messages.h"
+#include "libinn.h"
+#include "paths.h"
+#include "storage.h"
+
+#include "ov.h"
+#include "ovinterface.h"
+#include "ovdb.h"
+#include "ovdb-private.h"
+
+#ifdef HAVE_UNIX_DOMAIN_SOCKETS
+# include <sys/un.h>
+#endif
+
+#ifndef USE_BERKELEY_DB
+
+/* Provide stub functions if we don't have db */
+
+bool ovdb_open(int mode UNUSED)
+{
+ syslog(L_FATAL, "OVDB: ovdb support not enabled");
+ return false;
+}
+
+bool ovdb_groupstats(char *group UNUSED, int *lo UNUSED, int *hi UNUSED, int *count UNUSED, int *flag UNUSED)
+{ return false; }
+
+bool ovdb_groupadd(char *group UNUSED, ARTNUM lo UNUSED, ARTNUM hi UNUSED, char *flag UNUSED)
+{ return false; }
+
+bool ovdb_groupdel(char *group UNUSED)
+{ return false; }
+
+bool ovdb_add(char *group UNUSED, ARTNUM artnum UNUSED, TOKEN token UNUSED, char *data UNUSED, int len UNUSED, time_t arrived UNUSED, time_t expires UNUSED)
+{ return false; }
+
+bool ovdb_cancel(TOKEN token UNUSED)
+{ return false; }
+
+void *ovdb_opensearch(char *group UNUSED, int low UNUSED, int high UNUSED)
+{ return NULL; }
+
+bool ovdb_search(void *handle UNUSED, ARTNUM *artnum UNUSED, char **data UNUSED, int *len UNUSED, TOKEN *token UNUSED, time_t *arrived UNUSED)
+{ return false; }
+
+void ovdb_closesearch(void *handle UNUSED) { }
+
+bool ovdb_getartinfo(char *group UNUSED, ARTNUM artnum UNUSED, TOKEN *token UNUSED)
+{ return false; }
+
+bool ovdb_expiregroup(char *group UNUSED, int *lo UNUSED, struct history *h UNUSED)
+{ return false; }
+
+bool ovdb_ctl(OVCTLTYPE type UNUSED, void *val UNUSED)
+{ return false; }
+
+void ovdb_close(void) { }
+
+#else /* USE_BERKELEY_DB */
+
+#define EXPIREGROUP_TXN_SIZE 100
+#define DELETE_TXN_SIZE 500
+
+struct ovdb_conf ovdb_conf;
+DB_ENV *OVDBenv = NULL;
+int ovdb_errmode = OVDB_ERR_SYSLOG;
+
+static int OVDBmode;
+static bool Cutofflow;
+static DB **dbs = NULL;
+static int oneatatime = 0;
+static int current_db = -1;
+static time_t eo_start = 0;
+static int clientmode = 0;
+
+static DB *groupinfo = NULL;
+static DB *groupaliases = NULL;
+
+#define OVDBtxn_nosync 1
+#define OVDBnumdbfiles 2
+#define OVDBpagesize 3
+#define OVDBcachesize 4
+#define OVDBminkey 5
+#define OVDBmaxlocks 6
+#define OVDBnocompact 7
+#define OVDBreadserver 8
+#define OVDBnumrsprocs 9
+#define OVDBmaxrsconn 10
+#define OVDBuseshm 11
+#define OVDBshmkey 12
+
+static CONFTOKEN toks[] = {
+ { OVDBtxn_nosync, "txn_nosync" },
+ { OVDBnumdbfiles, "numdbfiles" },
+ { OVDBpagesize, "pagesize" },
+ { OVDBcachesize, "cachesize" },
+ { OVDBminkey, "minkey" },
+ { OVDBmaxlocks, "maxlocks" },
+ { OVDBnocompact, "nocompact" },
+ { OVDBreadserver, "readserver" },
+ { OVDBnumrsprocs, "numrsprocs" },
+ { OVDBmaxrsconn, "maxrsconn" },
+ { OVDBuseshm, "useshm" },
+ { OVDBshmkey, "shmkey" },
+ { 0, NULL },
+};
+
+#define _PATH_OVDBCONF "ovdb.conf"
+
+/*********** readserver functions ***********/
+
+static int clientfd = -1;
+
+/* read client send and recieve functions. */
+
+static int
+csend(void *data, int n)
+{
+ ssize_t status;
+
+ if (n == 0)
+ return 0;
+ status = xwrite(clientfd, data, n);
+ if (status < 0)
+ syswarn("OVDB: rc: cant write");
+ return status;
+}
+
+static int crecv(void *data, int n)
+{
+ int r, p = 0;
+
+ if(n == 0)
+ return 0;
+
+ while(p < n) {
+ r = read(clientfd, (char *)data + p, n - p);
+ if(r <= 0) {
+ if(r < 0 && errno == EINTR)
+ continue;
+ syslog(LOG_ERR, "OVDB: rc: cant read: %m");
+ clientfd = -1;
+ exit(1);
+ }
+ p+= r;
+ }
+ return p;
+}
+
+/* Attempt to connect to the readserver. If anything fails, we
+ return -1 so that ovdb_open can open the database directly. */
+
+static int client_connect()
+{
+ ssize_t r;
+ size_t p = 0;
+ char *path;
+#ifdef HAVE_UNIX_DOMAIN_SOCKETS
+ struct sockaddr_un sa;
+#else
+ struct sockaddr_in sa;
+#endif
+ char banner[sizeof(OVDB_SERVER_BANNER)];
+ fd_set fds;
+ struct timeval timeout;
+
+#ifdef HAVE_UNIX_DOMAIN_SOCKETS
+ clientfd = socket(AF_UNIX, SOCK_STREAM, 0);
+#else
+ clientfd = socket(AF_INET, SOCK_STREAM, 0);
+#endif
+ if(clientfd < 0) {
+ syslog(LOG_ERR, "OVDB: rc: socket: %m");
+ return -1;
+ }
+
+#ifdef HAVE_UNIX_DOMAIN_SOCKETS
+ sa.sun_family = AF_UNIX;
+ path = concatpath(innconf->pathrun, OVDB_SERVER_SOCKET);
+ strlcpy(sa.sun_path, path, sizeof(sa.sun_path));
+ free(path);
+#else
+ sa.sin_family = AF_INET;
+ sa.sin_port = 0;
+ sa.sin_addr.s_addr = htonl(INADDR_LOOPBACK);
+ bind(clientfd, (struct sockaddr *) &sa, sizeof sa);
+ sa.sin_port = htons(OVDB_SERVER_PORT);
+#endif
+ if((r = connect(clientfd, (struct sockaddr *) &sa, sizeof sa)) != 0) {
+ syslog(LOG_ERR, "OVDB: rc: cant connect to server: %m");
+ close(clientfd);
+ clientfd = -1;
+ return -1;
+ }
+
+ while(p < sizeof(OVDB_SERVER_BANNER)) {
+ FD_ZERO(&fds);
+ FD_SET(clientfd, &fds);
+ timeout.tv_sec = 30;
+ timeout.tv_usec = 0;
+
+ r = select(clientfd+1, &fds, NULL, NULL, &timeout);
+
+ if(r < 0) {
+ if(errno == EINTR)
+ continue;
+ syslog(LOG_ERR, "OVDB: rc: select: %m");
+ close(clientfd);
+ clientfd = -1;
+ return -1;
+ }
+ if(r == 0) {
+ syslog(LOG_ERR, "OVDB: rc: timeout waiting for server");
+ close(clientfd);
+ clientfd = -1;
+ return -1;
+ }
+
+ r = read(clientfd, banner + p, sizeof(OVDB_SERVER_BANNER) - p);
+ if(r <= 0) {
+ if(r < 0 && errno == EINTR)
+ continue;
+ syslog(LOG_ERR, "OVDB: rc: cant read: %m");
+ close(clientfd);
+ clientfd = -1;
+ return -1;
+ }
+ p+= r;
+ }
+
+ if(memcmp(banner, OVDB_SERVER_BANNER, sizeof(OVDB_SERVER_BANNER))) {
+ syslog(LOG_ERR, "OVDB: rc: unknown reply from server");
+ close(clientfd);
+ clientfd = -1;
+ return -1;
+ }
+ return 0;
+}
+
+static void
+client_disconnect(void)
+{
+ struct rs_cmd rs;
+
+ if (clientfd != -1) {
+ rs.what = CMD_QUIT;
+ csend(&rs, sizeof(rs));
+ }
+ clientfd = -1;
+}
+
+
+/*********** internal functions ***********/
+
+#if DB_VERSION_MAJOR == 2
+char *db_strerror(int err)
+{
+ switch(err) {
+ case DB_RUNRECOVERY:
+ return "Recovery Needed";
+ default:
+ return strerror(err);
+ }
+}
+#endif /* DB_VERSION_MAJOR == 2 */
+
+
+static bool conf_bool_val(char *str, bool *value)
+{
+ if(strcasecmp(str, "on") == 0
+ || strcasecmp(str, "true") == 0
+ || strcasecmp(str, "yes") == 0) {
+ *value = true;
+ return true;
+ }
+ if(strcasecmp(str, "off") == 0
+ || strcasecmp(str, "false") == 0
+ || strcasecmp(str, "no") == 0) {
+ *value = false;
+ return true;
+ }
+ return false;
+}
+
+static bool conf_long_val(char *str, long *value)
+{
+ long v;
+
+ errno = 0;
+ v = strtol(str, NULL, 10);
+ if(v == 0 && errno != 0) {
+ return false;
+ }
+ *value = v;
+ return true;
+}
+
+void read_ovdb_conf(void)
+{
+ static int confread = 0;
+ int done = 0;
+ char *path;
+ CONFFILE *f;
+ CONFTOKEN *tok;
+ bool b;
+ long l;
+
+ if(confread)
+ return;
+
+ /* defaults */
+ ovdb_conf.home = innconf->pathoverview;
+ ovdb_conf.txn_nosync = 1;
+ ovdb_conf.numdbfiles = 32;
+ ovdb_conf.pagesize = 8192;
+ ovdb_conf.cachesize = 8000 * 1024;
+ ovdb_conf.minkey = 0;
+ ovdb_conf.maxlocks = 4000;
+ ovdb_conf.nocompact = 1;
+ ovdb_conf.readserver = 0;
+ ovdb_conf.numrsprocs = 5;
+ ovdb_conf.maxrsconn = 0;
+ ovdb_conf.useshm = 0;
+ ovdb_conf.shmkey = 6400;
+
+ path = concatpath(innconf->pathetc, _PATH_OVDBCONF);
+ f = CONFfopen(path);
+ free(path);
+
+ if(f) {
+ while(!done && (tok = CONFgettoken(toks, f))) {
+ switch(tok->type) {
+ case OVDBtxn_nosync:
+ tok = CONFgettoken(0, f);
+ if(!tok) {
+ done = 1;
+ continue;
+ }
+ if(conf_bool_val(tok->name, &b)) {
+ ovdb_conf.txn_nosync = b;
+ }
+ break;
+ case OVDBnumdbfiles:
+ tok = CONFgettoken(0, f);
+ if(!tok) {
+ done = 1;
+ continue;
+ }
+ if(conf_long_val(tok->name, &l) && l > 0) {
+ ovdb_conf.numdbfiles = l;
+ }
+ break;
+ case OVDBpagesize:
+ tok = CONFgettoken(0, f);
+ if(!tok) {
+ done = 1;
+ continue;
+ }
+ if(conf_long_val(tok->name, &l) && l > 0) {
+ ovdb_conf.pagesize = l;
+ }
+ break;
+ case OVDBcachesize:
+ tok = CONFgettoken(0, f);
+ if(!tok) {
+ done = 1;
+ continue;
+ }
+ if(conf_long_val(tok->name, &l) && l > 0) {
+ ovdb_conf.cachesize = l * 1024;
+ }
+ break;
+ case OVDBminkey:
+ tok = CONFgettoken(0, f);
+ if(!tok) {
+ done = 1;
+ continue;
+ }
+ if(conf_long_val(tok->name, &l) && l > 1) {
+ ovdb_conf.minkey = l;
+ }
+ break;
+ case OVDBmaxlocks:
+ tok = CONFgettoken(0, f);
+ if(!tok) {
+ done = 1;
+ continue;
+ }
+ if(conf_long_val(tok->name, &l) && l > 0) {
+ ovdb_conf.maxlocks = l;
+ }
+ break;
+ case OVDBnocompact:
+ tok = CONFgettoken(0, f);
+ if(!tok) {
+ done = 1;
+ continue;
+ }
+ if(conf_long_val(tok->name, &l) && l >= 0) {
+ ovdb_conf.nocompact = l;
+ }
+ break;
+ case OVDBreadserver:
+ tok = CONFgettoken(0, f);
+ if(!tok) {
+ done = 1;
+ continue;
+ }
+ if(conf_bool_val(tok->name, &b)) {
+ ovdb_conf.readserver = b;
+ }
+ break;
+ case OVDBnumrsprocs:
+ tok = CONFgettoken(0, f);
+ if(!tok) {
+ done = 1;
+ continue;
+ }
+ if(conf_long_val(tok->name, &l) && l > 0) {
+ ovdb_conf.numrsprocs = l;
+ }
+ break;
+ case OVDBmaxrsconn:
+ tok = CONFgettoken(0, f);
+ if(!tok) {
+ done = 1;
+ continue;
+ }
+ if(conf_long_val(tok->name, &l) && l >= 0) {
+ ovdb_conf.maxrsconn = l;
+ }
+ break;
+ case OVDBuseshm:
+ tok = CONFgettoken(0, f);
+ if(!tok) {
+ done = 1;
+ continue;
+ }
+ if(conf_bool_val(tok->name, &b)) {
+ ovdb_conf.useshm = b;
+ }
+ break;
+ case OVDBshmkey:
+ tok = CONFgettoken(0, f);
+ if(!tok) {
+ done = 1;
+ continue;
+ }
+ if(conf_long_val(tok->name, &l) && l >= 0) {
+ ovdb_conf.shmkey = l;
+ }
+ break;
+ }
+ }
+ CONFfclose(f);
+ }
+
+ /* If user did not specify minkey, choose one based on pagesize */
+ if(ovdb_conf.minkey == 0) {
+ ovdb_conf.minkey = ovdb_conf.pagesize / 2048 - 1;
+ if(ovdb_conf.minkey < 2)
+ ovdb_conf.minkey = 2;
+ }
+
+ confread = 1;
+}
+
+
+/* Function that db will use to report errors */
+#if DB_VERSION_MAJOR > 4 || (DB_VERSION_MAJOR == 4 && DB_VERSION_MINOR >= 3)
+static void OVDBerror(const DB_ENV *dbenv UNUSED, const char *db_errpfx UNUSED, const char *buffer)
+#else
+static void OVDBerror(const char *db_errpfx UNUSED, char *buffer)
+#endif
+{
+ switch(ovdb_errmode) {
+ case OVDB_ERR_SYSLOG:
+ syslog(L_ERROR, "OVDB: %s", buffer);
+ break;
+ case OVDB_ERR_STDERR:
+ fprintf(stderr, "OVDB: %s\n", buffer);
+ break;
+ }
+}
+
+static u_int32_t _db_flags = 0;
+#if DB_VERSION_MAJOR == 2
+static DB_INFO _dbinfo;
+#endif
+
+static int open_db_file(int which)
+{
+ int ret;
+ char name[10];
+#if DB_VERSION_MAJOR > 4 || (DB_VERSION_MAJOR == 4 && DB_VERSION_MINOR >= 1)
+ DB_TXN *tid;
+#endif
+
+ if(dbs[which] != NULL)
+ return 0;
+
+ snprintf(name, sizeof(name), "ov%05d", which);
+
+#if DB_VERSION_MAJOR == 2
+ ret = db_open(name, DB_BTREE, _db_flags, 0666, OVDBenv, &_dbinfo,
+ &(dbs[which]));
+ if (ret != 0) {
+ dbs[which] = NULL;
+ return ret;
+ }
+#else
+ ret = db_create(&(dbs[which]), OVDBenv, 0);
+ if (ret != 0)
+ return ret;
+
+ if(ovdb_conf.minkey > 0)
+ (dbs[which])->set_bt_minkey(dbs[which], ovdb_conf.minkey);
+ if(ovdb_conf.pagesize > 0)
+ (dbs[which])->set_pagesize(dbs[which], ovdb_conf.pagesize);
+
+#if DB_VERSION_MAJOR > 4 || (DB_VERSION_MAJOR == 4 && DB_VERSION_MINOR >= 1)
+ TXN_START(t_open_db_file, tid);
+ ret = (dbs[which])->open(dbs[which], tid, name, NULL, DB_BTREE, _db_flags,
+ 0666);
+ if (ret == 0)
+ TXN_COMMIT(t_open_db_file, tid);
+#else
+ ret = (dbs[which])->open(dbs[which], name, NULL, DB_BTREE, _db_flags,
+ 0666);
+#endif
+ if (ret != 0) {
+ (dbs[which])->close(dbs[which], 0);
+ dbs[which] = NULL;
+ return ret;
+ }
+#endif
+ return 0;
+}
+
+static void close_db_file(int which)
+{
+ if(which == -1 || dbs[which] == NULL)
+ return;
+
+ dbs[which]->close(dbs[which], 0);
+ dbs[which] = NULL;
+}
+
+static int which_db(char *group)
+{
+ HASH grouphash;
+ unsigned int i;
+
+ grouphash = Hash(group, strlen(group));
+ memcpy(&i, &grouphash, sizeof(i));
+ return i % ovdb_conf.numdbfiles;
+}
+
+static DB *get_db_bynum(int which)
+{
+ int ret;
+ if(which >= ovdb_conf.numdbfiles)
+ return NULL;
+ if(oneatatime) {
+ if(which != current_db && current_db != -1)
+ close_db_file(current_db);
+
+ ret = open_db_file(which);
+ if (ret != 0)
+ syslog(L_ERROR, "OVDB: open_db_file failed: %s", db_strerror(ret));
+
+ current_db = which;
+ }
+ return(dbs[which]);
+}
+
+
+int ovdb_getgroupinfo(char *group, struct groupinfo *gi, int ignoredeleted, DB_TXN *tid, int getflags)
+{
+ int ret;
+ DBT key, val;
+
+ if(group == NULL) /* just in case */
+ return DB_NOTFOUND;
+
+ memset(&key, 0, sizeof key);
+ memset(&val, 0, sizeof val);
+
+ key.data = group;
+ key.size = strlen(group);
+ val.data = gi;
+ val.ulen = sizeof(struct groupinfo);
+ val.flags = DB_DBT_USERMEM;
+
+ ret = groupinfo->get(groupinfo, tid, &key, &val, getflags);
+ if (ret != 0)
+ return ret;
+
+ if(val.size != sizeof(struct groupinfo)) {
+ syslog(L_ERROR, "OVDB: wrong size for %s groupinfo (%u)",
+ group, val.size);
+ return DB_NOTFOUND;
+ }
+
+ if(ignoredeleted && (gi->status & GROUPINFO_DELETED))
+ return DB_NOTFOUND;
+
+ return 0;
+}
+
+#define GROUPID_MAX_FREELIST 10240
+#define GROUPID_MIN_FREELIST 100
+
+/* allocate a new group ID and return in gno */
+/* must be used in a transaction */
+static int groupid_new(group_id_t *gno, DB_TXN *tid)
+{
+ DBT key, val;
+ int ret, n;
+ group_id_t newgno, *freelist, one;
+
+ memset(&key, 0, sizeof key);
+ memset(&val, 0, sizeof val);
+
+ key.data = (char *) "!groupid_freelist";
+ key.size = sizeof("!groupid_freelist");
+
+ ret = groupinfo->get(groupinfo, tid, &key, &val, DB_RMW);
+ if (ret != 0) {
+ if(ret == DB_NOTFOUND) {
+ val.size = sizeof(group_id_t);
+ val.data = &one;
+ one = 1;
+ } else {
+ return ret;
+ }
+ }
+
+ if(val.size % sizeof(group_id_t)) {
+ syslog(L_ERROR, "OVDB: invalid size (%d) for !groupid_freelist",
+ val.size);
+ return EINVAL;
+ }
+
+ n = val.size / sizeof(group_id_t);
+ freelist = xmalloc(n * sizeof(group_id_t));
+ memcpy(freelist, val.data, val.size);
+ if(n <= GROUPID_MIN_FREELIST ) {
+ newgno = freelist[n-1];
+ (freelist[n-1])++;
+ val.data = freelist;
+ } else {
+ newgno = freelist[0];
+ val.data = &(freelist[1]);
+ val.size -= sizeof(group_id_t);
+ }
+
+ ret = groupinfo->put(groupinfo, tid, &key, &val, 0);
+ if (ret != 0) {
+ free(freelist);
+ return ret;
+ }
+
+ free(freelist);
+ *gno = newgno;
+ return 0;
+}
+
+/* mark given group ID as "unused" */
+/* must be used in a transaction */
+static int groupid_free(group_id_t gno, DB_TXN *tid)
+{
+ DBT key, val;
+ int ret, n, i;
+ group_id_t *freelist;
+
+ memset(&key, 0, sizeof key);
+ memset(&val, 0, sizeof val);
+
+ key.data = (char *) "!groupid_freelist";
+ key.size = sizeof("!groupid_freelist");
+
+ ret = groupinfo->get(groupinfo, tid, &key, &val, DB_RMW);
+ if (ret != 0) {
+ return ret;
+ }
+
+ if(val.size % sizeof(group_id_t)) {
+ syslog(L_ERROR, "OVDB: invalid size (%d) for !groupid_freelist",
+ val.size);
+ return EINVAL;
+ }
+
+ n = val.size / sizeof(group_id_t);
+ if(n > GROUPID_MAX_FREELIST)
+ return 0;
+ freelist = xmalloc((n + 1) * sizeof(group_id_t));
+ memcpy(freelist, val.data, val.size);
+
+ if(gno >= freelist[n-1]) { /* shouldn't happen */
+ free(freelist);
+ return 0;
+ }
+ for(i = 0; i < n-1; i++) {
+ if(gno == freelist[i]) { /* already on freelist */
+ free(freelist);
+ return 0;
+ }
+ }
+
+ freelist[n] = freelist[n-1];
+ freelist[n-1] = gno;
+ val.data = freelist;
+ val.size += sizeof(group_id_t);
+
+ ret = groupinfo->put(groupinfo, tid, &key, &val, 0);
+
+ free(freelist);
+ return ret;
+}
+
+/* Must be called outside of a transaction because it makes its own
+ transactions */
+static int delete_all_records(int whichdb, group_id_t gno)
+{
+ DB *db;
+ DBC *dbcursor;
+ DBT key, val;
+ struct datakey dk;
+ int count;
+ int ret;
+ DB_TXN *tid;
+
+ memset(&key, 0, sizeof key);
+ memset(&val, 0, sizeof val);
+ memset(&dk, 0, sizeof dk);
+
+ db = get_db_bynum(whichdb);
+ if(db == NULL)
+ return DB_NOTFOUND;
+
+ dk.groupnum = gno;
+ dk.artnum = 0;
+
+ while(1) {
+ TXN_START(t_del, tid);
+
+ /* get a cursor to traverse the ov records and delete them */
+ ret = db->cursor(db, tid, &dbcursor, 0);
+ switch(ret)
+ {
+ case 0:
+ break;
+ case TRYAGAIN:
+ TXN_RETRY(t_del, tid);
+ default:
+ TXN_ABORT(t_del, tid);
+ syslog(L_ERROR, "OVDB: delete_all_records: db->cursor: %s", db_strerror(ret));
+ return ret;
+ }
+
+ key.data = &dk;
+ key.size = sizeof dk;
+ val.flags = DB_DBT_PARTIAL;
+
+ for(count = 0; count < DELETE_TXN_SIZE; count++) {
+ ret = dbcursor->c_get(dbcursor, &key, &val,
+ count ? DB_NEXT : DB_SET_RANGE);
+ switch (ret)
+ {
+ case 0:
+ break;
+ case DB_NOTFOUND:
+ dbcursor->c_close(dbcursor);
+ TXN_COMMIT(t_del, tid);
+ return 0;
+ case TRYAGAIN:
+ dbcursor->c_close(dbcursor);
+ TXN_RETRY(t_del, tid);
+ default:
+ warn("OVDB: delete_all_records: DBcursor->c_get: %s",
+ db_strerror(ret));
+ dbcursor->c_close(dbcursor);
+ TXN_ABORT(t_del, tid);
+ return ret;
+ }
+
+ if(key.size == sizeof dk
+ && memcmp(key.data, &gno, sizeof gno)) {
+ break;
+ }
+
+ ret = dbcursor->c_del(dbcursor, 0);
+ switch (ret) {
+ case 0:
+ case DB_KEYEMPTY:
+ break;
+ case TRYAGAIN:
+ dbcursor->c_close(dbcursor);
+ TXN_RETRY(t_del, tid);
+ default:
+ warn("OVDB: delete_all_records: DBcursor->c_del: %s",
+ db_strerror(ret));
+ dbcursor->c_close(dbcursor);
+ TXN_ABORT(t_del, tid);
+ return ret;
+ }
+ }
+ dbcursor->c_close(dbcursor);
+ TXN_COMMIT(t_del, tid);
+ if(count < DELETE_TXN_SIZE) {
+ break;
+ }
+ }
+ return 0;
+}
+
+/* Make a temporary groupinfo key using the given db number and group ID.
+ Must be called in a transaction */
+static int
+mk_temp_groupinfo(int whichdb, group_id_t gno, DB_TXN *tid)
+{
+ char keystr[1 + sizeof gno];
+ DBT key, val;
+ struct groupinfo gi;
+ int ret;
+
+ memset(&key, 0, sizeof key);
+ memset(&val, 0, sizeof val);
+ memset(&gi, 0, sizeof gi);
+
+ keystr[0] = 0;
+ memcpy(keystr + 1, &gno, sizeof gno);
+
+ gi.current_db = whichdb;
+ gi.current_gid = gno;
+ gi.status = GROUPINFO_DELETED;
+
+ key.data = keystr;
+ key.size = sizeof keystr;
+ val.data = &gi;
+ val.size = sizeof gi;
+
+ ret = groupinfo->put(groupinfo, tid, &key, &val, 0);
+ switch (ret) {
+ case 0:
+ break;
+ default:
+ syslog(L_ERROR, "OVDB: mk_temp_groupinfo: groupinfo->put: %s", db_strerror(ret));
+ case TRYAGAIN:
+ return ret;
+ }
+
+ return 0;
+}
+
+/* Delete a temporary groupinfo key created by mk_temp_groupid, then
+ frees the group id.
+ Must NOT be called within a transaction. */
+static int
+rm_temp_groupinfo(group_id_t gno)
+{
+ char keystr[1 + sizeof gno];
+ DB_TXN *tid;
+ DBT key;
+ int ret;
+
+ memset(&key, 0, sizeof key);
+
+ keystr[0] = 0;
+ memcpy(keystr + 1, &gno, sizeof gno);
+
+ key.data = keystr;
+ key.size = sizeof keystr;
+
+ TXN_START(t_tmp, tid);
+
+ ret = groupinfo->del(groupinfo, tid, &key, 0);
+ switch(ret) {
+ case 0:
+ case DB_NOTFOUND:
+ break;
+ case TRYAGAIN:
+ TXN_RETRY(t_tmp, tid);
+ default:
+ TXN_ABORT(t_tmp, tid);
+ syslog(L_ERROR, "OVDB: rm_temp_groupinfo: groupinfo->del: %s", db_strerror(ret));
+ return ret;
+ }
+
+ switch(groupid_free(gno, tid)) {
+ case 0:
+ break;
+ case TRYAGAIN:
+ TXN_RETRY(t_tmp, tid);
+ default:
+ TXN_ABORT(t_tmp, tid);
+ syslog(L_ERROR, "OVDB: rm_temp_groupinfo: groupid_free: %s", db_strerror(ret));
+ return ret;
+ }
+
+ TXN_COMMIT(t_tmp, tid);
+ return 0;
+}
+
+/* This function deletes overview records for deleted or forgotton groups */
+/* argument: 0 = process deleted groups 1 = process forgotton groups */
+static bool delete_old_stuff(int forgotton)
+{
+ DBT key, val;
+ DBC *cursor;
+ DB_TXN *tid;
+ struct groupinfo gi;
+ char **dellist = NULL;
+ size_t *dellistsz = NULL;
+ int listlen, listcount;
+ int i, ret;
+
+ TXN_START(t_dellist, tid);
+ if (dellist != NULL) {
+ for (i = 0; i < listcount; ++i)
+ free(dellist[i]);
+ free(dellist);
+ }
+ if (dellistsz != NULL)
+ free(dellistsz);
+ listlen = 20;
+ listcount = 0;
+ dellist = xmalloc(listlen * sizeof(char *));
+ dellistsz = xmalloc(listlen * sizeof(size_t));
+
+ memset(&key, 0, sizeof key);
+ memset(&val, 0, sizeof val);
+
+ val.data = &gi;
+ val.ulen = val.dlen = sizeof gi;
+ val.flags = DB_DBT_USERMEM | DB_DBT_PARTIAL;
+
+ /* get a cursor to traverse all of the groupinfo records */
+ ret = groupinfo->cursor(groupinfo, tid, &cursor, 0);
+ if (ret != 0) {
+ syslog(L_ERROR, "OVDB: delete_old_stuff: groupinfo->cursor: %s", db_strerror(ret));
+ free(dellist);
+ free(dellistsz);
+ return false;
+ }
+
+ while((ret = cursor->c_get(cursor, &key, &val, DB_NEXT)) == 0) {
+ if(key.size == sizeof("!groupid_freelist") &&
+ !strcmp("!groupid_freelist", key.data))
+ continue;
+ if(val.size != sizeof(struct groupinfo)) {
+ syslog(L_ERROR, "OVDB: delete_old_stuff: wrong size for groupinfo record");
+ continue;
+ }
+ if((!forgotton && (gi.status & GROUPINFO_DELETED))
+ || (forgotton && (gi.expired < eo_start))) {
+ dellist[listcount] = xmalloc(key.size);
+ memcpy(dellist[listcount], key.data, key.size);
+ dellistsz[listcount] = key.size;
+ listcount++;
+ if(listcount >= listlen) {
+ listlen += 20;
+ dellist = xrealloc(dellist, listlen * sizeof(char *));
+ dellistsz = xrealloc(dellistsz, listlen * sizeof(size_t));
+ }
+ }
+ }
+ cursor->c_close(cursor);
+ switch (ret) {
+ case 0:
+ case DB_NOTFOUND:
+ TXN_COMMIT(t_dellist, tid);
+ break;
+ case TRYAGAIN:
+ TXN_RETRY(t_dellist, tid);
+ default:
+ TXN_ABORT(t_dellist, tid);
+ syslog(L_ERROR, "OVDB: delete_old_stuff: cursor->c_get: %s", db_strerror(ret));
+ for (i = 0; i < listcount; ++i)
+ free(dellist[i]);
+ free(dellist);
+ free(dellistsz);
+ return false;
+ }
+
+ for(i = 0; i < listcount; i++) {
+ TXN_START(t_dos, tid);
+
+ /* Can't use ovdb_getgroupinfo here */
+ key.data = dellist[i];
+ key.size = dellistsz[i];
+ val.data = &gi;
+ val.ulen = sizeof(struct groupinfo);
+ val.flags = DB_DBT_USERMEM;
+
+ ret = groupinfo->get(groupinfo, tid, &key, &val, 0);
+
+ switch (ret)
+ {
+ case 0:
+ break;
+ case TRYAGAIN:
+ TXN_RETRY(t_dos, tid);
+ case DB_NOTFOUND:
+ TXN_ABORT(t_dos, tid);
+ continue;
+ default:
+ syslog(L_ERROR, "OVDB: delete_old_stuff: groupinfo->get: %s\n", db_strerror(ret));
+ TXN_ABORT(t_dos, tid);
+ continue;
+ }
+ if(val.size != sizeof(struct groupinfo)) {
+ TXN_ABORT(t_dos, tid);
+ continue;
+ }
+
+ /* This if() is true if this ISN'T a key created by mk_temp_groupinfo */
+ if(*((char *)key.data) != 0 || key.size != 1 + sizeof(group_id_t)) {
+
+ switch(mk_temp_groupinfo(gi.current_db, gi.current_gid, tid)) {
+ case 0:
+ break;
+ case TRYAGAIN:
+ TXN_RETRY(t_dos, tid);
+ default:
+ TXN_ABORT(t_dos, tid);
+ continue;
+ }
+ if(gi.status & GROUPINFO_MOVING) {
+ switch(mk_temp_groupinfo(gi.new_db, gi.new_gid, tid)) {
+ case 0:
+ break;
+ case TRYAGAIN:
+ TXN_RETRY(t_dos, tid);
+ default:
+ TXN_ABORT(t_dos, tid);
+ continue;
+ }
+ }
+
+ /* delete the corresponding groupaliases record (if exists) */
+ switch(groupaliases->del(groupaliases, tid, &key, 0)) {
+ case 0:
+ case DB_NOTFOUND:
+ break;
+ case TRYAGAIN:
+ TXN_RETRY(t_dos, tid);
+ default:
+ TXN_ABORT(t_dos, tid);
+ continue;
+ }
+
+ switch(groupinfo->del(groupinfo, tid, &key, 0)) {
+ case 0:
+ case DB_NOTFOUND:
+ break;
+ case TRYAGAIN:
+ TXN_RETRY(t_dos, tid);
+ default:
+ TXN_ABORT(t_dos, tid);
+ continue;
+ }
+ }
+
+ TXN_COMMIT(t_dos, tid);
+
+ if(delete_all_records(gi.current_db, gi.current_gid) == 0) {
+ rm_temp_groupinfo(gi.current_gid);
+ }
+ if(gi.status & GROUPINFO_MOVING) {
+ if(delete_all_records(gi.new_db, gi.new_gid) == 0) {
+ rm_temp_groupinfo(gi.new_gid);
+ }
+ }
+ }
+out:
+ for(i = 0; i < listcount; i++)
+ free(dellist[i]);
+ free(dellist);
+ free(dellistsz);
+ return true;
+}
+
+static int count_records(struct groupinfo *gi)
+{
+ int ret;
+ DB *db;
+ DBC *cursor;
+ DBT key, val;
+ struct datakey dk;
+ u_int32_t artnum, newlow = 0;
+
+ memset(&key, 0, sizeof key);
+ memset(&val, 0, sizeof val);
+ memset(&dk, 0, sizeof dk);
+
+ db = get_db_bynum(gi->current_db);
+ if(db == NULL)
+ return DB_NOTFOUND;
+
+ dk.groupnum = gi->current_gid;
+ dk.artnum = 0;
+ key.data = &dk;
+ key.size = key.ulen = sizeof dk;
+ key.flags = DB_DBT_USERMEM;
+ val.flags = DB_DBT_PARTIAL;
+
+ gi->count = 0;
+
+ ret = db->cursor(db, NULL, &cursor, 0);
+ if (ret)
+ return ret;
+
+ ret = cursor->c_get(cursor, &key, &val, DB_SET_RANGE);
+ switch (ret)
+ {
+ case 0:
+ case DB_NOTFOUND:
+ break;
+ default:
+ cursor->c_close(cursor);
+ return ret;
+ }
+ while(ret == 0 && key.size == sizeof(dk) && dk.groupnum == gi->current_gid) {
+ artnum = ntohl(dk.artnum);
+ if(newlow == 0 || newlow > artnum)
+ newlow = artnum;
+ if(artnum > gi->high)
+ gi->high = artnum;
+ gi->count++;
+
+ ret = cursor->c_get(cursor, &key, &val, DB_NEXT);
+ }
+ cursor->c_close(cursor);
+ if(gi->count == 0)
+ gi->low = gi->high + 1;
+ else
+ gi->low = newlow;
+
+ if(ret == DB_NOTFOUND)
+ return 0;
+ return ret;
+}
+
+
+/*
+ * Locking: OVopen() calls ovdb_getlock(OVDB_LOCK_NORMAL). This
+ * aquires a read (shared) lock on the lockfile. Multiple processes
+ * can have this file locked at the same time. That way, if there
+ * are any processes that are using the database, the lock file will
+ * have one or more shared locks on it.
+ *
+ * ovdb_init, when starting, calls ovdb_getlock(OVDB_LOCK_EXCLUSIVE).
+ * This tries to get a write (exclusive) lock, which will fail if
+ * anyone has a shared lock. This way, ovdb_init can tell if there
+ * are any processes using the database. If not, and the excl. lock
+ * succeeds, ovdb_init is free to do DB_RUNRECOVER.
+ *
+ * ovdb_getlock() in the "normal" lock mode calls ovdb_check_monitor,
+ * which looks for the OVDB_MONITOR_PIDFILE. If said file does not
+ * exist, or the PID in it does not exist, it will fail. This will
+ * prevent OVopen() from opening the database if ovdb_monitor is not running.
+ *
+ * The OVDB_LOCK_ADMIN mode is used by ovdb_monitor to get a shared lock
+ * without testing the pidfile.
+ */
+static int lockfd = -1;
+bool ovdb_getlock(int mode)
+{
+ if(lockfd == -1) {
+ char *lockfn = concatpath(innconf->pathrun, OVDB_LOCKFN);
+ lockfd = open(lockfn,
+ mode == OVDB_LOCK_NORMAL ? O_RDWR : O_CREAT|O_RDWR, 0660);
+ if(lockfd == -1) {
+ free(lockfn);
+ if(errno == ENOENT)
+ syslog(L_FATAL, "OVDB: can not open database unless ovdb_monitor is running.");
+ return false;
+ }
+ close_on_exec(lockfd, true);
+ free(lockfn);
+ } else
+ return true;
+
+ if(mode == OVDB_LOCK_NORMAL) {
+ if(!ovdb_check_pidfile(OVDB_MONITOR_PIDFILE)) {
+ syslog(L_FATAL, "OVDB: can not open database unless ovdb_monitor is running.");
+ return false;
+ }
+ }
+ if(mode == OVDB_LOCK_EXCLUSIVE) {
+ if(!inn_lock_file(lockfd, INN_LOCK_WRITE, false)) {
+ close(lockfd);
+ lockfd = -1;
+ return false;
+ }
+ return true;
+ } else {
+ if(!inn_lock_file(lockfd, INN_LOCK_READ, false)) {
+ close(lockfd);
+ lockfd = -1;
+ return false;
+ }
+ return true;
+ }
+}
+
+bool ovdb_releaselock(void)
+{
+ bool r;
+ if(lockfd == -1)
+ return true;
+ r = inn_lock_file(lockfd, INN_LOCK_UNLOCK, false);
+ close(lockfd);
+ lockfd = -1;
+ return r;
+}
+
+bool ovdb_check_pidfile(char *file)
+{
+ int f, pid;
+ char buf[SMBUF];
+ char *pidfn = concatpath(innconf->pathrun, file);
+
+ f = open(pidfn, O_RDONLY);
+ if(f == -1) {
+ if(errno != ENOENT)
+ syslog(L_FATAL, "OVDB: can't open %s: %m", pidfn);
+ free(pidfn);
+ return false;
+ }
+ memset(buf, 0, SMBUF);
+ if(read(f, buf, SMBUF-1) < 0) {
+ syslog(L_FATAL, "OVDB: can't read from %s: %m", pidfn);
+ free(pidfn);
+ close(f);
+ return false;
+ }
+ close(f);
+ free(pidfn);
+ pid = atoi(buf);
+ if(pid <= 1) {
+ return false;
+ }
+ if(kill(pid, 0) < 0 && errno == ESRCH) {
+ return false;
+ }
+ return true;
+}
+
+/* make sure the effective uid is that of NEWSUSER */
+bool ovdb_check_user(void)
+{
+ struct passwd *p;
+ static int result = -1;
+
+ if(result == -1) {
+ p = getpwnam(NEWSUSER);
+ if(!p) {
+ syslog(L_FATAL, "OVDB: getpwnam(" NEWSUSER ") failed: %m");
+ return false;
+ }
+ result = (p->pw_uid == geteuid());
+ }
+ return result;
+}
+
+static int check_version(void)
+{
+ int ret;
+ DB *vdb;
+ DBT key, val;
+ u_int32_t dv;
+
+#if DB_VERSION_MAJOR == 2
+ DB_INFO dbinfo;
+ memset(&dbinfo, 0, sizeof dbinfo);
+
+ ret = db_open("version", DB_BTREE, _db_flags, 0666, OVDBenv, &dbinfo,
+ &vdb);
+ if (ret != 0) {
+ syslog(L_FATAL, "OVDB: db_open failed: %s", db_strerror(ret));
+ return ret;
+ }
+#else
+ /* open version db */
+ ret = db_create(&vdb, OVDBenv, 0);
+ if (ret != 0) {
+ syslog(L_FATAL, "OVDB: open: db_create: %s", db_strerror(ret));
+ return ret;
+ }
+#if DB_VERSION_MAJOR > 4 || (DB_VERSION_MAJOR == 4 && DB_VERSION_MINOR >= 1)
+ ret = vdb->open(vdb, NULL, "version", NULL, DB_BTREE, _db_flags, 0666);
+#else
+ ret = vdb->open(vdb, "version", NULL, DB_BTREE, _db_flags, 0666);
+#endif
+ if (ret != 0) {
+ vdb->close(vdb, 0);
+ syslog(L_FATAL, "OVDB: open: version->open: %s", db_strerror(ret));
+ return ret;
+ }
+#endif
+ memset(&key, 0, sizeof key);
+ memset(&val, 0, sizeof val);
+ key.data = (char *) "dataversion";
+ key.size = sizeof("dataversion");
+ ret = vdb->get(vdb, NULL, &key, &val, 0);
+ if (ret != 0) {
+ if(ret != DB_NOTFOUND) {
+ syslog(L_FATAL, "OVDB: open: can't retrieve version: %s", db_strerror(ret));
+ vdb->close(vdb, 0);
+ return ret;
+ }
+ }
+ if(ret == DB_NOTFOUND || val.size != sizeof dv) {
+ dv = DATA_VERSION;
+ if(!(OVDBmode & OV_WRITE)) {
+ vdb->close(vdb, 0);
+ return EACCES;
+ }
+ val.data = &dv;
+ val.size = sizeof dv;
+ ret = vdb->put(vdb, NULL, &key, &val, 0);
+ if (ret != 0) {
+ syslog(L_FATAL, "OVDB: open: can't store version: %s", db_strerror(ret));
+ vdb->close(vdb, 0);
+ return ret;
+ }
+ } else
+ memcpy(&dv, val.data, sizeof dv);
+
+ if(dv > DATA_VERSION) {
+ syslog(L_FATAL, "OVDB: can't open database: unknown version %d", dv);
+ vdb->close(vdb, 0);
+ return EINVAL;
+ }
+ if(dv < DATA_VERSION) {
+ syslog(L_FATAL, "OVDB: database is an old version, please run ovdb_init");
+ vdb->close(vdb, 0);
+ return EINVAL;
+ }
+
+ /* The version db also stores some config values, which will override the
+ corresponding ovdb.conf setting. */
+
+ key.data = (char *) "numdbfiles";
+ key.size = sizeof("numdbfiles");
+ ret = vdb->get(vdb, NULL, &key, &val, 0);
+ if (ret != 0) {
+ if(ret != DB_NOTFOUND) {
+ syslog(L_FATAL, "OVDB: open: can't retrieve numdbfiles: %s", db_strerror(ret));
+ vdb->close(vdb, 0);
+ return ret;
+ }
+ }
+ if(ret == DB_NOTFOUND || val.size != sizeof(ovdb_conf.numdbfiles)) {
+ if(!(OVDBmode & OV_WRITE)) {
+ vdb->close(vdb, 0);
+ return EACCES;
+ }
+ val.data = &(ovdb_conf.numdbfiles);
+ val.size = sizeof(ovdb_conf.numdbfiles);
+ ret = vdb->put(vdb, NULL, &key, &val, 0);
+ if (ret != 0) {
+ syslog(L_FATAL, "OVDB: open: can't store numdbfiles: %s", db_strerror(ret));
+ vdb->close(vdb, 0);
+ return ret;
+ }
+ } else {
+ memcpy(&(ovdb_conf.numdbfiles), val.data, sizeof(ovdb_conf.numdbfiles));
+ }
+
+ vdb->close(vdb, 0);
+ return 0;
+}
+
+
+int ovdb_open_berkeleydb(int mode, int flags)
+{
+ int ret;
+ u_int32_t ai_flags = DB_INIT_LOCK|DB_INIT_LOG|DB_INIT_MPOOL|DB_INIT_TXN;
+
+ OVDBmode = mode;
+ read_ovdb_conf();
+
+ if(OVDBenv != NULL)
+ return 0; /* already opened */
+
+ if(OVDBmode & OV_WRITE) {
+ _db_flags |= DB_CREATE;
+ ai_flags |= DB_CREATE;
+ } else {
+ _db_flags |= DB_RDONLY;
+ }
+ if(flags & OVDB_RECOVER)
+ ai_flags |= DB_RECOVER;
+
+#if DB_VERSION_MAJOR == 2 || (DB_VERSION_MAJOR == 3 && DB_VERSION_MINOR < 2)
+ if(ovdb_conf.txn_nosync)
+ ai_flags |= DB_TXN_NOSYNC;
+#endif
+
+#if DB_VERSION_MAJOR == 2
+
+ OVDBenv = xcalloc(1, sizeof(DB_ENV));
+
+ OVDBenv->db_errcall = OVDBerror;
+ OVDBenv->mp_size = ovdb_conf.cachesize;
+ OVDBenv->lk_max = ovdb_conf.maxlocks;
+
+ /* initialize environment */
+ ret = db_appinit(ovdb_conf.home, NULL, OVDBenv, ai_flags);
+ if (ret != 0) {
+ free(OVDBenv);
+ OVDBenv = NULL;
+ syslog(L_FATAL, "OVDB: db_appinit failed: %s", db_strerror(ret));
+ return ret;
+ }
+#else
+ ret = db_env_create(&OVDBenv, 0);
+ if (ret != 0) {
+ syslog(L_FATAL, "OVDB: db_env_create: %s", db_strerror(ret));
+ return ret;
+ }
+
+ if((flags & (OVDB_UPGRADE | OVDB_RECOVER)) == (OVDB_UPGRADE | OVDB_RECOVER))
+ ai_flags |= DB_PRIVATE;
+ if(!(ai_flags & DB_PRIVATE)) {
+ if(ovdb_conf.useshm)
+ ai_flags |= DB_SYSTEM_MEM;
+#if DB_VERSION_MAJOR >= 4 || (DB_VERSION_MAJOR == 3 && DB_VERSION_MINOR > 0)
+ OVDBenv->set_shm_key(OVDBenv, ovdb_conf.shmkey);
+#endif
+ }
+
+ OVDBenv->set_errcall(OVDBenv, OVDBerror);
+ OVDBenv->set_cachesize(OVDBenv, 0, ovdb_conf.cachesize, 1);
+#if DB_VERSION_MAJOR >= 4
+ OVDBenv->set_lk_max_locks(OVDBenv, ovdb_conf.maxlocks);
+ OVDBenv->set_lk_max_lockers(OVDBenv, ovdb_conf.maxlocks);
+ OVDBenv->set_lk_max_objects(OVDBenv, ovdb_conf.maxlocks);
+#else
+ OVDBenv->set_lk_max(OVDBenv, ovdb_conf.maxlocks);
+#endif
+
+#if DB_VERSION_MAJOR >= 4 || (DB_VERSION_MAJOR == 3 && DB_VERSION_MINOR >= 2)
+ if(ovdb_conf.txn_nosync)
+ OVDBenv->set_flags(OVDBenv, DB_TXN_NOSYNC, 1);
+#endif
+
+ if((flags & (OVDB_UPGRADE | OVDB_RECOVER)) != OVDB_UPGRADE) {
+#if DB_VERSION_MAJOR == 3 && DB_VERSION_MINOR == 0
+ ret = OVDBenv->open(OVDBenv, ovdb_conf.home, NULL, ai_flags, 0666);
+#else
+ ret = OVDBenv->open(OVDBenv, ovdb_conf.home, ai_flags, 0666);
+#endif
+ if (ret != 0) {
+ OVDBenv->close(OVDBenv, 0);
+ OVDBenv = NULL;
+ syslog(L_FATAL, "OVDB: OVDBenv->open: %s", db_strerror(ret));
+ return ret;
+ }
+ }
+#endif /* DB_VERSION_MAJOR == 2 */
+
+ return 0;
+}
+
+bool ovdb_open(int mode)
+{
+ int i, ret;
+#if DB_VERSION_MAJOR == 2
+ DB_INFO dbinfo;
+#else
+ DB_TXN *tid;
+#endif
+
+ if(OVDBenv != NULL || clientmode) {
+ syslog(L_ERROR, "OVDB: ovdb_open called more than once");
+ return false;
+ }
+
+ read_ovdb_conf();
+ if(ovdb_conf.readserver && mode == OV_READ)
+ clientmode = 1;
+
+ if(mode & OVDB_SERVER)
+ clientmode = 0;
+
+ if(clientmode) {
+ if(client_connect() == 0)
+ return true;
+ clientmode = 0;
+ }
+ if(!ovdb_check_user()) {
+ syslog(L_FATAL, "OVDB: must be running as " NEWSUSER " to access overview.");
+ return false;
+ }
+ if(!ovdb_getlock(OVDB_LOCK_NORMAL)) {
+ syslog(L_FATAL, "OVDB: ovdb_getlock failed: %m");
+ return false;
+ }
+
+ if(ovdb_open_berkeleydb(mode, 0) != 0)
+ return false;
+
+ if(check_version() != 0)
+ return false;
+
+ if(mode & OV_WRITE || mode & OVDB_SERVER) {
+ oneatatime = 0;
+ } else {
+ oneatatime = 1;
+ }
+
+#if DB_VERSION_MAJOR == 2
+ memset(&_dbinfo, 0, sizeof _dbinfo);
+ _dbinfo.db_pagesize = ovdb_conf.pagesize;
+ _dbinfo.bt_minkey = ovdb_conf.minkey;
+#endif
+
+ dbs = xcalloc(ovdb_conf.numdbfiles, sizeof(DB *));
+
+ if(!oneatatime) {
+ for(i = 0; i < ovdb_conf.numdbfiles; i++) {
+ ret = open_db_file(i);
+ if (ret != 0) {
+ syslog(L_FATAL, "OVDB: open_db_file failed: %s", db_strerror(ret));
+ return false;
+ }
+ }
+ }
+
+#if DB_VERSION_MAJOR == 2
+ memset(&dbinfo, 0, sizeof dbinfo);
+
+ ret = db_open("groupinfo", DB_BTREE, _db_flags, 0666, OVDBenv,
+ &dbinfo, &groupinfo);
+ if (ret != 0) {
+ syslog(L_FATAL, "OVDB: db_open failed: %s", db_strerror(ret));
+ return false;
+ }
+
+ ret = db_open("groupaliases", DB_HASH, _db_flags, 0666, OVDBenv,
+ &dbinfo, &groupaliases);
+ if (ret != 0) {
+ syslog(L_FATAL, "OVDB: db_open failed: %s", db_strerror(ret));
+ return false;
+ }
+#else
+ ret = db_create(&groupinfo, OVDBenv, 0);
+ if (ret != 0) {
+ syslog(L_FATAL, "OVDB: open: db_create: %s", db_strerror(ret));
+ return false;
+ }
+#if DB_VERSION_MAJOR > 4 || (DB_VERSION_MAJOR == 4 && DB_VERSION_MINOR >= 1)
+ TXN_START(t_open_groupinfo, tid);
+ ret = groupinfo->open(groupinfo, tid, "groupinfo", NULL, DB_BTREE,
+ _db_flags, 0666);
+ if (ret == 0)
+ TXN_COMMIT(t_open_groupinfo, tid);
+#else
+ ret = groupinfo->open(groupinfo, "groupinfo", NULL, DB_BTREE,
+ _db_flags, 0666);
+#endif
+ if (ret != 0) {
+ groupinfo->close(groupinfo, 0);
+ syslog(L_FATAL, "OVDB: open: groupinfo->open: %s", db_strerror(ret));
+ return false;
+ }
+ ret = db_create(&groupaliases, OVDBenv, 0);
+ if (ret != 0) {
+ syslog(L_FATAL, "OVDB: open: db_create: %s", db_strerror(ret));
+ return false;
+ }
+#if DB_VERSION_MAJOR > 4 || (DB_VERSION_MAJOR == 4 && DB_VERSION_MINOR >= 1)
+ TXN_START(t_open_groupaliases, tid);
+ ret = groupaliases->open(groupaliases, tid, "groupaliases", NULL, DB_HASH,
+ _db_flags, 0666);
+ if (ret == 0)
+ TXN_COMMIT(t_open_groupaliases, tid);
+#else
+ ret = groupaliases->open(groupaliases, "groupaliases", NULL, DB_HASH,
+ _db_flags, 0666);
+#endif
+ if (ret != 0) {
+ groupaliases->close(groupaliases, 0);
+ syslog(L_FATAL, "OVDB: open: groupaliases->open: %s", db_strerror(ret));
+ return false;
+ }
+#endif
+
+ Cutofflow = false;
+ return true;
+}
+
+
+bool
+ovdb_groupstats(char *group, int *lo, int *hi, int *count, int *flag)
+{
+ int ret;
+ struct groupinfo gi;
+
+ if (clientmode) {
+ struct rs_cmd rs;
+ struct rs_groupstats repl;
+
+ rs.what = CMD_GROUPSTATS;
+ rs.grouplen = strlen(group)+1;
+
+ if (csend(&rs, sizeof(rs)) < 0)
+ return false;
+ if (csend(group, rs.grouplen) < 0)
+ return false;
+ crecv(&repl, sizeof(repl));
+
+ if(repl.status != CMD_GROUPSTATS)
+ return false;
+
+ /* we don't use the alias yet, but the OV API will be extended
+ at some point so that the alias is returned also */
+ if(repl.aliaslen != 0) {
+ char *buf = xmalloc(repl.aliaslen);
+ crecv(buf, repl.aliaslen);
+ free(buf);
+ }
+
+ if(lo)
+ *lo = repl.lo;
+ if(hi)
+ *hi = repl.hi;
+ if(count)
+ *count = repl.count;
+ if(flag)
+ *flag = repl.flag;
+ return true;
+ }
+
+ ret = ovdb_getgroupinfo(group, &gi, true, NULL, 0);
+ switch (ret)
+ {
+ case 0:
+ break;
+ case DB_NOTFOUND:
+ return false;
+ default:
+ syslog(L_ERROR, "OVDB: ovdb_getgroupinfo failed: %s", db_strerror(ret));
+ return false;
+ }
+
+ if(lo != NULL)
+ *lo = gi.low;
+ if(hi != NULL)
+ *hi = gi.high;
+ if(count != NULL)
+ *count = gi.count;
+ if(flag != NULL)
+ *flag = gi.flag;
+ return true;
+}
+
+bool ovdb_groupadd(char *group, ARTNUM lo, ARTNUM hi, char *flag)
+{
+ DBT key, val;
+ struct groupinfo gi;
+ DB_TXN *tid;
+ int ret = 0;
+ int new;
+
+ memset(&key, 0, sizeof key);
+ memset(&val, 0, sizeof val);
+
+ TXN_START(t_groupadd, tid);
+
+ if(tid==NULL)
+ return false;
+
+ new = 0;
+ ret = ovdb_getgroupinfo(group, &gi, false, tid, DB_RMW);
+ switch (ret)
+ {
+ case DB_NOTFOUND:
+ new = 1;
+ case 0:
+ break;
+ case TRYAGAIN:
+ TXN_RETRY(t_groupadd, tid);
+ default:
+ TXN_ABORT(t_groupadd, tid);
+ syslog(L_ERROR, "OVDB: ovdb_getgroupinfo failed: %s", db_strerror(ret));
+ return false;
+ }
+
+ if(!new && (gi.status & GROUPINFO_DELETED)
+ && !(gi.status & GROUPINFO_EXPIRING)
+ && !(gi.status & GROUPINFO_MOVING)) {
+ int s, c = 0;
+ char g[MAXHEADERSIZE];
+
+ strlcpy(g, group, sizeof(g));
+ s = strlen(g) + 1;
+ key.data = g;
+ key.size = s + sizeof(int);
+ do {
+ c++;
+ memcpy(g+s, &c, sizeof(int));
+ ret = groupinfo->get(groupinfo, tid, &key, &val, 0);
+ } while(ret == 0);
+ if(ret == TRYAGAIN) {
+ TXN_RETRY(t_groupadd, tid);
+ }
+ val.data = &gi;
+ val.size = sizeof(gi);
+ ret = groupinfo->put(groupinfo, tid, &key, &val, 0);
+ switch (ret)
+ {
+ case 0:
+ break;
+ case TRYAGAIN:
+ TXN_RETRY(t_groupadd, tid);
+ default:
+ TXN_ABORT(t_groupadd, tid);
+ syslog(L_ERROR, "OVDB: groupinfo->put: %s", db_strerror(ret));
+ return false;
+ }
+ key.data = group;
+ key.size = strlen(group);
+ ret = groupinfo->del(groupinfo, tid, &key, 0);
+ switch (ret)
+ {
+ case 0:
+ break;
+ case TRYAGAIN:
+ TXN_RETRY(t_groupadd, tid);
+ default:
+ TXN_ABORT(t_groupadd, tid);
+ syslog(L_ERROR, "OVDB: groupinfo->del: %s", db_strerror(ret));
+ return false;
+ }
+ new = 1;
+ }
+
+ if(new) {
+ ret = groupid_new(&gi.current_gid, tid);
+ switch (ret)
+ {
+ case 0:
+ break;
+ case TRYAGAIN:
+ TXN_RETRY(t_groupadd, tid);
+ default:
+ TXN_ABORT(t_groupadd, tid);
+ syslog(L_ERROR, "OVDB: groupid_new: %s", db_strerror(ret));
+ return false;
+ }
+ gi.low = lo ? lo : 1;
+ gi.high = hi;
+ gi.count = 0;
+ gi.flag = *flag;
+ gi.expired = time(NULL);
+ gi.expiregrouppid = 0;
+ gi.current_db = gi.new_db = which_db(group);
+ gi.new_gid = gi.current_gid;
+ gi.status = 0;
+ } else {
+ gi.flag = *flag;
+ }
+
+ key.data = group;
+ key.size = strlen(group);
+ val.data = &gi;
+ val.size = sizeof gi;
+
+ ret = groupinfo->put(groupinfo, tid, &key, &val, 0);
+ switch (ret) {
+ case 0:
+ break;
+ case TRYAGAIN:
+ TXN_RETRY(t_groupadd, tid);
+ default:
+ TXN_ABORT(t_groupadd, tid);
+ syslog(L_ERROR, "OVDB: groupadd: groupinfo->put: %s", db_strerror(ret));
+ return false;
+ }
+
+ if(*flag == '=') {
+ key.data = group;
+ key.size = strlen(group);
+ val.data = flag + 1;
+ val.size = strcspn(flag, "\n") - 1;
+
+ switch(ret = groupaliases->put(groupaliases, tid, &key, &val, 0)) {
+ case 0:
+ break;
+ case TRYAGAIN:
+ TXN_RETRY(t_groupadd, tid);
+ default:
+ TXN_ABORT(t_groupadd, tid);
+ syslog(L_ERROR, "OVDB: groupadd: groupaliases->put: %s", db_strerror(ret));
+ return false;
+ }
+ }
+
+ TXN_COMMIT(t_groupadd, tid);
+ return true;
+}
+
+bool ovdb_groupdel(char *group)
+{
+ DBT key, val;
+ struct groupinfo gi;
+ DB_TXN *tid;
+ int ret = 0;
+
+ memset(&key, 0, sizeof key);
+ memset(&val, 0, sizeof val);
+
+ /* We only need to set the deleted flag in groupinfo to prevent readers
+ from seeing this group. The actual overview records aren't deleted
+ now, since that could take a significant amount of time (and innd
+ is who normally calls this function). The expireover run will
+ clean up the deleted groups. */
+
+ TXN_START(t_groupdel, tid);
+
+ if(tid==NULL)
+ return false;
+
+ ret = ovdb_getgroupinfo(group, &gi, true, tid, DB_RMW);
+ switch (ret)
+ {
+ case DB_NOTFOUND:
+ return true;
+ case 0:
+ break;
+ case TRYAGAIN:
+ TXN_RETRY(t_groupdel, tid);
+ default:
+ syslog(L_ERROR, "OVDB: ovdb_getgroupinfo failed: %s", db_strerror(ret));
+ TXN_ABORT(t_groupdel, tid);
+ return false;
+ }
+
+ gi.status |= GROUPINFO_DELETED;
+
+ key.data = group;
+ key.size = strlen(group);
+ val.data = &gi;
+ val.size = sizeof gi;
+
+ switch(ret = groupinfo->put(groupinfo, tid, &key, &val, 0)) {
+ case 0:
+ break;
+ case TRYAGAIN:
+ TXN_RETRY(t_groupdel, tid);
+ default:
+ TXN_ABORT(t_groupdel, tid);
+ syslog(L_ERROR, "OVDB: groupadd: groupinfo->put: %s", db_strerror(ret));
+ return false;
+ }
+
+ switch(ret = groupaliases->del(groupaliases, tid, &key, 0)) {
+ case 0:
+ case DB_NOTFOUND:
+ break;
+ case TRYAGAIN:
+ TXN_RETRY(t_groupdel, tid);
+ default:
+ syslog(L_ERROR, "OVDB: groupdel: groupaliases->del: %s", db_strerror(ret));
+ TXN_ABORT(t_groupdel, tid);
+ return false;
+ }
+
+ TXN_COMMIT(t_groupdel, tid);
+ return true;
+}
+
+bool ovdb_add(char *group, ARTNUM artnum, TOKEN token, char *data, int len, time_t arrived, time_t expires)
+{
+ static size_t databuflen = 0;
+ static char *databuf;
+ DB *db;
+ DBT key, val;
+ DB_TXN *tid;
+ struct groupinfo gi;
+ struct datakey dk;
+ int ret = 0;
+
+ memset(&dk, 0, sizeof dk);
+
+ if(databuflen == 0) {
+ databuflen = BIG_BUFFER;
+ databuf = xmalloc(databuflen);
+ }
+ if(databuflen < len + sizeof(struct ovdata)) {
+ databuflen = len + sizeof(struct ovdata);
+ databuf = xrealloc(databuf, databuflen);
+ }
+
+ /* hmm... BerkeleyDB needs something like a 'struct iovec' so that we don't
+ have to make a new buffer and copy everything in to it */
+
+ ((struct ovdata *)databuf)->token = token;
+ ((struct ovdata *)databuf)->arrived = arrived;
+ ((struct ovdata *)databuf)->expires = expires;
+ memcpy(databuf + sizeof(struct ovdata), data, len);
+ len += sizeof(struct ovdata);
+
+ memset(&key, 0, sizeof key);
+ memset(&val, 0, sizeof val);
+
+ /* start the transaction */
+ TXN_START(t_add, tid);
+
+ if(tid==NULL)
+ return false;
+
+ /* first, retrieve groupinfo */
+ ret = ovdb_getgroupinfo(group, &gi, true, tid, DB_RMW);
+ switch (ret)
+ {
+ case 0:
+ break;
+ case DB_NOTFOUND:
+ TXN_ABORT(t_add, tid);
+ return true;
+ case TRYAGAIN:
+ TXN_RETRY(t_add, tid);
+ default:
+ TXN_ABORT(t_add, tid);
+ syslog(L_ERROR, "OVDB: add: ovdb_getgroupinfo: %s", db_strerror(ret));
+ return false;
+ }
+
+ /* adjust groupinfo */
+ if(Cutofflow && gi.low > artnum) {
+ TXN_ABORT(t_add, tid);
+ return true;
+ }
+ if(gi.low == 0 || gi.low > artnum)
+ gi.low = artnum;
+ if(gi.high < artnum)
+ gi.high = artnum;
+ gi.count++;
+
+ /* store groupinfo */
+ key.data = group;
+ key.size = strlen(group);
+ val.data = &gi;
+ val.size = sizeof gi;
+
+ ret = groupinfo->put(groupinfo, tid, &key, &val, 0);
+ switch (ret)
+ {
+ case 0:
+ break;
+ case TRYAGAIN:
+ TXN_RETRY(t_add, tid);
+ default:
+ TXN_ABORT(t_add, tid);
+ syslog(L_ERROR, "OVDB: add: groupinfo->put: %s", db_strerror(ret));
+ return false;
+ }
+
+ /* store overview */
+ db = get_db_bynum(gi.current_db);
+ if(db == NULL) {
+ TXN_ABORT(t_add, tid);
+ return false;
+ }
+ dk.groupnum = gi.current_gid;
+ dk.artnum = htonl((u_int32_t)artnum);
+
+ key.data = &dk;
+ key.size = sizeof dk;
+ val.data = databuf;
+ val.size = len;
+
+ ret = db->put(db, tid, &key, &val, 0);
+ switch (ret)
+ {
+ case 0:
+ break;
+ case TRYAGAIN:
+ TXN_RETRY(t_add, tid);
+ default:
+ TXN_ABORT(t_add, tid);
+ syslog(L_ERROR, "OVDB: add: db->put: %s", db_strerror(ret));
+ return false;
+ }
+
+ if(artnum < gi.high && gi.status & GROUPINFO_MOVING) {
+ /* If the GROUPINFO_MOVING flag is set, then expireover
+ is writing overview records under a new groupid.
+ If this overview record is not at the highmark,
+ we need to also store it under the new groupid */
+ db = get_db_bynum(gi.new_db);
+ if(db == NULL) {
+ TXN_ABORT(t_add, tid);
+ return false;
+ }
+ dk.groupnum = gi.new_gid;
+
+ ret = db->put(db, tid, &key, &val, 0);
+ switch (ret)
+ {
+ case 0:
+ break;
+ case TRYAGAIN:
+ TXN_RETRY(t_add, tid);
+ default:
+ TXN_ABORT(t_add, tid);
+ syslog(L_ERROR, "OVDB: add: db->put: %s", db_strerror(ret));
+ return false;
+ }
+ }
+
+ TXN_COMMIT(t_add, tid);
+ return true;
+}
+
+bool ovdb_cancel(TOKEN token UNUSED)
+{
+ return true;
+}
+
+struct ovdbsearch {
+ DBC *cursor;
+ group_id_t gid;
+ u_int32_t firstart;
+ u_int32_t lastart;
+ int state;
+};
+
+/* Even though nnrpd only does one search at a time, a read server process could
+ do many concurrent searches; hence we must keep track of an arbitrary number of
+ open searches */
+static struct ovdbsearch **searches = NULL;
+static int nsearches = 0;
+static int maxsearches = 0;
+
+void *
+ovdb_opensearch(char *group, int low, int high)
+{
+ DB *db;
+ struct ovdbsearch *s;
+ struct groupinfo gi;
+ int ret;
+
+ if(clientmode) {
+ struct rs_cmd rs;
+ struct rs_opensrch repl;
+
+ rs.what = CMD_OPENSRCH;
+ rs.grouplen = strlen(group)+1;
+ rs.artlo = low;
+ rs.arthi = high;
+
+ if (csend(&rs, sizeof(rs)) < 0)
+ return NULL;
+ if (csend(group, rs.grouplen) < 0)
+ return NULL;
+ crecv(&repl, sizeof(repl));
+
+ if(repl.status != CMD_OPENSRCH)
+ return NULL;
+
+ return repl.handle;
+ }
+
+ ret = ovdb_getgroupinfo(group, &gi, true, NULL, 0);
+ switch (ret)
+ {
+ case 0:
+ break;
+ case DB_NOTFOUND:
+ return NULL;
+ default:
+ syslog(L_ERROR, "OVDB: ovdb_getgroupinfo failed: %s", db_strerror(ret));
+ return NULL;
+ }
+
+ s = xmalloc(sizeof(struct ovdbsearch));
+ db = get_db_bynum(gi.current_db);
+ if(db == NULL) {
+ free(s);
+ return NULL;
+ }
+
+ ret = db->cursor(db, NULL, &(s->cursor), 0);
+ if (ret != 0) {
+ syslog(L_ERROR, "OVDB: opensearch: s->db->cursor: %s", db_strerror(ret));
+ free(s);
+ return NULL;
+ }
+
+ s->gid = gi.current_gid;
+ s->firstart = low;
+ s->lastart = high;
+ s->state = 0;
+
+ if(searches == NULL) {
+ nsearches = 0;
+ maxsearches = 50;
+ searches = xmalloc(sizeof(struct ovdbsearch *) * maxsearches);
+ }
+ if(nsearches == maxsearches) {
+ maxsearches += 50;
+ searches = xrealloc(searches, sizeof(struct ovdbsearch *) * maxsearches);
+ }
+ searches[nsearches] = s;
+ nsearches++;
+
+ return (void *)s;
+}
+
+bool
+ovdb_search(void *handle, ARTNUM *artnum, char **data, int *len,
+ TOKEN *token, time_t *arrived)
+{
+ struct ovdbsearch *s = (struct ovdbsearch *)handle;
+ DBT key, val;
+ u_int32_t flags;
+ struct ovdata ovd;
+ struct datakey dk;
+ int ret;
+
+ if (clientmode) {
+ struct rs_cmd rs;
+ struct rs_srch repl;
+ static char *databuf;
+ static int buflen = 0;
+
+ rs.what = CMD_SRCH;
+ rs.handle = handle;
+
+ if (csend(&rs, sizeof(rs)) < 0)
+ return false;
+ if (crecv(&repl, sizeof(repl)) < 0)
+ return false;
+
+ if(repl.status != CMD_SRCH)
+ return false;
+ if(repl.len > buflen) {
+ if(buflen == 0) {
+ buflen = repl.len + 512;
+ databuf = xmalloc(buflen);
+ } else {
+ buflen = repl.len + 512;
+ databuf = xrealloc(databuf, buflen);
+ }
+ }
+ crecv(databuf, repl.len);
+
+ if(artnum)
+ *artnum = repl.artnum;
+ if(token)
+ *token = repl.token;
+ if(arrived)
+ *arrived = repl.arrived;
+ if(len)
+ *len = repl.len;
+ if(data)
+ *data = databuf;
+ return true;
+ }
+
+ switch(s->state) {
+ case 0:
+ flags = DB_SET_RANGE;
+ memset(&dk, 0, sizeof dk);
+ dk.groupnum = s->gid;
+ dk.artnum = htonl(s->firstart);
+ s->state = 1;
+ break;
+ case 1:
+ flags = DB_NEXT;
+ break;
+ case 2:
+ s->state = 3;
+ return false;
+ default:
+ syslog(L_ERROR, "OVDB: OVsearch called again after false return");
+ return false;
+ }
+
+ memset(&key, 0, sizeof key);
+ memset(&val, 0, sizeof val);
+ key.data = &dk;
+ key.size = key.ulen = sizeof(struct datakey);
+ key.flags = DB_DBT_USERMEM;
+
+ if(!data && !len) {
+ /* caller doesn't need data, so we don't have to retrieve it all */
+ val.flags |= DB_DBT_PARTIAL;
+
+ if(token || arrived)
+ val.dlen = sizeof(struct ovdata);
+ }
+
+ switch(ret = s->cursor->c_get(s->cursor, &key, &val, flags)) {
+ case 0:
+ break;
+ case DB_NOTFOUND:
+ s->state = 3;
+ s->cursor->c_close(s->cursor);
+ s->cursor = NULL;
+ return false;
+ default:
+ syslog(L_ERROR, "OVDB: search: c_get: %s", db_strerror(ret));
+ s->state = 3;
+ s->cursor->c_close(s->cursor);
+ s->cursor = NULL;
+ return false;
+ }
+
+ if(key.size != sizeof(struct datakey)) {
+ s->state = 3;
+ s->cursor->c_close(s->cursor);
+ s->cursor = NULL;
+ return false;
+ }
+
+ if(dk.groupnum != s->gid || ntohl(dk.artnum) > s->lastart) {
+ s->state = 3;
+ s->cursor->c_close(s->cursor);
+ s->cursor = NULL;
+ return false;
+ }
+
+ if( ((len || data) && val.size <= sizeof(struct ovdata))
+ || ((token || arrived) && val.size < sizeof(struct ovdata)) ) {
+ syslog(L_ERROR, "OVDB: search: bad value length");
+ s->state = 3;
+ s->cursor->c_close(s->cursor);
+ s->cursor = NULL;
+ return false;
+ }
+
+ if(ntohl(dk.artnum) == s->lastart) {
+ s->state = 2;
+ s->cursor->c_close(s->cursor);
+ s->cursor = NULL;
+ }
+
+ if(artnum)
+ *artnum = ntohl(dk.artnum);
+
+ if(token || arrived)
+ memcpy(&ovd, val.data, sizeof(struct ovdata));
+ if(token)
+ *token = ovd.token;
+ if(arrived)
+ *arrived = ovd.arrived;
+
+ if(len)
+ *len = val.size - sizeof(struct ovdata);
+ if(data)
+ *data = (char *)val.data + sizeof(struct ovdata);
+
+ return true;
+}
+
+void ovdb_closesearch(void *handle)
+{
+ int i;
+ if(clientmode) {
+ struct rs_cmd rs;
+
+ rs.what = CMD_CLOSESRCH;
+ rs.handle = handle;
+ csend(&rs, sizeof(rs));
+ /* no reply is sent for a CMD_CLOSESRCH */
+ } else {
+ struct ovdbsearch *s = (struct ovdbsearch *)handle;
+
+ if(s->cursor)
+ s->cursor->c_close(s->cursor);
+
+ for(i = 0; i < nsearches; i++) {
+ if(s == searches[i]) {
+ break;
+ }
+ }
+ nsearches--;
+ for( ; i < nsearches; i++) {
+ searches[i] = searches[i+1];
+ }
+
+ free(handle);
+ }
+}
+
+bool ovdb_getartinfo(char *group, ARTNUM artnum, TOKEN *token)
+{
+ int ret, cdb = 0;
+ group_id_t cgid = 0;
+ DB *db;
+ DBT key, val;
+ struct ovdata ovd;
+ struct datakey dk;
+ struct groupinfo gi;
+ int pass = 0;
+
+ if(clientmode) {
+ struct rs_cmd rs;
+ struct rs_artinfo repl;
+
+ rs.what = CMD_ARTINFO;
+ rs.grouplen = strlen(group)+1;
+ rs.artlo = artnum;
+
+ if (csend(&rs, sizeof(rs)) < 0)
+ return false;
+ if (csend(group, rs.grouplen) < 0)
+ return false;
+ crecv(&repl, sizeof(repl));
+
+ if(repl.status != CMD_ARTINFO)
+ return false;
+
+ if(token)
+ *token = repl.token;
+
+ return true;
+ }
+
+ while(1) {
+ ret = ovdb_getgroupinfo(group, &gi, true, NULL, 0);
+ switch (ret)
+ {
+ case 0:
+ break;
+ case DB_NOTFOUND:
+ return false;
+ default:
+ syslog(L_ERROR, "OVDB: ovdb_getgroupinfo failed: %s", db_strerror(ret));
+ return false;
+ }
+
+ if(pass) {
+ /* This was our second groupinfo retrieval; because the article
+ retrieval came up empty. If the group ID hasn't changed
+ since the first groupinfo retrieval, we can assume the article
+ is definitely not there. Otherwise, we'll try to retrieve
+ it the article again. */
+ if(cdb == gi.current_db && cgid == gi.current_gid)
+ return false;
+ }
+
+ db = get_db_bynum(gi.current_db);
+ if(db == NULL)
+ return false;
+
+ memset(&dk, 0, sizeof dk);
+ dk.groupnum = gi.current_gid;
+ dk.artnum = htonl((u_int32_t)artnum);
+
+ memset(&key, 0, sizeof key);
+ memset(&val, 0, sizeof val);
+
+ key.data = &dk;
+ key.size = sizeof dk;
+
+ /* caller doesn't need data, so we don't have to retrieve it all */
+ val.flags = DB_DBT_PARTIAL;
+
+ if(token)
+ val.dlen = sizeof(struct ovdata);
+
+ switch(ret = db->get(db, NULL, &key, &val, 0)) {
+ case 0:
+ case DB_NOTFOUND:
+ break;
+ default:
+ syslog(L_ERROR, "OVDB: getartinfo: db->get: %s", db_strerror(ret));
+ return false;
+ }
+
+ if(ret == DB_NOTFOUND) {
+ /* If the group is being moved (i.e., its group ID is going
+ to change), there's a chance the article is now under the
+ new ID. So we'll grab the groupinfo again to check for
+ that. */
+ if(!pass && (gi.status & GROUPINFO_MOVING)) {
+ cdb = gi.current_db;
+ cgid = gi.current_gid;
+ pass++;
+ continue;
+ }
+ return false;
+ }
+ break;
+ }
+
+ if(token && val.size < sizeof(struct ovdata)) {
+ syslog(L_ERROR, "OVDB: getartinfo: data too short");
+ return false;
+ }
+
+ if(token) {
+ memcpy(&ovd, val.data, sizeof(struct ovdata));
+ *token = ovd.token;
+ }
+ return true;
+}
+
+bool ovdb_expiregroup(char *group, int *lo, struct history *h)
+{
+ DB *db, *ndb = NULL;
+ DBT key, val, nkey, gkey, gval;
+ DB_TXN *tid;
+ DBC *cursor = NULL;
+ int ret = 0, delete, old_db = 0, cleanup;
+ struct groupinfo gi;
+ struct ovdata ovd;
+ struct datakey dk, ndk;
+ group_id_t old_gid = 0;
+ ARTHANDLE *ah;
+ u_int32_t artnum = 0, currentart, lowest;
+ int i, compact, done, currentcount, newcount;
+
+ if(eo_start == 0) {
+ eo_start = time(NULL);
+ delete_old_stuff(0); /* remove deleted groups first */
+ }
+
+ /* Special case: when called with NULL group, we're to clean out
+ * records for forgotton groups (groups removed from the active file
+ * but not from overview).
+ * This happens at the end of the expireover run, and only if all
+ * of the groups in the active file have been processed.
+ * delete_old_stuff(1) will remove groups that are in ovdb but
+ * have not been processed during this run.
+ */
+
+ if(group == NULL)
+ return delete_old_stuff(1);
+
+ memset(&key, 0, sizeof key);
+ memset(&nkey, 0, sizeof nkey);
+ memset(&val, 0, sizeof val);
+ memset(&dk, 0, sizeof dk);
+ memset(&ndk, 0, sizeof ndk);
+
+ TXN_START(t_expgroup_1, tid);
+
+ if(tid==NULL)
+ return false;
+
+ cleanup = 0;
+
+ ret = ovdb_getgroupinfo(group, &gi, true, tid, DB_RMW);
+ switch (ret)
+ {
+ case 0:
+ break;
+ case TRYAGAIN:
+ TXN_RETRY(t_expgroup_1, tid);
+ default:
+ syslog(L_ERROR, "OVDB: expiregroup: ovdb_getgroupinfo failed: %s", db_strerror(ret));
+ case DB_NOTFOUND:
+ TXN_ABORT(t_expgroup_1, tid);
+ return false;
+ }
+
+ if(gi.status & GROUPINFO_EXPIRING) {
+ /* is there another expireover working on this group? */
+ ret = kill(gi.expiregrouppid, 0);
+ switch(ret)
+ {
+ case 0:
+ case EPERM:
+ TXN_ABORT(t_expgroup_1, tid);
+ return false;
+ }
+
+ /* a previous expireover run must've died. We'll clean
+ up after it */
+ if(gi.status & GROUPINFO_MOVING) {
+ cleanup = 1;
+ old_db = gi.new_db;
+ old_gid = gi.new_gid;
+
+ ret = mk_temp_groupinfo(old_db, old_gid, tid);
+ switch(ret) {
+ case 0:
+ break;
+ case TRYAGAIN:
+ TXN_RETRY(t_expgroup_1, tid);
+ default:
+ TXN_ABORT(t_expgroup_1, tid);
+ return false;
+ }
+ gi.status &= ~GROUPINFO_MOVING;
+ }
+ }
+
+ if(gi.count < ovdb_conf.nocompact || ovdb_conf.nocompact == 0)
+ compact = 1;
+ else
+ compact = 0;
+
+ if(gi.count == 0)
+ compact = 0;
+
+ db = get_db_bynum(gi.current_db);
+ if(db == NULL) {
+ TXN_ABORT(t_expgroup_1, tid);
+ return false;
+ }
+
+ gi.status |= GROUPINFO_EXPIRING;
+ gi.expiregrouppid = getpid();
+ if(compact) {
+ gi.status |= GROUPINFO_MOVING;
+ gi.new_db = gi.current_db;
+ ndb = db;
+ ret = groupid_new(&gi.new_gid, tid);
+ switch (ret)
+ {
+ case 0:
+ break;
+ case TRYAGAIN:
+ TXN_RETRY(t_expgroup_1, tid);
+ default:
+ TXN_ABORT(t_expgroup_1, tid);
+ syslog(L_ERROR, "OVDB: expiregroup: groupid_new: %s", db_strerror(ret));
+ return false;
+ }
+ }
+
+ key.data = group;
+ key.size = strlen(group);
+ val.data = &gi;
+ val.size = sizeof gi;
+
+ ret = groupinfo->put(groupinfo, tid, &key, &val, 0);
+ switch (ret)
+ {
+ case 0:
+ break;
+ case TRYAGAIN:
+ TXN_RETRY(t_expgroup_1, tid);
+ default:
+ TXN_ABORT(t_expgroup_1, tid);
+ syslog(L_ERROR, "OVDB: expiregroup: groupinfo->put: %s", db_strerror(ret));
+ return false;
+ }
+ TXN_COMMIT(t_expgroup_1, tid);
+
+ if(cleanup) {
+ if(delete_all_records(old_db, old_gid) == 0) {
+ rm_temp_groupinfo(old_gid);
+ }
+ }
+
+ /*
+ * The following loop iterates over the OV records for the group in
+ * "batches", to limit transaction sizes.
+ *
+ * loop {
+ * start transaction
+ * get groupinfo
+ * process EXPIREGROUP_TXN_SIZE records
+ * write updated groupinfo
+ * commit transaction
+ * }
+ */
+ currentart = 0;
+ lowest = currentcount = 0;
+
+ memset(&gkey, 0, sizeof gkey);
+ memset(&gval, 0, sizeof gval);
+ gkey.data = group;
+ gkey.size = strlen(group);
+ gval.data = &gi;
+ gval.size = sizeof gi;
+
+ while(1) {
+ TXN_START(t_expgroup_loop, tid);
+ if(tid==NULL)
+ return false;
+ done = 0;
+ newcount = 0;
+
+ ret = ovdb_getgroupinfo(group, &gi, false, tid, DB_RMW);
+ switch (ret)
+ {
+ case 0:
+ break;
+ case TRYAGAIN:
+ TXN_RETRY(t_expgroup_loop, tid);
+ default:
+ TXN_ABORT(t_expgroup_loop, tid);
+ syslog(L_ERROR, "OVDB: expiregroup: ovdb_getgroupinfo: %s", db_strerror(ret));
+ return false;
+ }
+
+ ret = db->cursor(db, tid, &cursor, 0);
+ switch (ret)
+ {
+ case 0:
+ break;
+ case TRYAGAIN:
+ TXN_RETRY(t_expgroup_loop, tid);
+ default:
+ TXN_ABORT(t_expgroup_loop, tid);
+ syslog(L_ERROR, "OVDB: expiregroup: db->cursor: %s", db_strerror(ret));
+ return false;
+ }
+
+ dk.groupnum = gi.current_gid;
+ dk.artnum = htonl(currentart);
+ key.data = &dk;
+ key.size = key.ulen = sizeof dk;
+ key.flags = DB_DBT_USERMEM;
+
+ for(i=0; i < EXPIREGROUP_TXN_SIZE; i++) {
+ ret = cursor->c_get(cursor, &key, &val,
+ i == 0 ? DB_SET_RANGE : DB_NEXT);
+ switch (ret)
+ {
+ case 0:
+ case DB_NOTFOUND:
+ break;
+ case TRYAGAIN:
+ cursor->c_close(cursor);
+ TXN_RETRY(t_expgroup_loop, tid);
+ default:
+ cursor->c_close(cursor);
+ TXN_ABORT(t_expgroup_loop, tid);
+ syslog(L_ERROR, "OVDB: expiregroup: c_get: %s", db_strerror(ret));
+ return false;
+ }
+
+ /* stop if: there are no more keys, an unknown key is reached,
+ or reach a different group */
+
+ if(ret == DB_NOTFOUND
+ || key.size != sizeof dk
+ || dk.groupnum != gi.current_gid) {
+ done++;
+ break;
+ }
+
+ artnum = ntohl(dk.artnum);
+
+ delete = 0;
+ if(val.size < sizeof ovd) {
+ delete = 1; /* must be corrupt, just delete it */
+ } else {
+ memcpy(&ovd, val.data, sizeof ovd);
+
+ ah = NULL;
+ if (!SMprobe(EXPENSIVESTAT, &ovd.token, NULL) || OVstatall) {
+ if((ah = SMretrieve(ovd.token, RETR_STAT)) == NULL) {
+ delete = 1;
+ } else
+ SMfreearticle(ah);
+ } else {
+ if (!OVhisthasmsgid(h, (char *)val.data + sizeof(ovd))) {
+ delete = 1;
+ }
+ }
+ if (!delete && innconf->groupbaseexpiry &&
+ OVgroupbasedexpire(ovd.token, group,
+ (char *)val.data + sizeof(ovd),
+ val.size - sizeof(ovd),
+ ovd.arrived, ovd.expires)) {
+ delete = 1;
+ }
+ }
+
+ if(delete) {
+ if(!compact) {
+ switch(ret = cursor->c_del(cursor, 0)) {
+ case 0:
+ case DB_NOTFOUND:
+ case DB_KEYEMPTY:
+ break;
+ case TRYAGAIN:
+ cursor->c_close(cursor);
+ TXN_RETRY(t_expgroup_loop, tid);
+ default:
+ cursor->c_close(cursor);
+ TXN_ABORT(t_expgroup_loop, tid);
+ syslog(L_ERROR, "OVDB: expiregroup: c_del: %s", db_strerror(ret));
+ return false;
+ }
+ }
+ if(gi.count > 0)
+ gi.count--;
+ } else {
+ if(compact) {
+ ndk.groupnum = gi.new_gid;
+ ndk.artnum = dk.artnum;
+ nkey.data = &ndk;
+ nkey.size = sizeof ndk;
+
+ switch(ret = ndb->put(ndb, tid, &nkey, &val, 0)) {
+ case 0:
+ break;
+ case TRYAGAIN:
+ cursor->c_close(cursor);
+ TXN_RETRY(t_expgroup_loop, tid);
+ default:
+ cursor->c_close(cursor);
+ TXN_ABORT(t_expgroup_loop, tid);
+ syslog(L_ERROR, "OVDB: expiregroup: ndb->put: %s", db_strerror(ret));
+ return false;
+ }
+ }
+ newcount++;
+ if(lowest != -1 && (lowest == 0 || artnum < lowest))
+ lowest = artnum;
+ }
+ }
+ /* end of for loop */
+
+ if(cursor->c_close(cursor) == TRYAGAIN) {
+ TXN_RETRY(t_expgroup_loop, tid);
+ }
+
+ if(lowest != 0 && lowest != -1)
+ gi.low = lowest;
+
+ if(done) {
+ if(compact) {
+ old_db = gi.current_db;
+ gi.current_db = gi.new_db;
+ old_gid = gi.current_gid;
+ gi.current_gid = gi.new_gid;
+ gi.status &= ~GROUPINFO_MOVING;
+
+ ret = mk_temp_groupinfo(old_db, old_gid, tid);
+ switch(ret) {
+ case 0:
+ break;
+ case TRYAGAIN:
+ TXN_RETRY(t_expgroup_loop, tid);
+ default:
+ TXN_ABORT(t_expgroup_loop, tid);
+ return false;
+ }
+ }
+
+ gi.status &= ~GROUPINFO_EXPIRING;
+ gi.expired = time(NULL);
+ if(gi.count == 0 && lowest == 0)
+ gi.low = gi.high+1;
+ }
+
+ ret = groupinfo->put(groupinfo, tid, &gkey, &gval, 0);
+ switch (ret)
+ {
+ case 0:
+ break;
+ case TRYAGAIN:
+ TXN_RETRY(t_expgroup_loop, tid);
+ default:
+ TXN_ABORT(t_expgroup_loop, tid);
+ syslog(L_ERROR, "OVDB: expiregroup: groupinfo->put: %s", db_strerror(ret));
+ return false;
+ }
+ TXN_COMMIT(t_expgroup_loop, tid);
+
+ currentcount += newcount;
+ if(lowest != 0)
+ lowest = -1;
+
+ if(done)
+ break;
+
+ currentart = artnum+1;
+ }
+
+ if(compact) {
+ if(delete_all_records(old_db, old_gid) == 0) {
+ rm_temp_groupinfo(old_gid);
+ }
+ }
+
+ if(currentcount != gi.count) {
+ syslog(L_NOTICE, "OVDB: expiregroup: recounting %s", group);
+
+ TXN_START(t_expgroup_recount, tid);
+ if(tid == NULL)
+ return false;
+
+ switch(ret = ovdb_getgroupinfo(group, &gi, false, tid, DB_RMW)) {
+ case 0:
+ break;
+ case TRYAGAIN:
+ TXN_RETRY(t_expgroup_recount, tid);
+ default:
+ TXN_ABORT(t_expgroup_recount, tid);
+ syslog(L_ERROR, "OVDB: expiregroup: ovdb_getgroupinfo: %s", db_strerror(ret));
+ return false;
+ }
+
+ if(count_records(&gi) != 0) {
+ TXN_ABORT(t_expgroup_recount, tid);
+ return false;
+ }
+
+ ret = groupinfo->put(groupinfo, tid, &gkey, &gval, 0);
+ switch (ret)
+ {
+ case 0:
+ break;
+ case TRYAGAIN:
+ TXN_RETRY(t_expgroup_recount, tid);
+ default:
+ TXN_ABORT(t_expgroup_recount, tid);
+ syslog(L_ERROR, "OVDB: expiregroup: groupinfo->put: %s", db_strerror(ret));
+ return false;
+ }
+ TXN_COMMIT(t_expgroup_recount, tid);
+ }
+
+ if(lo)
+ *lo = gi.low;
+ return true;
+}
+
+bool ovdb_ctl(OVCTLTYPE type, void *val)
+{
+ int *i;
+ OVSORTTYPE *sorttype;
+ bool *boolval;
+
+ switch (type) {
+ case OVSPACE:
+ i = (int *)val;
+ *i = -1;
+ return true;
+ case OVSORT:
+ sorttype = (OVSORTTYPE *)val;
+ *sorttype = OVNEWSGROUP;
+ return true;
+ case OVCUTOFFLOW:
+ Cutofflow = *(bool *)val;
+ return true;
+ case OVSTATICSEARCH:
+ i = (int *)val;
+ *i = true;
+ return true;
+ case OVCACHEKEEP:
+ case OVCACHEFREE:
+ boolval = (bool *)val;
+ *boolval = false;
+ return true;
+ default:
+ return false;
+ }
+}
+
+void ovdb_close_berkeleydb(void)
+{
+ if(OVDBenv) {
+ /* close db environment */
+#if DB_VERSION_MAJOR == 2
+ db_appexit(OVDBenv);
+ free(OVDBenv);
+#else
+ OVDBenv->close(OVDBenv, 0);
+#endif
+ OVDBenv = NULL;
+ }
+}
+
+void ovdb_close(void)
+{
+ int i;
+
+ if(clientmode) {
+ client_disconnect();
+ return;
+ }
+
+ while(searches != NULL && nsearches) {
+ ovdb_closesearch(searches[0]);
+ }
+ if(searches != NULL) {
+ free(searches);
+ searches = NULL;
+ }
+
+ if(dbs) {
+ /* close databases */
+ for(i = 0; i < ovdb_conf.numdbfiles; i++)
+ close_db_file(i);
+
+ free(dbs);
+ dbs = NULL;
+ }
+ if(groupinfo) {
+ groupinfo->close(groupinfo, 0);
+ groupinfo = NULL;
+ }
+ if(groupaliases) {
+ groupaliases->close(groupaliases, 0);
+ groupaliases = NULL;
+ }
+
+ ovdb_close_berkeleydb();
+ ovdb_releaselock();
+}
+
+
+#endif /* USE_BERKELEY_DB */
+
--- /dev/null
+#ifndef _OVDB_H_
+#define _OVDB_H_
+
+#ifdef __cplusplus
+extern "C" {
+#endif /* __cplusplus */
+
+bool ovdb_open(int mode);
+bool ovdb_groupstats(char *group, int *lo, int *hi, int *count, int *flag);
+bool ovdb_groupadd(char *group, ARTNUM lo, ARTNUM hi, char *flag);
+bool ovdb_groupdel(char *group);
+bool ovdb_add(char *group, ARTNUM artnum, TOKEN token, char *data, int len, time_t arrived, time_t expires);
+bool ovdb_cancel(TOKEN token);
+void *ovdb_opensearch(char *group, int low, int high);
+bool ovdb_search(void *handle, ARTNUM *artnum, char **data, int *len, TOKEN *token, time_t *arrived);
+void ovdb_closesearch(void *handle);
+bool ovdb_getartinfo(char *group, ARTNUM artnum, TOKEN *token);
+bool ovdb_expiregroup(char *group, int *lo, struct history *h);
+bool ovdb_ctl(OVCTLTYPE type, void *val);
+void ovdb_close(void);
+
+#ifdef __cplusplus
+}
+#endif /* __cplusplus */
+
+#endif /* _OVDB_H_ */
--- /dev/null
+name = ovdb
+number = 4
+sources = ovdb.c
--- /dev/null
+/* $Id: overdata.c 7477 2005-12-24 21:34:38Z eagle $
+**
+** Overview data processing.
+**
+** Here be routines for creating and checking the overview data, the
+** tab-separated list of overview fields.
+*/
+
+#include "config.h"
+#include "clibrary.h"
+#include <ctype.h>
+
+#include "inn/buffer.h"
+#include "inn/innconf.h"
+#include "inn/messages.h"
+#include "inn/qio.h"
+#include "inn/wire.h"
+#include "inn/vector.h"
+#include "libinn.h"
+#include "ovinterface.h"
+#include "paths.h"
+
+
+/* The standard overview fields. */
+static const char * const fields[] = {
+ "Subject", "From", "Date", "Message-ID", "References", "Bytes", "Lines"
+};
+
+
+/*
+** Return a vector of the standard overview fields. Note there is no
+** way to free up the resulting data structure.
+*/
+const struct cvector *
+overview_fields(void)
+{
+ static struct cvector *list = NULL;
+
+ if (list == NULL) {
+ unsigned int field;
+
+ list = cvector_new();
+ cvector_resize(list, ARRAY_SIZE(fields));
+
+ for (field = 0; field < ARRAY_SIZE(fields); ++field) {
+ cvector_add(list, fields[field]);
+ }
+ }
+ return list;
+}
+
+/*
+** Parse the overview schema and return a vector of the additional fields
+** over the standard ones. Caller is responsible for freeing the vector.
+*/
+struct vector *
+overview_extra_fields(void)
+{
+ struct vector *list = NULL;
+ struct vector *result = NULL;
+ char *schema = NULL;
+ char *line, *p;
+ QIOSTATE *qp = NULL;
+ unsigned int field;
+ bool full = false;
+
+ schema = concatpath(innconf->pathetc, _PATH_SCHEMA);
+ qp = QIOopen(schema);
+ if (qp == NULL) {
+ syswarn("cannot open %s", schema);
+ goto done;
+ }
+ list = vector_new();
+ for (field = 0, line = QIOread(qp); line != NULL; line = QIOread(qp)) {
+ while (ISWHITE(*line))
+ line++;
+ p = strchr(line, '#');
+ if (p != NULL)
+ *p = '\0';
+ p = strchr(line, '\n');
+ if (p != NULL)
+ *p = '\0';
+ if (*line == '\0')
+ continue;
+ p = strchr(line, ':');
+ if (p != NULL) {
+ *p++ = '\0';
+ full = (strcmp(p, "full") == 0);
+ }
+ if (field >= ARRAY_SIZE(fields)) {
+ if (!full)
+ warn("additional field %s not marked with :full", line);
+ vector_add(list, line);
+ } else {
+ if (strcasecmp(line, fields[field]) != 0)
+ warn("field %d is %s, should be %s", field, line,
+ fields[field]);
+ }
+ field++;
+ }
+ if (QIOerror(qp)) {
+ if (QIOtoolong(qp)) {
+ warn("line too long in %s", schema);
+ } else {
+ syswarn("error while reading %s", schema);
+ }
+ }
+ result = list;
+
+done:
+ if (schema != NULL)
+ free(schema);
+ if (qp != NULL)
+ QIOclose(qp);
+ if (result == NULL && list != NULL)
+ vector_free(list);
+ return result;
+}
+
+
+/*
+** Given an article, its length, the name of a header, and a buffer to append
+** the data to, append header data for that header to the overview data
+** that's being constructed. Doesn't append any data if the header isn't
+** found.
+*/
+static void
+build_header(const char *article, size_t length, const char *header,
+ struct buffer *overview)
+{
+ ptrdiff_t size;
+ size_t offset;
+ const char *data, *end, *p;
+
+ data = wire_findheader(article, length, header);
+ if (data == NULL)
+ return;
+ end = wire_endheader(data, article + length - 1);
+ if (end == NULL)
+ return;
+
+ /* Someone managed to break their server so that they were appending
+ multiple Xref headers, and INN had a bug where it wouldn't notice this
+ and reject the article. Just in case, see if there are multiple Xref
+ headers and use the last one. */
+ if (strcasecmp(header, "xref") == 0) {
+ const char *next = end + 1;
+
+ while (next != NULL) {
+ next = wire_findheader(next, length - (next - article), header);
+ if (next != NULL) {
+ data = next;
+ end = wire_endheader(data, article + length - 1);
+ if (end == NULL)
+ return;
+ }
+ }
+ }
+
+ size = end - data + 1;
+ offset = overview->used + overview->left;
+ buffer_resize(overview, offset + size);
+
+ for (p = data; p <= end; p++) {
+ if (*p == '\r' && p[1] == '\n') {
+ p++;
+ continue;
+ }
+ if (*p == '\0' || *p == '\t' || *p == '\n' || *p == '\r')
+ overview->data[offset++] = ' ';
+ else
+ overview->data[offset++] = *p;
+ overview->left++;
+ }
+}
+
+
+/*
+** Given an article number, an article, and a vector of additional headers,
+** generate overview data into the provided buffer. If the buffer parameter
+** is NULL, a new buffer is allocated. The article should be in wire format.
+** Returns the buffer containing the overview data.
+*/
+struct buffer *
+overview_build(ARTNUM number, const char *article, size_t length,
+ const struct vector *extra, struct buffer *overview)
+{
+ unsigned int field;
+ char buffer[32];
+
+ snprintf(buffer, sizeof(buffer), "%lu", number);
+ if (overview == NULL)
+ overview = buffer_new();
+ buffer_set(overview, buffer, strlen(buffer));
+ for (field = 0; field < ARRAY_SIZE(fields); field++) {
+ buffer_append(overview, "\t", 1);
+ if (field == 5) {
+ snprintf(buffer, sizeof(buffer), "%lu", (unsigned long) length);
+ buffer_append(overview, buffer, strlen(buffer));
+ } else
+ build_header(article, length, fields[field], overview);
+ }
+ for (field = 0; field < extra->count; field++) {
+ buffer_append(overview, "\t", 1);
+ buffer_append(overview, extra->strings[field],
+ strlen(extra->strings[field]));
+ buffer_append(overview, ": ", 2);
+ build_header(article, length, extra->strings[field], overview);
+ }
+ buffer_append(overview, "\r\n", 2);
+ return overview;
+}
+
+
+/*
+** Check whether a given string is a valid number.
+*/
+static bool
+valid_number(const char *string)
+{
+ const char *p;
+
+ for (p = string; *p != '\0'; p++)
+ if (!CTYPE(isdigit, *p))
+ return false;
+ return true;
+}
+
+
+/*
+** Check whether a given string is a valid overview string (doesn't contain
+** CR or LF, and if the second argument is true must be preceeded by a header
+** name, colon, and space). Allow CRLF at the end of the data, but don't
+** require it.
+*/
+static bool
+valid_overview_string(const char *string, bool full)
+{
+ const unsigned char *p;
+
+ /* RFC 2822 says that header fields must consist of printable ASCII
+ characters (characters between 33 and 126, inclusive) excluding colon.
+ We also allow high-bit characters, just in case, but not DEL. */
+ p = (const unsigned char *) string;
+ if (full) {
+ for (; *p != '\0' && *p != ':'; p++)
+ if (*p < 33 || *p == 127)
+ return false;
+ if (*p != ':')
+ return false;
+ p++;
+ if (*p != ' ')
+ return false;
+ }
+ for (p++; *p != '\0'; p++) {
+ if (*p == '\015' && p[1] == '\012' && p[2] == '\0')
+ break;
+ if (*p == '\015' || *p == '\012')
+ return false;
+ }
+ return true;
+}
+
+
+/*
+** Check the given overview data and make sure it's well-formed. Extension
+** headers are not checked against overview.fmt (having a different set of
+** extension headers doesn't make the data invalid), but the presence of the
+** standard fields is checked. Also checked is whether the article number in
+** the data matches the passed article number. Returns true if the data is
+** okay, false otherwise.
+*/
+bool
+overview_check(const char *data, size_t length, ARTNUM article)
+{
+ char *copy;
+ struct cvector *overview;
+ ARTNUM overnum;
+ size_t i;
+
+ copy = xstrndup(data, length);
+ overview = cvector_split(copy, '\t', NULL);
+
+ /* The actual checks. We don't verify all of the data, since that data
+ may be malformed in the article, but we do check to be sure that the
+ fields that should be numbers are numbers. That should catch most
+ positional errors. We can't check Lines yet since right now INN is
+ still accepting the value from the post verbatim. */
+ if (overview->count < 8)
+ goto fail;
+ if (!valid_number(overview->strings[0]))
+ goto fail;
+ overnum = strtoul(overview->strings[0], NULL, 10);
+ if (overnum != article)
+ goto fail;
+ if (!valid_number(overview->strings[6]))
+ goto fail;
+ for (i = 1; i < 6; i++)
+ if (!valid_overview_string(overview->strings[i], false))
+ goto fail;
+ for (i = 8; i < overview->count; i++)
+ if (!valid_overview_string(overview->strings[i], true))
+ goto fail;
+ cvector_free(overview);
+ free(copy);
+ return true;
+
+ fail:
+ cvector_free(overview);
+ free(copy);
+ return false;
+}
+
+
+/*
+** Given an overview header, return the offset of the field within
+** the overview data, or -1 if the field is not present in the
+** overview schema for this installation.
+*/
+int
+overview_index(const char *field, const struct vector *extra)
+{
+ int i;
+
+ for (i = 0; i < (sizeof fields / sizeof fields[0]); ++i) {
+ if (strcasecmp(field, fields[i]) == 0)
+ return i;
+ }
+ for (i = 0; i < extra->count; i++) {
+ if (strcasecmp(field, extra->strings[i]) == 0)
+ return i + (sizeof fields / sizeof fields[0]);
+ }
+ return -1;
+}
+
+
+/*
+** Given an overview header line, split out a vector pointing at each
+** of the components (within line), returning a pointer to the
+** vector. If the vector initially passed in is NULL a new vector is
+** created, else the existing one is filled in.
+**
+** A member `n' of the vector is of length (vector->strings[n+1] -
+** vector->strings[n] - 1). Note that the last member of the vector
+** will always point beyond (line + length).
+*/
+struct cvector *
+overview_split(const char *line, size_t length, ARTNUM *number,
+ struct cvector *vector)
+{
+ const char *p = NULL;
+
+ if (vector == NULL) {
+ vector = cvector_new();
+ } else {
+ cvector_clear(vector);
+ }
+ while (line != NULL) {
+ /* the first field is the article number */
+ if (p == NULL) {
+ if (number != NULL) {
+ *number = atoi(line);
+ }
+ } else {
+ cvector_add(vector, line);
+ }
+ p = memchr(line, '\t', length);
+ if (p != NULL) {
+ /* skip over the tab */
+ ++p;
+ /* and calculate the remaining length */
+ length -= (p - line);
+ } else {
+ /* add in a pointer to beyond the end of the final
+ * component, so you can always calculate the length.
+ * overview lines are always terminated with \r\n, so the
+ * -1 ends up chopping those off */
+ cvector_add(vector, line + length - 1);
+ }
+ line = p;
+ }
+ return vector;
+}
+
+/*
+** Given an overview vector (from overview_split), return a copy of
+** the member which the caller is interested in (and must free).
+*/
+char *
+overview_getheader(const struct cvector *vector, int element,
+ const struct vector *extra)
+{
+ char *field = NULL;
+ size_t len;
+ const char *p;
+
+ if ((element + 1) >= vector->count ||
+ (element >= ARRAY_SIZE(fields) &&
+ (element - ARRAY_SIZE(fields)) >= extra->count)) {
+ warn("request for invalid overview field %d", element);
+ return NULL;
+ }
+ /* Note... this routine does not synthesise Newsgroups: on behalf
+ * of the caller... */
+ if (element >= ARRAY_SIZE(fields)) {
+ /* +2 for `: ' */
+ p = vector->strings[element] +
+ strlen(extra->strings[element - ARRAY_SIZE(fields)]) + 2;
+ len = vector->strings[element + 1] - p - 1;
+ } else {
+ p = vector->strings[element];
+ len = vector->strings[element + 1] - vector->strings[element] - 1;
+ }
+ field = xstrndup(p, len);
+ return field;
+}
--- /dev/null
+/* $Id: ovinterface.h 6113 2003-01-04 16:29:56Z kondou $
+**
+** Overview interface header
+*/
+
+#ifndef __OVINTERFACE_H__
+#define __OVINTERFACE_H__
+
+#include "config.h"
+#include "ov.h"
+#include "storage.h"
+#include "inn/history.h"
+
+struct buffer;
+struct vector;
+
+typedef struct {
+ const char *name;
+ bool (*open)(int mode);
+ bool (*groupstats)(char *group, int *lo, int *hi, int *count, int *flag);
+ bool (*groupadd)(char *group, ARTNUM lo, ARTNUM hi, char *flag);
+ bool (*groupdel)(char *group);
+ bool (*add)(char *group, ARTNUM artnum, TOKEN token, char *data, int len, time_t arrived, time_t expires);
+ bool (*cancel)(TOKEN token);
+ void *(*opensearch)(char *group, int low, int high);
+ bool (*search)(void *handle, ARTNUM *artnum, char **data, int *len, TOKEN *token, time_t *arrived);
+ void (*closesearch)(void *handle);
+ bool (*getartinfo)(char *group, ARTNUM artnum, TOKEN *token);
+ bool (*expiregroup)(char *group, int *lo, struct history *h);
+ bool (*ctl)(OVCTLTYPE type, void *val);
+ void (*close)(void);
+} OV_METHOD;
+
+extern time_t OVrealnow;
+bool OVgroupbasedexpire(TOKEN token, const char *group, const char *data,
+ int len, time_t arrived, time_t expires);
+bool OVgroupmatch(const char *group);
+bool OVhisthasmsgid(struct history *, const char *data);
+void OVEXPremove(TOKEN token, bool deletedgroups, char **xref, int ngroups);
+void OVEXPcleanup(void);
+bool OVstatall;
+time_t OVnow;
+char *ACTIVE;
+FILE *EXPunlinkfile;
+bool OVignoreselfexpire;
+bool OVusepost;
+bool OVkeep;
+bool OVearliest;
+bool OVquiet;
+int OVnumpatterns;
+char **OVpatterns;
+time_t OVrealnow;
+
+#define DEFAULT_MAX_XREF_LEN 8192
+
+#endif /* __OVINTERFACE_H__ */
--- /dev/null
+The timecaf storage manager is like the timehash storage manager, except that
+it stores multiple articles in one file. The file format is called CAF
+(for "crunched article file", putting multiple articles together into one big
+file), and uses a library 'caf.c' dating back from the pre-storage manager
+days when I made a locally-hacked version of INN1.5 that used this
+code in order to boost performance on my system. Originally I had planned to
+do one big file per newsgroup, but it turns out that a time-based file layout
+rather than newsgroup-name-based is a. more efficient and b. much easier to
+fit into the current storage manager interface paradigm. Anyway, the
+pathnames for the files are of the form
+ <patharticles>/timecaf-nn/bb/aacc.CF
+where 'nn' is the numeric storage class (same as in 'timehash') and the
+file contains all articles written during the interval from
+(time_t) 0xaabbcc00 to 0xaabbccFF.
+
+ The way expiration works on the 'timecaf' storage manager is a bit
+complicated. When articles are expired or cancelled (via SMcancel())
+they are at first just marked as expired in the .CF file -- no actual
+disk space is freed at first. But if fastrm/SMcancel() notices that a
+certain amount of space has been marked as free, then it will do a
+sort of garbage collection pass on the file, writing out a new file
+containing only the articles from the old file that have not yet
+expired and removing the old file. If fastrm notices that *all* the
+articles in a file have been expired, it just deletes the file and
+doesn't create a new one. This means that if your setup has
+newsgroups with differing expiration lengths put in the same timecaf
+storage class, everything will work ok but your expire runs will spend
+some extra time copying files about. In my experience this hasn't been too
+much of a problem. If you find that it is a problem, you may wish to
+consider dividing up your spool layout so each storage class gets newsgroups
+that expire at more-or-less the same time, or putting *.binaries in their own
+storage class.
+
+Some advantages and disadvantages compared to the 'timehash' and
+'CNFS' storage methods:
+
+ timecaf is somewhat faster than timehash in writing articles (the tests
+I did on the old news.ecn.uoknor.edu showed a roughly 4x improvement in
+artwrite speed). This is presumably due to improved locality of reference and
+not having to open/close article files all the time but only every 4 minutes or
+so. Artcancel speed, on the other hand, is not much different, because
+cancel requests have terrible locality of reference. Expire times seem
+to be generally somewhat faster than timehash as well, even given the
+extra copying overhead mentioned above.
+
+ Timecaf is probably slower than CNFS, but I haven't had a chance
+to do any comparison tests. Timecaf does share the feature with timehash
+that you can get much more fine-tuned control of your expire times (on a
+group-by-group basis, if needed) than you can with CNFS.
+
+Down below is an old README telling more about the implementation details
+of the CAF file format. Most people won't care about this, but if you're
+curious, read on; it also tells some of the historical developments that
+went on in this code. I've been running some version of this code off and
+on for the past two years, and have been running it as a storage manager
+module for the past few months, so I'm pretty sure of it's stability.
+
+ Richard Todd
+ (rmtodd@mailhost.ecn.ou.edu/rmtodd@servalan.servalan.com)
+
+\f
+Implementation details (format of a CAF file) and some design rationale:
+
+ Look at include/caf.h for the details, but basically, the layout is
+something like this. Each CAF file has a blocksize associated with it
+(usually 512 bytes, but it can vary). The layout of a CAF file is as
+follows:
+ 1. Header (~52 bytes) containing information like low and high
+article numbers, amount of free space, blocksize.
+ 2. Free space bitmap (size given by the FreeZoneTabSize field of the
+header).
+ 3. CAFTOCENTs (CAF Table of Contents Entries), 1/article storable
+in the file. Each CAFTOCENT gives the article's size, creation time,
+and offset in the CAF file. Usually space is alloted in the CAF file
+for 64K CAFTOCENTs, even if the # of articles in the CAF file is
+nowhere near that amount. The unused CAFTOCENTs are all zeros, and
+this means CAF files are almost always sparse.
+ 4. Articles, always stored starting at blocksize boundaries.
+
+When fastrm is told to remove an article, the article is not actually
+removed as such, it is merely marked as non-existent (the CAFTOCENT is
+zeroed), and the blocks taken up by the article are marked as 'free'
+in the free space bitmap. When innd writes an article to a CAF file,
+it first looks to see if the CAF file has any free blocks in a
+contiguous chunk large enough to handle the article, and if so writes
+the article into those blocks and marks those blocks as being in use.
+If there is no suitable free space chunk in the CAF file, then innd
+merely appends the article to the end of the CAF file and records the
+article's position in the TOC. [Given the way the CAF code is currently
+used by the timecaf storage manager, it's almost always the case that we're
+appending to the end of the file.]
+
+ A note on the free bitmap portion of the CAF file: it's not just a simple
+bitmap (each bit of the bitmap tells whether a data block is in use or free.)
+First there is an 'index' bitmap which tells which blocks of the 'main' bitmap
+have free blocks listed in them, and then a 'main' bitmap which tells whether
+the data blocks are in use or free. This setup means that we can have
+bitmaps for CAF files as large as 8GB, while still being able to find free
+space by only reading the 'index' bitmap and one block of the 'main' bitmap.
+(Previous versions of the CAF code had just a 'main' bitmap and scaled the
+blocksize up when CAF files got large; this became rather, um, non-optimal
+when control.cancel started to hit hundreds of thousands of articles and 8K
+blocksizes.) In practice, CAF files over 2GB or 4GB may be a problem because
+of unsigned/signed long problems, and ones over 4G are probably impossible
+on anything besides an Alpha unless you track down all the places in innd
+where they assume off_t is a long and fix it to work with long longs.
+
+ At some point I'd also like to try some other, more efficient
+directory layout for the CAF files, as opposed to the old
+/var/spool/news/newsgroup/name/component/ scheme. At the time I
+started implementing this, it seemed like it'd be too much of a hassle
+to change this in INN as it stands. I'm hoping that changing this
+assumption (that newsgroup a.b.c is always in directory a/b/c) will be
+easier once INN acquires a nice interface for specifying alternate
+storage managers. [It is and it isn't; as I said, we've currently abandoned
+all relationship between newsgroup names and CAF file names, which
+provided a sizable jump in performance. Before that, I had changed the code
+so that the CAF file for, e.g.,
+alt.tv.babylon-5 will now be /var/spool/news/alt/tv/babylon-5.CF -- note the
+final . instead of a /. This pretty much bypasses the need for the 'terminal'
+layer of directories to be read, and means that these directory blocks will not
+be fighting with other blocks for the limited space available in the buffer
+cache. This provides more of an improvement than you might think; thruput on
+news.ecn.uoknor.edu went from 160,000 articles/day to >200,000 articles/day
+with this patch, and this is on an aging 32M 486/66.]
--- /dev/null
+/* $Id: caf.c 6723 2004-05-16 21:12:53Z rra $
+**
+** Library routines needed for handling CAF (Crunched Article Files)
+** Written by Richard Todd (rmtodd@mailhost.ecn.uoknor.edu) 3/24/96,
+** modified extensively since then. Altered to work with storage manager
+** in INN1.8 by rmtodd 3/27/98.
+*/
+
+#include "config.h"
+#include "clibrary.h"
+#include <errno.h>
+#include <fcntl.h>
+#include <sys/stat.h>
+#include <time.h>
+
+#include "libinn.h"
+
+#define CAF_INNARDS
+#include "caf.h"
+
+/* following code lifted from inndf.c */
+
+#ifdef HAVE_STATVFS
+#include <sys/statvfs.h> /* specific includes */
+/* XXX is there a 'fstatvfs'? I don't have such a system to check--rmtodd*/
+#define STATFUNCT fstatvfs /* function call */
+#define STATSTRUC statvfs /* structure name */
+#define STATAVAIL f_bavail /* blocks available */
+#define STATMULTI f_frsize /* fragment size/block size */
+#define STATINODE f_favail /* inodes available */
+#define STATTYPES u_long /* type of f_bavail etc */
+#define STATFORMT "%lu" /* format string to match */
+#define STATFORMTPAD "%*lu" /* format string to match */
+#endif /* HAVE_STATVFS */
+
+#ifdef HAVE_STATFS
+#ifdef HAVE_SYS_VFS_H
+#include <sys/vfs.h>
+#endif /* HAVE_SYS_VFS_H */
+#ifdef HAVE_SYS_PARAM_H
+#include <sys/param.h>
+#endif /* HAVE_SYS_PARAM_H */
+#ifdef HAVE_SYS_MOUNT_H
+#include <sys/mount.h>
+#endif /* HAVE_SYS_MOUNT_H */
+#define STATFUNCT fstatfs
+#define STATSTRUC statfs
+#define STATAVAIL f_bavail
+#define STATMULTI f_bsize
+#define STATINODE f_ffree;
+#define STATTYPES long
+#define STATFORMT "%ld"
+#define STATFORMTPAD "%*ld"
+#endif /* HAVE_STATFS */
+
+int CAFClean(char *path, int verbose, double PercentFreeThreshold);
+
+int caf_error = 0;
+int caf_errno = 0;
+
+/* check assertions in code (lifted from lib/malloc.c) */
+#define ASSERT(p) do { if (!(p)) botch(__FILE__, __LINE__, #p); } while (0)
+
+static void
+botch(const char *f, int l, const char *s)
+{
+
+ fprintf(stderr, "assertion botched: %s:%d:%s\n", f,l,s);
+ fflush(stderr); /* if stderr writing to file--needed? */
+ abort();
+}
+
+
+/* set error code appropriately. */
+static void
+CAFError(int code)
+{
+ caf_error = code;
+ if (caf_error == CAF_ERR_IO) {
+ caf_errno = errno;
+ }
+}
+
+/*
+** Wrapper around read that calls CAFError if needed. 0 for success, -1 for failure.
+*/
+
+static int
+OurRead(int fd, void *buf, size_t n)
+{
+ ssize_t rval;
+
+ rval = read(fd, buf, n);
+ if (rval < 0) {
+ CAFError(CAF_ERR_IO);
+ return -1;
+ }
+ if ((size_t) rval < n) {
+ /* not enough data! */
+ CAFError(CAF_ERR_BADFILE);
+ return -1;
+ }
+ return 0;
+}
+
+/* Same as OurRead except for writes. */
+static int
+OurWrite(int fd, const void *buf, size_t n)
+{
+ ssize_t rval;
+
+ rval = write(fd, buf, n);
+ if (rval < 0) {
+ CAFError(CAF_ERR_IO);
+ return -1;
+ }
+ if ((size_t) rval < n) {
+ /* not enough data written */
+ CAFError(CAF_ERR_IO);
+ return -1;
+ }
+ return 0;
+}
+
+/*
+** Given an fd, read in a CAF_HEADER from a file. Ret. 0 on success.
+*/
+
+int
+CAFReadHeader(int fd, CAFHEADER *h)
+{
+ /* probably already at start anyway, but paranoia is good. */
+ if (lseek(fd, 0L, SEEK_SET) < 0) {
+ CAFError(CAF_ERR_IO);
+ return -1;
+ }
+
+ if (OurRead(fd, h, sizeof(CAFHEADER)) < 0) return -1;
+
+ if (strncmp(h->Magic, CAF_MAGIC, CAF_MAGIC_LEN) != 0) {
+ CAFError(CAF_ERR_BADFILE);
+ return -1;
+ }
+ return 0;
+}
+
+/*
+** Seek to the TOC entry for a given article. As usual, -1 for error, 0 succ.
+*/
+
+static int
+CAFSeekTOCEnt(int fd, CAFHEADER *head, ARTNUM art)
+{
+ off_t offset;
+
+ offset = sizeof(CAFHEADER) + head->FreeZoneTabSize;
+ offset += (art - head->Low) * sizeof(CAFTOCENT);
+ if (lseek(fd, offset, SEEK_SET) < 0) {
+ CAFError(CAF_ERR_IO);
+ return -1;
+ }
+ return 0;
+}
+
+/*
+** Fetch the TOC entry for a given article. As usual -1 for error, 0 success */
+
+static int
+CAFGetTOCEnt(int fd, CAFHEADER *head, ARTNUM art, CAFTOCENT *tocp)
+{
+ if (CAFSeekTOCEnt(fd, head, art) < 0) {
+ return -1;
+ }
+
+ if (OurRead(fd, tocp, sizeof(CAFTOCENT)) < 0) return -1;
+
+ return 0;
+}
+
+/*
+** Round an offset up to the next highest block boundary. Needs the CAFHEADER
+** to find out what the blocksize is.
+*/
+off_t
+CAFRoundOffsetUp(off_t off, unsigned int blocksize)
+{
+ off_t off2;
+
+ /* Zero means default blocksize, though we shouldn't need this for long,
+ as all new CAF files will have BlockSize set. */
+ if (blocksize == 0) {
+ blocksize = CAF_DEFAULT_BLOCKSIZE;
+ }
+
+ off2 = ((off + blocksize - 1) / blocksize) * blocksize;
+ return off2;
+}
+
+/*
+** Dispose of an already-allocated CAFBITMAP.
+*/
+void
+CAFDisposeBitmap(CAFBITMAP *bm)
+{
+ unsigned int i;
+ CAFBMB *bmb;
+
+ for (i = 0 ; i < bm->NumBMB ; ++i) {
+ if (bm->Blocks[i]) {
+ bmb = bm->Blocks[i];
+ if (bmb->BMBBits) free(bmb->BMBBits);
+ free(bmb);
+ }
+ }
+ free(bm->Blocks);
+ free(bm->Bits);
+ free(bm);
+}
+
+/*
+** Read the index bitmap from a CAF file, return a CAFBITMAP structure.
+*/
+
+/* define this instead of littering all our formulas with semi-mysterious 8s. */
+#define BYTEWIDTH 8
+
+CAFBITMAP *
+CAFReadFreeBM(int fd, CAFHEADER *h)
+{
+ size_t i;
+ struct stat statbuf;
+ CAFBITMAP *bm;
+
+ if (lseek(fd, sizeof(CAFHEADER), SEEK_SET) < 0) {
+ CAFError(CAF_ERR_IO);
+ return NULL;
+ }
+ bm = xmalloc(sizeof(CAFBITMAP));
+
+ bm->FreeZoneTabSize = h->FreeZoneTabSize;
+ bm->FreeZoneIndexSize = h->FreeZoneIndexSize;
+ bm->NumBMB = BYTEWIDTH * bm->FreeZoneIndexSize;
+ bm->BytesPerBMB = (h->BlockSize) * (h->BlockSize * BYTEWIDTH);
+ bm->BlockSize = h->BlockSize;
+
+ bm->Blocks = xmalloc(bm->NumBMB * sizeof(CAFBMB *));
+ bm->Bits = xmalloc(bm->FreeZoneIndexSize);
+ for (i = 0 ; i < bm->NumBMB ; ++i) {
+ bm->Blocks[i] = NULL;
+ }
+
+ if (OurRead(fd, bm->Bits, bm->FreeZoneIndexSize) < 0) {
+ CAFDisposeBitmap(bm);
+ return NULL;
+ }
+
+ bm->StartDataBlock = h->StartDataBlock;
+
+ if (fstat(fd, &statbuf) < 0) {
+ /* it'd odd for this to fail, but paranoia is good for the soul. */
+ CAFError(CAF_ERR_IO);
+ CAFDisposeBitmap(bm);
+ return NULL;
+ }
+ /* round st_size down to a mult. of BlockSize */
+ bm->MaxDataBlock = (statbuf.st_size / bm->BlockSize) * bm->BlockSize + bm->BlockSize;
+ /* (note: MaxDataBlock points to the block *after* the last block of the file. */
+ return bm;
+}
+
+/*
+** Fetch a given bitmap block into memory, and make the CAFBITMAP point to
+** the new BMB appropriately. Return NULL on failure, and the BMB * on success.
+*/
+static CAFBMB *
+CAFFetchBMB(unsigned int blkno, int fd, CAFBITMAP *bm)
+{
+ CAFBMB *newbmb;
+
+ ASSERT(blkno < bm->NumBMB);
+ /* if already in memory, don't need to do anything. */
+ if (bm->Blocks[blkno]) return bm->Blocks[blkno];
+
+ newbmb = xmalloc(sizeof(CAFBMB));
+
+ newbmb->Dirty = 0;
+ newbmb->StartDataBlock = bm->StartDataBlock + blkno*(bm->BytesPerBMB);
+
+ newbmb->MaxDataBlock = newbmb->StartDataBlock + bm->BytesPerBMB;
+ if (newbmb->MaxDataBlock > bm->MaxDataBlock) {
+ /* limit the per-BMB MaxDataBlock to that for the bitmap as a whole */
+ newbmb->MaxDataBlock = bm->MaxDataBlock;
+ }
+
+ newbmb->BMBBits = xmalloc(bm->BlockSize);
+
+ if (lseek(fd, (blkno + 1) * bm->BlockSize, SEEK_SET) < 0) {
+ free(newbmb->BMBBits);
+ free(newbmb);
+ CAFError(CAF_ERR_IO);
+ return NULL;
+ }
+
+ if (OurRead(fd, newbmb->BMBBits, bm->BlockSize) < 0) {
+ free(newbmb->BMBBits);
+ free(newbmb);
+ return NULL;
+ }
+
+ bm->Blocks[blkno] = newbmb;
+ return newbmb;
+}
+
+/*
+** Flush out (if needed) a BMB to disk. Return 0 on success, -1 on failure.
+*/
+
+static int
+CAFFlushBMB(unsigned int blkno, int fd, CAFBITMAP *bm)
+{
+ CAFBMB *bmb;
+
+ ASSERT(blkno < bm->NumBMB);
+
+ if (bm->Blocks[blkno] == NULL) return 0; /* nothing to do. */
+
+ bmb = bm->Blocks[blkno];
+ if (!bmb->Dirty) return 0;
+
+ if (lseek(fd, (blkno + 1) * bm->BlockSize, SEEK_SET) < 0) {
+ CAFError(CAF_ERR_IO);
+ return -1;
+ }
+
+ if (OurWrite(fd, bmb->BMBBits, bm->BlockSize) < 0) return -1;
+
+ bmb->Dirty = 0;
+ return 0;
+}
+
+
+/*
+** Write the free bit map to the CAF file. Return 0 on success, -1 on failure.
+*/
+static int
+CAFWriteFreeBM(int fd, CAFBITMAP *bm)
+{
+ size_t blkno;
+
+ for (blkno = 0 ; blkno < bm->NumBMB ; ++blkno) {
+ if (CAFFlushBMB(blkno, fd, bm) < 0) {
+ return -1;
+ }
+ }
+
+ if (lseek(fd, sizeof(CAFHEADER), SEEK_SET) < 0) {
+ CAFError(CAF_ERR_IO);
+ return -1;
+ }
+
+ if(OurWrite(fd, bm->Bits, bm->FreeZoneIndexSize) < 0) return -1;
+
+ return 0;
+}
+
+/*
+** Determine if a block at a given offset is free. Return 1 if it is, 0
+** otherwise.
+*/
+
+int
+CAFIsBlockFree(CAFBITMAP *bm, int fd, off_t block)
+{
+ unsigned int ind;
+ char mask;
+ int blkno;
+ CAFBMB *bmb;
+
+ /* round block down to BlockSize boundary. */
+ block = block - (block % bm->BlockSize);
+
+ /* if < Start, always return 0 (should never happen in real usage) */
+ if (block < bm->StartDataBlock) return 0;
+
+ /* if off the end, also return 0. */
+ if (block >= bm->MaxDataBlock) return 0;
+
+ /* find blk # of appropriate BMB */
+ blkno = (block - bm->StartDataBlock) / bm->BytesPerBMB;
+
+ bmb = CAFFetchBMB(blkno, fd, bm);
+ /* ick. not a lot we can do here if this fails. */
+ if (bmb == NULL) return 0;
+
+ /* Sanity checking that we have the right BMB. */
+ ASSERT(block >= bmb->StartDataBlock);
+ ASSERT(block < bmb->MaxDataBlock);
+
+ ind = ((block - bmb->StartDataBlock) / bm->BlockSize) / BYTEWIDTH;
+ mask = 1 << (((block - bmb->StartDataBlock) / bm->BlockSize) % BYTEWIDTH);
+
+ ASSERT(ind < bm->BlockSize);
+
+ return ((bmb->BMBBits[ind]) & mask) != 0;
+}
+
+/*
+** Check if a bitmap chunk is all zeros or not.
+*/
+static int
+IsMapAllZero(char *data, int len)
+{
+ int i;
+ for (i = 0 ; i < len ; ++i) {
+ if (data[i] != 0) return 0;
+ }
+ return 1;
+}
+
+/* Set the free bitmap entry for a given block to be a given value (1 or 0). */
+static void
+CAFSetBlockFree(CAFBITMAP *bm, int fd, off_t block, int isfree)
+{
+ unsigned int ind;
+ char mask;
+ int blkno;
+ CAFBMB *bmb;
+ int allzeros;
+
+ /* round block down to BlockSize boundary. */
+ block = block - (block % bm->BlockSize);
+
+ /* if < Start, always return (should never happen in real usage) */
+ if (block < bm->StartDataBlock) return;
+
+ /* if off the end, also return. */
+ if (block >= bm->MaxDataBlock) return;
+ /* find blk # of appropriate BMB */
+ blkno = (block - bm->StartDataBlock) / bm->BytesPerBMB;
+
+ bmb = CAFFetchBMB(blkno, fd, bm);
+ /* ick. not a lot we can do here if this fails. */
+ if (bmb == NULL) return;
+
+ /* Sanity checking that we have the right BMB. */
+ ASSERT(block >= bmb->StartDataBlock);
+ ASSERT(block < bmb->MaxDataBlock);
+
+ ind = ((block - bmb->StartDataBlock) / bm->BlockSize) / BYTEWIDTH;
+ mask = 1 << (((block - bmb->StartDataBlock) / bm->BlockSize) % BYTEWIDTH);
+
+ ASSERT(ind < bm->BlockSize);
+
+ if (isfree) {
+ bmb->BMBBits[ind] |= mask; /* set bit */
+ } else {
+ bmb->BMBBits[ind] &= ~mask; /* clear bit. */
+ }
+
+ bmb->Dirty = 1;
+
+ /* now have to set top level (index) bitmap appropriately */
+ allzeros = IsMapAllZero(bmb->BMBBits, bm->BlockSize);
+
+ ind = blkno/BYTEWIDTH;
+ mask = 1 << (blkno % BYTEWIDTH);
+
+ if (allzeros) {
+ bm->Bits[ind] &= ~mask; /* clear bit */
+ } else {
+ bm->Bits[ind] |= mask;
+ }
+
+ return;
+}
+
+/*
+** Search a freebitmap to find n contiguous free blocks. Returns 0 for
+** failure, offset of starting block if successful.
+** XXX does not attempt to find chunks that span BMB boundaries. This is
+** messy to fix.
+** (Actually I think this case works, as does the case when it tries to find
+** a block bigger than BytesPerBMB. Testing reveals that it does seem to work,
+** though not optimally (some BMBs will get scanned several times).
+*/
+static off_t
+CAFFindFreeBlocks(CAFBITMAP *bm, int fd, unsigned int n)
+{
+ off_t startblk, curblk;
+ unsigned int i, ind, blkno, j;
+ unsigned int bmblkno, k, l;
+ CAFBMB *bmb;
+
+ /* Iterate over all bytes and all bits in the toplevel bitmap. */
+ for (k = 0 ; k < bm->FreeZoneIndexSize ; ++k) {
+ if (bm->Bits[k] == 0) continue;
+ for (l = 0; l < BYTEWIDTH ; ++l) {
+ if ((bm->Bits[k] & (1 << l)) != 0) {
+ /* found a bit set! fetch the BMB. */
+ bmblkno = k*BYTEWIDTH + l;
+ bmb = CAFFetchBMB(bmblkno, fd, bm);
+ if (bmb == NULL) return 0;
+
+ curblk = bmb->StartDataBlock;
+ while (curblk < bmb->MaxDataBlock) {
+ blkno = (curblk - bmb->StartDataBlock)/(bm->BlockSize);
+ ind = blkno/BYTEWIDTH;
+ if (bmb->BMBBits[ind] == 0) {
+ /* nothing set in this byte, skip this byte and move on. */
+ blkno = (ind+1)*BYTEWIDTH;
+ curblk = blkno*bm->BlockSize + bmb->StartDataBlock;
+ continue;
+ }
+
+ /* scan rest of current byte for 1 bits */
+ for (j = blkno % BYTEWIDTH ; j < BYTEWIDTH ; j++, curblk += bm->BlockSize) {
+ if ((bmb->BMBBits[ind] & (1 << j)) != 0) break;
+ }
+ if (j == BYTEWIDTH) continue;
+
+ /* found a 1 bit, set startblk to be locn of corresponding free blk. */
+ startblk = curblk;
+ curblk += bm->BlockSize;
+
+ /* scan for n blocks in a row. */
+ for (i = 1 ; i < n ; ++i, curblk += bm->BlockSize) {
+ if (!CAFIsBlockFree(bm, fd, curblk)) break;
+ }
+
+ if (i == n) return startblk;
+
+ /* otherwise curblk points to a non-free blk, continue searching from there. */
+ continue;
+ }
+ }
+ }
+ }
+ return 0;
+}
+
+/*
+** Open a CAF file for reading and seek to the start of a given article.
+** Take as args the CAF file pathname, article #, and a pointer to where
+** the art. length can be returned.
+*/
+
+int
+CAFOpenArtRead(const char *path, ARTNUM art, size_t *len)
+{
+ CAFHEADER head;
+ int fd;
+ CAFTOCENT tocent;
+ struct stat st;
+
+ if ( (fd = open(path, O_RDONLY)) < 0) {
+ /*
+ ** if ENOENT (not there), just call this "article not found",
+ ** otherwise it's a more serious error and stash the errno.
+ */
+ if (errno == ENOENT) {
+ CAFError(CAF_ERR_ARTNOTHERE);
+ } else {
+ CAFError(CAF_ERR_IO);
+ }
+ return -1;
+ }
+
+ /* Fetch the header */
+ if (CAFReadHeader(fd, &head) < 0) {
+ close(fd);
+ return -1;
+ }
+
+ /* Is the requested article even in the file? */
+ if (art < head.Low || art > head.High) {
+ CAFError(CAF_ERR_ARTNOTHERE);
+ close(fd);
+ return -1;
+ }
+
+ if (CAFGetTOCEnt(fd, &head, art, &tocent) < 0) {
+ close(fd);
+ return -1;
+ }
+
+ if (tocent.Size == 0) {
+ /* empty/otherwise not present article */
+ CAFError(CAF_ERR_ARTNOTHERE);
+ close(fd);
+ return -1;
+ }
+
+ if (lseek(fd, tocent.Offset, SEEK_SET) < 0) {
+ CAFError(CAF_ERR_IO);
+ close(fd);
+ return -1;
+ }
+
+ /* I'm not sure if this fstat is worth the speed hit, but unless we check
+ here, we may simply segfault when we try to access mmap'd space beyond
+ the end of the file. I think robustness wins. */
+ if (fstat(fd, &st) == 0)
+ if (tocent.Size > st.st_size - tocent.Offset) {
+ CAFError(CAF_ERR_IO);
+ close(fd);
+ return -1;
+ }
+
+ *len = tocent.Size;
+ return fd;
+}
+
+/*
+** variables for keeping track of currently pending write.
+** FIXME: assumes only one article open for writing at a time.
+*/
+
+static int CAF_fd_write;
+static ARTNUM CAF_artnum_write;
+static off_t CAF_startoffset_write;
+static CAFHEADER CAF_header_write;
+static CAFBITMAP *CAF_free_bitmap_write;
+static unsigned int CAF_numblks_write;
+
+/*
+** Given estimated size of CAF file (i.e., the size of the old CAF file found
+** by cafclean), find an "optimal" blocksize (one big enough so that the
+** default FreeZoneTabSize can cover the entire
+** file so that we don't "lose" free space and not be able to reuse it.
+** (Currently only returns CAF_DEFAULT_BLOCKSIZE, as with the new 2-level
+** bitmaps, the FreeZoneTabSize that results from a 512-byte blocksize can
+** handle any newsgroup with <7.3G of data. Yow!)
+*/
+
+static unsigned int
+CAFFindOptimalBlocksize(ARTNUM tocsize UNUSED, size_t cfsize)
+{
+
+ if (cfsize == 0) return CAF_DEFAULT_BLOCKSIZE; /* no size given, use default. */
+
+ return CAF_DEFAULT_BLOCKSIZE;
+}
+
+/*
+** Create an empty CAF file. Used by CAFOpenArtWrite.
+** Must be careful here and create the new CAF file under a temp name and then
+** link it into place, to avoid possible race conditions.
+** Note: CAFCreateCAFFile returns fd locked, also to avoid race conds.
+** New args added for benefit of the cleaner program: "nolink", a flag that
+** tells it not to bother with the link business, and "temppath", a pointer
+** to a buffer that (if non-null) gets the pathname of the temp file copied
+** to it. "estcfsize", if nonzero, is an estimate of what the CF filesize will
+** be, used to automatically select a good blocksize.
+*/
+int
+CAFCreateCAFFile(char *cfpath, ARTNUM artnum, ARTNUM tocsize,
+ size_t estcfsize, int nolink, char *temppath)
+{
+ CAFHEADER head;
+ int fd;
+ char path[SPOOLNAMEBUFF];
+ char finalpath[SPOOLNAMEBUFF];
+ off_t offset;
+ char nulls[1];
+
+ strlcpy(finalpath, cfpath, sizeof(finalpath));
+ snprintf(path, sizeof(path), "%s.%d", cfpath, getpid());/* create path with PID attached */
+ /*
+ ** Shouldn't be anyone else with our pid trying to write to the temp.
+ ** file, but there might be an old one lying around. Nuke it.
+ ** (yeah, I'm probably being overly paranoid.)
+ */
+ if (unlink(path) < 0 && errno != ENOENT) {
+ CAFError(CAF_ERR_IO);
+ return -1;
+ }
+ if ((fd = open(path, O_CREAT|O_EXCL|O_RDWR, 0666)) < 0) {
+ CAFError(CAF_ERR_IO);
+ return -1;
+ }
+
+ /* Initialize the header. */
+ strncpy(head.Magic, CAF_MAGIC, CAF_MAGIC_LEN);
+ head.Low = artnum;
+ head.High = artnum;
+ head.NumSlots = tocsize;
+ head.Free = 0;
+ head.BlockSize = CAFFindOptimalBlocksize(tocsize, estcfsize);
+ head.FreeZoneIndexSize = head.BlockSize - sizeof(CAFHEADER);
+ head.FreeZoneTabSize = head.FreeZoneIndexSize
+ + head.BlockSize*head.FreeZoneIndexSize*BYTEWIDTH;
+ head.StartDataBlock = CAFRoundOffsetUp(sizeof(CAFHEADER)
+ + head.FreeZoneTabSize + tocsize*sizeof(CAFTOCENT), head.BlockSize);
+
+ head.spare[0] = head.spare[1] = head.spare[2] = 0;
+
+ if (OurWrite(fd, &head, sizeof(head)) < 0) {
+ close(fd);
+ return -1;
+ }
+
+ offset = sizeof(CAFHEADER) + head.FreeZoneTabSize +
+ sizeof(CAFTOCENT) * tocsize;
+
+ if (lseek(fd, offset, SEEK_SET) < 0) {
+ CAFError(CAF_ERR_IO);
+ return -1;
+ }
+ /*
+ ** put a null after the TOC as a 'placeholder', so that we'll have a sparse
+ ** file and that EOF will be at where the articles should start going.
+ */
+ nulls[0] = 0;
+ if (OurWrite(fd, nulls, 1) < 0) {
+ close(fd);
+ return -1;
+ }
+ /* shouldn't be anyone else locking our file, since temp file has unique
+ PID-based name ... */
+ if (!inn_lock_file(fd, INN_LOCK_WRITE, false)) {
+ CAFError(CAF_ERR_IO);
+ close(fd);
+ return -1;
+ }
+
+ if (nolink) {
+ if (temppath != NULL) {
+ strcpy(temppath, path);
+ }
+ return fd;
+ }
+
+ /*
+ ** Try to link to the real one. NOTE: we may get EEXIST here, which we
+ ** will handle specially in OpenArtWrite.
+ */
+ if (link(path, finalpath) < 0) {
+ CAFError(CAF_ERR_IO);
+ /* bounced on the link attempt, go ahead and unlink the temp file and return. */
+ unlink(path);
+ close(fd);
+ return -1;
+ }
+ /*
+ ** Unlink the temp. link. Do we really care if this fails? XXX
+ ** Not sure what we can do anyway.
+ */
+ unlink(path);
+ return fd;
+}
+
+/*
+** Try to open a CAF file for writing a given article. Return an fd to
+** write to (already positioned to the right place to write at) if successful,
+** else -1 on error. if LockFlag is true, we wait for a lock on the file,
+** otherwise we fail if we can't lock it. If size is != 0, we try to allocate
+** a chunk from free space in the CAF instead of writing at the end of the
+** file. Artp is a pointer to the article number to use; if the article number
+** is zero, the next free article # ("High"+1) will be used, and *artp will
+** be set accordingly. Once the CAF file is open/created, CAFStartWriteFd()
+** does the remaining dirty work.
+*/
+
+int
+CAFOpenArtWrite(char *path, ARTNUM *artp, int waitlock, size_t size)
+{
+ int fd;
+
+ while (true) {
+ /* try to open the file and lock it. */
+ if ((fd = open(path, O_RDWR)) < 0) {
+ /* if ENOENT, try creating CAF file, otherwise punt. */
+ if (errno != ENOENT) {
+ CAFError(CAF_ERR_IO);
+ return -1;
+ } else {
+ /*
+ ** the *artp? business is so that if *artp==0, we set initial
+ ** article # to 1.
+ */
+ fd = CAFCreateCAFFile(path, (*artp ? *artp : 1),
+ CAF_DEFAULT_TOC_SIZE, 0, 0, NULL);
+ /*
+ ** XXX possible race condition here, so we check to see if
+ ** create failed because of EEXIST. If so, we go back to top
+ ** of loop, because someone else was trying to create at the
+ ** same time.
+ ** Is this the best way to solve this?
+ ** (Hmm. this condition should be quite rare, occuring only
+ ** when two different programs are simultaneously doing
+ ** CAFOpenArtWrite()s, and no CF file exists previously.)
+ */
+ if (fd < 0) {
+ if (caf_errno == EEXIST) {
+ /* ignore the error and try again */
+ continue;
+ }
+ return -1; /* other error, assume caf_errno set properly. */
+ }
+ /*
+ ** break here, because CreateCAFFile does
+ ** lock fd, so we don't need to flock it ourselves.
+ */
+ break;
+ }
+ }
+
+ /* try a nonblocking lock attempt first. */
+ if (inn_lock_file(fd, INN_LOCK_WRITE, false)) break;
+
+ if (!waitlock) {
+ CAFError(CAF_ERR_FILEBUSY);
+ close(fd); /* keep from leaking fds. */
+ return -1;
+ }
+ /* wait around to try and get a lock. */
+ inn_lock_file(fd, INN_LOCK_WRITE, true);
+ /*
+ ** and then close and reopen the file, in case someone changed the
+ ** file out from under us.
+ */
+ close(fd);
+ }
+ return CAFStartWriteFd(fd, artp, size);
+}
+
+/*
+** Like CAFOpenArtWrite(), except we assume the CAF file is already
+** open/locked, and we have an open fd to it.
+*/
+int
+CAFStartWriteFd(int fd, ARTNUM *artp, size_t size)
+{
+ CAFHEADER head;
+ CAFTOCENT tocent;
+ off_t offset, startoffset;
+ unsigned int numblks = 0;
+ CAFBITMAP *freebm;
+ ARTNUM art;
+
+ /* fd is open to the CAF file, open for write and locked. */
+ /* Fetch the header */
+ if (CAFReadHeader(fd, &head) < 0) {
+ close(fd);
+ return -1;
+ }
+
+ /* check for zero article number and handle accordingly. */
+ art = *artp;
+ if (art == 0) {
+ /* assign next highest article number. */
+ art = head.High + 1;
+ /* and pass to caller. */
+ *artp = art;
+ }
+
+ /* Is the requested article even in the file? */
+ if (art < head.Low || art >= head.Low + head.NumSlots) {
+ CAFError(CAF_ERR_ARTWONTFIT);
+ close(fd);
+ return -1;
+ }
+
+ /*
+ ** Get the CAFTOCENT for that article, but only if article# is in the range
+ ** Low <= art# <= High. If art# > High, use a zero CAFTOCENT. This means
+ ** that in cases where the CAF file is inconsistent due to a crash ---
+ ** the CAFTOCENT shows an article as being existent, but the header
+ ** doesn't show that article as being in the currently valid range ---
+ ** the header value "wins" and we assume the article does not exist.
+ ** This avoids problems with "half-existent" articles that showed up
+ ** in the CAF TOC, but were never picked up by ctlinnd renumber '' .
+ */
+ /* (Note: We already checked above that art >= head.Low.) */
+
+ if (art > head.High) {
+ /* clear the tocent */
+ memset(&tocent, 0, sizeof(tocent));
+ } else {
+ if (CAFGetTOCEnt(fd, &head, art, &tocent) < 0) {
+ close(fd);
+ return -1;
+ }
+ }
+
+ if (tocent.Size != 0) {
+ /* article is already here */
+ CAFError(CAF_ERR_ARTALREADYHERE);
+ close(fd);
+ return -1;
+ }
+
+ startoffset = 0;
+ freebm = NULL;
+
+ if (size != 0 && (freebm = CAFReadFreeBM(fd, &head)) != NULL) {
+ numblks = (size + head.BlockSize - 1) / head.BlockSize;
+ startoffset = CAFFindFreeBlocks(freebm, fd, numblks);
+ if (startoffset == 0) {
+ CAFDisposeBitmap(freebm);
+ freebm = NULL;
+ }
+ }
+
+ if (startoffset == 0) {
+ /*
+ ** No size given or free space not available, so
+ ** seek to EOF to prepare to start writing article.
+ */
+
+ if ((offset = lseek(fd, 0, SEEK_END)) < 0) {
+ CAFError(CAF_ERR_IO);
+ close(fd);
+ return -1;
+ }
+ /* and round up offset to a block boundary. */
+ startoffset = CAFRoundOffsetUp(offset, head.BlockSize);
+ }
+
+ /* Seek to starting offset for the new artiicle. */
+ if (lseek(fd, startoffset, SEEK_SET) < 0) {
+ CAFError(CAF_ERR_IO);
+ close(fd);
+ return -1;
+ }
+
+ /* stash data for FinishArtWrite's use. */
+ CAF_fd_write = fd;
+ CAF_artnum_write = art;
+ CAF_startoffset_write = startoffset;
+ CAF_header_write = head;
+ CAF_free_bitmap_write = freebm;
+ CAF_numblks_write = numblks;
+
+ return fd;
+}
+
+/*
+** write out TOC entries for the previous article. Note that we do *not*
+** (as was previously done) close the fd; this allows reuse of the fd to write
+** another article to this CAF file w/o an (soemwhat expensive) open().
+*/
+
+int
+CAFFinishArtWrite(int fd)
+{
+ off_t curpos;
+ CAFTOCENT tocentry;
+ off_t curblk;
+ CAFHEADER *headp;
+ unsigned int i;
+
+ /* blah, really should handle multiple pending OpenArtWrites. */
+ if (fd != CAF_fd_write) {
+ fprintf(stderr, "CAF: fd mismatch in CloseArtWrite.\n");
+ abort();
+ }
+
+ headp = &CAF_header_write;
+
+ /* Find out where we left off writing in the file. */
+ if ((curpos = lseek(fd, 0, SEEK_CUR)) < 0) {
+ CAFError(CAF_ERR_IO);
+ CAF_fd_write = 0;
+ return -1;
+ }
+
+ /* Write the new TOC entry. */
+ if (CAFSeekTOCEnt(fd, headp, CAF_artnum_write) < 0) {
+ CAF_fd_write = 0;
+ return -1;
+ }
+ tocentry.Offset = CAF_startoffset_write;
+ tocentry.Size = curpos - CAF_startoffset_write;
+ tocentry.ModTime = time((time_t *)NULL);
+ if (OurWrite(fd, &tocentry, sizeof(CAFTOCENT)) < 0) {
+ CAF_fd_write = 0;
+ return -1;
+ }
+
+ /* if needed, update free bitmap. */
+ if (CAF_free_bitmap_write != NULL) {
+ /* Paranoia: check to make sure we didn't write more than we said we would. */
+ if (tocentry.Size > CAF_numblks_write * headp->BlockSize) {
+ /*
+ ** for now core dump (might as well, if we've done this the CAF
+ ** file is probably thoroughly hosed anyway.)
+ */
+ fprintf(stderr, "CAF: article written overran declared size.\n");
+ abort();
+ }
+
+ curblk = CAF_startoffset_write;
+
+ for (i = 0 ; i < CAF_numblks_write ; ++i, curblk += headp->BlockSize) {
+ CAFSetBlockFree(CAF_free_bitmap_write, fd, curblk, 0);
+ }
+ if (CAFWriteFreeBM(fd, CAF_free_bitmap_write) < 0){
+ CAFError(CAF_ERR_IO);
+ CAF_fd_write = 0;
+ return -1;
+ }
+ CAFDisposeBitmap(CAF_free_bitmap_write);
+ /* and update the Free value in the header. */
+ headp->Free -= CAF_numblks_write * headp->BlockSize;
+ }
+
+ if (CAF_artnum_write > headp->High || CAF_free_bitmap_write) {
+ /* need to update header. */
+ if (CAF_artnum_write > headp->High) {
+ headp->High = CAF_artnum_write;
+ }
+ if (lseek(fd, 0, SEEK_SET) < 0) {
+ CAFError(CAF_ERR_IO);
+ CAF_fd_write = 0;
+ return -1;
+ }
+ if (OurWrite(fd, headp, sizeof(CAFHEADER)) < 0) {
+ CAF_fd_write = 0;
+ return -1;
+ }
+ }
+#if 0
+ if (close(fd) < 0) {
+ CAFError(CAF_ERR_IO);
+ CAF_fd_write =0;
+ return -1;
+ }
+#endif
+ CAF_fd_write = 0;
+ return 0;
+}
+
+/*
+** return a string containing a description of the error.
+** Warning: uses a static buffer, or possibly a static string.
+*/
+
+static char errbuf[512];
+
+const char *
+CAFErrorStr(void)
+{
+ if (caf_error == CAF_ERR_IO || caf_error == CAF_ERR_CANTCREATECAF) {
+ snprintf(errbuf, sizeof(errbuf), "%s errno=%s\n",
+ (caf_error == CAF_ERR_IO) ? "CAF_ERR_IO" : "CAF_ERR_CANTCREATECAF",
+ strerror(errno));
+ return errbuf;
+ } else {
+ switch(caf_error) {
+ case CAF_ERR_BADFILE:
+ return "CAF_ERR_BADFILE";
+ case CAF_ERR_ARTNOTHERE:
+ return "CAF_ERR_ARTNOTHERE";
+ case CAF_ERR_FILEBUSY:
+ return "CAF_ERR_FILEBUSY";
+ case CAF_ERR_ARTWONTFIT:
+ return "CAF_ERR_ARTWONTFIT";
+ case CAF_ERR_ARTALREADYHERE:
+ return "CAF_ERR_ARTALREADYHERE";
+ case CAF_ERR_BOGUSPATH:
+ return "CAF_ERR_BOGUSPATH";
+ default:
+ snprintf(errbuf, sizeof(errbuf), "CAF error %d", caf_error);
+ return errbuf;
+ }
+ }
+}
+
+/*
+** Open a CAF file, snarf the TOC entries for all the articles inside,
+** and close the file. NOTE: returns the header for the CAF file in
+** the storage pointed to by *ch. Dynamically allocates storage for
+** the TOC entries, which should be freed by the caller when the
+** caller's done with it. Return NULL on failure.
+**
+** This function calls CAFOpenReadTOC(dir, ch, &tocp), which does most
+** (practically all) of the dirty work. CAFOpenReadTOC leaves the fd open
+** (and returns it); this is needed by cafls. CAFReadTOC() closes the fd
+** after CAFOpenReadTOC() is done with it.
+*/
+
+CAFTOCENT *
+CAFReadTOC(char *path, CAFHEADER *ch)
+{
+ CAFTOCENT *tocp;
+ int fd;
+
+ if ((fd = CAFOpenReadTOC(path, ch, &tocp)) < 0) {
+ return NULL; /* some sort of error happened */
+ }
+
+ close(fd);
+ return tocp;
+}
+
+int
+CAFOpenReadTOC(char *path, CAFHEADER *ch, CAFTOCENT **tocpp)
+{
+ int fd;
+ int nb;
+ CAFTOCENT *tocp;
+ off_t offset;
+
+ if ( (fd = open(path, O_RDONLY)) < 0) {
+ /*
+ ** if ENOENT (not there), just call this "article not found",
+ ** otherwise it's a more serious error and stash the errno.
+ */
+ if (errno == ENOENT) {
+ CAFError(CAF_ERR_ARTNOTHERE);
+ } else {
+ CAFError(CAF_ERR_IO);
+ }
+ return -1;
+ }
+
+ /* Fetch the header */
+ if (CAFReadHeader(fd, ch) < 0) {
+ close(fd);
+ return -1;
+ }
+
+ /* Allocate memory for TOC. */
+ tocp = xmalloc((ch->High - ch->Low + 1) * sizeof(CAFTOCENT));
+ nb = (sizeof(CAFTOCENT))*(ch->High - ch->Low + 1); /* # bytes to read for toc. */
+
+ /* seek to beginning of TOC */
+ offset = sizeof(CAFHEADER) + ch->FreeZoneTabSize;
+
+ if (lseek(fd, offset, SEEK_SET) < 0) {
+ CAFError(CAF_ERR_IO);
+ return -1;
+ }
+
+ if (OurRead(fd, tocp, nb) < 0) {
+ return -1;
+ }
+
+ /* read TOC successfully, return fd and stash tocp where we were told to */
+ *tocpp = tocp;
+ return fd;
+}
+
+
+/*
+** Cancel/expire articles from a CAF file. This involves zeroing the Size
+** field of the TOC entry, and updating the Free field of the CAF header.
+** note that no disk space is actually freed by this process; space will only
+** be returned to the OS when the cleaner daemon runs on the CAF file.
+*/
+
+int
+CAFRemoveMultArts(char *path, unsigned int narts, ARTNUM *artnums)
+{
+ int fd;
+ CAFHEADER head;
+ CAFTOCENT tocent;
+ CAFBITMAP *freebitmap;
+ ARTNUM art;
+ unsigned int numblksfreed, i, j;
+ off_t curblk;
+ int errorfound = false;
+
+ while (true) {
+ /* try to open the file and lock it */
+ if ((fd = open(path, O_RDWR)) < 0) {
+ /* if ENOENT, CAF file isn't there, so return ARTNOTHERE, otherwise it's an I/O error. */
+ if (errno != ENOENT) {
+ CAFError(CAF_ERR_IO);
+ return -1;
+ } else {
+ CAFError(CAF_ERR_ARTNOTHERE);
+ return -1;
+ }
+ }
+ /* try a nonblocking lock attempt first. */
+ if (inn_lock_file(fd, INN_LOCK_WRITE, false)) break;
+
+ /* wait around to try and get a lock. */
+ inn_lock_file(fd, INN_LOCK_WRITE, true);
+ /*
+ ** and then close and reopen the file, in case someone changed the
+ ** file out from under us.
+ */
+ close(fd);
+ }
+ /* got the file, open for write and locked. */
+ /* Fetch the header */
+ if (CAFReadHeader(fd, &head) < 0) {
+ close(fd);
+ return -1;
+ }
+
+ if ((freebitmap = CAFReadFreeBM(fd, &head)) == NULL) {
+ close(fd);
+ return -1;
+ }
+
+ for (j = 0 ; j < narts ; ++j) {
+ art = artnums[j];
+
+ /* Is the requested article even in the file? */
+ if (art < head.Low || art > head.High) {
+ CAFError(CAF_ERR_ARTNOTHERE);
+ errorfound = true;
+ continue; /* don't abandon the whole remove if just one art is missing */
+ }
+
+ if (CAFGetTOCEnt(fd, &head, art, &tocent) < 0) {
+ close(fd);
+ CAFDisposeBitmap(freebitmap);
+ return -1;
+ }
+
+ if (tocent.Size == 0) {
+ CAFError(CAF_ERR_ARTNOTHERE);
+ errorfound = true;
+ continue; /* don't abandon the whole remove if just one art is missing */
+ }
+
+ numblksfreed = (tocent.Size + head.BlockSize - 1) / head.BlockSize;
+
+ /* Mark all the blocks as free. */
+ for (curblk = tocent.Offset, i = 0 ; i < numblksfreed; ++i, curblk += head.BlockSize) {
+ CAFSetBlockFree(freebitmap, fd, curblk, 1);
+ }
+ /* Note the amount of free space added. */
+ head.Free += numblksfreed * head.BlockSize;
+ /* and mark the tocent as a deleted entry. */
+ tocent.Size = 0;
+
+ if (CAFSeekTOCEnt(fd, &head, art) < 0) {
+ close(fd);
+ CAFDisposeBitmap(freebitmap);
+ return -1;
+ }
+
+ if (OurWrite(fd, &tocent, sizeof(CAFTOCENT)) < 0) {
+ close(fd);
+ CAFDisposeBitmap(freebitmap);
+ return -1;
+ }
+ }
+
+ if (CAFWriteFreeBM(fd, freebitmap) < 0) {
+ close(fd);
+ CAFDisposeBitmap(freebitmap);
+ return -1;
+ }
+ /* dispose of bitmap storage. */
+ CAFDisposeBitmap(freebitmap);
+
+ /* need to update header. */
+ if (lseek(fd, 0, SEEK_SET) < 0) {
+ CAFError(CAF_ERR_IO);
+ return -1;
+ }
+ if (OurWrite(fd, &head, sizeof(CAFHEADER)) < 0) {
+ return -1;
+ }
+
+ if (close(fd) < 0) {
+ CAFError(CAF_ERR_IO);
+ return -1;
+ }
+
+ if (CAFClean(path, 0, 10.0) < 0) errorfound=true;
+
+ return errorfound ? -1 : 0;
+}
+
+/*
+** Do a fake stat() of a CAF-stored article. Both 'inpaths' and 'innfeed'
+** find this functionality useful, so we've added a function to do this.
+** Caveats: not all of the stat structure is filled in, only these items:
+** st_mode, st_size, st_atime, st_ctime, st_mtime. (Note:
+** atime==ctime==mtime always, as we don't track times of CAF reads.)
+*/
+
+int
+CAFStatArticle(char *path, ARTNUM art, struct stat *stbuf)
+{
+ CAFHEADER head;
+ int fd;
+ CAFTOCENT tocent;
+
+ if ( (fd = open(path, O_RDONLY)) < 0) {
+ /*
+ ** if ENOENT (not there), just call this "article not found",
+ ** otherwise it's a more serious error and stash the errno.
+ */
+ if (errno == ENOENT) {
+ CAFError(CAF_ERR_ARTNOTHERE);
+ } else {
+ CAFError(CAF_ERR_IO);
+ }
+ return -1;
+ }
+
+ /* Fetch the header */
+ if (CAFReadHeader(fd, &head) < 0) {
+ close(fd);
+ return -1;
+ }
+
+ /* Is the requested article even in the file? */
+ if (art < head.Low || art > head.High) {
+ CAFError(CAF_ERR_ARTNOTHERE);
+ close(fd);
+ return -1;
+ }
+
+ if (CAFGetTOCEnt(fd, &head, art, &tocent) < 0) {
+ close(fd);
+ return -1;
+ }
+
+ if (tocent.Size == 0) {
+ /* empty/otherwise not present article */
+ CAFError(CAF_ERR_ARTNOTHERE);
+ close(fd);
+ return -1;
+ }
+
+ /* done with file, can close it. */
+ close(fd);
+
+ memset(stbuf, 0, sizeof(struct stat));
+ stbuf->st_mode = S_IFREG | 0444;
+ stbuf->st_size = tocent.Size;
+ stbuf->st_atime = stbuf->st_ctime = stbuf->st_mtime = tocent.ModTime;
+ return 0;
+}
+
+/*
+** Taken from the old 'cafclean' program.
+** Function to clean a single CAF file.
+** Possibly the ugliest function I've ever written in my life.
+*/
+/*
+** We try to keep the total TOC size this many times larger than the actual
+** amount of TOC data in use so as not to have to reclean or compact the TOC
+** so often.
+*/
+#define TOC_CLEAN_RATIO 10
+/*
+** ditto, but for compacting, we want to force a compacting if the High art#
+** wanders into the top nth of the TOC slots.
+*/
+#define TOC_COMPACT_RATIO 5
+
+int
+CAFClean(char *path, int verbose, double PercentFreeThreshold)
+{
+ char *newpath;
+ CAFHEADER head, newhead;
+ int fdin, fdout;
+ ARTNUM newlow;
+ ARTNUM i;
+ CAFTOCENT *tocarray, *tocp;
+ CAFTOCENT *newtocarray, *newtocp;
+ size_t newtocsize;
+ FILE *infile, *outfile;
+ off_t startoffset, newstartoffset;
+ char buf[BUFSIZ];
+ int nbytes, ncur;
+ int n;
+ unsigned int blocksize;
+ char *zerobuff;
+ struct stat statbuf;
+ size_t datasize;
+ double percentfree;
+ int toc_needs_expansion;
+ int toc_needs_compacting;
+
+#ifdef STATFUNCT
+ struct STATSTRUC fsinfo;
+ long num_diskblocks_needed;
+#endif
+
+ /* allocate buffer for newpath */
+ newpath = xmalloc(strlen(path) + 10);
+ while (true) {
+ /* try to open the file and lock it. */
+ if ((fdin = open(path, O_RDWR)) < 0) {
+ /*
+ ** if ENOENT, obviously no CAF file is here, so just return,
+ ** otherwise report an error.
+ */
+ if (errno != ENOENT) {
+ CAFError(CAF_ERR_IO);
+ return -1;
+ } else {
+ return 0;
+ }
+ }
+
+ /* try a nonblocking lock attempt first. */
+ if (inn_lock_file(fdin, INN_LOCK_WRITE, false)) break;
+
+ /* wait around to try and get a lock. */
+ inn_lock_file(fdin, INN_LOCK_WRITE, true);
+ /*
+ ** and then close and reopen the file, in case someone changed the
+ ** file out from under us.
+ */
+ close(fdin);
+ }
+
+ /* got the file, open for write and locked. */
+ /* Fetch the header */
+ if (CAFReadHeader(fdin, &head) < 0) {
+ close(fdin);
+ return -1;
+ }
+
+ /* Stat the file to see how big it is */
+ if (fstat(fdin, &statbuf) < 0) {
+ close(fdin);
+ CAFError(CAF_ERR_IO);
+ perror(path);
+ return -1;
+ }
+
+ /* compute amount of actual data in file. */
+ datasize = statbuf.st_size - head.StartDataBlock;
+ if (datasize <= 0) {
+ /* nothing in the file, set percentfree==0 so won't bother cleaning */
+ percentfree = 0;
+ } else {
+ percentfree = (100.0 * head.Free) / datasize;
+ }
+
+ /*
+ ** Grumble, we need to read the TOC now even before we clean, just so
+ ** we can decide if a clean or a compaction is needed.
+ */
+
+ lseek(fdin, 0L, SEEK_SET);
+
+ /* make input file stdio-buffered. */
+ if ((infile = fdopen(fdin, "r+")) == NULL) {
+ CAFError(CAF_ERR_IO);
+ close(fdin);
+ return -1;
+ }
+
+ /* Allocate memory for TOC. */
+ tocarray = xmalloc((head.High - head.Low + 1) * sizeof(CAFTOCENT));
+
+ fseeko(infile, (off_t) (sizeof(CAFHEADER) + head.FreeZoneTabSize),
+ SEEK_SET);
+
+ n = fread(tocarray, sizeof(CAFTOCENT), (head.High - head.Low + 1), infile);
+ if (n < 0) {
+ CAFError(CAF_ERR_IO);
+ fclose(infile);
+ free(tocarray);
+ free(newpath);
+ return -1;
+ }
+
+ if ((unsigned long) n < (head.High - head.Low +1)) {
+ CAFError(CAF_ERR_BADFILE);
+ fclose(infile);
+ free(tocarray);
+ free(newpath);
+ return -1;
+ }
+
+ /* Scan to see what the new lower bound for CAF file should be. */
+ newlow = head.High + 1;
+
+ for (tocp = tocarray, i = head.Low; i <= head.High; ++tocp, ++i) {
+ if (tocp->Size != 0) {
+ newlow = i;
+ break;
+ }
+ }
+
+ /*
+ ** if newlow is head.High+1, the TOC is completely empty and we can
+ ** just remove the entire file.
+ */
+ if (newlow == head.High + 1) {
+ unlink(path);
+ fclose(infile);
+ free(tocarray);
+ free(newpath);
+ return 0;
+ }
+
+ /*
+ ** Ah. NOW we get to decide if we need a clean!
+ ** Clean if either
+ ** 1) the absolute freespace threshold is crossed
+ ** 2) the percent free threshold is crossed.
+ ** 3) The CAF TOC is over 10% full (assume it needs to be expanded,
+ ** so we force a clean)
+ ** Note that even if we do not need a clean, we may need a compaction
+ ** if the high article number is in the top nth of the TOC.
+ */
+
+ toc_needs_expansion = 0;
+ if ( (head.High - newlow) >= head.NumSlots/TOC_CLEAN_RATIO) {
+ toc_needs_expansion = 1;
+ }
+
+ toc_needs_compacting = 0;
+ if ( (head.Low + head.NumSlots - head.NumSlots/TOC_COMPACT_RATIO) <= head.High) {
+ toc_needs_compacting = 1;
+ }
+
+ if ( (percentfree < PercentFreeThreshold)
+ && (!toc_needs_expansion) ) {
+ /* no cleaning, but do we need a TOC compaction ? */
+ if (toc_needs_compacting) {
+ int delta;
+ CAFTOCENT *tocp2;
+
+ if (verbose) {
+ printf("Compacting %s: Free=%lu (%f%%)\n", path,
+ (unsigned long) head.Free, percentfree);
+ }
+
+ delta = newlow - head.Low;
+
+ /* slide TOC array down delta units. */
+ for (i = newlow, tocp = tocarray, tocp2 = tocarray+delta;
+ i <= head.High ; ++i) {
+ *tocp++ = *tocp2++;
+ }
+
+ head.Low = newlow;
+ /* note we don't set LastCleaned, this doesn't count a a clean. */
+ /* (XXX: do we need a LastCompacted as well? might be nice.) */
+
+ /* write new header on top of old */
+ fseeko(infile, 0, SEEK_SET);
+ if (fwrite(&head, sizeof(CAFHEADER), 1, infile) < 1) {
+ CAFError(CAF_ERR_IO);
+ free(tocarray);
+ free(newpath);
+ fclose(infile);
+ return -1;
+ }
+ /*
+ ** this next fseeko might actually fail, because we have buffered
+ ** stuff that might fail on write.
+ */
+ if (fseeko(infile, sizeof(CAFHEADER) + head.FreeZoneTabSize,
+ SEEK_SET) < 0) {
+ perror(path);
+ free(tocarray);
+ free(newpath);
+ fclose(infile);
+ unlink(newpath);
+ return -1;
+ }
+ if (fwrite(tocarray, sizeof(CAFTOCENT), head.High - newlow + 1, infile) < head.High - newlow + 1
+ || fflush(infile) < 0) {
+ CAFError(CAF_ERR_IO);
+ free(tocarray);
+ free(newpath);
+ fclose(infile);
+ return -1;
+ }
+ /* all done, return. */
+ fclose(infile);
+ free(tocarray);
+ free(newpath);
+ return 0;
+ } else {
+ /* need neither full cleaning nor compaction, so return. */
+ if (verbose) {
+ printf("Not cleaning %s: Free=%lu (%f%%)\n", path,
+ (unsigned long) head.Free, percentfree);
+ }
+ fclose(infile);
+ free(tocarray);
+ free(newpath);
+ return 0;
+ }
+ }
+
+ /*
+ ** If OS supports it, try to check for free space and skip this file if
+ ** not enough free space on this filesystem.
+ */
+#ifdef STATFUNCT
+ if (STATFUNCT(fdin, &fsinfo) >= 0) {
+ /* compare avail # blocks to # blocks needed for current file.
+ ** # blocks needed is approximately
+ ** datasize/blocksize + (size of the TOC)/blocksize
+ ** + Head.BlockSize/blocksize, but we need to take rounding
+ ** into account.
+ */
+#define RoundIt(n) (CAFRoundOffsetUp((n), fsinfo.STATMULTI) / fsinfo.STATMULTI)
+
+ num_diskblocks_needed = RoundIt((head.High - head.Low + 1)*sizeof(CAFTOCENT))
+ + RoundIt(datasize - head.Free) + RoundIt(head.BlockSize);
+ if (num_diskblocks_needed > fsinfo.STATAVAIL) {
+ if (verbose) {
+ printf("CANNOT clean %s: needs %ld blocks, only %ld avail.\n",
+ path, num_diskblocks_needed,
+ (unsigned long) fsinfo.f_bavail);
+ }
+ fclose(infile);
+ free(tocarray);
+ free(newpath);
+ return 0;
+ }
+ }
+#endif
+
+ if (verbose) {
+ printf("Am cleaning %s: Free=%d (%f%%) %s\n", path, head.Free,
+ percentfree, toc_needs_expansion ? "(Expanding TOC)" : "");
+ }
+
+ /* decide on proper size for new TOC */
+ newtocsize = CAF_DEFAULT_TOC_SIZE;
+ if (head.High - newlow > newtocsize/TOC_CLEAN_RATIO) {
+ newtocsize = TOC_CLEAN_RATIO*(head.High - newlow);
+ }
+
+ /* try to create new CAF file with some temp. pathname */
+ /* note: new CAF file is created in flocked state. */
+ if ((fdout = CAFCreateCAFFile(path, newlow, newtocsize,
+ statbuf.st_size, 1, newpath)) < 0) {
+ fclose(infile);
+ free(tocarray);
+ free(newpath);
+ return -1;
+ }
+
+ if ((outfile = fdopen(fdout, "w+")) == NULL) {
+ CAFError(CAF_ERR_IO);
+ fclose(infile);
+ free(tocarray);
+ unlink(newpath);
+ free(newpath);
+ return -1;
+ }
+
+ newtocarray = xcalloc((head.High - newlow + 1), sizeof(CAFTOCENT));
+
+ if (fseeko(outfile, 0, SEEK_SET) < 0) {
+ perror(newpath);
+ free(tocarray);
+ free(newtocarray);
+ fclose(infile);
+ fclose(outfile);
+ unlink(newpath);
+ free(newpath);
+ return -1;
+ }
+
+ /* read in the CAFheader from the new file. */
+ if (fread(&newhead, sizeof(CAFHEADER), 1, outfile) < 1) {
+ perror(newpath);
+ free(tocarray);
+ free(newtocarray);
+ fclose(infile);
+ fclose(outfile);
+ unlink(newpath);
+ free(newpath);
+ return -1;
+ }
+
+ /* initialize blocksize, zeroes buffer. */
+ blocksize = newhead.BlockSize;
+ if (blocksize == 0) blocksize=CAF_DEFAULT_BLOCKSIZE;
+
+ zerobuff = xcalloc(blocksize, 1);
+
+ /* seek to end of output file/place to start writing new articles */
+ fseeko(outfile, 0, SEEK_END);
+ startoffset = ftello(outfile);
+ startoffset = CAFRoundOffsetUp(startoffset, blocksize);
+ fseeko(outfile, (off_t) startoffset, SEEK_SET);
+
+ /*
+ ** Note: startoffset will always give the start offset of the next
+ ** art to be written to the outfile.
+ */
+
+ /*
+ ** Loop over all arts in old TOC, copy arts that are still here to new
+ ** file and new TOC.
+ */
+
+ for (tocp = tocarray, i = head.Low; i <= head.High; ++tocp, ++i) {
+ if (tocp->Size != 0) {
+ newtocp = &newtocarray[i - newlow];
+ newtocp->Offset = startoffset;
+ newtocp->Size = tocp->Size;
+ newtocp->ModTime = tocp->ModTime;
+
+ /* seek to right place in input. */
+ fseeko(infile, (off_t) tocp->Offset, SEEK_SET);
+
+ nbytes = tocp->Size;
+ while (nbytes > 0) {
+ ncur = (nbytes > BUFSIZ) ? BUFSIZ : nbytes;
+ if (fread(buf, sizeof(char), ncur, infile) < ncur
+ || fwrite(buf, sizeof(char), ncur, outfile) < ncur) {
+ if (feof(infile)) {
+ CAFError(CAF_ERR_BADFILE);
+ } else {
+ CAFError(CAF_ERR_IO);
+ }
+
+ errorexit:
+ fclose(infile);
+ fclose(outfile);
+ free(tocarray);
+ free(newtocarray);
+ free(zerobuff);
+ unlink(newpath);
+ free(newpath);
+ return -1;
+ }
+ nbytes -= ncur;
+ }
+ /* startoffset = ftello(outfile); */
+ startoffset += tocp->Size;
+ newstartoffset = CAFRoundOffsetUp(startoffset, blocksize);
+ /* fseeko(outfile, (off_t) startoffset, SEEK_SET); */
+ /* but we don't want to call fseeko, since that seems to always
+ force a write(2) syscall, even when the new location would
+ still be inside stdio's buffer. */
+ if (newstartoffset - startoffset > 0) {
+ ncur = newstartoffset - startoffset;
+ if (fwrite(zerobuff, sizeof(char), ncur, outfile) < ncur) {
+ /* write failed, must be disk error of some sort. */
+ perror(newpath);
+ goto errorexit; /* yeah, it's a goto. eurggh. */
+ }
+ }
+ startoffset = newstartoffset;
+ }
+ }
+
+ free(tocarray); /* don't need this guy anymore. */
+ free(zerobuff);
+
+ /*
+ ** set up new file header, TOC.
+ ** this next fseeko might actually fail, because we have buffered stuff
+ ** that might fail on write.
+ */
+ if (fseeko(outfile, 0, SEEK_SET) < 0) {
+ perror(newpath);
+ free(newtocarray);
+ fclose(infile);
+ fclose(outfile);
+ unlink(newpath);
+ free(newpath);
+ return -1;
+ }
+
+ /* Change what we need in new file's header. */
+ newhead.Low = newlow;
+ newhead.High = head.High;
+ newhead.LastCleaned = time((time_t *) NULL);
+/* newhead.NumSlots = newtocsize; */
+/* newhead.Free = 0; */
+
+ if (fwrite(&newhead, sizeof(CAFHEADER), 1, outfile) < 1) {
+ CAFError(CAF_ERR_IO);
+ free(newtocarray);
+ fclose(infile);
+ fclose(outfile);
+ unlink(newpath);
+ free(newpath);
+ return -1;
+ }
+
+ /*
+ ** this next fseeko might actually fail, because we have buffered stuff
+ ** that might fail on write.
+ */
+ if (fseeko(outfile, sizeof(CAFHEADER) + newhead.FreeZoneTabSize,
+ SEEK_SET) < 0) {
+ perror(newpath);
+ free(newtocarray);
+ fclose(infile);
+ fclose(outfile);
+ unlink(newpath);
+ free(newpath);
+ return -1;
+ }
+
+ if (fwrite(newtocarray, sizeof(CAFTOCENT), head.High - newlow + 1, outfile) < head.High - newlow + 1
+ || fflush(outfile) < 0) {
+ CAFError(CAF_ERR_IO);
+ free(newtocarray);
+ fclose(infile);
+ fclose(outfile);
+ unlink(newpath);
+ free(newpath);
+ return -1;
+ }
+
+ if (rename(newpath, path) < 0) {
+ CAFError(CAF_ERR_IO);
+ free(newtocarray);
+ free(newpath);
+ fclose(infile);
+ fclose(outfile);
+ /* if can't rename, probably no point in trying to unlink newpath, is there? */
+ return -1;
+ }
+ /* written and flushed newtocarray, can safely fclose and get out of
+ here! */
+ free(newtocarray);
+ free(newpath);
+ fclose(outfile);
+ fclose(infile);
+ return 0;
+}
--- /dev/null
+/* $Revision: 5558 $
+** Declarations needed for handling CAF (Crunched Article Files)
+** Written by Richard Todd (rmtodd@mailhost.ecn.uoknor.edu) 3/24/96
+*/
+
+
+/*
+** Format of a crunched article file:
+** Header:
+*/
+
+typedef struct _CAFHEADER {
+ char Magic[4]; /* Magic Number "CRMT" */
+ ARTNUM Low; /* lowest article in the file */
+ ARTNUM NumSlots; /* number of articles there are room for in the TOC */
+ ARTNUM High; /* last article actually present in the file */
+ size_t Free; /* amount of space currently unused (freed by cancels/expires) */
+ off_t StartDataBlock; /* offset of first article data block. */
+ unsigned int BlockSize; /* unit of allocation for CAF files. */
+ size_t FreeZoneTabSize; /* amount of space taken up by the free zone table. */
+ size_t FreeZoneIndexSize; /* size taken up by the "index" part of the free zone table. */
+ time_t LastCleaned; /* note time of last cleaning. */
+ int spare[3];
+} CAFHEADER;
+
+#define CAF_MAGIC "CRMT"
+#define CAF_MAGIC_LEN 4
+#define CAF_DEFAULT_BLOCKSIZE 512
+
+/*
+** then the table of free blocks. The table is FreeZoneTabSize bytes
+** long. First comes a "first-level" or "index" bitmap, taking up the
+** space from the end of the CAFHEADER to the end of the first
+** block, i.e. FreeZoneIndexBytes. The rest of the table is a big bitmap
+** listing free blocks in the 'data' portion of the CAF file.
+**
+** In the "index" bitmap: LSB of bitmap byte 0 is 1 if there are any 1s
+** (free blocks) listed in the first block of the big bitmap, and 0 if there
+** are no 1s in that block. The remaining bits of the index bitmap
+** correspond to the remaining blocks of the big bitmap accordingly.
+** The idea is that from the index bitmap one can tell which part of the
+** main bitmap is likely to have free blocks w/o having to read the entire
+** main bitmap.
+**
+** As for the main bitmap, each bit is 1 if the corresponding data
+** block (BlockSize bytes) is free. LSB of bitmap byte 0 corresponds
+** to the block @ offset StartDataBlock, and all the rest follow on
+** accordingly.
+**
+** Note that the main part of the bitmap is *always* FreeZoneIndexByte*8
+** blocks long, no matter how big the CAF file is. The table of free blocks
+** is almost always sparse. Also note that blocks past EOF in the CAF file
+** are *not* considered free. If the CAF article write routines fail to
+** find free space in the fre block bitmaps, they will always attempt to
+** extend the CAF file instead.
+*/
+
+#define CAF_DEFAULT_FZSIZE (512-sizeof(CAFHEADER))
+
+/*
+** (Note: the CAFBITMAP structure isn't what's actually stored on disk
+** in the free bitmap region, this is just a convenient structure to
+** keep the bitmap size and StartBlockOffset together with the bitmap
+** w/o having keep passing the original CAFHEADER to every routine
+** that wants it. The bitmap structure contains the first (index) bitmap,
+** as well as pointers to structures for each block of the main bitmap that
+** has been read into memory.
+*/
+
+typedef struct _CAFBITMAP {
+ off_t StartDataBlock;
+ off_t MaxDataBlock; /* can only handle offsets < this with this bitmap. */
+ size_t FreeZoneTabSize;
+ size_t FreeZoneIndexSize;
+ size_t BytesPerBMB; /* size of chunk, in bytes, that any given BMBLK can map. */
+ unsigned int BlockSize;
+ unsigned int NumBMB; /* size of Blocks array. */
+ struct _CAFBMB **Blocks;
+ char *Bits;
+} CAFBITMAP;
+
+typedef struct _CAFBMB {
+ off_t StartDataBlock;
+ off_t MaxDataBlock;
+ int Dirty; /* 1 if this BMB has had any bits changed. */
+ char *BMBBits;
+} CAFBMB;
+
+/*
+** Next in the file are the TOC (Table of Contents) entries. Each TOC
+** entry describes an article.
+*/
+
+typedef struct _CAFTOCENT {
+ off_t Offset;
+ size_t Size;
+ time_t ModTime;
+} CAFTOCENT;
+
+/*
+** and then after the NumSlots TOC Entries, the actual articles, one after
+** another, always starting at offsets == 0 mod BlockSize
+*/
+
+/*
+** Number of slots to put in TOC by default. Can be raised if we ever get
+** more than 256K articles in a newsgroup (frightening thought).
+*/
+
+#define CAF_DEFAULT_TOC_SIZE (256 * 1024)
+
+/*
+** Default name for CAF file in the news spool dir for a given newsgroup.
+*/
+#define CAF_NAME "CF"
+
+extern int CAFOpenArtRead(const char *cfpath, ARTNUM art, size_t *len);
+extern int CAFOpenArtWrite(char *cfpath, ARTNUM *art, int WaitLock, size_t size);
+extern int CAFStartWriteFd(int fd, ARTNUM *art, size_t size);
+extern int CAFFinishWriteFd(int fd);
+extern int CAFFinishArtWrite(int fd);
+extern int CAFCreateCAFFile(char *cfpath, ARTNUM lowart, ARTNUM tocsize, size_t cfsize, int nolink, char *temppath);
+extern const char *CAFErrorStr(void);
+extern CAFTOCENT *CAFReadTOC(char *cfpath, CAFHEADER *ch);
+extern int CAFRemoveMultArts(char *cfpath, unsigned int narts, ARTNUM *arts);
+extern int CAFStatArticle(char *path, ARTNUM art, struct stat *st);
+
+#ifdef CAF_INNARDS
+/* functions used internally by caf.c, and by the cleaner program, and cafls
+ but probably aren't useful/desirable to be used by others. */
+extern int CAFOpenReadTOC(char *cfpath, CAFHEADER *ch, CAFTOCENT **tocpp);
+extern int CAFReadHeader(int fd, CAFHEADER *h);
+extern off_t CAFRoundOffsetUp(off_t offt, unsigned int bsize);
+extern CAFBITMAP * CAFReadFreeBM(int fd, CAFHEADER *h);
+extern void CAFDisposeBitmap(CAFBITMAP *cbm);
+/*
+** note! CAFIsBlockFree needs the fd, since blocks of the free bitmap may
+** need to be fetched from disk.
+*/
+extern int CAFIsBlockFree(CAFBITMAP *bm, int fd, off_t block);
+#endif
+
+extern int caf_error; /* last error encountered by library. */
+extern int caf_errno; /* saved value of errno here if I/O error hit by lib. */
+
+#define CAF_ERR_IO 1 /* generic I/O error, check caf_errno for details */
+#define CAF_ERR_BADFILE 2 /* corrupt file */
+#define CAF_ERR_ARTNOTHERE 3 /* article not in the database */
+#define CAF_ERR_CANTCREATECAF 4 /* can't create the CAF file, see errno. */
+#define CAF_ERR_FILEBUSY 5 /* file locked by someone else. */
+#define CAF_ERR_ARTWONTFIT 6 /* outside the range in the TOC */
+#define CAF_ERR_ARTALREADYHERE 7 /* tried to create an article that was already here. */
+#define CAF_ERR_BOGUSPATH 8 /* pathname not parseable. */
--- /dev/null
+name = timecaf
+number = 4
+sources = caf.c timecaf.c
--- /dev/null
+/* $Id: timecaf.c 7412 2005-10-09 03:44:35Z eagle $
+**
+** Like the timehash storage method (and heavily inspired by it), but uses
+** the CAF library to store multiple articles in a single file.
+*/
+
+#include "config.h"
+#include "clibrary.h"
+#include "portable/mmap.h"
+#include <ctype.h>
+#include <dirent.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <syslog.h>
+#include <sys/stat.h>
+#include <time.h>
+
+#include "caf.h"
+#include "inn/innconf.h"
+#include "inn/wire.h"
+#include "libinn.h"
+#include "methods.h"
+#include "timecaf.h"
+#include "paths.h"
+
+/* Needed for htonl() and friends on AIX 4.1. */
+#include <netinet/in.h>
+
+typedef struct {
+ char *artdata; /* start of the article data -- may be mmaped */
+ char *mmapbase; /* actual start of mmaped region (on pagesize bndry, not necessarily == artdaya */
+ unsigned int artlen; /* art length. */
+ size_t mmaplen; /* length of mmap region. */
+ DIR *top; /* open handle on top level dir. */
+ DIR *sec; /* open handle on the 2nd level directory */
+ DIR *ter; /* open handle on 3rd level dir. */
+ struct dirent *topde; /* last entry we got from top */
+ struct dirent *secde; /* last entry we got from sec */
+ struct dirent *terde; /* last entry we got from sec */
+ CAFTOCENT *curtoc;
+ ARTNUM curartnum;
+ CAFHEADER curheader;
+} PRIV_TIMECAF;
+
+/* current path/fd for an open CAF file */
+typedef struct {
+ char *path; /* path to file. */
+ int fd; /* open fd -- -1 if no file currently open. */
+} CAFOPENFILE;
+
+static CAFOPENFILE ReadingFile, WritingFile;
+static char *DeletePath;
+static ARTNUM *DeleteArtnums;
+static unsigned int NumDeleteArtnums, MaxDeleteArtnums;
+
+typedef enum {FIND_DIR, FIND_CAF, FIND_TOPDIR} FINDTYPE;
+
+/*
+** Structures for the cache for stat information (to make expireover etc.
+** faster.
+**
+** The first structure contains the TOC info for a single CAF file. The 2nd
+** one has pointers to the info for up to 256 CAF files, indexed
+** by the 2nd least significant byte of the arrival time.
+*/
+
+struct caftoccacheent {
+ CAFTOCENT *toc;
+ CAFHEADER header;
+};
+typedef struct caftoccacheent CAFTOCCACHEENT;
+
+struct caftocl1cache {
+ CAFTOCCACHEENT *entries[256];
+};
+typedef struct caftocl1cache CAFTOCL1CACHE;
+
+/*
+** and similar structures indexed by the 3rd and 4th bytes of the arrival time.
+** pointing to the lower level structures. Note that the top level structure
+** (the one indexed by the MSByte of the timestamp) is likely to have only
+** one active pointer, unless your spool keeps more than 194 days of articles,
+** but it doesn't cost much to keep that one structure around and keep the
+** code general.
+*/
+
+struct caftocl2cache {
+ CAFTOCL1CACHE *l1ptr[256];
+};
+typedef struct caftocl2cache CAFTOCL2CACHE;
+
+struct caftocl3cache {
+ CAFTOCL2CACHE *l2ptr[256];
+};
+typedef struct caftocl3cache CAFTOCL3CACHE;
+
+static CAFTOCL3CACHE *TOCCache[256]; /* indexed by storage class! */
+static int TOCCacheHits, TOCCacheMisses;
+
+
+static TOKEN MakeToken(time_t now, int seqnum, STORAGECLASS class, TOKEN *oldtoken) {
+ TOKEN token;
+ unsigned int i;
+ unsigned short s;
+
+ if (oldtoken == (TOKEN *)NULL)
+ memset(&token, '\0', sizeof(token));
+ else
+ memcpy(&token, oldtoken, sizeof(token));
+ token.type = TOKEN_TIMECAF;
+ token.class = class;
+ i = htonl(now);
+ memcpy(token.token, &i, sizeof(i));
+ if (sizeof(i) > 4)
+ memmove(token.token, &token.token[sizeof(i) - 4], 4);
+ s = htons(seqnum);
+ memcpy(&token.token[4], &s + (sizeof(s) - 2), 2);
+ return token;
+}
+
+
+static void BreakToken(TOKEN token, int *now, int *seqnum) {
+ unsigned int i;
+ unsigned short s = 0;
+
+ memcpy(&i, token.token, sizeof(i));
+ memcpy(&s, &token.token[4], sizeof(s));
+ *now = ntohl(i);
+ *seqnum = (int)ntohs(s);
+}
+
+/*
+** Note: the time here is really "time>>8", i.e. a timestamp that's been
+** shifted right by 8 bits.
+*/
+static char *MakePath(int now, const STORAGECLASS class) {
+ char *path;
+ size_t length;
+
+ /* innconf->patharticles + '/timecaf-zz/xx/xxxx.CF' */
+ length = strlen(innconf->patharticles) + 32;
+ path = xmalloc(length);
+ snprintf(path, length, "%s/timecaf-%02x/%02x/%02x%02x.CF",
+ innconf->patharticles, class,
+ (now >> 8) & 0xff, (now >> 16) & 0xff, now & 0xff);
+
+ return path;
+}
+
+static TOKEN *PathNumToToken(char *path, ARTNUM artnum) {
+ int n;
+ unsigned int t1, t2, class;
+ unsigned int timestamp;
+ static TOKEN token;
+
+ n = sscanf(path, "timecaf-%02x/%02x/%04x.CF", &class, &t1, &t2);
+ if (n != 3)
+ return (TOKEN *)NULL;
+ timestamp = ((t1 << 8) & 0xff00) | ((t2 << 8) & 0xff0000) | ((t2 << 0) & 0xff);
+ token = MakeToken(timestamp, artnum, class, (TOKEN *)NULL);
+ return &token;
+}
+
+
+bool timecaf_init(SMATTRIBUTE *attr) {
+ if (attr == NULL) {
+ syslog(L_ERROR, "timecaf: attr is NULL");
+ SMseterror(SMERR_INTERNAL, "attr is NULL");
+ return false;
+ }
+ attr->selfexpire = false;
+ attr->expensivestat = false;
+ if (STORAGE_TOKEN_LENGTH < 6) {
+ syslog(L_FATAL, "timecaf: token length is less than 6 bytes");
+ SMseterror(SMERR_TOKENSHORT, NULL);
+ return false;
+ }
+ ReadingFile.fd = WritingFile.fd = -1;
+ ReadingFile.path = WritingFile.path = (char *)NULL;
+ return true;
+}
+
+/*
+** Routines for managing the 'TOC cache' (cache of TOCs of various CAF files)
+**
+** Attempt to look up a given TOC entry in the cache. Takes the timestamp
+** as arguments.
+*/
+
+static CAFTOCCACHEENT *
+CheckTOCCache(int timestamp, int tokenclass)
+{
+ CAFTOCL2CACHE *l2;
+ CAFTOCL1CACHE *l1;
+ CAFTOCCACHEENT *cent;
+ unsigned char tmp;
+
+ if (TOCCache[tokenclass] == NULL) return NULL; /* cache is empty */
+
+ tmp = (timestamp>>16) & 0xff;
+ l2 = TOCCache[tokenclass]->l2ptr[tmp];
+ if (l2 == NULL) return NULL;
+
+ tmp = (timestamp>>8) & 0xff;
+ l1 = l2->l1ptr[tmp];
+ if (l1 == NULL) return NULL;
+
+ tmp = (timestamp) & 0xff;
+ cent = l1->entries[tmp];
+
+ ++TOCCacheHits;
+ return cent;
+}
+
+/*
+** Add given TOC and header to the cache. Assume entry is not already in
+** cache.
+*/
+static CAFTOCCACHEENT *
+AddTOCCache(int timestamp, CAFTOCENT *toc, CAFHEADER head, int tokenclass)
+{
+ CAFTOCL2CACHE *l2;
+ CAFTOCL1CACHE *l1;
+ CAFTOCCACHEENT *cent;
+ unsigned char tmp;
+ int i;
+
+ if (TOCCache[tokenclass] == NULL) {
+ TOCCache[tokenclass] = xmalloc(sizeof(CAFTOCL3CACHE));
+ for (i = 0 ; i < 256 ; ++i) TOCCache[tokenclass]->l2ptr[i] = NULL;
+ }
+
+ tmp = (timestamp>>16) & 0xff;
+ l2 = TOCCache[tokenclass]->l2ptr[tmp];
+ if (l2 == NULL) {
+ TOCCache[tokenclass]->l2ptr[tmp] = l2 = xmalloc(sizeof(CAFTOCL2CACHE));
+ for (i = 0 ; i < 256 ; ++i) l2->l1ptr[i] = NULL;
+ }
+
+ tmp = (timestamp>>8) & 0xff;
+ l1 = l2->l1ptr[tmp];
+ if (l1 == NULL) {
+ l2->l1ptr[tmp] = l1 = xmalloc(sizeof(CAFTOCL1CACHE));
+ for (i = 0 ; i < 256 ; ++i) l1->entries[i] = NULL;
+ }
+
+ tmp = (timestamp) & 0xff;
+ cent = xmalloc(sizeof(CAFTOCCACHEENT));
+ l1->entries[tmp] = cent;
+
+ cent->header = head;
+ cent->toc = toc;
+ ++TOCCacheMisses;
+ return cent;
+}
+
+/*
+** Do stating of an article, going thru the TOC cache if possible.
+*/
+
+static ARTHANDLE *
+StatArticle(int timestamp, ARTNUM artnum, int tokenclass)
+{
+ CAFTOCCACHEENT *cent;
+ CAFTOCENT *toc;
+ CAFHEADER head;
+ char *path;
+ CAFTOCENT *tocentry;
+ ARTHANDLE *art;
+
+ cent = CheckTOCCache(timestamp,tokenclass);
+ if (cent == NULL) {
+ path = MakePath(timestamp, tokenclass);
+ toc = CAFReadTOC(path, &head);
+ if (toc == NULL) {
+ if (caf_error == CAF_ERR_ARTNOTHERE) {
+ SMseterror(SMERR_NOENT, NULL);
+ } else {
+ SMseterror(SMERR_UNDEFINED, NULL);
+ }
+ free(path);
+ return NULL;
+ }
+ cent = AddTOCCache(timestamp, toc, head, tokenclass);
+ free(path);
+ }
+
+ /* check current TOC for the given artnum. */
+ if (artnum < cent->header.Low || artnum > cent->header.High) {
+ SMseterror(SMERR_NOENT, NULL);
+ return NULL;
+ }
+
+ tocentry = &(cent->toc[artnum - cent->header.Low]);
+ if (tocentry->Size == 0) {
+ /* no article with that article number present */
+ SMseterror(SMERR_NOENT, NULL);
+ return NULL;
+ }
+
+ /* stat is a success, so build a null art struct to represent that. */
+ art = xmalloc(sizeof(ARTHANDLE));
+ art->type = TOKEN_TIMECAF;
+ art->data = NULL;
+ art->len = 0;
+ art->private = NULL;
+ return art;
+}
+
+
+static void
+CloseOpenFile(CAFOPENFILE *foo) {
+ if (foo->fd >= 0) {
+ close(foo->fd);
+ foo->fd = -1;
+ free(foo->path);
+ foo->path = NULL;
+ }
+}
+
+TOKEN timecaf_store(const ARTHANDLE article, const STORAGECLASS class) {
+ char *path;
+ char *p;
+ time_t now;
+ int timestamp;
+ TOKEN token;
+ int fd;
+ ssize_t result;
+ ARTNUM art;
+
+ if (article.arrived == (time_t)0)
+ now = time(NULL);
+ else
+ now = article.arrived;
+
+ timestamp = now>>8;
+ art = 0; /* magic: 0=="next available article number. */
+
+ path = MakePath(timestamp, class);
+ /* check to see if we have this CAF file already open. */
+ if (WritingFile.fd < 0 || strcmp(WritingFile.path, path) != 0) {
+ /* we're writing to a different file, close old one and start new one. */
+ CloseOpenFile(&WritingFile);
+ fd = CAFOpenArtWrite(path, &art, true, article.len);
+ if (fd < 0) {
+ if (caf_error == CAF_ERR_IO && caf_errno == ENOENT) {
+ /* directories in the path don't exist, try creating them. */
+ p = strrchr(path, '/');
+ *p = '\0';
+ if (!MakeDirectory(path, true)) {
+ syslog(L_ERROR, "timecaf: could not make directory %s %m", path);
+ token.type = TOKEN_EMPTY;
+ free(path);
+ SMseterror(SMERR_UNDEFINED, NULL);
+ return token;
+ } else {
+ *p = '/';
+ fd = CAFOpenArtWrite(path, &art, true, article.len);
+ if (fd < 0) {
+ syslog(L_ERROR, "timecaf: could not OpenArtWrite %s/%ld, %s", path, art, CAFErrorStr());
+ SMseterror(SMERR_UNDEFINED, NULL);
+ free(path);
+ token.type = TOKEN_EMPTY;
+ return token;
+ }
+ }
+ } else {
+ syslog(L_ERROR, "timecaf: could not OpenArtWrite %s/%ld, %s", path, art, CAFErrorStr());
+ SMseterror(SMERR_UNDEFINED, NULL);
+ free(path);
+ token.type = TOKEN_EMPTY;
+ return token;
+ }
+ }
+ } else {
+ /* can reuse existing fd, assuming all goes well. */
+ fd = WritingFile.fd;
+
+ /* nuke extraneous copy of path to avoid mem leaks. */
+ free(path);
+ path = WritingFile.path;
+
+ if (CAFStartWriteFd(fd, &art, article.len) < 0) {
+ syslog(L_ERROR, "timecaf: could not OpenArtWrite %s/%ld, %s", path, art, CAFErrorStr());
+ SMseterror(SMERR_UNDEFINED, NULL);
+ free(path);
+ token.type = TOKEN_EMPTY;
+ return token;
+ }
+ }
+ WritingFile.fd = fd;
+ WritingFile.path = path;
+ close_on_exec(fd, true);
+ result = xwritev(fd, article.iov, article.iovcnt);
+ if (result != (ssize_t) article.len) {
+ SMseterror(SMERR_UNDEFINED, NULL);
+ syslog(L_ERROR, "timecaf error writing %s %m", path);
+ token.type = TOKEN_EMPTY;
+ CloseOpenFile(&WritingFile);
+ return token;
+ }
+ if (CAFFinishArtWrite(fd) < 0) {
+ SMseterror(SMERR_UNDEFINED, NULL);
+ syslog(L_ERROR, "timecaf error writing %s %s", path, CAFErrorStr());
+ token.type = TOKEN_EMPTY;
+ CloseOpenFile(&WritingFile);
+ return token;
+ }
+
+ return MakeToken(timestamp, art, class, article.token);
+}
+
+/* Get a handle to article artnum in CAF-file path. */
+static ARTHANDLE *OpenArticle(const char *path, ARTNUM artnum, const RETRTYPE amount) {
+ int fd;
+ PRIV_TIMECAF *private;
+ char *p;
+ size_t len;
+ ARTHANDLE *art;
+ static long pagesize = 0;
+
+ if (pagesize == 0) {
+ pagesize = getpagesize();
+ if (pagesize < 0) {
+ syslog(L_ERROR, "timecaf getpagesize failed: %m");
+ pagesize = 0;
+ return NULL;
+ }
+ }
+
+/* XXX need to figure some way to cache open fds or something? */
+ if ((fd = CAFOpenArtRead((char *)path, artnum, &len)) < 0) {
+ if (caf_error == CAF_ERR_ARTNOTHERE) {
+ SMseterror(SMERR_NOENT, NULL);
+ } else {
+ SMseterror(SMERR_UNDEFINED, NULL);
+ }
+ return NULL;
+ }
+
+ art = xmalloc(sizeof(ARTHANDLE));
+ art->type = TOKEN_TIMECAF;
+
+ if (amount == RETR_STAT) {
+ art->data = NULL;
+ art->len = 0;
+ art->private = NULL;
+ close(fd);
+ return art;
+ }
+
+ private = xmalloc(sizeof(PRIV_TIMECAF));
+ art->private = (void *)private;
+ private->artlen = len;
+ if (innconf->articlemmap) {
+ off_t curoff, tmpoff;
+ size_t delta;
+
+ curoff = lseek(fd, (off_t) 0, SEEK_CUR);
+ delta = curoff % pagesize;
+ tmpoff = curoff - delta;
+ private->mmaplen = len + delta;
+ if ((private->mmapbase = mmap(NULL, private->mmaplen, PROT_READ, MAP_SHARED, fd, tmpoff)) == MAP_FAILED) {
+ SMseterror(SMERR_UNDEFINED, NULL);
+ syslog(L_ERROR, "timecaf: could not mmap article: %m");
+ free(art->private);
+ free(art);
+ return NULL;
+ }
+ mmap_invalidate(private->mmapbase, private->mmaplen);
+ if (amount == RETR_ALL)
+ madvise(private->mmapbase, private->mmaplen, MADV_WILLNEED);
+ else
+ madvise(private->mmapbase, private->mmaplen, MADV_SEQUENTIAL);
+ private->artdata = private->mmapbase + delta;
+ } else {
+ private->artdata = xmalloc(private->artlen);
+ if (read(fd, private->artdata, private->artlen) < 0) {
+ SMseterror(SMERR_UNDEFINED, NULL);
+ syslog(L_ERROR, "timecaf: could not read article: %m");
+ free(private->artdata);
+ free(art->private);
+ free(art);
+ return NULL;
+ }
+ }
+ close(fd);
+
+ private->top = NULL;
+ private->sec = NULL;
+ private->ter = NULL;
+ private->curtoc = NULL;
+ private->curartnum = 0;
+ private->topde = NULL;
+ private->secde = NULL;
+ private->terde = NULL;
+
+ if (amount == RETR_ALL) {
+ art->data = private->artdata;
+ art->len = private->artlen;
+ return art;
+ }
+
+ if ((p = wire_findbody(private->artdata, private->artlen)) == NULL) {
+ SMseterror(SMERR_NOBODY, NULL);
+ if (innconf->articlemmap)
+ munmap(private->mmapbase, private->mmaplen);
+ else
+ free(private->artdata);
+ free(art->private);
+ free(art);
+ return NULL;
+ }
+
+ if (amount == RETR_HEAD) {
+ art->data = private->artdata;
+ art->len = p - private->artdata;
+ return art;
+ }
+
+ if (amount == RETR_BODY) {
+ art->data = p + 4;
+ art->len = art->len - (private->artdata - p - 4);
+ return art;
+ }
+ SMseterror(SMERR_UNDEFINED, "Invalid retrieve request");
+ if (innconf->articlemmap)
+ munmap(private->mmapbase, private->mmaplen);
+ else
+ free(private->artdata);
+ free(art->private);
+ free(art);
+ return NULL;
+}
+
+ARTHANDLE *timecaf_retrieve(const TOKEN token, const RETRTYPE amount) {
+ int timestamp;
+ int artnum;
+ char *path;
+ ARTHANDLE *art;
+ static TOKEN ret_token;
+ time_t now;
+
+ if (token.type != TOKEN_TIMECAF) {
+ SMseterror(SMERR_INTERNAL, NULL);
+ return NULL;
+ }
+
+ BreakToken(token, ×tamp, &artnum);
+
+ /*
+ ** Do a possible shortcut on RETR_STAT requests, going thru the "TOC cache"
+ ** we mentioned above. We only try to go thru the TOC Cache under these
+ ** conditions:
+ ** 1) SMpreopen is true (so we're "preopening" the TOCs.)
+ ** 2) the timestamp is older than the timestamp corresponding to current
+ ** time. Any timestamp that matches current time (to within 256 secondsf
+ ** would be in a CAF file that innd is actively
+ ** writing, in which case we would not want to cache the TOC for that
+ ** CAF file.
+ */
+
+ if (SMpreopen && amount == RETR_STAT) {
+ now = time(NULL);
+ if (timestamp < ((now >> 8) & 0xffffff)) {
+ return StatArticle(timestamp, artnum, token.class);
+ }
+ }
+
+ path = MakePath(timestamp, token.class);
+ if ((art = OpenArticle(path, artnum, amount)) != (ARTHANDLE *)NULL) {
+ art->arrived = timestamp<<8; /* XXX not quite accurate arrival time,
+ ** but getting a more accurate one would
+ ** require more fiddling with CAF innards.
+ */
+ ret_token = token;
+ art->token = &ret_token;
+ }
+ free(path);
+ return art;
+}
+
+void timecaf_freearticle(ARTHANDLE *article) {
+ PRIV_TIMECAF *private;
+
+ if (!article)
+ return;
+
+ if (article->private) {
+ private = (PRIV_TIMECAF *)article->private;
+ if (innconf->articlemmap)
+ munmap(private->mmapbase, private->mmaplen);
+ else
+ free(private->artdata);
+ if (private->top)
+ closedir(private->top);
+ if (private->sec)
+ closedir(private->sec);
+ if (private->ter)
+ closedir(private->ter);
+ if (private->curtoc)
+ free(private->curtoc);
+ free(private);
+ }
+ free(article);
+}
+
+/* Do cancels of all the article ids collected for a given pathname. */
+
+static void
+DoCancels(void) {
+ if (DeletePath != NULL) {
+ if (NumDeleteArtnums != 0) {
+ /*
+ ** Murgle. If we are trying to cancel something out of the
+ ** currently open-for-writing file, we need to close it before
+ ** doing CAFRemove...
+ */
+ if (WritingFile.path != NULL && strcmp(WritingFile.path, DeletePath) == 0) {
+ CloseOpenFile(&WritingFile);
+ }
+ /* XXX should really check err. code here, but not much we can really do. */
+ CAFRemoveMultArts(DeletePath, NumDeleteArtnums, DeleteArtnums);
+ free(DeleteArtnums);
+ DeleteArtnums = NULL;
+ NumDeleteArtnums = MaxDeleteArtnums = 0;
+ }
+ free(DeletePath);
+ DeletePath = NULL;
+ }
+}
+
+bool timecaf_cancel(TOKEN token) {
+ int now;
+ int seqnum;
+ char *path;
+
+ BreakToken(token, &now, &seqnum);
+ path = MakePath(now, token.class);
+ if (DeletePath == NULL) {
+ DeletePath = path;
+ } else if (strcmp(DeletePath, path) != 0) {
+ /* different path, so flush all pending cancels. */
+ DoCancels();
+ DeletePath = path;
+ } else {
+ free(path); /* free redundant copy of path */
+ }
+ if (NumDeleteArtnums >= MaxDeleteArtnums) {
+ /* allocate/expand storage for artnums. */
+ if (MaxDeleteArtnums == 0) {
+ MaxDeleteArtnums = 100;
+ } else {
+ MaxDeleteArtnums *= 2;
+ }
+ DeleteArtnums = xrealloc(DeleteArtnums, MaxDeleteArtnums * sizeof(ARTNUM));
+ }
+ DeleteArtnums[NumDeleteArtnums++] = seqnum;
+
+ return true;
+}
+
+static struct dirent *FindDir(DIR *dir, FINDTYPE type) {
+ struct dirent *de;
+
+ while ((de = readdir(dir)) != NULL) {
+ if (type == FIND_TOPDIR)
+ if ((strlen(de->d_name) == 10) &&
+ (strncmp(de->d_name, "timecaf-", 8) == 0) &&
+ CTYPE(isxdigit, de->d_name[8]) &&
+ CTYPE(isxdigit, de->d_name[9]))
+ return de;
+
+ if (type == FIND_DIR)
+ if ((strlen(de->d_name) == 2)
+ && CTYPE(isxdigit, de->d_name[0])
+ && CTYPE(isxdigit, de->d_name[1]))
+ return de;
+
+ if (type == FIND_CAF)
+ if ((strlen(de->d_name) == 7) &&
+ CTYPE(isxdigit, de->d_name[0]) &&
+ CTYPE(isxdigit, de->d_name[1]) &&
+ CTYPE(isxdigit, de->d_name[2]) &&
+ CTYPE(isxdigit, de->d_name[3]) &&
+ (de->d_name[4] == '.') &&
+ (de->d_name[5] == 'C') &&
+ (de->d_name[6] == 'F'))
+ return de;
+ }
+
+ return NULL;
+}
+
+/* Grovel thru a CAF table-of-contents finding the next still-existing article */
+static int
+FindNextArt(const CAFHEADER *head, CAFTOCENT *toc, ARTNUM *artp)
+{
+ ARTNUM art;
+ CAFTOCENT *tocp;
+ art = *artp;
+ if (art == 0) {
+ art = head->Low - 1; /* we never use art # 0, so 0 is a flag to start
+ searching at the beginning */
+ }
+ while (true) {
+ art++;
+ if (art > head->High) return false; /* ran off the end of the TOC */
+ tocp = &toc[art - head->Low];
+ if (tocp->Size != 0) {
+ /* got a valid article */
+ *artp = art;
+ return true;
+ }
+ }
+}
+
+
+
+ARTHANDLE *timecaf_next(const ARTHANDLE *article, const RETRTYPE amount) {
+ PRIV_TIMECAF priv, *newpriv;
+ char *path;
+ ARTHANDLE *art;
+ size_t length;
+
+ length = strlen(innconf->patharticles) + 32;
+ path = xmalloc(length);
+ if (article == NULL) {
+ priv.top = NULL;
+ priv.sec = NULL;
+ priv.ter = NULL;
+ priv.curtoc = NULL;
+ priv.topde = NULL;
+ priv.secde = NULL;
+ priv.terde = NULL;
+ } else {
+ priv = *(PRIV_TIMECAF *)article->private;
+ free(article->private);
+ free((void *)article);
+ if (innconf->articlemmap)
+ munmap(priv.mmapbase, priv.mmaplen);
+ else
+ free(priv.artdata);
+ }
+
+ while (priv.curtoc == NULL || !FindNextArt(&priv.curheader, priv.curtoc, &priv.curartnum)) {
+ if (priv.curtoc) {
+ free(priv.curtoc);
+ priv.curtoc = NULL;
+ }
+ while (!priv.ter || ((priv.terde = FindDir(priv.ter, FIND_CAF)) == NULL)) {
+ if (priv.ter) {
+ closedir(priv.ter);
+ priv.ter = NULL;
+ }
+ while (!priv.sec || ((priv.secde = FindDir(priv.sec, FIND_DIR)) == NULL)) {
+ if (priv.sec) {
+ closedir(priv.sec);
+ priv.sec = NULL;
+ }
+ if (!priv.top || ((priv.topde = FindDir(priv.top, FIND_TOPDIR)) == NULL)) {
+ if (priv.top) {
+ /* end of search */
+ closedir(priv.top);
+ priv.top = NULL;
+ free(path);
+ return NULL;
+ }
+ snprintf(path, length, "%s", innconf->patharticles);
+ if ((priv.top = opendir(path)) == NULL) {
+ SMseterror(SMERR_UNDEFINED, NULL);
+ free(path);
+ return NULL;
+ }
+ if ((priv.topde = FindDir(priv.top, FIND_TOPDIR)) == NULL) {
+ SMseterror(SMERR_UNDEFINED, NULL);
+ closedir(priv.top);
+ free(path);
+ return NULL;
+ }
+ }
+ snprintf(path, length, "%s/%s", innconf->patharticles, priv.topde->d_name);
+ if ((priv.sec = opendir(path)) == NULL)
+ continue;
+ }
+ snprintf(path, length, "%s/%s/%s", innconf->patharticles, priv.topde->d_name, priv.secde->d_name);
+ if ((priv.ter = opendir(path)) == NULL)
+ continue;
+ }
+ snprintf(path, length, "%s/%s/%s/%s", innconf->patharticles, priv.topde->d_name, priv.secde->d_name, priv.terde->d_name);
+ if ((priv.curtoc = CAFReadTOC(path, &priv.curheader)) == NULL)
+ continue;
+ priv.curartnum = 0;
+ }
+ snprintf(path, length, "%s/%s/%s/%s", innconf->patharticles, priv.topde->d_name, priv.secde->d_name, priv.terde->d_name);
+ art = OpenArticle(path, priv.curartnum, amount);
+ if (art == (ARTHANDLE *)NULL) {
+ art = xmalloc(sizeof(ARTHANDLE));
+ art->type = TOKEN_TIMECAF;
+ art->data = NULL;
+ art->len = 0;
+ art->private = xmalloc(sizeof(PRIV_TIMECAF));
+ }
+ newpriv = (PRIV_TIMECAF *)art->private;
+ newpriv->top = priv.top;
+ newpriv->sec = priv.sec;
+ newpriv->ter = priv.ter;
+ newpriv->topde = priv.topde;
+ newpriv->secde = priv.secde;
+ newpriv->terde = priv.terde;
+ newpriv->curheader = priv.curheader;
+ newpriv->curtoc = priv.curtoc;
+ newpriv->curartnum = priv.curartnum;
+
+ snprintf(path, length, "%s/%s/%s", priv.topde->d_name, priv.secde->d_name, priv.terde->d_name);
+ art->token = PathNumToToken(path, priv.curartnum);
+ art->arrived = priv.curtoc[priv.curartnum - priv.curheader.Low].ModTime;
+ free(path);
+ return art;
+}
+
+bool timecaf_ctl(PROBETYPE type, TOKEN *token UNUSED, void *value) {
+ struct artngnum *ann;
+
+ switch (type) {
+ case SMARTNGNUM:
+ if ((ann = (struct artngnum *)value) == NULL)
+ return false;
+ /* make SMprobe() call timecaf_retrieve() */
+ ann->artnum = 0;
+ return true;
+ default:
+ return false;
+ }
+}
+
+bool timecaf_flushcacheddata(FLUSHTYPE type) {
+ if (type == SM_ALL || type == SM_CANCELEDART)
+ DoCancels();
+ return true;
+}
+
+void
+timecaf_printfiles(FILE *file, TOKEN token, char **xref UNUSED,
+ int ngroups UNUSED)
+{
+ fprintf(file, "%s\n", TokenToText(token));
+}
+
+void timecaf_shutdown(void) {
+ CloseOpenFile(&WritingFile);
+ DoCancels();
+}
--- /dev/null
+/* $Id: timecaf.h 4266 2001-01-04 06:01:36Z rra $
+**
+** timecaf -- like the timehash storage method (and heavily inspired
+** by it), but uses the CAF library to store multiple articles in a
+** single file.
+*/
+
+#ifndef __TIMECAF_H__
+#define __TIMECAF_H__
+
+#include "config.h"
+#include "interface.h"
+
+bool timecaf_init(SMATTRIBUTE *attr);
+TOKEN timecaf_store(const ARTHANDLE article, const STORAGECLASS class);
+ARTHANDLE *timecaf_retrieve(const TOKEN token, const RETRTYPE amount);
+ARTHANDLE *timecaf_next(const ARTHANDLE *article, const RETRTYPE amount);
+void timecaf_freearticle(ARTHANDLE *article);
+bool timecaf_cancel(TOKEN token);
+bool timecaf_ctl(PROBETYPE type, TOKEN *token, void *value);
+bool timecaf_flushcacheddata(FLUSHTYPE type);
+void timecaf_printfiles(FILE *file, TOKEN token, char **xref, int ngroups);
+void timecaf_shutdown(void);
+
+#endif
--- /dev/null
+name = timehash
+number = 2
+sources = timehash.c
--- /dev/null
+/* $Id: timehash.c 7412 2005-10-09 03:44:35Z eagle $
+**
+** Timehash based storage method.
+*/
+
+#include "config.h"
+#include "clibrary.h"
+#include "portable/mmap.h"
+#include <ctype.h>
+#include <dirent.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <netinet/in.h>
+#include <syslog.h>
+#include <sys/stat.h>
+#include <time.h>
+
+#include "inn/innconf.h"
+#include "inn/wire.h"
+#include "libinn.h"
+#include "methods.h"
+#include "paths.h"
+#include "timehash.h"
+
+typedef struct {
+ char *base; /* Base of the mmaped file */
+ int len; /* Length of the file */
+ DIR *top; /* Open handle on the top level directory */
+ DIR *sec; /* Open handle on the 2nd level directory */
+ DIR *ter; /* Open handle on the third level directory */
+ DIR *artdir; /* Open handle on the article directory */
+ struct dirent *topde; /* dirent entry for the last entry retrieved in top */
+ struct dirent *secde; /* dirent entry for the last entry retrieved in sec */
+ struct dirent *terde; /* dirent entry for the last entry retrieved in ter */
+} PRIV_TIMEHASH;
+
+typedef enum {FIND_DIR, FIND_ART, FIND_TOPDIR} FINDTYPE;
+
+static int SeqNum = 0;
+
+static TOKEN MakeToken(time_t now, int seqnum, STORAGECLASS class, TOKEN *oldtoken) {
+ TOKEN token;
+ unsigned int i;
+ unsigned short s;
+
+ if (oldtoken == (TOKEN *)NULL)
+ memset(&token, '\0', sizeof(token));
+ else
+ memcpy(&token, oldtoken, sizeof(token));
+ token.type = TOKEN_TIMEHASH;
+ token.class = class;
+ i = htonl(now);
+ memcpy(token.token, &i, sizeof(i));
+ if (sizeof(i) > 4)
+ memmove(token.token, &token.token[sizeof(i) - 4], 4);
+ s = htons(seqnum);
+ memcpy(&token.token[4], &s + (sizeof(s) - 2), 2);
+ return token;
+}
+
+static void BreakToken(TOKEN token, int *now, int *seqnum) {
+ unsigned int i;
+ unsigned short s = 0;
+
+ memcpy(&i, token.token, sizeof(i));
+ memcpy(&s, &token.token[4], sizeof(s));
+ *now = ntohl(i);
+ *seqnum = (int)ntohs(s);
+}
+
+static char *MakePath(int now, int seqnum, const STORAGECLASS class) {
+ char *path;
+ size_t length;
+
+ /* innconf->patharticles + '/time-zz/xx/xx/yyyy-xxxx' */
+ length = strlen(innconf->patharticles) + 32;
+ path = xmalloc(length);
+ snprintf(path, length, "%s/time-%02x/%02x/%02x/%04x-%04x",
+ innconf->patharticles, class,
+ (now >> 16) & 0xff, (now >> 8) & 0xff, seqnum,
+ (now & 0xff) | ((now >> 16 & 0xff00)));
+ return path;
+}
+
+static TOKEN *PathToToken(char *path) {
+ int n;
+ unsigned int t1, t2, t3, seqnum, class;
+ time_t now;
+ static TOKEN token;
+
+ n = sscanf(path, "time-%02x/%02x/%02x/%04x-%04x", &class, &t1, &t2, &seqnum, &t3);
+ if (n != 5)
+ return (TOKEN *)NULL;
+ now = ((t1 << 16) & 0xff0000) | ((t2 << 8) & 0xff00) | ((t3 << 16) & 0xff000000) | (t3 & 0xff);
+ token = MakeToken(now, seqnum, class, (TOKEN *)NULL);
+ return &token;
+}
+
+bool timehash_init(SMATTRIBUTE *attr) {
+ if (attr == NULL) {
+ syslog(L_ERROR, "timehash: attr is NULL");
+ SMseterror(SMERR_INTERNAL, "attr is NULL");
+ return false;
+ }
+ attr->selfexpire = false;
+ attr->expensivestat = true;
+ if (STORAGE_TOKEN_LENGTH < 6) {
+ syslog(L_FATAL, "timehash: token length is less than 6 bytes");
+ SMseterror(SMERR_TOKENSHORT, NULL);
+ return false;
+ }
+ return true;
+}
+
+TOKEN timehash_store(const ARTHANDLE article, const STORAGECLASS class) {
+ char *path;
+ char *p;
+ time_t now;
+ TOKEN token;
+ int fd;
+ ssize_t result;
+ int seq;
+ int i;
+
+ if (article.arrived == (time_t)0)
+ now = time(NULL);
+ else
+ now = article.arrived;
+
+ for (i = 0; i < 0x10000; i++) {
+ seq = SeqNum;
+ SeqNum = (SeqNum + 1) & 0xffff;
+ path = MakePath(now, seq, class);
+
+ if ((fd = open(path, O_CREAT|O_EXCL|O_WRONLY, ARTFILE_MODE)) < 0) {
+ if (errno == EEXIST)
+ continue;
+ p = strrchr(path, '/');
+ *p = '\0';
+ if (!MakeDirectory(path, true)) {
+ syslog(L_ERROR, "timehash: could not make directory %s %m", path);
+ token.type = TOKEN_EMPTY;
+ free(path);
+ SMseterror(SMERR_UNDEFINED, NULL);
+ return token;
+ } else {
+ *p = '/';
+ if ((fd = open(path, O_CREAT|O_EXCL|O_WRONLY, ARTFILE_MODE)) < 0) {
+ SMseterror(SMERR_UNDEFINED, NULL);
+ syslog(L_ERROR, "timehash: could not open %s %m", path);
+ token.type = TOKEN_EMPTY;
+ free(path);
+ return token;
+ }
+ }
+ }
+ break;
+ }
+ if (i == 0x10000) {
+ SMseterror(SMERR_UNDEFINED, NULL);
+ syslog(L_ERROR, "timehash: all sequence numbers for the time and class are reserved %lu %d", (unsigned long)now, class);
+ token.type = TOKEN_EMPTY;
+ free(path);
+ return token;
+ }
+
+ result = xwritev(fd, article.iov, article.iovcnt);
+ if (result != (ssize_t) article.len) {
+ SMseterror(SMERR_UNDEFINED, NULL);
+ syslog(L_ERROR, "timehash error writing %s %m", path);
+ close(fd);
+ token.type = TOKEN_EMPTY;
+ unlink(path);
+ free(path);
+ return token;
+ }
+ close(fd);
+ free(path);
+ return MakeToken(now, seq, class, article.token);
+}
+
+static ARTHANDLE *OpenArticle(const char *path, RETRTYPE amount) {
+ int fd;
+ PRIV_TIMEHASH *private;
+ char *p;
+ struct stat sb;
+ ARTHANDLE *art;
+
+ if (amount == RETR_STAT) {
+ if (access(path, R_OK) < 0) {
+ SMseterror(SMERR_UNDEFINED, NULL);
+ return NULL;
+ }
+ art = xmalloc(sizeof(ARTHANDLE));
+ art->type = TOKEN_TIMEHASH;
+ art->data = NULL;
+ art->len = 0;
+ art->private = NULL;
+ return art;
+ }
+
+ if ((fd = open(path, O_RDONLY)) < 0) {
+ SMseterror(SMERR_UNDEFINED, NULL);
+ return NULL;
+ }
+
+ art = xmalloc(sizeof(ARTHANDLE));
+ art->type = TOKEN_TIMEHASH;
+
+ if (fstat(fd, &sb) < 0) {
+ SMseterror(SMERR_UNDEFINED, NULL);
+ syslog(L_ERROR, "timehash: could not fstat article: %m");
+ free(art);
+ return NULL;
+ }
+
+ private = xmalloc(sizeof(PRIV_TIMEHASH));
+ art->private = (void *)private;
+ private->len = sb.st_size;
+ if (innconf->articlemmap) {
+ if ((private->base = mmap(NULL, sb.st_size, PROT_READ, MAP_SHARED, fd, 0)) == MAP_FAILED) {
+ SMseterror(SMERR_UNDEFINED, NULL);
+ syslog(L_ERROR, "timehash: could not mmap article: %m");
+ free(art->private);
+ free(art);
+ return NULL;
+ }
+ if (amount == RETR_ALL)
+ madvise(private->base, sb.st_size, MADV_WILLNEED);
+ else
+ madvise(private->base, sb.st_size, MADV_SEQUENTIAL);
+ } else {
+ private->base = xmalloc(private->len);
+ if (read(fd, private->base, private->len) < 0) {
+ SMseterror(SMERR_UNDEFINED, NULL);
+ syslog(L_ERROR, "timehash: could not read article: %m");
+ free(private->base);
+ free(art->private);
+ free(art);
+ return NULL;
+ }
+ }
+ close(fd);
+
+ private->top = NULL;
+ private->sec = NULL;
+ private->ter = NULL;
+ private->artdir = NULL;
+ private->topde = NULL;
+ private->secde = NULL;
+ private->terde = NULL;
+
+ if (amount == RETR_ALL) {
+ art->data = private->base;
+ art->len = private->len;
+ return art;
+ }
+
+ if ((p = wire_findbody(private->base, private->len)) == NULL) {
+ SMseterror(SMERR_NOBODY, NULL);
+ if (innconf->articlemmap)
+ munmap(private->base, private->len);
+ else
+ free(private->base);
+ free(art->private);
+ free(art);
+ return NULL;
+ }
+
+ if (amount == RETR_HEAD) {
+ art->data = private->base;
+ art->len = p - private->base;
+ return art;
+ }
+
+ if (amount == RETR_BODY) {
+ art->data = p;
+ art->len = private->len - (p - private->base);
+ return art;
+ }
+ SMseterror(SMERR_UNDEFINED, "Invalid retrieve request");
+ if (innconf->articlemmap)
+ munmap(private->base, private->len);
+ else
+ free(private->base);
+ free(art->private);
+ free(art);
+ return NULL;
+}
+
+ARTHANDLE *timehash_retrieve(const TOKEN token, const RETRTYPE amount) {
+ int now;
+ int seqnum;
+ char *path;
+ ARTHANDLE *art;
+ static TOKEN ret_token;
+
+ if (token.type != TOKEN_TIMEHASH) {
+ SMseterror(SMERR_INTERNAL, NULL);
+ return NULL;
+ }
+
+ BreakToken(token, &now, &seqnum);
+ path = MakePath(now, seqnum, token.class);
+ if ((art = OpenArticle(path, amount)) != (ARTHANDLE *)NULL) {
+ art->arrived = now;
+ ret_token = token;
+ art->token = &ret_token;
+ }
+ free(path);
+ return art;
+}
+
+void timehash_freearticle(ARTHANDLE *article) {
+ PRIV_TIMEHASH *private;
+
+ if (!article)
+ return;
+
+ if (article->private) {
+ private = (PRIV_TIMEHASH *)article->private;
+ if (innconf->articlemmap)
+ munmap(private->base, private->len);
+ else
+ free(private->base);
+ if (private->top)
+ closedir(private->top);
+ if (private->sec)
+ closedir(private->sec);
+ if (private->ter)
+ closedir(private->ter);
+ if (private->artdir)
+ closedir(private->artdir);
+ free(private);
+ }
+ free(article);
+}
+
+bool timehash_cancel(TOKEN token) {
+ int now;
+ int seqnum;
+ char *path;
+ int result;
+
+ BreakToken(token, &now, &seqnum);
+ path = MakePath(now, seqnum, token.class);
+ result = unlink(path);
+ free(path);
+ if (result < 0) {
+ SMseterror(SMERR_UNDEFINED, NULL);
+ return false;
+ }
+ return true;
+}
+
+static struct dirent *FindDir(DIR *dir, FINDTYPE type) {
+ struct dirent *de;
+
+ while ((de = readdir(dir)) != NULL) {
+ if (type == FIND_TOPDIR)
+ if ((strlen(de->d_name) == 7) &&
+ (strncmp(de->d_name, "time-", 5) == 0) &&
+ isxdigit((int)de->d_name[5]) &&
+ isxdigit((int)de->d_name[6]))
+ return de;
+
+ if (type == FIND_DIR)
+ if ((strlen(de->d_name) == 2) && isxdigit((int)de->d_name[0]) && isxdigit((int)de->d_name[1]))
+ return de;
+
+ if (type == FIND_ART)
+ if ((strlen(de->d_name) == 9) &&
+ isxdigit((int)de->d_name[0]) &&
+ isxdigit((int)de->d_name[1]) &&
+ isxdigit((int)de->d_name[2]) &&
+ isxdigit((int)de->d_name[3]) &&
+ isxdigit((int)de->d_name[5]) &&
+ isxdigit((int)de->d_name[6]) &&
+ isxdigit((int)de->d_name[7]) &&
+ isxdigit((int)de->d_name[8]) &&
+ (de->d_name[4] == '-'))
+ return de;
+ }
+
+ return NULL;
+}
+
+ARTHANDLE *timehash_next(const ARTHANDLE *article, const RETRTYPE amount) {
+ PRIV_TIMEHASH priv;
+ PRIV_TIMEHASH *newpriv;
+ char *path;
+ struct dirent *de;
+ ARTHANDLE *art;
+ int seqnum;
+ size_t length;
+
+ length = strlen(innconf->patharticles) + 32;
+ path = xmalloc(length);
+ if (article == NULL) {
+ priv.top = NULL;
+ priv.sec = NULL;
+ priv.ter = NULL;
+ priv.artdir = NULL;
+ priv.topde = NULL;
+ priv.secde = NULL;
+ priv.terde = NULL;
+ } else {
+ priv = *(PRIV_TIMEHASH *)article->private;
+ free(article->private);
+ free((void *)article);
+ if (priv.base != NULL) {
+ if (innconf->articlemmap)
+ munmap(priv.base, priv.len);
+ else
+ free(priv.base);
+ }
+ }
+
+ while (!priv.artdir || ((de = FindDir(priv.artdir, FIND_ART)) == NULL)) {
+ if (priv.artdir) {
+ closedir(priv.artdir);
+ priv.artdir = NULL;
+ }
+ while (!priv.ter || ((priv.terde = FindDir(priv.ter, FIND_DIR)) == NULL)) {
+ if (priv.ter) {
+ closedir(priv.ter);
+ priv.ter = NULL;
+ }
+ while (!priv.sec || ((priv.secde = FindDir(priv.sec, FIND_DIR)) == NULL)) {
+ if (priv.sec) {
+ closedir(priv.sec);
+ priv.sec = NULL;
+ }
+ if (!priv.top || ((priv.topde = FindDir(priv.top, FIND_TOPDIR)) == NULL)) {
+ if (priv.top) {
+ /* end of search */
+ closedir(priv.top);
+ priv.top = NULL;
+ free(path);
+ return NULL;
+ }
+ snprintf(path, length, "%s", innconf->patharticles);
+ if ((priv.top = opendir(path)) == NULL) {
+ SMseterror(SMERR_UNDEFINED, NULL);
+ free(path);
+ return NULL;
+ }
+ if ((priv.topde = FindDir(priv.top, FIND_TOPDIR)) == NULL) {
+ SMseterror(SMERR_UNDEFINED, NULL);
+ closedir(priv.top);
+ free(path);
+ return NULL;
+ }
+ }
+ snprintf(path, length, "%s/%s", innconf->patharticles, priv.topde->d_name);
+ if ((priv.sec = opendir(path)) == NULL)
+ continue;
+ }
+ snprintf(path, length, "%s/%s/%s", innconf->patharticles, priv.topde->d_name, priv.secde->d_name);
+ if ((priv.ter = opendir(path)) == NULL)
+ continue;
+ }
+ snprintf(path, length, "%s/%s/%s/%s", innconf->patharticles, priv.topde->d_name, priv.secde->d_name, priv.terde->d_name);
+ if ((priv.artdir = opendir(path)) == NULL)
+ continue;
+ }
+ if (de == NULL)
+ return NULL;
+ snprintf(path, length, "%s/%s/%s/%s/%s", innconf->patharticles, priv.topde->d_name, priv.secde->d_name, priv.terde->d_name, de->d_name);
+
+ art = OpenArticle(path, amount);
+ if (art == (ARTHANDLE *)NULL) {
+ art = xmalloc(sizeof(ARTHANDLE));
+ art->type = TOKEN_TIMEHASH;
+ art->data = NULL;
+ art->len = 0;
+ art->private = xmalloc(sizeof(PRIV_TIMEHASH));
+ newpriv = (PRIV_TIMEHASH *)art->private;
+ newpriv->base = NULL;
+ }
+ newpriv = (PRIV_TIMEHASH *)art->private;
+ newpriv->top = priv.top;
+ newpriv->sec = priv.sec;
+ newpriv->ter = priv.ter;
+ newpriv->artdir = priv.artdir;
+ newpriv->topde = priv.topde;
+ newpriv->secde = priv.secde;
+ newpriv->terde = priv.terde;
+ snprintf(path, length, "%s/%s/%s/%s", priv.topde->d_name, priv.secde->d_name, priv.terde->d_name, de->d_name);
+ art->token = PathToToken(path);
+ BreakToken(*art->token, (int *)&(art->arrived), &seqnum);
+ free(path);
+ return art;
+}
+
+bool timehash_ctl(PROBETYPE type, TOKEN *token UNUSED, void *value) {
+ struct artngnum *ann;
+
+ switch (type) {
+ case SMARTNGNUM:
+ if ((ann = (struct artngnum *)value) == NULL)
+ return false;
+ /* make SMprobe() call timehash_retrieve() */
+ ann->artnum = 0;
+ return true;
+ default:
+ return false;
+ }
+}
+
+bool
+timehash_flushcacheddata(FLUSHTYPE type UNUSED)
+{
+ return true;
+}
+
+void
+timehash_printfiles(FILE *file, TOKEN token, char **xref UNUSED,
+ int ngroups UNUSED)
+{
+ int now, seqnum;
+ char *path;
+
+ BreakToken(token, &now, &seqnum);
+ path = MakePath(now, seqnum, token.class);
+ fprintf(file, "%s\n", path);
+}
+
+void timehash_shutdown(void) {
+}
--- /dev/null
+/* $Id: timehash.h 4268 2001-01-04 06:02:50Z rra $
+**
+** timehash based storing method header
+*/
+
+#ifndef __TIMEHASH_H__
+#define __TIMEHASH_H__
+
+#include "config.h"
+#include "interface.h"
+
+bool timehash_init(SMATTRIBUTE *attr);
+TOKEN timehash_store(const ARTHANDLE article, const STORAGECLASS class);
+ARTHANDLE *timehash_retrieve(const TOKEN token, const RETRTYPE amount);
+ARTHANDLE *timehash_next(const ARTHANDLE *article, const RETRTYPE amount);
+void timehash_freearticle(ARTHANDLE *article);
+bool timehash_cancel(TOKEN token);
+bool timehash_ctl(PROBETYPE type, TOKEN *token, void *value);
+bool timehash_flushcacheddata(FLUSHTYPE type);
+void timehash_printfiles(FILE *file, TOKEN token, char **xref, int ngroups);
+void timehash_shutdown(void);
+
+#endif
--- /dev/null
+name = tradindexed
+number = 2
+sources = tdx-cache.c tdx-group.c tdx-data.c tradindexed.c
+extra-sources = tdx-util.c
+programs = tdx-util
--- /dev/null
+tradindexed/tdx-util.o: tradindexed/tdx-util.c
+ $(CC) $(CFLAGS) -c -o $@ tradindexed/tdx-util.c
+
+tradindexed/tdx-util: tradindexed/tdx-util.o libstorage.$(EXTLIB) $(LIBHIST)
+ $(LIBLD) $(LDFLAGS) -o $@ tradindexed/tdx-util.o \
+ $(LIBSTORAGE) $(LIBHIST) $(LIBINN) $(EXTSTORAGELIBS) $(LIBS)
--- /dev/null
+/* $Id: tdx-cache.c 5394 2002-04-04 22:55:24Z rra $
+**
+** Cache functions for open overview data files.
+**
+** This code maintains a cache of open overview data files to avoid some of
+** the overhead involved in closing and reopening files. All opens and
+** closes should go through this code, and the hit ratio is tracked to check
+** cache effectiveness.
+*/
+
+#include "config.h"
+#include "clibrary.h"
+#include <time.h>
+
+#include "inn/hashtab.h"
+#include "inn/messages.h"
+#include "libinn.h"
+#include "storage.h"
+#include "tdx-private.h"
+
+/* Returned to callers as an opaque data type, this struct holds all of the
+ information about the cache. */
+struct cache {
+ struct hash *hashtable;
+ unsigned int count;
+ unsigned int max;
+ unsigned long queries;
+ unsigned long hits;
+};
+
+/* A cache entry, holding a group_data struct and some additional information
+ used to do cache lookups and to choose what to drop from the cache. */
+struct cache_entry {
+ struct group_data *data;
+ HASH hash;
+ time_t lastused;
+};
+
+
+/*
+** The hash function for a cache_entry. Just return as much of the group
+** hash as can fit in an unsigned long.
+*/
+static unsigned long
+entry_hash(const void *key)
+{
+ const HASH *hash = key;
+ unsigned long bucket;
+
+ memcpy(&bucket, hash, sizeof(bucket));
+ return bucket;
+}
+
+
+/*
+** Given a cache_entry, return its key.
+*/
+static const void *
+entry_key(const void *data)
+{
+ const struct cache_entry *entry = (const struct cache_entry *) data;
+
+ return &entry->hash;
+}
+
+
+/*
+** Check to see if two entries are equal.
+*/
+static bool
+entry_equal(const void *key, const void *data)
+{
+ const HASH *hash = (const HASH *) key;
+ const struct cache_entry *entry = (const struct cache_entry *) data;
+
+ return (memcmp(hash, &entry->hash, sizeof(HASH)) == 0);
+}
+
+
+/*
+** Free a cache entry.
+*/
+static void
+entry_delete(void *data)
+{
+ struct cache_entry *entry = (struct cache_entry *) data;
+
+ entry->data->refcount--;
+ if (entry->data->refcount == 0)
+ tdx_data_close(entry->data);
+ free(entry);
+}
+
+
+/*
+** Called by hash_traverse, this function finds the oldest entry with the
+** smallest refcount and stores it in the provided pointer so that it can be
+** freed. This is used when the cache is full to drop the least useful
+** entry.
+*/
+static void
+entry_find_oldest(void *data, void *cookie)
+{
+ struct cache_entry *entry = (struct cache_entry *) data;
+ struct cache_entry **oldest = (struct cache_entry **) cookie;
+
+ if (*oldest == NULL) {
+ *oldest = entry;
+ return;
+ }
+ if (entry->data->refcount > (*oldest)->data->refcount)
+ return;
+ if (entry->lastused > (*oldest)->lastused)
+ return;
+ *oldest = data;
+}
+
+
+/*
+** Create a new cache with the given size.
+*/
+struct cache *
+tdx_cache_create(unsigned int size)
+{
+ struct cache *cache;
+
+ cache = xmalloc(sizeof(struct cache));
+ cache->count = 0;
+ cache->max = size;
+ cache->queries = 0;
+ cache->hits = 0;
+ cache->hashtable = hash_create(size * 4 / 3, entry_hash, entry_key,
+ entry_equal, entry_delete);
+ return cache;
+}
+
+
+/*
+** Look up a particular entry and return it.
+*/
+struct group_data *
+tdx_cache_lookup(struct cache *cache, HASH hash)
+{
+ struct cache_entry *entry;
+
+ cache->queries++;
+ entry = hash_lookup(cache->hashtable, &hash);
+ if (entry != NULL) {
+ cache->hits++;
+ entry->lastused = time(NULL);
+ }
+ return (entry == NULL) ? NULL : entry->data;
+}
+
+
+/*
+** Insert a new entry, clearing out the oldest entry if the cache is
+** currently full.
+*/
+void
+tdx_cache_insert(struct cache *cache, HASH hash, struct group_data *data)
+{
+ struct cache_entry *entry;
+
+ if (cache->count == cache->max) {
+ struct cache_entry *oldest = NULL;
+
+ hash_traverse(cache->hashtable, entry_find_oldest, &oldest);
+ if (oldest == NULL) {
+ warn("tradindexed: unable to find oldest cache entry");
+ return;
+ } else {
+ if (!hash_delete(cache->hashtable, &oldest->hash)) {
+ warn("tradindexed: cannot delete oldest cache entry");
+ return;
+ }
+ }
+ cache->count--;
+ }
+ entry = xmalloc(sizeof(struct cache_entry));
+ entry->data = data;
+ entry->hash = hash;
+ entry->lastused = time(NULL);
+ if (!hash_insert(cache->hashtable, &entry->hash, entry)) {
+ warn("tradindexed: duplicate cache entry for %s", HashToText(hash));
+ free(entry);
+ } else {
+ entry->data->refcount++;
+ cache->count++;
+ }
+}
+
+
+/*
+** Delete an entry from the cache.
+*/
+void
+tdx_cache_delete(struct cache *cache, HASH hash)
+{
+ if (!hash_delete(cache->hashtable, &hash))
+ warn("tradindexed: unable to remove cache entry for %s",
+ HashToText(hash));
+}
+
+
+/*
+** Delete the cache and all of the resources that it's holding open.
+*/
+void
+tdx_cache_free(struct cache *cache)
+{
+ hash_free(cache->hashtable);
+ free(cache);
+}
--- /dev/null
+/* $Id: tdx-data.c 7598 2007-02-09 02:40:51Z eagle $
+**
+** Overview data file handling for the tradindexed overview method.
+**
+** Implements the handling of the .IDX and .DAT files for the tradindexed
+** overview method. The .IDX files are flat arrays of binary structs
+** specifying the offset in the data file of the overview data for a given
+** article as well as the length of that data and some additional meta-data
+** about that article. The .DAT files contain all of the overview data for
+** that group in wire format.
+**
+** Externally visible functions have a tdx_ prefix; internal functions do
+** not. (Externally visible unfortunately means everything that needs to be
+** visible outside of this object file, not just interfaces exported to
+** consumers of the overview API.)
+*/
+
+#include "config.h"
+#include "clibrary.h"
+#include "portable/mmap.h"
+#include <errno.h>
+#include <fcntl.h>
+#include <sys/stat.h>
+
+#include "inn/history.h"
+#include "inn/innconf.h"
+#include "inn/messages.h"
+#include "inn/mmap.h"
+#include "libinn.h"
+#include "ov.h"
+#include "ovinterface.h"
+#include "storage.h"
+#include "tdx-private.h"
+#include "tdx-structure.h"
+
+/* Returned to callers as an opaque data type, this holds the information
+ needed to manage a search in progress. */
+struct search {
+ ARTNUM limit;
+ ARTNUM current;
+ struct group_data *data;
+};
+
+/* Internal prototypes. */
+static char *group_path(const char *group);
+static int file_open(const char *base, const char *suffix, bool writable,
+ bool append);
+static bool file_open_index(struct group_data *, const char *suffix);
+static bool file_open_data(struct group_data *, const char *suffix);
+static void *map_file(int fd, size_t length, const char *base,
+ const char *suffix);
+static bool map_index(struct group_data *data);
+static bool map_data(struct group_data *data);
+static void unmap_index(struct group_data *data);
+static void unmap_data(struct group_data *data);
+static ARTNUM index_base(ARTNUM artnum);
+
+
+/*
+** Determine the path to the data files for a particular group and return
+** it. Allocates memory which the caller is responsible for freeing.
+*/
+static char *
+group_path(const char *group)
+{
+ char *path, *p;
+ size_t length;
+ const char *gp;
+
+ /* The path of the data files for news.groups is dir/n/g/news.groups. In
+ other words, the first letter of each component becomes a directory.
+ The length of the path is therefore the length of the base overview
+ directory path, one character for the slash, two characters for the
+ first letter and initial slash, two characters for each hierarchical
+ level of the group, and then the length of the group name.
+
+ For robustness, we want to handle leading or multiple consecutive
+ periods. We only recognize a new hierarchical level after a string of
+ periods (which doesn't end the group name). */
+ length = strlen(innconf->pathoverview);
+ for (gp = group; *gp != '\0'; gp++)
+ if (*gp == '.') {
+ if (gp[1] == '.' || gp[0] == '\0')
+ continue;
+ length += 2;
+ }
+ length += 1 + 2 + strlen(group) + 1;
+ path = xmalloc(length);
+ strlcpy(path, innconf->pathoverview, length);
+ p = path + strlen(innconf->pathoverview);
+
+ /* Generate the hierarchical directories. */
+ if (*group != '.' && *group != '\0') {
+ *p++ = '/';
+ *p++ = *group;
+ }
+ for (gp = strchr(group, '.'); gp != NULL; gp = strchr(gp, '.')) {
+ gp++;
+ if (gp == group + 1)
+ continue;
+ if (*gp != '\0' && *gp != '.' && *gp != '/') {
+ *p++ = '/';
+ *p++ = *gp;
+ }
+ }
+ *p++ = '/';
+
+ /* Finally, append the group name to the generated path and then replace
+ all slashes with commas. Commas have the advantage of being clearly
+ illegal in newsgroup names because of the syntax of the Newsgroups
+ header, but aren't shell metacharacters. */
+ strlcpy(p, group, length - (p - path));
+ for (; *p != '\0'; p++)
+ if (*p == '/')
+ *p = ',';
+ return path;
+}
+
+
+/*
+** Open a data file for a group. Takes the base portion of the file, the
+** suffix, a bool saying whether or not the file is being opened for write,
+** and a bool saying whether to open it for append. Returns the file
+** descriptor.
+*/
+static int
+file_open(const char *base, const char *suffix, bool writable, bool append)
+{
+ char *file;
+ int flags, fd;
+
+ file = concat(base, ".", suffix, (char *) 0);
+ flags = writable ? (O_RDWR | O_CREAT) : O_RDONLY;
+ if (append)
+ flags |= O_APPEND;
+ fd = open(file, flags, ARTFILE_MODE);
+ if (fd < 0 && writable && errno == ENOENT) {
+ char *p = strrchr(file, '/');
+
+ *p = '\0';
+ if (!MakeDirectory(file, true)) {
+ syswarn("tradindexed: cannot create directory %s", file);
+ free(file);
+ return -1;
+ }
+ *p = '/';
+ fd = open(file, flags, ARTFILE_MODE);
+ }
+ if (fd < 0) {
+ if (errno != ENOENT)
+ syswarn("tradindexed: cannot open %s", file);
+ free(file);
+ return -1;
+ }
+ free(file);
+ return fd;
+}
+
+
+/*
+** Open the index file for a group. Takes an optional suffix to use instead
+** of IDX (used primarily for expiring).
+*/
+static bool
+file_open_index(struct group_data *data, const char *suffix)
+{
+ struct stat st;
+
+ if (suffix == NULL)
+ suffix = "IDX";
+ if (data->indexfd >= 0)
+ close(data->indexfd);
+ data->indexfd = file_open(data->path, suffix, data->writable, false);
+ if (data->indexfd < 0)
+ return false;
+ if (fstat(data->indexfd, &st) < 0) {
+ syswarn("tradindexed: cannot stat %s.%s", data->path, suffix);
+ close(data->indexfd);
+ return false;
+ }
+ data->indexinode = st.st_ino;
+ close_on_exec(data->indexfd, true);
+ return true;
+}
+
+
+/*
+** Open the data file for a group. Takes an optional suffix to use instead
+** of DAT (used primarily for expiring).
+*/
+static bool
+file_open_data(struct group_data *data, const char *suffix)
+{
+ if (suffix == NULL)
+ suffix = "DAT";
+ if (data->datafd >= 0)
+ close(data->datafd);
+ data->datafd = file_open(data->path, suffix, data->writable, true);
+ if (data->datafd < 0)
+ return false;
+ close_on_exec(data->datafd, true);
+ return true;
+}
+
+
+/*
+** Open a particular group. Allocates a new struct group_data that should be
+** passed to tdx_data_close() when the caller is done with it.
+*/
+struct group_data *
+tdx_data_new(const char *group, bool writable)
+{
+ struct group_data *data;
+
+ data = xmalloc(sizeof(struct group_data));
+ data->path = group_path(group);
+ data->writable = writable;
+ data->high = 0;
+ data->base = 0;
+ data->indexfd = -1;
+ data->datafd = -1;
+ data->index = NULL;
+ data->data = NULL;
+ data->indexlen = 0;
+ data->datalen = 0;
+ data->indexinode = 0;
+ data->refcount = 0;
+
+ return data;
+}
+
+
+/*
+** Open the index and data files for a group.
+*/
+bool
+tdx_data_open_files(struct group_data *data)
+{
+ unmap_index(data);
+ unmap_data(data);
+ data->index = NULL;
+ data->data = NULL;
+ if (!file_open_index(data, NULL))
+ goto fail;
+ if (!file_open_data(data, NULL))
+ goto fail;
+ return true;
+
+ fail:
+ if (data->indexfd >= 0)
+ close(data->indexfd);
+ if (data->datafd >= 0)
+ close(data->datafd);
+ return false;
+}
+
+
+/*
+** Map a data file (either index or data), or read in all of the data in the
+** file if we're avoiding mmap. Takes the base and suffix of the file for
+** error reporting.
+*/
+static void *
+map_file(int fd, size_t length, const char *base, const char *suffix)
+{
+ char *data;
+
+ if (length == 0)
+ return NULL;
+
+ if (!innconf->tradindexedmmap) {
+ ssize_t status;
+
+ data = xmalloc(length);
+ status = read(fd, data, length);
+ if ((size_t) status != length) {
+ syswarn("tradindexed: cannot read data file %s.%s", base, suffix);
+ free(data);
+ return NULL;
+ }
+ } else {
+ data = mmap(NULL, length, PROT_READ, MAP_SHARED, fd, 0);
+ if (data == MAP_FAILED) {
+ syswarn("tradindexed: cannot mmap %s.%s", base, suffix);
+ return NULL;
+ }
+ }
+ return data;
+}
+
+
+/*
+** Memory map the index file.
+*/
+static bool
+map_index(struct group_data *data)
+{
+ struct stat st;
+ int r;
+
+ r = fstat(data->indexfd, &st);
+ if (r == -1) {
+ if (errno == ESTALE) {
+ r = file_open_index(data, NULL);
+ } else {
+ syswarn("tradindexed: cannot stat %s.IDX", data->path);
+ }
+ }
+ if (r == -1)
+ return false;
+ data->indexlen = st.st_size;
+ data->index = map_file(data->indexfd, data->indexlen, data->path, "IDX");
+ return (data->index == NULL && data->indexlen > 0) ? false : true;
+}
+
+
+/*
+** Memory map the data file.
+*/
+static bool
+map_data(struct group_data *data)
+{
+ struct stat st;
+ int r;
+
+ r = fstat(data->datafd, &st);
+ if (r == -1) {
+ if (errno == ESTALE) {
+ r = file_open_data(data, NULL);
+ } else {
+ syswarn("tradindexed: cannot stat %s.DAT", data->path);
+ }
+ }
+ if (r == -1)
+ return false;
+ data->datalen = st.st_size;
+ data->data = map_file(data->datafd, data->datalen, data->path, "DAT");
+ return (data->data == NULL && data->indexlen > 0) ? false : true;
+}
+
+
+/*
+** Unmap a data file or free the memory copy if we're not using mmap. Takes
+** the memory to free or unmap, the length for munmap, and the name base and
+** suffix for error reporting.
+*/
+static void
+unmap_file(void *data, off_t length, const char *base, const char *suffix)
+{
+ if (data == NULL)
+ return;
+ if (!innconf->tradindexedmmap)
+ free(data);
+ else {
+ if (munmap(data, length) < 0)
+ syswarn("tradindexed: cannot munmap %s.%s", base, suffix);
+ }
+ return;
+}
+
+/*
+** Unmap the index file.
+*/
+static void
+unmap_index(struct group_data *data)
+{
+ unmap_file(data->index, data->indexlen, data->path, "IDX");
+ data->index = NULL;
+}
+
+
+/*
+** Unmap the data file.
+*/
+static void
+unmap_data(struct group_data *data)
+{
+ unmap_file(data->data, data->datalen, data->path, "DAT");
+ data->data = NULL;
+}
+
+/*
+** Determine if the file handle associated with the index table is stale
+*/
+static bool
+stale_index(struct group_data *data)
+{
+ struct stat st;
+ int r;
+
+ r = fstat(data->indexfd, &st);
+ return r == -1 && errno == ESTALE;
+}
+
+
+/*
+** Determine if the file handle associated with the data table is stale
+*/
+static bool
+stale_data(struct group_data *data)
+{
+ struct stat st;
+ int r;
+
+ r = fstat(data->datafd, &st);
+ return r == -1 && errno == ESTALE;
+}
+
+
+/*
+** Retrieves the article metainformation stored in the index table (all the
+** stuff we can return without opening the data file). Takes the article
+** number and returns a pointer to the index entry. Also takes the high
+** water mark from the group index; this is used to decide whether to attempt
+** remapping of the index file if the current high water mark is too low.
+*/
+const struct index_entry *
+tdx_article_entry(struct group_data *data, ARTNUM article, ARTNUM high)
+{
+ struct index_entry *entry;
+ ARTNUM offset;
+
+ if (article > data->high && high > data->high) {
+ unmap_index(data);
+ map_index(data);
+ data->high = high;
+ } else if (innconf->nfsreader && stale_index(data))
+ unmap_index(data);
+ if (data->index == NULL)
+ if (!map_index(data))
+ return NULL;
+
+ if (article < data->base)
+ return NULL;
+ offset = article - data->base;
+ if (offset >= data->indexlen / sizeof(struct index_entry))
+ return NULL;
+ entry = data->index + offset;
+ if (entry->length == 0)
+ return NULL;
+ return entry;
+}
+
+
+/*
+** Begin an overview search. In addition to the bounds of the search, we
+** also take the high water mark from the group index; this is used to decide
+** whether or not to attempt remapping of the index file if the current high
+** water mark is too low.
+*/
+struct search *
+tdx_search_open(struct group_data *data, ARTNUM start, ARTNUM end, ARTNUM high)
+{
+ struct search *search;
+
+ if (end < data->base)
+ return NULL;
+ if (end < start)
+ return NULL;
+
+ if (end > data->high && high > data->high) {
+ unmap_index(data);
+ map_index(data);
+ data->high = high;
+ }
+ if (start > data->high)
+ return NULL;
+
+ if (innconf->nfsreader && stale_index(data))
+ unmap_index(data);
+ if (data->index == NULL)
+ if (!map_index(data))
+ return NULL;
+ if (innconf->nfsreader && stale_data(data))
+ unmap_data(data);
+ if (data->data == NULL)
+ if (!map_data(data))
+ return NULL;
+
+ search = xmalloc(sizeof(struct search));
+ search->limit = end - data->base;
+ search->current = (start < data->base) ? 0 : start - data->base;
+ search->data = data;
+ search->data->refcount++;
+
+ return search;
+}
+
+
+/*
+** Return the next record in a search.
+*/
+bool
+tdx_search(struct search *search, struct article *artdata)
+{
+ struct index_entry *entry;
+ size_t max;
+
+ if (search == NULL || search->data == NULL)
+ return false;
+ if (search->data->index == NULL || search->data->data == NULL)
+ return false;
+
+ max = (search->data->indexlen / sizeof(struct index_entry)) - 1;
+ entry = search->data->index + search->current;
+ while (search->current <= search->limit && search->current <= max) {
+ if (entry->length != 0)
+ break;
+ search->current++;
+ entry++;
+ }
+ if (search->current > search->limit || search->current > max)
+ return false;
+
+ /* Make sure that the offset into the data file is sensible, and try
+ remapping the data file if the portion the offset is pointing to isn't
+ currently mapped. Otherwise, warn about possible corruption and return
+ a miss. */
+ if (entry->offset + entry->length > search->data->datalen) {
+ unmap_data(search->data);
+ if (!map_data(search->data))
+ return false;
+ }
+ if (entry->offset + entry->length > search->data->datalen) {
+ warn("Invalid entry for article %lu in %s.IDX: offset %lu length %lu",
+ search->current + search->data->base, search->data->path,
+ (unsigned long) entry->offset, (unsigned long) entry->length);
+ return false;
+ }
+
+ artdata->number = search->current + search->data->base;
+ artdata->overview = search->data->data + entry->offset;
+ artdata->overlen = entry->length;
+ artdata->token = entry->token;
+ artdata->arrived = entry->arrived;
+ artdata->expires = entry->expires;
+
+ search->current++;
+ return true;
+}
+
+
+/*
+** End an overview search.
+*/
+void
+tdx_search_close(struct search *search)
+{
+ if (search->data != NULL) {
+ search->data->refcount--;
+ if (search->data->refcount == 0)
+ tdx_data_close(search->data);
+ }
+ free(search);
+}
+
+
+/*
+** Given an article number, return an index base appropriate for that article
+** number. This includes a degree of slop so that we don't have to
+** constantly repack if the article numbers are clustered around a particular
+** value but don't come in order.
+*/
+ARTNUM
+index_base(ARTNUM artnum)
+{
+ return (artnum > 128) ? (artnum - 128) : 1;
+}
+
+
+/*
+** Store the data for a single article into the overview files for a group.
+** Assumes any necessary repacking has already been done. If the base value
+** in the group_data structure is 0, assumes this is the first time we've
+** written overview information to this group and sets it appropriately.
+*/
+bool
+tdx_data_store(struct group_data *data, const struct article *article)
+{
+ struct index_entry entry;
+ off_t offset;
+
+ if (!data->writable)
+ return false;
+ if (data->base == 0)
+ data->base = index_base(article->number);
+ if (data->base > article->number) {
+ warn("tradindexed: cannot add %lu to %s.IDX, base == %lu",
+ article->number, data->path, data->base);
+ return false;
+ }
+
+ /* Write out the data and fill in the index entry. */
+ memset(&entry, 0, sizeof(entry));
+ if (xwrite(data->datafd, article->overview, article->overlen) < 0) {
+ syswarn("tradindexed: cannot append %lu of data for %lu to %s.DAT",
+ (unsigned long) article->overlen, article->number,
+ data->path);
+ return false;
+ }
+ entry.offset = lseek(data->datafd, 0, SEEK_CUR);
+ if (entry.offset < 0) {
+ syswarn("tradindexed: cannot get offset for article %lu in %s.DAT",
+ article->number, data->path);
+ return false;
+ }
+ entry.length = article->overlen;
+ entry.offset -= entry.length;
+ entry.arrived = article->arrived;
+ entry.expires = article->expires;
+ entry.token = article->token;
+
+ /* Write out the index entry. */
+ offset = (article->number - data->base) * sizeof(struct index_entry);
+ if (xpwrite(data->indexfd, &entry, sizeof(entry), offset) < 0) {
+ syswarn("tradindexed: cannot write index record for %lu in %s.IDX",
+ article->number, data->path);
+ return false;
+ }
+ return true;
+}
+
+
+/*
+** Start the process of packing a group (rewriting its index file so that it
+** uses a different article base). Takes the article number of an article
+** that needs to be written to the index file and is below the current base.
+** Returns the true success and false on failure, and sets data->base to the
+** new article base and data->indexinode to the new inode number. At the
+** conclusion of this routine, the new index file has been created, but it
+** has not yet been moved into place; that is done by tdx_data_pack_finish.
+*/
+bool
+tdx_data_pack_start(struct group_data *data, ARTNUM artnum)
+{
+ ARTNUM base;
+ unsigned long delta;
+ int fd;
+ char *idxfile;
+ struct stat st;
+
+ if (!data->writable)
+ return false;
+ if (data->base <= artnum) {
+ warn("tradindexed: tdx_data_pack_start called unnecessarily");
+ return false;
+ }
+
+ /* Open the new index file. */
+ base = index_base(artnum);
+ delta = data->base - base;
+ fd = file_open(data->path, "IDX-NEW", true, false);
+ if (fd < 0)
+ return false;
+ if (fstat(fd, &st) < 0) {
+ warn("tradindexed: cannot stat %s.IDX-NEW", data->path);
+ goto fail;
+ }
+
+ /* For convenience, memory map the old index file. */
+ unmap_index(data);
+ if (!map_index(data))
+ goto fail;
+
+ /* Write the contents of the old index file to the new index file. */
+ if (lseek(fd, delta * sizeof(struct index_entry), SEEK_SET) < 0) {
+ syswarn("tradindexed: cannot seek in %s.IDX-NEW", data->path);
+ goto fail;
+ }
+ if (xwrite(fd, data->index, data->indexlen) < 0) {
+ syswarn("tradindexed: cannot write to %s.IDX-NEW", data->path);
+ goto fail;
+ }
+ if (close(fd) < 0) {
+ syswarn("tradindexed: cannot close %s.IDX-NEW", data->path);
+ goto fail;
+ }
+ data->base = base;
+ data->indexinode = st.st_ino;
+ return true;
+
+ fail:
+ if (fd >= 0) {
+ close(fd);
+ idxfile = concat(data->path, ".IDX-NEW", (char *) 0);
+ if (unlink(idxfile) < 0)
+ syswarn("tradindexed: cannot unlink %s", idxfile);
+ free(idxfile);
+ }
+ return false;
+}
+
+
+/*
+** Finish the process of packing a group by replacing the new index with the
+** old index. Also reopen the index file and update indexinode to keep our
+** caller from having to close and reopen the index file themselves.
+*/
+bool
+tdx_data_pack_finish(struct group_data *data)
+{
+ char *newidx, *idx;
+
+ if (!data->writable)
+ return false;
+ newidx = concat(data->path, ".IDX-NEW", (char *) 0);
+ idx = concat(data->path, ".IDX", (char *) 0);
+ if (rename(newidx, idx) < 0) {
+ syswarn("tradindexed: cannot rename %s to %s", newidx, idx);
+ unlink(newidx);
+ free(newidx);
+ free(idx);
+ return false;
+ } else {
+ free(newidx);
+ free(idx);
+ if (!file_open_index(data, NULL))
+ return false;
+ return true;
+ }
+}
+
+
+/*
+** Open the data files for a group data rebuild, and return a struct
+** group_data for the new files. Calling this function doesn't interfere
+** with the existing data for the group. Either tdx_data_rebuild_abort or
+** tdx_data_rebuild_finish should be called on the returned struct group_data
+** when the caller is done.
+*/
+struct group_data *
+tdx_data_rebuild_start(const char *group)
+{
+ struct group_data *data;
+
+ data = tdx_data_new(group, true);
+ tdx_data_delete(group, "-NEW");
+ if (!file_open_index(data, "IDX-NEW"))
+ goto fail;
+ if (!file_open_data(data, "DAT-NEW"))
+ goto fail;
+ return data;
+
+ fail:
+ tdx_data_delete(group, "-NEW");
+ tdx_data_close(data);
+ return NULL;
+}
+
+
+/*
+** Finish a rebuild by renaming the new index and data files to their
+** permanent names.
+*/
+bool
+tdx_data_rebuild_finish(const char *group)
+{
+ char *base, *newidx, *bakidx, *idx, *newdat, *dat;
+ bool saved = false;
+
+ base = group_path(group);
+ idx = concat(base, ".IDX", (char *) 0);
+ newidx = concat(base, ".IDX-NEW", (char *) 0);
+ bakidx = concat(base, ".IDX-BAK", (char *) 0);
+ dat = concat(base, ".DAT", (char *) 0);
+ newdat = concat(base, ".DAT-NEW", (char *) 0);
+ free(base);
+ if (rename(idx, bakidx) < 0) {
+ syswarn("tradindexed: cannot rename %s to %s", idx, bakidx);
+ goto fail;
+ } else {
+ saved = true;
+ }
+ if (rename(newidx, idx) < 0) {
+ syswarn("tradindexed: cannot rename %s to %s", newidx, idx);
+ goto fail;
+ }
+ if (rename(newdat, dat) < 0) {
+ syswarn("tradindexed: cannot rename %s to %s", newdat, dat);
+ goto fail;
+ }
+ if (unlink(bakidx) < 0)
+ syswarn("tradindexed: cannot remove backup %s", bakidx);
+ free(idx);
+ free(newidx);
+ free(bakidx);
+ free(dat);
+ free(newdat);
+ return true;
+
+ fail:
+ if (saved && rename(bakidx, idx) < 0)
+ syswarn("tradindexed: cannot restore old index %s", bakidx);
+ free(idx);
+ free(newidx);
+ free(bakidx);
+ free(dat);
+ free(newdat);
+ return false;
+}
+
+
+/*
+** Do the main work of expiring a group. Step through each article in the
+** group, only writing the unexpired entries out to the new group. There's
+** probably some room for optimization here for newsgroups that don't expire
+** so that the files don't have to be rewritten, or newsgroups where all the
+** data at the end of the file is still good and just needs to be moved
+** as-is.
+*/
+bool
+tdx_data_expire_start(const char *group, struct group_data *data,
+ struct group_entry *index, struct history *history)
+{
+ struct group_data *new_data;
+ struct search *search;
+ struct article article;
+ ARTNUM high;
+
+ new_data = tdx_data_rebuild_start(group);
+ if (new_data == NULL)
+ return false;
+ index->indexinode = new_data->indexinode;
+
+ /* Try to make sure that the search range is okay for even an empty group
+ so that we can treat all errors on opening a search as errors. */
+ high = index->high > 0 ? index->high : data->base;
+ new_data->high = high;
+ search = tdx_search_open(data, data->base, high, high);
+ if (search == NULL)
+ goto fail;
+
+ /* Loop through all of the articles in the group, adding the ones that are
+ still valid to the new index. */
+ while (tdx_search(search, &article)) {
+ ARTHANDLE *ah;
+
+ if (!SMprobe(EXPENSIVESTAT, &article.token, NULL) || OVstatall) {
+ ah = SMretrieve(article.token, RETR_STAT);
+ if (ah == NULL)
+ continue;
+ SMfreearticle(ah);
+ } else {
+ if (!OVhisthasmsgid(history, article.overview))
+ continue;
+ }
+ if (innconf->groupbaseexpiry)
+ if (OVgroupbasedexpire(article.token, group, article.overview,
+ article.overlen, article.arrived,
+ article.expires))
+ continue;
+ if (!tdx_data_store(new_data, &article))
+ goto fail;
+ if (index->base == 0) {
+ index->base = new_data->base;
+ index->low = article.number;
+ }
+ if (article.number > index->high)
+ index->high = article.number;
+ index->count++;
+ }
+
+ /* Done; the rest happens in tdx_data_rebuild_finish. */
+ tdx_data_close(new_data);
+ return true;
+
+ fail:
+ tdx_data_delete(group, "-NEW");
+ tdx_data_close(new_data);
+ return false;
+}
+
+
+/*
+** Close the data files for a group and free the data structure.
+*/
+void
+tdx_data_close(struct group_data *data)
+{
+ unmap_index(data);
+ unmap_data(data);
+ if (data->indexfd >= 0)
+ close(data->indexfd);
+ if (data->datafd >= 0)
+ close(data->datafd);
+ free(data->path);
+ free(data);
+}
+
+
+/*
+** Delete the data files for a particular group, called when that group is
+** deleted from the server. Takes an optional suffix, which if present is
+** appended to the ends of the file names (used by expire to delete the -NEW
+** versions of the files).
+*/
+void
+tdx_data_delete(const char *group, const char *suffix)
+{
+ char *path, *idx, *dat;
+
+ path = group_path(group);
+ idx = concat(path, ".IDX", suffix, (char *) 0);
+ dat = concat(path, ".DAT", suffix, (char *) 0);
+ if (unlink(idx) < 0 && errno != ENOENT)
+ syswarn("tradindexed: cannot unlink %s", idx);
+ if (unlink(dat) < 0 && errno != ENOENT)
+ syswarn("tradindexed: cannot unlink %s", dat);
+ free(dat);
+ free(idx);
+ free(path);
+}
+
+
+/*
+** RECOVERY AND AUDITING
+**
+** All code below this point is not used in the normal operations of the
+** overview method. Instead, it's code to dump various data structures or
+** audit them for consistency, used by recovery tools and inspection tools.
+*/
+
+/*
+** Dump the index file for a given group in human-readable format.
+*/
+void
+tdx_data_index_dump(struct group_data *data, FILE *output)
+{
+ ARTNUM current;
+ struct index_entry *entry, *end;
+
+ if (data->index == NULL)
+ if (!map_index(data))
+ return;
+
+ current = data->base;
+ end = data->index + (data->indexlen / sizeof(struct index_entry));
+ for (entry = data->index; entry < end; entry++) {
+ fprintf(output, "%lu %lu %lu %lu %lu %s\n", current,
+ (unsigned long) entry->offset, (unsigned long) entry->length,
+ (unsigned long) entry->arrived,
+ (unsigned long) entry->expires, TokenToText(entry->token));
+ current++;
+ }
+}
+
+
+/*
+** Audit a specific index entry for a particular article. If there's
+** anything wrong with it, we delete it; to repair a particular group, it's
+** best to just regenerate it from scratch.
+*/
+static void
+entry_audit(struct group_data *data, struct index_entry *entry,
+ const char *group, ARTNUM article, bool fix)
+{
+ struct index_entry new_entry;
+ off_t offset;
+
+ if (entry->length < 0) {
+ warn("tradindexed: negative length %d in %s:%lu", entry->length,
+ group, article);
+ if (fix)
+ goto clear;
+ return;
+ }
+ if (entry->offset > data->datalen || entry->length > data->datalen) {
+ warn("tradindexed: offset %lu or length %lu out of bounds for %s:%lu",
+ (unsigned long) entry->offset, (unsigned long) entry->length,
+ group, article);
+ if (fix)
+ goto clear;
+ return;
+ }
+ if (entry->offset + entry->length > data->datalen) {
+ warn("tradindexed: offset %lu plus length %lu out of bounds for"
+ " %s:%lu", (unsigned long) entry->offset,
+ (unsigned long) entry->length, group, article);
+ if (fix)
+ goto clear;
+ return;
+ }
+ if (!overview_check(data->data + entry->offset, entry->length, article)) {
+ warn("tradindexed: malformed overview data for %s:%lu", group,
+ article);
+ if (fix)
+ goto clear;
+ }
+ return;
+
+ clear:
+ new_entry = *entry;
+ new_entry.offset = 0;
+ new_entry.length = 0;
+ offset = (entry - data->index) * sizeof(struct index_entry);
+ if (xpwrite(data->indexfd, &new_entry, sizeof(new_entry), offset) != 0)
+ warn("tradindexed: unable to repair %s:%lu", group, article);
+}
+
+
+/*
+** Audit the data for a particular group. Takes the index entry from the
+** group.index file and optionally corrects any problems with the data or the
+** index entry based on the contents of the data.
+*/
+void
+tdx_data_audit(const char *group, struct group_entry *index, bool fix)
+{
+ struct group_data *data;
+ struct index_entry *entry;
+ long count;
+ off_t expected;
+ unsigned long entries, current;
+ ARTNUM low = 0;
+ bool changed = false;
+
+ data = tdx_data_new(group, true);
+ if (!tdx_data_open_files(data))
+ return;
+ if (!map_index(data))
+ goto end;
+ if (!map_data(data))
+ goto end;
+
+ /* Check the inode of the index. */
+ if (data->indexinode != index->indexinode) {
+ warn("tradindexed: index inode mismatch for %s: %lu != %lu", group,
+ (unsigned long) data->indexinode,
+ (unsigned long) index->indexinode);
+ if (fix) {
+ index->indexinode = data->indexinode;
+ changed = true;
+ }
+ }
+
+ /* Check the index size. */
+ entries = data->indexlen / sizeof(struct index_entry);
+ expected = entries * sizeof(struct index_entry);
+ if (data->indexlen != expected) {
+ warn("tradindexed: %lu bytes of trailing trash in %s.IDX",
+ (unsigned long)(data->indexlen - expected), data->path);
+ if (fix) {
+ unmap_index(data);
+ if (ftruncate(data->indexfd, expected) < 0)
+ syswarn("tradindexed: cannot truncate %s.IDX", data->path);
+ if (!map_index(data))
+ goto end;
+ }
+ }
+
+ /* Now iterate through all of the index entries. In addition to checking
+ each one individually, also count the number of valid entries to check
+ the count in the index and verify that the low water mark is
+ correct. */
+ for (current = 0, count = 0; current < entries; current++) {
+ entry = &data->index[current];
+ if (entry->length == 0)
+ continue;
+ entry_audit(data, entry, group, index->base + current, fix);
+ if (entry->length != 0) {
+ if (low == 0)
+ low = index->base + current;
+ count++;
+ }
+ }
+ if (index->low != low && entries != 0) {
+ warn("tradindexed: low water mark incorrect for %s: %lu != %lu",
+ group, low, index->low);
+ if (fix) {
+ index->low = low;
+ changed = true;
+ }
+ }
+ if (index->count != count) {
+ warn("tradindexed: count incorrect for %s: %lu != %lu", group,
+ (unsigned long) count, (unsigned long) index->count);
+ if (fix) {
+ index->count = count;
+ changed = true;
+ }
+ }
+
+ /* All done. Close things down and flush the data we changed, if
+ necessary. */
+ if (changed)
+ inn_mapcntl(index, sizeof(*index), MS_ASYNC);
+
+ end:
+ tdx_data_close(data);
+}
--- /dev/null
+/* $Id: tdx-group.c 7598 2007-02-09 02:40:51Z eagle $
+**
+** Group index handling for the tradindexed overview method.
+**
+** Implements the handling of the group.index file for the tradindexed
+** overview method. This file contains an entry for every group and stores
+** the high and low article marks and the base article numbers for each
+** individual group index file.
+**
+** Externally visible functions have a tdx_ prefix; internal functions do
+** not. (Externally visible unfortunately means everything that needs to be
+** visible outside of this object file, not just interfaces exported to
+** consumers of the overview API.)
+**
+** This code has to support readers and writers sharing the same files, and
+** we want to avoid locking where possible since locking may be very slow
+** (such as over NFS). Each group has two data files (and one has to get the
+** right index file for a given data file or get mangled results) and one
+** piece of data in the main index file required to interpret the individual
+** index file, namely the article base of that index.
+**
+** We can make the following assumptions:
+**
+** - The high water mark for a group is monotonically increasing; in other
+** words, the highest numbered article in a group won't ever decrease.
+**
+** - While the article base may either increase or decrease, it will never
+** change unless the inode of the index file on disk also changes, since
+** changing the base requires rewriting the index file.
+**
+** - No two files will have the same inode (this requirement should be safe
+** even in strange Unix file formats, since the files are all in the same
+** directory).
+**
+** We therefore use the following procedure to update the data: The high
+** water mark may be changed at any time but surrounded in a write lock. The
+** base may only be changed as part of an index rebuild. To do an index
+** rebuild, we follow the following procedure:
+**
+** 1) Obtain a write lock on the group entry in the main index.
+** 2) Write out new index and data files to new temporary file names.
+** 3) Store the new index inode into the main index.
+** 4) Update the high, low, and base article numbers in the main index.
+** 5) Rename the data file to its correct name.
+** 6) Rename the index file to its correct name.
+** 7) Release the write lock.
+**
+** We use the following procedure to read the data:
+**
+** 1) Open the group data files (both index and data).
+** 2) Store copies of the current high water mark and base in variables.
+** 3) Check to be sure the index inode matches the master index file.
+**
+** If it does match, then we have a consistent set of data, since the high
+** water mark and base values have to match the index we have (the inode
+** value is updated first). It may not be the most current set of data, but
+** since we have those index and data files open, even if they're later
+** rebuilt we'll continue looking at the same files. They may have further
+** data appended to them, but that's safe.
+**
+** If the index inode doesn't match, someone's rebuilt the file while we were
+** trying to open it. Continue with the following procedure:
+**
+** 4) Close the data files that we opened.
+** 5) Obtain a read lock on the group entry in the main index.
+** 6) Reopen the data files.
+** 7) Grab the current high water mark and base.
+** 8) Release the read lock.
+**
+** In other words, if there appears to be contention, we fall back to using
+** locking so that we don't try to loop (which also avoids an infinite loop
+** in the event of corruption of the main index).
+**
+** Note that once we have a consistent set of data files open, we don't need
+** to aggressively check for new data files until someone asks for an article
+** outside the range of articles that we know about. We may be working from
+** outdated data files, but the most we'll miss is a cancel or an expiration
+** run. Overview data doesn't change; new data is appended and old data is
+** expired. We can afford to check only every once in a while, just to be
+** sure that we're not going to hand out overview data for a bunch of expired
+** articles.
+*/
+
+#include "config.h"
+#include "clibrary.h"
+#include "portable/mmap.h"
+#include <errno.h>
+#include <fcntl.h>
+#include <limits.h>
+#include <sys/stat.h>
+#include <time.h>
+
+#include "inn/hashtab.h"
+#include "inn/innconf.h"
+#include "inn/messages.h"
+#include "inn/mmap.h"
+#include "inn/qio.h"
+#include "inn/vector.h"
+#include "libinn.h"
+#include "paths.h"
+#include "tdx-private.h"
+#include "tdx-structure.h"
+
+/* Returned to callers as an opaque data type, this stashes all of the
+ information about an open group.index file. */
+struct group_index {
+ char *path;
+ int fd;
+ bool writable;
+ struct group_header *header;
+ struct group_entry *entries;
+ int count;
+};
+
+/* Forward declaration. */
+struct hashmap;
+
+/* Internal prototypes. */
+static int index_entry_count(size_t size);
+static size_t index_file_size(int count);
+static bool index_lock(int fd, enum inn_locktype type);
+static bool index_lock_group(int fd, ptrdiff_t offset, enum inn_locktype);
+static bool index_map(struct group_index *);
+static bool index_maybe_remap(struct group_index *, long loc);
+static void index_unmap(struct group_index *);
+static bool index_expand(struct group_index *);
+static long index_find(struct group_index *, const char *group);
+
+
+/*
+** Given a file size, return the number of group entries that it contains.
+*/
+static int
+index_entry_count(size_t size)
+{
+ return (size - sizeof(struct group_header)) / sizeof(struct group_entry);
+}
+
+
+/*
+** Given a number of group entries, return the required file size.
+*/
+static size_t
+index_file_size(int count)
+{
+ return sizeof(struct group_header) + count * sizeof(struct group_entry);
+}
+
+
+/*
+** Lock the hash table for the group index, used to acquire global locks on
+** the group index when updating it.
+*/
+static bool
+index_lock(int fd, enum inn_locktype type)
+{
+ bool status;
+
+ status = inn_lock_range(fd, type, true, 0, sizeof(struct group_header));
+ if (!status)
+ syswarn("tradindexed: cannot %s index hash table",
+ (type == INN_LOCK_UNLOCK) ? "unlock" : "lock");
+ return status;
+}
+
+
+/*
+** Lock the group entry for a particular group. Takes the offset of that
+** group entry from the start of the group entries (not the start of the
+** file; we have to add the size of the group header). Used for coordinating
+** updates of the data for a group.
+*/
+static bool
+index_lock_group(int fd, ptrdiff_t offset, enum inn_locktype type)
+{
+ bool status;
+ size_t size;
+
+ size = sizeof(struct group_entry);
+ offset = offset * size + sizeof(struct group_header);
+ status = inn_lock_range(fd, type, true, offset, size);
+ if (!status)
+ syswarn("tradindexed: cannot %s group entry at %lu",
+ (type == INN_LOCK_UNLOCK) ? "unlock" : "lock",
+ (unsigned long) offset);
+ return status;
+}
+
+
+/*
+** Memory map (or read into memory) the key portions of the group.index
+** file. Takes a struct group_index to fill in and returns true on success
+** and false on failure.
+*/
+static bool
+index_map(struct group_index *index)
+{
+ if (!innconf->tradindexedmmap && index->writable) {
+ warn("tradindexed: cannot open for writing without mmap");
+ return false;
+ }
+
+ if (!innconf->tradindexedmmap) {
+ ssize_t header_size;
+ ssize_t entry_size;
+
+ header_size = sizeof(struct group_header);
+ entry_size = index->count * sizeof(struct group_entry);
+ index->header = xmalloc(header_size);
+ index->entries = xmalloc(entry_size);
+ if (read(index->fd, index->header, header_size) != header_size) {
+ syswarn("tradindexed: cannot read header from %s", index->path);
+ goto fail;
+ }
+ if (read(index->fd, index->entries, entry_size) != entry_size) {
+ syswarn("tradindexed: cannot read entries from %s", index->path);
+ goto fail;
+ }
+ return true;
+
+ fail:
+ free(index->header);
+ free(index->entries);
+ index->header = NULL;
+ index->entries = NULL;
+ return false;
+
+ } else {
+ char *data;
+ size_t size;
+ int flag = PROT_READ;
+
+ if (index->writable)
+ flag = PROT_READ | PROT_WRITE;
+ size = index_file_size(index->count);
+ data = mmap(NULL, size, flag, MAP_SHARED, index->fd, 0);
+ if (data == MAP_FAILED) {
+ syswarn("tradindexed: cannot mmap %s", index->path);
+ return false;
+ }
+ index->header = (struct group_header *)(void *) data;
+ index->entries = (struct group_entry *)
+ (void *)(data + sizeof(struct group_header));
+ return true;
+ }
+}
+
+
+static bool
+file_open_group_index(struct group_index *index, struct stat *st)
+{
+ int open_mode;
+
+ index->header = NULL;
+ open_mode = index->writable ? O_RDWR | O_CREAT : O_RDONLY;
+ index->fd = open(index->path, open_mode, ARTFILE_MODE);
+ if (index->fd < 0) {
+ syswarn("tradindexed: cannot open %s", index->path);
+ goto fail;
+ }
+
+ if (fstat(index->fd, st) < 0) {
+ syswarn("tradindexed: cannot fstat %s", index->path);
+ goto fail;
+ }
+ close_on_exec(index->fd, true);
+ return true;
+
+ fail:
+ if (index->fd >= 0) {
+ close(index->fd);
+ index->fd = -1;
+ }
+ return false;
+}
+
+
+/*
+** Given a group location, remap the index file if our existing mapping isn't
+** large enough to include that group. (This can be the case when another
+** writer is appending entries to the group index.)
+*/
+static bool
+index_maybe_remap(struct group_index *index, long loc)
+{
+ struct stat st;
+ int count;
+ int r;
+
+ if (loc < index->count)
+ return true;
+
+ /* Don't remap if remapping wouldn't actually help. */
+ r = fstat(index->fd, &st);
+ if (r == -1) {
+ if (errno == ESTALE) {
+ index_unmap(index);
+ if (!file_open_group_index(index, &st))
+ return false;
+ } else {
+ syswarn("tradindexed: cannot stat %s", index->path);
+ return false;
+ }
+ }
+ count = index_entry_count(st.st_size);
+ if (count < loc && index->header != NULL)
+ return true;
+
+ /* Okay, remapping will actually help. */
+ index_unmap(index);
+ index->count = count;
+ return index_map(index);
+}
+
+
+/*
+** Unmap the index file, either in preparation for closing the overview
+** method or to get ready to remap it. We warn about failures to munmap but
+** don't do anything about them; there isn't much that we can do.
+*/
+static void
+index_unmap(struct group_index *index)
+{
+ if (index->header == NULL)
+ return;
+ if (!innconf->tradindexedmmap) {
+ free(index->header);
+ free(index->entries);
+ } else {
+ if (munmap(index->header, index_file_size(index->count)) < 0)
+ syswarn("tradindexed: cannot munmap %s", index->path);
+ }
+ index->header = NULL;
+ index->entries = NULL;
+}
+
+
+/*
+** Expand the group.index file to hold more entries; also used to build the
+** initial file. The caller is expected to lock the group index.
+*/
+static bool
+index_expand(struct group_index *index)
+{
+ int i;
+
+ index_unmap(index);
+ index->count += 1024;
+ if (ftruncate(index->fd, index_file_size(index->count)) < 0) {
+ syswarn("tradindexed: cannot expand %s", index->path);
+ return false;
+ }
+
+ /* If mapping the index fails, we've already extended it but we haven't
+ done anything with the new portion of the file. That means that it's
+ all zeroes, which means that it contains index entries who all think
+ their next entry is entry 0. We don't want to leave things in this
+ state (particularly if this was the first expansion of the index file,
+ in which case entry 0 points to entry 0 and our walking functions may
+ go into infinite loops. Undo the file expansion. */
+ if (!index_map(index)) {
+ index->count -= 1024;
+ if (ftruncate(index->fd, index_file_size(index->count)) < 0) {
+ syswarn("tradindexed: cannot shrink %s", index->path);
+ }
+ return false;
+ }
+
+ /* If the magic isn't right, assume this is a new index file. */
+ if (index->header->magic != TDX_MAGIC) {
+ index->header->magic = TDX_MAGIC;
+ index->header->freelist.recno = -1;
+ for (i = 0; i < TDX_HASH_SIZE; i++)
+ index->header->hash[i].recno = -1;
+ }
+
+ /* Walk the new entries back to front, adding them to the free list. */
+ for (i = index->count - 1; i >= index->count - 1024; i--) {
+ index->entries[i].next = index->header->freelist;
+ index->header->freelist.recno = i;
+ }
+
+ inn_mapcntl(index->header, index_file_size(index->count), MS_ASYNC);
+ return true;
+}
+
+
+/*
+** Open the group.index file and allocate a new struct for it, returning a
+** pointer to that struct. Takes a bool saying whether or not the overview
+** should be opened for write.
+*/
+struct group_index *
+tdx_index_open(bool writable)
+{
+ struct group_index *index;
+ struct stat st;
+
+ index = xmalloc(sizeof(struct group_index));
+ index->path = concatpath(innconf->pathoverview, "group.index");
+ index->writable = writable;
+ if (!file_open_group_index(index, &st)) {
+ goto fail;
+ }
+ if ((size_t) st.st_size > sizeof(struct group_header)) {
+ index->count = index_entry_count(st.st_size);
+ if (!index_map(index))
+ goto fail;
+ } else {
+ index->count = 0;
+ if (index->writable) {
+ if (st.st_size > 0)
+ warn("tradindexed: recreating truncated %s", index->path);
+ if (!index_expand(index))
+ goto fail;
+ } else {
+ index->header = NULL;
+ index->entries = NULL;
+ }
+ }
+ return index;
+
+ fail:
+ tdx_index_close(index);
+ return NULL;
+}
+
+
+/*
+** Given a group name hash, return an index into the hash table in the
+** group.index header.
+*/
+static long
+index_bucket(HASH hash)
+{
+ unsigned int bucket;
+
+ memcpy(&bucket, &hash, sizeof(bucket));
+ return bucket % TDX_HASH_SIZE;
+}
+
+
+/*
+** Given a pointer to a group entry, return its location number.
+*/
+static long
+entry_loc(const struct group_index *index, const struct group_entry *entry)
+{
+ return entry - index->entries;
+}
+
+
+/*
+** Splice out a particular group entry. Takes the entry and a pointer to the
+** location where a pointer to it is stored.
+*/
+static void
+entry_splice(struct group_entry *entry, int *parent)
+{
+ *parent = entry->next.recno;
+ entry->next.recno = -1;
+ inn_mapcntl(parent, sizeof(*parent), MS_ASYNC);
+}
+
+
+/*
+** Add a new entry to the appropriate hash chain.
+*/
+static void
+index_add(struct group_index *index, struct group_entry *entry)
+{
+ long bucket, loc;
+
+ bucket = index_bucket(entry->hash);
+ loc = entry_loc(index, entry);
+ if (loc == index->header->hash[bucket].recno) {
+ warn("tradindexed: refusing to add a loop for %ld in bucket %ld",
+ loc, bucket);
+ return;
+ }
+ entry->next.recno = index->header->hash[bucket].recno;
+ index->header->hash[bucket].recno = entry_loc(index, entry);
+ inn_mapcntl(&index->header->hash[bucket], sizeof(struct loc), MS_ASYNC);
+ inn_mapcntl(entry, sizeof(*entry), MS_ASYNC);
+}
+
+
+/*
+** Find a group in the index file, returning the group number for that group
+** or -1 if the group can't be found.
+*/
+static long
+index_find(struct group_index *index, const char *group)
+{
+ HASH hash;
+ long loc;
+
+ if (index->header == NULL || index->entries == NULL)
+ return -1;
+ hash = Hash(group, strlen(group));
+ if (innconf->nfsreader && !index_maybe_remap(index, LONG_MAX))
+ return -1;
+ loc = index->header->hash[index_bucket(hash)].recno;
+
+ while (loc >= 0 && loc < index->count) {
+ struct group_entry *entry;
+
+ if (loc > index->count && !index_maybe_remap(index, loc))
+ return -1;
+ entry = index->entries + loc;
+ if (entry->deleted == 0)
+ if (memcmp(&hash, &entry->hash, sizeof(hash)) == 0)
+ return loc;
+ if (loc == entry->next.recno) {
+ syswarn("tradindexed: index loop for entry %ld", loc);
+ return -1;
+ }
+ loc = entry->next.recno;
+ }
+ return -1;
+}
+
+
+/*
+** Add a given entry to the free list.
+*/
+static void
+freelist_add(struct group_index *index, struct group_entry *entry)
+{
+ entry->next.recno = index->header->freelist.recno;
+ index->header->freelist.recno = entry_loc(index, entry);
+ inn_mapcntl(&index->header->freelist, sizeof(struct loc), MS_ASYNC);
+ inn_mapcntl(entry, sizeof(*entry), MS_ASYNC);
+}
+
+
+/*
+** Find an entry by hash value (rather than group name) and splice it out of
+** whatever chain it might belong to. This function is called by both
+** index_unlink and index_audit_group. Locking must be done by the caller.
+** Returns the group location of the spliced group.
+*/
+static long
+index_unlink_hash(struct group_index *index, HASH hash)
+{
+ int *parent;
+ long current;
+
+ parent = &index->header->hash[index_bucket(hash)].recno;
+ current = *parent;
+
+ while (current >= 0 && current < index->count) {
+ struct group_entry *entry;
+
+ if (current > index->count && !index_maybe_remap(index, current))
+ return -1;
+ entry = &index->entries[current];
+ if (entry->deleted == 0)
+ if (memcmp(&hash, &entry->hash, sizeof(hash)) == 0) {
+ entry_splice(entry, parent);
+ return current;
+ }
+ if (current == entry->next.recno) {
+ syswarn("tradindexed: index loop for entry %ld", current);
+ return -1;
+ }
+ parent = &entry->next.recno;
+ current = *parent;
+ }
+ return -1;
+}
+
+
+/*
+** Like index_find, but also removes that entry out of whatever chain it
+** might belong to. This function is called by tdx_index_delete. Locking
+** must be done by the caller.
+*/
+static long
+index_unlink(struct group_index *index, const char *group)
+{
+ HASH hash;
+
+ hash = Hash(group, strlen(group));
+ return index_unlink_hash(index, hash);
+}
+
+
+/*
+** Return the information stored about a given group in the group index.
+*/
+struct group_entry *
+tdx_index_entry(struct group_index *index, const char *group)
+{
+ long loc;
+ struct group_entry *entry;
+
+ loc = index_find(index, group);
+ if (loc == -1)
+ return NULL;
+ entry = index->entries + loc;
+ if (innconf->tradindexedmmap && innconf->nfsreader)
+ inn_mapcntl(entry, sizeof *entry, MS_INVALIDATE);
+ return entry;
+}
+
+
+/*
+** Add a new newsgroup to the group.index file. Takes the newsgroup name,
+** its high and low water marks, and the newsgroup flag. Note that aliased
+** newsgroups are not currently handled. If the group already exists, just
+** update the flag (not the high and low water marks).
+*/
+bool
+tdx_index_add(struct group_index *index, const char *group, ARTNUM low,
+ ARTNUM high, const char *flag)
+{
+ HASH hash;
+ long loc;
+ struct group_entry *entry;
+ struct group_data *data;
+
+ if (!index->writable)
+ return false;
+
+ /* If the group already exists, update the flag as necessary and then
+ we're all done. */
+ loc = index_find(index, group);
+ if (loc != -1) {
+ entry = &index->entries[loc];
+ if (entry->flag != *flag) {
+ entry->flag = *flag;
+ inn_mapcntl(entry, sizeof(*entry), MS_ASYNC);
+ }
+ return true;
+ }
+
+ index_lock(index->fd, INN_LOCK_WRITE);
+
+ /* Find a free entry. If we don't have any free space, make some. */
+ if (index->header->freelist.recno == -1)
+ if (!index_expand(index)) {
+ index_lock(index->fd, INN_LOCK_UNLOCK);
+ return false;
+ }
+ loc = index->header->freelist.recno;
+ index->header->freelist.recno = index->entries[loc].next.recno;
+ inn_mapcntl(&index->header->freelist, sizeof(struct loc), MS_ASYNC);
+
+ /* Initialize the entry. */
+ entry = &index->entries[loc];
+ hash = Hash(group, strlen(group));
+ entry->hash = hash;
+ entry->low = (low == 0 && high != 0) ? high + 1 : low;
+ entry->high = high;
+ entry->deleted = 0;
+ entry->base = 0;
+ entry->count = 0;
+ entry->flag = *flag;
+ data = tdx_data_new(group, index->writable);
+ if (!tdx_data_open_files(data))
+ warn("tradindexed: unable to create data files for %s", group);
+ entry->indexinode = data->indexinode;
+ tdx_data_close(data);
+ index_add(index, entry);
+
+ index_lock(index->fd, INN_LOCK_UNLOCK);
+ return true;
+}
+
+
+/*
+** Delete a group index entry.
+*/
+bool
+tdx_index_delete(struct group_index *index, const char *group)
+{
+ long loc;
+ struct group_entry *entry;
+
+ if (!index->writable)
+ return false;
+
+ /* Lock the header for the entire operation, mostly as prevention against
+ interfering with ongoing audits (which lock while they're running). */
+ index_lock(index->fd, INN_LOCK_WRITE);
+
+ /* Splice out the entry and mark it as deleted. */
+ loc = index_unlink(index, group);
+ if (loc == -1) {
+ index_lock(index->fd, INN_LOCK_UNLOCK);
+ return false;
+ }
+ entry = &index->entries[loc];
+ entry->deleted = time(NULL);
+ HashClear(&entry->hash);
+
+ /* Add the entry to the free list. */
+ freelist_add(index, entry);
+ index_lock(index->fd, INN_LOCK_UNLOCK);
+
+ /* Delete the group data files for this group. */
+ tdx_data_delete(group, NULL);
+
+ return true;
+}
+
+
+/*
+** Close an open handle to the group index file, freeing the group_index
+** structure at the same time. The argument to this function becomes invalid
+** after this call.
+*/
+void
+tdx_index_close(struct group_index *index)
+{
+ index_unmap(index);
+ if (index->fd >= 0) {
+ close(index->fd);
+ index->fd = -1;
+ }
+ free(index->path);
+ free(index);
+}
+
+
+/*
+** Open the data files for a particular group. The interface to this has to
+** be in this file because we have to lock the group and retry if the inode
+** of the opened index file doesn't match the one recorded in the group index
+** file. Optionally take a pointer to the group index entry if the caller
+** has already gone to the work of finding it.
+*/
+struct group_data *
+tdx_data_open(struct group_index *index, const char *group,
+ struct group_entry *entry)
+{
+ struct group_data *data;
+ ARTNUM high, base;
+ ptrdiff_t offset;
+
+ if (entry == NULL) {
+ entry = tdx_index_entry(index, group);
+ if (entry == NULL)
+ return NULL;
+ }
+ offset = entry - index->entries;
+ data = tdx_data_new(group, index->writable);
+
+ /* Check to see if the inode of the index file matches. If it doesn't,
+ this probably means that as we were opening the index file, someone
+ else rewrote it (either expire or repack). Obtain a lock and try
+ again. If there's still a mismatch, go with what we get; there's some
+ sort of corruption.
+
+ This code is very sensitive to order and parallelism. See the comment
+ at the beginning of this file for methodology. */
+ if (!tdx_data_open_files(data))
+ goto fail;
+ high = entry->high;
+ base = entry->base;
+ if (entry->indexinode != data->indexinode) {
+ index_lock_group(index->fd, offset, INN_LOCK_READ);
+ if (!tdx_data_open_files(data)) {
+ index_lock_group(index->fd, offset, INN_LOCK_UNLOCK);
+ goto fail;
+ }
+ if (entry->indexinode != data->indexinode)
+ warn("tradindexed: index inode mismatch for %s", group);
+ high = entry->high;
+ base = entry->base;
+ index_lock_group(index->fd, offset, INN_LOCK_UNLOCK);
+ }
+ data->high = high;
+ data->base = base;
+ return data;
+
+ fail:
+ tdx_data_close(data);
+ return NULL;
+}
+
+
+/*
+** Add an overview record for a particular article. Takes the group entry,
+** the open overview data structure, and the information about the article
+** and returns true on success, false on failure. This function calls
+** tdx_data_store to do most of the real work and then updates the index
+** information.
+*/
+bool
+tdx_data_add(struct group_index *index, struct group_entry *entry,
+ struct group_data *data, const struct article *article)
+{
+ ARTNUM old_base;
+ ino_t old_inode;
+ ptrdiff_t offset = entry - index->entries;
+
+ if (!index->writable)
+ return false;
+ index_lock_group(index->fd, offset, INN_LOCK_WRITE);
+
+ /* Make sure we have the most current data files and that we have the
+ right base article number. */
+ if (entry->indexinode != data->indexinode) {
+ if (!tdx_data_open_files(data))
+ goto fail;
+ if (entry->indexinode != data->indexinode)
+ warn("tradindexed: index inode mismatch for %s",
+ HashToText(entry->hash));
+ data->base = entry->base;
+ }
+
+ /* If the article number is too low to store in the group index, repack
+ the group with a lower base index. */
+ if (entry->base > article->number) {
+ if (!tdx_data_pack_start(data, article->number))
+ goto fail;
+ old_inode = entry->indexinode;
+ old_base = entry->base;
+ entry->indexinode = data->indexinode;
+ entry->base = data->base;
+ inn_mapcntl(entry, sizeof(*entry), MS_ASYNC);
+ if (!tdx_data_pack_finish(data)) {
+ entry->base = old_base;
+ entry->indexinode = old_inode;
+ inn_mapcntl(entry, sizeof(*entry), MS_ASYNC);
+ goto fail;
+ }
+ }
+
+ /* Store the data. */
+ if (!tdx_data_store(data, article))
+ goto fail;
+ if (entry->base == 0)
+ entry->base = data->base;
+ if (entry->low == 0 || entry->low > article->number)
+ entry->low = article->number;
+ if (entry->high < article->number)
+ entry->high = article->number;
+ entry->count++;
+ inn_mapcntl(entry, sizeof(*entry), MS_ASYNC);
+ index_lock_group(index->fd, offset, INN_LOCK_UNLOCK);
+ return true;
+
+ fail:
+ index_lock_group(index->fd, offset, INN_LOCK_UNLOCK);
+ return false;
+}
+
+
+/*
+** Start a rebuild of the group data for a newsgroup. Right now, all this
+** does is lock the group index entry.
+*/
+bool
+tdx_index_rebuild_start(struct group_index *index, struct group_entry *entry)
+{
+ ptrdiff_t offset;
+
+ offset = entry - index->entries;
+ return index_lock_group(index->fd, offset, INN_LOCK_WRITE);
+}
+
+
+/*
+** Finish a rebuild of the group data for a newsgroup. Takes the old and new
+** entry and writes the data from the new entry into the group index, and
+** then unlocks it.
+*/
+bool
+tdx_index_rebuild_finish(struct group_index *index, struct group_entry *entry,
+ struct group_entry *new)
+{
+ ptrdiff_t offset;
+ ino_t new_inode;
+
+ new_inode = new->indexinode;
+ new->indexinode = entry->indexinode;
+ *entry = *new;
+ entry->indexinode = new_inode;
+ new->indexinode = new_inode;
+ inn_mapcntl(entry, sizeof(*entry), MS_ASYNC);
+ offset = entry - index->entries;
+ index_lock_group(index->fd, offset, INN_LOCK_UNLOCK);
+ return true;
+}
+
+
+/*
+** Expire a single newsgroup. Most of the work is done by tdx_data_expire*,
+** but this routine has the responsibility to do locking (the same as would
+** be done for repacking, since the group base may change) and updating the
+** group entry.
+*/
+bool
+tdx_expire(const char *group, ARTNUM *low, struct history *history)
+{
+ struct group_index *index;
+ struct group_entry *entry;
+ struct group_entry new_entry;
+ struct group_data *data = NULL;
+ ptrdiff_t offset;
+ ARTNUM old_base;
+ ino_t old_inode;
+
+ index = tdx_index_open(true);
+ if (index == NULL)
+ return false;
+ entry = tdx_index_entry(index, group);
+ if (entry == NULL) {
+ tdx_index_close(index);
+ return false;
+ }
+ tdx_index_rebuild_start(index, entry);
+
+ /* tdx_data_expire_start builds the new IDX and DAT files and fills in the
+ struct group_entry that was passed to it. tdx_data_rebuild_finish does
+ the renaming of the new files to the final file names. */
+ new_entry = *entry;
+ new_entry.low = 0;
+ new_entry.count = 0;
+ new_entry.base = 0;
+ data = tdx_data_open(index, group, entry);
+ if (data == NULL)
+ goto fail;
+ if (!tdx_data_expire_start(group, data, &new_entry, history))
+ goto fail;
+ old_inode = entry->indexinode;
+ old_base = entry->base;
+ entry->indexinode = new_entry.indexinode;
+ entry->base = new_entry.base;
+ inn_mapcntl(entry, sizeof(*entry), MS_ASYNC);
+ tdx_data_close(data);
+ if (!tdx_data_rebuild_finish(group)) {
+ entry->base = old_base;
+ entry->indexinode = old_inode;
+ inn_mapcntl(entry, sizeof(*entry), MS_ASYNC);
+ goto fail;
+ }
+
+ /* Almost done. Update the group index. If there are no articles in the
+ group, the low water mark should be one more than the high water
+ mark. */
+ if (new_entry.low == 0)
+ new_entry.low = new_entry.high + 1;
+ tdx_index_rebuild_finish(index, entry, &new_entry);
+ if (low != NULL)
+ *low = entry->low;
+ tdx_index_close(index);
+ return true;
+
+ fail:
+ offset = entry - index->entries;
+ index_lock_group(index->fd, offset, INN_LOCK_UNLOCK);
+ if (data != NULL)
+ tdx_data_close(data);
+ tdx_index_close(index);
+ return false;
+}
+
+
+/*
+** RECOVERY AND AUDITING
+**
+** All code below this point is not used in the normal operations of the
+** overview method. Instead, it's code to dump various data structures or
+** audit them for consistency, used by recovery tools and inspection tools.
+*/
+
+/* Holds a newsgroup name and its hash, used to form a hash table mapping
+ newsgroup hash values to the actual names. */
+struct hashmap {
+ HASH hash;
+ char *name;
+ char flag;
+};
+
+/* Holds information needed by hash traversal functions. Right now, this is
+ just the pointer to the group index and a flag saying whether to fix
+ problems or not. */
+struct audit_data {
+ struct group_index *index;
+ bool fix;
+};
+
+
+/*
+** Hash table functions for the mapping from group hashes to names.
+*/
+static unsigned long
+hashmap_hash(const void *entry)
+{
+ unsigned long hash;
+ const struct hashmap *group = entry;
+
+ memcpy(&hash, &group->hash, sizeof(hash));
+ return hash;
+}
+
+
+static const void *
+hashmap_key(const void *entry)
+{
+ return &((const struct hashmap *) entry)->hash;
+}
+
+
+static bool
+hashmap_equal(const void *key, const void *entry)
+{
+ const HASH *first = key;
+ const HASH *second;
+
+ second = &((const struct hashmap *) entry)->hash;
+ return memcmp(first, second, sizeof(HASH)) == 0;
+}
+
+
+static void
+hashmap_delete(void *entry)
+{
+ struct hashmap *group = entry;
+
+ free(group->name);
+ free(group);
+}
+
+
+/*
+** Construct a hash table of group hashes to group names by scanning the
+** active file. Returns the constructed hash table.
+*/
+static struct hash *
+hashmap_load(void)
+{
+ struct hash *hash;
+ QIOSTATE *active;
+ char *activepath, *line;
+ struct cvector *data = NULL;
+ struct stat st;
+ size_t hash_size;
+ struct hashmap *group;
+ HASH grouphash;
+
+ activepath = concatpath(innconf->pathdb, _PATH_ACTIVE);
+ active = QIOopen(activepath);
+ free(activepath);
+ if (active == NULL)
+ return NULL;
+ if (fstat(QIOfileno(active), &st) < 0)
+ hash_size = 32 * 1024;
+ else
+ hash_size = st.st_size / 30;
+ hash = hash_create(hash_size, hashmap_hash, hashmap_key, hashmap_equal,
+ hashmap_delete);
+
+ line = QIOread(active);
+ while (line != NULL) {
+ data = cvector_split_space(line, data);
+ if (data->count != 4) {
+ warn("tradindexed: malformed active file line %s", line);
+ continue;
+ }
+ group = xmalloc(sizeof(struct hashmap));
+ group->name = xstrdup(data->strings[0]);
+ group->flag = data->strings[3][0];
+ grouphash = Hash(group->name, strlen(group->name));
+ memcpy(&group->hash, &grouphash, sizeof(HASH));
+ hash_insert(hash, &group->hash, group);
+ line = QIOread(active);
+ }
+ cvector_free(data);
+ QIOclose(active);
+ return hash;
+}
+
+
+/*
+** Print the stored information about a single group in human-readable form
+** to stdout. The format is:
+**
+** name high low base count flag deleted inode
+**
+** all on one line. Name is passed into this function.
+*/
+void
+tdx_index_print(const char *name, const struct group_entry *entry,
+ FILE *output)
+{
+ fprintf(output, "%s %lu %lu %lu %lu %c %lu %lu\n", name, entry->high,
+ entry->low, entry->base, (unsigned long) entry->count,
+ entry->flag, (unsigned long) entry->deleted,
+ (unsigned long) entry->indexinode);
+}
+
+
+/*
+** Dump the complete contents of the group.index file in human-readable form
+** to the specified file, one line per group.
+*/
+void
+tdx_index_dump(struct group_index *index, FILE *output)
+{
+ int bucket;
+ long current;
+ struct group_entry *entry;
+ struct hash *hashmap;
+ struct hashmap *group;
+ char *name;
+
+ if (index->header == NULL || index->entries == NULL)
+ return;
+ hashmap = hashmap_load();
+ for (bucket = 0; bucket < TDX_HASH_SIZE; bucket++) {
+ current = index->header->hash[bucket].recno;
+ while (current != -1) {
+ if (!index_maybe_remap(index, current))
+ return;
+ entry = index->entries + current;
+ name = NULL;
+ if (hashmap != NULL) {
+ group = hash_lookup(hashmap, &entry->hash);
+ if (group != NULL)
+ name = group->name;
+ }
+ if (name == NULL)
+ name = HashToText(entry->hash);
+ tdx_index_print(name, entry, output);
+ if (current == entry->next.recno) {
+ warn("tradindexed: index loop for entry %ld", current);
+ return;
+ }
+ current = entry->next.recno;
+ }
+ }
+ if (hashmap != NULL)
+ hash_free(hashmap);
+}
+
+
+/*
+** Audit a particular group entry location to ensure that it points to a
+** valid entry within the group index file. Takes a pointer to the location,
+** the number of the location, a pointer to the group entry if any (if not,
+** the location is assumed to be part of the header hash table), and a flag
+** saying whether to fix problems that are found.
+*/
+static void
+index_audit_loc(struct group_index *index, int *loc, long number,
+ struct group_entry *entry, bool fix)
+{
+ bool error = false;
+
+ if (*loc >= index->count) {
+ warn("tradindexed: out of range index %d in %s %ld",
+ *loc, (entry == NULL ? "bucket" : "entry"), number);
+ error = true;
+ }
+ if (*loc < 0 && *loc != -1) {
+ warn("tradindexed: invalid negative index %d in %s %ld",
+ *loc, (entry == NULL ? "bucket" : "entry"), number);
+ error = true;
+ }
+ if (entry != NULL && *loc == number) {
+ warn("tradindexed: index loop for entry %ld", number);
+ error = true;
+ }
+
+ if (fix && error) {
+ *loc = -1;
+ inn_mapcntl(loc, sizeof(*loc), MS_ASYNC);
+ }
+}
+
+
+/*
+** Check an entry to see if it was actually deleted. Make sure that all the
+** information is consistent with a deleted group if it's not and the fix
+** flag is set.
+*/
+static void
+index_audit_deleted(struct group_entry *entry, long number, bool fix)
+{
+ if (entry->deleted != 0 && !HashEmpty(entry->hash)) {
+ warn("tradindexed: entry %ld has a delete time but a non-zero hash",
+ number);
+ if (fix) {
+ HashClear(&entry->hash);
+ inn_mapcntl(entry, sizeof(*entry), MS_ASYNC);
+ }
+ }
+}
+
+
+/*
+** Audit the group header for any inconsistencies. This checks the
+** reachability of all of the group entries, makes sure that deleted entries
+** are on the free list, and otherwise checks the linked structure of the
+** whole file. The data in individual entries is not examined. If the
+** second argument is true, also attempt to fix inconsistencies.
+*/
+static void
+index_audit_header(struct group_index *index, bool fix)
+{
+ long bucket, current;
+ struct group_entry *entry;
+ int *parent, *next;
+ bool *reachable;
+
+ /* First, walk all of the regular hash buckets, making sure that all of
+ the group location pointers are valid and sane, that all groups that
+ have been deleted are correctly marked as such, and that all groups are
+ in their correct hash chain. Build reachability information as we go,
+ used later to ensure that all group entries are reachable. */
+ reachable = xcalloc(index->count, sizeof(bool));
+ for (bucket = 0; bucket < TDX_HASH_SIZE; bucket++) {
+ parent = &index->header->hash[bucket].recno;
+ index_audit_loc(index, parent, bucket, NULL, fix);
+ current = *parent;
+ while (current >= 0 && current < index->count) {
+ entry = &index->entries[current];
+ next = &entry->next.recno;
+ if (entry->deleted == 0 && bucket != index_bucket(entry->hash)) {
+ warn("tradindexed: entry %ld is in bucket %ld instead of its"
+ " correct bucket %ld", current, bucket,
+ index_bucket(entry->hash));
+ if (fix) {
+ entry_splice(entry, parent);
+ next = parent;
+ }
+ } else {
+ if (reachable[current])
+ warn("tradindexed: entry %ld is reachable from multiple"
+ " paths", current);
+ reachable[current] = true;
+ }
+ index_audit_deleted(entry, current, fix);
+ index_audit_loc(index, &entry->next.recno, current, entry, fix);
+ if (entry->deleted != 0) {
+ warn("tradindexed: entry %ld is deleted but not in the free"
+ " list", current);
+ if (fix) {
+ entry_splice(entry, parent);
+ next = parent;
+ reachable[current] = false;
+ }
+ }
+ if (*next == current)
+ break;
+ parent = next;
+ current = *parent;
+ }
+ }
+
+ /* Now, walk the free list. Make sure that each group in the free list is
+ actually deleted, and update the reachability information. */
+ index_audit_loc(index, &index->header->freelist.recno, 0, NULL, fix);
+ parent = &index->header->freelist.recno;
+ current = *parent;
+ while (current >= 0 && current < index->count) {
+ entry = &index->entries[current];
+ index_audit_deleted(entry, current, fix);
+ reachable[current] = true;
+ if (!HashEmpty(entry->hash) && entry->deleted == 0) {
+ warn("tradindexed: undeleted entry %ld in free list", current);
+ if (fix) {
+ entry_splice(entry, parent);
+ reachable[current] = false;
+ }
+ }
+ index_audit_loc(index, &entry->next.recno, current, entry, fix);
+ if (entry->next.recno == current)
+ break;
+ parent = &entry->next.recno;
+ current = *parent;
+ }
+
+ /* Finally, check all of the unreachable entries and if fix is true, try
+ to reattach them in the appropriate location. */
+ for (current = 0; current < index->count; current++)
+ if (!reachable[current]) {
+ warn("tradindexed: unreachable entry %ld", current);
+ if (fix) {
+ entry = &index->entries[current];
+ if (!HashEmpty(entry->hash) && entry->deleted == 0)
+ index_add(index, entry);
+ else {
+ HashClear(&entry->hash);
+ entry->deleted = 0;
+ freelist_add(index, entry);
+ }
+ }
+ }
+
+ /* All done. */
+ free(reachable);
+}
+
+
+/*
+** Audit a particular group entry for any inconsistencies. This doesn't
+** check any of the structure, or whether the group is deleted, just the data
+** as stored in the group data files (mostly by calling tdx_data_audit to do
+** the real work). Note that while the low water mark may be updated, the
+** high water mark is left unchanged.
+*/
+static void
+index_audit_group(struct group_index *index, struct group_entry *entry,
+ struct hash *hashmap, bool fix)
+{
+ struct hashmap *group;
+ ptrdiff_t offset;
+
+ offset = entry - index->entries;
+ index_lock_group(index->fd, offset, INN_LOCK_WRITE);
+ group = hash_lookup(hashmap, &entry->hash);
+ if (group == NULL) {
+ warn("tradindexed: group %ld not found in active file",
+ entry_loc(index, entry));
+ if (fix) {
+ index_unlink_hash(index, entry->hash);
+ HashClear(&entry->hash);
+ entry->deleted = time(NULL);
+ freelist_add(index, entry);
+ }
+ } else {
+ if (entry->flag != group->flag) {
+ entry->flag = group->flag;
+ inn_mapcntl(entry, sizeof(*entry), MS_ASYNC);
+ }
+ tdx_data_audit(group->name, entry, fix);
+ }
+ index_lock_group(index->fd, offset, INN_LOCK_UNLOCK);
+}
+
+
+/*
+** Check to be sure that a given group exists in the overview index, and if
+** missing, adds it. Assumes that the index isn't locked, since it calls the
+** normal functions for adding new groups (this should only be called after
+** the index has already been repaired, for the same reason). Called as a
+** hash traversal function, walking the hash table of groups from the active
+** file.
+*/
+static void
+index_audit_active(void *value, void *cookie)
+{
+ struct hashmap *group = value;
+ struct audit_data *data = cookie;
+ struct group_entry *entry;
+
+ entry = tdx_index_entry(data->index, group->name);
+ if (entry == NULL) {
+ warn("tradindexed: group %s missing from overview", group->name);
+ if (data->fix)
+ tdx_index_add(data->index, group->name, 0, 0, &group->flag);
+ }
+}
+
+
+/*
+** Audit the group index for any inconsistencies. If the argument is true,
+** also attempt to fix those inconsistencies.
+*/
+void
+tdx_index_audit(bool fix)
+{
+ struct group_index *index;
+ struct stat st;
+ off_t expected;
+ int count;
+ struct hash *hashmap;
+ long bucket;
+ struct group_entry *entry;
+ struct audit_data data;
+
+ index = tdx_index_open(true);
+ if (index == NULL)
+ return;
+
+ /* Keep a lock on the header through the whole audit process. This will
+ stall any newgroups or rmgroups, but not normal article reception. We
+ don't want the structure of the group entries changing out from under
+ us, although we don't mind if the data does until we're validating that
+ particular group. */
+ index_lock(index->fd, INN_LOCK_WRITE);
+
+ /* Make sure the size looks sensible. */
+ if (fstat(index->fd, &st) < 0) {
+ syswarn("tradindexed: cannot fstat %s", index->path);
+ return;
+ }
+ count = index_entry_count(st.st_size);
+ expected = index_file_size(count);
+ if (expected != st.st_size) {
+ syswarn("tradindexed: %ld bytes of trailing trash in %s",
+ (unsigned long) (st.st_size - expected), index->path);
+ if (fix)
+ if (ftruncate(index->fd, expected) < 0)
+ syswarn("tradindexed: cannot truncate %s", index->path);
+ }
+ index_maybe_remap(index, count);
+
+ /* Okay everything is now mapped and happy. Validate the header. */
+ index_audit_header(index, fix);
+ index_lock(index->fd, INN_LOCK_UNLOCK);
+
+ /* Walk all the group entries and check them individually. To do this, we
+ need to map hashes to group names, so load a hash of the active file to
+ do that resolution. */
+ hashmap = hashmap_load();
+ data.index = index;
+ data.fix = fix;
+ hash_traverse(hashmap, index_audit_active, &data);
+ for (bucket = 0; bucket < index->count; bucket++) {
+ entry = &index->entries[bucket];
+ if (HashEmpty(entry->hash) || entry->deleted != 0)
+ continue;
+ index_audit_group(index, entry, hashmap, fix);
+ }
+ if (hashmap != NULL)
+ hash_free(hashmap);
+}
--- /dev/null
+/* $Id: tdx-private.h 5465 2002-05-06 05:46:15Z rra $
+**
+** Private APIs for the tradindexed overview method.
+*/
+
+#ifndef INN_TDX_PRIVATE_H
+#define INN_TDX_PRIVATE_H 1
+
+#include "config.h"
+#include <stdio.h>
+#include <sys/types.h>
+
+#include "libinn.h"
+#include "storage.h"
+
+/* Forward declarations to avoid unnecessary includes. */
+struct history;
+
+/* Opaque data structure used by the cache. */
+struct cache;
+
+/* Opaque data structure returned by group index functions. */
+struct group_index;
+
+/* Opaque data structure returned by search functions. */
+struct search;
+
+/* All of the information about an open set of group data files. */
+struct group_data {
+ char *path;
+ bool writable;
+ ARTNUM high;
+ ARTNUM base;
+ int indexfd;
+ int datafd;
+ struct index_entry *index;
+ char *data;
+ off_t indexlen;
+ off_t datalen;
+ ino_t indexinode;
+ int refcount;
+};
+
+/* All of the data about an article, used as the return of the search
+ functions. This is just cleaner than passing back all of the information
+ that's used by the regular interface. */
+struct article {
+ ARTNUM number;
+ const char *overview;
+ size_t overlen;
+ TOKEN token;
+ time_t arrived;
+ time_t expires;
+};
+
+BEGIN_DECLS
+
+/* tdx-cache.c */
+
+/* Create a new cache with the given number of entries. */
+struct cache *tdx_cache_create(unsigned int size);
+
+/* Look up a given newsgroup hash in the cache, returning the group_data
+ struct for its open data files if present. */
+struct group_data *tdx_cache_lookup(struct cache *, HASH);
+
+/* Insert a new group_data struct into the cache. */
+void tdx_cache_insert(struct cache *, HASH, struct group_data *);
+
+/* Delete a group entry from the cache. */
+void tdx_cache_delete(struct cache *, HASH);
+
+/* Free the cache and its resources. */
+void tdx_cache_free(struct cache *);
+
+
+/* tdx-group.c */
+
+/* Open the group index and return an opaque data structure to use for further
+ queries. */
+struct group_index *tdx_index_open(bool writable);
+
+/* Return the stored information about a single newsgroup. */
+struct group_entry *tdx_index_entry(struct group_index *, const char *group);
+
+/* Print the contents of a single group entry in human-readable form. */
+void tdx_index_print(const char *name, const struct group_entry *, FILE *);
+
+/* Add a new newsgroup to the index file. */
+bool tdx_index_add(struct group_index *, const char *group, ARTNUM low,
+ ARTNUM high, const char *flag);
+
+/* Delete a newsgroup from the index file. */
+bool tdx_index_delete(struct group_index *, const char *group);
+
+/* Dump the contents of the index file to stdout in human-readable form. */
+void tdx_index_dump(struct group_index *, FILE *);
+
+/* Audit all of the overview data, optionally trying to fix it. */
+void tdx_index_audit(bool fix);
+
+/* Close the open index file and dispose of the opaque data structure. */
+void tdx_index_close(struct group_index *);
+
+/* Open the overview information for a particular group. */
+struct group_data *tdx_data_open(struct group_index *, const char *group,
+ struct group_entry *);
+
+/* Add a new overview entry. */
+bool tdx_data_add(struct group_index *, struct group_entry *,
+ struct group_data *, const struct article *);
+
+/* Handle rebuilds of the data for a particular group. Call _start first and
+ then _finish when done, with the new group_entry information. */
+bool tdx_index_rebuild_start(struct group_index *, struct group_entry *);
+bool tdx_index_rebuild_finish(struct group_index *, struct group_entry *,
+ struct group_entry *new);
+
+/* Expire a single group. */
+bool tdx_expire(const char *group, ARTNUM *low, struct history *);
+
+
+/* tdx-data.c */
+
+/* Create a new group data structure. */
+struct group_data *tdx_data_new(const char *group, bool writable);
+
+/* Open the data files for a group. */
+bool tdx_data_open_files(struct group_data *);
+
+/* Return the metadata about a particular article in a group. */
+const struct index_entry *tdx_article_entry(struct group_data *,
+ ARTNUM article, ARTNUM high);
+
+/* Create, perform, and close a search. */
+struct search *tdx_search_open(struct group_data *, ARTNUM start, ARTNUM end,
+ ARTNUM high);
+bool tdx_search(struct search *, struct article *);
+void tdx_search_close(struct search *);
+
+/* Store article data. */
+bool tdx_data_store(struct group_data *, const struct article *);
+
+/* Start a repack of the files for a newsgroup. */
+bool tdx_data_pack_start(struct group_data *, ARTNUM);
+
+/* Complete a repack of the files for a newsgroup. */
+bool tdx_data_pack_finish(struct group_data *);
+
+/* Manage a rebuild of the data files for a particular group. Until
+ tdx_data_rebuild_finish is called, anything stored into the returned struct
+ group_data will have no effect on the data for that group. Does not handle
+ updating the index entries; that must be done separately. */
+struct group_data *tdx_data_rebuild_start(const char *group);
+bool tdx_data_rebuild_finish(const char *group);
+
+/* Start the expiration of a newsgroup and do most of the work, filling out
+ the provided group_entry struct. Complete with tdx_data_rebuild_finish. */
+bool tdx_data_expire_start(const char *group, struct group_data *,
+ struct group_entry *, struct history *);
+
+/* Dump the contents of the index file for a group. */
+void tdx_data_index_dump(struct group_data *, FILE *);
+
+/* Audit the data for a particular group, optionally trying to fix it. */
+void tdx_data_audit(const char *group, struct group_entry *, bool fix);
+
+/* Close the open data files for a group and free the structure. */
+void tdx_data_close(struct group_data *);
+
+/* Delete the data files for a group. */
+void tdx_data_delete(const char *group, const char *suffix);
+
+END_DECLS
+
+#endif /* INN_TDX_PRIVATE_H */
--- /dev/null
+/* $Id: tdx-structure.h 5327 2002-03-16 00:33:55Z rra $
+**
+** Data structures for the tradindexed overview method.
+**
+** This header defines the data structures used by the tradindexed overview
+** method. Currently, these data structures are read and written directly to
+** disk (and the disk files are therefore endian-dependent and possibly
+** architecture-dependent due to structure padding). This will eventually be
+** fixed.
+**
+** The structure of a tradindexed overview spool is as follows: At the root
+** of the spool is a group.index file composed of a struct group_header
+** followed by some number of struct group_entry's, one for each group plus
+** possibly some number of free entries linked to a free list that's headed
+** in the struct index_header. Each entry corresponds to a particular
+** newsgroup carried by the server and stores the high and low article
+** numbers for that group, its status flag, and the base of the index file
+** for each group.
+**
+** The storage of the group.index file implements a hash table with chaining;
+** in other words, there is a table indexed by hash value stored in the
+** header that points to the starts of the chains, new entries are appended
+** to the end of the file and added to the hash table, and if they collide
+** with an existing entry are instead linked to the appropriate hash chain.
+**
+** The overview information for each group is stored in a pair of files named
+** <group>.IDX and <group>.DAT. These files are found in a subdirectory
+** formed by taking the first letter of component of the newsgroup name as
+** a directory name; in other words, news.announce.newgroups overview data is
+** stored in <pathoverview>/n/a/n/news.announce.newgroups.{IDX,DAT}. The
+** .DAT file contains the individual overview entries, one per line, stored
+** in wire format (in other words, suitable for dumping directly across the
+** network to a client in response to an XOVER command). The overview data
+** stored in that file may be out of order.
+**
+** The .IDX file consists of a series of struct index_entry's, one for each
+** overview entry stored in the .DAT file. Each index entry stores the
+** offset of the data for one article in the .DAT file and its length, along
+** with some additional metainformation about the article used to drive
+** article expiration. The .IDX file is addressed like an array; the first
+** entry corresponds to the article with the number stored in the base field
+** of the group_entry for that newsgroup in the group.index file and each
+** entry stores the data for the next consecutive article. Index entries may
+** be tagged as deleted if that article has been deleted or expired.
+*/
+
+#ifndef INN_TDX_STRUCTURE_H
+#define INN_TDX_STRUCTURE_H 1
+
+#include "config.h"
+#include <sys/types.h>
+
+#include "libinn.h"
+#include "storage.h"
+
+/* A location in group.index (this many records past the end of the header of
+ the file). There's no reason for this to be a struct, but that can't be
+ changed until the format of the group.index file is changed to be
+ architecture-independent since putting it into a struct may have changed
+ the alignment or padding on some architectures. */
+struct loc {
+ int recno;
+};
+
+/* The hard-coded constant size of the hash table for group.index. This need
+ not be a power of two and has no special constraints. Changing this at
+ present will break backward compatibility with group.index files written by
+ previous versions of the code. */
+#define TDX_HASH_SIZE (16 * 1024)
+
+/* A magic number for the group.index file so that we can later change the
+ format in a backward-compatible fashion. */
+#define TDX_MAGIC (~(0xf1f0f33d))
+
+/* The header at the top of group.index. magic contains GROUPHEADERMAGIC
+ always; hash contains pointers to the heads of the entry chains, and
+ freelist points to a linked list of free entries (entries that were used
+ for groups that have since been deleted). */
+struct group_header {
+ int magic;
+ struct loc hash[TDX_HASH_SIZE];
+ struct loc freelist;
+};
+
+/* An entry for a particular group. Note that a good bit of active file
+ information is duplicated here, and depending on the portion of INN asking
+ questions, sometimes the main active file is canonical and sometimes the
+ overview data is canonical. This needs to be rethought at some point.
+
+ Groups are matched based on the MD5 hash of their name. This may prove
+ inadequate in the future. Ideally, INN really needs to assign unique
+ numbers to each group, which could then be used here as well as in
+ tradspool rather than having to do hacks like using a hash of the group
+ name or constructing one's own number to name mapping like tradspool does.
+ Unfortunately, this ideally requires a non-backward-compatible change to
+ the active file format.
+
+ Several of these elements aren't used. This structure, like the others,
+ cannot be changed until the whole format of the group.index file is changed
+ since it's currently read as binary structs directly from disk. */
+struct group_entry {
+ HASH hash; /* MD5 hash of the group name. */
+ HASH alias; /* Intended to point to the group this group
+ is an alias for. Not currently used. */
+ ARTNUM high; /* High article number in the group. */
+ ARTNUM low; /* Low article number in the group. */
+ ARTNUM base; /* Article number of the first entry in the
+ .IDX index file for the group. */
+ int count; /* Number of articles in group. */
+ int flag; /* Posting/moderation status. */
+ time_t deleted; /* When this group was deleted, or 0 if the
+ group is still valid. */
+ ino_t indexinode; /* The inode of the index file for the group,
+ used to detect when the file has been
+ recreated and swapped out. */
+ struct loc next; /* Next block in this chain. */
+};
+
+/* An entry in the per-group .IDX index file. */
+struct index_entry {
+ off_t offset;
+ int length;
+ time_t arrived;
+ time_t expires; /* Expiration time from Expires: header. */
+ TOKEN token;
+};
+
+#endif /* INN_TDX_STRUCTURE_H */
--- /dev/null
+/* $Id: tdx-util.c 6289 2003-04-09 04:16:16Z rra $
+**
+** Utility for managing a tradindexed overview spool.
+**
+** This utility can manipulate a tradindexed overview spool in various ways,
+** including some ways that are useful for recovery from crashes. It allows
+** the user to view the contents of the various data structures that
+** tradindexed stores on disk.
+*/
+
+#include "config.h"
+#include "clibrary.h"
+#include <ctype.h>
+#include <dirent.h>
+#include <pwd.h>
+#include <sys/stat.h>
+
+#include "inn/buffer.h"
+#include "inn/history.h"
+#include "inn/innconf.h"
+#include "inn/messages.h"
+#include "inn/vector.h"
+#include "libinn.h"
+#include "ov.h"
+#include "ovinterface.h"
+#include "paths.h"
+#include "tdx-private.h"
+#include "tdx-structure.h"
+
+/*
+** Dump the main index data, either all of it or that for a particular group
+** if the group argument is non-NULL.
+*/
+static void
+dump_index(const char *group)
+{
+ struct group_index *index;
+
+ index = tdx_index_open(OV_READ);
+ if (index == NULL)
+ return;
+ if (group == NULL)
+ tdx_index_dump(index, stdout);
+ else {
+ const struct group_entry *entry;
+
+ entry = tdx_index_entry(index, group);
+ if (entry == NULL) {
+ warn("cannot find group %s", group);
+ return;
+ }
+ tdx_index_print(group, entry, stdout);
+ }
+ tdx_index_close(index);
+}
+
+
+/*
+** Dump the data index file for a particular group.
+*/
+static void
+dump_group_index(const char *group)
+{
+ struct group_index *index;
+ struct group_entry *entry;
+ struct group_data *data;
+
+ index = tdx_index_open(OV_READ);
+ if (index == NULL)
+ return;
+ entry = tdx_index_entry(index, group);
+ if (entry == NULL) {
+ warn("cannot find group %s in the index", group);
+ return;
+ }
+ data = tdx_data_open(index, group, entry);
+ if (data == NULL) {
+ warn("cannot open group %s", group);
+ return;
+ }
+ tdx_data_index_dump(data, stdout);
+ tdx_data_close(data);
+ tdx_index_close(index);
+}
+
+
+/*
+** Dump the overview data for a particular group. If number is 0, dump the
+** overview data for all current articles; otherwise, only dump the data for
+** that particular article. Include the article number, token, arrived time,
+** and expires time (if any) in the overview data as additional fields.
+*/
+static void
+dump_overview(const char *group, ARTNUM number)
+{
+ struct group_index *index;
+ struct group_data *data;
+ struct group_entry *entry;
+ struct article article;
+ struct search *search;
+ char datestring[256];
+
+ index = tdx_index_open(OV_READ);
+ if (index == NULL)
+ return;
+ entry = tdx_index_entry(index, group);
+ if (entry == NULL) {
+ warn("cannot find group %s", group);
+ return;
+ }
+ data = tdx_data_open(index, group, entry);
+ if (data == NULL) {
+ warn("cannot open group %s", group);
+ return;
+ }
+ data->refcount++;
+
+ if (number != 0)
+ search = tdx_search_open(data, number, number, entry->high);
+ else
+ search = tdx_search_open(data, entry->low, entry->high, entry->high);
+ if (search == NULL) {
+ if (number != 0)
+ puts("Article not found");
+ else
+ warn("cannot open search in %s: %lu - %lu", group, entry->low,
+ entry->high);
+ return;
+ }
+ while (tdx_search(search, &article)) {
+ fwrite(article.overview, article.overlen - 2, 1, stdout);
+ printf("\tArticle: %lu\tToken: %s", article.number,
+ TokenToText(article.token));
+ makedate(article.arrived, true, datestring, sizeof(datestring));
+ printf("\tArrived: %s", datestring);
+ if (article.expires != 0) {
+ makedate(article.expires, true, datestring, sizeof(datestring));
+ printf("\tExpires: %s", datestring);
+ }
+ printf("\n");
+ }
+ tdx_search_close(search);
+ tdx_data_close(data);
+ tdx_index_close(index);
+}
+
+
+/*
+** Check a string to see if its a valid number.
+*/
+static bool
+check_number(const char *string)
+{
+ const char *p;
+
+ for (p = string; *p != '\0'; p++)
+ if (!CTYPE(isdigit, *p))
+ return false;
+ return true;
+}
+
+
+/*
+** Find the message ID in the group overview data and return a copy of it.
+** Caller is responsible for freeing.
+*/
+static char *
+extract_messageid(const char *overview)
+{
+ const char *p, *end;
+ int count;
+
+ for (p = overview, count = 0; count < 4; count++) {
+ p = strchr(p + 1, '\t');
+ if (p == NULL)
+ return NULL;
+ }
+ p++;
+ end = strchr(p, '\t');
+ if (end == NULL)
+ return NULL;
+ return xstrndup(p, end - p);
+}
+
+
+/*
+** Compare two file names assuming they're numbers, used to sort the list of
+** articles numerically. Suitable for use as a comparison function for
+** qsort.
+*/
+static int
+file_compare(const void *p1, const void *p2)
+{
+ const char *file1 = *((const char * const *) p1);
+ const char *file2 = *((const char * const *) p2);
+ ARTNUM n1, n2;
+
+ n1 = strtoul(file1, NULL, 10);
+ n2 = strtoul(file2, NULL, 10);
+ if (n1 > n2)
+ return 1;
+ else if (n1 < n2)
+ return -1;
+ else
+ return 0;
+}
+
+
+/*
+** Get a list of articles in a directory, sorted by article number.
+*/
+static struct vector *
+article_list(const char *directory)
+{
+ DIR *articles;
+ struct dirent *file;
+ struct vector *list;
+
+ list = vector_new();
+ articles = opendir(directory);
+ if (articles == NULL)
+ sysdie("cannot open directory %s", directory);
+ while ((file = readdir(articles)) != NULL) {
+ if (!check_number(file->d_name))
+ continue;
+ vector_add(list, file->d_name);
+ }
+ closedir(articles);
+
+ qsort(list->strings, list->count, sizeof(list->strings[0]), file_compare);
+ return list;
+}
+
+
+/*
+** Rebuild the overview data for a particular group. Takes a path to a
+** directory containing all the articles, as individual files, that should be
+** in that group. The names of the files should be the article numbers in
+** the group.
+*/
+static void
+group_rebuild(const char *group, const char *path)
+{
+ char *filename, *histpath, *article, *wireformat, *p;
+ size_t size, file;
+ int flags, length;
+ struct buffer *overview = NULL;
+ struct vector *extra, *files;
+ struct history *history;
+ struct group_index *index;
+ struct group_data *data;
+ struct group_entry *entry, info;
+ struct article artdata;
+ struct stat st;
+
+ index = tdx_index_open(OV_READ);
+ if (index == NULL)
+ die("cannot open group index");
+ entry = tdx_index_entry(index, group);
+ if (entry == NULL) {
+ if (!tdx_index_add(index, group, 1, 0, "y"))
+ die("cannot create group %s", group);
+ entry = tdx_index_entry(index, group);
+ if (entry == NULL)
+ die("cannot find group %s", group);
+ }
+ info = *entry;
+ data = tdx_data_rebuild_start(group);
+ if (data == NULL)
+ die("cannot start data rebuild for %s", group);
+ if (!tdx_index_rebuild_start(index, entry))
+ die("cannot start index rebuild for %s", group);
+
+ histpath = concatpath(innconf->pathdb, _PATH_HISTORY);
+ flags = HIS_RDONLY | HIS_ONDISK;
+ history = HISopen(histpath, innconf->hismethod, flags);
+ if (history == NULL)
+ sysdie("cannot open history %s", histpath);
+ free(histpath);
+
+ extra = overview_extra_fields();
+ files = article_list(path);
+
+ info.count = 0;
+ info.high = 0;
+ info.low = 0;
+ for (file = 0; file < files->count; file++) {
+ filename = concatpath(path, files->strings[file]);
+ article = ReadInFile(filename, &st);
+ size = st.st_size;
+ if (article == NULL) {
+ syswarn("cannot read in %s", filename);
+ free(filename);
+ continue;
+ }
+
+ /* Check to see if the article is not in wire format. If it isn't,
+ convert it. We only check the first line ending. */
+ p = strchr(article, '\n');
+ if (p != NULL && (p == article || p[-1] != '\r')) {
+ wireformat = ToWireFmt(article, size, (size_t *)&length);
+ free(article);
+ article = wireformat;
+ size = length;
+ }
+
+ artdata.number = strtoul(files->strings[file], NULL, 10);
+ if (artdata.number > info.high)
+ info.high = artdata.number;
+ if (artdata.number < info.low || info.low == 0)
+ info.low = artdata.number;
+ info.count++;
+ overview = overview_build(artdata.number, article, size, extra,
+ overview);
+ artdata.overview = overview->data;
+ artdata.overlen = overview->left;
+ p = extract_messageid(overview->data);
+ if (p == NULL) {
+ warn("cannot find message ID in %s", filename);
+ free(filename);
+ free(article);
+ continue;
+ }
+ if (HISlookup(history, p, &artdata.arrived, NULL, &artdata.expires,
+ &artdata.token)) {
+ if (!tdx_data_store(data, &artdata))
+ warn("cannot store data for %s", filename);
+ } else {
+ warn("cannot find article %s in history", p);
+ }
+ free(p);
+ free(filename);
+ free(article);
+ }
+ vector_free(files);
+ vector_free(extra);
+
+ info.indexinode = data->indexinode;
+ info.base = data->base;
+ if (!tdx_index_rebuild_finish(index, entry, &info))
+ die("cannot update group index for %s", group);
+ if (!tdx_data_rebuild_finish(group))
+ die("cannot finish rebuilding data for group %s", group);
+ tdx_data_close(data);
+ HISclose(history);
+}
+
+
+/*
+** Change to the news user if possible, and if not, die. Used for operations
+** that may change the overview files so as not to mess up the ownership.
+*/
+static void
+setuid_news(void)
+{
+ struct passwd *pwd;
+
+ pwd = getpwnam(NEWSUSER);
+ if (pwd == NULL)
+ die("can't resolve %s to a UID (account doesn't exist?)", NEWSUSER);
+ if (getuid() == 0)
+ setuid(pwd->pw_uid);
+ if (getuid() != pwd->pw_uid)
+ die("must be run as %s", NEWSUSER);
+}
+
+
+/*
+** Main routine. Load inn.conf, parse the arguments, and dispatch to the
+** appropriate function.
+*/
+int
+main(int argc, char *argv[])
+{
+ int option;
+ char mode = '\0';
+ const char *newsgroup = NULL;
+ const char *path = NULL;
+ ARTNUM article = 0;
+
+ message_program_name = "tdx-util";
+
+ if (!innconf_read(NULL))
+ exit(1);
+
+ /* Parse options. */
+ opterr = 0;
+ while ((option = getopt(argc, argv, "a:n:p:AFR:gio")) != EOF) {
+ switch (option) {
+ case 'a':
+ article = strtoul(optarg, NULL, 10);
+ if (article == 0)
+ die("invalid article number %s", optarg);
+ break;
+ case 'n':
+ newsgroup = optarg;
+ break;
+ case 'p':
+ innconf->pathoverview = xstrdup(optarg);
+ break;
+ case 'A':
+ if (mode != '\0')
+ die("only one mode option allowed");
+ mode = 'A';
+ break;
+ case 'F':
+ if (mode != '\0')
+ die("only one mode option allowed");
+ mode = 'F';
+ break;
+ case 'R':
+ if (mode != '\0')
+ die("only one mode option allowed");
+ mode = 'R';
+ path = optarg;
+ break;
+ case 'g':
+ if (mode != '\0')
+ die("only one mode option allowed");
+ mode = 'g';
+ break;
+ case 'i':
+ if (mode != '\0')
+ die("only one mode option allowed");
+ mode = 'i';
+ break;
+ case 'o':
+ if (mode != '\0')
+ die("only one mode option allowed");
+ mode = 'o';
+ break;
+ default:
+ die("invalid option %c", optopt);
+ break;
+ }
+ }
+
+ /* Modes g and o require a group be specified. */
+ if ((mode == 'g' || mode == 'o' || mode == 'R') && newsgroup == NULL)
+ die("group must be specified for -%c", mode);
+
+ /* Run the specified function. */
+ switch (mode) {
+ case 'A':
+ tdx_index_audit(false);
+ break;
+ case 'F':
+ setuid_news();
+ tdx_index_audit(true);
+ break;
+ case 'R':
+ setuid_news();
+ group_rebuild(newsgroup, path);
+ break;
+ case 'i':
+ dump_index(newsgroup);
+ break;
+ case 'g':
+ dump_group_index(newsgroup);
+ break;
+ case 'o':
+ dump_overview(newsgroup, article);
+ break;
+ default:
+ die("a mode option must be specified");
+ break;
+ }
+ exit(0);
+}
--- /dev/null
+/* $Id: tradindexed.c 7138 2005-03-16 18:15:51Z hkehoe $
+**
+** Interface implementation for the tradindexed overview method.
+**
+** This code converts between the internal interface used by the tradindexed
+** implementation and the interface expected by the INN overview API. The
+** internal interface is in some cases better suited to the data structures
+** that the tradindexed overview method uses, and this way the internal
+** interface can be kept isolated from the external interface. (There are
+** also some operations that can be performed entirely in the interface
+** layer.)
+*/
+
+#include "config.h"
+#include "clibrary.h"
+
+#include "inn/innconf.h"
+#include "inn/messages.h"
+#include "libinn.h"
+#include "ov.h"
+#include "storage.h"
+#include "tdx-private.h"
+#include "tdx-structure.h"
+#include "tradindexed.h"
+
+/* This structure holds all of the data about the open overview files. We can
+ eventually pass one of these structures back to the caller of open when the
+ overview API is more object-oriented. */
+struct tradindexed {
+ struct group_index *index;
+ struct cache *cache;
+ bool cutoff;
+};
+
+/* Global data about the open tradindexed method. */
+static struct tradindexed *tradindexed;
+
+
+/*
+** Helper function to open a group_data structure via the cache, inserting it
+** into the cache if it wasn't found in the cache.
+*/
+static struct group_data *
+data_cache_open(struct tradindexed *global, const char *group,
+ struct group_entry *entry)
+{
+ struct group_data *data;
+
+ data = tdx_cache_lookup(global->cache, entry->hash);
+ if (data == NULL) {
+ data = tdx_data_open(global->index, group, entry);
+ if (data == NULL)
+ return NULL;
+ tdx_cache_insert(global->cache, entry->hash, data);
+ }
+ return data;
+}
+
+
+/*
+** Helper function to reopen the data files and remove the old entry from the
+** cache if we think that might help better fulfill a search.
+*/
+static struct group_data *
+data_cache_reopen(struct tradindexed *global, const char *group,
+ struct group_entry *entry)
+{
+ struct group_data *data;
+
+ tdx_cache_delete(global->cache, entry->hash);
+ data = tdx_data_open(global->index, group, entry);
+ if (data == NULL)
+ return NULL;
+ tdx_cache_insert(global->cache, entry->hash, data);
+ return data;
+}
+
+
+/*
+** Open the overview method.
+*/
+bool
+tradindexed_open(int mode)
+{
+ unsigned int cache_size, fdlimit;
+
+ if (tradindexed != NULL) {
+ warn("tradindexed: overview method already open");
+ return false;
+ }
+ tradindexed = xmalloc(sizeof(struct tradindexed));
+ tradindexed->index = tdx_index_open((mode & OV_WRITE) ? true : false);
+ tradindexed->cutoff = false;
+
+ /* Use a cache size of two for read-only connections. We may want to
+ rethink the limitation of the cache for reading later based on
+ real-world experience. */
+ cache_size = (mode & OV_WRITE) ? innconf->overcachesize : 1;
+ fdlimit = getfdlimit();
+ if (fdlimit > 0 && fdlimit < cache_size * 2) {
+ warn("tradindexed: not enough file descriptors for an overview cache"
+ " size of %u; increase rlimitnofile or decrease overcachesize"
+ " to at most %u", cache_size, fdlimit / 2);
+ cache_size = (fdlimit > 2) ? fdlimit / 2 : 1;
+ }
+ tradindexed->cache = tdx_cache_create(cache_size);
+
+ return (tradindexed->index == NULL) ? false : true;
+}
+
+
+/*
+** Get statistics about a group. Convert between the multiple pointer API
+** and the structure API used internally.
+*/
+bool
+tradindexed_groupstats(char *group, int *low, int *high, int *count,
+ int *flag)
+{
+ const struct group_entry *entry;
+
+ if (tradindexed == NULL || tradindexed->index == NULL) {
+ warn("tradindexed: overview method not initialized");
+ return false;
+ }
+ entry = tdx_index_entry(tradindexed->index, group);
+ if (entry == NULL)
+ return false;
+ if (low != NULL)
+ *low = entry->low;
+ if (high != NULL)
+ *high = entry->high;
+ if (count != NULL)
+ *count = entry->count;
+ if (flag != NULL)
+ *flag = entry->flag;
+ return true;
+}
+
+
+/*
+** Add a new newsgroup to the index.
+*/
+bool
+tradindexed_groupadd(char *group, ARTNUM low, ARTNUM high, char *flag)
+{
+ if (tradindexed == NULL || tradindexed->index == NULL) {
+ warn("tradindexed: overview method not initialized");
+ return false;
+ }
+ return tdx_index_add(tradindexed->index, group, low, high, flag);
+}
+
+
+/*
+** Delete a newsgroup from the index.
+*/
+bool
+tradindexed_groupdel(char *group)
+{
+ if (tradindexed == NULL || tradindexed->index == NULL) {
+ warn("tradindexed: overview method not initialized");
+ return false;
+ }
+ return tdx_index_delete(tradindexed->index, group);
+}
+
+
+/*
+** Add data about a single article. Convert between the multiple argument
+** API and the structure API used internally, and also implement low article
+** cutoff if that was requested.
+*/
+bool
+tradindexed_add(char *group, ARTNUM artnum, TOKEN token, char *data,
+ int length, time_t arrived, time_t expires)
+{
+ struct article article;
+ struct group_data *group_data;
+ struct group_entry *entry;
+
+ if (tradindexed == NULL || tradindexed->index == NULL) {
+ warn("tradindexed: overview method not initialized");
+ return false;
+ }
+
+ /* Get the group index entry and don't do any work if cutoff is set and
+ the article number is lower than the low water mark for the group. */
+ entry = tdx_index_entry(tradindexed->index, group);
+ if (entry == NULL)
+ return true;
+ if (tradindexed->cutoff && entry->low > artnum)
+ return true;
+
+ /* Fill out the article data structure. */
+ article.number = artnum;
+ article.overview = data;
+ article.overlen = length;
+ article.token = token;
+ article.arrived = arrived;
+ article.expires = expires;
+
+ /* Open the appropriate data structures, using the cache. */
+ group_data = data_cache_open(tradindexed, group, entry);
+ if (group_data == NULL)
+ return false;
+ return tdx_data_add(tradindexed->index, entry, group_data, &article);
+}
+
+
+/*
+** Cancel an article. At present, tradindexed can't do anything with this
+** information because we lack a mapping from the token to newsgroup names
+** and article numbers, so we just silently return true and let expiration
+** take care of this.
+*/
+bool
+tradindexed_cancel(TOKEN token UNUSED)
+{
+ return true;
+}
+
+
+/*
+** Open an overview search. Open the appropriate group and then start a
+** search in it.
+*/
+void *
+tradindexed_opensearch(char *group, int low, int high)
+{
+ struct group_entry *entry;
+ struct group_data *data;
+
+ if (tradindexed == NULL || tradindexed->index == NULL) {
+ warn("tradindexed: overview method not initialized");
+ return NULL;
+ }
+ entry = tdx_index_entry(tradindexed->index, group);
+ if (entry == NULL)
+ return NULL;
+ data = data_cache_open(tradindexed, group, entry);
+ if (data == NULL)
+ return NULL;
+ if (entry->base != data->base)
+ if (data->base > (ARTNUM) low && entry->base < data->base) {
+ data = data_cache_reopen(tradindexed, group, entry);
+ if (data == NULL)
+ return NULL;
+ }
+ return tdx_search_open(data, low, high, entry->high);
+}
+
+
+/*
+** Get the next article returned by a search. Convert between the multiple
+** pointer API and the structure API we use internally.
+*/
+bool
+tradindexed_search(void *handle, ARTNUM *artnum, char **data, int *length,
+ TOKEN *token, time_t *arrived)
+{
+ struct article article;
+
+ if (tradindexed == NULL || tradindexed->index == NULL) {
+ warn("tradindexed: overview method not initialized");
+ return false;
+ }
+ if (!tdx_search(handle, &article))
+ return false;
+ if (artnum != NULL)
+ *artnum = article.number;
+ if (data != NULL)
+ *data = (char *) article.overview;
+ if (length != NULL)
+ *length = article.overlen;
+ if (token != NULL)
+ *token = article.token;
+ if (arrived != NULL)
+ *arrived = article.arrived;
+ return true;
+}
+
+
+/*
+** Close an overview search.
+*/
+void
+tradindexed_closesearch(void *handle)
+{
+ tdx_search_close(handle);
+}
+
+
+/*
+** Get information for a single article. Open the appropriate group and then
+** convert from the pointer API to the struct API used internally.
+*/
+bool
+tradindexed_getartinfo(char *group, ARTNUM artnum, TOKEN *token)
+{
+ struct group_entry *entry;
+ struct group_data *data;
+ const struct index_entry *index_entry;
+
+ if (tradindexed == NULL || tradindexed->index == NULL) {
+ warn("tradindexed: overview method not initialized");
+ return false;
+ }
+ entry = tdx_index_entry(tradindexed->index, group);
+ if (entry == NULL)
+ return false;
+ data = data_cache_open(tradindexed, group, entry);
+ if (data == NULL)
+ return false;
+ if (entry->base != data->base)
+ if (data->base > artnum && entry->base <= artnum) {
+ data = data_cache_reopen(tradindexed, group, entry);
+ if (data == NULL)
+ return false;
+ }
+ index_entry = tdx_article_entry(data, artnum, entry->high);
+ if (index_entry == NULL)
+ return false;
+ if (token != NULL)
+ *token = index_entry->token;
+ return true;
+}
+
+
+/*
+** Expire a single newsgroup.
+*/
+bool
+tradindexed_expiregroup(char *group, int *low, struct history *history)
+{
+ ARTNUM new_low;
+ bool status;
+
+ /* tradindexed doesn't have any periodic cleanup. */
+ if (group == NULL)
+ return true;
+
+ status = tdx_expire(group, &new_low, history);
+ if (status && low != NULL)
+ *low = (int) new_low;
+ return status;
+}
+
+
+/*
+** Set various options or query various paramaters for the overview method.
+** The interface is, at present, not particularly sane.
+*/
+bool
+tradindexed_ctl(OVCTLTYPE type, void *val)
+{
+ int *i;
+ bool *b;
+ OVSORTTYPE *sort;
+
+ if (tradindexed == NULL) {
+ warn("tradindexed: overview method not initialized");
+ return false;
+ }
+
+ switch (type) {
+ case OVSPACE:
+ i = (int *) val;
+ *i = -1;
+ return true;
+ case OVSORT:
+ sort = (OVSORTTYPE *) val;
+ *sort = OVNEWSGROUP;
+ return true;
+ case OVCUTOFFLOW:
+ b = (bool *) val;
+ tradindexed->cutoff = *b;
+ return true;
+ case OVSTATICSEARCH:
+ i = (int *) val;
+ *i = false;
+ return true;
+ case OVCACHEKEEP:
+ case OVCACHEFREE:
+ b = (bool *) val;
+ *b = false;
+ return true;
+ default:
+ return false;
+ }
+}
+
+
+/*
+** Close the overview method.
+*/
+void
+tradindexed_close(void)
+{
+ if (tradindexed != NULL) {
+ if (tradindexed->index != NULL)
+ tdx_index_close(tradindexed->index);
+ if (tradindexed->cache != NULL)
+ tdx_cache_free(tradindexed->cache);
+ free(tradindexed);
+ tradindexed = NULL;
+ }
+}
--- /dev/null
+/* $Id: tradindexed.h 5324 2002-03-15 21:09:33Z rra $
+**
+** Public interface for the tradindexed overview method.
+**
+** The exact API specified here must match the expectations of the overview
+** API. Any changes here have to be made to all the overview methods at the
+** same time.
+*/
+
+#ifndef TRADINDEXED_H
+#define TRADINDEXED_H 1
+
+#include "config.h"
+#include <sys/types.h>
+
+#include "ov.h"
+#include "storage.h"
+
+BEGIN_DECLS
+
+bool tradindexed_open(int mode);
+bool tradindexed_groupstats(char *group, int *low, int *high, int *count,
+ int *flag);
+bool tradindexed_groupadd(char *group, ARTNUM low, ARTNUM high, char *flag);
+bool tradindexed_groupdel(char *group);
+bool tradindexed_add(char *group, ARTNUM artnum, TOKEN token, char *data,
+ int length, time_t arrived, time_t expires);
+bool tradindexed_cancel(TOKEN token);
+void *tradindexed_opensearch(char *group, int low, int high);
+bool tradindexed_search(void *handle, ARTNUM *artnum, char **data,
+ int *length, TOKEN *token, time_t *arrived);
+void tradindexed_closesearch(void *handle);
+bool tradindexed_getartinfo(char *group, ARTNUM artnum, TOKEN *token);
+bool tradindexed_expiregroup(char *group, int *low, struct history *);
+bool tradindexed_ctl(OVCTLTYPE type, void *val);
+void tradindexed_close(void);
+
+END_DECLS
+
+#endif /* TRADINDEXED_H */
--- /dev/null
+This storage manager attempts to implement the 'traditional' INN storage
+layout, i.e a message crossposted to alt.fan.james-brister and rec.pets.wombats
+will be written as a file in
+ <patharticles>/alt/fan/james-brister/nnnnn
+and a symlink pointing to the above file in
+ <patharticles>/rec/pets/wombats/mmmmmm
+where nnnnn and mmmmmm are the article numbers that article has in each of
+those two newsgroups. (Actually in the traditional spool form the link
+could be either a symlink or a regular link).
+
+The storage token data for a tradspool stored article is a 16-byte block.
+The storage token contains two ints; the first one is a number
+telling what the name of the "primary" newsgroup is for this article, the
+second one telling what article number the article has in that newsgroup.
+The mapping between newsgroup name and number is given by a database in
+the file
+ <pathspool>/ts.ng.db
+; this file is a straight ASCII file listing newsgroup names and
+numbers, and will be automatically generated by innd if one does not
+exist already. This database is read in automatically by any program
+that uses this storage manager module, and is updated by innd whenever
+a new newsgroup is encountered. Other programs (like innfeed) check
+the mod time of that database every 5 minutes to see if they need to
+recheck it for any new newsgroups that might have been added. Should
+the database become corrupted, simply shutting down news, removing the
+database, and doing a makehistory will recreate the database. It
+should, in principle, be possible to write a perl script to recreate
+just the database from just the spool files and history files without
+doing a full makehistory.
+
+Currently the storage manager code works, although not perhaps as fast
+as it could. The expiration code is somewhat unwieldy; since the storage
+token does not have enough space to hold all the newsgroups an article
+is posted to, when expiration is done SMCancel() has to open the article
+to find out what other newsgroups the article is posted to. Eurggh.
+Suggestions for a better scheme are welcome.
+
+Other problems of note: the storage manager code has no way to get to the
+'DoLinks' (-L) flag setting of innd, so currently you can't use the
+"crosspost" program with tradspool. I guess the proper thing to do would be
+to make DoLinks a config option in storage.conf instead, but I haven't
+done that yet.
+
--- /dev/null
+name = tradspool
+number = 5
+sources = tradspool.c
--- /dev/null
+/* $Id: tradspool.c 7412 2005-10-09 03:44:35Z eagle $
+**
+** Storage manager module for traditional spool format.
+*/
+
+#include "config.h"
+#include "clibrary.h"
+#include "portable/mmap.h"
+#include <ctype.h>
+#include <dirent.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <syslog.h>
+#include <sys/stat.h>
+#include <sys/uio.h>
+#include <time.h>
+
+/* Needed for htonl() and friends on AIX 4.1. */
+#include <netinet/in.h>
+
+#include "inn/innconf.h"
+#include "inn/qio.h"
+#include "inn/wire.h"
+#include "libinn.h"
+#include "paths.h"
+
+#include "methods.h"
+#include "tradspool.h"
+
+typedef struct {
+ char *artbase; /* start of the article data -- may be mmaped */
+ unsigned int artlen; /* art length. */
+ int nextindex;
+ char *curdirname;
+ DIR *curdir;
+ struct _ngtent *ngtp;
+ bool mmapped;
+} PRIV_TRADSPOOL;
+
+/*
+** The 64-bit hashed representation of a ng name that gets stashed in each token.
+*/
+
+#define HASHEDNGLEN 8
+typedef struct {
+ char hash[HASHEDNGLEN];
+} HASHEDNG;
+
+/*
+** We have two structures here for facilitating newsgroup name->number mapping
+** and number->name mapping. NGTable is a hash table based on hashing the
+** newsgroup name, and is used to give the name->number mapping. NGTree is
+** a binary tree, indexed by newsgroup number, used for the number->name
+** mapping.
+*/
+
+#define NGT_SIZE 2048
+
+typedef struct _ngtent {
+ char *ngname;
+/* HASHEDNG hash; XXX */
+ unsigned long ngnumber;
+ struct _ngtent *next;
+ struct _ngtreenode *node;
+} NGTENT;
+
+typedef struct _ngtreenode {
+ unsigned long ngnumber;
+ struct _ngtreenode *left, *right;
+ NGTENT *ngtp;
+} NGTREENODE;
+
+NGTENT *NGTable[NGT_SIZE];
+unsigned long MaxNgNumber = 0;
+NGTREENODE *NGTree;
+
+bool NGTableUpdated; /* set to true if we've added any entries since reading
+ in the database file */
+
+/*
+** Convert all .s to /s in a newsgroup name. Modifies the passed string
+** inplace.
+*/
+static void
+DeDotify(char *ngname) {
+ char *p = ngname;
+
+ for ( ; *p ; ++p) {
+ if (*p == '.') *p = '/';
+ }
+ return;
+}
+
+/*
+** Hash a newsgroup name to an 8-byte. Basically, we convert all .s to
+** /s (so it doesn't matter if we're passed the spooldir name or newsgroup
+** name) and then call Hash to MD5 the mess, then take 4 bytes worth of
+** data from the front of the hash. This should be good enough for our
+** purposes.
+*/
+
+static HASHEDNG
+HashNGName(char *ng) {
+ HASH hash;
+ HASHEDNG return_hash;
+ char *p;
+
+ p = xstrdup(ng);
+ DeDotify(p);
+ hash = Hash(p, strlen(p));
+ free(p);
+
+ memcpy(return_hash.hash, hash.hash, HASHEDNGLEN);
+
+ return return_hash;
+}
+
+#if 0 /* XXX */
+/* compare two hashes */
+static int
+CompareHash(HASHEDNG *h1, HASHEDNG *h2) {
+ int i;
+ for (i = 0 ; i < HASHEDNGLEN ; ++i) {
+ if (h1->hash[i] != h2->hash[i]) {
+ return h1->hash[i] - h2->hash[i];
+ }
+ }
+ return 0;
+}
+#endif
+
+/* Add a new newsgroup name to the NG table. */
+static void
+AddNG(char *ng, unsigned long number) {
+ char *p;
+ unsigned int h;
+ HASHEDNG hash;
+ NGTENT *ngtp, **ngtpp;
+ NGTREENODE *newnode, *curnode, **nextnode;
+
+ p = xstrdup(ng);
+ DeDotify(p); /* canonicalize p to standard (/) form. */
+ hash = HashNGName(p);
+
+ h = (unsigned char)hash.hash[0];
+ h = h + (((unsigned char)hash.hash[1])<<8);
+
+ h = h % NGT_SIZE;
+
+ ngtp = NGTable[h];
+ ngtpp = &NGTable[h];
+ while (true) {
+ if (ngtp == NULL) {
+ /* ng wasn't in table, add new entry. */
+ NGTableUpdated = true;
+
+ ngtp = xmalloc(sizeof(NGTENT));
+ ngtp->ngname = p; /* note: we store canonicalized name */
+ /* ngtp->hash = hash XXX */
+ ngtp->next = NULL;
+
+ /* assign a new NG number if needed (not given) */
+ if (number == 0) {
+ number = ++MaxNgNumber;
+ }
+ ngtp->ngnumber = number;
+
+ /* link new table entry into the hash table chain. */
+ *ngtpp = ngtp;
+
+ /* Now insert an appropriate record into the binary tree */
+ newnode = xmalloc(sizeof(NGTREENODE));
+ newnode->left = newnode->right = (NGTREENODE *) NULL;
+ newnode->ngnumber = number;
+ newnode->ngtp = ngtp;
+ ngtp->node = newnode;
+
+ if (NGTree == NULL) {
+ /* tree was empty, so put our one element in and return */
+ NGTree = newnode;
+ return;
+ } else {
+ nextnode = &NGTree;
+ while (*nextnode) {
+ curnode = *nextnode;
+ if (curnode->ngnumber < number) {
+ nextnode = &curnode->right;
+ } else if (curnode->ngnumber > number) {
+ nextnode = &curnode->left;
+ } else {
+ /* Error, same number is already in NGtree (shouldn't happen!) */
+ syslog(L_ERROR, "tradspool: AddNG: duplicate newsgroup number in NGtree: %ld(%s)", number, p);
+ return;
+ }
+ }
+ *nextnode = newnode;
+ return;
+ }
+ } else if (strcmp(ngtp->ngname, p) == 0) {
+ /* entry in table already, so return */
+ free(p);
+ return;
+#if 0 /* XXX */
+ } else if (CompareHash(&ngtp->hash, &hash) == 0) {
+ /* eep! we hit a hash collision. */
+ syslog(L_ERROR, "tradspool: AddNG: Hash collison %s/%s", ngtp->ngname, p);
+ free(p);
+ return;
+#endif
+ } else {
+ /* not found yet, so advance to next entry in chain */
+ ngtpp = &(ngtp->next);
+ ngtp = ngtp->next;
+ }
+ }
+}
+
+/* find a newsgroup table entry, given only the name. */
+static NGTENT *
+FindNGByName(char *ngname) {
+ NGTENT *ngtp;
+ unsigned int h;
+ HASHEDNG hash;
+ char *p;
+
+ p = xstrdup(ngname);
+ DeDotify(p); /* canonicalize p to standard (/) form. */
+ hash = HashNGName(p);
+
+ h = (unsigned char)hash.hash[0];
+ h = h + (((unsigned char)hash.hash[1])<<8);
+
+ h = h % NGT_SIZE;
+
+ ngtp = NGTable[h];
+
+ while (ngtp) {
+ if (strcmp(p, ngtp->ngname) == 0) {
+ free(p);
+ return ngtp;
+ }
+ ngtp = ngtp->next;
+ }
+ free(p);
+ return NULL;
+}
+
+/* find a newsgroup/spooldir name, given only the newsgroup number */
+static char *
+FindNGByNum(unsigned long ngnumber) {
+ NGTENT *ngtp;
+ NGTREENODE *curnode;
+
+ curnode = NGTree;
+
+ while (curnode) {
+ if (curnode->ngnumber == ngnumber) {
+ ngtp = curnode->ngtp;
+ return ngtp->ngname;
+ }
+ if (curnode->ngnumber < ngnumber) {
+ curnode = curnode->right;
+ } else {
+ curnode = curnode->left;
+ }
+ }
+ /* not in tree, return NULL */
+ return NULL;
+}
+
+#define _PATH_TRADSPOOLNGDB "tradspool.map"
+#define _PATH_NEWTSNGDB "tradspool.map.new"
+
+
+/* dump DB to file. */
+static void
+DumpDB(void)
+{
+ char *fname, *fnamenew;
+ NGTENT *ngtp;
+ unsigned int i;
+ FILE *out;
+
+ if (!SMopenmode) return; /* don't write if we're not in read/write mode. */
+ if (!NGTableUpdated) return; /* no need to dump new DB */
+
+ fname = concatpath(innconf->pathspool, _PATH_TRADSPOOLNGDB);
+ fnamenew = concatpath(innconf->pathspool, _PATH_NEWTSNGDB);
+
+ if ((out = fopen(fnamenew, "w")) == NULL) {
+ syslog(L_ERROR, "tradspool: DumpDB: can't write %s: %m", fnamenew);
+ free(fname);
+ free(fnamenew);
+ return;
+ }
+ for (i = 0 ; i < NGT_SIZE ; ++i) {
+ ngtp = NGTable[i];
+ for ( ; ngtp ; ngtp = ngtp->next) {
+ fprintf(out, "%s %lu\n", ngtp->ngname, ngtp->ngnumber);
+ }
+ }
+ if (fclose(out) < 0) {
+ syslog(L_ERROR, "tradspool: DumpDB: can't close %s: %m", fnamenew);
+ free(fname);
+ free(fnamenew);
+ return;
+ }
+ if (rename(fnamenew, fname) < 0) {
+ syslog(L_ERROR, "tradspool: can't rename %s", fnamenew);
+ free(fname);
+ free(fnamenew);
+ return;
+ }
+ free(fname);
+ free(fnamenew);
+ NGTableUpdated = false; /* reset modification flag. */
+ return;
+}
+
+/*
+** init NGTable from saved database file and from active. Note that
+** entries in the database file get added first, and get their specifications
+** of newsgroup number from there.
+*/
+
+static bool
+ReadDBFile(void)
+{
+ char *fname;
+ QIOSTATE *qp;
+ char *line;
+ char *p;
+ unsigned long number;
+
+ fname = concatpath(innconf->pathspool, _PATH_TRADSPOOLNGDB);
+ if ((qp = QIOopen(fname)) == NULL) {
+ /* only warn if db not found. */
+ syslog(L_NOTICE, "tradspool: %s not found", fname);
+ } else {
+ while ((line = QIOread(qp)) != NULL) {
+ p = strchr(line, ' ');
+ if (p == NULL) {
+ syslog(L_FATAL, "tradspool: corrupt line in active %s", line);
+ QIOclose(qp);
+ free(fname);
+ return false;
+ }
+ *p++ = 0;
+ number = atol(p);
+ AddNG(line, number);
+ if (MaxNgNumber < number) MaxNgNumber = number;
+ }
+ QIOclose(qp);
+ }
+ free(fname);
+ return true;
+}
+
+static bool
+ReadActiveFile(void)
+{
+ char *fname;
+ QIOSTATE *qp;
+ char *line;
+ char *p;
+
+ fname = concatpath(innconf->pathdb, _PATH_ACTIVE);
+ if ((qp = QIOopen(fname)) == NULL) {
+ syslog(L_FATAL, "tradspool: can't open %s", fname);
+ free(fname);
+ return false;
+ }
+
+ while ((line = QIOread(qp)) != NULL) {
+ p = strchr(line, ' ');
+ if (p == NULL) {
+ syslog(L_FATAL, "tradspool: corrupt line in active %s", line);
+ QIOclose(qp);
+ free(fname);
+ return false;
+ }
+ *p = 0;
+ AddNG(line, 0);
+ }
+ QIOclose(qp);
+ free(fname);
+ /* dump any newly added changes to database */
+ DumpDB();
+ return true;
+}
+
+static bool
+InitNGTable(void)
+{
+ if (!ReadDBFile()) return false;
+
+ /*
+ ** set NGTableUpdated to false; that way we know if the load of active or
+ ** any AddNGs later on did in fact add new entries to the db.
+ */
+ NGTableUpdated = false;
+ if (!SMopenmode)
+ /* don't read active unless write mode. */
+ return true;
+ return ReadActiveFile();
+}
+
+/*
+** Routine called to check every so often to see if we need to reload the
+** database and add in any new groups that have been added. This is primarily
+** for the benefit of innfeed in funnel mode, which otherwise would never
+** get word that any new newsgroups had been added.
+*/
+
+#define RELOAD_TIME_CHECK 600
+
+static void
+CheckNeedReloadDB(bool force)
+{
+ static TIMEINFO lastcheck, oldlastcheck, now;
+ struct stat sb;
+ char *fname;
+
+ if (GetTimeInfo(&now) < 0) return; /* anyone ever seen gettimeofday fail? :-) */
+ if (!force && lastcheck.time + RELOAD_TIME_CHECK > now.time) return;
+
+ oldlastcheck = lastcheck;
+ lastcheck = now;
+
+ fname = concatpath(innconf->pathspool, _PATH_TRADSPOOLNGDB);
+ if (stat(fname, &sb) < 0) {
+ free(fname);
+ return;
+ }
+ free(fname);
+ if (sb.st_mtime > oldlastcheck.time) {
+ /* add any newly added ngs to our in-memory copy of the db. */
+ ReadDBFile();
+ }
+}
+
+/* Init routine, called by SMinit */
+
+bool
+tradspool_init(SMATTRIBUTE *attr) {
+ if (attr == NULL) {
+ syslog(L_ERROR, "tradspool: attr is NULL");
+ SMseterror(SMERR_INTERNAL, "attr is NULL");
+ return false;
+ }
+ if (!innconf->storeonxref) {
+ syslog(L_ERROR, "tradspool: storeonxref needs to be true");
+ SMseterror(SMERR_INTERNAL, "storeonxref needs to be true");
+ return false;
+ }
+ attr->selfexpire = false;
+ attr->expensivestat = true;
+ return InitNGTable();
+}
+
+/* Make a token for an article given the primary newsgroup name and article # */
+static TOKEN
+MakeToken(char *ng, unsigned long artnum, STORAGECLASS class) {
+ TOKEN token;
+ NGTENT *ngtp;
+ unsigned long num;
+
+ memset(&token, '\0', sizeof(token));
+
+ token.type = TOKEN_TRADSPOOL;
+ token.class = class;
+
+ /*
+ ** if not already in the NG Table, be sure to add this ng! This way we
+ ** catch things like newsgroups added since startup.
+ */
+ if ((ngtp = FindNGByName(ng)) == NULL) {
+ AddNG(ng, 0);
+ DumpDB(); /* flush to disk so other programs can see the change */
+ ngtp = FindNGByName(ng);
+ }
+
+ num = ngtp->ngnumber;
+ num = htonl(num);
+
+ memcpy(token.token, &num, sizeof(num));
+ artnum = htonl(artnum);
+ memcpy(&token.token[sizeof(num)], &artnum, sizeof(artnum));
+ return token;
+}
+
+/*
+** Convert a token back to a pathname.
+*/
+static char *
+TokenToPath(TOKEN token) {
+ unsigned long ngnum;
+ unsigned long artnum;
+ char *ng, *path;
+ size_t length;
+
+ CheckNeedReloadDB(false);
+
+ memcpy(&ngnum, &token.token[0], sizeof(ngnum));
+ memcpy(&artnum, &token.token[sizeof(ngnum)], sizeof(artnum));
+ artnum = ntohl(artnum);
+ ngnum = ntohl(ngnum);
+
+ ng = FindNGByNum(ngnum);
+ if (ng == NULL) {
+ CheckNeedReloadDB(true);
+ ng = FindNGByNum(ngnum);
+ if (ng == NULL)
+ return NULL;
+ }
+
+ length = strlen(ng) + 20 + strlen(innconf->patharticles);
+ path = xmalloc(length);
+ snprintf(path, length, "%s/%s/%lu", innconf->patharticles, ng, artnum);
+ return path;
+}
+
+/*
+** Crack an Xref line apart into separate strings, each of the form "ng:artnum".
+** Return in "num" the number of newsgroups found.
+*/
+static char **
+CrackXref(char *xref, unsigned int *lenp) {
+ char *p;
+ char **xrefs;
+ char *q;
+ unsigned int len, xrefsize;
+
+ len = 0;
+ xrefsize = 5;
+ xrefs = xmalloc(xrefsize * sizeof(char *));
+
+ /* no path element should exist, nor heading white spaces exist */
+ p = xref;
+ while (true) {
+ /* check for EOL */
+ /* shouldn't ever hit null w/o hitting a \r\n first, but best to be paranoid */
+ if (*p == '\n' || *p == '\r' || *p == 0) {
+ /* hit EOL, return. */
+ *lenp = len;
+ return xrefs;
+ }
+ /* skip to next space or EOL */
+ for (q=p; *q && *q != ' ' && *q != '\n' && *q != '\r' ; ++q) ;
+
+ xrefs[len] = xstrndup(p, q - p);
+
+ if (++len == xrefsize) {
+ /* grow xrefs if needed. */
+ xrefsize *= 2;
+ xrefs = xrealloc(xrefs, xrefsize * sizeof(char *));
+ }
+
+ p = q;
+ /* skip spaces */
+ for ( ; *p == ' ' ; p++) ;
+ }
+}
+
+TOKEN
+tradspool_store(const ARTHANDLE article, const STORAGECLASS class) {
+ char **xrefs;
+ char *xrefhdr;
+ TOKEN token;
+ unsigned int numxrefs;
+ char *ng, *p, *onebuffer;
+ unsigned long artnum;
+ char *path, *linkpath, *dirname;
+ int fd;
+ size_t used;
+ char *nonwfarticle; /* copy of article converted to non-wire format */
+ unsigned int i;
+ size_t length, nonwflen;
+
+ xrefhdr = article.groups;
+ if ((xrefs = CrackXref(xrefhdr, &numxrefs)) == NULL || numxrefs == 0) {
+ token.type = TOKEN_EMPTY;
+ SMseterror(SMERR_UNDEFINED, "bogus Xref: header");
+ if (xrefs != NULL)
+ free(xrefs);
+ return token;
+ }
+
+ if ((p = strchr(xrefs[0], ':')) == NULL) {
+ token.type = TOKEN_EMPTY;
+ SMseterror(SMERR_UNDEFINED, "bogus Xref: header");
+ for (i = 0 ; i < numxrefs; ++i) free(xrefs[i]);
+ free(xrefs);
+ return token;
+ }
+ *p++ = '\0';
+ ng = xrefs[0];
+ DeDotify(ng);
+ artnum = atol(p);
+
+ token = MakeToken(ng, artnum, class);
+
+ length = strlen(innconf->patharticles) + strlen(ng) + 32;
+ path = xmalloc(length);
+ snprintf(path, length, "%s/%s/%lu", innconf->patharticles, ng, artnum);
+
+ /* following chunk of code boldly stolen from timehash.c :-) */
+ if ((fd = open(path, O_CREAT|O_EXCL|O_WRONLY, ARTFILE_MODE)) < 0) {
+ p = strrchr(path, '/');
+ *p = '\0';
+ if (!MakeDirectory(path, true)) {
+ syslog(L_ERROR, "tradspool: could not make directory %s %m", path);
+ token.type = TOKEN_EMPTY;
+ free(path);
+ SMseterror(SMERR_UNDEFINED, NULL);
+ for (i = 0 ; i < numxrefs; ++i) free(xrefs[i]);
+ free(xrefs);
+ return token;
+ } else {
+ *p = '/';
+ if ((fd = open(path, O_CREAT|O_EXCL|O_WRONLY, ARTFILE_MODE)) < 0) {
+ SMseterror(SMERR_UNDEFINED, NULL);
+ syslog(L_ERROR, "tradspool: could not open %s %m", path);
+ token.type = TOKEN_EMPTY;
+ free(path);
+ for (i = 0 ; i < numxrefs; ++i) free(xrefs[i]);
+ free(xrefs);
+ return token;
+ }
+ }
+ }
+ if (innconf->wireformat) {
+ if (xwritev(fd, article.iov, article.iovcnt) != (ssize_t) article.len) {
+ SMseterror(SMERR_UNDEFINED, NULL);
+ syslog(L_ERROR, "tradspool error writing %s %m", path);
+ close(fd);
+ token.type = TOKEN_EMPTY;
+ unlink(path);
+ free(path);
+ for (i = 0 ; i < numxrefs; ++i) free(xrefs[i]);
+ free(xrefs);
+ return token;
+ }
+ } else {
+ onebuffer = xmalloc(article.len);
+ for (used = i = 0 ; i < article.iovcnt ; i++) {
+ memcpy(&onebuffer[used], article.iov[i].iov_base, article.iov[i].iov_len);
+ used += article.iov[i].iov_len;
+ }
+ nonwfarticle = FromWireFmt(onebuffer, used, &nonwflen);
+ free(onebuffer);
+ if (write(fd, nonwfarticle, nonwflen) != (ssize_t) nonwflen) {
+ free(nonwfarticle);
+ SMseterror(SMERR_UNDEFINED, NULL);
+ syslog(L_ERROR, "tradspool error writing %s %m", path);
+ close(fd);
+ token.type = TOKEN_EMPTY;
+ unlink(path);
+ free(path);
+ for (i = 0 ; i < numxrefs; ++i) free(xrefs[i]);
+ free(xrefs);
+ return token;
+ }
+ free(nonwfarticle);
+ }
+ close(fd);
+
+ /*
+ ** blah, this is ugly. Have to make symlinks under other pathnames for
+ ** backwards compatiblility purposes.
+ */
+
+ if (numxrefs > 1) {
+ for (i = 1; i < numxrefs ; ++i) {
+ if ((p = strchr(xrefs[i], ':')) == NULL) continue;
+ *p++ = '\0';
+ ng = xrefs[i];
+ DeDotify(ng);
+ artnum = atol(p);
+
+ length = strlen(innconf->patharticles) + strlen(ng) + 32;
+ linkpath = xmalloc(length);
+ snprintf(linkpath, length, "%s/%s/%lu", innconf->patharticles,
+ ng, artnum);
+ if (link(path, linkpath) < 0) {
+ p = strrchr(linkpath, '/');
+ *p = '\0';
+ dirname = xstrdup(linkpath);
+ *p = '/';
+ if (!MakeDirectory(dirname, true) || link(path, linkpath) < 0) {
+#if !defined(HAVE_SYMLINK)
+ SMseterror(SMERR_UNDEFINED, NULL);
+ syslog(L_ERROR, "tradspool: could not link %s to %s %m", path, linkpath);
+ token.type = TOKEN_EMPTY;
+ free(dirname);
+ free(linkpath);
+ free(path);
+ for (i = 0 ; i < numxrefs; ++i) free(xrefs[i]);
+ free(xrefs);
+ return token;
+#else
+ if (symlink(path, linkpath) < 0) {
+ SMseterror(SMERR_UNDEFINED, NULL);
+ syslog(L_ERROR, "tradspool: could not symlink %s to %s %m", path, linkpath);
+ token.type = TOKEN_EMPTY;
+ free(dirname);
+ free(linkpath);
+ free(path);
+ for (i = 0 ; i < numxrefs; ++i) free(xrefs[i]);
+ free(xrefs);
+ return token;
+ }
+#endif /* !defined(HAVE_SYMLINK) */
+ }
+ free(dirname);
+ }
+ free(linkpath);
+ }
+ }
+ free(path);
+ for (i = 0 ; i < numxrefs; ++i) free(xrefs[i]);
+ free(xrefs);
+ return token;
+}
+
+static ARTHANDLE *
+OpenArticle(const char *path, RETRTYPE amount) {
+ int fd;
+ PRIV_TRADSPOOL *private;
+ char *p;
+ struct stat sb;
+ ARTHANDLE *art;
+ char *wfarticle;
+ size_t wflen;
+
+ if (amount == RETR_STAT) {
+ if (access(path, R_OK) < 0) {
+ SMseterror(SMERR_UNDEFINED, NULL);
+ return NULL;
+ }
+ art = xmalloc(sizeof(ARTHANDLE));
+ art->type = TOKEN_TRADSPOOL;
+ art->data = NULL;
+ art->len = 0;
+ art->private = NULL;
+ return art;
+ }
+
+ if ((fd = open(path, O_RDONLY)) < 0) {
+ SMseterror(SMERR_UNDEFINED, NULL);
+ return NULL;
+ }
+
+ art = xmalloc(sizeof(ARTHANDLE));
+ art->type = TOKEN_TRADSPOOL;
+
+ if (fstat(fd, &sb) < 0) {
+ SMseterror(SMERR_UNDEFINED, NULL);
+ syslog(L_ERROR, "tradspool: could not fstat article: %m");
+ free(art);
+ close(fd);
+ return NULL;
+ }
+
+ art->arrived = sb.st_mtime;
+
+ private = xmalloc(sizeof(PRIV_TRADSPOOL));
+ art->private = (void *)private;
+ private->artlen = sb.st_size;
+ if (innconf->articlemmap) {
+ if ((private->artbase = mmap(NULL, sb.st_size, PROT_READ, MAP_SHARED, fd, 0)) == MAP_FAILED) {
+ SMseterror(SMERR_UNDEFINED, NULL);
+ syslog(L_ERROR, "tradspool: could not mmap article: %m");
+ free(art->private);
+ free(art);
+ close(fd);
+ return NULL;
+ }
+ if (amount == RETR_ALL)
+ madvise(private->artbase, sb.st_size, MADV_WILLNEED);
+ else
+ madvise(private->artbase, sb.st_size, MADV_SEQUENTIAL);
+
+ /* consider coexisting both wireformatted and nonwireformatted */
+ p = memchr(private->artbase, '\n', private->artlen);
+ if (p == NULL || p == private->artbase) {
+ SMseterror(SMERR_UNDEFINED, NULL);
+ syslog(L_ERROR, "tradspool: could not mmap article: %m");
+ munmap(private->artbase, private->artlen);
+ free(art->private);
+ free(art);
+ close(fd);
+ return NULL;
+ }
+ if (p[-1] == '\r') {
+ private->mmapped = true;
+ } else {
+ wfarticle = ToWireFmt(private->artbase, private->artlen, &wflen);
+ munmap(private->artbase, private->artlen);
+ private->artbase = wfarticle;
+ private->artlen = wflen;
+ private->mmapped = false;
+ }
+ } else {
+ private->mmapped = false;
+ private->artbase = xmalloc(private->artlen);
+ if (read(fd, private->artbase, private->artlen) < 0) {
+ SMseterror(SMERR_UNDEFINED, NULL);
+ syslog(L_ERROR, "tradspool: could not read article: %m");
+ free(private->artbase);
+ free(art->private);
+ free(art);
+ close(fd);
+ return NULL;
+ }
+ p = memchr(private->artbase, '\n', private->artlen);
+ if (p == NULL || p == private->artbase) {
+ SMseterror(SMERR_UNDEFINED, NULL);
+ syslog(L_ERROR, "tradspool: could not mmap article: %m");
+ free(art->private);
+ free(art);
+ close(fd);
+ return NULL;
+ }
+ if (p[-1] != '\r') {
+ /* need to make a wireformat copy of the article */
+ wfarticle = ToWireFmt(private->artbase, private->artlen, &wflen);
+ free(private->artbase);
+ private->artbase = wfarticle;
+ private->artlen = wflen;
+ }
+ }
+ close(fd);
+
+ private->ngtp = NULL;
+ private->curdir = NULL;
+ private->curdirname = NULL;
+ private->nextindex = -1;
+
+ if (amount == RETR_ALL) {
+ art->data = private->artbase;
+ art->len = private->artlen;
+ return art;
+ }
+
+ if (((p = wire_findbody(private->artbase, private->artlen)) == NULL)) {
+ if (private->mmapped)
+ munmap(private->artbase, private->artlen);
+ else
+ free(private->artbase);
+ SMseterror(SMERR_NOBODY, NULL);
+ free(art->private);
+ free(art);
+ return NULL;
+ }
+
+ if (amount == RETR_HEAD) {
+ art->data = private->artbase;
+ art->len = p - private->artbase;
+ return art;
+ }
+
+ if (amount == RETR_BODY) {
+ art->data = p;
+ art->len = private->artlen - (p - private->artbase);
+ return art;
+ }
+ SMseterror(SMERR_UNDEFINED, "Invalid retrieve request");
+ if (private->mmapped)
+ munmap(private->artbase, private->artlen);
+ else
+ free(private->artbase);
+ free(art->private);
+ free(art);
+ return NULL;
+}
+
+
+ARTHANDLE *
+tradspool_retrieve(const TOKEN token, const RETRTYPE amount) {
+ char *path;
+ ARTHANDLE *art;
+ static TOKEN ret_token;
+
+ if (token.type != TOKEN_TRADSPOOL) {
+ SMseterror(SMERR_INTERNAL, NULL);
+ return NULL;
+ }
+
+ if ((path = TokenToPath(token)) == NULL) {
+ SMseterror(SMERR_NOENT, NULL);
+ return NULL;
+ }
+ if ((art = OpenArticle(path, amount)) != (ARTHANDLE *)NULL) {
+ ret_token = token;
+ art->token = &ret_token;
+ }
+ free(path);
+ return art;
+}
+
+void
+tradspool_freearticle(ARTHANDLE *article)
+{
+ PRIV_TRADSPOOL *private;
+
+ if (article == NULL)
+ return;
+
+ if (article->private) {
+ private = (PRIV_TRADSPOOL *) article->private;
+ if (private->mmapped)
+ munmap(private->artbase, private->artlen);
+ else
+ free(private->artbase);
+ if (private->curdir)
+ closedir(private->curdir);
+ free(private->curdirname);
+ free(private);
+ }
+ free(article);
+}
+
+bool
+tradspool_cancel(TOKEN token) {
+ char **xrefs;
+ char *xrefhdr;
+ ARTHANDLE *article;
+ unsigned int numxrefs;
+ char *ng, *p;
+ char *path, *linkpath;
+ unsigned int i;
+ bool result = true;
+ unsigned long artnum;
+ size_t length;
+
+ if ((path = TokenToPath(token)) == NULL) {
+ SMseterror(SMERR_UNDEFINED, NULL);
+ free(path);
+ return false;
+ }
+ /*
+ ** Ooooh, this is gross. To find the symlinks pointing to this article,
+ ** we open the article and grab its Xref line (since the token isn't long
+ ** enough to store this info on its own). This is *not* going to do
+ ** good things for performance of fastrm... -- rmtodd
+ */
+ if ((article = OpenArticle(path, RETR_HEAD)) == NULL) {
+ free(path);
+ SMseterror(SMERR_UNDEFINED, NULL);
+ return false;
+ }
+
+ xrefhdr = wire_findheader(article->data, article->len, "Xref");
+ if (xrefhdr == NULL) {
+ /* for backwards compatibility; there is no Xref unless crossposted
+ for 1.4 and 1.5 */
+ if (unlink(path) < 0) result = false;
+ free(path);
+ tradspool_freearticle(article);
+ return result;
+ }
+
+ if ((xrefs = CrackXref(xrefhdr, &numxrefs)) == NULL || numxrefs == 0) {
+ if (xrefs != NULL)
+ free(xrefs);
+ free(path);
+ tradspool_freearticle(article);
+ SMseterror(SMERR_UNDEFINED, NULL);
+ return false;
+ }
+
+ tradspool_freearticle(article);
+ for (i = 1 ; i < numxrefs ; ++i) {
+ if ((p = strchr(xrefs[i], ':')) == NULL) continue;
+ *p++ = '\0';
+ ng = xrefs[i];
+ DeDotify(ng);
+ artnum = atol(p);
+
+ length = strlen(innconf->patharticles) + strlen(ng) + 32;
+ linkpath = xmalloc(length);
+ snprintf(linkpath, length, "%s/%s/%lu", innconf->patharticles, ng,
+ artnum);
+ /* repeated unlinkings of a crossposted article may fail on account
+ of the file no longer existing without it truly being an error */
+ if (unlink(linkpath) < 0)
+ if (errno != ENOENT || i == 1)
+ result = false;
+ free(linkpath);
+ }
+ if (unlink(path) < 0)
+ if (errno != ENOENT || numxrefs == 1)
+ result = false;
+ free(path);
+ for (i = 0 ; i < numxrefs ; ++i) free(xrefs[i]);
+ free(xrefs);
+ return result;
+}
+
+
+/*
+** Find entries for possible articles in dir. "dir" (directory name "dirname").
+** The dirname is needed so we can do stats in the directory to disambiguate
+** files from symlinks and directories.
+*/
+
+static struct dirent *
+FindDir(DIR *dir, char *dirname) {
+ struct dirent *de;
+ int i;
+ bool flag;
+ char *path;
+ struct stat sb;
+ size_t length;
+ unsigned char namelen;
+
+ while ((de = readdir(dir)) != NULL) {
+ namelen = strlen(de->d_name);
+ for (i = 0, flag = true ; i < namelen ; ++i) {
+ if (!CTYPE(isdigit, de->d_name[i])) {
+ flag = false;
+ break;
+ }
+ }
+ if (!flag) continue; /* if not all digits, skip this entry. */
+
+ length = strlen(dirname) + namelen + 2;
+ path = xmalloc(length);
+ strlcpy(path, dirname, length);
+ strlcat(path, "/", length);
+ strlcat(path, de->d_name, length);
+
+ if (lstat(path, &sb) < 0) {
+ free(path);
+ continue;
+ }
+ free(path);
+ if (!S_ISREG(sb.st_mode)) continue;
+ return de;
+ }
+ return NULL;
+}
+
+ARTHANDLE *tradspool_next(const ARTHANDLE *article, const RETRTYPE amount) {
+ PRIV_TRADSPOOL priv;
+ PRIV_TRADSPOOL *newpriv;
+ char *path, *linkpath;
+ struct dirent *de;
+ ARTHANDLE *art;
+ unsigned long artnum;
+ unsigned int i;
+ static TOKEN token;
+ char **xrefs;
+ char *xrefhdr, *ng, *p, *expires, *x;
+ unsigned int numxrefs;
+ STORAGE_SUB *sub;
+ size_t length;
+
+ if (article == NULL) {
+ priv.ngtp = NULL;
+ priv.curdir = NULL;
+ priv.curdirname = NULL;
+ priv.nextindex = -1;
+ } else {
+ priv = *(PRIV_TRADSPOOL *) article->private;
+ free(article->private);
+ free((void*)article);
+ if (priv.artbase != NULL) {
+ if (priv.mmapped)
+ munmap(priv.artbase, priv.artlen);
+ else
+ free(priv.artbase);
+ }
+ }
+
+ while (!priv.curdir || ((de = FindDir(priv.curdir, priv.curdirname)) == NULL)) {
+ if (priv.curdir) {
+ closedir(priv.curdir);
+ priv.curdir = NULL;
+ free(priv.curdirname);
+ priv.curdirname = NULL;
+ }
+
+ /*
+ ** advance ngtp to the next entry, if it exists, otherwise start
+ ** searching down another ngtable hashchain.
+ */
+ while (priv.ngtp == NULL || (priv.ngtp = priv.ngtp->next) == NULL) {
+ /*
+ ** note that at the start of a search nextindex is -1, so the inc.
+ ** makes nextindex 0, as it should be.
+ */
+ priv.nextindex++;
+ if (priv.nextindex >= NGT_SIZE) {
+ /* ran off the end of the table, so return. */
+ return NULL;
+ }
+ priv.ngtp = NGTable[priv.nextindex];
+ if (priv.ngtp != NULL)
+ break;
+ }
+
+ priv.curdirname = concatpath(innconf->patharticles, priv.ngtp->ngname);
+ priv.curdir = opendir(priv.curdirname);
+ }
+
+ path = concatpath(priv.curdirname, de->d_name);
+ i = strlen(priv.curdirname);
+ /* get the article number while we're here, we'll need it later. */
+ artnum = atol(&path[i+1]);
+
+ art = OpenArticle(path, amount);
+ if (art == (ARTHANDLE *)NULL) {
+ art = xmalloc(sizeof(ARTHANDLE));
+ art->type = TOKEN_TRADSPOOL;
+ art->data = NULL;
+ art->len = 0;
+ art->private = xmalloc(sizeof(PRIV_TRADSPOOL));
+ art->expires = 0;
+ art->groups = NULL;
+ art->groupslen = 0;
+ newpriv = (PRIV_TRADSPOOL *) art->private;
+ newpriv->artbase = NULL;
+ } else {
+ /* Skip linked (not symlinked) crossposted articles.
+
+ This algorithm is rather questionable; it only works if the first
+ group/number combination listed in the Xref header is the
+ canonical path. This will always be true for spools created by
+ this implementation, but for traditional INN 1.x servers,
+ articles are expired indepedently from each group and may expire
+ out of the first listed newsgroup before other groups. This
+ algorithm will orphan such articles, not adding them to history.
+
+ The bit of skipping articles by setting the length of the article
+ to zero is also rather suspect, and I'm not sure what
+ implications that might have for the callers of SMnext.
+
+ Basically, this whole area really needs to be rethought. */
+ xrefhdr = wire_findheader(art->data, art->len, "Xref");
+ if (xrefhdr != NULL) {
+ if ((xrefs = CrackXref(xrefhdr, &numxrefs)) == NULL || numxrefs == 0) {
+ art->len = 0;
+ } else {
+ /* assumes first one is the original */
+ if ((p = strchr(xrefs[1], ':')) != NULL) {
+ *p++ = '\0';
+ ng = xrefs[1];
+ DeDotify(ng);
+ artnum = atol(p);
+
+ length = strlen(innconf->patharticles) + strlen(ng) + 32;
+ linkpath = xmalloc(length);
+ snprintf(linkpath, length, "%s/%s/%lu",
+ innconf->patharticles, ng, artnum);
+ if (strcmp(path, linkpath) != 0) {
+ /* this is linked article, skip it */
+ art->len = 0;
+ }
+ free(linkpath);
+ }
+ }
+ for (i = 0 ; i < numxrefs ; ++i) free(xrefs[i]);
+ free(xrefs);
+ if (innconf->storeonxref) {
+ /* skip path element */
+ if ((xrefhdr = strchr(xrefhdr, ' ')) == NULL) {
+ art->groups = NULL;
+ art->groupslen = 0;
+ } else {
+ for (xrefhdr++; *xrefhdr == ' '; xrefhdr++);
+ art->groups = xrefhdr;
+ for (p = xrefhdr ; (*p != '\n') && (*p != '\r') ; p++);
+ art->groupslen = p - xrefhdr;
+ }
+ }
+ }
+ if (xrefhdr == NULL || !innconf->storeonxref) {
+ ng = wire_findheader(art->data, art->len, "Newsgroups");
+ if (ng == NULL) {
+ art->groups = NULL;
+ art->groupslen = 0;
+ } else {
+ art->groups = ng;
+ for (p = ng ; (*p != '\n') && (*p != '\r') ; p++);
+ art->groupslen = p - ng;
+ }
+ }
+ expires = wire_findheader(art->data, art->len, "Expires");
+ if (expires == NULL) {
+ art->expires = 0;
+ } else {
+ /* optionally parse expire header */
+ for (p = expires + 1; (*p != '\n') && (*(p - 1) != '\r'); p++);
+ x = xmalloc(p - expires);
+ memcpy(x, expires, p - expires - 1);
+ x[p - expires - 1] = '\0';
+
+ art->expires = parsedate(x, NULL);
+ if (art->expires == -1)
+ art->expires = 0;
+ else
+ art->expires -= time(0);
+ free(x);
+ }
+ /* for backwards compatibility; assumes no Xref unless crossposted
+ for 1.4 and 1.5: just fall through */
+ }
+ newpriv = (PRIV_TRADSPOOL *) art->private;
+ newpriv->nextindex = priv.nextindex;
+ newpriv->curdir = priv.curdir;
+ newpriv->curdirname = priv.curdirname;
+ newpriv->ngtp = priv.ngtp;
+
+ if ((sub = SMgetsub(*art)) == NULL || sub->type != TOKEN_TRADSPOOL) {
+ /* maybe storage.conf is modified, after receiving article */
+ token = MakeToken(priv.ngtp->ngname, artnum, 0);
+
+ /* Only log an error if art->len is non-zero, since otherwise we get
+ all the ones skipped via the hard-link skipping algorithm
+ commented above. */
+ if (art->len > 0)
+ syslog(L_ERROR, "tradspool: can't determine class: %s: %s",
+ TokenToText(token), SMerrorstr);
+ } else {
+ token = MakeToken(priv.ngtp->ngname, artnum, sub->class);
+ }
+ art->token = &token;
+ free(path);
+ return art;
+}
+
+static void
+FreeNGTree(void)
+{
+ unsigned int i;
+ NGTENT *ngtp, *nextngtp;
+
+ for (i = 0 ; i < NGT_SIZE ; i++) {
+ ngtp = NGTable[i];
+ for ( ; ngtp != NULL ; ngtp = nextngtp) {
+ nextngtp = ngtp->next;
+ free(ngtp->ngname);
+ free(ngtp->node);
+ free(ngtp);
+ }
+ NGTable[i] = NULL;
+ }
+ MaxNgNumber = 0;
+ NGTree = NULL;
+}
+
+bool tradspool_ctl(PROBETYPE type, TOKEN *token, void *value) {
+ struct artngnum *ann;
+ unsigned long ngnum;
+ unsigned long artnum;
+ char *ng, *p;
+
+ switch (type) {
+ case SMARTNGNUM:
+ if ((ann = (struct artngnum *)value) == NULL)
+ return false;
+ CheckNeedReloadDB(false);
+ memcpy(&ngnum, &token->token[0], sizeof(ngnum));
+ memcpy(&artnum, &token->token[sizeof(ngnum)], sizeof(artnum));
+ artnum = ntohl(artnum);
+ ngnum = ntohl(ngnum);
+ ng = FindNGByNum(ngnum);
+ if (ng == NULL) {
+ CheckNeedReloadDB(true);
+ ng = FindNGByNum(ngnum);
+ if (ng == NULL)
+ return false;
+ }
+ ann->groupname = xstrdup(ng);
+ for (p = ann->groupname; *p != 0; p++)
+ if (*p == '/')
+ *p = '.';
+ ann->artnum = (ARTNUM)artnum;
+ return true;
+ default:
+ return false;
+ }
+}
+
+bool
+tradspool_flushcacheddata(FLUSHTYPE type UNUSED)
+{
+ return true;
+}
+
+void
+tradspool_printfiles(FILE *file, TOKEN token UNUSED, char **xref, int ngroups)
+{
+ int i;
+ char *path, *p;
+
+ for (i = 0; i < ngroups; i++) {
+ path = xstrdup(xref[i]);
+ for (p = path; *p != '\0'; p++)
+ if (*p == '.' || *p == ':')
+ *p = '/';
+ fprintf(file, "%s\n", path);
+ free(path);
+ }
+}
+
+void
+tradspool_shutdown(void) {
+ DumpDB();
+ FreeNGTree();
+}
--- /dev/null
+/*
+** $Id: tradspool.h 4269 2001-01-04 06:04:10Z rra $
+** tradspool -- storage manager for traditional spool format.
+*/
+
+#ifndef __TRADSPOOL_H__
+#define __TRADSPOOL_H__
+
+#include "config.h"
+#include "interface.h"
+
+bool tradspool_init(SMATTRIBUTE *attr);
+TOKEN tradspool_store(const ARTHANDLE article, const STORAGECLASS class);
+ARTHANDLE *tradspool_retrieve(const TOKEN token, const RETRTYPE amount);
+ARTHANDLE *tradspool_next(const ARTHANDLE *article, const RETRTYPE amount);
+void tradspool_freearticle(ARTHANDLE *article);
+bool tradspool_cancel(TOKEN token);
+bool tradspool_ctl(PROBETYPE type, TOKEN *token, void *value);
+bool tradspool_flushcacheddata(FLUSHTYPE type);
+void tradspool_printfiles(FILE *file, TOKEN token, char **xref, int ngroups);
+void tradspool_shutdown(void);
+
+#endif
--- /dev/null
+name = trash
+number = 0
+sources = trash.c
--- /dev/null
+/* $Id: trash.c 6124 2003-01-14 06:03:29Z rra $
+**
+** Trashing articles method
+*/
+#include "config.h"
+#include "clibrary.h"
+#include "libinn.h"
+#include "methods.h"
+
+#include "trash.h"
+
+bool
+trash_init(SMATTRIBUTE *attr)
+{
+ if (attr == NULL) {
+ SMseterror(SMERR_INTERNAL, "attr is NULL");
+ return false;
+ }
+ attr->selfexpire = true;
+ attr->expensivestat = false;
+ return true;
+}
+
+TOKEN
+trash_store(const ARTHANDLE article, const STORAGECLASS class)
+{
+ TOKEN token;
+
+ if (article.token == (TOKEN *)NULL)
+ memset(&token, '\0', sizeof(token));
+ else {
+ memcpy(&token, article.token, sizeof(token));
+ memset(&token.token, '\0', STORAGE_TOKEN_LENGTH);
+ }
+ token.type = TOKEN_TRASH;
+ token.class = class;
+ return token;
+}
+
+ARTHANDLE *
+trash_retrieve(const TOKEN token, const RETRTYPE amount UNUSED)
+{
+ if (token.type != TOKEN_TRASH) {
+ SMseterror(SMERR_INTERNAL, NULL);
+ return (ARTHANDLE *)NULL;
+ }
+ SMseterror(SMERR_NOENT, NULL);
+ return (ARTHANDLE *)NULL;
+}
+
+void
+trash_freearticle(ARTHANDLE *article UNUSED)
+{
+}
+
+bool
+trash_cancel(TOKEN token UNUSED)
+{
+ SMseterror(SMERR_NOENT, NULL);
+ return false;
+}
+
+bool
+trash_ctl(PROBETYPE type, TOKEN *token UNUSED, void *value UNUSED)
+{
+ switch (type) {
+ case SMARTNGNUM:
+ default:
+ return false;
+ }
+}
+
+bool
+trash_flushcacheddata(FLUSHTYPE type UNUSED)
+{
+ return true;
+}
+
+void
+trash_printfiles(FILE *file UNUSED, TOKEN token UNUSED, char **xref UNUSED,
+ int ngroups UNUSED)
+{
+}
+
+ARTHANDLE *
+trash_next(const ARTHANDLE *article UNUSED, const RETRTYPE amount UNUSED)
+{
+ return NULL;
+}
+
+void
+trash_shutdown(void)
+{
+}
--- /dev/null
+/* $Id: trash.h 4267 2001-01-04 06:02:02Z rra $
+**
+** trashing articles method header
+*/
+
+#ifndef __TRASH_H__
+#define __TRASH_H__
+
+#include "config.h"
+#include "interface.h"
+
+bool trash_init(SMATTRIBUTE *attr);
+TOKEN trash_store(const ARTHANDLE article, const STORAGECLASS class);
+ARTHANDLE *trash_retrieve(const TOKEN token, const RETRTYPE amount);
+ARTHANDLE *trash_next(const ARTHANDLE *article, const RETRTYPE amount);
+void trash_freearticle(ARTHANDLE *article);
+bool trash_cancel(TOKEN token);
+bool trash_ctl(PROBETYPE type, TOKEN *token, void *value);
+bool trash_flushcacheddata(FLUSHTYPE type);
+void trash_printfiles(FILE *file, TOKEN token, char **xref, int ngroups);
+void trash_shutdown(void);
+
+#endif
--- /dev/null
+#! /bin/sh
+# Attempt to guess a canonical system name.
+# Copyright (C) 1992, 1993, 1994, 1995, 1996, 1997, 1998, 1999,
+# 2000, 2001, 2002, 2003, 2004, 2005, 2006, 2007, 2008
+# Free Software Foundation, Inc.
+
+timestamp='2008-04-14'
+
+# This file is free software; you can redistribute it and/or modify it
+# under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful, but
+# WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+# General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 51 Franklin Street - Fifth Floor, Boston, MA
+# 02110-1301, USA.
+#
+# As a special exception to the GNU General Public License, if you
+# distribute this file as part of a program that contains a
+# configuration script generated by Autoconf, you may include it under
+# the same distribution terms that you use for the rest of that program.
+
+
+# Originally written by Per Bothner <per@bothner.com>.
+# Please send patches to <config-patches@gnu.org>. Submit a context
+# diff and a properly formatted ChangeLog entry.
+#
+# This script attempts to guess a canonical system name similar to
+# config.sub. If it succeeds, it prints the system name on stdout, and
+# exits with 0. Otherwise, it exits with 1.
+#
+# The plan is that this can be called by configure scripts if you
+# don't specify an explicit build system type.
+
+me=`echo "$0" | sed -e 's,.*/,,'`
+
+usage="\
+Usage: $0 [OPTION]
+
+Output the configuration name of the system \`$me' is run on.
+
+Operation modes:
+ -h, --help print this help, then exit
+ -t, --time-stamp print date of last modification, then exit
+ -v, --version print version number, then exit
+
+Report bugs and patches to <config-patches@gnu.org>."
+
+version="\
+GNU config.guess ($timestamp)
+
+Originally written by Per Bothner.
+Copyright (C) 1992, 1993, 1994, 1995, 1996, 1997, 1998, 1999, 2000, 2001,
+2002, 2003, 2004, 2005, 2006, 2007, 2008 Free Software Foundation, Inc.
+
+This is free software; see the source for copying conditions. There is NO
+warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE."
+
+help="
+Try \`$me --help' for more information."
+
+# Parse command line
+while test $# -gt 0 ; do
+ case $1 in
+ --time-stamp | --time* | -t )
+ echo "$timestamp" ; exit ;;
+ --version | -v )
+ echo "$version" ; exit ;;
+ --help | --h* | -h )
+ echo "$usage"; exit ;;
+ -- ) # Stop option processing
+ shift; break ;;
+ - ) # Use stdin as input.
+ break ;;
+ -* )
+ echo "$me: invalid option $1$help" >&2
+ exit 1 ;;
+ * )
+ break ;;
+ esac
+done
+
+if test $# != 0; then
+ echo "$me: too many arguments$help" >&2
+ exit 1
+fi
+
+trap 'exit 1' 1 2 15
+
+# CC_FOR_BUILD -- compiler used by this script. Note that the use of a
+# compiler to aid in system detection is discouraged as it requires
+# temporary files to be created and, as you can see below, it is a
+# headache to deal with in a portable fashion.
+
+# Historically, `CC_FOR_BUILD' used to be named `HOST_CC'. We still
+# use `HOST_CC' if defined, but it is deprecated.
+
+# Portable tmp directory creation inspired by the Autoconf team.
+
+set_cc_for_build='
+trap "exitcode=\$?; (rm -f \$tmpfiles 2>/dev/null; rmdir \$tmp 2>/dev/null) && exit \$exitcode" 0 ;
+trap "rm -f \$tmpfiles 2>/dev/null; rmdir \$tmp 2>/dev/null; exit 1" 1 2 13 15 ;
+: ${TMPDIR=/tmp} ;
+ { tmp=`(umask 077 && mktemp -d "$TMPDIR/cgXXXXXX") 2>/dev/null` && test -n "$tmp" && test -d "$tmp" ; } ||
+ { test -n "$RANDOM" && tmp=$TMPDIR/cg$$-$RANDOM && (umask 077 && mkdir $tmp) ; } ||
+ { tmp=$TMPDIR/cg-$$ && (umask 077 && mkdir $tmp) && echo "Warning: creating insecure temp directory" >&2 ; } ||
+ { echo "$me: cannot create a temporary directory in $TMPDIR" >&2 ; exit 1 ; } ;
+dummy=$tmp/dummy ;
+tmpfiles="$dummy.c $dummy.o $dummy.rel $dummy" ;
+case $CC_FOR_BUILD,$HOST_CC,$CC in
+ ,,) echo "int x;" > $dummy.c ;
+ for c in cc gcc c89 c99 ; do
+ if ($c -c -o $dummy.o $dummy.c) >/dev/null 2>&1 ; then
+ CC_FOR_BUILD="$c"; break ;
+ fi ;
+ done ;
+ if test x"$CC_FOR_BUILD" = x ; then
+ CC_FOR_BUILD=no_compiler_found ;
+ fi
+ ;;
+ ,,*) CC_FOR_BUILD=$CC ;;
+ ,*,*) CC_FOR_BUILD=$HOST_CC ;;
+esac ; set_cc_for_build= ;'
+
+# This is needed to find uname on a Pyramid OSx when run in the BSD universe.
+# (ghazi@noc.rutgers.edu 1994-08-24)
+if (test -f /.attbin/uname) >/dev/null 2>&1 ; then
+ PATH=$PATH:/.attbin ; export PATH
+fi
+
+UNAME_MACHINE=`(uname -m) 2>/dev/null` || UNAME_MACHINE=unknown
+UNAME_RELEASE=`(uname -r) 2>/dev/null` || UNAME_RELEASE=unknown
+UNAME_SYSTEM=`(uname -s) 2>/dev/null` || UNAME_SYSTEM=unknown
+UNAME_VERSION=`(uname -v) 2>/dev/null` || UNAME_VERSION=unknown
+
+# Note: order is significant - the case branches are not exclusive.
+
+case "${UNAME_MACHINE}:${UNAME_SYSTEM}:${UNAME_RELEASE}:${UNAME_VERSION}" in
+ *:NetBSD:*:*)
+ # NetBSD (nbsd) targets should (where applicable) match one or
+ # more of the tupples: *-*-netbsdelf*, *-*-netbsdaout*,
+ # *-*-netbsdecoff* and *-*-netbsd*. For targets that recently
+ # switched to ELF, *-*-netbsd* would select the old
+ # object file format. This provides both forward
+ # compatibility and a consistent mechanism for selecting the
+ # object file format.
+ #
+ # Note: NetBSD doesn't particularly care about the vendor
+ # portion of the name. We always set it to "unknown".
+ sysctl="sysctl -n hw.machine_arch"
+ UNAME_MACHINE_ARCH=`(/sbin/$sysctl 2>/dev/null || \
+ /usr/sbin/$sysctl 2>/dev/null || echo unknown)`
+ case "${UNAME_MACHINE_ARCH}" in
+ armeb) machine=armeb-unknown ;;
+ arm*) machine=arm-unknown ;;
+ sh3el) machine=shl-unknown ;;
+ sh3eb) machine=sh-unknown ;;
+ sh5el) machine=sh5le-unknown ;;
+ *) machine=${UNAME_MACHINE_ARCH}-unknown ;;
+ esac
+ # The Operating System including object format, if it has switched
+ # to ELF recently, or will in the future.
+ case "${UNAME_MACHINE_ARCH}" in
+ arm*|i386|m68k|ns32k|sh3*|sparc|vax)
+ eval $set_cc_for_build
+ if echo __ELF__ | $CC_FOR_BUILD -E - 2>/dev/null \
+ | grep __ELF__ >/dev/null
+ then
+ # Once all utilities can be ECOFF (netbsdecoff) or a.out (netbsdaout).
+ # Return netbsd for either. FIX?
+ os=netbsd
+ else
+ os=netbsdelf
+ fi
+ ;;
+ *)
+ os=netbsd
+ ;;
+ esac
+ # The OS release
+ # Debian GNU/NetBSD machines have a different userland, and
+ # thus, need a distinct triplet. However, they do not need
+ # kernel version information, so it can be replaced with a
+ # suitable tag, in the style of linux-gnu.
+ case "${UNAME_VERSION}" in
+ Debian*)
+ release='-gnu'
+ ;;
+ *)
+ release=`echo ${UNAME_RELEASE}|sed -e 's/[-_].*/\./'`
+ ;;
+ esac
+ # Since CPU_TYPE-MANUFACTURER-KERNEL-OPERATING_SYSTEM:
+ # contains redundant information, the shorter form:
+ # CPU_TYPE-MANUFACTURER-OPERATING_SYSTEM is used.
+ echo "${machine}-${os}${release}"
+ exit ;;
+ *:OpenBSD:*:*)
+ UNAME_MACHINE_ARCH=`arch | sed 's/OpenBSD.//'`
+ echo ${UNAME_MACHINE_ARCH}-unknown-openbsd${UNAME_RELEASE}
+ exit ;;
+ *:ekkoBSD:*:*)
+ echo ${UNAME_MACHINE}-unknown-ekkobsd${UNAME_RELEASE}
+ exit ;;
+ *:SolidBSD:*:*)
+ echo ${UNAME_MACHINE}-unknown-solidbsd${UNAME_RELEASE}
+ exit ;;
+ macppc:MirBSD:*:*)
+ echo powerpc-unknown-mirbsd${UNAME_RELEASE}
+ exit ;;
+ *:MirBSD:*:*)
+ echo ${UNAME_MACHINE}-unknown-mirbsd${UNAME_RELEASE}
+ exit ;;
+ alpha:OSF1:*:*)
+ case $UNAME_RELEASE in
+ *4.0)
+ UNAME_RELEASE=`/usr/sbin/sizer -v | awk '{print $3}'`
+ ;;
+ *5.*)
+ UNAME_RELEASE=`/usr/sbin/sizer -v | awk '{print $4}'`
+ ;;
+ esac
+ # According to Compaq, /usr/sbin/psrinfo has been available on
+ # OSF/1 and Tru64 systems produced since 1995. I hope that
+ # covers most systems running today. This code pipes the CPU
+ # types through head -n 1, so we only detect the type of CPU 0.
+ ALPHA_CPU_TYPE=`/usr/sbin/psrinfo -v | sed -n -e 's/^ The alpha \(.*\) processor.*$/\1/p' | head -n 1`
+ case "$ALPHA_CPU_TYPE" in
+ "EV4 (21064)")
+ UNAME_MACHINE="alpha" ;;
+ "EV4.5 (21064)")
+ UNAME_MACHINE="alpha" ;;
+ "LCA4 (21066/21068)")
+ UNAME_MACHINE="alpha" ;;
+ "EV5 (21164)")
+ UNAME_MACHINE="alphaev5" ;;
+ "EV5.6 (21164A)")
+ UNAME_MACHINE="alphaev56" ;;
+ "EV5.6 (21164PC)")
+ UNAME_MACHINE="alphapca56" ;;
+ "EV5.7 (21164PC)")
+ UNAME_MACHINE="alphapca57" ;;
+ "EV6 (21264)")
+ UNAME_MACHINE="alphaev6" ;;
+ "EV6.7 (21264A)")
+ UNAME_MACHINE="alphaev67" ;;
+ "EV6.8CB (21264C)")
+ UNAME_MACHINE="alphaev68" ;;
+ "EV6.8AL (21264B)")
+ UNAME_MACHINE="alphaev68" ;;
+ "EV6.8CX (21264D)")
+ UNAME_MACHINE="alphaev68" ;;
+ "EV6.9A (21264/EV69A)")
+ UNAME_MACHINE="alphaev69" ;;
+ "EV7 (21364)")
+ UNAME_MACHINE="alphaev7" ;;
+ "EV7.9 (21364A)")
+ UNAME_MACHINE="alphaev79" ;;
+ esac
+ # A Pn.n version is a patched version.
+ # A Vn.n version is a released version.
+ # A Tn.n version is a released field test version.
+ # A Xn.n version is an unreleased experimental baselevel.
+ # 1.2 uses "1.2" for uname -r.
+ echo ${UNAME_MACHINE}-dec-osf`echo ${UNAME_RELEASE} | sed -e 's/^[PVTX]//' | tr 'ABCDEFGHIJKLMNOPQRSTUVWXYZ' 'abcdefghijklmnopqrstuvwxyz'`
+ exit ;;
+ Alpha\ *:Windows_NT*:*)
+ # How do we know it's Interix rather than the generic POSIX subsystem?
+ # Should we change UNAME_MACHINE based on the output of uname instead
+ # of the specific Alpha model?
+ echo alpha-pc-interix
+ exit ;;
+ 21064:Windows_NT:50:3)
+ echo alpha-dec-winnt3.5
+ exit ;;
+ Amiga*:UNIX_System_V:4.0:*)
+ echo m68k-unknown-sysv4
+ exit ;;
+ *:[Aa]miga[Oo][Ss]:*:*)
+ echo ${UNAME_MACHINE}-unknown-amigaos
+ exit ;;
+ *:[Mm]orph[Oo][Ss]:*:*)
+ echo ${UNAME_MACHINE}-unknown-morphos
+ exit ;;
+ *:OS/390:*:*)
+ echo i370-ibm-openedition
+ exit ;;
+ *:z/VM:*:*)
+ echo s390-ibm-zvmoe
+ exit ;;
+ *:OS400:*:*)
+ echo powerpc-ibm-os400
+ exit ;;
+ arm:RISC*:1.[012]*:*|arm:riscix:1.[012]*:*)
+ echo arm-acorn-riscix${UNAME_RELEASE}
+ exit ;;
+ arm:riscos:*:*|arm:RISCOS:*:*)
+ echo arm-unknown-riscos
+ exit ;;
+ SR2?01:HI-UX/MPP:*:* | SR8000:HI-UX/MPP:*:*)
+ echo hppa1.1-hitachi-hiuxmpp
+ exit ;;
+ Pyramid*:OSx*:*:* | MIS*:OSx*:*:* | MIS*:SMP_DC-OSx*:*:*)
+ # akee@wpdis03.wpafb.af.mil (Earle F. Ake) contributed MIS and NILE.
+ if test "`(/bin/universe) 2>/dev/null`" = att ; then
+ echo pyramid-pyramid-sysv3
+ else
+ echo pyramid-pyramid-bsd
+ fi
+ exit ;;
+ NILE*:*:*:dcosx)
+ echo pyramid-pyramid-svr4
+ exit ;;
+ DRS?6000:unix:4.0:6*)
+ echo sparc-icl-nx6
+ exit ;;
+ DRS?6000:UNIX_SV:4.2*:7* | DRS?6000:isis:4.2*:7*)
+ case `/usr/bin/uname -p` in
+ sparc) echo sparc-icl-nx7; exit ;;
+ esac ;;
+ sun4H:SunOS:5.*:*)
+ echo sparc-hal-solaris2`echo ${UNAME_RELEASE}|sed -e 's/[^.]*//'`
+ exit ;;
+ sun4*:SunOS:5.*:* | tadpole*:SunOS:5.*:*)
+ echo sparc-sun-solaris2`echo ${UNAME_RELEASE}|sed -e 's/[^.]*//'`
+ exit ;;
+ i86pc:SunOS:5.*:* | i86xen:SunOS:5.*:*)
+ echo i386-pc-solaris2`echo ${UNAME_RELEASE}|sed -e 's/[^.]*//'`
+ exit ;;
+ sun4*:SunOS:6*:*)
+ # According to config.sub, this is the proper way to canonicalize
+ # SunOS6. Hard to guess exactly what SunOS6 will be like, but
+ # it's likely to be more like Solaris than SunOS4.
+ echo sparc-sun-solaris3`echo ${UNAME_RELEASE}|sed -e 's/[^.]*//'`
+ exit ;;
+ sun4*:SunOS:*:*)
+ case "`/usr/bin/arch -k`" in
+ Series*|S4*)
+ UNAME_RELEASE=`uname -v`
+ ;;
+ esac
+ # Japanese Language versions have a version number like `4.1.3-JL'.
+ echo sparc-sun-sunos`echo ${UNAME_RELEASE}|sed -e 's/-/_/'`
+ exit ;;
+ sun3*:SunOS:*:*)
+ echo m68k-sun-sunos${UNAME_RELEASE}
+ exit ;;
+ sun*:*:4.2BSD:*)
+ UNAME_RELEASE=`(sed 1q /etc/motd | awk '{print substr($5,1,3)}') 2>/dev/null`
+ test "x${UNAME_RELEASE}" = "x" && UNAME_RELEASE=3
+ case "`/bin/arch`" in
+ sun3)
+ echo m68k-sun-sunos${UNAME_RELEASE}
+ ;;
+ sun4)
+ echo sparc-sun-sunos${UNAME_RELEASE}
+ ;;
+ esac
+ exit ;;
+ aushp:SunOS:*:*)
+ echo sparc-auspex-sunos${UNAME_RELEASE}
+ exit ;;
+ # The situation for MiNT is a little confusing. The machine name
+ # can be virtually everything (everything which is not
+ # "atarist" or "atariste" at least should have a processor
+ # > m68000). The system name ranges from "MiNT" over "FreeMiNT"
+ # to the lowercase version "mint" (or "freemint"). Finally
+ # the system name "TOS" denotes a system which is actually not
+ # MiNT. But MiNT is downward compatible to TOS, so this should
+ # be no problem.
+ atarist[e]:*MiNT:*:* | atarist[e]:*mint:*:* | atarist[e]:*TOS:*:*)
+ echo m68k-atari-mint${UNAME_RELEASE}
+ exit ;;
+ atari*:*MiNT:*:* | atari*:*mint:*:* | atarist[e]:*TOS:*:*)
+ echo m68k-atari-mint${UNAME_RELEASE}
+ exit ;;
+ *falcon*:*MiNT:*:* | *falcon*:*mint:*:* | *falcon*:*TOS:*:*)
+ echo m68k-atari-mint${UNAME_RELEASE}
+ exit ;;
+ milan*:*MiNT:*:* | milan*:*mint:*:* | *milan*:*TOS:*:*)
+ echo m68k-milan-mint${UNAME_RELEASE}
+ exit ;;
+ hades*:*MiNT:*:* | hades*:*mint:*:* | *hades*:*TOS:*:*)
+ echo m68k-hades-mint${UNAME_RELEASE}
+ exit ;;
+ *:*MiNT:*:* | *:*mint:*:* | *:*TOS:*:*)
+ echo m68k-unknown-mint${UNAME_RELEASE}
+ exit ;;
+ m68k:machten:*:*)
+ echo m68k-apple-machten${UNAME_RELEASE}
+ exit ;;
+ powerpc:machten:*:*)
+ echo powerpc-apple-machten${UNAME_RELEASE}
+ exit ;;
+ RISC*:Mach:*:*)
+ echo mips-dec-mach_bsd4.3
+ exit ;;
+ RISC*:ULTRIX:*:*)
+ echo mips-dec-ultrix${UNAME_RELEASE}
+ exit ;;
+ VAX*:ULTRIX*:*:*)
+ echo vax-dec-ultrix${UNAME_RELEASE}
+ exit ;;
+ 2020:CLIX:*:* | 2430:CLIX:*:*)
+ echo clipper-intergraph-clix${UNAME_RELEASE}
+ exit ;;
+ mips:*:*:UMIPS | mips:*:*:RISCos)
+ eval $set_cc_for_build
+ sed 's/^ //' << EOF >$dummy.c
+#ifdef __cplusplus
+#include <stdio.h> /* for printf() prototype */
+ int main (int argc, char *argv[]) {
+#else
+ int main (argc, argv) int argc; char *argv[]; {
+#endif
+ #if defined (host_mips) && defined (MIPSEB)
+ #if defined (SYSTYPE_SYSV)
+ printf ("mips-mips-riscos%ssysv\n", argv[1]); exit (0);
+ #endif
+ #if defined (SYSTYPE_SVR4)
+ printf ("mips-mips-riscos%ssvr4\n", argv[1]); exit (0);
+ #endif
+ #if defined (SYSTYPE_BSD43) || defined(SYSTYPE_BSD)
+ printf ("mips-mips-riscos%sbsd\n", argv[1]); exit (0);
+ #endif
+ #endif
+ exit (-1);
+ }
+EOF
+ $CC_FOR_BUILD -o $dummy $dummy.c &&
+ dummyarg=`echo "${UNAME_RELEASE}" | sed -n 's/\([0-9]*\).*/\1/p'` &&
+ SYSTEM_NAME=`$dummy $dummyarg` &&
+ { echo "$SYSTEM_NAME"; exit; }
+ echo mips-mips-riscos${UNAME_RELEASE}
+ exit ;;
+ Motorola:PowerMAX_OS:*:*)
+ echo powerpc-motorola-powermax
+ exit ;;
+ Motorola:*:4.3:PL8-*)
+ echo powerpc-harris-powermax
+ exit ;;
+ Night_Hawk:*:*:PowerMAX_OS | Synergy:PowerMAX_OS:*:*)
+ echo powerpc-harris-powermax
+ exit ;;
+ Night_Hawk:Power_UNIX:*:*)
+ echo powerpc-harris-powerunix
+ exit ;;
+ m88k:CX/UX:7*:*)
+ echo m88k-harris-cxux7
+ exit ;;
+ m88k:*:4*:R4*)
+ echo m88k-motorola-sysv4
+ exit ;;
+ m88k:*:3*:R3*)
+ echo m88k-motorola-sysv3
+ exit ;;
+ AViiON:dgux:*:*)
+ # DG/UX returns AViiON for all architectures
+ UNAME_PROCESSOR=`/usr/bin/uname -p`
+ if [ $UNAME_PROCESSOR = mc88100 ] || [ $UNAME_PROCESSOR = mc88110 ]
+ then
+ if [ ${TARGET_BINARY_INTERFACE}x = m88kdguxelfx ] || \
+ [ ${TARGET_BINARY_INTERFACE}x = x ]
+ then
+ echo m88k-dg-dgux${UNAME_RELEASE}
+ else
+ echo m88k-dg-dguxbcs${UNAME_RELEASE}
+ fi
+ else
+ echo i586-dg-dgux${UNAME_RELEASE}
+ fi
+ exit ;;
+ M88*:DolphinOS:*:*) # DolphinOS (SVR3)
+ echo m88k-dolphin-sysv3
+ exit ;;
+ M88*:*:R3*:*)
+ # Delta 88k system running SVR3
+ echo m88k-motorola-sysv3
+ exit ;;
+ XD88*:*:*:*) # Tektronix XD88 system running UTekV (SVR3)
+ echo m88k-tektronix-sysv3
+ exit ;;
+ Tek43[0-9][0-9]:UTek:*:*) # Tektronix 4300 system running UTek (BSD)
+ echo m68k-tektronix-bsd
+ exit ;;
+ *:IRIX*:*:*)
+ echo mips-sgi-irix`echo ${UNAME_RELEASE}|sed -e 's/-/_/g'`
+ exit ;;
+ ????????:AIX?:[12].1:2) # AIX 2.2.1 or AIX 2.1.1 is RT/PC AIX.
+ echo romp-ibm-aix # uname -m gives an 8 hex-code CPU id
+ exit ;; # Note that: echo "'`uname -s`'" gives 'AIX '
+ i*86:AIX:*:*)
+ echo i386-ibm-aix
+ exit ;;
+ ia64:AIX:*:*)
+ if [ -x /usr/bin/oslevel ] ; then
+ IBM_REV=`/usr/bin/oslevel`
+ else
+ IBM_REV=${UNAME_VERSION}.${UNAME_RELEASE}
+ fi
+ echo ${UNAME_MACHINE}-ibm-aix${IBM_REV}
+ exit ;;
+ *:AIX:2:3)
+ if grep bos325 /usr/include/stdio.h >/dev/null 2>&1; then
+ eval $set_cc_for_build
+ sed 's/^ //' << EOF >$dummy.c
+ #include <sys/systemcfg.h>
+
+ main()
+ {
+ if (!__power_pc())
+ exit(1);
+ puts("powerpc-ibm-aix3.2.5");
+ exit(0);
+ }
+EOF
+ if $CC_FOR_BUILD -o $dummy $dummy.c && SYSTEM_NAME=`$dummy`
+ then
+ echo "$SYSTEM_NAME"
+ else
+ echo rs6000-ibm-aix3.2.5
+ fi
+ elif grep bos324 /usr/include/stdio.h >/dev/null 2>&1; then
+ echo rs6000-ibm-aix3.2.4
+ else
+ echo rs6000-ibm-aix3.2
+ fi
+ exit ;;
+ *:AIX:*:[456])
+ IBM_CPU_ID=`/usr/sbin/lsdev -C -c processor -S available | sed 1q | awk '{ print $1 }'`
+ if /usr/sbin/lsattr -El ${IBM_CPU_ID} | grep ' POWER' >/dev/null 2>&1; then
+ IBM_ARCH=rs6000
+ else
+ IBM_ARCH=powerpc
+ fi
+ if [ -x /usr/bin/oslevel ] ; then
+ IBM_REV=`/usr/bin/oslevel`
+ else
+ IBM_REV=${UNAME_VERSION}.${UNAME_RELEASE}
+ fi
+ echo ${IBM_ARCH}-ibm-aix${IBM_REV}
+ exit ;;
+ *:AIX:*:*)
+ echo rs6000-ibm-aix
+ exit ;;
+ ibmrt:4.4BSD:*|romp-ibm:BSD:*)
+ echo romp-ibm-bsd4.4
+ exit ;;
+ ibmrt:*BSD:*|romp-ibm:BSD:*) # covers RT/PC BSD and
+ echo romp-ibm-bsd${UNAME_RELEASE} # 4.3 with uname added to
+ exit ;; # report: romp-ibm BSD 4.3
+ *:BOSX:*:*)
+ echo rs6000-bull-bosx
+ exit ;;
+ DPX/2?00:B.O.S.:*:*)
+ echo m68k-bull-sysv3
+ exit ;;
+ 9000/[34]??:4.3bsd:1.*:*)
+ echo m68k-hp-bsd
+ exit ;;
+ hp300:4.4BSD:*:* | 9000/[34]??:4.3bsd:2.*:*)
+ echo m68k-hp-bsd4.4
+ exit ;;
+ 9000/[34678]??:HP-UX:*:*)
+ HPUX_REV=`echo ${UNAME_RELEASE}|sed -e 's/[^.]*.[0B]*//'`
+ case "${UNAME_MACHINE}" in
+ 9000/31? ) HP_ARCH=m68000 ;;
+ 9000/[34]?? ) HP_ARCH=m68k ;;
+ 9000/[678][0-9][0-9])
+ if [ -x /usr/bin/getconf ]; then
+ sc_cpu_version=`/usr/bin/getconf SC_CPU_VERSION 2>/dev/null`
+ sc_kernel_bits=`/usr/bin/getconf SC_KERNEL_BITS 2>/dev/null`
+ case "${sc_cpu_version}" in
+ 523) HP_ARCH="hppa1.0" ;; # CPU_PA_RISC1_0
+ 528) HP_ARCH="hppa1.1" ;; # CPU_PA_RISC1_1
+ 532) # CPU_PA_RISC2_0
+ case "${sc_kernel_bits}" in
+ 32) HP_ARCH="hppa2.0n" ;;
+ 64) HP_ARCH="hppa2.0w" ;;
+ '') HP_ARCH="hppa2.0" ;; # HP-UX 10.20
+ esac ;;
+ esac
+ fi
+ if [ "${HP_ARCH}" = "" ]; then
+ eval $set_cc_for_build
+ sed 's/^ //' << EOF >$dummy.c
+
+ #define _HPUX_SOURCE
+ #include <stdlib.h>
+ #include <unistd.h>
+
+ int main ()
+ {
+ #if defined(_SC_KERNEL_BITS)
+ long bits = sysconf(_SC_KERNEL_BITS);
+ #endif
+ long cpu = sysconf (_SC_CPU_VERSION);
+
+ switch (cpu)
+ {
+ case CPU_PA_RISC1_0: puts ("hppa1.0"); break;
+ case CPU_PA_RISC1_1: puts ("hppa1.1"); break;
+ case CPU_PA_RISC2_0:
+ #if defined(_SC_KERNEL_BITS)
+ switch (bits)
+ {
+ case 64: puts ("hppa2.0w"); break;
+ case 32: puts ("hppa2.0n"); break;
+ default: puts ("hppa2.0"); break;
+ } break;
+ #else /* !defined(_SC_KERNEL_BITS) */
+ puts ("hppa2.0"); break;
+ #endif
+ default: puts ("hppa1.0"); break;
+ }
+ exit (0);
+ }
+EOF
+ (CCOPTS= $CC_FOR_BUILD -o $dummy $dummy.c 2>/dev/null) && HP_ARCH=`$dummy`
+ test -z "$HP_ARCH" && HP_ARCH=hppa
+ fi ;;
+ esac
+ if [ ${HP_ARCH} = "hppa2.0w" ]
+ then
+ eval $set_cc_for_build
+
+ # hppa2.0w-hp-hpux* has a 64-bit kernel and a compiler generating
+ # 32-bit code. hppa64-hp-hpux* has the same kernel and a compiler
+ # generating 64-bit code. GNU and HP use different nomenclature:
+ #
+ # $ CC_FOR_BUILD=cc ./config.guess
+ # => hppa2.0w-hp-hpux11.23
+ # $ CC_FOR_BUILD="cc +DA2.0w" ./config.guess
+ # => hppa64-hp-hpux11.23
+
+ if echo __LP64__ | (CCOPTS= $CC_FOR_BUILD -E - 2>/dev/null) |
+ grep __LP64__ >/dev/null
+ then
+ HP_ARCH="hppa2.0w"
+ else
+ HP_ARCH="hppa64"
+ fi
+ fi
+ echo ${HP_ARCH}-hp-hpux${HPUX_REV}
+ exit ;;
+ ia64:HP-UX:*:*)
+ HPUX_REV=`echo ${UNAME_RELEASE}|sed -e 's/[^.]*.[0B]*//'`
+ echo ia64-hp-hpux${HPUX_REV}
+ exit ;;
+ 3050*:HI-UX:*:*)
+ eval $set_cc_for_build
+ sed 's/^ //' << EOF >$dummy.c
+ #include <unistd.h>
+ int
+ main ()
+ {
+ long cpu = sysconf (_SC_CPU_VERSION);
+ /* The order matters, because CPU_IS_HP_MC68K erroneously returns
+ true for CPU_PA_RISC1_0. CPU_IS_PA_RISC returns correct
+ results, however. */
+ if (CPU_IS_PA_RISC (cpu))
+ {
+ switch (cpu)
+ {
+ case CPU_PA_RISC1_0: puts ("hppa1.0-hitachi-hiuxwe2"); break;
+ case CPU_PA_RISC1_1: puts ("hppa1.1-hitachi-hiuxwe2"); break;
+ case CPU_PA_RISC2_0: puts ("hppa2.0-hitachi-hiuxwe2"); break;
+ default: puts ("hppa-hitachi-hiuxwe2"); break;
+ }
+ }
+ else if (CPU_IS_HP_MC68K (cpu))
+ puts ("m68k-hitachi-hiuxwe2");
+ else puts ("unknown-hitachi-hiuxwe2");
+ exit (0);
+ }
+EOF
+ $CC_FOR_BUILD -o $dummy $dummy.c && SYSTEM_NAME=`$dummy` &&
+ { echo "$SYSTEM_NAME"; exit; }
+ echo unknown-hitachi-hiuxwe2
+ exit ;;
+ 9000/7??:4.3bsd:*:* | 9000/8?[79]:4.3bsd:*:* )
+ echo hppa1.1-hp-bsd
+ exit ;;
+ 9000/8??:4.3bsd:*:*)
+ echo hppa1.0-hp-bsd
+ exit ;;
+ *9??*:MPE/iX:*:* | *3000*:MPE/iX:*:*)
+ echo hppa1.0-hp-mpeix
+ exit ;;
+ hp7??:OSF1:*:* | hp8?[79]:OSF1:*:* )
+ echo hppa1.1-hp-osf
+ exit ;;
+ hp8??:OSF1:*:*)
+ echo hppa1.0-hp-osf
+ exit ;;
+ i*86:OSF1:*:*)
+ if [ -x /usr/sbin/sysversion ] ; then
+ echo ${UNAME_MACHINE}-unknown-osf1mk
+ else
+ echo ${UNAME_MACHINE}-unknown-osf1
+ fi
+ exit ;;
+ parisc*:Lites*:*:*)
+ echo hppa1.1-hp-lites
+ exit ;;
+ C1*:ConvexOS:*:* | convex:ConvexOS:C1*:*)
+ echo c1-convex-bsd
+ exit ;;
+ C2*:ConvexOS:*:* | convex:ConvexOS:C2*:*)
+ if getsysinfo -f scalar_acc
+ then echo c32-convex-bsd
+ else echo c2-convex-bsd
+ fi
+ exit ;;
+ C34*:ConvexOS:*:* | convex:ConvexOS:C34*:*)
+ echo c34-convex-bsd
+ exit ;;
+ C38*:ConvexOS:*:* | convex:ConvexOS:C38*:*)
+ echo c38-convex-bsd
+ exit ;;
+ C4*:ConvexOS:*:* | convex:ConvexOS:C4*:*)
+ echo c4-convex-bsd
+ exit ;;
+ CRAY*Y-MP:*:*:*)
+ echo ymp-cray-unicos${UNAME_RELEASE} | sed -e 's/\.[^.]*$/.X/'
+ exit ;;
+ CRAY*[A-Z]90:*:*:*)
+ echo ${UNAME_MACHINE}-cray-unicos${UNAME_RELEASE} \
+ | sed -e 's/CRAY.*\([A-Z]90\)/\1/' \
+ -e y/ABCDEFGHIJKLMNOPQRSTUVWXYZ/abcdefghijklmnopqrstuvwxyz/ \
+ -e 's/\.[^.]*$/.X/'
+ exit ;;
+ CRAY*TS:*:*:*)
+ echo t90-cray-unicos${UNAME_RELEASE} | sed -e 's/\.[^.]*$/.X/'
+ exit ;;
+ CRAY*T3E:*:*:*)
+ echo alphaev5-cray-unicosmk${UNAME_RELEASE} | sed -e 's/\.[^.]*$/.X/'
+ exit ;;
+ CRAY*SV1:*:*:*)
+ echo sv1-cray-unicos${UNAME_RELEASE} | sed -e 's/\.[^.]*$/.X/'
+ exit ;;
+ *:UNICOS/mp:*:*)
+ echo craynv-cray-unicosmp${UNAME_RELEASE} | sed -e 's/\.[^.]*$/.X/'
+ exit ;;
+ F30[01]:UNIX_System_V:*:* | F700:UNIX_System_V:*:*)
+ FUJITSU_PROC=`uname -m | tr 'ABCDEFGHIJKLMNOPQRSTUVWXYZ' 'abcdefghijklmnopqrstuvwxyz'`
+ FUJITSU_SYS=`uname -p | tr 'ABCDEFGHIJKLMNOPQRSTUVWXYZ' 'abcdefghijklmnopqrstuvwxyz' | sed -e 's/\///'`
+ FUJITSU_REL=`echo ${UNAME_RELEASE} | sed -e 's/ /_/'`
+ echo "${FUJITSU_PROC}-fujitsu-${FUJITSU_SYS}${FUJITSU_REL}"
+ exit ;;
+ 5000:UNIX_System_V:4.*:*)
+ FUJITSU_SYS=`uname -p | tr 'ABCDEFGHIJKLMNOPQRSTUVWXYZ' 'abcdefghijklmnopqrstuvwxyz' | sed -e 's/\///'`
+ FUJITSU_REL=`echo ${UNAME_RELEASE} | tr 'ABCDEFGHIJKLMNOPQRSTUVWXYZ' 'abcdefghijklmnopqrstuvwxyz' | sed -e 's/ /_/'`
+ echo "sparc-fujitsu-${FUJITSU_SYS}${FUJITSU_REL}"
+ exit ;;
+ i*86:BSD/386:*:* | i*86:BSD/OS:*:* | *:Ascend\ Embedded/OS:*:*)
+ echo ${UNAME_MACHINE}-pc-bsdi${UNAME_RELEASE}
+ exit ;;
+ sparc*:BSD/OS:*:*)
+ echo sparc-unknown-bsdi${UNAME_RELEASE}
+ exit ;;
+ *:BSD/OS:*:*)
+ echo ${UNAME_MACHINE}-unknown-bsdi${UNAME_RELEASE}
+ exit ;;
+ *:FreeBSD:*:*)
+ case ${UNAME_MACHINE} in
+ pc98)
+ echo i386-unknown-freebsd`echo ${UNAME_RELEASE}|sed -e 's/[-(].*//'` ;;
+ amd64)
+ echo x86_64-unknown-freebsd`echo ${UNAME_RELEASE}|sed -e 's/[-(].*//'` ;;
+ *)
+ echo ${UNAME_MACHINE}-unknown-freebsd`echo ${UNAME_RELEASE}|sed -e 's/[-(].*//'` ;;
+ esac
+ exit ;;
+ i*:CYGWIN*:*)
+ echo ${UNAME_MACHINE}-pc-cygwin
+ exit ;;
+ *:MINGW*:*)
+ echo ${UNAME_MACHINE}-pc-mingw32
+ exit ;;
+ i*:windows32*:*)
+ # uname -m includes "-pc" on this system.
+ echo ${UNAME_MACHINE}-mingw32
+ exit ;;
+ i*:PW*:*)
+ echo ${UNAME_MACHINE}-pc-pw32
+ exit ;;
+ *:Interix*:[3456]*)
+ case ${UNAME_MACHINE} in
+ x86)
+ echo i586-pc-interix${UNAME_RELEASE}
+ exit ;;
+ EM64T | authenticamd)
+ echo x86_64-unknown-interix${UNAME_RELEASE}
+ exit ;;
+ IA64)
+ echo ia64-unknown-interix${UNAME_RELEASE}
+ exit ;;
+ esac ;;
+ [345]86:Windows_95:* | [345]86:Windows_98:* | [345]86:Windows_NT:*)
+ echo i${UNAME_MACHINE}-pc-mks
+ exit ;;
+ i*:Windows_NT*:* | Pentium*:Windows_NT*:*)
+ # How do we know it's Interix rather than the generic POSIX subsystem?
+ # It also conflicts with pre-2.0 versions of AT&T UWIN. Should we
+ # UNAME_MACHINE based on the output of uname instead of i386?
+ echo i586-pc-interix
+ exit ;;
+ i*:UWIN*:*)
+ echo ${UNAME_MACHINE}-pc-uwin
+ exit ;;
+ amd64:CYGWIN*:*:* | x86_64:CYGWIN*:*:*)
+ echo x86_64-unknown-cygwin
+ exit ;;
+ p*:CYGWIN*:*)
+ echo powerpcle-unknown-cygwin
+ exit ;;
+ prep*:SunOS:5.*:*)
+ echo powerpcle-unknown-solaris2`echo ${UNAME_RELEASE}|sed -e 's/[^.]*//'`
+ exit ;;
+ *:GNU:*:*)
+ # the GNU system
+ echo `echo ${UNAME_MACHINE}|sed -e 's,[-/].*$,,'`-unknown-gnu`echo ${UNAME_RELEASE}|sed -e 's,/.*$,,'`
+ exit ;;
+ *:GNU/*:*:*)
+ # other systems with GNU libc and userland
+ echo ${UNAME_MACHINE}-unknown-`echo ${UNAME_SYSTEM} | sed 's,^[^/]*/,,' | tr '[A-Z]' '[a-z]'``echo ${UNAME_RELEASE}|sed -e 's/[-(].*//'`-gnu
+ exit ;;
+ i*86:Minix:*:*)
+ echo ${UNAME_MACHINE}-pc-minix
+ exit ;;
+ arm*:Linux:*:*)
+ eval $set_cc_for_build
+ if echo __ARM_EABI__ | $CC_FOR_BUILD -E - 2>/dev/null \
+ | grep -q __ARM_EABI__
+ then
+ echo ${UNAME_MACHINE}-unknown-linux-gnu
+ else
+ echo ${UNAME_MACHINE}-unknown-linux-gnueabi
+ fi
+ exit ;;
+ avr32*:Linux:*:*)
+ echo ${UNAME_MACHINE}-unknown-linux-gnu
+ exit ;;
+ cris:Linux:*:*)
+ echo cris-axis-linux-gnu
+ exit ;;
+ crisv32:Linux:*:*)
+ echo crisv32-axis-linux-gnu
+ exit ;;
+ frv:Linux:*:*)
+ echo frv-unknown-linux-gnu
+ exit ;;
+ ia64:Linux:*:*)
+ echo ${UNAME_MACHINE}-unknown-linux-gnu
+ exit ;;
+ m32r*:Linux:*:*)
+ echo ${UNAME_MACHINE}-unknown-linux-gnu
+ exit ;;
+ m68*:Linux:*:*)
+ echo ${UNAME_MACHINE}-unknown-linux-gnu
+ exit ;;
+ mips:Linux:*:*)
+ eval $set_cc_for_build
+ sed 's/^ //' << EOF >$dummy.c
+ #undef CPU
+ #undef mips
+ #undef mipsel
+ #if defined(__MIPSEL__) || defined(__MIPSEL) || defined(_MIPSEL) || defined(MIPSEL)
+ CPU=mipsel
+ #else
+ #if defined(__MIPSEB__) || defined(__MIPSEB) || defined(_MIPSEB) || defined(MIPSEB)
+ CPU=mips
+ #else
+ CPU=
+ #endif
+ #endif
+EOF
+ eval "`$CC_FOR_BUILD -E $dummy.c 2>/dev/null | sed -n '
+ /^CPU/{
+ s: ::g
+ p
+ }'`"
+ test x"${CPU}" != x && { echo "${CPU}-unknown-linux-gnu"; exit; }
+ ;;
+ mips64:Linux:*:*)
+ eval $set_cc_for_build
+ sed 's/^ //' << EOF >$dummy.c
+ #undef CPU
+ #undef mips64
+ #undef mips64el
+ #if defined(__MIPSEL__) || defined(__MIPSEL) || defined(_MIPSEL) || defined(MIPSEL)
+ CPU=mips64el
+ #else
+ #if defined(__MIPSEB__) || defined(__MIPSEB) || defined(_MIPSEB) || defined(MIPSEB)
+ CPU=mips64
+ #else
+ CPU=
+ #endif
+ #endif
+EOF
+ eval "`$CC_FOR_BUILD -E $dummy.c 2>/dev/null | sed -n '
+ /^CPU/{
+ s: ::g
+ p
+ }'`"
+ test x"${CPU}" != x && { echo "${CPU}-unknown-linux-gnu"; exit; }
+ ;;
+ or32:Linux:*:*)
+ echo or32-unknown-linux-gnu
+ exit ;;
+ ppc:Linux:*:*)
+ echo powerpc-unknown-linux-gnu
+ exit ;;
+ ppc64:Linux:*:*)
+ echo powerpc64-unknown-linux-gnu
+ exit ;;
+ alpha:Linux:*:*)
+ case `sed -n '/^cpu model/s/^.*: \(.*\)/\1/p' < /proc/cpuinfo` in
+ EV5) UNAME_MACHINE=alphaev5 ;;
+ EV56) UNAME_MACHINE=alphaev56 ;;
+ PCA56) UNAME_MACHINE=alphapca56 ;;
+ PCA57) UNAME_MACHINE=alphapca56 ;;
+ EV6) UNAME_MACHINE=alphaev6 ;;
+ EV67) UNAME_MACHINE=alphaev67 ;;
+ EV68*) UNAME_MACHINE=alphaev68 ;;
+ esac
+ objdump --private-headers /bin/sh | grep ld.so.1 >/dev/null
+ if test "$?" = 0 ; then LIBC="libc1" ; else LIBC="" ; fi
+ echo ${UNAME_MACHINE}-unknown-linux-gnu${LIBC}
+ exit ;;
+ parisc:Linux:*:* | hppa:Linux:*:*)
+ # Look for CPU level
+ case `grep '^cpu[^a-z]*:' /proc/cpuinfo 2>/dev/null | cut -d' ' -f2` in
+ PA7*) echo hppa1.1-unknown-linux-gnu ;;
+ PA8*) echo hppa2.0-unknown-linux-gnu ;;
+ *) echo hppa-unknown-linux-gnu ;;
+ esac
+ exit ;;
+ parisc64:Linux:*:* | hppa64:Linux:*:*)
+ echo hppa64-unknown-linux-gnu
+ exit ;;
+ s390:Linux:*:* | s390x:Linux:*:*)
+ echo ${UNAME_MACHINE}-ibm-linux
+ exit ;;
+ sh64*:Linux:*:*)
+ echo ${UNAME_MACHINE}-unknown-linux-gnu
+ exit ;;
+ sh*:Linux:*:*)
+ echo ${UNAME_MACHINE}-unknown-linux-gnu
+ exit ;;
+ sparc:Linux:*:* | sparc64:Linux:*:*)
+ echo ${UNAME_MACHINE}-unknown-linux-gnu
+ exit ;;
+ vax:Linux:*:*)
+ echo ${UNAME_MACHINE}-dec-linux-gnu
+ exit ;;
+ x86_64:Linux:*:*)
+ echo x86_64-unknown-linux-gnu
+ exit ;;
+ xtensa*:Linux:*:*)
+ echo ${UNAME_MACHINE}-unknown-linux-gnu
+ exit ;;
+ i*86:Linux:*:*)
+ # The BFD linker knows what the default object file format is, so
+ # first see if it will tell us. cd to the root directory to prevent
+ # problems with other programs or directories called `ld' in the path.
+ # Set LC_ALL=C to ensure ld outputs messages in English.
+ ld_supported_targets=`cd /; LC_ALL=C ld --help 2>&1 \
+ | sed -ne '/supported targets:/!d
+ s/[ ][ ]*/ /g
+ s/.*supported targets: *//
+ s/ .*//
+ p'`
+ case "$ld_supported_targets" in
+ elf32-i386)
+ TENTATIVE="${UNAME_MACHINE}-pc-linux-gnu"
+ ;;
+ a.out-i386-linux)
+ echo "${UNAME_MACHINE}-pc-linux-gnuaout"
+ exit ;;
+ "")
+ # Either a pre-BFD a.out linker (linux-gnuoldld) or
+ # one that does not give us useful --help.
+ echo "${UNAME_MACHINE}-pc-linux-gnuoldld"
+ exit ;;
+ esac
+ # Determine whether the default compiler is a.out or elf
+ eval $set_cc_for_build
+ sed 's/^ //' << EOF >$dummy.c
+ #include <features.h>
+ #ifdef __ELF__
+ # ifdef __GLIBC__
+ # if __GLIBC__ >= 2
+ LIBC=gnu
+ # else
+ LIBC=gnulibc1
+ # endif
+ # else
+ LIBC=gnulibc1
+ # endif
+ #else
+ #if defined(__INTEL_COMPILER) || defined(__PGI) || defined(__SUNPRO_C) || defined(__SUNPRO_CC)
+ LIBC=gnu
+ #else
+ LIBC=gnuaout
+ #endif
+ #endif
+ #ifdef __dietlibc__
+ LIBC=dietlibc
+ #endif
+EOF
+ eval "`$CC_FOR_BUILD -E $dummy.c 2>/dev/null | sed -n '
+ /^LIBC/{
+ s: ::g
+ p
+ }'`"
+ test x"${LIBC}" != x && {
+ echo "${UNAME_MACHINE}-pc-linux-${LIBC}"
+ exit
+ }
+ test x"${TENTATIVE}" != x && { echo "${TENTATIVE}"; exit; }
+ ;;
+ i*86:DYNIX/ptx:4*:*)
+ # ptx 4.0 does uname -s correctly, with DYNIX/ptx in there.
+ # earlier versions are messed up and put the nodename in both
+ # sysname and nodename.
+ echo i386-sequent-sysv4
+ exit ;;
+ i*86:UNIX_SV:4.2MP:2.*)
+ # Unixware is an offshoot of SVR4, but it has its own version
+ # number series starting with 2...
+ # I am not positive that other SVR4 systems won't match this,
+ # I just have to hope. -- rms.
+ # Use sysv4.2uw... so that sysv4* matches it.
+ echo ${UNAME_MACHINE}-pc-sysv4.2uw${UNAME_VERSION}
+ exit ;;
+ i*86:OS/2:*:*)
+ # If we were able to find `uname', then EMX Unix compatibility
+ # is probably installed.
+ echo ${UNAME_MACHINE}-pc-os2-emx
+ exit ;;
+ i*86:XTS-300:*:STOP)
+ echo ${UNAME_MACHINE}-unknown-stop
+ exit ;;
+ i*86:atheos:*:*)
+ echo ${UNAME_MACHINE}-unknown-atheos
+ exit ;;
+ i*86:syllable:*:*)
+ echo ${UNAME_MACHINE}-pc-syllable
+ exit ;;
+ i*86:LynxOS:2.*:* | i*86:LynxOS:3.[01]*:* | i*86:LynxOS:4.0*:*)
+ echo i386-unknown-lynxos${UNAME_RELEASE}
+ exit ;;
+ i*86:*DOS:*:*)
+ echo ${UNAME_MACHINE}-pc-msdosdjgpp
+ exit ;;
+ i*86:*:4.*:* | i*86:SYSTEM_V:4.*:*)
+ UNAME_REL=`echo ${UNAME_RELEASE} | sed 's/\/MP$//'`
+ if grep Novell /usr/include/link.h >/dev/null 2>/dev/null; then
+ echo ${UNAME_MACHINE}-univel-sysv${UNAME_REL}
+ else
+ echo ${UNAME_MACHINE}-pc-sysv${UNAME_REL}
+ fi
+ exit ;;
+ i*86:*:5:[678]*)
+ # UnixWare 7.x, OpenUNIX and OpenServer 6.
+ case `/bin/uname -X | grep "^Machine"` in
+ *486*) UNAME_MACHINE=i486 ;;
+ *Pentium) UNAME_MACHINE=i586 ;;
+ *Pent*|*Celeron) UNAME_MACHINE=i686 ;;
+ esac
+ echo ${UNAME_MACHINE}-unknown-sysv${UNAME_RELEASE}${UNAME_SYSTEM}${UNAME_VERSION}
+ exit ;;
+ i*86:*:3.2:*)
+ if test -f /usr/options/cb.name; then
+ UNAME_REL=`sed -n 's/.*Version //p' </usr/options/cb.name`
+ echo ${UNAME_MACHINE}-pc-isc$UNAME_REL
+ elif /bin/uname -X 2>/dev/null >/dev/null ; then
+ UNAME_REL=`(/bin/uname -X|grep Release|sed -e 's/.*= //')`
+ (/bin/uname -X|grep i80486 >/dev/null) && UNAME_MACHINE=i486
+ (/bin/uname -X|grep '^Machine.*Pentium' >/dev/null) \
+ && UNAME_MACHINE=i586
+ (/bin/uname -X|grep '^Machine.*Pent *II' >/dev/null) \
+ && UNAME_MACHINE=i686
+ (/bin/uname -X|grep '^Machine.*Pentium Pro' >/dev/null) \
+ && UNAME_MACHINE=i686
+ echo ${UNAME_MACHINE}-pc-sco$UNAME_REL
+ else
+ echo ${UNAME_MACHINE}-pc-sysv32
+ fi
+ exit ;;
+ pc:*:*:*)
+ # Left here for compatibility:
+ # uname -m prints for DJGPP always 'pc', but it prints nothing about
+ # the processor, so we play safe by assuming i386.
+ echo i386-pc-msdosdjgpp
+ exit ;;
+ Intel:Mach:3*:*)
+ echo i386-pc-mach3
+ exit ;;
+ paragon:*:*:*)
+ echo i860-intel-osf1
+ exit ;;
+ i860:*:4.*:*) # i860-SVR4
+ if grep Stardent /usr/include/sys/uadmin.h >/dev/null 2>&1 ; then
+ echo i860-stardent-sysv${UNAME_RELEASE} # Stardent Vistra i860-SVR4
+ else # Add other i860-SVR4 vendors below as they are discovered.
+ echo i860-unknown-sysv${UNAME_RELEASE} # Unknown i860-SVR4
+ fi
+ exit ;;
+ mini*:CTIX:SYS*5:*)
+ # "miniframe"
+ echo m68010-convergent-sysv
+ exit ;;
+ mc68k:UNIX:SYSTEM5:3.51m)
+ echo m68k-convergent-sysv
+ exit ;;
+ M680?0:D-NIX:5.3:*)
+ echo m68k-diab-dnix
+ exit ;;
+ M68*:*:R3V[5678]*:*)
+ test -r /sysV68 && { echo 'm68k-motorola-sysv'; exit; } ;;
+ 3[345]??:*:4.0:3.0 | 3[34]??A:*:4.0:3.0 | 3[34]??,*:*:4.0:3.0 | 3[34]??/*:*:4.0:3.0 | 4400:*:4.0:3.0 | 4850:*:4.0:3.0 | SKA40:*:4.0:3.0 | SDS2:*:4.0:3.0 | SHG2:*:4.0:3.0 | S7501*:*:4.0:3.0)
+ OS_REL=''
+ test -r /etc/.relid \
+ && OS_REL=.`sed -n 's/[^ ]* [^ ]* \([0-9][0-9]\).*/\1/p' < /etc/.relid`
+ /bin/uname -p 2>/dev/null | grep 86 >/dev/null \
+ && { echo i486-ncr-sysv4.3${OS_REL}; exit; }
+ /bin/uname -p 2>/dev/null | /bin/grep entium >/dev/null \
+ && { echo i586-ncr-sysv4.3${OS_REL}; exit; } ;;
+ 3[34]??:*:4.0:* | 3[34]??,*:*:4.0:*)
+ /bin/uname -p 2>/dev/null | grep 86 >/dev/null \
+ && { echo i486-ncr-sysv4; exit; } ;;
+ m68*:LynxOS:2.*:* | m68*:LynxOS:3.0*:*)
+ echo m68k-unknown-lynxos${UNAME_RELEASE}
+ exit ;;
+ mc68030:UNIX_System_V:4.*:*)
+ echo m68k-atari-sysv4
+ exit ;;
+ TSUNAMI:LynxOS:2.*:*)
+ echo sparc-unknown-lynxos${UNAME_RELEASE}
+ exit ;;
+ rs6000:LynxOS:2.*:*)
+ echo rs6000-unknown-lynxos${UNAME_RELEASE}
+ exit ;;
+ PowerPC:LynxOS:2.*:* | PowerPC:LynxOS:3.[01]*:* | PowerPC:LynxOS:4.0*:*)
+ echo powerpc-unknown-lynxos${UNAME_RELEASE}
+ exit ;;
+ SM[BE]S:UNIX_SV:*:*)
+ echo mips-dde-sysv${UNAME_RELEASE}
+ exit ;;
+ RM*:ReliantUNIX-*:*:*)
+ echo mips-sni-sysv4
+ exit ;;
+ RM*:SINIX-*:*:*)
+ echo mips-sni-sysv4
+ exit ;;
+ *:SINIX-*:*:*)
+ if uname -p 2>/dev/null >/dev/null ; then
+ UNAME_MACHINE=`(uname -p) 2>/dev/null`
+ echo ${UNAME_MACHINE}-sni-sysv4
+ else
+ echo ns32k-sni-sysv
+ fi
+ exit ;;
+ PENTIUM:*:4.0*:*) # Unisys `ClearPath HMP IX 4000' SVR4/MP effort
+ # says <Richard.M.Bartel@ccMail.Census.GOV>
+ echo i586-unisys-sysv4
+ exit ;;
+ *:UNIX_System_V:4*:FTX*)
+ # From Gerald Hewes <hewes@openmarket.com>.
+ # How about differentiating between stratus architectures? -djm
+ echo hppa1.1-stratus-sysv4
+ exit ;;
+ *:*:*:FTX*)
+ # From seanf@swdc.stratus.com.
+ echo i860-stratus-sysv4
+ exit ;;
+ i*86:VOS:*:*)
+ # From Paul.Green@stratus.com.
+ echo ${UNAME_MACHINE}-stratus-vos
+ exit ;;
+ *:VOS:*:*)
+ # From Paul.Green@stratus.com.
+ echo hppa1.1-stratus-vos
+ exit ;;
+ mc68*:A/UX:*:*)
+ echo m68k-apple-aux${UNAME_RELEASE}
+ exit ;;
+ news*:NEWS-OS:6*:*)
+ echo mips-sony-newsos6
+ exit ;;
+ R[34]000:*System_V*:*:* | R4000:UNIX_SYSV:*:* | R*000:UNIX_SV:*:*)
+ if [ -d /usr/nec ]; then
+ echo mips-nec-sysv${UNAME_RELEASE}
+ else
+ echo mips-unknown-sysv${UNAME_RELEASE}
+ fi
+ exit ;;
+ BeBox:BeOS:*:*) # BeOS running on hardware made by Be, PPC only.
+ echo powerpc-be-beos
+ exit ;;
+ BeMac:BeOS:*:*) # BeOS running on Mac or Mac clone, PPC only.
+ echo powerpc-apple-beos
+ exit ;;
+ BePC:BeOS:*:*) # BeOS running on Intel PC compatible.
+ echo i586-pc-beos
+ exit ;;
+ BePC:Haiku:*:*) # Haiku running on Intel PC compatible.
+ echo i586-pc-haiku
+ exit ;;
+ SX-4:SUPER-UX:*:*)
+ echo sx4-nec-superux${UNAME_RELEASE}
+ exit ;;
+ SX-5:SUPER-UX:*:*)
+ echo sx5-nec-superux${UNAME_RELEASE}
+ exit ;;
+ SX-6:SUPER-UX:*:*)
+ echo sx6-nec-superux${UNAME_RELEASE}
+ exit ;;
+ SX-7:SUPER-UX:*:*)
+ echo sx7-nec-superux${UNAME_RELEASE}
+ exit ;;
+ SX-8:SUPER-UX:*:*)
+ echo sx8-nec-superux${UNAME_RELEASE}
+ exit ;;
+ SX-8R:SUPER-UX:*:*)
+ echo sx8r-nec-superux${UNAME_RELEASE}
+ exit ;;
+ Power*:Rhapsody:*:*)
+ echo powerpc-apple-rhapsody${UNAME_RELEASE}
+ exit ;;
+ *:Rhapsody:*:*)
+ echo ${UNAME_MACHINE}-apple-rhapsody${UNAME_RELEASE}
+ exit ;;
+ *:Darwin:*:*)
+ UNAME_PROCESSOR=`uname -p` || UNAME_PROCESSOR=unknown
+ case $UNAME_PROCESSOR in
+ unknown) UNAME_PROCESSOR=powerpc ;;
+ esac
+ echo ${UNAME_PROCESSOR}-apple-darwin${UNAME_RELEASE}
+ exit ;;
+ *:procnto*:*:* | *:QNX:[0123456789]*:*)
+ UNAME_PROCESSOR=`uname -p`
+ if test "$UNAME_PROCESSOR" = "x86"; then
+ UNAME_PROCESSOR=i386
+ UNAME_MACHINE=pc
+ fi
+ echo ${UNAME_PROCESSOR}-${UNAME_MACHINE}-nto-qnx${UNAME_RELEASE}
+ exit ;;
+ *:QNX:*:4*)
+ echo i386-pc-qnx
+ exit ;;
+ NSE-?:NONSTOP_KERNEL:*:*)
+ echo nse-tandem-nsk${UNAME_RELEASE}
+ exit ;;
+ NSR-?:NONSTOP_KERNEL:*:*)
+ echo nsr-tandem-nsk${UNAME_RELEASE}
+ exit ;;
+ *:NonStop-UX:*:*)
+ echo mips-compaq-nonstopux
+ exit ;;
+ BS2000:POSIX*:*:*)
+ echo bs2000-siemens-sysv
+ exit ;;
+ DS/*:UNIX_System_V:*:*)
+ echo ${UNAME_MACHINE}-${UNAME_SYSTEM}-${UNAME_RELEASE}
+ exit ;;
+ *:Plan9:*:*)
+ # "uname -m" is not consistent, so use $cputype instead. 386
+ # is converted to i386 for consistency with other x86
+ # operating systems.
+ if test "$cputype" = "386"; then
+ UNAME_MACHINE=i386
+ else
+ UNAME_MACHINE="$cputype"
+ fi
+ echo ${UNAME_MACHINE}-unknown-plan9
+ exit ;;
+ *:TOPS-10:*:*)
+ echo pdp10-unknown-tops10
+ exit ;;
+ *:TENEX:*:*)
+ echo pdp10-unknown-tenex
+ exit ;;
+ KS10:TOPS-20:*:* | KL10:TOPS-20:*:* | TYPE4:TOPS-20:*:*)
+ echo pdp10-dec-tops20
+ exit ;;
+ XKL-1:TOPS-20:*:* | TYPE5:TOPS-20:*:*)
+ echo pdp10-xkl-tops20
+ exit ;;
+ *:TOPS-20:*:*)
+ echo pdp10-unknown-tops20
+ exit ;;
+ *:ITS:*:*)
+ echo pdp10-unknown-its
+ exit ;;
+ SEI:*:*:SEIUX)
+ echo mips-sei-seiux${UNAME_RELEASE}
+ exit ;;
+ *:DragonFly:*:*)
+ echo ${UNAME_MACHINE}-unknown-dragonfly`echo ${UNAME_RELEASE}|sed -e 's/[-(].*//'`
+ exit ;;
+ *:*VMS:*:*)
+ UNAME_MACHINE=`(uname -p) 2>/dev/null`
+ case "${UNAME_MACHINE}" in
+ A*) echo alpha-dec-vms ; exit ;;
+ I*) echo ia64-dec-vms ; exit ;;
+ V*) echo vax-dec-vms ; exit ;;
+ esac ;;
+ *:XENIX:*:SysV)
+ echo i386-pc-xenix
+ exit ;;
+ i*86:skyos:*:*)
+ echo ${UNAME_MACHINE}-pc-skyos`echo ${UNAME_RELEASE}` | sed -e 's/ .*$//'
+ exit ;;
+ i*86:rdos:*:*)
+ echo ${UNAME_MACHINE}-pc-rdos
+ exit ;;
+esac
+
+#echo '(No uname command or uname output not recognized.)' 1>&2
+#echo "${UNAME_MACHINE}:${UNAME_SYSTEM}:${UNAME_RELEASE}:${UNAME_VERSION}" 1>&2
+
+eval $set_cc_for_build
+cat >$dummy.c <<EOF
+#ifdef _SEQUENT_
+# include <sys/types.h>
+# include <sys/utsname.h>
+#endif
+main ()
+{
+#if defined (sony)
+#if defined (MIPSEB)
+ /* BFD wants "bsd" instead of "newsos". Perhaps BFD should be changed,
+ I don't know.... */
+ printf ("mips-sony-bsd\n"); exit (0);
+#else
+#include <sys/param.h>
+ printf ("m68k-sony-newsos%s\n",
+#ifdef NEWSOS4
+ "4"
+#else
+ ""
+#endif
+ ); exit (0);
+#endif
+#endif
+
+#if defined (__arm) && defined (__acorn) && defined (__unix)
+ printf ("arm-acorn-riscix\n"); exit (0);
+#endif
+
+#if defined (hp300) && !defined (hpux)
+ printf ("m68k-hp-bsd\n"); exit (0);
+#endif
+
+#if defined (NeXT)
+#if !defined (__ARCHITECTURE__)
+#define __ARCHITECTURE__ "m68k"
+#endif
+ int version;
+ version=`(hostinfo | sed -n 's/.*NeXT Mach \([0-9]*\).*/\1/p') 2>/dev/null`;
+ if (version < 4)
+ printf ("%s-next-nextstep%d\n", __ARCHITECTURE__, version);
+ else
+ printf ("%s-next-openstep%d\n", __ARCHITECTURE__, version);
+ exit (0);
+#endif
+
+#if defined (MULTIMAX) || defined (n16)
+#if defined (UMAXV)
+ printf ("ns32k-encore-sysv\n"); exit (0);
+#else
+#if defined (CMU)
+ printf ("ns32k-encore-mach\n"); exit (0);
+#else
+ printf ("ns32k-encore-bsd\n"); exit (0);
+#endif
+#endif
+#endif
+
+#if defined (__386BSD__)
+ printf ("i386-pc-bsd\n"); exit (0);
+#endif
+
+#if defined (sequent)
+#if defined (i386)
+ printf ("i386-sequent-dynix\n"); exit (0);
+#endif
+#if defined (ns32000)
+ printf ("ns32k-sequent-dynix\n"); exit (0);
+#endif
+#endif
+
+#if defined (_SEQUENT_)
+ struct utsname un;
+
+ uname(&un);
+
+ if (strncmp(un.version, "V2", 2) == 0) {
+ printf ("i386-sequent-ptx2\n"); exit (0);
+ }
+ if (strncmp(un.version, "V1", 2) == 0) { /* XXX is V1 correct? */
+ printf ("i386-sequent-ptx1\n"); exit (0);
+ }
+ printf ("i386-sequent-ptx\n"); exit (0);
+
+#endif
+
+#if defined (vax)
+# if !defined (ultrix)
+# include <sys/param.h>
+# if defined (BSD)
+# if BSD == 43
+ printf ("vax-dec-bsd4.3\n"); exit (0);
+# else
+# if BSD == 199006
+ printf ("vax-dec-bsd4.3reno\n"); exit (0);
+# else
+ printf ("vax-dec-bsd\n"); exit (0);
+# endif
+# endif
+# else
+ printf ("vax-dec-bsd\n"); exit (0);
+# endif
+# else
+ printf ("vax-dec-ultrix\n"); exit (0);
+# endif
+#endif
+
+#if defined (alliant) && defined (i860)
+ printf ("i860-alliant-bsd\n"); exit (0);
+#endif
+
+ exit (1);
+}
+EOF
+
+$CC_FOR_BUILD -o $dummy $dummy.c 2>/dev/null && SYSTEM_NAME=`$dummy` &&
+ { echo "$SYSTEM_NAME"; exit; }
+
+# Apollos put the system type in the environment.
+
+test -d /usr/apollo && { echo ${ISP}-apollo-${SYSTYPE}; exit; }
+
+# Convex versions that predate uname can use getsysinfo(1)
+
+if [ -x /usr/convex/getsysinfo ]
+then
+ case `getsysinfo -f cpu_type` in
+ c1*)
+ echo c1-convex-bsd
+ exit ;;
+ c2*)
+ if getsysinfo -f scalar_acc
+ then echo c32-convex-bsd
+ else echo c2-convex-bsd
+ fi
+ exit ;;
+ c34*)
+ echo c34-convex-bsd
+ exit ;;
+ c38*)
+ echo c38-convex-bsd
+ exit ;;
+ c4*)
+ echo c4-convex-bsd
+ exit ;;
+ esac
+fi
+
+cat >&2 <<EOF
+$0: unable to guess system type
+
+This script, last modified $timestamp, has failed to recognize
+the operating system you are using. It is advised that you
+download the most up to date version of the config scripts from
+
+ http://git.savannah.gnu.org/gitweb/?p=config.git;a=blob_plain;f=config.guess;hb=HEAD
+and
+ http://git.savannah.gnu.org/gitweb/?p=config.git;a=blob_plain;f=config.sub;hb=HEAD
+
+If the version you run ($0) is already up to date, please
+send the following data and any information you think might be
+pertinent to <config-patches@gnu.org> in order to provide the needed
+information to handle your system.
+
+config.guess timestamp = $timestamp
+
+uname -m = `(uname -m) 2>/dev/null || echo unknown`
+uname -r = `(uname -r) 2>/dev/null || echo unknown`
+uname -s = `(uname -s) 2>/dev/null || echo unknown`
+uname -v = `(uname -v) 2>/dev/null || echo unknown`
+
+/usr/bin/uname -p = `(/usr/bin/uname -p) 2>/dev/null`
+/bin/uname -X = `(/bin/uname -X) 2>/dev/null`
+
+hostinfo = `(hostinfo) 2>/dev/null`
+/bin/universe = `(/bin/universe) 2>/dev/null`
+/usr/bin/arch -k = `(/usr/bin/arch -k) 2>/dev/null`
+/bin/arch = `(/bin/arch) 2>/dev/null`
+/usr/bin/oslevel = `(/usr/bin/oslevel) 2>/dev/null`
+/usr/convex/getsysinfo = `(/usr/convex/getsysinfo) 2>/dev/null`
+
+UNAME_MACHINE = ${UNAME_MACHINE}
+UNAME_RELEASE = ${UNAME_RELEASE}
+UNAME_SYSTEM = ${UNAME_SYSTEM}
+UNAME_VERSION = ${UNAME_VERSION}
+EOF
+
+exit 1
+
+# Local variables:
+# eval: (add-hook 'write-file-hooks 'time-stamp)
+# time-stamp-start: "timestamp='"
+# time-stamp-format: "%:y-%02m-%02d"
+# time-stamp-end: "'"
+# End:
--- /dev/null
+#! /bin/sh
+# Configuration validation subroutine script.
+# Copyright (C) 1992, 1993, 1994, 1995, 1996, 1997, 1998, 1999,
+# 2000, 2001, 2002, 2003, 2004, 2005, 2006, 2007, 2008
+# Free Software Foundation, Inc.
+
+timestamp='2008-04-14'
+
+# This file is (in principle) common to ALL GNU software.
+# The presence of a machine in this file suggests that SOME GNU software
+# can handle that machine. It does not imply ALL GNU software can.
+#
+# This file is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 51 Franklin Street - Fifth Floor, Boston, MA
+# 02110-1301, USA.
+#
+# As a special exception to the GNU General Public License, if you
+# distribute this file as part of a program that contains a
+# configuration script generated by Autoconf, you may include it under
+# the same distribution terms that you use for the rest of that program.
+
+
+# Please send patches to <config-patches@gnu.org>. Submit a context
+# diff and a properly formatted ChangeLog entry.
+#
+# Configuration subroutine to validate and canonicalize a configuration type.
+# Supply the specified configuration type as an argument.
+# If it is invalid, we print an error message on stderr and exit with code 1.
+# Otherwise, we print the canonical config type on stdout and succeed.
+
+# This file is supposed to be the same for all GNU packages
+# and recognize all the CPU types, system types and aliases
+# that are meaningful with *any* GNU software.
+# Each package is responsible for reporting which valid configurations
+# it does not support. The user should be able to distinguish
+# a failure to support a valid configuration from a meaningless
+# configuration.
+
+# The goal of this file is to map all the various variations of a given
+# machine specification into a single specification in the form:
+# CPU_TYPE-MANUFACTURER-OPERATING_SYSTEM
+# or in some cases, the newer four-part form:
+# CPU_TYPE-MANUFACTURER-KERNEL-OPERATING_SYSTEM
+# It is wrong to echo any other type of specification.
+
+me=`echo "$0" | sed -e 's,.*/,,'`
+
+usage="\
+Usage: $0 [OPTION] CPU-MFR-OPSYS
+ $0 [OPTION] ALIAS
+
+Canonicalize a configuration name.
+
+Operation modes:
+ -h, --help print this help, then exit
+ -t, --time-stamp print date of last modification, then exit
+ -v, --version print version number, then exit
+
+Report bugs and patches to <config-patches@gnu.org>."
+
+version="\
+GNU config.sub ($timestamp)
+
+Copyright (C) 1992, 1993, 1994, 1995, 1996, 1997, 1998, 1999, 2000, 2001,
+2002, 2003, 2004, 2005, 2006, 2007, 2008 Free Software Foundation, Inc.
+
+This is free software; see the source for copying conditions. There is NO
+warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE."
+
+help="
+Try \`$me --help' for more information."
+
+# Parse command line
+while test $# -gt 0 ; do
+ case $1 in
+ --time-stamp | --time* | -t )
+ echo "$timestamp" ; exit ;;
+ --version | -v )
+ echo "$version" ; exit ;;
+ --help | --h* | -h )
+ echo "$usage"; exit ;;
+ -- ) # Stop option processing
+ shift; break ;;
+ - ) # Use stdin as input.
+ break ;;
+ -* )
+ echo "$me: invalid option $1$help"
+ exit 1 ;;
+
+ *local*)
+ # First pass through any local machine types.
+ echo $1
+ exit ;;
+
+ * )
+ break ;;
+ esac
+done
+
+case $# in
+ 0) echo "$me: missing argument$help" >&2
+ exit 1;;
+ 1) ;;
+ *) echo "$me: too many arguments$help" >&2
+ exit 1;;
+esac
+
+# Separate what the user gave into CPU-COMPANY and OS or KERNEL-OS (if any).
+# Here we must recognize all the valid KERNEL-OS combinations.
+maybe_os=`echo $1 | sed 's/^\(.*\)-\([^-]*-[^-]*\)$/\2/'`
+case $maybe_os in
+ nto-qnx* | linux-gnu* | linux-dietlibc | linux-newlib* | linux-uclibc* | \
+ uclinux-uclibc* | uclinux-gnu* | kfreebsd*-gnu* | knetbsd*-gnu* | netbsd*-gnu* | \
+ storm-chaos* | os2-emx* | rtmk-nova*)
+ os=-$maybe_os
+ basic_machine=`echo $1 | sed 's/^\(.*\)-\([^-]*-[^-]*\)$/\1/'`
+ ;;
+ *)
+ basic_machine=`echo $1 | sed 's/-[^-]*$//'`
+ if [ $basic_machine != $1 ]
+ then os=`echo $1 | sed 's/.*-/-/'`
+ else os=; fi
+ ;;
+esac
+
+### Let's recognize common machines as not being operating systems so
+### that things like config.sub decstation-3100 work. We also
+### recognize some manufacturers as not being operating systems, so we
+### can provide default operating systems below.
+case $os in
+ -sun*os*)
+ # Prevent following clause from handling this invalid input.
+ ;;
+ -dec* | -mips* | -sequent* | -encore* | -pc532* | -sgi* | -sony* | \
+ -att* | -7300* | -3300* | -delta* | -motorola* | -sun[234]* | \
+ -unicom* | -ibm* | -next | -hp | -isi* | -apollo | -altos* | \
+ -convergent* | -ncr* | -news | -32* | -3600* | -3100* | -hitachi* |\
+ -c[123]* | -convex* | -sun | -crds | -omron* | -dg | -ultra | -tti* | \
+ -harris | -dolphin | -highlevel | -gould | -cbm | -ns | -masscomp | \
+ -apple | -axis | -knuth | -cray)
+ os=
+ basic_machine=$1
+ ;;
+ -sim | -cisco | -oki | -wec | -winbond)
+ os=
+ basic_machine=$1
+ ;;
+ -scout)
+ ;;
+ -wrs)
+ os=-vxworks
+ basic_machine=$1
+ ;;
+ -chorusos*)
+ os=-chorusos
+ basic_machine=$1
+ ;;
+ -chorusrdb)
+ os=-chorusrdb
+ basic_machine=$1
+ ;;
+ -hiux*)
+ os=-hiuxwe2
+ ;;
+ -sco6)
+ os=-sco5v6
+ basic_machine=`echo $1 | sed -e 's/86-.*/86-pc/'`
+ ;;
+ -sco5)
+ os=-sco3.2v5
+ basic_machine=`echo $1 | sed -e 's/86-.*/86-pc/'`
+ ;;
+ -sco4)
+ os=-sco3.2v4
+ basic_machine=`echo $1 | sed -e 's/86-.*/86-pc/'`
+ ;;
+ -sco3.2.[4-9]*)
+ os=`echo $os | sed -e 's/sco3.2./sco3.2v/'`
+ basic_machine=`echo $1 | sed -e 's/86-.*/86-pc/'`
+ ;;
+ -sco3.2v[4-9]*)
+ # Don't forget version if it is 3.2v4 or newer.
+ basic_machine=`echo $1 | sed -e 's/86-.*/86-pc/'`
+ ;;
+ -sco5v6*)
+ # Don't forget version if it is 3.2v4 or newer.
+ basic_machine=`echo $1 | sed -e 's/86-.*/86-pc/'`
+ ;;
+ -sco*)
+ os=-sco3.2v2
+ basic_machine=`echo $1 | sed -e 's/86-.*/86-pc/'`
+ ;;
+ -udk*)
+ basic_machine=`echo $1 | sed -e 's/86-.*/86-pc/'`
+ ;;
+ -isc)
+ os=-isc2.2
+ basic_machine=`echo $1 | sed -e 's/86-.*/86-pc/'`
+ ;;
+ -clix*)
+ basic_machine=clipper-intergraph
+ ;;
+ -isc*)
+ basic_machine=`echo $1 | sed -e 's/86-.*/86-pc/'`
+ ;;
+ -lynx*)
+ os=-lynxos
+ ;;
+ -ptx*)
+ basic_machine=`echo $1 | sed -e 's/86-.*/86-sequent/'`
+ ;;
+ -windowsnt*)
+ os=`echo $os | sed -e 's/windowsnt/winnt/'`
+ ;;
+ -psos*)
+ os=-psos
+ ;;
+ -mint | -mint[0-9]*)
+ basic_machine=m68k-atari
+ os=-mint
+ ;;
+esac
+
+# Decode aliases for certain CPU-COMPANY combinations.
+case $basic_machine in
+ # Recognize the basic CPU types without company name.
+ # Some are omitted here because they have special meanings below.
+ 1750a | 580 \
+ | a29k \
+ | alpha | alphaev[4-8] | alphaev56 | alphaev6[78] | alphapca5[67] \
+ | alpha64 | alpha64ev[4-8] | alpha64ev56 | alpha64ev6[78] | alpha64pca5[67] \
+ | am33_2.0 \
+ | arc | arm | arm[bl]e | arme[lb] | armv[2345] | armv[345][lb] | avr | avr32 \
+ | bfin \
+ | c4x | clipper \
+ | d10v | d30v | dlx | dsp16xx \
+ | fido | fr30 | frv \
+ | h8300 | h8500 | hppa | hppa1.[01] | hppa2.0 | hppa2.0[nw] | hppa64 \
+ | i370 | i860 | i960 | ia64 \
+ | ip2k | iq2000 \
+ | m32c | m32r | m32rle | m68000 | m68k | m88k \
+ | maxq | mb | microblaze | mcore | mep | metag \
+ | mips | mipsbe | mipseb | mipsel | mipsle \
+ | mips16 \
+ | mips64 | mips64el \
+ | mips64octeon | mips64octeonel \
+ | mips64orion | mips64orionel \
+ | mips64r5900 | mips64r5900el \
+ | mips64vr | mips64vrel \
+ | mips64vr4100 | mips64vr4100el \
+ | mips64vr4300 | mips64vr4300el \
+ | mips64vr5000 | mips64vr5000el \
+ | mips64vr5900 | mips64vr5900el \
+ | mipsisa32 | mipsisa32el \
+ | mipsisa32r2 | mipsisa32r2el \
+ | mipsisa64 | mipsisa64el \
+ | mipsisa64r2 | mipsisa64r2el \
+ | mipsisa64sb1 | mipsisa64sb1el \
+ | mipsisa64sr71k | mipsisa64sr71kel \
+ | mipstx39 | mipstx39el \
+ | mn10200 | mn10300 \
+ | mt \
+ | msp430 \
+ | nios | nios2 \
+ | ns16k | ns32k \
+ | or32 \
+ | pdp10 | pdp11 | pj | pjl \
+ | powerpc | powerpc64 | powerpc64le | powerpcle | ppcbe \
+ | pyramid \
+ | score \
+ | sh | sh[1234] | sh[24]a | sh[23]e | sh[34]eb | sheb | shbe | shle | sh[1234]le | sh3ele \
+ | sh64 | sh64le \
+ | sparc | sparc64 | sparc64b | sparc64v | sparc86x | sparclet | sparclite \
+ | sparcv8 | sparcv9 | sparcv9b | sparcv9v \
+ | spu | strongarm \
+ | tahoe | thumb | tic4x | tic80 | tron \
+ | v850 | v850e \
+ | we32k \
+ | x86 | xc16x | xscale | xscalee[bl] | xstormy16 | xtensa \
+ | z8k)
+ basic_machine=$basic_machine-unknown
+ ;;
+ m6811 | m68hc11 | m6812 | m68hc12)
+ # Motorola 68HC11/12.
+ basic_machine=$basic_machine-unknown
+ os=-none
+ ;;
+ m88110 | m680[12346]0 | m683?2 | m68360 | m5200 | v70 | w65 | z8k)
+ ;;
+ ms1)
+ basic_machine=mt-unknown
+ ;;
+
+ # We use `pc' rather than `unknown'
+ # because (1) that's what they normally are, and
+ # (2) the word "unknown" tends to confuse beginning users.
+ i*86 | x86_64)
+ basic_machine=$basic_machine-pc
+ ;;
+ # Object if more than one company name word.
+ *-*-*)
+ echo Invalid configuration \`$1\': machine \`$basic_machine\' not recognized 1>&2
+ exit 1
+ ;;
+ # Recognize the basic CPU types with company name.
+ 580-* \
+ | a29k-* \
+ | alpha-* | alphaev[4-8]-* | alphaev56-* | alphaev6[78]-* \
+ | alpha64-* | alpha64ev[4-8]-* | alpha64ev56-* | alpha64ev6[78]-* \
+ | alphapca5[67]-* | alpha64pca5[67]-* | arc-* \
+ | arm-* | armbe-* | armle-* | armeb-* | armv*-* \
+ | avr-* | avr32-* \
+ | bfin-* | bs2000-* \
+ | c[123]* | c30-* | [cjt]90-* | c4x-* | c54x-* | c55x-* | c6x-* \
+ | clipper-* | craynv-* | cydra-* \
+ | d10v-* | d30v-* | dlx-* \
+ | elxsi-* \
+ | f30[01]-* | f700-* | fido-* | fr30-* | frv-* | fx80-* \
+ | h8300-* | h8500-* \
+ | hppa-* | hppa1.[01]-* | hppa2.0-* | hppa2.0[nw]-* | hppa64-* \
+ | i*86-* | i860-* | i960-* | ia64-* \
+ | ip2k-* | iq2000-* \
+ | m32c-* | m32r-* | m32rle-* \
+ | m68000-* | m680[012346]0-* | m68360-* | m683?2-* | m68k-* \
+ | m88110-* | m88k-* | maxq-* | mcore-* | metag-* \
+ | mips-* | mipsbe-* | mipseb-* | mipsel-* | mipsle-* \
+ | mips16-* \
+ | mips64-* | mips64el-* \
+ | mips64octeon-* | mips64octeonel-* \
+ | mips64orion-* | mips64orionel-* \
+ | mips64r5900-* | mips64r5900el-* \
+ | mips64vr-* | mips64vrel-* \
+ | mips64vr4100-* | mips64vr4100el-* \
+ | mips64vr4300-* | mips64vr4300el-* \
+ | mips64vr5000-* | mips64vr5000el-* \
+ | mips64vr5900-* | mips64vr5900el-* \
+ | mipsisa32-* | mipsisa32el-* \
+ | mipsisa32r2-* | mipsisa32r2el-* \
+ | mipsisa64-* | mipsisa64el-* \
+ | mipsisa64r2-* | mipsisa64r2el-* \
+ | mipsisa64sb1-* | mipsisa64sb1el-* \
+ | mipsisa64sr71k-* | mipsisa64sr71kel-* \
+ | mipstx39-* | mipstx39el-* \
+ | mmix-* \
+ | mt-* \
+ | msp430-* \
+ | nios-* | nios2-* \
+ | none-* | np1-* | ns16k-* | ns32k-* \
+ | orion-* \
+ | pdp10-* | pdp11-* | pj-* | pjl-* | pn-* | power-* \
+ | powerpc-* | powerpc64-* | powerpc64le-* | powerpcle-* | ppcbe-* \
+ | pyramid-* \
+ | romp-* | rs6000-* \
+ | sh-* | sh[1234]-* | sh[24]a-* | sh[23]e-* | sh[34]eb-* | sheb-* | shbe-* \
+ | shle-* | sh[1234]le-* | sh3ele-* | sh64-* | sh64le-* \
+ | sparc-* | sparc64-* | sparc64b-* | sparc64v-* | sparc86x-* | sparclet-* \
+ | sparclite-* \
+ | sparcv8-* | sparcv9-* | sparcv9b-* | sparcv9v-* | strongarm-* | sv1-* | sx?-* \
+ | tahoe-* | thumb-* \
+ | tic30-* | tic4x-* | tic54x-* | tic55x-* | tic6x-* | tic80-* | tile-* \
+ | tron-* \
+ | v850-* | v850e-* | vax-* \
+ | we32k-* \
+ | x86-* | x86_64-* | xc16x-* | xps100-* | xscale-* | xscalee[bl]-* \
+ | xstormy16-* | xtensa*-* \
+ | ymp-* \
+ | z8k-*)
+ ;;
+ # Recognize the basic CPU types without company name, with glob match.
+ xtensa*)
+ basic_machine=$basic_machine-unknown
+ ;;
+ # Recognize the various machine names and aliases which stand
+ # for a CPU type and a company and sometimes even an OS.
+ 386bsd)
+ basic_machine=i386-unknown
+ os=-bsd
+ ;;
+ 3b1 | 7300 | 7300-att | att-7300 | pc7300 | safari | unixpc)
+ basic_machine=m68000-att
+ ;;
+ 3b*)
+ basic_machine=we32k-att
+ ;;
+ a29khif)
+ basic_machine=a29k-amd
+ os=-udi
+ ;;
+ abacus)
+ basic_machine=abacus-unknown
+ ;;
+ adobe68k)
+ basic_machine=m68010-adobe
+ os=-scout
+ ;;
+ alliant | fx80)
+ basic_machine=fx80-alliant
+ ;;
+ altos | altos3068)
+ basic_machine=m68k-altos
+ ;;
+ am29k)
+ basic_machine=a29k-none
+ os=-bsd
+ ;;
+ amd64)
+ basic_machine=x86_64-pc
+ ;;
+ amd64-*)
+ basic_machine=x86_64-`echo $basic_machine | sed 's/^[^-]*-//'`
+ ;;
+ amdahl)
+ basic_machine=580-amdahl
+ os=-sysv
+ ;;
+ amiga | amiga-*)
+ basic_machine=m68k-unknown
+ ;;
+ amigaos | amigados)
+ basic_machine=m68k-unknown
+ os=-amigaos
+ ;;
+ amigaunix | amix)
+ basic_machine=m68k-unknown
+ os=-sysv4
+ ;;
+ apollo68)
+ basic_machine=m68k-apollo
+ os=-sysv
+ ;;
+ apollo68bsd)
+ basic_machine=m68k-apollo
+ os=-bsd
+ ;;
+ aux)
+ basic_machine=m68k-apple
+ os=-aux
+ ;;
+ balance)
+ basic_machine=ns32k-sequent
+ os=-dynix
+ ;;
+ blackfin)
+ basic_machine=bfin-unknown
+ os=-linux
+ ;;
+ blackfin-*)
+ basic_machine=bfin-`echo $basic_machine | sed 's/^[^-]*-//'`
+ os=-linux
+ ;;
+ c90)
+ basic_machine=c90-cray
+ os=-unicos
+ ;;
+ convex-c1)
+ basic_machine=c1-convex
+ os=-bsd
+ ;;
+ convex-c2)
+ basic_machine=c2-convex
+ os=-bsd
+ ;;
+ convex-c32)
+ basic_machine=c32-convex
+ os=-bsd
+ ;;
+ convex-c34)
+ basic_machine=c34-convex
+ os=-bsd
+ ;;
+ convex-c38)
+ basic_machine=c38-convex
+ os=-bsd
+ ;;
+ cray | j90)
+ basic_machine=j90-cray
+ os=-unicos
+ ;;
+ craynv)
+ basic_machine=craynv-cray
+ os=-unicosmp
+ ;;
+ cr16)
+ basic_machine=cr16-unknown
+ os=-elf
+ ;;
+ crds | unos)
+ basic_machine=m68k-crds
+ ;;
+ crisv32 | crisv32-* | etraxfs*)
+ basic_machine=crisv32-axis
+ ;;
+ cris | cris-* | etrax*)
+ basic_machine=cris-axis
+ ;;
+ crx)
+ basic_machine=crx-unknown
+ os=-elf
+ ;;
+ da30 | da30-*)
+ basic_machine=m68k-da30
+ ;;
+ decstation | decstation-3100 | pmax | pmax-* | pmin | dec3100 | decstatn)
+ basic_machine=mips-dec
+ ;;
+ decsystem10* | dec10*)
+ basic_machine=pdp10-dec
+ os=-tops10
+ ;;
+ decsystem20* | dec20*)
+ basic_machine=pdp10-dec
+ os=-tops20
+ ;;
+ delta | 3300 | motorola-3300 | motorola-delta \
+ | 3300-motorola | delta-motorola)
+ basic_machine=m68k-motorola
+ ;;
+ delta88)
+ basic_machine=m88k-motorola
+ os=-sysv3
+ ;;
+ dicos)
+ basic_machine=i686-pc
+ os=-dicos
+ ;;
+ djgpp)
+ basic_machine=i586-pc
+ os=-msdosdjgpp
+ ;;
+ dpx20 | dpx20-*)
+ basic_machine=rs6000-bull
+ os=-bosx
+ ;;
+ dpx2* | dpx2*-bull)
+ basic_machine=m68k-bull
+ os=-sysv3
+ ;;
+ ebmon29k)
+ basic_machine=a29k-amd
+ os=-ebmon
+ ;;
+ elxsi)
+ basic_machine=elxsi-elxsi
+ os=-bsd
+ ;;
+ encore | umax | mmax)
+ basic_machine=ns32k-encore
+ ;;
+ es1800 | OSE68k | ose68k | ose | OSE)
+ basic_machine=m68k-ericsson
+ os=-ose
+ ;;
+ fx2800)
+ basic_machine=i860-alliant
+ ;;
+ genix)
+ basic_machine=ns32k-ns
+ ;;
+ gmicro)
+ basic_machine=tron-gmicro
+ os=-sysv
+ ;;
+ go32)
+ basic_machine=i386-pc
+ os=-go32
+ ;;
+ h3050r* | hiux*)
+ basic_machine=hppa1.1-hitachi
+ os=-hiuxwe2
+ ;;
+ h8300hms)
+ basic_machine=h8300-hitachi
+ os=-hms
+ ;;
+ h8300xray)
+ basic_machine=h8300-hitachi
+ os=-xray
+ ;;
+ h8500hms)
+ basic_machine=h8500-hitachi
+ os=-hms
+ ;;
+ harris)
+ basic_machine=m88k-harris
+ os=-sysv3
+ ;;
+ hp300-*)
+ basic_machine=m68k-hp
+ ;;
+ hp300bsd)
+ basic_machine=m68k-hp
+ os=-bsd
+ ;;
+ hp300hpux)
+ basic_machine=m68k-hp
+ os=-hpux
+ ;;
+ hp3k9[0-9][0-9] | hp9[0-9][0-9])
+ basic_machine=hppa1.0-hp
+ ;;
+ hp9k2[0-9][0-9] | hp9k31[0-9])
+ basic_machine=m68000-hp
+ ;;
+ hp9k3[2-9][0-9])
+ basic_machine=m68k-hp
+ ;;
+ hp9k6[0-9][0-9] | hp6[0-9][0-9])
+ basic_machine=hppa1.0-hp
+ ;;
+ hp9k7[0-79][0-9] | hp7[0-79][0-9])
+ basic_machine=hppa1.1-hp
+ ;;
+ hp9k78[0-9] | hp78[0-9])
+ # FIXME: really hppa2.0-hp
+ basic_machine=hppa1.1-hp
+ ;;
+ hp9k8[67]1 | hp8[67]1 | hp9k80[24] | hp80[24] | hp9k8[78]9 | hp8[78]9 | hp9k893 | hp893)
+ # FIXME: really hppa2.0-hp
+ basic_machine=hppa1.1-hp
+ ;;
+ hp9k8[0-9][13679] | hp8[0-9][13679])
+ basic_machine=hppa1.1-hp
+ ;;
+ hp9k8[0-9][0-9] | hp8[0-9][0-9])
+ basic_machine=hppa1.0-hp
+ ;;
+ hppa-next)
+ os=-nextstep3
+ ;;
+ hppaosf)
+ basic_machine=hppa1.1-hp
+ os=-osf
+ ;;
+ hppro)
+ basic_machine=hppa1.1-hp
+ os=-proelf
+ ;;
+ i370-ibm* | ibm*)
+ basic_machine=i370-ibm
+ ;;
+# I'm not sure what "Sysv32" means. Should this be sysv3.2?
+ i*86v32)
+ basic_machine=`echo $1 | sed -e 's/86.*/86-pc/'`
+ os=-sysv32
+ ;;
+ i*86v4*)
+ basic_machine=`echo $1 | sed -e 's/86.*/86-pc/'`
+ os=-sysv4
+ ;;
+ i*86v)
+ basic_machine=`echo $1 | sed -e 's/86.*/86-pc/'`
+ os=-sysv
+ ;;
+ i*86sol2)
+ basic_machine=`echo $1 | sed -e 's/86.*/86-pc/'`
+ os=-solaris2
+ ;;
+ i386mach)
+ basic_machine=i386-mach
+ os=-mach
+ ;;
+ i386-vsta | vsta)
+ basic_machine=i386-unknown
+ os=-vsta
+ ;;
+ iris | iris4d)
+ basic_machine=mips-sgi
+ case $os in
+ -irix*)
+ ;;
+ *)
+ os=-irix4
+ ;;
+ esac
+ ;;
+ isi68 | isi)
+ basic_machine=m68k-isi
+ os=-sysv
+ ;;
+ m68knommu)
+ basic_machine=m68k-unknown
+ os=-linux
+ ;;
+ m68knommu-*)
+ basic_machine=m68k-`echo $basic_machine | sed 's/^[^-]*-//'`
+ os=-linux
+ ;;
+ m88k-omron*)
+ basic_machine=m88k-omron
+ ;;
+ magnum | m3230)
+ basic_machine=mips-mips
+ os=-sysv
+ ;;
+ merlin)
+ basic_machine=ns32k-utek
+ os=-sysv
+ ;;
+ mingw32)
+ basic_machine=i386-pc
+ os=-mingw32
+ ;;
+ mingw32ce)
+ basic_machine=arm-unknown
+ os=-mingw32ce
+ ;;
+ miniframe)
+ basic_machine=m68000-convergent
+ ;;
+ *mint | -mint[0-9]* | *MiNT | *MiNT[0-9]*)
+ basic_machine=m68k-atari
+ os=-mint
+ ;;
+ mips3*-*)
+ basic_machine=`echo $basic_machine | sed -e 's/mips3/mips64/'`
+ ;;
+ mips3*)
+ basic_machine=`echo $basic_machine | sed -e 's/mips3/mips64/'`-unknown
+ ;;
+ monitor)
+ basic_machine=m68k-rom68k
+ os=-coff
+ ;;
+ morphos)
+ basic_machine=powerpc-unknown
+ os=-morphos
+ ;;
+ msdos)
+ basic_machine=i386-pc
+ os=-msdos
+ ;;
+ ms1-*)
+ basic_machine=`echo $basic_machine | sed -e 's/ms1-/mt-/'`
+ ;;
+ mvs)
+ basic_machine=i370-ibm
+ os=-mvs
+ ;;
+ ncr3000)
+ basic_machine=i486-ncr
+ os=-sysv4
+ ;;
+ netbsd386)
+ basic_machine=i386-unknown
+ os=-netbsd
+ ;;
+ netwinder)
+ basic_machine=armv4l-rebel
+ os=-linux
+ ;;
+ news | news700 | news800 | news900)
+ basic_machine=m68k-sony
+ os=-newsos
+ ;;
+ news1000)
+ basic_machine=m68030-sony
+ os=-newsos
+ ;;
+ news-3600 | risc-news)
+ basic_machine=mips-sony
+ os=-newsos
+ ;;
+ necv70)
+ basic_machine=v70-nec
+ os=-sysv
+ ;;
+ next | m*-next )
+ basic_machine=m68k-next
+ case $os in
+ -nextstep* )
+ ;;
+ -ns2*)
+ os=-nextstep2
+ ;;
+ *)
+ os=-nextstep3
+ ;;
+ esac
+ ;;
+ nh3000)
+ basic_machine=m68k-harris
+ os=-cxux
+ ;;
+ nh[45]000)
+ basic_machine=m88k-harris
+ os=-cxux
+ ;;
+ nindy960)
+ basic_machine=i960-intel
+ os=-nindy
+ ;;
+ mon960)
+ basic_machine=i960-intel
+ os=-mon960
+ ;;
+ nonstopux)
+ basic_machine=mips-compaq
+ os=-nonstopux
+ ;;
+ np1)
+ basic_machine=np1-gould
+ ;;
+ nsr-tandem)
+ basic_machine=nsr-tandem
+ ;;
+ op50n-* | op60c-*)
+ basic_machine=hppa1.1-oki
+ os=-proelf
+ ;;
+ openrisc | openrisc-*)
+ basic_machine=or32-unknown
+ ;;
+ os400)
+ basic_machine=powerpc-ibm
+ os=-os400
+ ;;
+ OSE68000 | ose68000)
+ basic_machine=m68000-ericsson
+ os=-ose
+ ;;
+ os68k)
+ basic_machine=m68k-none
+ os=-os68k
+ ;;
+ pa-hitachi)
+ basic_machine=hppa1.1-hitachi
+ os=-hiuxwe2
+ ;;
+ paragon)
+ basic_machine=i860-intel
+ os=-osf
+ ;;
+ parisc)
+ basic_machine=hppa-unknown
+ os=-linux
+ ;;
+ parisc-*)
+ basic_machine=hppa-`echo $basic_machine | sed 's/^[^-]*-//'`
+ os=-linux
+ ;;
+ pbd)
+ basic_machine=sparc-tti
+ ;;
+ pbb)
+ basic_machine=m68k-tti
+ ;;
+ pc532 | pc532-*)
+ basic_machine=ns32k-pc532
+ ;;
+ pc98)
+ basic_machine=i386-pc
+ ;;
+ pc98-*)
+ basic_machine=i386-`echo $basic_machine | sed 's/^[^-]*-//'`
+ ;;
+ pentium | p5 | k5 | k6 | nexgen | viac3)
+ basic_machine=i586-pc
+ ;;
+ pentiumpro | p6 | 6x86 | athlon | athlon_*)
+ basic_machine=i686-pc
+ ;;
+ pentiumii | pentium2 | pentiumiii | pentium3)
+ basic_machine=i686-pc
+ ;;
+ pentium4)
+ basic_machine=i786-pc
+ ;;
+ pentium-* | p5-* | k5-* | k6-* | nexgen-* | viac3-*)
+ basic_machine=i586-`echo $basic_machine | sed 's/^[^-]*-//'`
+ ;;
+ pentiumpro-* | p6-* | 6x86-* | athlon-*)
+ basic_machine=i686-`echo $basic_machine | sed 's/^[^-]*-//'`
+ ;;
+ pentiumii-* | pentium2-* | pentiumiii-* | pentium3-*)
+ basic_machine=i686-`echo $basic_machine | sed 's/^[^-]*-//'`
+ ;;
+ pentium4-*)
+ basic_machine=i786-`echo $basic_machine | sed 's/^[^-]*-//'`
+ ;;
+ pn)
+ basic_machine=pn-gould
+ ;;
+ power) basic_machine=power-ibm
+ ;;
+ ppc) basic_machine=powerpc-unknown
+ ;;
+ ppc-*) basic_machine=powerpc-`echo $basic_machine | sed 's/^[^-]*-//'`
+ ;;
+ ppcle | powerpclittle | ppc-le | powerpc-little)
+ basic_machine=powerpcle-unknown
+ ;;
+ ppcle-* | powerpclittle-*)
+ basic_machine=powerpcle-`echo $basic_machine | sed 's/^[^-]*-//'`
+ ;;
+ ppc64) basic_machine=powerpc64-unknown
+ ;;
+ ppc64-*) basic_machine=powerpc64-`echo $basic_machine | sed 's/^[^-]*-//'`
+ ;;
+ ppc64le | powerpc64little | ppc64-le | powerpc64-little)
+ basic_machine=powerpc64le-unknown
+ ;;
+ ppc64le-* | powerpc64little-*)
+ basic_machine=powerpc64le-`echo $basic_machine | sed 's/^[^-]*-//'`
+ ;;
+ ps2)
+ basic_machine=i386-ibm
+ ;;
+ pw32)
+ basic_machine=i586-unknown
+ os=-pw32
+ ;;
+ rdos)
+ basic_machine=i386-pc
+ os=-rdos
+ ;;
+ rom68k)
+ basic_machine=m68k-rom68k
+ os=-coff
+ ;;
+ rm[46]00)
+ basic_machine=mips-siemens
+ ;;
+ rtpc | rtpc-*)
+ basic_machine=romp-ibm
+ ;;
+ s390 | s390-*)
+ basic_machine=s390-ibm
+ ;;
+ s390x | s390x-*)
+ basic_machine=s390x-ibm
+ ;;
+ sa29200)
+ basic_machine=a29k-amd
+ os=-udi
+ ;;
+ sb1)
+ basic_machine=mipsisa64sb1-unknown
+ ;;
+ sb1el)
+ basic_machine=mipsisa64sb1el-unknown
+ ;;
+ sde)
+ basic_machine=mipsisa32-sde
+ os=-elf
+ ;;
+ sei)
+ basic_machine=mips-sei
+ os=-seiux
+ ;;
+ sequent)
+ basic_machine=i386-sequent
+ ;;
+ sh)
+ basic_machine=sh-hitachi
+ os=-hms
+ ;;
+ sh5el)
+ basic_machine=sh5le-unknown
+ ;;
+ sh64)
+ basic_machine=sh64-unknown
+ ;;
+ sparclite-wrs | simso-wrs)
+ basic_machine=sparclite-wrs
+ os=-vxworks
+ ;;
+ sps7)
+ basic_machine=m68k-bull
+ os=-sysv2
+ ;;
+ spur)
+ basic_machine=spur-unknown
+ ;;
+ st2000)
+ basic_machine=m68k-tandem
+ ;;
+ stratus)
+ basic_machine=i860-stratus
+ os=-sysv4
+ ;;
+ sun2)
+ basic_machine=m68000-sun
+ ;;
+ sun2os3)
+ basic_machine=m68000-sun
+ os=-sunos3
+ ;;
+ sun2os4)
+ basic_machine=m68000-sun
+ os=-sunos4
+ ;;
+ sun3os3)
+ basic_machine=m68k-sun
+ os=-sunos3
+ ;;
+ sun3os4)
+ basic_machine=m68k-sun
+ os=-sunos4
+ ;;
+ sun4os3)
+ basic_machine=sparc-sun
+ os=-sunos3
+ ;;
+ sun4os4)
+ basic_machine=sparc-sun
+ os=-sunos4
+ ;;
+ sun4sol2)
+ basic_machine=sparc-sun
+ os=-solaris2
+ ;;
+ sun3 | sun3-*)
+ basic_machine=m68k-sun
+ ;;
+ sun4)
+ basic_machine=sparc-sun
+ ;;
+ sun386 | sun386i | roadrunner)
+ basic_machine=i386-sun
+ ;;
+ sv1)
+ basic_machine=sv1-cray
+ os=-unicos
+ ;;
+ symmetry)
+ basic_machine=i386-sequent
+ os=-dynix
+ ;;
+ t3e)
+ basic_machine=alphaev5-cray
+ os=-unicos
+ ;;
+ t90)
+ basic_machine=t90-cray
+ os=-unicos
+ ;;
+ tic54x | c54x*)
+ basic_machine=tic54x-unknown
+ os=-coff
+ ;;
+ tic55x | c55x*)
+ basic_machine=tic55x-unknown
+ os=-coff
+ ;;
+ tic6x | c6x*)
+ basic_machine=tic6x-unknown
+ os=-coff
+ ;;
+ tile*)
+ basic_machine=tile-unknown
+ os=-linux-gnu
+ ;;
+ tx39)
+ basic_machine=mipstx39-unknown
+ ;;
+ tx39el)
+ basic_machine=mipstx39el-unknown
+ ;;
+ toad1)
+ basic_machine=pdp10-xkl
+ os=-tops20
+ ;;
+ tower | tower-32)
+ basic_machine=m68k-ncr
+ ;;
+ tpf)
+ basic_machine=s390x-ibm
+ os=-tpf
+ ;;
+ udi29k)
+ basic_machine=a29k-amd
+ os=-udi
+ ;;
+ ultra3)
+ basic_machine=a29k-nyu
+ os=-sym1
+ ;;
+ v810 | necv810)
+ basic_machine=v810-nec
+ os=-none
+ ;;
+ vaxv)
+ basic_machine=vax-dec
+ os=-sysv
+ ;;
+ vms)
+ basic_machine=vax-dec
+ os=-vms
+ ;;
+ vpp*|vx|vx-*)
+ basic_machine=f301-fujitsu
+ ;;
+ vxworks960)
+ basic_machine=i960-wrs
+ os=-vxworks
+ ;;
+ vxworks68)
+ basic_machine=m68k-wrs
+ os=-vxworks
+ ;;
+ vxworks29k)
+ basic_machine=a29k-wrs
+ os=-vxworks
+ ;;
+ w65*)
+ basic_machine=w65-wdc
+ os=-none
+ ;;
+ w89k-*)
+ basic_machine=hppa1.1-winbond
+ os=-proelf
+ ;;
+ xbox)
+ basic_machine=i686-pc
+ os=-mingw32
+ ;;
+ xps | xps100)
+ basic_machine=xps100-honeywell
+ ;;
+ ymp)
+ basic_machine=ymp-cray
+ os=-unicos
+ ;;
+ z8k-*-coff)
+ basic_machine=z8k-unknown
+ os=-sim
+ ;;
+ none)
+ basic_machine=none-none
+ os=-none
+ ;;
+
+# Here we handle the default manufacturer of certain CPU types. It is in
+# some cases the only manufacturer, in others, it is the most popular.
+ w89k)
+ basic_machine=hppa1.1-winbond
+ ;;
+ op50n)
+ basic_machine=hppa1.1-oki
+ ;;
+ op60c)
+ basic_machine=hppa1.1-oki
+ ;;
+ romp)
+ basic_machine=romp-ibm
+ ;;
+ mmix)
+ basic_machine=mmix-knuth
+ ;;
+ rs6000)
+ basic_machine=rs6000-ibm
+ ;;
+ vax)
+ basic_machine=vax-dec
+ ;;
+ pdp10)
+ # there are many clones, so DEC is not a safe bet
+ basic_machine=pdp10-unknown
+ ;;
+ pdp11)
+ basic_machine=pdp11-dec
+ ;;
+ we32k)
+ basic_machine=we32k-att
+ ;;
+ sh[1234] | sh[24]a | sh[34]eb | sh[1234]le | sh[23]ele)
+ basic_machine=sh-unknown
+ ;;
+ sparc | sparcv8 | sparcv9 | sparcv9b | sparcv9v)
+ basic_machine=sparc-sun
+ ;;
+ cydra)
+ basic_machine=cydra-cydrome
+ ;;
+ orion)
+ basic_machine=orion-highlevel
+ ;;
+ orion105)
+ basic_machine=clipper-highlevel
+ ;;
+ mac | mpw | mac-mpw)
+ basic_machine=m68k-apple
+ ;;
+ pmac | pmac-mpw)
+ basic_machine=powerpc-apple
+ ;;
+ *-unknown)
+ # Make sure to match an already-canonicalized machine name.
+ ;;
+ *)
+ echo Invalid configuration \`$1\': machine \`$basic_machine\' not recognized 1>&2
+ exit 1
+ ;;
+esac
+
+# Here we canonicalize certain aliases for manufacturers.
+case $basic_machine in
+ *-digital*)
+ basic_machine=`echo $basic_machine | sed 's/digital.*/dec/'`
+ ;;
+ *-commodore*)
+ basic_machine=`echo $basic_machine | sed 's/commodore.*/cbm/'`
+ ;;
+ *)
+ ;;
+esac
+
+# Decode manufacturer-specific aliases for certain operating systems.
+
+if [ x"$os" != x"" ]
+then
+case $os in
+ # First match some system type aliases
+ # that might get confused with valid system types.
+ # -solaris* is a basic system type, with this one exception.
+ -solaris1 | -solaris1.*)
+ os=`echo $os | sed -e 's|solaris1|sunos4|'`
+ ;;
+ -solaris)
+ os=-solaris2
+ ;;
+ -svr4*)
+ os=-sysv4
+ ;;
+ -unixware*)
+ os=-sysv4.2uw
+ ;;
+ -gnu/linux*)
+ os=`echo $os | sed -e 's|gnu/linux|linux-gnu|'`
+ ;;
+ # First accept the basic system types.
+ # The portable systems comes first.
+ # Each alternative MUST END IN A *, to match a version number.
+ # -sysv* is not here because it comes later, after sysvr4.
+ -gnu* | -bsd* | -mach* | -minix* | -genix* | -ultrix* | -irix* \
+ | -*vms* | -sco* | -esix* | -isc* | -aix* | -sunos | -sunos[34]*\
+ | -hpux* | -unos* | -osf* | -luna* | -dgux* | -solaris* | -sym* \
+ | -amigaos* | -amigados* | -msdos* | -newsos* | -unicos* | -aof* \
+ | -aos* \
+ | -nindy* | -vxsim* | -vxworks* | -ebmon* | -hms* | -mvs* \
+ | -clix* | -riscos* | -uniplus* | -iris* | -rtu* | -xenix* \
+ | -hiux* | -386bsd* | -knetbsd* | -mirbsd* | -netbsd* \
+ | -openbsd* | -solidbsd* \
+ | -ekkobsd* | -kfreebsd* | -freebsd* | -riscix* | -lynxos* \
+ | -bosx* | -nextstep* | -cxux* | -aout* | -elf* | -oabi* \
+ | -ptx* | -coff* | -ecoff* | -winnt* | -domain* | -vsta* \
+ | -udi* | -eabi* | -lites* | -ieee* | -go32* | -aux* \
+ | -chorusos* | -chorusrdb* \
+ | -cygwin* | -pe* | -psos* | -moss* | -proelf* | -rtems* \
+ | -mingw32* | -linux-gnu* | -linux-newlib* | -linux-uclibc* \
+ | -uxpv* | -beos* | -mpeix* | -udk* \
+ | -interix* | -uwin* | -mks* | -rhapsody* | -darwin* | -opened* \
+ | -openstep* | -oskit* | -conix* | -pw32* | -nonstopux* \
+ | -storm-chaos* | -tops10* | -tenex* | -tops20* | -its* \
+ | -os2* | -vos* | -palmos* | -uclinux* | -nucleus* \
+ | -morphos* | -superux* | -rtmk* | -rtmk-nova* | -windiss* \
+ | -powermax* | -dnix* | -nx6 | -nx7 | -sei* | -dragonfly* \
+ | -skyos* | -haiku* | -rdos* | -toppers* | -drops*)
+ # Remember, each alternative MUST END IN *, to match a version number.
+ ;;
+ -qnx*)
+ case $basic_machine in
+ x86-* | i*86-*)
+ ;;
+ *)
+ os=-nto$os
+ ;;
+ esac
+ ;;
+ -nto-qnx*)
+ ;;
+ -nto*)
+ os=`echo $os | sed -e 's|nto|nto-qnx|'`
+ ;;
+ -sim | -es1800* | -hms* | -xray | -os68k* | -none* | -v88r* \
+ | -windows* | -osx | -abug | -netware* | -os9* | -beos* | -haiku* \
+ | -macos* | -mpw* | -magic* | -mmixware* | -mon960* | -lnews*)
+ ;;
+ -mac*)
+ os=`echo $os | sed -e 's|mac|macos|'`
+ ;;
+ -linux-dietlibc)
+ os=-linux-dietlibc
+ ;;
+ -linux*)
+ os=`echo $os | sed -e 's|linux|linux-gnu|'`
+ ;;
+ -sunos5*)
+ os=`echo $os | sed -e 's|sunos5|solaris2|'`
+ ;;
+ -sunos6*)
+ os=`echo $os | sed -e 's|sunos6|solaris3|'`
+ ;;
+ -opened*)
+ os=-openedition
+ ;;
+ -os400*)
+ os=-os400
+ ;;
+ -wince*)
+ os=-wince
+ ;;
+ -osfrose*)
+ os=-osfrose
+ ;;
+ -osf*)
+ os=-osf
+ ;;
+ -utek*)
+ os=-bsd
+ ;;
+ -dynix*)
+ os=-bsd
+ ;;
+ -acis*)
+ os=-aos
+ ;;
+ -atheos*)
+ os=-atheos
+ ;;
+ -syllable*)
+ os=-syllable
+ ;;
+ -386bsd)
+ os=-bsd
+ ;;
+ -ctix* | -uts*)
+ os=-sysv
+ ;;
+ -nova*)
+ os=-rtmk-nova
+ ;;
+ -ns2 )
+ os=-nextstep2
+ ;;
+ -nsk*)
+ os=-nsk
+ ;;
+ # Preserve the version number of sinix5.
+ -sinix5.*)
+ os=`echo $os | sed -e 's|sinix|sysv|'`
+ ;;
+ -sinix*)
+ os=-sysv4
+ ;;
+ -tpf*)
+ os=-tpf
+ ;;
+ -triton*)
+ os=-sysv3
+ ;;
+ -oss*)
+ os=-sysv3
+ ;;
+ -svr4)
+ os=-sysv4
+ ;;
+ -svr3)
+ os=-sysv3
+ ;;
+ -sysvr4)
+ os=-sysv4
+ ;;
+ # This must come after -sysvr4.
+ -sysv*)
+ ;;
+ -ose*)
+ os=-ose
+ ;;
+ -es1800*)
+ os=-ose
+ ;;
+ -xenix)
+ os=-xenix
+ ;;
+ -*mint | -mint[0-9]* | -*MiNT | -MiNT[0-9]*)
+ os=-mint
+ ;;
+ -aros*)
+ os=-aros
+ ;;
+ -kaos*)
+ os=-kaos
+ ;;
+ -zvmoe)
+ os=-zvmoe
+ ;;
+ -dicos*)
+ os=-dicos
+ ;;
+ -none)
+ ;;
+ *)
+ # Get rid of the `-' at the beginning of $os.
+ os=`echo $os | sed 's/[^-]*-//'`
+ echo Invalid configuration \`$1\': system \`$os\' not recognized 1>&2
+ exit 1
+ ;;
+esac
+else
+
+# Here we handle the default operating systems that come with various machines.
+# The value should be what the vendor currently ships out the door with their
+# machine or put another way, the most popular os provided with the machine.
+
+# Note that if you're going to try to match "-MANUFACTURER" here (say,
+# "-sun"), then you have to tell the case statement up towards the top
+# that MANUFACTURER isn't an operating system. Otherwise, code above
+# will signal an error saying that MANUFACTURER isn't an operating
+# system, and we'll never get to this point.
+
+case $basic_machine in
+ score-*)
+ os=-elf
+ ;;
+ spu-*)
+ os=-elf
+ ;;
+ *-acorn)
+ os=-riscix1.2
+ ;;
+ arm*-rebel)
+ os=-linux
+ ;;
+ arm*-semi)
+ os=-aout
+ ;;
+ c4x-* | tic4x-*)
+ os=-coff
+ ;;
+ # This must come before the *-dec entry.
+ pdp10-*)
+ os=-tops20
+ ;;
+ pdp11-*)
+ os=-none
+ ;;
+ *-dec | vax-*)
+ os=-ultrix4.2
+ ;;
+ m68*-apollo)
+ os=-domain
+ ;;
+ i386-sun)
+ os=-sunos4.0.2
+ ;;
+ m68000-sun)
+ os=-sunos3
+ # This also exists in the configure program, but was not the
+ # default.
+ # os=-sunos4
+ ;;
+ m68*-cisco)
+ os=-aout
+ ;;
+ mep-*)
+ os=-elf
+ ;;
+ mips*-cisco)
+ os=-elf
+ ;;
+ mips*-*)
+ os=-elf
+ ;;
+ or32-*)
+ os=-coff
+ ;;
+ *-tti) # must be before sparc entry or we get the wrong os.
+ os=-sysv3
+ ;;
+ sparc-* | *-sun)
+ os=-sunos4.1.1
+ ;;
+ *-be)
+ os=-beos
+ ;;
+ *-haiku)
+ os=-haiku
+ ;;
+ *-ibm)
+ os=-aix
+ ;;
+ *-knuth)
+ os=-mmixware
+ ;;
+ *-wec)
+ os=-proelf
+ ;;
+ *-winbond)
+ os=-proelf
+ ;;
+ *-oki)
+ os=-proelf
+ ;;
+ *-hp)
+ os=-hpux
+ ;;
+ *-hitachi)
+ os=-hiux
+ ;;
+ i860-* | *-att | *-ncr | *-altos | *-motorola | *-convergent)
+ os=-sysv
+ ;;
+ *-cbm)
+ os=-amigaos
+ ;;
+ *-dg)
+ os=-dgux
+ ;;
+ *-dolphin)
+ os=-sysv3
+ ;;
+ m68k-ccur)
+ os=-rtu
+ ;;
+ m88k-omron*)
+ os=-luna
+ ;;
+ *-next )
+ os=-nextstep
+ ;;
+ *-sequent)
+ os=-ptx
+ ;;
+ *-crds)
+ os=-unos
+ ;;
+ *-ns)
+ os=-genix
+ ;;
+ i370-*)
+ os=-mvs
+ ;;
+ *-next)
+ os=-nextstep3
+ ;;
+ *-gould)
+ os=-sysv
+ ;;
+ *-highlevel)
+ os=-bsd
+ ;;
+ *-encore)
+ os=-bsd
+ ;;
+ *-sgi)
+ os=-irix
+ ;;
+ *-siemens)
+ os=-sysv4
+ ;;
+ *-masscomp)
+ os=-rtu
+ ;;
+ f30[01]-fujitsu | f700-fujitsu)
+ os=-uxpv
+ ;;
+ *-rom68k)
+ os=-coff
+ ;;
+ *-*bug)
+ os=-coff
+ ;;
+ *-apple)
+ os=-macos
+ ;;
+ *-atari*)
+ os=-mint
+ ;;
+ *)
+ os=-none
+ ;;
+esac
+fi
+
+# Here we handle the case where we know the os, and the CPU type, but not the
+# manufacturer. We pick the logical manufacturer.
+vendor=unknown
+case $basic_machine in
+ *-unknown)
+ case $os in
+ -riscix*)
+ vendor=acorn
+ ;;
+ -sunos*)
+ vendor=sun
+ ;;
+ -aix*)
+ vendor=ibm
+ ;;
+ -beos*)
+ vendor=be
+ ;;
+ -hpux*)
+ vendor=hp
+ ;;
+ -mpeix*)
+ vendor=hp
+ ;;
+ -hiux*)
+ vendor=hitachi
+ ;;
+ -unos*)
+ vendor=crds
+ ;;
+ -dgux*)
+ vendor=dg
+ ;;
+ -luna*)
+ vendor=omron
+ ;;
+ -genix*)
+ vendor=ns
+ ;;
+ -mvs* | -opened*)
+ vendor=ibm
+ ;;
+ -os400*)
+ vendor=ibm
+ ;;
+ -ptx*)
+ vendor=sequent
+ ;;
+ -tpf*)
+ vendor=ibm
+ ;;
+ -vxsim* | -vxworks* | -windiss*)
+ vendor=wrs
+ ;;
+ -aux*)
+ vendor=apple
+ ;;
+ -hms*)
+ vendor=hitachi
+ ;;
+ -mpw* | -macos*)
+ vendor=apple
+ ;;
+ -*mint | -mint[0-9]* | -*MiNT | -MiNT[0-9]*)
+ vendor=atari
+ ;;
+ -vos*)
+ vendor=stratus
+ ;;
+ esac
+ basic_machine=`echo $basic_machine | sed "s/unknown/$vendor/"`
+ ;;
+esac
+
+echo $basic_machine$os
+exit
+
+# Local variables:
+# eval: (add-hook 'write-file-hooks 'time-stamp)
+# time-stamp-start: "timestamp='"
+# time-stamp-format: "%:y-%02m-%02d"
+# time-stamp-end: "'"
+# End:
--- /dev/null
+#! @_PATH_SH@
+
+## $Id: fixscript.in 2805 1999-11-27 07:23:49Z rra $
+##
+## Fix interpretor paths and INN variable load paths.
+##
+## Scripts shipped with INN always have the invocation path for the
+## interpretor on the first line and the command to load INN variable
+## settings into the script on the second line. For example, for a Bourne
+## shell script:
+##
+## #!/bin/sh
+## . /var/news/lib/innshellvars
+##
+## This script takes as input such a script and outputs the same script
+## with the first two lines replaced to have the correct path to the
+## interpretor and to the INN variable library, as determined by configure.
+##
+## If the script is invoked with the -i flag, only fix the interpretor
+## path and don't modify the second line of the file to include the
+## appropriate innshellvars.
+
+SED='@_PATH_SED@'
+
+PERLPATH='@_PATH_PERL@'
+SHPATH='@_PATH_SH@'
+LIBDIR='@LIBDIR@'
+
+options=true
+addlib=true
+while $options ; do
+ case X"$1" in
+ X-i) addlib=false ;;
+ *) options=false ;;
+ esac
+ $options && shift
+done
+
+input="$1"
+if [ -z "$input" ] ; then
+ echo "No input file specified" >&2
+ exit 1
+fi
+
+output="$2"
+if [ -z "$output" ] ; then
+ output=`echo "$input" | sed 's/\.in$//'`
+fi
+if [ x"$input" = x"$output" ] ; then
+ echo "No output file specified and input file doesn't end in .in" >&2
+ exit 1
+fi
+
+interpretor=`head -1 "$input"`
+case "$interpretor" in
+*/sh|*SH*)
+ path="$SHPATH"
+ lib=". $LIBDIR/innshellvars"
+ ;;
+*/perl*|*PERL*)
+ path=`echo "$interpretor" | sed 's%^#! *[^ ][^ ]*%'"$PERLPATH%"`
+ lib="require '$LIBDIR/innshellvars.pl';"
+ ;;
+*)
+ echo "Unknown interpretor $interpretor" >&2
+ exit 1
+ ;;
+esac
+
+echo "#! $path" > "$output"
+if $addlib ; then
+ echo "$lib" >> "$output"
+ "$SED" 1,2d "$input" >> "$output"
+else
+ "$SED" 1d "$input" >> "$output"
+fi
+chmod 755 "$output"
--- /dev/null
+#! /bin/sh
+
+## $Id: indent 5165 2002-03-02 01:43:53Z rra $
+##
+## Run indent on source files with INN options.
+##
+## This is a simple wrapper around GNU indent to call it with all of the
+## options suitable for INN's coding style and typedefs. These options
+## are also documented in HACKING. Assumes indent is on the user's path.
+##
+## The order of options matches the order in which they're described in
+## the GNU indent info manual. In order, each line sets options for:
+## blank lines, comments, statements, declarations, indentation, breaking
+## long lines, and typedefs used by INN.
+##
+## Note that the resulting output should not be used without manual review,
+## nor should this script be run automatically. indent still has a few
+## bugs, tends to mangle case statements written compactly, and varies from
+## the prevailing INN style in a few ways that can't be changed.
+
+indent \
+ -bad -bap -nsob \
+ -fca -lc78 -cd32 -cp1 \
+ -br -ce -cdw -cli0 -ss -npcs -cs \
+ -di1 -nbc -psl -brs \
+ -i4 -ci4 -lp -ts8 -nut -ip5 -lps \
+ -l78 -bbo -hnl \
+ -T off_t -T size_t -T uint32_t -T time_t -T FILE \
+ $*
--- /dev/null
+#!/bin/sh
+#
+# install - install a program, script, or datafile
+# This comes from X11R5 (mit/util/scripts/install.sh).
+#
+# Copyright 1991 by the Massachusetts Institute of Technology
+#
+# Permission to use, copy, modify, distribute, and sell this software and its
+# documentation for any purpose is hereby granted without fee, provided that
+# the above copyright notice appear in all copies and that both that
+# copyright notice and this permission notice appear in supporting
+# documentation, and that the name of M.I.T. not be used in advertising or
+# publicity pertaining to distribution of the software without specific,
+# written prior permission. M.I.T. makes no representations about the
+# suitability of this software for any purpose. It is provided "as is"
+# without express or implied warranty.
+#
+# Calling this script install-sh is preferred over install.sh, to prevent
+# `make' implicit rules from creating a file called install from it
+# when there is no Makefile.
+#
+# This script is compatible with the BSD install script, but was written
+# from scratch. It can only install one file at a time, a restriction
+# shared with many OS's install programs.
+
+# Modified for INN by adding the -B option to request saving of the original
+# file (if install is overwriting an existing file). -B takes an argument,
+# the suffix to use. INN invokes this script as install-sh -B .OLD. Also
+# modified to use cp -p instead of just cp to install programs when invoked
+# as install -c.
+
+
+# set DOITPROG to echo to test this script
+
+# Don't use :- since 4.3BSD and earlier shells don't like it.
+doit="${DOITPROG-}"
+
+
+# put in absolute paths if you don't have them in your path; or use env. vars.
+
+mvprog="${MVPROG-mv}"
+cpprog="${CPPROG-cp}"
+chmodprog="${CHMODPROG-chmod}"
+chownprog="${CHOWNPROG-chown}"
+chgrpprog="${CHGRPPROG-chgrp}"
+stripprog="${STRIPPROG-strip}"
+rmprog="${RMPROG-rm}"
+mkdirprog="${MKDIRPROG-mkdir}"
+
+backupsuffix=""
+transformbasename=""
+transform_arg=""
+instcmd="$mvprog"
+chmodcmd="$chmodprog 0755"
+chowncmd=""
+chgrpcmd=""
+stripcmd=""
+rmcmd="$rmprog -f"
+mvcmd="$mvprog"
+src=""
+dst=""
+dir_arg=""
+
+while [ x"$1" != x ]; do
+ case $1 in
+ -B) backupsuffix="$2"
+ shift
+ shift
+ continue;;
+
+ -c) instcmd="$cpprog -p"
+ shift
+ continue;;
+
+ -d) dir_arg=true
+ shift
+ continue;;
+
+ -m) chmodcmd="$chmodprog $2"
+ shift
+ shift
+ continue;;
+
+ -o) chowncmd="$chownprog $2"
+ shift
+ shift
+ continue;;
+
+ -g) chgrpcmd="$chgrpprog $2"
+ shift
+ shift
+ continue;;
+
+ -s) stripcmd="$stripprog"
+ shift
+ continue;;
+
+ -t=*) transformarg=`echo $1 | sed 's/-t=//'`
+ shift
+ continue;;
+
+ -b=*) transformbasename=`echo $1 | sed 's/-b=//'`
+ shift
+ continue;;
+
+ *) if [ x"$src" = x ]
+ then
+ src=$1
+ else
+ # this colon is to work around a 386BSD /bin/sh bug
+ :
+ dst=$1
+ fi
+ shift
+ continue;;
+ esac
+done
+
+if [ x"$src" = x ]
+then
+ echo "install: no input file specified"
+ exit 1
+else
+ true
+fi
+
+# For Cygwin compatibility.
+if [ -x "$src".exe ]; then
+ src=${src}.exe
+fi
+
+if [ x"$dir_arg" != x ]; then
+ dst=$src
+ src=""
+
+ if [ -d $dst ]; then
+ instcmd=:
+ chmodcmd=""
+ chowncmd=""
+ else
+ instcmd=mkdir
+ fi
+else
+
+# Waiting for this to be detected by the "$instcmd $src $dsttmp" command
+# might cause directories to be created, which would be especially bad
+# if $src (and thus $dsttmp) contains '*'.
+
+ if [ -f $src -o -d $src ]
+ then
+ true
+ else
+ echo "install: $src does not exist"
+ exit 1
+ fi
+
+ if [ x"$dst" = x ]
+ then
+ echo "install: no destination specified"
+ exit 1
+ else
+ true
+ fi
+
+# If destination is a directory, append the input filename; if your system
+# does not like double slashes in filenames, you may need to add some logic
+
+ if [ -d $dst ]
+ then
+ dst="$dst"/`basename $src`
+ else
+ true
+ fi
+fi
+
+## this sed command emulates the dirname command
+dstdir=`echo $dst | sed -e 's,[^/]*$,,;s,/$,,;s,^$,.,'`
+
+# Make sure that the destination directory exists.
+# this part is taken from Noah Friedman's mkinstalldirs script
+
+# Skip lots of stat calls in the usual case.
+if [ ! -d "$dstdir" ]; then
+defaultIFS='
+'
+IFS="${IFS-${defaultIFS}}"
+
+oIFS="${IFS}"
+# Some sh's can't handle IFS=/ for some reason.
+IFS='%'
+set - `echo ${dstdir} | sed -e 's@/@%@g' -e 's@^%@/@'`
+IFS="${oIFS}"
+
+pathcomp=''
+
+while [ $# -ne 0 ] ; do
+ pathcomp="${pathcomp}${1}"
+ shift
+
+ if [ ! -d "${pathcomp}" ] ;
+ then
+ $mkdirprog "${pathcomp}"
+ else
+ true
+ fi
+
+ pathcomp="${pathcomp}/"
+done
+fi
+
+if [ x"$dir_arg" != x ]
+then
+ $doit $instcmd $dst &&
+
+ if [ x"$chowncmd" != x ]; then $doit $chowncmd $dst; else true ; fi &&
+ if [ x"$chgrpcmd" != x ]; then $doit $chgrpcmd $dst; else true ; fi &&
+ if [ x"$stripcmd" != x ]; then $doit $stripcmd $dst; else true ; fi &&
+ if [ x"$chmodcmd" != x ]; then $doit $chmodcmd $dst; else true ; fi
+else
+
+# If we're going to rename the final executable, determine the name now.
+
+ if [ x"$transformarg" = x ]
+ then
+ dstfile=`basename $dst`
+ else
+ dstfile=`basename $dst $transformbasename |
+ sed $transformarg`$transformbasename
+ fi
+
+# don't allow the sed command to completely eliminate the filename
+
+ if [ x"$dstfile" = x ]
+ then
+ dstfile=`basename $dst`
+ else
+ true
+ fi
+
+# Make a temp file name in the proper directory.
+
+ dsttmp=$dstdir/#inst.$$#
+
+# Move or copy the file name to the temp name
+
+ $doit $instcmd $src $dsttmp &&
+
+ trap "rm -f ${dsttmp}" 0 &&
+
+# and set any options; do chmod last to preserve setuid bits
+
+# If any of these fail, we abort the whole thing. If we want to
+# ignore errors from any of these, just make sure not to ignore
+# errors from the above "$doit $instcmd $src $dsttmp" command.
+
+ if [ x"$chowncmd" != x ]; then $doit $chowncmd $dsttmp; else true;fi &&
+ if [ x"$chgrpcmd" != x ]; then $doit $chgrpcmd $dsttmp; else true;fi &&
+ if [ x"$stripcmd" != x ]; then $doit $stripcmd $dsttmp; else true;fi &&
+ if [ x"$chmodcmd" != x ]; then $doit $chmodcmd $dsttmp; else true;fi &&
+
+# Now rename the file to the real destination. If $backupsuffix
+# is set, rename the existing file if it exists; otherwise, just
+# remove it.
+
+ if [ x"$backupsuffix" != x ] && [ -f "$dstdir/$dstfile" ]; then
+ $doit $mvcmd $dstdir/$dstfile $dstdir/$dstfile$backupsuffix
+ else
+ $doit $rmcmd -f $dstdir/$dstfile
+ fi &&
+
+ $doit $mvcmd $dsttmp $dstdir/$dstfile
+
+fi &&
+
+
+exit 0
--- /dev/null
+# ltmain.sh - Provide generalized library-building support services.
+# NOTE: Changing this file will not affect anything until you rerun configure.
+#
+# Copyright (C) 1996, 1997, 1998, 1999, 2000, 2001
+# Free Software Foundation, Inc.
+# Originally by Gordon Matzigkeit <gord@gnu.ai.mit.edu>, 1996
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful, but
+# WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+# General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+#
+# As a special exception to the GNU General Public License, if you
+# distribute this file as part of a program that contains a
+# configuration script generated by Autoconf, you may include it under
+# the same distribution terms that you use for the rest of that program.
+
+# This file has been modified from the standard libtool version to
+# recognize the additional -O, -G, and -b flags that INN's install program
+# recognizes; apart from that, it's identical to the stock libtool
+# distribution.
+
+# Check that we have a working $echo.
+if test "X$1" = X--no-reexec; then
+ # Discard the --no-reexec flag, and continue.
+ shift
+elif test "X$1" = X--fallback-echo; then
+ # Avoid inline document here, it may be left over
+ :
+elif test "X`($echo '\t') 2>/dev/null`" = 'X\t'; then
+ # Yippee, $echo works!
+ :
+else
+ # Restart under the correct shell, and then maybe $echo will work.
+ exec $SHELL "$0" --no-reexec ${1+"$@"}
+fi
+
+if test "X$1" = X--fallback-echo; then
+ # used as fallback echo
+ shift
+ cat <<EOF
+$*
+EOF
+ exit 0
+fi
+
+# The name of this program.
+progname=`$echo "$0" | sed 's%^.*/%%'`
+modename="$progname"
+
+# Constants.
+PROGRAM=ltmain.sh
+PACKAGE=libtool
+VERSION=1.4.2
+TIMESTAMP=" (1.922.2.53 2001/09/11 03:18:52)"
+
+default_mode=
+help="Try \`$progname --help' for more information."
+magic="%%%MAGIC variable%%%"
+mkdir="mkdir"
+mv="mv -f"
+rm="rm -f"
+
+# Sed substitution that helps us do robust quoting. It backslashifies
+# metacharacters that are still active within double-quoted strings.
+Xsed='sed -e 1s/^X//'
+sed_quote_subst='s/\([\\`\\"$\\\\]\)/\\\1/g'
+SP2NL='tr \040 \012'
+NL2SP='tr \015\012 \040\040'
+
+# NLS nuisances.
+# Only set LANG and LC_ALL to C if already set.
+# These must not be set unconditionally because not all systems understand
+# e.g. LANG=C (notably SCO).
+# We save the old values to restore during execute mode.
+if test "${LC_ALL+set}" = set; then
+ save_LC_ALL="$LC_ALL"; LC_ALL=C; export LC_ALL
+fi
+if test "${LANG+set}" = set; then
+ save_LANG="$LANG"; LANG=C; export LANG
+fi
+
+# Make sure IFS has a sensible default
+: ${IFS=" "}
+
+if test "$build_libtool_libs" != yes && test "$build_old_libs" != yes; then
+ echo "$modename: not configured to build any kind of library" 1>&2
+ echo "Fatal configuration error. See the $PACKAGE docs for more information." 1>&2
+ exit 1
+fi
+
+# Global variables.
+mode=$default_mode
+nonopt=
+prev=
+prevopt=
+run=
+show="$echo"
+show_help=
+execute_dlfiles=
+lo2o="s/\\.lo\$/.${objext}/"
+o2lo="s/\\.${objext}\$/.lo/"
+
+# Parse our command line options once, thoroughly.
+while test $# -gt 0
+do
+ arg="$1"
+ shift
+
+ case $arg in
+ -*=*) optarg=`$echo "X$arg" | $Xsed -e 's/[-_a-zA-Z0-9]*=//'` ;;
+ *) optarg= ;;
+ esac
+
+ # If the previous option needs an argument, assign it.
+ if test -n "$prev"; then
+ case $prev in
+ execute_dlfiles)
+ execute_dlfiles="$execute_dlfiles $arg"
+ ;;
+ *)
+ eval "$prev=\$arg"
+ ;;
+ esac
+
+ prev=
+ prevopt=
+ continue
+ fi
+
+ # Have we seen a non-optional argument yet?
+ case $arg in
+ --help)
+ show_help=yes
+ ;;
+
+ --version)
+ echo "$PROGRAM (GNU $PACKAGE) $VERSION$TIMESTAMP"
+ exit 0
+ ;;
+
+ --config)
+ sed -e '1,/^# ### BEGIN LIBTOOL CONFIG/d' -e '/^# ### END LIBTOOL CONFIG/,$d' $0
+ exit 0
+ ;;
+
+ --debug)
+ echo "$progname: enabling shell trace mode"
+ set -x
+ ;;
+
+ --dry-run | -n)
+ run=:
+ ;;
+
+ --features)
+ echo "host: $host"
+ if test "$build_libtool_libs" = yes; then
+ echo "enable shared libraries"
+ else
+ echo "disable shared libraries"
+ fi
+ if test "$build_old_libs" = yes; then
+ echo "enable static libraries"
+ else
+ echo "disable static libraries"
+ fi
+ exit 0
+ ;;
+
+ --finish) mode="finish" ;;
+
+ --mode) prevopt="--mode" prev=mode ;;
+ --mode=*) mode="$optarg" ;;
+
+ --quiet | --silent)
+ show=:
+ ;;
+
+ -dlopen)
+ prevopt="-dlopen"
+ prev=execute_dlfiles
+ ;;
+
+ -*)
+ $echo "$modename: unrecognized option \`$arg'" 1>&2
+ $echo "$help" 1>&2
+ exit 1
+ ;;
+
+ *)
+ nonopt="$arg"
+ break
+ ;;
+ esac
+done
+
+if test -n "$prevopt"; then
+ $echo "$modename: option \`$prevopt' requires an argument" 1>&2
+ $echo "$help" 1>&2
+ exit 1
+fi
+
+# If this variable is set in any of the actions, the command in it
+# will be execed at the end. This prevents here-documents from being
+# left over by shells.
+exec_cmd=
+
+if test -z "$show_help"; then
+
+ # Infer the operation mode.
+ if test -z "$mode"; then
+ case $nonopt in
+ *cc | *++ | gcc* | *-gcc*)
+ mode=link
+ for arg
+ do
+ case $arg in
+ -c)
+ mode=compile
+ break
+ ;;
+ esac
+ done
+ ;;
+ *db | *dbx | *strace | *truss)
+ mode=execute
+ ;;
+ *install*|cp|mv)
+ mode=install
+ ;;
+ *rm)
+ mode=uninstall
+ ;;
+ *)
+ # If we have no mode, but dlfiles were specified, then do execute mode.
+ test -n "$execute_dlfiles" && mode=execute
+
+ # Just use the default operation mode.
+ if test -z "$mode"; then
+ if test -n "$nonopt"; then
+ $echo "$modename: warning: cannot infer operation mode from \`$nonopt'" 1>&2
+ else
+ $echo "$modename: warning: cannot infer operation mode without MODE-ARGS" 1>&2
+ fi
+ fi
+ ;;
+ esac
+ fi
+
+ # Only execute mode is allowed to have -dlopen flags.
+ if test -n "$execute_dlfiles" && test "$mode" != execute; then
+ $echo "$modename: unrecognized option \`-dlopen'" 1>&2
+ $echo "$help" 1>&2
+ exit 1
+ fi
+
+ # Change the help message to a mode-specific one.
+ generic_help="$help"
+ help="Try \`$modename --help --mode=$mode' for more information."
+
+ # These modes are in order of execution frequency so that they run quickly.
+ case $mode in
+ # libtool compile mode
+ compile)
+ modename="$modename: compile"
+ # Get the compilation command and the source file.
+ base_compile=
+ prev=
+ lastarg=
+ srcfile="$nonopt"
+ suppress_output=
+
+ user_target=no
+ for arg
+ do
+ case $prev in
+ "") ;;
+ xcompiler)
+ # Aesthetically quote the previous argument.
+ prev=
+ lastarg=`$echo "X$arg" | $Xsed -e "$sed_quote_subst"`
+
+ case $arg in
+ # Double-quote args containing other shell metacharacters.
+ # Many Bourne shells cannot handle close brackets correctly
+ # in scan sets, so we specify it separately.
+ *[\[\~\#\^\&\*\(\)\{\}\|\;\<\>\?\'\ \ ]*|*]*|"")
+ arg="\"$arg\""
+ ;;
+ esac
+
+ # Add the previous argument to base_compile.
+ if test -z "$base_compile"; then
+ base_compile="$lastarg"
+ else
+ base_compile="$base_compile $lastarg"
+ fi
+ continue
+ ;;
+ esac
+
+ # Accept any command-line options.
+ case $arg in
+ -o)
+ if test "$user_target" != "no"; then
+ $echo "$modename: you cannot specify \`-o' more than once" 1>&2
+ exit 1
+ fi
+ user_target=next
+ ;;
+
+ -static)
+ build_old_libs=yes
+ continue
+ ;;
+
+ -prefer-pic)
+ pic_mode=yes
+ continue
+ ;;
+
+ -prefer-non-pic)
+ pic_mode=no
+ continue
+ ;;
+
+ -Xcompiler)
+ prev=xcompiler
+ continue
+ ;;
+
+ -Wc,*)
+ args=`$echo "X$arg" | $Xsed -e "s/^-Wc,//"`
+ lastarg=
+ save_ifs="$IFS"; IFS=','
+ for arg in $args; do
+ IFS="$save_ifs"
+
+ # Double-quote args containing other shell metacharacters.
+ # Many Bourne shells cannot handle close brackets correctly
+ # in scan sets, so we specify it separately.
+ case $arg in
+ *[\[\~\#\^\&\*\(\)\{\}\|\;\<\>\?\'\ \ ]*|*]*|"")
+ arg="\"$arg\""
+ ;;
+ esac
+ lastarg="$lastarg $arg"
+ done
+ IFS="$save_ifs"
+ lastarg=`$echo "X$lastarg" | $Xsed -e "s/^ //"`
+
+ # Add the arguments to base_compile.
+ if test -z "$base_compile"; then
+ base_compile="$lastarg"
+ else
+ base_compile="$base_compile $lastarg"
+ fi
+ continue
+ ;;
+ esac
+
+ case $user_target in
+ next)
+ # The next one is the -o target name
+ user_target=yes
+ continue
+ ;;
+ yes)
+ # We got the output file
+ user_target=set
+ libobj="$arg"
+ continue
+ ;;
+ esac
+
+ # Accept the current argument as the source file.
+ lastarg="$srcfile"
+ srcfile="$arg"
+
+ # Aesthetically quote the previous argument.
+
+ # Backslashify any backslashes, double quotes, and dollar signs.
+ # These are the only characters that are still specially
+ # interpreted inside of double-quoted scrings.
+ lastarg=`$echo "X$lastarg" | $Xsed -e "$sed_quote_subst"`
+
+ # Double-quote args containing other shell metacharacters.
+ # Many Bourne shells cannot handle close brackets correctly
+ # in scan sets, so we specify it separately.
+ case $lastarg in
+ *[\[\~\#\^\&\*\(\)\{\}\|\;\<\>\?\'\ \ ]*|*]*|"")
+ lastarg="\"$lastarg\""
+ ;;
+ esac
+
+ # Add the previous argument to base_compile.
+ if test -z "$base_compile"; then
+ base_compile="$lastarg"
+ else
+ base_compile="$base_compile $lastarg"
+ fi
+ done
+
+ case $user_target in
+ set)
+ ;;
+ no)
+ # Get the name of the library object.
+ libobj=`$echo "X$srcfile" | $Xsed -e 's%^.*/%%'`
+ ;;
+ *)
+ $echo "$modename: you must specify a target with \`-o'" 1>&2
+ exit 1
+ ;;
+ esac
+
+ # Recognize several different file suffixes.
+ # If the user specifies -o file.o, it is replaced with file.lo
+ xform='[cCFSfmso]'
+ case $libobj in
+ *.ada) xform=ada ;;
+ *.adb) xform=adb ;;
+ *.ads) xform=ads ;;
+ *.asm) xform=asm ;;
+ *.c++) xform=c++ ;;
+ *.cc) xform=cc ;;
+ *.cpp) xform=cpp ;;
+ *.cxx) xform=cxx ;;
+ *.f90) xform=f90 ;;
+ *.for) xform=for ;;
+ esac
+
+ libobj=`$echo "X$libobj" | $Xsed -e "s/\.$xform$/.lo/"`
+
+ case $libobj in
+ *.lo) obj=`$echo "X$libobj" | $Xsed -e "$lo2o"` ;;
+ *)
+ $echo "$modename: cannot determine name of library object from \`$libobj'" 1>&2
+ exit 1
+ ;;
+ esac
+
+ if test -z "$base_compile"; then
+ $echo "$modename: you must specify a compilation command" 1>&2
+ $echo "$help" 1>&2
+ exit 1
+ fi
+
+ # Delete any leftover library objects.
+ if test "$build_old_libs" = yes; then
+ removelist="$obj $libobj"
+ else
+ removelist="$libobj"
+ fi
+
+ $run $rm $removelist
+ trap "$run $rm $removelist; exit 1" 1 2 15
+
+ # On Cygwin there's no "real" PIC flag so we must build both object types
+ case $host_os in
+ cygwin* | mingw* | pw32* | os2*)
+ pic_mode=default
+ ;;
+ esac
+ if test $pic_mode = no && test "$deplibs_check_method" != pass_all; then
+ # non-PIC code in shared libraries is not supported
+ pic_mode=default
+ fi
+
+ # Calculate the filename of the output object if compiler does
+ # not support -o with -c
+ if test "$compiler_c_o" = no; then
+ output_obj=`$echo "X$srcfile" | $Xsed -e 's%^.*/%%' -e 's%\.[^.]*$%%'`.${objext}
+ lockfile="$output_obj.lock"
+ removelist="$removelist $output_obj $lockfile"
+ trap "$run $rm $removelist; exit 1" 1 2 15
+ else
+ need_locks=no
+ lockfile=
+ fi
+
+ # Lock this critical section if it is needed
+ # We use this script file to make the link, it avoids creating a new file
+ if test "$need_locks" = yes; then
+ until $run ln "$0" "$lockfile" 2>/dev/null; do
+ $show "Waiting for $lockfile to be removed"
+ sleep 2
+ done
+ elif test "$need_locks" = warn; then
+ if test -f "$lockfile"; then
+ echo "\
+*** ERROR, $lockfile exists and contains:
+`cat $lockfile 2>/dev/null`
+
+This indicates that another process is trying to use the same
+temporary object file, and libtool could not work around it because
+your compiler does not support \`-c' and \`-o' together. If you
+repeat this compilation, it may succeed, by chance, but you had better
+avoid parallel builds (make -j) in this platform, or get a better
+compiler."
+
+ $run $rm $removelist
+ exit 1
+ fi
+ echo $srcfile > "$lockfile"
+ fi
+
+ if test -n "$fix_srcfile_path"; then
+ eval srcfile=\"$fix_srcfile_path\"
+ fi
+
+ # Only build a PIC object if we are building libtool libraries.
+ if test "$build_libtool_libs" = yes; then
+ # Without this assignment, base_compile gets emptied.
+ fbsd_hideous_sh_bug=$base_compile
+
+ if test "$pic_mode" != no; then
+ # All platforms use -DPIC, to notify preprocessed assembler code.
+ command="$base_compile $srcfile $pic_flag -DPIC"
+ else
+ # Don't build PIC code
+ command="$base_compile $srcfile"
+ fi
+ if test "$build_old_libs" = yes; then
+ lo_libobj="$libobj"
+ dir=`$echo "X$libobj" | $Xsed -e 's%/[^/]*$%%'`
+ if test "X$dir" = "X$libobj"; then
+ dir="$objdir"
+ else
+ dir="$dir/$objdir"
+ fi
+ libobj="$dir/"`$echo "X$libobj" | $Xsed -e 's%^.*/%%'`
+
+ if test -d "$dir"; then
+ $show "$rm $libobj"
+ $run $rm $libobj
+ else
+ $show "$mkdir $dir"
+ $run $mkdir $dir
+ status=$?
+ if test $status -ne 0 && test ! -d $dir; then
+ exit $status
+ fi
+ fi
+ fi
+ if test "$compiler_o_lo" = yes; then
+ output_obj="$libobj"
+ command="$command -o $output_obj"
+ elif test "$compiler_c_o" = yes; then
+ output_obj="$obj"
+ command="$command -o $output_obj"
+ fi
+
+ $run $rm "$output_obj"
+ $show "$command"
+ if $run eval "$command"; then :
+ else
+ test -n "$output_obj" && $run $rm $removelist
+ exit 1
+ fi
+
+ if test "$need_locks" = warn &&
+ test x"`cat $lockfile 2>/dev/null`" != x"$srcfile"; then
+ echo "\
+*** ERROR, $lockfile contains:
+`cat $lockfile 2>/dev/null`
+
+but it should contain:
+$srcfile
+
+This indicates that another process is trying to use the same
+temporary object file, and libtool could not work around it because
+your compiler does not support \`-c' and \`-o' together. If you
+repeat this compilation, it may succeed, by chance, but you had better
+avoid parallel builds (make -j) in this platform, or get a better
+compiler."
+
+ $run $rm $removelist
+ exit 1
+ fi
+
+ # Just move the object if needed, then go on to compile the next one
+ if test x"$output_obj" != x"$libobj"; then
+ $show "$mv $output_obj $libobj"
+ if $run $mv $output_obj $libobj; then :
+ else
+ error=$?
+ $run $rm $removelist
+ exit $error
+ fi
+ fi
+
+ # If we have no pic_flag, then copy the object into place and finish.
+ if (test -z "$pic_flag" || test "$pic_mode" != default) &&
+ test "$build_old_libs" = yes; then
+ # Rename the .lo from within objdir to obj
+ if test -f $obj; then
+ $show $rm $obj
+ $run $rm $obj
+ fi
+
+ $show "$mv $libobj $obj"
+ if $run $mv $libobj $obj; then :
+ else
+ error=$?
+ $run $rm $removelist
+ exit $error
+ fi
+
+ xdir=`$echo "X$obj" | $Xsed -e 's%/[^/]*$%%'`
+ if test "X$xdir" = "X$obj"; then
+ xdir="."
+ else
+ xdir="$xdir"
+ fi
+ baseobj=`$echo "X$obj" | $Xsed -e "s%.*/%%"`
+ libobj=`$echo "X$baseobj" | $Xsed -e "$o2lo"`
+ # Now arrange that obj and lo_libobj become the same file
+ $show "(cd $xdir && $LN_S $baseobj $libobj)"
+ if $run eval '(cd $xdir && $LN_S $baseobj $libobj)'; then
+ # Unlock the critical section if it was locked
+ if test "$need_locks" != no; then
+ $run $rm "$lockfile"
+ fi
+ exit 0
+ else
+ error=$?
+ $run $rm $removelist
+ exit $error
+ fi
+ fi
+
+ # Allow error messages only from the first compilation.
+ suppress_output=' >/dev/null 2>&1'
+ fi
+
+ # Only build a position-dependent object if we build old libraries.
+ if test "$build_old_libs" = yes; then
+ if test "$pic_mode" != yes; then
+ # Don't build PIC code
+ command="$base_compile $srcfile"
+ else
+ # All platforms use -DPIC, to notify preprocessed assembler code.
+ command="$base_compile $srcfile $pic_flag -DPIC"
+ fi
+ if test "$compiler_c_o" = yes; then
+ command="$command -o $obj"
+ output_obj="$obj"
+ fi
+
+ # Suppress compiler output if we already did a PIC compilation.
+ command="$command$suppress_output"
+ $run $rm "$output_obj"
+ $show "$command"
+ if $run eval "$command"; then :
+ else
+ $run $rm $removelist
+ exit 1
+ fi
+
+ if test "$need_locks" = warn &&
+ test x"`cat $lockfile 2>/dev/null`" != x"$srcfile"; then
+ echo "\
+*** ERROR, $lockfile contains:
+`cat $lockfile 2>/dev/null`
+
+but it should contain:
+$srcfile
+
+This indicates that another process is trying to use the same
+temporary object file, and libtool could not work around it because
+your compiler does not support \`-c' and \`-o' together. If you
+repeat this compilation, it may succeed, by chance, but you had better
+avoid parallel builds (make -j) in this platform, or get a better
+compiler."
+
+ $run $rm $removelist
+ exit 1
+ fi
+
+ # Just move the object if needed
+ if test x"$output_obj" != x"$obj"; then
+ $show "$mv $output_obj $obj"
+ if $run $mv $output_obj $obj; then :
+ else
+ error=$?
+ $run $rm $removelist
+ exit $error
+ fi
+ fi
+
+ # Create an invalid libtool object if no PIC, so that we do not
+ # accidentally link it into a program.
+ if test "$build_libtool_libs" != yes; then
+ $show "echo timestamp > $libobj"
+ $run eval "echo timestamp > \$libobj" || exit $?
+ else
+ # Move the .lo from within objdir
+ $show "$mv $libobj $lo_libobj"
+ if $run $mv $libobj $lo_libobj; then :
+ else
+ error=$?
+ $run $rm $removelist
+ exit $error
+ fi
+ fi
+ fi
+
+ # Unlock the critical section if it was locked
+ if test "$need_locks" != no; then
+ $run $rm "$lockfile"
+ fi
+
+ exit 0
+ ;;
+
+ # libtool link mode
+ link | relink)
+ modename="$modename: link"
+ case $host in
+ *-*-cygwin* | *-*-mingw* | *-*-pw32* | *-*-os2*)
+ # It is impossible to link a dll without this setting, and
+ # we shouldn't force the makefile maintainer to figure out
+ # which system we are compiling for in order to pass an extra
+ # flag for every libtool invokation.
+ # allow_undefined=no
+
+ # FIXME: Unfortunately, there are problems with the above when trying
+ # to make a dll which has undefined symbols, in which case not
+ # even a static library is built. For now, we need to specify
+ # -no-undefined on the libtool link line when we can be certain
+ # that all symbols are satisfied, otherwise we get a static library.
+ allow_undefined=yes
+ ;;
+ *)
+ allow_undefined=yes
+ ;;
+ esac
+ libtool_args="$nonopt"
+ compile_command="$nonopt"
+ finalize_command="$nonopt"
+
+ compile_rpath=
+ finalize_rpath=
+ compile_shlibpath=
+ finalize_shlibpath=
+ convenience=
+ old_convenience=
+ deplibs=
+ old_deplibs=
+ compiler_flags=
+ linker_flags=
+ dllsearchpath=
+ lib_search_path=`pwd`
+
+ avoid_version=no
+ dlfiles=
+ dlprefiles=
+ dlself=no
+ export_dynamic=no
+ export_symbols=
+ export_symbols_regex=
+ generated=
+ libobjs=
+ ltlibs=
+ module=no
+ no_install=no
+ objs=
+ prefer_static_libs=no
+ preload=no
+ prev=
+ prevarg=
+ release=
+ rpath=
+ xrpath=
+ perm_rpath=
+ temp_rpath=
+ thread_safe=no
+ vinfo=
+
+ # We need to know -static, to get the right output filenames.
+ for arg
+ do
+ case $arg in
+ -all-static | -static)
+ if test "X$arg" = "X-all-static"; then
+ if test "$build_libtool_libs" = yes && test -z "$link_static_flag"; then
+ $echo "$modename: warning: complete static linking is impossible in this configuration" 1>&2
+ fi
+ if test -n "$link_static_flag"; then
+ dlopen_self=$dlopen_self_static
+ fi
+ else
+ if test -z "$pic_flag" && test -n "$link_static_flag"; then
+ dlopen_self=$dlopen_self_static
+ fi
+ fi
+ build_libtool_libs=no
+ build_old_libs=yes
+ prefer_static_libs=yes
+ break
+ ;;
+ esac
+ done
+
+ # See if our shared archives depend on static archives.
+ test -n "$old_archive_from_new_cmds" && build_old_libs=yes
+
+ # Go through the arguments, transforming them on the way.
+ while test $# -gt 0; do
+ arg="$1"
+ shift
+ case $arg in
+ *[\[\~\#\^\&\*\(\)\{\}\|\;\<\>\?\'\ \ ]*|*]*|"")
+ qarg=\"`$echo "X$arg" | $Xsed -e "$sed_quote_subst"`\" ### testsuite: skip nested quoting test
+ ;;
+ *) qarg=$arg ;;
+ esac
+ libtool_args="$libtool_args $qarg"
+
+ # If the previous option needs an argument, assign it.
+ if test -n "$prev"; then
+ case $prev in
+ output)
+ compile_command="$compile_command @OUTPUT@"
+ finalize_command="$finalize_command @OUTPUT@"
+ ;;
+ esac
+
+ case $prev in
+ dlfiles|dlprefiles)
+ if test "$preload" = no; then
+ # Add the symbol object into the linking commands.
+ compile_command="$compile_command @SYMFILE@"
+ finalize_command="$finalize_command @SYMFILE@"
+ preload=yes
+ fi
+ case $arg in
+ *.la | *.lo) ;; # We handle these cases below.
+ force)
+ if test "$dlself" = no; then
+ dlself=needless
+ export_dynamic=yes
+ fi
+ prev=
+ continue
+ ;;
+ self)
+ if test "$prev" = dlprefiles; then
+ dlself=yes
+ elif test "$prev" = dlfiles && test "$dlopen_self" != yes; then
+ dlself=yes
+ else
+ dlself=needless
+ export_dynamic=yes
+ fi
+ prev=
+ continue
+ ;;
+ *)
+ if test "$prev" = dlfiles; then
+ dlfiles="$dlfiles $arg"
+ else
+ dlprefiles="$dlprefiles $arg"
+ fi
+ prev=
+ continue
+ ;;
+ esac
+ ;;
+ expsyms)
+ export_symbols="$arg"
+ if test ! -f "$arg"; then
+ $echo "$modename: symbol file \`$arg' does not exist"
+ exit 1
+ fi
+ prev=
+ continue
+ ;;
+ expsyms_regex)
+ export_symbols_regex="$arg"
+ prev=
+ continue
+ ;;
+ release)
+ release="-$arg"
+ prev=
+ continue
+ ;;
+ rpath | xrpath)
+ # We need an absolute path.
+ case $arg in
+ [\\/]* | [A-Za-z]:[\\/]*) ;;
+ *)
+ $echo "$modename: only absolute run-paths are allowed" 1>&2
+ exit 1
+ ;;
+ esac
+ if test "$prev" = rpath; then
+ case "$rpath " in
+ *" $arg "*) ;;
+ *) rpath="$rpath $arg" ;;
+ esac
+ else
+ case "$xrpath " in
+ *" $arg "*) ;;
+ *) xrpath="$xrpath $arg" ;;
+ esac
+ fi
+ prev=
+ continue
+ ;;
+ xcompiler)
+ compiler_flags="$compiler_flags $qarg"
+ prev=
+ compile_command="$compile_command $qarg"
+ finalize_command="$finalize_command $qarg"
+ continue
+ ;;
+ xlinker)
+ linker_flags="$linker_flags $qarg"
+ compiler_flags="$compiler_flags $wl$qarg"
+ prev=
+ compile_command="$compile_command $wl$qarg"
+ finalize_command="$finalize_command $wl$qarg"
+ continue
+ ;;
+ *)
+ eval "$prev=\"\$arg\""
+ prev=
+ continue
+ ;;
+ esac
+ fi # test -n $prev
+
+ prevarg="$arg"
+
+ case $arg in
+ -all-static)
+ if test -n "$link_static_flag"; then
+ compile_command="$compile_command $link_static_flag"
+ finalize_command="$finalize_command $link_static_flag"
+ fi
+ continue
+ ;;
+
+ -allow-undefined)
+ # FIXME: remove this flag sometime in the future.
+ $echo "$modename: \`-allow-undefined' is deprecated because it is the default" 1>&2
+ continue
+ ;;
+
+ -avoid-version)
+ avoid_version=yes
+ continue
+ ;;
+
+ -dlopen)
+ prev=dlfiles
+ continue
+ ;;
+
+ -dlpreopen)
+ prev=dlprefiles
+ continue
+ ;;
+
+ -export-dynamic)
+ export_dynamic=yes
+ continue
+ ;;
+
+ -export-symbols | -export-symbols-regex)
+ if test -n "$export_symbols" || test -n "$export_symbols_regex"; then
+ $echo "$modename: more than one -exported-symbols argument is not allowed"
+ exit 1
+ fi
+ if test "X$arg" = "X-export-symbols"; then
+ prev=expsyms
+ else
+ prev=expsyms_regex
+ fi
+ continue
+ ;;
+
+ # The native IRIX linker understands -LANG:*, -LIST:* and -LNO:*
+ # so, if we see these flags be careful not to treat them like -L
+ -L[A-Z][A-Z]*:*)
+ case $with_gcc/$host in
+ no/*-*-irix*)
+ compile_command="$compile_command $arg"
+ finalize_command="$finalize_command $arg"
+ ;;
+ esac
+ continue
+ ;;
+
+ -L*)
+ dir=`$echo "X$arg" | $Xsed -e 's/^-L//'`
+ # We need an absolute path.
+ case $dir in
+ [\\/]* | [A-Za-z]:[\\/]*) ;;
+ *)
+ absdir=`cd "$dir" && pwd`
+ if test -z "$absdir"; then
+ $echo "$modename: cannot determine absolute directory name of \`$dir'" 1>&2
+ exit 1
+ fi
+ dir="$absdir"
+ ;;
+ esac
+ case "$deplibs " in
+ *" -L$dir "*) ;;
+ *)
+ deplibs="$deplibs -L$dir"
+ lib_search_path="$lib_search_path $dir"
+ ;;
+ esac
+ case $host in
+ *-*-cygwin* | *-*-mingw* | *-*-pw32* | *-*-os2*)
+ case :$dllsearchpath: in
+ *":$dir:"*) ;;
+ *) dllsearchpath="$dllsearchpath:$dir";;
+ esac
+ ;;
+ esac
+ continue
+ ;;
+
+ -l*)
+ if test "X$arg" = "X-lc" || test "X$arg" = "X-lm"; then
+ case $host in
+ *-*-cygwin* | *-*-pw32* | *-*-beos*)
+ # These systems don't actually have a C or math library (as such)
+ continue
+ ;;
+ *-*-mingw* | *-*-os2*)
+ # These systems don't actually have a C library (as such)
+ test "X$arg" = "X-lc" && continue
+ ;;
+ *-*-openbsd*)
+ # Do not include libc due to us having libc/libc_r.
+ test "X$arg" = "X-lc" && continue
+ ;;
+ esac
+ elif test "X$arg" = "X-lc_r"; then
+ case $host in
+ *-*-openbsd*)
+ # Do not include libc_r directly, use -pthread flag.
+ continue
+ ;;
+ esac
+ fi
+ deplibs="$deplibs $arg"
+ continue
+ ;;
+
+ -module)
+ module=yes
+ continue
+ ;;
+
+ -no-fast-install)
+ fast_install=no
+ continue
+ ;;
+
+ -no-install)
+ case $host in
+ *-*-cygwin* | *-*-mingw* | *-*-pw32* | *-*-os2*)
+ # The PATH hackery in wrapper scripts is required on Windows
+ # in order for the loader to find any dlls it needs.
+ $echo "$modename: warning: \`-no-install' is ignored for $host" 1>&2
+ $echo "$modename: warning: assuming \`-no-fast-install' instead" 1>&2
+ fast_install=no
+ ;;
+ *) no_install=yes ;;
+ esac
+ continue
+ ;;
+
+ -no-undefined)
+ allow_undefined=no
+ continue
+ ;;
+
+ -o) prev=output ;;
+
+ -release)
+ prev=release
+ continue
+ ;;
+
+ -rpath)
+ prev=rpath
+ continue
+ ;;
+
+ -R)
+ prev=xrpath
+ continue
+ ;;
+
+ -R*)
+ dir=`$echo "X$arg" | $Xsed -e 's/^-R//'`
+ # We need an absolute path.
+ case $dir in
+ [\\/]* | [A-Za-z]:[\\/]*) ;;
+ *)
+ $echo "$modename: only absolute run-paths are allowed" 1>&2
+ exit 1
+ ;;
+ esac
+ case "$xrpath " in
+ *" $dir "*) ;;
+ *) xrpath="$xrpath $dir" ;;
+ esac
+ continue
+ ;;
+
+ -static)
+ # The effects of -static are defined in a previous loop.
+ # We used to do the same as -all-static on platforms that
+ # didn't have a PIC flag, but the assumption that the effects
+ # would be equivalent was wrong. It would break on at least
+ # Digital Unix and AIX.
+ continue
+ ;;
+
+ -thread-safe)
+ thread_safe=yes
+ continue
+ ;;
+
+ -version-info)
+ prev=vinfo
+ continue
+ ;;
+
+ -Wc,*)
+ args=`$echo "X$arg" | $Xsed -e "$sed_quote_subst" -e 's/^-Wc,//'`
+ arg=
+ save_ifs="$IFS"; IFS=','
+ for flag in $args; do
+ IFS="$save_ifs"
+ case $flag in
+ *[\[\~\#\^\&\*\(\)\{\}\|\;\<\>\?\'\ \ ]*|*]*|"")
+ flag="\"$flag\""
+ ;;
+ esac
+ arg="$arg $wl$flag"
+ compiler_flags="$compiler_flags $flag"
+ done
+ IFS="$save_ifs"
+ arg=`$echo "X$arg" | $Xsed -e "s/^ //"`
+ ;;
+
+ -Wl,*)
+ args=`$echo "X$arg" | $Xsed -e "$sed_quote_subst" -e 's/^-Wl,//'`
+ arg=
+ save_ifs="$IFS"; IFS=','
+ for flag in $args; do
+ IFS="$save_ifs"
+ case $flag in
+ *[\[\~\#\^\&\*\(\)\{\}\|\;\<\>\?\'\ \ ]*|*]*|"")
+ flag="\"$flag\""
+ ;;
+ esac
+ arg="$arg $wl$flag"
+ compiler_flags="$compiler_flags $wl$flag"
+ linker_flags="$linker_flags $flag"
+ done
+ IFS="$save_ifs"
+ arg=`$echo "X$arg" | $Xsed -e "s/^ //"`
+ ;;
+
+ -Xcompiler)
+ prev=xcompiler
+ continue
+ ;;
+
+ -Xlinker)
+ prev=xlinker
+ continue
+ ;;
+
+ # Some other compiler flag.
+ -* | +*)
+ # Unknown arguments in both finalize_command and compile_command need
+ # to be aesthetically quoted because they are evaled later.
+ arg=`$echo "X$arg" | $Xsed -e "$sed_quote_subst"`
+ case $arg in
+ *[\[\~\#\^\&\*\(\)\{\}\|\;\<\>\?\'\ \ ]*|*]*|"")
+ arg="\"$arg\""
+ ;;
+ esac
+ ;;
+
+ *.lo | *.$objext)
+ # A library or standard object.
+ if test "$prev" = dlfiles; then
+ # This file was specified with -dlopen.
+ if test "$build_libtool_libs" = yes && test "$dlopen_support" = yes; then
+ dlfiles="$dlfiles $arg"
+ prev=
+ continue
+ else
+ # If libtool objects are unsupported, then we need to preload.
+ prev=dlprefiles
+ fi
+ fi
+
+ if test "$prev" = dlprefiles; then
+ # Preload the old-style object.
+ dlprefiles="$dlprefiles "`$echo "X$arg" | $Xsed -e "$lo2o"`
+ prev=
+ else
+ case $arg in
+ *.lo) libobjs="$libobjs $arg" ;;
+ *) objs="$objs $arg" ;;
+ esac
+ fi
+ ;;
+
+ *.$libext)
+ # An archive.
+ deplibs="$deplibs $arg"
+ old_deplibs="$old_deplibs $arg"
+ continue
+ ;;
+
+ *.la)
+ # A libtool-controlled library.
+
+ if test "$prev" = dlfiles; then
+ # This library was specified with -dlopen.
+ dlfiles="$dlfiles $arg"
+ prev=
+ elif test "$prev" = dlprefiles; then
+ # The library was specified with -dlpreopen.
+ dlprefiles="$dlprefiles $arg"
+ prev=
+ else
+ deplibs="$deplibs $arg"
+ fi
+ continue
+ ;;
+
+ # Some other compiler argument.
+ *)
+ # Unknown arguments in both finalize_command and compile_command need
+ # to be aesthetically quoted because they are evaled later.
+ arg=`$echo "X$arg" | $Xsed -e "$sed_quote_subst"`
+ case $arg in
+ *[\[\~\#\^\&\*\(\)\{\}\|\;\<\>\?\'\ \ ]*|*]*|"")
+ arg="\"$arg\""
+ ;;
+ esac
+ ;;
+ esac # arg
+
+ # Now actually substitute the argument into the commands.
+ if test -n "$arg"; then
+ compile_command="$compile_command $arg"
+ finalize_command="$finalize_command $arg"
+ fi
+ done # argument parsing loop
+
+ if test -n "$prev"; then
+ $echo "$modename: the \`$prevarg' option requires an argument" 1>&2
+ $echo "$help" 1>&2
+ exit 1
+ fi
+
+ if test "$export_dynamic" = yes && test -n "$export_dynamic_flag_spec"; then
+ eval arg=\"$export_dynamic_flag_spec\"
+ compile_command="$compile_command $arg"
+ finalize_command="$finalize_command $arg"
+ fi
+
+ # calculate the name of the file, without its directory
+ outputname=`$echo "X$output" | $Xsed -e 's%^.*/%%'`
+ libobjs_save="$libobjs"
+
+ if test -n "$shlibpath_var"; then
+ # get the directories listed in $shlibpath_var
+ eval shlib_search_path=\`\$echo \"X\${$shlibpath_var}\" \| \$Xsed -e \'s/:/ /g\'\`
+ else
+ shlib_search_path=
+ fi
+ eval sys_lib_search_path=\"$sys_lib_search_path_spec\"
+ eval sys_lib_dlsearch_path=\"$sys_lib_dlsearch_path_spec\"
+
+ output_objdir=`$echo "X$output" | $Xsed -e 's%/[^/]*$%%'`
+ if test "X$output_objdir" = "X$output"; then
+ output_objdir="$objdir"
+ else
+ output_objdir="$output_objdir/$objdir"
+ fi
+ # Create the object directory.
+ if test ! -d $output_objdir; then
+ $show "$mkdir $output_objdir"
+ $run $mkdir $output_objdir
+ status=$?
+ if test $status -ne 0 && test ! -d $output_objdir; then
+ exit $status
+ fi
+ fi
+
+ # Determine the type of output
+ case $output in
+ "")
+ $echo "$modename: you must specify an output file" 1>&2
+ $echo "$help" 1>&2
+ exit 1
+ ;;
+ *.$libext) linkmode=oldlib ;;
+ *.lo | *.$objext) linkmode=obj ;;
+ *.la) linkmode=lib ;;
+ *) linkmode=prog ;; # Anything else should be a program.
+ esac
+
+ specialdeplibs=
+ libs=
+ # Find all interdependent deplibs by searching for libraries
+ # that are linked more than once (e.g. -la -lb -la)
+ for deplib in $deplibs; do
+ case "$libs " in
+ *" $deplib "*) specialdeplibs="$specialdeplibs $deplib" ;;
+ esac
+ libs="$libs $deplib"
+ done
+ deplibs=
+ newdependency_libs=
+ newlib_search_path=
+ need_relink=no # whether we're linking any uninstalled libtool libraries
+ notinst_deplibs= # not-installed libtool libraries
+ notinst_path= # paths that contain not-installed libtool libraries
+ case $linkmode in
+ lib)
+ passes="conv link"
+ for file in $dlfiles $dlprefiles; do
+ case $file in
+ *.la) ;;
+ *)
+ $echo "$modename: libraries can \`-dlopen' only libtool libraries: $file" 1>&2
+ exit 1
+ ;;
+ esac
+ done
+ ;;
+ prog)
+ compile_deplibs=
+ finalize_deplibs=
+ alldeplibs=no
+ newdlfiles=
+ newdlprefiles=
+ passes="conv scan dlopen dlpreopen link"
+ ;;
+ *) passes="conv"
+ ;;
+ esac
+ for pass in $passes; do
+ if test $linkmode = prog; then
+ # Determine which files to process
+ case $pass in
+ dlopen)
+ libs="$dlfiles"
+ save_deplibs="$deplibs" # Collect dlpreopened libraries
+ deplibs=
+ ;;
+ dlpreopen) libs="$dlprefiles" ;;
+ link) libs="$deplibs %DEPLIBS% $dependency_libs" ;;
+ esac
+ fi
+ for deplib in $libs; do
+ lib=
+ found=no
+ case $deplib in
+ -l*)
+ if test $linkmode = oldlib && test $linkmode = obj; then
+ $echo "$modename: warning: \`-l' is ignored for archives/objects: $deplib" 1>&2
+ continue
+ fi
+ if test $pass = conv; then
+ deplibs="$deplib $deplibs"
+ continue
+ fi
+ name=`$echo "X$deplib" | $Xsed -e 's/^-l//'`
+ for searchdir in $newlib_search_path $lib_search_path $sys_lib_search_path $shlib_search_path; do
+ # Search the libtool library
+ lib="$searchdir/lib${name}.la"
+ if test -f "$lib"; then
+ found=yes
+ break
+ fi
+ done
+ if test "$found" != yes; then
+ # deplib doesn't seem to be a libtool library
+ if test "$linkmode,$pass" = "prog,link"; then
+ compile_deplibs="$deplib $compile_deplibs"
+ finalize_deplibs="$deplib $finalize_deplibs"
+ else
+ deplibs="$deplib $deplibs"
+ test $linkmode = lib && newdependency_libs="$deplib $newdependency_libs"
+ fi
+ continue
+ fi
+ ;; # -l
+ -L*)
+ case $linkmode in
+ lib)
+ deplibs="$deplib $deplibs"
+ test $pass = conv && continue
+ newdependency_libs="$deplib $newdependency_libs"
+ newlib_search_path="$newlib_search_path "`$echo "X$deplib" | $Xsed -e 's/^-L//'`
+ ;;
+ prog)
+ if test $pass = conv; then
+ deplibs="$deplib $deplibs"
+ continue
+ fi
+ if test $pass = scan; then
+ deplibs="$deplib $deplibs"
+ newlib_search_path="$newlib_search_path "`$echo "X$deplib" | $Xsed -e 's/^-L//'`
+ else
+ compile_deplibs="$deplib $compile_deplibs"
+ finalize_deplibs="$deplib $finalize_deplibs"
+ fi
+ ;;
+ *)
+ $echo "$modename: warning: \`-L' is ignored for archives/objects: $deplib" 1>&2
+ ;;
+ esac # linkmode
+ continue
+ ;; # -L
+ -R*)
+ if test $pass = link; then
+ dir=`$echo "X$deplib" | $Xsed -e 's/^-R//'`
+ # Make sure the xrpath contains only unique directories.
+ case "$xrpath " in
+ *" $dir "*) ;;
+ *) xrpath="$xrpath $dir" ;;
+ esac
+ fi
+ deplibs="$deplib $deplibs"
+ continue
+ ;;
+ *.la) lib="$deplib" ;;
+ *.$libext)
+ if test $pass = conv; then
+ deplibs="$deplib $deplibs"
+ continue
+ fi
+ case $linkmode in
+ lib)
+ if test "$deplibs_check_method" != pass_all; then
+ echo
+ echo "*** Warning: This library needs some functionality provided by $deplib."
+ echo "*** I have the capability to make that library automatically link in when"
+ echo "*** you link to this library. But I can only do this if you have a"
+ echo "*** shared version of the library, which you do not appear to have."
+ else
+ echo
+ echo "*** Warning: Linking the shared library $output against the"
+ echo "*** static library $deplib is not portable!"
+ deplibs="$deplib $deplibs"
+ fi
+ continue
+ ;;
+ prog)
+ if test $pass != link; then
+ deplibs="$deplib $deplibs"
+ else
+ compile_deplibs="$deplib $compile_deplibs"
+ finalize_deplibs="$deplib $finalize_deplibs"
+ fi
+ continue
+ ;;
+ esac # linkmode
+ ;; # *.$libext
+ *.lo | *.$objext)
+ if test $pass = dlpreopen || test "$dlopen_support" != yes || test "$build_libtool_libs" = no; then
+ # If there is no dlopen support or we're linking statically,
+ # we need to preload.
+ newdlprefiles="$newdlprefiles $deplib"
+ compile_deplibs="$deplib $compile_deplibs"
+ finalize_deplibs="$deplib $finalize_deplibs"
+ else
+ newdlfiles="$newdlfiles $deplib"
+ fi
+ continue
+ ;;
+ %DEPLIBS%)
+ alldeplibs=yes
+ continue
+ ;;
+ esac # case $deplib
+ if test $found = yes || test -f "$lib"; then :
+ else
+ $echo "$modename: cannot find the library \`$lib'" 1>&2
+ exit 1
+ fi
+
+ # Check to see that this really is a libtool archive.
+ if (sed -e '2q' $lib | egrep "^# Generated by .*$PACKAGE") >/dev/null 2>&1; then :
+ else
+ $echo "$modename: \`$lib' is not a valid libtool archive" 1>&2
+ exit 1
+ fi
+
+ ladir=`$echo "X$lib" | $Xsed -e 's%/[^/]*$%%'`
+ test "X$ladir" = "X$lib" && ladir="."
+
+ dlname=
+ dlopen=
+ dlpreopen=
+ libdir=
+ library_names=
+ old_library=
+ # If the library was installed with an old release of libtool,
+ # it will not redefine variable installed.
+ installed=yes
+
+ # Read the .la file
+ case $lib in
+ */* | *\\*) . $lib ;;
+ *) . ./$lib ;;
+ esac
+
+ if test "$linkmode,$pass" = "lib,link" ||
+ test "$linkmode,$pass" = "prog,scan" ||
+ { test $linkmode = oldlib && test $linkmode = obj; }; then
+ # Add dl[pre]opened files of deplib
+ test -n "$dlopen" && dlfiles="$dlfiles $dlopen"
+ test -n "$dlpreopen" && dlprefiles="$dlprefiles $dlpreopen"
+ fi
+
+ if test $pass = conv; then
+ # Only check for convenience libraries
+ deplibs="$lib $deplibs"
+ if test -z "$libdir"; then
+ if test -z "$old_library"; then
+ $echo "$modename: cannot find name of link library for \`$lib'" 1>&2
+ exit 1
+ fi
+ # It is a libtool convenience library, so add in its objects.
+ convenience="$convenience $ladir/$objdir/$old_library"
+ old_convenience="$old_convenience $ladir/$objdir/$old_library"
+ tmp_libs=
+ for deplib in $dependency_libs; do
+ deplibs="$deplib $deplibs"
+ case "$tmp_libs " in
+ *" $deplib "*) specialdeplibs="$specialdeplibs $deplib" ;;
+ esac
+ tmp_libs="$tmp_libs $deplib"
+ done
+ elif test $linkmode != prog && test $linkmode != lib; then
+ $echo "$modename: \`$lib' is not a convenience library" 1>&2
+ exit 1
+ fi
+ continue
+ fi # $pass = conv
+
+ # Get the name of the library we link against.
+ linklib=
+ for l in $old_library $library_names; do
+ linklib="$l"
+ done
+ if test -z "$linklib"; then
+ $echo "$modename: cannot find name of link library for \`$lib'" 1>&2
+ exit 1
+ fi
+
+ # This library was specified with -dlopen.
+ if test $pass = dlopen; then
+ if test -z "$libdir"; then
+ $echo "$modename: cannot -dlopen a convenience library: \`$lib'" 1>&2
+ exit 1
+ fi
+ if test -z "$dlname" || test "$dlopen_support" != yes || test "$build_libtool_libs" = no; then
+ # If there is no dlname, no dlopen support or we're linking
+ # statically, we need to preload.
+ dlprefiles="$dlprefiles $lib"
+ else
+ newdlfiles="$newdlfiles $lib"
+ fi
+ continue
+ fi # $pass = dlopen
+
+ # We need an absolute path.
+ case $ladir in
+ [\\/]* | [A-Za-z]:[\\/]*) abs_ladir="$ladir" ;;
+ *)
+ abs_ladir=`cd "$ladir" && pwd`
+ if test -z "$abs_ladir"; then
+ $echo "$modename: warning: cannot determine absolute directory name of \`$ladir'" 1>&2
+ $echo "$modename: passing it literally to the linker, although it might fail" 1>&2
+ abs_ladir="$ladir"
+ fi
+ ;;
+ esac
+ laname=`$echo "X$lib" | $Xsed -e 's%^.*/%%'`
+
+ # Find the relevant object directory and library name.
+ if test "X$installed" = Xyes; then
+ if test ! -f "$libdir/$linklib" && test -f "$abs_ladir/$linklib"; then
+ $echo "$modename: warning: library \`$lib' was moved." 1>&2
+ dir="$ladir"
+ absdir="$abs_ladir"
+ libdir="$abs_ladir"
+ else
+ dir="$libdir"
+ absdir="$libdir"
+ fi
+ else
+ dir="$ladir/$objdir"
+ absdir="$abs_ladir/$objdir"
+ # Remove this search path later
+ notinst_path="$notinst_path $abs_ladir"
+ fi # $installed = yes
+ name=`$echo "X$laname" | $Xsed -e 's/\.la$//' -e 's/^lib//'`
+
+ # This library was specified with -dlpreopen.
+ if test $pass = dlpreopen; then
+ if test -z "$libdir"; then
+ $echo "$modename: cannot -dlpreopen a convenience library: \`$lib'" 1>&2
+ exit 1
+ fi
+ # Prefer using a static library (so that no silly _DYNAMIC symbols
+ # are required to link).
+ if test -n "$old_library"; then
+ newdlprefiles="$newdlprefiles $dir/$old_library"
+ # Otherwise, use the dlname, so that lt_dlopen finds it.
+ elif test -n "$dlname"; then
+ newdlprefiles="$newdlprefiles $dir/$dlname"
+ else
+ newdlprefiles="$newdlprefiles $dir/$linklib"
+ fi
+ fi # $pass = dlpreopen
+
+ if test -z "$libdir"; then
+ # Link the convenience library
+ if test $linkmode = lib; then
+ deplibs="$dir/$old_library $deplibs"
+ elif test "$linkmode,$pass" = "prog,link"; then
+ compile_deplibs="$dir/$old_library $compile_deplibs"
+ finalize_deplibs="$dir/$old_library $finalize_deplibs"
+ else
+ deplibs="$lib $deplibs"
+ fi
+ continue
+ fi
+
+ if test $linkmode = prog && test $pass != link; then
+ newlib_search_path="$newlib_search_path $ladir"
+ deplibs="$lib $deplibs"
+
+ linkalldeplibs=no
+ if test "$link_all_deplibs" != no || test -z "$library_names" ||
+ test "$build_libtool_libs" = no; then
+ linkalldeplibs=yes
+ fi
+
+ tmp_libs=
+ for deplib in $dependency_libs; do
+ case $deplib in
+ -L*) newlib_search_path="$newlib_search_path "`$echo "X$deplib" | $Xsed -e 's/^-L//'`;; ### testsuite: skip nested quoting test
+ esac
+ # Need to link against all dependency_libs?
+ if test $linkalldeplibs = yes; then
+ deplibs="$deplib $deplibs"
+ else
+ # Need to hardcode shared library paths
+ # or/and link against static libraries
+ newdependency_libs="$deplib $newdependency_libs"
+ fi
+ case "$tmp_libs " in
+ *" $deplib "*) specialdeplibs="$specialdeplibs $deplib" ;;
+ esac
+ tmp_libs="$tmp_libs $deplib"
+ done # for deplib
+ continue
+ fi # $linkmode = prog...
+
+ link_static=no # Whether the deplib will be linked statically
+ if test -n "$library_names" &&
+ { test "$prefer_static_libs" = no || test -z "$old_library"; }; then
+ # Link against this shared library
+
+ if test "$linkmode,$pass" = "prog,link" ||
+ { test $linkmode = lib && test $hardcode_into_libs = yes; }; then
+ # Hardcode the library path.
+ # Skip directories that are in the system default run-time
+ # search path.
+ case " $sys_lib_dlsearch_path " in
+ *" $absdir "*) ;;
+ *)
+ case "$compile_rpath " in
+ *" $absdir "*) ;;
+ *) compile_rpath="$compile_rpath $absdir"
+ esac
+ ;;
+ esac
+ case " $sys_lib_dlsearch_path " in
+ *" $libdir "*) ;;
+ *)
+ case "$finalize_rpath " in
+ *" $libdir "*) ;;
+ *) finalize_rpath="$finalize_rpath $libdir"
+ esac
+ ;;
+ esac
+ if test $linkmode = prog; then
+ # We need to hardcode the library path
+ if test -n "$shlibpath_var"; then
+ # Make sure the rpath contains only unique directories.
+ case "$temp_rpath " in
+ *" $dir "*) ;;
+ *" $absdir "*) ;;
+ *) temp_rpath="$temp_rpath $dir" ;;
+ esac
+ fi
+ fi
+ fi # $linkmode,$pass = prog,link...
+
+ if test "$alldeplibs" = yes &&
+ { test "$deplibs_check_method" = pass_all ||
+ { test "$build_libtool_libs" = yes &&
+ test -n "$library_names"; }; }; then
+ # We only need to search for static libraries
+ continue
+ fi
+
+ if test "$installed" = no; then
+ notinst_deplibs="$notinst_deplibs $lib"
+ need_relink=yes
+ fi
+
+ if test -n "$old_archive_from_expsyms_cmds"; then
+ # figure out the soname
+ set dummy $library_names
+ realname="$2"
+ shift; shift
+ libname=`eval \\$echo \"$libname_spec\"`
+ # use dlname if we got it. it's perfectly good, no?
+ if test -n "$dlname"; then
+ soname="$dlname"
+ elif test -n "$soname_spec"; then
+ # bleh windows
+ case $host in
+ *cygwin*)
+ major=`expr $current - $age`
+ versuffix="-$major"
+ ;;
+ esac
+ eval soname=\"$soname_spec\"
+ else
+ soname="$realname"
+ fi
+
+ # Make a new name for the extract_expsyms_cmds to use
+ soroot="$soname"
+ soname=`echo $soroot | sed -e 's/^.*\///'`
+ newlib="libimp-`echo $soname | sed 's/^lib//;s/\.dll$//'`.a"
+
+ # If the library has no export list, then create one now
+ if test -f "$output_objdir/$soname-def"; then :
+ else
+ $show "extracting exported symbol list from \`$soname'"
+ save_ifs="$IFS"; IFS='~'
+ eval cmds=\"$extract_expsyms_cmds\"
+ for cmd in $cmds; do
+ IFS="$save_ifs"
+ $show "$cmd"
+ $run eval "$cmd" || exit $?
+ done
+ IFS="$save_ifs"
+ fi
+
+ # Create $newlib
+ if test -f "$output_objdir/$newlib"; then :; else
+ $show "generating import library for \`$soname'"
+ save_ifs="$IFS"; IFS='~'
+ eval cmds=\"$old_archive_from_expsyms_cmds\"
+ for cmd in $cmds; do
+ IFS="$save_ifs"
+ $show "$cmd"
+ $run eval "$cmd" || exit $?
+ done
+ IFS="$save_ifs"
+ fi
+ # make sure the library variables are pointing to the new library
+ dir=$output_objdir
+ linklib=$newlib
+ fi # test -n $old_archive_from_expsyms_cmds
+
+ if test $linkmode = prog || test "$mode" != relink; then
+ add_shlibpath=
+ add_dir=
+ add=
+ lib_linked=yes
+ case $hardcode_action in
+ immediate | unsupported)
+ if test "$hardcode_direct" = no; then
+ add="$dir/$linklib"
+ elif test "$hardcode_minus_L" = no; then
+ case $host in
+ *-*-sunos*) add_shlibpath="$dir" ;;
+ esac
+ add_dir="-L$dir"
+ add="-l$name"
+ elif test "$hardcode_shlibpath_var" = no; then
+ add_shlibpath="$dir"
+ add="-l$name"
+ else
+ lib_linked=no
+ fi
+ ;;
+ relink)
+ if test "$hardcode_direct" = yes; then
+ add="$dir/$linklib"
+ elif test "$hardcode_minus_L" = yes; then
+ add_dir="-L$dir"
+ add="-l$name"
+ elif test "$hardcode_shlibpath_var" = yes; then
+ add_shlibpath="$dir"
+ add="-l$name"
+ else
+ lib_linked=no
+ fi
+ ;;
+ *) lib_linked=no ;;
+ esac
+
+ if test "$lib_linked" != yes; then
+ $echo "$modename: configuration error: unsupported hardcode properties"
+ exit 1
+ fi
+
+ if test -n "$add_shlibpath"; then
+ case :$compile_shlibpath: in
+ *":$add_shlibpath:"*) ;;
+ *) compile_shlibpath="$compile_shlibpath$add_shlibpath:" ;;
+ esac
+ fi
+ if test $linkmode = prog; then
+ test -n "$add_dir" && compile_deplibs="$add_dir $compile_deplibs"
+ test -n "$add" && compile_deplibs="$add $compile_deplibs"
+ else
+ test -n "$add_dir" && deplibs="$add_dir $deplibs"
+ test -n "$add" && deplibs="$add $deplibs"
+ if test "$hardcode_direct" != yes && \
+ test "$hardcode_minus_L" != yes && \
+ test "$hardcode_shlibpath_var" = yes; then
+ case :$finalize_shlibpath: in
+ *":$libdir:"*) ;;
+ *) finalize_shlibpath="$finalize_shlibpath$libdir:" ;;
+ esac
+ fi
+ fi
+ fi
+
+ if test $linkmode = prog || test "$mode" = relink; then
+ add_shlibpath=
+ add_dir=
+ add=
+ # Finalize command for both is simple: just hardcode it.
+ if test "$hardcode_direct" = yes; then
+ add="$libdir/$linklib"
+ elif test "$hardcode_minus_L" = yes; then
+ add_dir="-L$libdir"
+ add="-l$name"
+ elif test "$hardcode_shlibpath_var" = yes; then
+ case :$finalize_shlibpath: in
+ *":$libdir:"*) ;;
+ *) finalize_shlibpath="$finalize_shlibpath$libdir:" ;;
+ esac
+ add="-l$name"
+ else
+ # We cannot seem to hardcode it, guess we'll fake it.
+ add_dir="-L$libdir"
+ add="-l$name"
+ fi
+
+ if test $linkmode = prog; then
+ test -n "$add_dir" && finalize_deplibs="$add_dir $finalize_deplibs"
+ test -n "$add" && finalize_deplibs="$add $finalize_deplibs"
+ else
+ test -n "$add_dir" && deplibs="$add_dir $deplibs"
+ test -n "$add" && deplibs="$add $deplibs"
+ fi
+ fi
+ elif test $linkmode = prog; then
+ if test "$alldeplibs" = yes &&
+ { test "$deplibs_check_method" = pass_all ||
+ { test "$build_libtool_libs" = yes &&
+ test -n "$library_names"; }; }; then
+ # We only need to search for static libraries
+ continue
+ fi
+
+ # Try to link the static library
+ # Here we assume that one of hardcode_direct or hardcode_minus_L
+ # is not unsupported. This is valid on all known static and
+ # shared platforms.
+ if test "$hardcode_direct" != unsupported; then
+ test -n "$old_library" && linklib="$old_library"
+ compile_deplibs="$dir/$linklib $compile_deplibs"
+ finalize_deplibs="$dir/$linklib $finalize_deplibs"
+ else
+ compile_deplibs="-l$name -L$dir $compile_deplibs"
+ finalize_deplibs="-l$name -L$dir $finalize_deplibs"
+ fi
+ elif test "$build_libtool_libs" = yes; then
+ # Not a shared library
+ if test "$deplibs_check_method" != pass_all; then
+ # We're trying link a shared library against a static one
+ # but the system doesn't support it.
+
+ # Just print a warning and add the library to dependency_libs so
+ # that the program can be linked against the static library.
+ echo
+ echo "*** Warning: This library needs some functionality provided by $lib."
+ echo "*** I have the capability to make that library automatically link in when"
+ echo "*** you link to this library. But I can only do this if you have a"
+ echo "*** shared version of the library, which you do not appear to have."
+ if test "$module" = yes; then
+ echo "*** Therefore, libtool will create a static module, that should work "
+ echo "*** as long as the dlopening application is linked with the -dlopen flag."
+ if test -z "$global_symbol_pipe"; then
+ echo
+ echo "*** However, this would only work if libtool was able to extract symbol"
+ echo "*** lists from a program, using \`nm' or equivalent, but libtool could"
+ echo "*** not find such a program. So, this module is probably useless."
+ echo "*** \`nm' from GNU binutils and a full rebuild may help."
+ fi
+ if test "$build_old_libs" = no; then
+ build_libtool_libs=module
+ build_old_libs=yes
+ else
+ build_libtool_libs=no
+ fi
+ fi
+ else
+ convenience="$convenience $dir/$old_library"
+ old_convenience="$old_convenience $dir/$old_library"
+ deplibs="$dir/$old_library $deplibs"
+ link_static=yes
+ fi
+ fi # link shared/static library?
+
+ if test $linkmode = lib; then
+ if test -n "$dependency_libs" &&
+ { test $hardcode_into_libs != yes || test $build_old_libs = yes ||
+ test $link_static = yes; }; then
+ # Extract -R from dependency_libs
+ temp_deplibs=
+ for libdir in $dependency_libs; do
+ case $libdir in
+ -R*) temp_xrpath=`$echo "X$libdir" | $Xsed -e 's/^-R//'`
+ case " $xrpath " in
+ *" $temp_xrpath "*) ;;
+ *) xrpath="$xrpath $temp_xrpath";;
+ esac;;
+ *) temp_deplibs="$temp_deplibs $libdir";;
+ esac
+ done
+ dependency_libs="$temp_deplibs"
+ fi
+
+ newlib_search_path="$newlib_search_path $absdir"
+ # Link against this library
+ test "$link_static" = no && newdependency_libs="$abs_ladir/$laname $newdependency_libs"
+ # ... and its dependency_libs
+ tmp_libs=
+ for deplib in $dependency_libs; do
+ newdependency_libs="$deplib $newdependency_libs"
+ case "$tmp_libs " in
+ *" $deplib "*) specialdeplibs="$specialdeplibs $deplib" ;;
+ esac
+ tmp_libs="$tmp_libs $deplib"
+ done
+
+ if test $link_all_deplibs != no; then
+ # Add the search paths of all dependency libraries
+ for deplib in $dependency_libs; do
+ case $deplib in
+ -L*) path="$deplib" ;;
+ *.la)
+ dir=`$echo "X$deplib" | $Xsed -e 's%/[^/]*$%%'`
+ test "X$dir" = "X$deplib" && dir="."
+ # We need an absolute path.
+ case $dir in
+ [\\/]* | [A-Za-z]:[\\/]*) absdir="$dir" ;;
+ *)
+ absdir=`cd "$dir" && pwd`
+ if test -z "$absdir"; then
+ $echo "$modename: warning: cannot determine absolute directory name of \`$dir'" 1>&2
+ absdir="$dir"
+ fi
+ ;;
+ esac
+ if grep "^installed=no" $deplib > /dev/null; then
+ path="-L$absdir/$objdir"
+ else
+ eval libdir=`sed -n -e 's/^libdir=\(.*\)$/\1/p' $deplib`
+ if test -z "$libdir"; then
+ $echo "$modename: \`$deplib' is not a valid libtool archive" 1>&2
+ exit 1
+ fi
+ if test "$absdir" != "$libdir"; then
+ $echo "$modename: warning: \`$deplib' seems to be moved" 1>&2
+ fi
+ path="-L$absdir"
+ fi
+ ;;
+ *) continue ;;
+ esac
+ case " $deplibs " in
+ *" $path "*) ;;
+ *) deplibs="$deplibs $path" ;;
+ esac
+ done
+ fi # link_all_deplibs != no
+ fi # linkmode = lib
+ done # for deplib in $libs
+ if test $pass = dlpreopen; then
+ # Link the dlpreopened libraries before other libraries
+ for deplib in $save_deplibs; do
+ deplibs="$deplib $deplibs"
+ done
+ fi
+ if test $pass != dlopen; then
+ test $pass != scan && dependency_libs="$newdependency_libs"
+ if test $pass != conv; then
+ # Make sure lib_search_path contains only unique directories.
+ lib_search_path=
+ for dir in $newlib_search_path; do
+ case "$lib_search_path " in
+ *" $dir "*) ;;
+ *) lib_search_path="$lib_search_path $dir" ;;
+ esac
+ done
+ newlib_search_path=
+ fi
+
+ if test "$linkmode,$pass" != "prog,link"; then
+ vars="deplibs"
+ else
+ vars="compile_deplibs finalize_deplibs"
+ fi
+ for var in $vars dependency_libs; do
+ # Add libraries to $var in reverse order
+ eval tmp_libs=\"\$$var\"
+ new_libs=
+ for deplib in $tmp_libs; do
+ case $deplib in
+ -L*) new_libs="$deplib $new_libs" ;;
+ *)
+ case " $specialdeplibs " in
+ *" $deplib "*) new_libs="$deplib $new_libs" ;;
+ *)
+ case " $new_libs " in
+ *" $deplib "*) ;;
+ *) new_libs="$deplib $new_libs" ;;
+ esac
+ ;;
+ esac
+ ;;
+ esac
+ done
+ tmp_libs=
+ for deplib in $new_libs; do
+ case $deplib in
+ -L*)
+ case " $tmp_libs " in
+ *" $deplib "*) ;;
+ *) tmp_libs="$tmp_libs $deplib" ;;
+ esac
+ ;;
+ *) tmp_libs="$tmp_libs $deplib" ;;
+ esac
+ done
+ eval $var=\"$tmp_libs\"
+ done # for var
+ fi
+ if test "$pass" = "conv" &&
+ { test "$linkmode" = "lib" || test "$linkmode" = "prog"; }; then
+ libs="$deplibs" # reset libs
+ deplibs=
+ fi
+ done # for pass
+ if test $linkmode = prog; then
+ dlfiles="$newdlfiles"
+ dlprefiles="$newdlprefiles"
+ fi
+
+ case $linkmode in
+ oldlib)
+ if test -n "$dlfiles$dlprefiles" || test "$dlself" != no; then
+ $echo "$modename: warning: \`-dlopen' is ignored for archives" 1>&2
+ fi
+
+ if test -n "$rpath"; then
+ $echo "$modename: warning: \`-rpath' is ignored for archives" 1>&2
+ fi
+
+ if test -n "$xrpath"; then
+ $echo "$modename: warning: \`-R' is ignored for archives" 1>&2
+ fi
+
+ if test -n "$vinfo"; then
+ $echo "$modename: warning: \`-version-info' is ignored for archives" 1>&2
+ fi
+
+ if test -n "$release"; then
+ $echo "$modename: warning: \`-release' is ignored for archives" 1>&2
+ fi
+
+ if test -n "$export_symbols" || test -n "$export_symbols_regex"; then
+ $echo "$modename: warning: \`-export-symbols' is ignored for archives" 1>&2
+ fi
+
+ # Now set the variables for building old libraries.
+ build_libtool_libs=no
+ oldlibs="$output"
+ objs="$objs$old_deplibs"
+ ;;
+
+ lib)
+ # Make sure we only generate libraries of the form `libNAME.la'.
+ case $outputname in
+ lib*)
+ name=`$echo "X$outputname" | $Xsed -e 's/\.la$//' -e 's/^lib//'`
+ eval libname=\"$libname_spec\"
+ ;;
+ *)
+ if test "$module" = no; then
+ $echo "$modename: libtool library \`$output' must begin with \`lib'" 1>&2
+ $echo "$help" 1>&2
+ exit 1
+ fi
+ if test "$need_lib_prefix" != no; then
+ # Add the "lib" prefix for modules if required
+ name=`$echo "X$outputname" | $Xsed -e 's/\.la$//'`
+ eval libname=\"$libname_spec\"
+ else
+ libname=`$echo "X$outputname" | $Xsed -e 's/\.la$//'`
+ fi
+ ;;
+ esac
+
+ if test -n "$objs"; then
+ if test "$deplibs_check_method" != pass_all; then
+ $echo "$modename: cannot build libtool library \`$output' from non-libtool objects on this host:$objs" 2>&1
+ exit 1
+ else
+ echo
+ echo "*** Warning: Linking the shared library $output against the non-libtool"
+ echo "*** objects $objs is not portable!"
+ libobjs="$libobjs $objs"
+ fi
+ fi
+
+ if test "$dlself" != no; then
+ $echo "$modename: warning: \`-dlopen self' is ignored for libtool libraries" 1>&2
+ fi
+
+ set dummy $rpath
+ if test $# -gt 2; then
+ $echo "$modename: warning: ignoring multiple \`-rpath's for a libtool library" 1>&2
+ fi
+ install_libdir="$2"
+
+ oldlibs=
+ if test -z "$rpath"; then
+ if test "$build_libtool_libs" = yes; then
+ # Building a libtool convenience library.
+ libext=al
+ oldlibs="$output_objdir/$libname.$libext $oldlibs"
+ build_libtool_libs=convenience
+ build_old_libs=yes
+ fi
+
+ if test -n "$vinfo"; then
+ $echo "$modename: warning: \`-version-info' is ignored for convenience libraries" 1>&2
+ fi
+
+ if test -n "$release"; then
+ $echo "$modename: warning: \`-release' is ignored for convenience libraries" 1>&2
+ fi
+ else
+
+ # Parse the version information argument.
+ save_ifs="$IFS"; IFS=':'
+ set dummy $vinfo 0 0 0
+ IFS="$save_ifs"
+
+ if test -n "$8"; then
+ $echo "$modename: too many parameters to \`-version-info'" 1>&2
+ $echo "$help" 1>&2
+ exit 1
+ fi
+
+ current="$2"
+ revision="$3"
+ age="$4"
+
+ # Check that each of the things are valid numbers.
+ case $current in
+ 0 | [1-9] | [1-9][0-9] | [1-9][0-9][0-9]) ;;
+ *)
+ $echo "$modename: CURRENT \`$current' is not a nonnegative integer" 1>&2
+ $echo "$modename: \`$vinfo' is not valid version information" 1>&2
+ exit 1
+ ;;
+ esac
+
+ case $revision in
+ 0 | [1-9] | [1-9][0-9] | [1-9][0-9][0-9]) ;;
+ *)
+ $echo "$modename: REVISION \`$revision' is not a nonnegative integer" 1>&2
+ $echo "$modename: \`$vinfo' is not valid version information" 1>&2
+ exit 1
+ ;;
+ esac
+
+ case $age in
+ 0 | [1-9] | [1-9][0-9] | [1-9][0-9][0-9]) ;;
+ *)
+ $echo "$modename: AGE \`$age' is not a nonnegative integer" 1>&2
+ $echo "$modename: \`$vinfo' is not valid version information" 1>&2
+ exit 1
+ ;;
+ esac
+
+ if test $age -gt $current; then
+ $echo "$modename: AGE \`$age' is greater than the current interface number \`$current'" 1>&2
+ $echo "$modename: \`$vinfo' is not valid version information" 1>&2
+ exit 1
+ fi
+
+ # Calculate the version variables.
+ major=
+ versuffix=
+ verstring=
+ case $version_type in
+ none) ;;
+
+ darwin)
+ # Like Linux, but with the current version available in
+ # verstring for coding it into the library header
+ major=.`expr $current - $age`
+ versuffix="$major.$age.$revision"
+ # Darwin ld doesn't like 0 for these options...
+ minor_current=`expr $current + 1`
+ verstring="-compatibility_version $minor_current -current_version $minor_current.$revision"
+ ;;
+
+ freebsd-aout)
+ major=".$current"
+ versuffix=".$current.$revision";
+ ;;
+
+ freebsd-elf)
+ major=".$current"
+ versuffix=".$current";
+ ;;
+
+ irix)
+ major=`expr $current - $age + 1`
+ verstring="sgi$major.$revision"
+
+ # Add in all the interfaces that we are compatible with.
+ loop=$revision
+ while test $loop != 0; do
+ iface=`expr $revision - $loop`
+ loop=`expr $loop - 1`
+ verstring="sgi$major.$iface:$verstring"
+ done
+
+ # Before this point, $major must not contain `.'.
+ major=.$major
+ versuffix="$major.$revision"
+ ;;
+
+ linux)
+ major=.`expr $current - $age`
+ versuffix="$major.$age.$revision"
+ ;;
+
+ osf)
+ major=`expr $current - $age`
+ versuffix=".$current.$age.$revision"
+ verstring="$current.$age.$revision"
+
+ # Add in all the interfaces that we are compatible with.
+ loop=$age
+ while test $loop != 0; do
+ iface=`expr $current - $loop`
+ loop=`expr $loop - 1`
+ verstring="$verstring:${iface}.0"
+ done
+
+ # Make executables depend on our current version.
+ verstring="$verstring:${current}.0"
+ ;;
+
+ sunos)
+ major=".$current"
+ versuffix=".$current.$revision"
+ ;;
+
+ windows)
+ # Use '-' rather than '.', since we only want one
+ # extension on DOS 8.3 filesystems.
+ major=`expr $current - $age`
+ versuffix="-$major"
+ ;;
+
+ *)
+ $echo "$modename: unknown library version type \`$version_type'" 1>&2
+ echo "Fatal configuration error. See the $PACKAGE docs for more information." 1>&2
+ exit 1
+ ;;
+ esac
+
+ # Clear the version info if we defaulted, and they specified a release.
+ if test -z "$vinfo" && test -n "$release"; then
+ major=
+ verstring="0.0"
+ case $version_type in
+ darwin)
+ # we can't check for "0.0" in archive_cmds due to quoting
+ # problems, so we reset it completely
+ verstring=""
+ ;;
+ *)
+ verstring="0.0"
+ ;;
+ esac
+ if test "$need_version" = no; then
+ versuffix=
+ else
+ versuffix=".0.0"
+ fi
+ fi
+
+ # Remove version info from name if versioning should be avoided
+ if test "$avoid_version" = yes && test "$need_version" = no; then
+ major=
+ versuffix=
+ verstring=""
+ fi
+
+ # Check to see if the archive will have undefined symbols.
+ if test "$allow_undefined" = yes; then
+ if test "$allow_undefined_flag" = unsupported; then
+ $echo "$modename: warning: undefined symbols not allowed in $host shared libraries" 1>&2
+ build_libtool_libs=no
+ build_old_libs=yes
+ fi
+ else
+ # Don't allow undefined symbols.
+ allow_undefined_flag="$no_undefined_flag"
+ fi
+ fi
+
+ if test "$mode" != relink; then
+ # Remove our outputs.
+ $show "${rm}r $output_objdir/$outputname $output_objdir/$libname.* $output_objdir/${libname}${release}.*"
+ $run ${rm}r $output_objdir/$outputname $output_objdir/$libname.* $output_objdir/${libname}${release}.*
+ fi
+
+ # Now set the variables for building old libraries.
+ if test "$build_old_libs" = yes && test "$build_libtool_libs" != convenience ; then
+ oldlibs="$oldlibs $output_objdir/$libname.$libext"
+
+ # Transform .lo files to .o files.
+ oldobjs="$objs "`$echo "X$libobjs" | $SP2NL | $Xsed -e '/\.'${libext}'$/d' -e "$lo2o" | $NL2SP`
+ fi
+
+ # Eliminate all temporary directories.
+ for path in $notinst_path; do
+ lib_search_path=`echo "$lib_search_path " | sed -e 's% $path % %g'`
+ deplibs=`echo "$deplibs " | sed -e 's% -L$path % %g'`
+ dependency_libs=`echo "$dependency_libs " | sed -e 's% -L$path % %g'`
+ done
+
+ if test -n "$xrpath"; then
+ # If the user specified any rpath flags, then add them.
+ temp_xrpath=
+ for libdir in $xrpath; do
+ temp_xrpath="$temp_xrpath -R$libdir"
+ case "$finalize_rpath " in
+ *" $libdir "*) ;;
+ *) finalize_rpath="$finalize_rpath $libdir" ;;
+ esac
+ done
+ if test $hardcode_into_libs != yes || test $build_old_libs = yes; then
+ dependency_libs="$temp_xrpath $dependency_libs"
+ fi
+ fi
+
+ # Make sure dlfiles contains only unique files that won't be dlpreopened
+ old_dlfiles="$dlfiles"
+ dlfiles=
+ for lib in $old_dlfiles; do
+ case " $dlprefiles $dlfiles " in
+ *" $lib "*) ;;
+ *) dlfiles="$dlfiles $lib" ;;
+ esac
+ done
+
+ # Make sure dlprefiles contains only unique files
+ old_dlprefiles="$dlprefiles"
+ dlprefiles=
+ for lib in $old_dlprefiles; do
+ case "$dlprefiles " in
+ *" $lib "*) ;;
+ *) dlprefiles="$dlprefiles $lib" ;;
+ esac
+ done
+
+ if test "$build_libtool_libs" = yes; then
+ if test -n "$rpath"; then
+ case $host in
+ *-*-cygwin* | *-*-mingw* | *-*-pw32* | *-*-os2* | *-*-beos*)
+ # these systems don't actually have a c library (as such)!
+ ;;
+ *-*-rhapsody* | *-*-darwin1.[012])
+ # Rhapsody C library is in the System framework
+ deplibs="$deplibs -framework System"
+ ;;
+ *-*-netbsd*)
+ # Don't link with libc until the a.out ld.so is fixed.
+ ;;
+ *-*-openbsd*)
+ # Do not include libc due to us having libc/libc_r.
+ ;;
+ *)
+ # Add libc to deplibs on all other systems if necessary.
+ if test $build_libtool_need_lc = "yes"; then
+ deplibs="$deplibs -lc"
+ fi
+ ;;
+ esac
+ fi
+
+ # Transform deplibs into only deplibs that can be linked in shared.
+ name_save=$name
+ libname_save=$libname
+ release_save=$release
+ versuffix_save=$versuffix
+ major_save=$major
+ # I'm not sure if I'm treating the release correctly. I think
+ # release should show up in the -l (ie -lgmp5) so we don't want to
+ # add it in twice. Is that correct?
+ release=""
+ versuffix=""
+ major=""
+ newdeplibs=
+ droppeddeps=no
+ case $deplibs_check_method in
+ pass_all)
+ # Don't check for shared/static. Everything works.
+ # This might be a little naive. We might want to check
+ # whether the library exists or not. But this is on
+ # osf3 & osf4 and I'm not really sure... Just
+ # implementing what was already the behaviour.
+ newdeplibs=$deplibs
+ ;;
+ test_compile)
+ # This code stresses the "libraries are programs" paradigm to its
+ # limits. Maybe even breaks it. We compile a program, linking it
+ # against the deplibs as a proxy for the library. Then we can check
+ # whether they linked in statically or dynamically with ldd.
+ $rm conftest.c
+ cat > conftest.c <<EOF
+ int main() { return 0; }
+EOF
+ $rm conftest
+ $CC -o conftest conftest.c $deplibs
+ if test $? -eq 0 ; then
+ ldd_output=`ldd conftest`
+ for i in $deplibs; do
+ name="`expr $i : '-l\(.*\)'`"
+ # If $name is empty we are operating on a -L argument.
+ if test -n "$name" && test "$name" != "0"; then
+ libname=`eval \\$echo \"$libname_spec\"`
+ deplib_matches=`eval \\$echo \"$library_names_spec\"`
+ set dummy $deplib_matches
+ deplib_match=$2
+ if test `expr "$ldd_output" : ".*$deplib_match"` -ne 0 ; then
+ newdeplibs="$newdeplibs $i"
+ else
+ droppeddeps=yes
+ echo
+ echo "*** Warning: This library needs some functionality provided by $i."
+ echo "*** I have the capability to make that library automatically link in when"
+ echo "*** you link to this library. But I can only do this if you have a"
+ echo "*** shared version of the library, which you do not appear to have."
+ fi
+ else
+ newdeplibs="$newdeplibs $i"
+ fi
+ done
+ else
+ # Error occured in the first compile. Let's try to salvage the situation:
+ # Compile a seperate program for each library.
+ for i in $deplibs; do
+ name="`expr $i : '-l\(.*\)'`"
+ # If $name is empty we are operating on a -L argument.
+ if test -n "$name" && test "$name" != "0"; then
+ $rm conftest
+ $CC -o conftest conftest.c $i
+ # Did it work?
+ if test $? -eq 0 ; then
+ ldd_output=`ldd conftest`
+ libname=`eval \\$echo \"$libname_spec\"`
+ deplib_matches=`eval \\$echo \"$library_names_spec\"`
+ set dummy $deplib_matches
+ deplib_match=$2
+ if test `expr "$ldd_output" : ".*$deplib_match"` -ne 0 ; then
+ newdeplibs="$newdeplibs $i"
+ else
+ droppeddeps=yes
+ echo
+ echo "*** Warning: This library needs some functionality provided by $i."
+ echo "*** I have the capability to make that library automatically link in when"
+ echo "*** you link to this library. But I can only do this if you have a"
+ echo "*** shared version of the library, which you do not appear to have."
+ fi
+ else
+ droppeddeps=yes
+ echo
+ echo "*** Warning! Library $i is needed by this library but I was not able to"
+ echo "*** make it link in! You will probably need to install it or some"
+ echo "*** library that it depends on before this library will be fully"
+ echo "*** functional. Installing it before continuing would be even better."
+ fi
+ else
+ newdeplibs="$newdeplibs $i"
+ fi
+ done
+ fi
+ ;;
+ file_magic*)
+ set dummy $deplibs_check_method
+ file_magic_regex=`expr "$deplibs_check_method" : "$2 \(.*\)"`
+ for a_deplib in $deplibs; do
+ name="`expr $a_deplib : '-l\(.*\)'`"
+ # If $name is empty we are operating on a -L argument.
+ if test -n "$name" && test "$name" != "0"; then
+ libname=`eval \\$echo \"$libname_spec\"`
+ for i in $lib_search_path $sys_lib_search_path $shlib_search_path; do
+ potential_libs=`ls $i/$libname[.-]* 2>/dev/null`
+ for potent_lib in $potential_libs; do
+ # Follow soft links.
+ if ls -lLd "$potent_lib" 2>/dev/null \
+ | grep " -> " >/dev/null; then
+ continue
+ fi
+ # The statement above tries to avoid entering an
+ # endless loop below, in case of cyclic links.
+ # We might still enter an endless loop, since a link
+ # loop can be closed while we follow links,
+ # but so what?
+ potlib="$potent_lib"
+ while test -h "$potlib" 2>/dev/null; do
+ potliblink=`ls -ld $potlib | sed 's/.* -> //'`
+ case $potliblink in
+ [\\/]* | [A-Za-z]:[\\/]*) potlib="$potliblink";;
+ *) potlib=`$echo "X$potlib" | $Xsed -e 's,[^/]*$,,'`"$potliblink";;
+ esac
+ done
+ if eval $file_magic_cmd \"\$potlib\" 2>/dev/null \
+ | sed 10q \
+ | egrep "$file_magic_regex" > /dev/null; then
+ newdeplibs="$newdeplibs $a_deplib"
+ a_deplib=""
+ break 2
+ fi
+ done
+ done
+ if test -n "$a_deplib" ; then
+ droppeddeps=yes
+ echo
+ echo "*** Warning: This library needs some functionality provided by $a_deplib."
+ echo "*** I have the capability to make that library automatically link in when"
+ echo "*** you link to this library. But I can only do this if you have a"
+ echo "*** shared version of the library, which you do not appear to have."
+ fi
+ else
+ # Add a -L argument.
+ newdeplibs="$newdeplibs $a_deplib"
+ fi
+ done # Gone through all deplibs.
+ ;;
+ match_pattern*)
+ set dummy $deplibs_check_method
+ match_pattern_regex=`expr "$deplibs_check_method" : "$2 \(.*\)"`
+ for a_deplib in $deplibs; do
+ name="`expr $a_deplib : '-l\(.*\)'`"
+ # If $name is empty we are operating on a -L argument.
+ if test -n "$name" && test "$name" != "0"; then
+ libname=`eval \\$echo \"$libname_spec\"`
+ for i in $lib_search_path $sys_lib_search_path $shlib_search_path; do
+ potential_libs=`ls $i/$libname[.-]* 2>/dev/null`
+ for potent_lib in $potential_libs; do
+ if eval echo \"$potent_lib\" 2>/dev/null \
+ | sed 10q \
+ | egrep "$match_pattern_regex" > /dev/null; then
+ newdeplibs="$newdeplibs $a_deplib"
+ a_deplib=""
+ break 2
+ fi
+ done
+ done
+ if test -n "$a_deplib" ; then
+ droppeddeps=yes
+ echo
+ echo "*** Warning: This library needs some functionality provided by $a_deplib."
+ echo "*** I have the capability to make that library automatically link in when"
+ echo "*** you link to this library. But I can only do this if you have a"
+ echo "*** shared version of the library, which you do not appear to have."
+ fi
+ else
+ # Add a -L argument.
+ newdeplibs="$newdeplibs $a_deplib"
+ fi
+ done # Gone through all deplibs.
+ ;;
+ none | unknown | *)
+ newdeplibs=""
+ if $echo "X $deplibs" | $Xsed -e 's/ -lc$//' \
+ -e 's/ -[LR][^ ]*//g' -e 's/[ ]//g' |
+ grep . >/dev/null; then
+ echo
+ if test "X$deplibs_check_method" = "Xnone"; then
+ echo "*** Warning: inter-library dependencies are not supported in this platform."
+ else
+ echo "*** Warning: inter-library dependencies are not known to be supported."
+ fi
+ echo "*** All declared inter-library dependencies are being dropped."
+ droppeddeps=yes
+ fi
+ ;;
+ esac
+ versuffix=$versuffix_save
+ major=$major_save
+ release=$release_save
+ libname=$libname_save
+ name=$name_save
+
+ case $host in
+ *-*-rhapsody* | *-*-darwin1.[012])
+ # On Rhapsody replace the C library is the System framework
+ newdeplibs=`$echo "X $newdeplibs" | $Xsed -e 's/ -lc / -framework System /'`
+ ;;
+ esac
+
+ if test "$droppeddeps" = yes; then
+ if test "$module" = yes; then
+ echo
+ echo "*** Warning: libtool could not satisfy all declared inter-library"
+ echo "*** dependencies of module $libname. Therefore, libtool will create"
+ echo "*** a static module, that should work as long as the dlopening"
+ echo "*** application is linked with the -dlopen flag."
+ if test -z "$global_symbol_pipe"; then
+ echo
+ echo "*** However, this would only work if libtool was able to extract symbol"
+ echo "*** lists from a program, using \`nm' or equivalent, but libtool could"
+ echo "*** not find such a program. So, this module is probably useless."
+ echo "*** \`nm' from GNU binutils and a full rebuild may help."
+ fi
+ if test "$build_old_libs" = no; then
+ oldlibs="$output_objdir/$libname.$libext"
+ build_libtool_libs=module
+ build_old_libs=yes
+ else
+ build_libtool_libs=no
+ fi
+ else
+ echo "*** The inter-library dependencies that have been dropped here will be"
+ echo "*** automatically added whenever a program is linked with this library"
+ echo "*** or is declared to -dlopen it."
+
+ if test $allow_undefined = no; then
+ echo
+ echo "*** Since this library must not contain undefined symbols,"
+ echo "*** because either the platform does not support them or"
+ echo "*** it was explicitly requested with -no-undefined,"
+ echo "*** libtool will only create a static version of it."
+ if test "$build_old_libs" = no; then
+ oldlibs="$output_objdir/$libname.$libext"
+ build_libtool_libs=module
+ build_old_libs=yes
+ else
+ build_libtool_libs=no
+ fi
+ fi
+ fi
+ fi
+ # Done checking deplibs!
+ deplibs=$newdeplibs
+ fi
+
+ # All the library-specific variables (install_libdir is set above).
+ library_names=
+ old_library=
+ dlname=
+
+ # Test again, we may have decided not to build it any more
+ if test "$build_libtool_libs" = yes; then
+ if test $hardcode_into_libs = yes; then
+ # Hardcode the library paths
+ hardcode_libdirs=
+ dep_rpath=
+ rpath="$finalize_rpath"
+ test "$mode" != relink && rpath="$compile_rpath$rpath"
+ for libdir in $rpath; do
+ if test -n "$hardcode_libdir_flag_spec"; then
+ if test -n "$hardcode_libdir_separator"; then
+ if test -z "$hardcode_libdirs"; then
+ hardcode_libdirs="$libdir"
+ else
+ # Just accumulate the unique libdirs.
+ case $hardcode_libdir_separator$hardcode_libdirs$hardcode_libdir_separator in
+ *"$hardcode_libdir_separator$libdir$hardcode_libdir_separator"*)
+ ;;
+ *)
+ hardcode_libdirs="$hardcode_libdirs$hardcode_libdir_separator$libdir"
+ ;;
+ esac
+ fi
+ else
+ eval flag=\"$hardcode_libdir_flag_spec\"
+ dep_rpath="$dep_rpath $flag"
+ fi
+ elif test -n "$runpath_var"; then
+ case "$perm_rpath " in
+ *" $libdir "*) ;;
+ *) perm_rpath="$perm_rpath $libdir" ;;
+ esac
+ fi
+ done
+ # Substitute the hardcoded libdirs into the rpath.
+ if test -n "$hardcode_libdir_separator" &&
+ test -n "$hardcode_libdirs"; then
+ libdir="$hardcode_libdirs"
+ eval dep_rpath=\"$hardcode_libdir_flag_spec\"
+ fi
+ if test -n "$runpath_var" && test -n "$perm_rpath"; then
+ # We should set the runpath_var.
+ rpath=
+ for dir in $perm_rpath; do
+ rpath="$rpath$dir:"
+ done
+ eval "$runpath_var='$rpath\$$runpath_var'; export $runpath_var"
+ fi
+ test -n "$dep_rpath" && deplibs="$dep_rpath $deplibs"
+ fi
+
+ shlibpath="$finalize_shlibpath"
+ test "$mode" != relink && shlibpath="$compile_shlibpath$shlibpath"
+ if test -n "$shlibpath"; then
+ eval "$shlibpath_var='$shlibpath\$$shlibpath_var'; export $shlibpath_var"
+ fi
+
+ # Get the real and link names of the library.
+ eval library_names=\"$library_names_spec\"
+ set dummy $library_names
+ realname="$2"
+ shift; shift
+
+ if test -n "$soname_spec"; then
+ eval soname=\"$soname_spec\"
+ else
+ soname="$realname"
+ fi
+ test -z "$dlname" && dlname=$soname
+
+ lib="$output_objdir/$realname"
+ for link
+ do
+ linknames="$linknames $link"
+ done
+
+ # Ensure that we have .o objects for linkers which dislike .lo
+ # (e.g. aix) in case we are running --disable-static
+ for obj in $libobjs; do
+ xdir=`$echo "X$obj" | $Xsed -e 's%/[^/]*$%%'`
+ if test "X$xdir" = "X$obj"; then
+ xdir="."
+ else
+ xdir="$xdir"
+ fi
+ baseobj=`$echo "X$obj" | $Xsed -e 's%^.*/%%'`
+ oldobj=`$echo "X$baseobj" | $Xsed -e "$lo2o"`
+ if test ! -f $xdir/$oldobj; then
+ $show "(cd $xdir && ${LN_S} $baseobj $oldobj)"
+ $run eval '(cd $xdir && ${LN_S} $baseobj $oldobj)' || exit $?
+ fi
+ done
+
+ # Use standard objects if they are pic
+ test -z "$pic_flag" && libobjs=`$echo "X$libobjs" | $SP2NL | $Xsed -e "$lo2o" | $NL2SP`
+
+ # Prepare the list of exported symbols
+ if test -z "$export_symbols"; then
+ if test "$always_export_symbols" = yes || test -n "$export_symbols_regex"; then
+ $show "generating symbol list for \`$libname.la'"
+ export_symbols="$output_objdir/$libname.exp"
+ $run $rm $export_symbols
+ eval cmds=\"$export_symbols_cmds\"
+ save_ifs="$IFS"; IFS='~'
+ for cmd in $cmds; do
+ IFS="$save_ifs"
+ $show "$cmd"
+ $run eval "$cmd" || exit $?
+ done
+ IFS="$save_ifs"
+ if test -n "$export_symbols_regex"; then
+ $show "egrep -e \"$export_symbols_regex\" \"$export_symbols\" > \"${export_symbols}T\""
+ $run eval 'egrep -e "$export_symbols_regex" "$export_symbols" > "${export_symbols}T"'
+ $show "$mv \"${export_symbols}T\" \"$export_symbols\""
+ $run eval '$mv "${export_symbols}T" "$export_symbols"'
+ fi
+ fi
+ fi
+
+ if test -n "$export_symbols" && test -n "$include_expsyms"; then
+ $run eval '$echo "X$include_expsyms" | $SP2NL >> "$export_symbols"'
+ fi
+
+ if test -n "$convenience"; then
+ if test -n "$whole_archive_flag_spec"; then
+ eval libobjs=\"\$libobjs $whole_archive_flag_spec\"
+ else
+ gentop="$output_objdir/${outputname}x"
+ $show "${rm}r $gentop"
+ $run ${rm}r "$gentop"
+ $show "mkdir $gentop"
+ $run mkdir "$gentop"
+ status=$?
+ if test $status -ne 0 && test ! -d "$gentop"; then
+ exit $status
+ fi
+ generated="$generated $gentop"
+
+ for xlib in $convenience; do
+ # Extract the objects.
+ case $xlib in
+ [\\/]* | [A-Za-z]:[\\/]*) xabs="$xlib" ;;
+ *) xabs=`pwd`"/$xlib" ;;
+ esac
+ xlib=`$echo "X$xlib" | $Xsed -e 's%^.*/%%'`
+ xdir="$gentop/$xlib"
+
+ $show "${rm}r $xdir"
+ $run ${rm}r "$xdir"
+ $show "mkdir $xdir"
+ $run mkdir "$xdir"
+ status=$?
+ if test $status -ne 0 && test ! -d "$xdir"; then
+ exit $status
+ fi
+ $show "(cd $xdir && $AR x $xabs)"
+ $run eval "(cd \$xdir && $AR x \$xabs)" || exit $?
+
+ libobjs="$libobjs "`find $xdir -name \*.o -print -o -name \*.lo -print | $NL2SP`
+ done
+ fi
+ fi
+
+ if test "$thread_safe" = yes && test -n "$thread_safe_flag_spec"; then
+ eval flag=\"$thread_safe_flag_spec\"
+ linker_flags="$linker_flags $flag"
+ fi
+
+ # Make a backup of the uninstalled library when relinking
+ if test "$mode" = relink; then
+ $run eval '(cd $output_objdir && $rm ${realname}U && $mv $realname ${realname}U)' || exit $?
+ fi
+
+ # Do each of the archive commands.
+ if test -n "$export_symbols" && test -n "$archive_expsym_cmds"; then
+ eval cmds=\"$archive_expsym_cmds\"
+ else
+ eval cmds=\"$archive_cmds\"
+ fi
+ save_ifs="$IFS"; IFS='~'
+ for cmd in $cmds; do
+ IFS="$save_ifs"
+ $show "$cmd"
+ $run eval "$cmd" || exit $?
+ done
+ IFS="$save_ifs"
+
+ # Restore the uninstalled library and exit
+ if test "$mode" = relink; then
+ $run eval '(cd $output_objdir && $rm ${realname}T && $mv $realname ${realname}T && $mv "$realname"U $realname)' || exit $?
+ exit 0
+ fi
+
+ # Create links to the real library.
+ for linkname in $linknames; do
+ if test "$realname" != "$linkname"; then
+ $show "(cd $output_objdir && $rm $linkname && $LN_S $realname $linkname)"
+ $run eval '(cd $output_objdir && $rm $linkname && $LN_S $realname $linkname)' || exit $?
+ fi
+ done
+
+ # If -module or -export-dynamic was specified, set the dlname.
+ if test "$module" = yes || test "$export_dynamic" = yes; then
+ # On all known operating systems, these are identical.
+ dlname="$soname"
+ fi
+ fi
+ ;;
+
+ obj)
+ if test -n "$deplibs"; then
+ $echo "$modename: warning: \`-l' and \`-L' are ignored for objects" 1>&2
+ fi
+
+ if test -n "$dlfiles$dlprefiles" || test "$dlself" != no; then
+ $echo "$modename: warning: \`-dlopen' is ignored for objects" 1>&2
+ fi
+
+ if test -n "$rpath"; then
+ $echo "$modename: warning: \`-rpath' is ignored for objects" 1>&2
+ fi
+
+ if test -n "$xrpath"; then
+ $echo "$modename: warning: \`-R' is ignored for objects" 1>&2
+ fi
+
+ if test -n "$vinfo"; then
+ $echo "$modename: warning: \`-version-info' is ignored for objects" 1>&2
+ fi
+
+ if test -n "$release"; then
+ $echo "$modename: warning: \`-release' is ignored for objects" 1>&2
+ fi
+
+ case $output in
+ *.lo)
+ if test -n "$objs$old_deplibs"; then
+ $echo "$modename: cannot build library object \`$output' from non-libtool objects" 1>&2
+ exit 1
+ fi
+ libobj="$output"
+ obj=`$echo "X$output" | $Xsed -e "$lo2o"`
+ ;;
+ *)
+ libobj=
+ obj="$output"
+ ;;
+ esac
+
+ # Delete the old objects.
+ $run $rm $obj $libobj
+
+ # Objects from convenience libraries. This assumes
+ # single-version convenience libraries. Whenever we create
+ # different ones for PIC/non-PIC, this we'll have to duplicate
+ # the extraction.
+ reload_conv_objs=
+ gentop=
+ # reload_cmds runs $LD directly, so let us get rid of
+ # -Wl from whole_archive_flag_spec
+ wl=
+
+ if test -n "$convenience"; then
+ if test -n "$whole_archive_flag_spec"; then
+ eval reload_conv_objs=\"\$reload_objs $whole_archive_flag_spec\"
+ else
+ gentop="$output_objdir/${obj}x"
+ $show "${rm}r $gentop"
+ $run ${rm}r "$gentop"
+ $show "mkdir $gentop"
+ $run mkdir "$gentop"
+ status=$?
+ if test $status -ne 0 && test ! -d "$gentop"; then
+ exit $status
+ fi
+ generated="$generated $gentop"
+
+ for xlib in $convenience; do
+ # Extract the objects.
+ case $xlib in
+ [\\/]* | [A-Za-z]:[\\/]*) xabs="$xlib" ;;
+ *) xabs=`pwd`"/$xlib" ;;
+ esac
+ xlib=`$echo "X$xlib" | $Xsed -e 's%^.*/%%'`
+ xdir="$gentop/$xlib"
+
+ $show "${rm}r $xdir"
+ $run ${rm}r "$xdir"
+ $show "mkdir $xdir"
+ $run mkdir "$xdir"
+ status=$?
+ if test $status -ne 0 && test ! -d "$xdir"; then
+ exit $status
+ fi
+ $show "(cd $xdir && $AR x $xabs)"
+ $run eval "(cd \$xdir && $AR x \$xabs)" || exit $?
+
+ reload_conv_objs="$reload_objs "`find $xdir -name \*.o -print -o -name \*.lo -print | $NL2SP`
+ done
+ fi
+ fi
+
+ # Create the old-style object.
+ reload_objs="$objs$old_deplibs "`$echo "X$libobjs" | $SP2NL | $Xsed -e '/\.'${libext}$'/d' -e '/\.lib$/d' -e "$lo2o" | $NL2SP`" $reload_conv_objs" ### testsuite: skip nested quoting test
+
+ output="$obj"
+ eval cmds=\"$reload_cmds\"
+ save_ifs="$IFS"; IFS='~'
+ for cmd in $cmds; do
+ IFS="$save_ifs"
+ $show "$cmd"
+ $run eval "$cmd" || exit $?
+ done
+ IFS="$save_ifs"
+
+ # Exit if we aren't doing a library object file.
+ if test -z "$libobj"; then
+ if test -n "$gentop"; then
+ $show "${rm}r $gentop"
+ $run ${rm}r $gentop
+ fi
+
+ exit 0
+ fi
+
+ if test "$build_libtool_libs" != yes; then
+ if test -n "$gentop"; then
+ $show "${rm}r $gentop"
+ $run ${rm}r $gentop
+ fi
+
+ # Create an invalid libtool object if no PIC, so that we don't
+ # accidentally link it into a program.
+ $show "echo timestamp > $libobj"
+ $run eval "echo timestamp > $libobj" || exit $?
+ exit 0
+ fi
+
+ if test -n "$pic_flag" || test "$pic_mode" != default; then
+ # Only do commands if we really have different PIC objects.
+ reload_objs="$libobjs $reload_conv_objs"
+ output="$libobj"
+ eval cmds=\"$reload_cmds\"
+ save_ifs="$IFS"; IFS='~'
+ for cmd in $cmds; do
+ IFS="$save_ifs"
+ $show "$cmd"
+ $run eval "$cmd" || exit $?
+ done
+ IFS="$save_ifs"
+ else
+ # Just create a symlink.
+ $show $rm $libobj
+ $run $rm $libobj
+ xdir=`$echo "X$libobj" | $Xsed -e 's%/[^/]*$%%'`
+ if test "X$xdir" = "X$libobj"; then
+ xdir="."
+ else
+ xdir="$xdir"
+ fi
+ baseobj=`$echo "X$libobj" | $Xsed -e 's%^.*/%%'`
+ oldobj=`$echo "X$baseobj" | $Xsed -e "$lo2o"`
+ $show "(cd $xdir && $LN_S $oldobj $baseobj)"
+ $run eval '(cd $xdir && $LN_S $oldobj $baseobj)' || exit $?
+ fi
+
+ if test -n "$gentop"; then
+ $show "${rm}r $gentop"
+ $run ${rm}r $gentop
+ fi
+
+ exit 0
+ ;;
+
+ prog)
+ case $host in
+ *cygwin*) output=`echo $output | sed -e 's,.exe$,,;s,$,.exe,'` ;;
+ esac
+ if test -n "$vinfo"; then
+ $echo "$modename: warning: \`-version-info' is ignored for programs" 1>&2
+ fi
+
+ if test -n "$release"; then
+ $echo "$modename: warning: \`-release' is ignored for programs" 1>&2
+ fi
+
+ if test "$preload" = yes; then
+ if test "$dlopen_support" = unknown && test "$dlopen_self" = unknown &&
+ test "$dlopen_self_static" = unknown; then
+ $echo "$modename: warning: \`AC_LIBTOOL_DLOPEN' not used. Assuming no dlopen support."
+ fi
+ fi
+
+ case $host in
+ *-*-rhapsody* | *-*-darwin1.[012])
+ # On Rhapsody replace the C library is the System framework
+ compile_deplibs=`$echo "X $compile_deplibs" | $Xsed -e 's/ -lc / -framework System /'`
+ finalize_deplibs=`$echo "X $finalize_deplibs" | $Xsed -e 's/ -lc / -framework System /'`
+ ;;
+ esac
+
+ compile_command="$compile_command $compile_deplibs"
+ finalize_command="$finalize_command $finalize_deplibs"
+
+ if test -n "$rpath$xrpath"; then
+ # If the user specified any rpath flags, then add them.
+ for libdir in $rpath $xrpath; do
+ # This is the magic to use -rpath.
+ case "$finalize_rpath " in
+ *" $libdir "*) ;;
+ *) finalize_rpath="$finalize_rpath $libdir" ;;
+ esac
+ done
+ fi
+
+ # Now hardcode the library paths
+ rpath=
+ hardcode_libdirs=
+ for libdir in $compile_rpath $finalize_rpath; do
+ if test -n "$hardcode_libdir_flag_spec"; then
+ if test -n "$hardcode_libdir_separator"; then
+ if test -z "$hardcode_libdirs"; then
+ hardcode_libdirs="$libdir"
+ else
+ # Just accumulate the unique libdirs.
+ case $hardcode_libdir_separator$hardcode_libdirs$hardcode_libdir_separator in
+ *"$hardcode_libdir_separator$libdir$hardcode_libdir_separator"*)
+ ;;
+ *)
+ hardcode_libdirs="$hardcode_libdirs$hardcode_libdir_separator$libdir"
+ ;;
+ esac
+ fi
+ else
+ eval flag=\"$hardcode_libdir_flag_spec\"
+ rpath="$rpath $flag"
+ fi
+ elif test -n "$runpath_var"; then
+ case "$perm_rpath " in
+ *" $libdir "*) ;;
+ *) perm_rpath="$perm_rpath $libdir" ;;
+ esac
+ fi
+ case $host in
+ *-*-cygwin* | *-*-mingw* | *-*-pw32* | *-*-os2*)
+ case :$dllsearchpath: in
+ *":$libdir:"*) ;;
+ *) dllsearchpath="$dllsearchpath:$libdir";;
+ esac
+ ;;
+ esac
+ done
+ # Substitute the hardcoded libdirs into the rpath.
+ if test -n "$hardcode_libdir_separator" &&
+ test -n "$hardcode_libdirs"; then
+ libdir="$hardcode_libdirs"
+ eval rpath=\" $hardcode_libdir_flag_spec\"
+ fi
+ compile_rpath="$rpath"
+
+ rpath=
+ hardcode_libdirs=
+ for libdir in $finalize_rpath; do
+ if test -n "$hardcode_libdir_flag_spec"; then
+ if test -n "$hardcode_libdir_separator"; then
+ if test -z "$hardcode_libdirs"; then
+ hardcode_libdirs="$libdir"
+ else
+ # Just accumulate the unique libdirs.
+ case $hardcode_libdir_separator$hardcode_libdirs$hardcode_libdir_separator in
+ *"$hardcode_libdir_separator$libdir$hardcode_libdir_separator"*)
+ ;;
+ *)
+ hardcode_libdirs="$hardcode_libdirs$hardcode_libdir_separator$libdir"
+ ;;
+ esac
+ fi
+ else
+ eval flag=\"$hardcode_libdir_flag_spec\"
+ rpath="$rpath $flag"
+ fi
+ elif test -n "$runpath_var"; then
+ case "$finalize_perm_rpath " in
+ *" $libdir "*) ;;
+ *) finalize_perm_rpath="$finalize_perm_rpath $libdir" ;;
+ esac
+ fi
+ done
+ # Substitute the hardcoded libdirs into the rpath.
+ if test -n "$hardcode_libdir_separator" &&
+ test -n "$hardcode_libdirs"; then
+ libdir="$hardcode_libdirs"
+ eval rpath=\" $hardcode_libdir_flag_spec\"
+ fi
+ finalize_rpath="$rpath"
+
+ if test -n "$libobjs" && test "$build_old_libs" = yes; then
+ # Transform all the library objects into standard objects.
+ compile_command=`$echo "X$compile_command" | $SP2NL | $Xsed -e "$lo2o" | $NL2SP`
+ finalize_command=`$echo "X$finalize_command" | $SP2NL | $Xsed -e "$lo2o" | $NL2SP`
+ fi
+
+ dlsyms=
+ if test -n "$dlfiles$dlprefiles" || test "$dlself" != no; then
+ if test -n "$NM" && test -n "$global_symbol_pipe"; then
+ dlsyms="${outputname}S.c"
+ else
+ $echo "$modename: not configured to extract global symbols from dlpreopened files" 1>&2
+ fi
+ fi
+
+ if test -n "$dlsyms"; then
+ case $dlsyms in
+ "") ;;
+ *.c)
+ # Discover the nlist of each of the dlfiles.
+ nlist="$output_objdir/${outputname}.nm"
+
+ $show "$rm $nlist ${nlist}S ${nlist}T"
+ $run $rm "$nlist" "${nlist}S" "${nlist}T"
+
+ # Parse the name list into a source file.
+ $show "creating $output_objdir/$dlsyms"
+
+ test -z "$run" && $echo > "$output_objdir/$dlsyms" "\
+/* $dlsyms - symbol resolution table for \`$outputname' dlsym emulation. */
+/* Generated by $PROGRAM - GNU $PACKAGE $VERSION$TIMESTAMP */
+
+#ifdef __cplusplus
+extern \"C\" {
+#endif
+
+/* Prevent the only kind of declaration conflicts we can make. */
+#define lt_preloaded_symbols some_other_symbol
+
+/* External symbol declarations for the compiler. */\
+"
+
+ if test "$dlself" = yes; then
+ $show "generating symbol list for \`$output'"
+
+ test -z "$run" && $echo ': @PROGRAM@ ' > "$nlist"
+
+ # Add our own program objects to the symbol list.
+ progfiles=`$echo "X$objs$old_deplibs" | $SP2NL | $Xsed -e "$lo2o" | $NL2SP`
+ for arg in $progfiles; do
+ $show "extracting global C symbols from \`$arg'"
+ $run eval "$NM $arg | $global_symbol_pipe >> '$nlist'"
+ done
+
+ if test -n "$exclude_expsyms"; then
+ $run eval 'egrep -v " ($exclude_expsyms)$" "$nlist" > "$nlist"T'
+ $run eval '$mv "$nlist"T "$nlist"'
+ fi
+
+ if test -n "$export_symbols_regex"; then
+ $run eval 'egrep -e "$export_symbols_regex" "$nlist" > "$nlist"T'
+ $run eval '$mv "$nlist"T "$nlist"'
+ fi
+
+ # Prepare the list of exported symbols
+ if test -z "$export_symbols"; then
+ export_symbols="$output_objdir/$output.exp"
+ $run $rm $export_symbols
+ $run eval "sed -n -e '/^: @PROGRAM@$/d' -e 's/^.* \(.*\)$/\1/p' "'< "$nlist" > "$export_symbols"'
+ else
+ $run eval "sed -e 's/\([][.*^$]\)/\\\1/g' -e 's/^/ /' -e 's/$/$/'"' < "$export_symbols" > "$output_objdir/$output.exp"'
+ $run eval 'grep -f "$output_objdir/$output.exp" < "$nlist" > "$nlist"T'
+ $run eval 'mv "$nlist"T "$nlist"'
+ fi
+ fi
+
+ for arg in $dlprefiles; do
+ $show "extracting global C symbols from \`$arg'"
+ name=`echo "$arg" | sed -e 's%^.*/%%'`
+ $run eval 'echo ": $name " >> "$nlist"'
+ $run eval "$NM $arg | $global_symbol_pipe >> '$nlist'"
+ done
+
+ if test -z "$run"; then
+ # Make sure we have at least an empty file.
+ test -f "$nlist" || : > "$nlist"
+
+ if test -n "$exclude_expsyms"; then
+ egrep -v " ($exclude_expsyms)$" "$nlist" > "$nlist"T
+ $mv "$nlist"T "$nlist"
+ fi
+
+ # Try sorting and uniquifying the output.
+ if grep -v "^: " < "$nlist" | sort +2 | uniq > "$nlist"S; then
+ :
+ else
+ grep -v "^: " < "$nlist" > "$nlist"S
+ fi
+
+ if test -f "$nlist"S; then
+ eval "$global_symbol_to_cdecl"' < "$nlist"S >> "$output_objdir/$dlsyms"'
+ else
+ echo '/* NONE */' >> "$output_objdir/$dlsyms"
+ fi
+
+ $echo >> "$output_objdir/$dlsyms" "\
+
+#undef lt_preloaded_symbols
+
+#if defined (__STDC__) && __STDC__
+# define lt_ptr void *
+#else
+# define lt_ptr char *
+# define const
+#endif
+
+/* The mapping between symbol names and symbols. */
+const struct {
+ const char *name;
+ lt_ptr address;
+}
+lt_preloaded_symbols[] =
+{\
+"
+
+ eval "$global_symbol_to_c_name_address" < "$nlist" >> "$output_objdir/$dlsyms"
+
+ $echo >> "$output_objdir/$dlsyms" "\
+ {0, (lt_ptr) 0}
+};
+
+/* This works around a problem in FreeBSD linker */
+#ifdef FREEBSD_WORKAROUND
+static const void *lt_preloaded_setup() {
+ return lt_preloaded_symbols;
+}
+#endif
+
+#ifdef __cplusplus
+}
+#endif\
+"
+ fi
+
+ pic_flag_for_symtable=
+ case $host in
+ # compiling the symbol table file with pic_flag works around
+ # a FreeBSD bug that causes programs to crash when -lm is
+ # linked before any other PIC object. But we must not use
+ # pic_flag when linking with -static. The problem exists in
+ # FreeBSD 2.2.6 and is fixed in FreeBSD 3.1.
+ *-*-freebsd2*|*-*-freebsd3.0*|*-*-freebsdelf3.0*)
+ case "$compile_command " in
+ *" -static "*) ;;
+ *) pic_flag_for_symtable=" $pic_flag -DPIC -DFREEBSD_WORKAROUND";;
+ esac;;
+ *-*-hpux*)
+ case "$compile_command " in
+ *" -static "*) ;;
+ *) pic_flag_for_symtable=" $pic_flag -DPIC";;
+ esac
+ esac
+
+ # Now compile the dynamic symbol file.
+ $show "(cd $output_objdir && $CC -c$no_builtin_flag$pic_flag_for_symtable \"$dlsyms\")"
+ $run eval '(cd $output_objdir && $CC -c$no_builtin_flag$pic_flag_for_symtable "$dlsyms")' || exit $?
+
+ # Clean up the generated files.
+ $show "$rm $output_objdir/$dlsyms $nlist ${nlist}S ${nlist}T"
+ $run $rm "$output_objdir/$dlsyms" "$nlist" "${nlist}S" "${nlist}T"
+
+ # Transform the symbol file into the correct name.
+ compile_command=`$echo "X$compile_command" | $Xsed -e "s%@SYMFILE@%$output_objdir/${outputname}S.${objext}%"`
+ finalize_command=`$echo "X$finalize_command" | $Xsed -e "s%@SYMFILE@%$output_objdir/${outputname}S.${objext}%"`
+ ;;
+ *)
+ $echo "$modename: unknown suffix for \`$dlsyms'" 1>&2
+ exit 1
+ ;;
+ esac
+ else
+ # We keep going just in case the user didn't refer to
+ # lt_preloaded_symbols. The linker will fail if global_symbol_pipe
+ # really was required.
+
+ # Nullify the symbol file.
+ compile_command=`$echo "X$compile_command" | $Xsed -e "s% @SYMFILE@%%"`
+ finalize_command=`$echo "X$finalize_command" | $Xsed -e "s% @SYMFILE@%%"`
+ fi
+
+ if test $need_relink = no || test "$build_libtool_libs" != yes; then
+ # Replace the output file specification.
+ compile_command=`$echo "X$compile_command" | $Xsed -e 's%@OUTPUT@%'"$output"'%g'`
+ link_command="$compile_command$compile_rpath"
+
+ # We have no uninstalled library dependencies, so finalize right now.
+ $show "$link_command"
+ $run eval "$link_command"
+ status=$?
+
+ # Delete the generated files.
+ if test -n "$dlsyms"; then
+ $show "$rm $output_objdir/${outputname}S.${objext}"
+ $run $rm "$output_objdir/${outputname}S.${objext}"
+ fi
+
+ exit $status
+ fi
+
+ if test -n "$shlibpath_var"; then
+ # We should set the shlibpath_var
+ rpath=
+ for dir in $temp_rpath; do
+ case $dir in
+ [\\/]* | [A-Za-z]:[\\/]*)
+ # Absolute path.
+ rpath="$rpath$dir:"
+ ;;
+ *)
+ # Relative path: add a thisdir entry.
+ rpath="$rpath\$thisdir/$dir:"
+ ;;
+ esac
+ done
+ temp_rpath="$rpath"
+ fi
+
+ if test -n "$compile_shlibpath$finalize_shlibpath"; then
+ compile_command="$shlibpath_var=\"$compile_shlibpath$finalize_shlibpath\$$shlibpath_var\" $compile_command"
+ fi
+ if test -n "$finalize_shlibpath"; then
+ finalize_command="$shlibpath_var=\"$finalize_shlibpath\$$shlibpath_var\" $finalize_command"
+ fi
+
+ compile_var=
+ finalize_var=
+ if test -n "$runpath_var"; then
+ if test -n "$perm_rpath"; then
+ # We should set the runpath_var.
+ rpath=
+ for dir in $perm_rpath; do
+ rpath="$rpath$dir:"
+ done
+ compile_var="$runpath_var=\"$rpath\$$runpath_var\" "
+ fi
+ if test -n "$finalize_perm_rpath"; then
+ # We should set the runpath_var.
+ rpath=
+ for dir in $finalize_perm_rpath; do
+ rpath="$rpath$dir:"
+ done
+ finalize_var="$runpath_var=\"$rpath\$$runpath_var\" "
+ fi
+ fi
+
+ if test "$no_install" = yes; then
+ # We don't need to create a wrapper script.
+ link_command="$compile_var$compile_command$compile_rpath"
+ # Replace the output file specification.
+ link_command=`$echo "X$link_command" | $Xsed -e 's%@OUTPUT@%'"$output"'%g'`
+ # Delete the old output file.
+ $run $rm $output
+ # Link the executable and exit
+ $show "$link_command"
+ $run eval "$link_command" || exit $?
+ exit 0
+ fi
+
+ if test "$hardcode_action" = relink; then
+ # Fast installation is not supported
+ link_command="$compile_var$compile_command$compile_rpath"
+ relink_command="$finalize_var$finalize_command$finalize_rpath"
+
+ $echo "$modename: warning: this platform does not like uninstalled shared libraries" 1>&2
+ $echo "$modename: \`$output' will be relinked during installation" 1>&2
+ else
+ if test "$fast_install" != no; then
+ link_command="$finalize_var$compile_command$finalize_rpath"
+ if test "$fast_install" = yes; then
+ relink_command=`$echo "X$compile_var$compile_command$compile_rpath" | $Xsed -e 's%@OUTPUT@%\$progdir/\$file%g'`
+ else
+ # fast_install is set to needless
+ relink_command=
+ fi
+ else
+ link_command="$compile_var$compile_command$compile_rpath"
+ relink_command="$finalize_var$finalize_command$finalize_rpath"
+ fi
+ fi
+
+ # Replace the output file specification.
+ link_command=`$echo "X$link_command" | $Xsed -e 's%@OUTPUT@%'"$output_objdir/$outputname"'%g'`
+
+ # Delete the old output files.
+ $run $rm $output $output_objdir/$outputname $output_objdir/lt-$outputname
+
+ $show "$link_command"
+ $run eval "$link_command" || exit $?
+
+ # Now create the wrapper script.
+ $show "creating $output"
+
+ # Quote the relink command for shipping.
+ if test -n "$relink_command"; then
+ # Preserve any variables that may affect compiler behavior
+ for var in $variables_saved_for_relink; do
+ if eval test -z \"\${$var+set}\"; then
+ relink_command="{ test -z \"\${$var+set}\" || unset $var || { $var=; export $var; }; }; $relink_command"
+ elif eval var_value=\$$var; test -z "$var_value"; then
+ relink_command="$var=; export $var; $relink_command"
+ else
+ var_value=`$echo "X$var_value" | $Xsed -e "$sed_quote_subst"`
+ relink_command="$var=\"$var_value\"; export $var; $relink_command"
+ fi
+ done
+ relink_command="cd `pwd`; $relink_command"
+ relink_command=`$echo "X$relink_command" | $Xsed -e "$sed_quote_subst"`
+ fi
+
+ # Quote $echo for shipping.
+ if test "X$echo" = "X$SHELL $0 --fallback-echo"; then
+ case $0 in
+ [\\/]* | [A-Za-z]:[\\/]*) qecho="$SHELL $0 --fallback-echo";;
+ *) qecho="$SHELL `pwd`/$0 --fallback-echo";;
+ esac
+ qecho=`$echo "X$qecho" | $Xsed -e "$sed_quote_subst"`
+ else
+ qecho=`$echo "X$echo" | $Xsed -e "$sed_quote_subst"`
+ fi
+
+ # Only actually do things if our run command is non-null.
+ if test -z "$run"; then
+ # win32 will think the script is a binary if it has
+ # a .exe suffix, so we strip it off here.
+ case $output in
+ *.exe) output=`echo $output|sed 's,.exe$,,'` ;;
+ esac
+ # test for cygwin because mv fails w/o .exe extensions
+ case $host in
+ *cygwin*) exeext=.exe ;;
+ *) exeext= ;;
+ esac
+ $rm $output
+ trap "$rm $output; exit 1" 1 2 15
+
+ $echo > $output "\
+#! $SHELL
+
+# $output - temporary wrapper script for $objdir/$outputname
+# Generated by $PROGRAM - GNU $PACKAGE $VERSION$TIMESTAMP
+#
+# The $output program cannot be directly executed until all the libtool
+# libraries that it depends on are installed.
+#
+# This wrapper script should never be moved out of the build directory.
+# If it is, it will not operate correctly.
+
+# Sed substitution that helps us do robust quoting. It backslashifies
+# metacharacters that are still active within double-quoted strings.
+Xsed='sed -e 1s/^X//'
+sed_quote_subst='$sed_quote_subst'
+
+# The HP-UX ksh and POSIX shell print the target directory to stdout
+# if CDPATH is set.
+if test \"\${CDPATH+set}\" = set; then CDPATH=:; export CDPATH; fi
+
+relink_command=\"$relink_command\"
+
+# This environment variable determines our operation mode.
+if test \"\$libtool_install_magic\" = \"$magic\"; then
+ # install mode needs the following variable:
+ notinst_deplibs='$notinst_deplibs'
+else
+ # When we are sourced in execute mode, \$file and \$echo are already set.
+ if test \"\$libtool_execute_magic\" != \"$magic\"; then
+ echo=\"$qecho\"
+ file=\"\$0\"
+ # Make sure echo works.
+ if test \"X\$1\" = X--no-reexec; then
+ # Discard the --no-reexec flag, and continue.
+ shift
+ elif test \"X\`(\$echo '\t') 2>/dev/null\`\" = 'X\t'; then
+ # Yippee, \$echo works!
+ :
+ else
+ # Restart under the correct shell, and then maybe \$echo will work.
+ exec $SHELL \"\$0\" --no-reexec \${1+\"\$@\"}
+ fi
+ fi\
+"
+ $echo >> $output "\
+
+ # Find the directory that this script lives in.
+ thisdir=\`\$echo \"X\$file\" | \$Xsed -e 's%/[^/]*$%%'\`
+ test \"x\$thisdir\" = \"x\$file\" && thisdir=.
+
+ # Follow symbolic links until we get to the real thisdir.
+ file=\`ls -ld \"\$file\" | sed -n 's/.*-> //p'\`
+ while test -n \"\$file\"; do
+ destdir=\`\$echo \"X\$file\" | \$Xsed -e 's%/[^/]*\$%%'\`
+
+ # If there was a directory component, then change thisdir.
+ if test \"x\$destdir\" != \"x\$file\"; then
+ case \"\$destdir\" in
+ [\\\\/]* | [A-Za-z]:[\\\\/]*) thisdir=\"\$destdir\" ;;
+ *) thisdir=\"\$thisdir/\$destdir\" ;;
+ esac
+ fi
+
+ file=\`\$echo \"X\$file\" | \$Xsed -e 's%^.*/%%'\`
+ file=\`ls -ld \"\$thisdir/\$file\" | sed -n 's/.*-> //p'\`
+ done
+
+ # Try to get the absolute directory name.
+ absdir=\`cd \"\$thisdir\" && pwd\`
+ test -n \"\$absdir\" && thisdir=\"\$absdir\"
+"
+
+ if test "$fast_install" = yes; then
+ echo >> $output "\
+ program=lt-'$outputname'$exeext
+ progdir=\"\$thisdir/$objdir\"
+
+ if test ! -f \"\$progdir/\$program\" || \\
+ { file=\`ls -1dt \"\$progdir/\$program\" \"\$progdir/../\$program\" 2>/dev/null | sed 1q\`; \\
+ test \"X\$file\" != \"X\$progdir/\$program\"; }; then
+
+ file=\"\$\$-\$program\"
+
+ if test ! -d \"\$progdir\"; then
+ $mkdir \"\$progdir\"
+ else
+ $rm \"\$progdir/\$file\"
+ fi"
+
+ echo >> $output "\
+
+ # relink executable if necessary
+ if test -n \"\$relink_command\"; then
+ if relink_command_output=\`eval \$relink_command 2>&1\`; then :
+ else
+ $echo \"\$relink_command_output\" >&2
+ $rm \"\$progdir/\$file\"
+ exit 1
+ fi
+ fi
+
+ $mv \"\$progdir/\$file\" \"\$progdir/\$program\" 2>/dev/null ||
+ { $rm \"\$progdir/\$program\";
+ $mv \"\$progdir/\$file\" \"\$progdir/\$program\"; }
+ $rm \"\$progdir/\$file\"
+ fi"
+ else
+ echo >> $output "\
+ program='$outputname'
+ progdir=\"\$thisdir/$objdir\"
+"
+ fi
+
+ echo >> $output "\
+
+ if test -f \"\$progdir/\$program\"; then"
+
+ # Export our shlibpath_var if we have one.
+ if test "$shlibpath_overrides_runpath" = yes && test -n "$shlibpath_var" && test -n "$temp_rpath"; then
+ $echo >> $output "\
+ # Add our own library path to $shlibpath_var
+ $shlibpath_var=\"$temp_rpath\$$shlibpath_var\"
+
+ # Some systems cannot cope with colon-terminated $shlibpath_var
+ # The second colon is a workaround for a bug in BeOS R4 sed
+ $shlibpath_var=\`\$echo \"X\$$shlibpath_var\" | \$Xsed -e 's/::*\$//'\`
+
+ export $shlibpath_var
+"
+ fi
+
+ # fixup the dll searchpath if we need to.
+ if test -n "$dllsearchpath"; then
+ $echo >> $output "\
+ # Add the dll search path components to the executable PATH
+ PATH=$dllsearchpath:\$PATH
+"
+ fi
+
+ $echo >> $output "\
+ if test \"\$libtool_execute_magic\" != \"$magic\"; then
+ # Run the actual program with our arguments.
+"
+ case $host in
+ # win32 systems need to use the prog path for dll
+ # lookup to work
+ *-*-cygwin* | *-*-pw32*)
+ $echo >> $output "\
+ exec \$progdir/\$program \${1+\"\$@\"}
+"
+ ;;
+
+ # Backslashes separate directories on plain windows
+ *-*-mingw | *-*-os2*)
+ $echo >> $output "\
+ exec \$progdir\\\\\$program \${1+\"\$@\"}
+"
+ ;;
+
+ *)
+ $echo >> $output "\
+ # Export the path to the program.
+ PATH=\"\$progdir:\$PATH\"
+ export PATH
+
+ exec \$program \${1+\"\$@\"}
+"
+ ;;
+ esac
+ $echo >> $output "\
+ \$echo \"\$0: cannot exec \$program \${1+\"\$@\"}\"
+ exit 1
+ fi
+ else
+ # The program doesn't exist.
+ \$echo \"\$0: error: \$progdir/\$program does not exist\" 1>&2
+ \$echo \"This script is just a wrapper for \$program.\" 1>&2
+ echo \"See the $PACKAGE documentation for more information.\" 1>&2
+ exit 1
+ fi
+fi\
+"
+ chmod +x $output
+ fi
+ exit 0
+ ;;
+ esac
+
+ # See if we need to build an old-fashioned archive.
+ for oldlib in $oldlibs; do
+
+ if test "$build_libtool_libs" = convenience; then
+ oldobjs="$libobjs_save"
+ addlibs="$convenience"
+ build_libtool_libs=no
+ else
+ if test "$build_libtool_libs" = module; then
+ oldobjs="$libobjs_save"
+ build_libtool_libs=no
+ else
+ oldobjs="$objs$old_deplibs "`$echo "X$libobjs_save" | $SP2NL | $Xsed -e '/\.'${libext}'$/d' -e '/\.lib$/d' -e "$lo2o" | $NL2SP`
+ fi
+ addlibs="$old_convenience"
+ fi
+
+ if test -n "$addlibs"; then
+ gentop="$output_objdir/${outputname}x"
+ $show "${rm}r $gentop"
+ $run ${rm}r "$gentop"
+ $show "mkdir $gentop"
+ $run mkdir "$gentop"
+ status=$?
+ if test $status -ne 0 && test ! -d "$gentop"; then
+ exit $status
+ fi
+ generated="$generated $gentop"
+
+ # Add in members from convenience archives.
+ for xlib in $addlibs; do
+ # Extract the objects.
+ case $xlib in
+ [\\/]* | [A-Za-z]:[\\/]*) xabs="$xlib" ;;
+ *) xabs=`pwd`"/$xlib" ;;
+ esac
+ xlib=`$echo "X$xlib" | $Xsed -e 's%^.*/%%'`
+ xdir="$gentop/$xlib"
+
+ $show "${rm}r $xdir"
+ $run ${rm}r "$xdir"
+ $show "mkdir $xdir"
+ $run mkdir "$xdir"
+ status=$?
+ if test $status -ne 0 && test ! -d "$xdir"; then
+ exit $status
+ fi
+ $show "(cd $xdir && $AR x $xabs)"
+ $run eval "(cd \$xdir && $AR x \$xabs)" || exit $?
+
+ oldobjs="$oldobjs "`find $xdir -name \*.${objext} -print -o -name \*.lo -print | $NL2SP`
+ done
+ fi
+
+ # Do each command in the archive commands.
+ if test -n "$old_archive_from_new_cmds" && test "$build_libtool_libs" = yes; then
+ eval cmds=\"$old_archive_from_new_cmds\"
+ else
+ # Ensure that we have .o objects in place in case we decided
+ # not to build a shared library, and have fallen back to building
+ # static libs even though --disable-static was passed!
+ for oldobj in $oldobjs; do
+ if test ! -f $oldobj; then
+ xdir=`$echo "X$oldobj" | $Xsed -e 's%/[^/]*$%%'`
+ if test "X$xdir" = "X$oldobj"; then
+ xdir="."
+ else
+ xdir="$xdir"
+ fi
+ baseobj=`$echo "X$oldobj" | $Xsed -e 's%^.*/%%'`
+ obj=`$echo "X$baseobj" | $Xsed -e "$o2lo"`
+ $show "(cd $xdir && ${LN_S} $obj $baseobj)"
+ $run eval '(cd $xdir && ${LN_S} $obj $baseobj)' || exit $?
+ fi
+ done
+
+ eval cmds=\"$old_archive_cmds\"
+ fi
+ save_ifs="$IFS"; IFS='~'
+ for cmd in $cmds; do
+ IFS="$save_ifs"
+ $show "$cmd"
+ $run eval "$cmd" || exit $?
+ done
+ IFS="$save_ifs"
+ done
+
+ if test -n "$generated"; then
+ $show "${rm}r$generated"
+ $run ${rm}r$generated
+ fi
+
+ # Now create the libtool archive.
+ case $output in
+ *.la)
+ old_library=
+ test "$build_old_libs" = yes && old_library="$libname.$libext"
+ $show "creating $output"
+
+ # Preserve any variables that may affect compiler behavior
+ for var in $variables_saved_for_relink; do
+ if eval test -z \"\${$var+set}\"; then
+ relink_command="{ test -z \"\${$var+set}\" || unset $var || { $var=; export $var; }; }; $relink_command"
+ elif eval var_value=\$$var; test -z "$var_value"; then
+ relink_command="$var=; export $var; $relink_command"
+ else
+ var_value=`$echo "X$var_value" | $Xsed -e "$sed_quote_subst"`
+ relink_command="$var=\"$var_value\"; export $var; $relink_command"
+ fi
+ done
+ # Quote the link command for shipping.
+ relink_command="cd `pwd`; $SHELL $0 --mode=relink $libtool_args"
+ relink_command=`$echo "X$relink_command" | $Xsed -e "$sed_quote_subst"`
+
+ # Only create the output if not a dry run.
+ if test -z "$run"; then
+ for installed in no yes; do
+ if test "$installed" = yes; then
+ if test -z "$install_libdir"; then
+ break
+ fi
+ output="$output_objdir/$outputname"i
+ # Replace all uninstalled libtool libraries with the installed ones
+ newdependency_libs=
+ for deplib in $dependency_libs; do
+ case $deplib in
+ *.la)
+ name=`$echo "X$deplib" | $Xsed -e 's%^.*/%%'`
+ eval libdir=`sed -n -e 's/^libdir=\(.*\)$/\1/p' $deplib`
+ if test -z "$libdir"; then
+ $echo "$modename: \`$deplib' is not a valid libtool archive" 1>&2
+ exit 1
+ fi
+ newdependency_libs="$newdependency_libs $libdir/$name"
+ ;;
+ *) newdependency_libs="$newdependency_libs $deplib" ;;
+ esac
+ done
+ dependency_libs="$newdependency_libs"
+ newdlfiles=
+ for lib in $dlfiles; do
+ name=`$echo "X$lib" | $Xsed -e 's%^.*/%%'`
+ eval libdir=`sed -n -e 's/^libdir=\(.*\)$/\1/p' $lib`
+ if test -z "$libdir"; then
+ $echo "$modename: \`$lib' is not a valid libtool archive" 1>&2
+ exit 1
+ fi
+ newdlfiles="$newdlfiles $libdir/$name"
+ done
+ dlfiles="$newdlfiles"
+ newdlprefiles=
+ for lib in $dlprefiles; do
+ name=`$echo "X$lib" | $Xsed -e 's%^.*/%%'`
+ eval libdir=`sed -n -e 's/^libdir=\(.*\)$/\1/p' $lib`
+ if test -z "$libdir"; then
+ $echo "$modename: \`$lib' is not a valid libtool archive" 1>&2
+ exit 1
+ fi
+ newdlprefiles="$newdlprefiles $libdir/$name"
+ done
+ dlprefiles="$newdlprefiles"
+ fi
+ $rm $output
+ # place dlname in correct position for cygwin
+ tdlname=$dlname
+ case $host,$output,$installed,$module,$dlname in
+ *cygwin*,*lai,yes,no,*.dll) tdlname=../bin/$dlname ;;
+ esac
+ $echo > $output "\
+# $outputname - a libtool library file
+# Generated by $PROGRAM - GNU $PACKAGE $VERSION$TIMESTAMP
+#
+# Please DO NOT delete this file!
+# It is necessary for linking the library.
+
+# The name that we can dlopen(3).
+dlname='$tdlname'
+
+# Names of this library.
+library_names='$library_names'
+
+# The name of the static archive.
+old_library='$old_library'
+
+# Libraries that this one depends upon.
+dependency_libs='$dependency_libs'
+
+# Version information for $libname.
+current=$current
+age=$age
+revision=$revision
+
+# Is this an already installed library?
+installed=$installed
+
+# Files to dlopen/dlpreopen
+dlopen='$dlfiles'
+dlpreopen='$dlprefiles'
+
+# Directory that this library needs to be installed in:
+libdir='$install_libdir'"
+ if test "$installed" = no && test $need_relink = yes; then
+ $echo >> $output "\
+relink_command=\"$relink_command\""
+ fi
+ done
+ fi
+
+ # Do a symbolic link so that the libtool archive can be found in
+ # LD_LIBRARY_PATH before the program is installed.
+ $show "(cd $output_objdir && $rm $outputname && $LN_S ../$outputname $outputname)"
+ $run eval '(cd $output_objdir && $rm $outputname && $LN_S ../$outputname $outputname)' || exit $?
+ ;;
+ esac
+ exit 0
+ ;;
+
+ # libtool install mode
+ install)
+ modename="$modename: install"
+
+ # There may be an optional sh(1) argument at the beginning of
+ # install_prog (especially on Windows NT).
+ if test "$nonopt" = "$SHELL" || test "$nonopt" = /bin/sh ||
+ # Allow the use of GNU shtool's install command.
+ $echo "X$nonopt" | $Xsed | grep shtool > /dev/null; then
+ # Aesthetically quote it.
+ arg=`$echo "X$nonopt" | $Xsed -e "$sed_quote_subst"`
+ case $arg in
+ *[\[\~\#\^\&\*\(\)\{\}\|\;\<\>\?\'\ \ ]*|*]*)
+ arg="\"$arg\""
+ ;;
+ esac
+ install_prog="$arg "
+ arg="$1"
+ shift
+ else
+ install_prog=
+ arg="$nonopt"
+ fi
+
+ # The real first argument should be the name of the installation program.
+ # Aesthetically quote it.
+ arg=`$echo "X$arg" | $Xsed -e "$sed_quote_subst"`
+ case $arg in
+ *[\[\~\#\^\&\*\(\)\{\}\|\;\<\>\?\'\ \ ]*|*]*)
+ arg="\"$arg\""
+ ;;
+ esac
+ install_prog="$install_prog$arg"
+
+ # We need to accept at least all the BSD install flags.
+ dest=
+ files=
+ opts=
+ prev=
+ install_type=
+ isdir=no
+ stripme=
+ for arg
+ do
+ if test -n "$dest"; then
+ files="$files $dest"
+ dest="$arg"
+ continue
+ fi
+
+ case $arg in
+ -d) isdir=yes ;;
+ -f) prev="-f" ;;
+ -g) prev="-g" ;;
+ -m) prev="-m" ;;
+ -o) prev="-o" ;;
+
+ # Added to support INN's install program.
+ -O) prev="-O" ;;
+ -G) prev="-G" ;;
+ -B) prev="-B" ;;
+
+ -s)
+ stripme=" -s"
+ continue
+ ;;
+ -*) ;;
+
+ *)
+ # If the previous option needed an argument, then skip it.
+ if test -n "$prev"; then
+ prev=
+ else
+ dest="$arg"
+ continue
+ fi
+ ;;
+ esac
+
+ # Aesthetically quote the argument.
+ arg=`$echo "X$arg" | $Xsed -e "$sed_quote_subst"`
+ case $arg in
+ *[\[\~\#\^\&\*\(\)\{\}\|\;\<\>\?\'\ \ ]*|*]*)
+ arg="\"$arg\""
+ ;;
+ esac
+ install_prog="$install_prog $arg"
+ done
+
+ if test -z "$install_prog"; then
+ $echo "$modename: you must specify an install program" 1>&2
+ $echo "$help" 1>&2
+ exit 1
+ fi
+
+ if test -n "$prev"; then
+ $echo "$modename: the \`$prev' option requires an argument" 1>&2
+ $echo "$help" 1>&2
+ exit 1
+ fi
+
+ if test -z "$files"; then
+ if test -z "$dest"; then
+ $echo "$modename: no file or destination specified" 1>&2
+ else
+ $echo "$modename: you must specify a destination" 1>&2
+ fi
+ $echo "$help" 1>&2
+ exit 1
+ fi
+
+ # Strip any trailing slash from the destination.
+ dest=`$echo "X$dest" | $Xsed -e 's%/$%%'`
+
+ # Check to see that the destination is a directory.
+ test -d "$dest" && isdir=yes
+ if test "$isdir" = yes; then
+ destdir="$dest"
+ destname=
+ else
+ destdir=`$echo "X$dest" | $Xsed -e 's%/[^/]*$%%'`
+ test "X$destdir" = "X$dest" && destdir=.
+ destname=`$echo "X$dest" | $Xsed -e 's%^.*/%%'`
+
+ # Not a directory, so check to see that there is only one file specified.
+ set dummy $files
+ if test $# -gt 2; then
+ $echo "$modename: \`$dest' is not a directory" 1>&2
+ $echo "$help" 1>&2
+ exit 1
+ fi
+ fi
+ case $destdir in
+ [\\/]* | [A-Za-z]:[\\/]*) ;;
+ *)
+ for file in $files; do
+ case $file in
+ *.lo) ;;
+ *)
+ $echo "$modename: \`$destdir' must be an absolute directory name" 1>&2
+ $echo "$help" 1>&2
+ exit 1
+ ;;
+ esac
+ done
+ ;;
+ esac
+
+ # This variable tells wrapper scripts just to set variables rather
+ # than running their programs.
+ libtool_install_magic="$magic"
+
+ staticlibs=
+ future_libdirs=
+ current_libdirs=
+ for file in $files; do
+
+ # Do each installation.
+ case $file in
+ *.$libext)
+ # Do the static libraries later.
+ staticlibs="$staticlibs $file"
+ ;;
+
+ *.la)
+ # Check to see that this really is a libtool archive.
+ if (sed -e '2q' $file | egrep "^# Generated by .*$PACKAGE") >/dev/null 2>&1; then :
+ else
+ $echo "$modename: \`$file' is not a valid libtool archive" 1>&2
+ $echo "$help" 1>&2
+ exit 1
+ fi
+
+ library_names=
+ old_library=
+ relink_command=
+ # If there is no directory component, then add one.
+ case $file in
+ */* | *\\*) . $file ;;
+ *) . ./$file ;;
+ esac
+
+ # Add the libdir to current_libdirs if it is the destination.
+ if test "X$destdir" = "X$libdir"; then
+ case "$current_libdirs " in
+ *" $libdir "*) ;;
+ *) current_libdirs="$current_libdirs $libdir" ;;
+ esac
+ else
+ # Note the libdir as a future libdir.
+ case "$future_libdirs " in
+ *" $libdir "*) ;;
+ *) future_libdirs="$future_libdirs $libdir" ;;
+ esac
+ fi
+
+ dir=`$echo "X$file" | $Xsed -e 's%/[^/]*$%%'`/
+ test "X$dir" = "X$file/" && dir=
+ dir="$dir$objdir"
+
+ if test -n "$relink_command"; then
+ $echo "$modename: warning: relinking \`$file'" 1>&2
+ $show "$relink_command"
+ if $run eval "$relink_command"; then :
+ else
+ $echo "$modename: error: relink \`$file' with the above command before installing it" 1>&2
+ continue
+ fi
+ fi
+
+ # See the names of the shared library.
+ set dummy $library_names
+ if test -n "$2"; then
+ realname="$2"
+ shift
+ shift
+
+ srcname="$realname"
+ test -n "$relink_command" && srcname="$realname"T
+
+ # Install the shared library and build the symlinks.
+ $show "$install_prog $dir/$srcname $destdir/$realname"
+ $run eval "$install_prog $dir/$srcname $destdir/$realname" || exit $?
+ if test -n "$stripme" && test -n "$striplib"; then
+ $show "$striplib $destdir/$realname"
+ $run eval "$striplib $destdir/$realname" || exit $?
+ fi
+
+ if test $# -gt 0; then
+ # Delete the old symlinks, and create new ones.
+ for linkname
+ do
+ if test "$linkname" != "$realname"; then
+ $show "(cd $destdir && $rm $linkname && $LN_S $realname $linkname)"
+ $run eval "(cd $destdir && $rm $linkname && $LN_S $realname $linkname)"
+ fi
+ done
+ fi
+
+ # Do each command in the postinstall commands.
+ lib="$destdir/$realname"
+ eval cmds=\"$postinstall_cmds\"
+ save_ifs="$IFS"; IFS='~'
+ for cmd in $cmds; do
+ IFS="$save_ifs"
+ $show "$cmd"
+ $run eval "$cmd" || exit $?
+ done
+ IFS="$save_ifs"
+ fi
+
+ # Install the pseudo-library for information purposes.
+ name=`$echo "X$file" | $Xsed -e 's%^.*/%%'`
+ instname="$dir/$name"i
+ $show "$install_prog $instname $destdir/$name"
+ $run eval "$install_prog $instname $destdir/$name" || exit $?
+
+ # Maybe install the static library, too.
+ test -n "$old_library" && staticlibs="$staticlibs $dir/$old_library"
+ ;;
+
+ *.lo)
+ # Install (i.e. copy) a libtool object.
+
+ # Figure out destination file name, if it wasn't already specified.
+ if test -n "$destname"; then
+ destfile="$destdir/$destname"
+ else
+ destfile=`$echo "X$file" | $Xsed -e 's%^.*/%%'`
+ destfile="$destdir/$destfile"
+ fi
+
+ # Deduce the name of the destination old-style object file.
+ case $destfile in
+ *.lo)
+ staticdest=`$echo "X$destfile" | $Xsed -e "$lo2o"`
+ ;;
+ *.$objext)
+ staticdest="$destfile"
+ destfile=
+ ;;
+ *)
+ $echo "$modename: cannot copy a libtool object to \`$destfile'" 1>&2
+ $echo "$help" 1>&2
+ exit 1
+ ;;
+ esac
+
+ # Install the libtool object if requested.
+ if test -n "$destfile"; then
+ $show "$install_prog $file $destfile"
+ $run eval "$install_prog $file $destfile" || exit $?
+ fi
+
+ # Install the old object if enabled.
+ if test "$build_old_libs" = yes; then
+ # Deduce the name of the old-style object file.
+ staticobj=`$echo "X$file" | $Xsed -e "$lo2o"`
+
+ $show "$install_prog $staticobj $staticdest"
+ $run eval "$install_prog \$staticobj \$staticdest" || exit $?
+ fi
+ exit 0
+ ;;
+
+ *)
+ # Figure out destination file name, if it wasn't already specified.
+ if test -n "$destname"; then
+ destfile="$destdir/$destname"
+ else
+ destfile=`$echo "X$file" | $Xsed -e 's%^.*/%%'`
+ destfile="$destdir/$destfile"
+ fi
+
+ # Do a test to see if this is really a libtool program.
+ if (sed -e '4q' $file | egrep "^# Generated by .*$PACKAGE") >/dev/null 2>&1; then
+ notinst_deplibs=
+ relink_command=
+
+ # If there is no directory component, then add one.
+ case $file in
+ */* | *\\*) . $file ;;
+ *) . ./$file ;;
+ esac
+
+ # Check the variables that should have been set.
+ if test -z "$notinst_deplibs"; then
+ $echo "$modename: invalid libtool wrapper script \`$file'" 1>&2
+ exit 1
+ fi
+
+ finalize=yes
+ for lib in $notinst_deplibs; do
+ # Check to see that each library is installed.
+ libdir=
+ if test -f "$lib"; then
+ # If there is no directory component, then add one.
+ case $lib in
+ */* | *\\*) . $lib ;;
+ *) . ./$lib ;;
+ esac
+ fi
+ libfile="$libdir/"`$echo "X$lib" | $Xsed -e 's%^.*/%%g'` ### testsuite: skip nested quoting test
+ if test -n "$libdir" && test ! -f "$libfile"; then
+ $echo "$modename: warning: \`$lib' has not been installed in \`$libdir'" 1>&2
+ finalize=no
+ fi
+ done
+
+ relink_command=
+ # If there is no directory component, then add one.
+ case $file in
+ */* | *\\*) . $file ;;
+ *) . ./$file ;;
+ esac
+
+ outputname=
+ if test "$fast_install" = no && test -n "$relink_command"; then
+ if test "$finalize" = yes && test -z "$run"; then
+ tmpdir="/tmp"
+ test -n "$TMPDIR" && tmpdir="$TMPDIR"
+ tmpdir="$tmpdir/libtool-$$"
+ if $mkdir -p "$tmpdir" && chmod 700 "$tmpdir"; then :
+ else
+ $echo "$modename: error: cannot create temporary directory \`$tmpdir'" 1>&2
+ continue
+ fi
+ file=`$echo "X$file" | $Xsed -e 's%^.*/%%'`
+ outputname="$tmpdir/$file"
+ # Replace the output file specification.
+ relink_command=`$echo "X$relink_command" | $Xsed -e 's%@OUTPUT@%'"$outputname"'%g'`
+
+ $show "$relink_command"
+ if $run eval "$relink_command"; then :
+ else
+ $echo "$modename: error: relink \`$file' with the above command before installing it" 1>&2
+ ${rm}r "$tmpdir"
+ continue
+ fi
+ file="$outputname"
+ else
+ $echo "$modename: warning: cannot relink \`$file'" 1>&2
+ fi
+ else
+ # Install the binary that we compiled earlier.
+ file=`$echo "X$file" | $Xsed -e "s%\([^/]*\)$%$objdir/\1%"`
+ fi
+ fi
+
+ # remove .exe since cygwin /usr/bin/install will append another
+ # one anyways
+ case $install_prog,$host in
+ /usr/bin/install*,*cygwin*)
+ case $file:$destfile in
+ *.exe:*.exe)
+ # this is ok
+ ;;
+ *.exe:*)
+ destfile=$destfile.exe
+ ;;
+ *:*.exe)
+ destfile=`echo $destfile | sed -e 's,.exe$,,'`
+ ;;
+ esac
+ ;;
+ esac
+ $show "$install_prog$stripme $file $destfile"
+ $run eval "$install_prog\$stripme \$file \$destfile" || exit $?
+ test -n "$outputname" && ${rm}r "$tmpdir"
+ ;;
+ esac
+ done
+
+ for file in $staticlibs; do
+ name=`$echo "X$file" | $Xsed -e 's%^.*/%%'`
+
+ # Set up the ranlib parameters.
+ oldlib="$destdir/$name"
+
+ $show "$install_prog $file $oldlib"
+ $run eval "$install_prog \$file \$oldlib" || exit $?
+
+ if test -n "$stripme" && test -n "$striplib"; then
+ $show "$old_striplib $oldlib"
+ $run eval "$old_striplib $oldlib" || exit $?
+ fi
+
+ # Do each command in the postinstall commands.
+ eval cmds=\"$old_postinstall_cmds\"
+ save_ifs="$IFS"; IFS='~'
+ for cmd in $cmds; do
+ IFS="$save_ifs"
+ $show "$cmd"
+ $run eval "$cmd" || exit $?
+ done
+ IFS="$save_ifs"
+ done
+
+ if test -n "$future_libdirs"; then
+ $echo "$modename: warning: remember to run \`$progname --finish$future_libdirs'" 1>&2
+ fi
+
+ if test -n "$current_libdirs"; then
+ # Maybe just do a dry run.
+ test -n "$run" && current_libdirs=" -n$current_libdirs"
+ exec_cmd='$SHELL $0 --finish$current_libdirs'
+ else
+ exit 0
+ fi
+ ;;
+
+ # libtool finish mode
+ finish)
+ modename="$modename: finish"
+ libdirs="$nonopt"
+ admincmds=
+
+ if test -n "$finish_cmds$finish_eval" && test -n "$libdirs"; then
+ for dir
+ do
+ libdirs="$libdirs $dir"
+ done
+
+ for libdir in $libdirs; do
+ if test -n "$finish_cmds"; then
+ # Do each command in the finish commands.
+ eval cmds=\"$finish_cmds\"
+ save_ifs="$IFS"; IFS='~'
+ for cmd in $cmds; do
+ IFS="$save_ifs"
+ $show "$cmd"
+ $run eval "$cmd" || admincmds="$admincmds
+ $cmd"
+ done
+ IFS="$save_ifs"
+ fi
+ if test -n "$finish_eval"; then
+ # Do the single finish_eval.
+ eval cmds=\"$finish_eval\"
+ $run eval "$cmds" || admincmds="$admincmds
+ $cmds"
+ fi
+ done
+ fi
+
+ # Exit here if they wanted silent mode.
+ test "$show" = ":" && exit 0
+
+ echo "----------------------------------------------------------------------"
+ echo "Libraries have been installed in:"
+ for libdir in $libdirs; do
+ echo " $libdir"
+ done
+ echo
+ echo "If you ever happen to want to link against installed libraries"
+ echo "in a given directory, LIBDIR, you must either use libtool, and"
+ echo "specify the full pathname of the library, or use the \`-LLIBDIR'"
+ echo "flag during linking and do at least one of the following:"
+ if test -n "$shlibpath_var"; then
+ echo " - add LIBDIR to the \`$shlibpath_var' environment variable"
+ echo " during execution"
+ fi
+ if test -n "$runpath_var"; then
+ echo " - add LIBDIR to the \`$runpath_var' environment variable"
+ echo " during linking"
+ fi
+ if test -n "$hardcode_libdir_flag_spec"; then
+ libdir=LIBDIR
+ eval flag=\"$hardcode_libdir_flag_spec\"
+
+ echo " - use the \`$flag' linker flag"
+ fi
+ if test -n "$admincmds"; then
+ echo " - have your system administrator run these commands:$admincmds"
+ fi
+ if test -f /etc/ld.so.conf; then
+ echo " - have your system administrator add LIBDIR to \`/etc/ld.so.conf'"
+ fi
+ echo
+ echo "See any operating system documentation about shared libraries for"
+ echo "more information, such as the ld(1) and ld.so(8) manual pages."
+ echo "----------------------------------------------------------------------"
+ exit 0
+ ;;
+
+ # libtool execute mode
+ execute)
+ modename="$modename: execute"
+
+ # The first argument is the command name.
+ cmd="$nonopt"
+ if test -z "$cmd"; then
+ $echo "$modename: you must specify a COMMAND" 1>&2
+ $echo "$help"
+ exit 1
+ fi
+
+ # Handle -dlopen flags immediately.
+ for file in $execute_dlfiles; do
+ if test ! -f "$file"; then
+ $echo "$modename: \`$file' is not a file" 1>&2
+ $echo "$help" 1>&2
+ exit 1
+ fi
+
+ dir=
+ case $file in
+ *.la)
+ # Check to see that this really is a libtool archive.
+ if (sed -e '2q' $file | egrep "^# Generated by .*$PACKAGE") >/dev/null 2>&1; then :
+ else
+ $echo "$modename: \`$lib' is not a valid libtool archive" 1>&2
+ $echo "$help" 1>&2
+ exit 1
+ fi
+
+ # Read the libtool library.
+ dlname=
+ library_names=
+
+ # If there is no directory component, then add one.
+ case $file in
+ */* | *\\*) . $file ;;
+ *) . ./$file ;;
+ esac
+
+ # Skip this library if it cannot be dlopened.
+ if test -z "$dlname"; then
+ # Warn if it was a shared library.
+ test -n "$library_names" && $echo "$modename: warning: \`$file' was not linked with \`-export-dynamic'"
+ continue
+ fi
+
+ dir=`$echo "X$file" | $Xsed -e 's%/[^/]*$%%'`
+ test "X$dir" = "X$file" && dir=.
+
+ if test -f "$dir/$objdir/$dlname"; then
+ dir="$dir/$objdir"
+ else
+ $echo "$modename: cannot find \`$dlname' in \`$dir' or \`$dir/$objdir'" 1>&2
+ exit 1
+ fi
+ ;;
+
+ *.lo)
+ # Just add the directory containing the .lo file.
+ dir=`$echo "X$file" | $Xsed -e 's%/[^/]*$%%'`
+ test "X$dir" = "X$file" && dir=.
+ ;;
+
+ *)
+ $echo "$modename: warning \`-dlopen' is ignored for non-libtool libraries and objects" 1>&2
+ continue
+ ;;
+ esac
+
+ # Get the absolute pathname.
+ absdir=`cd "$dir" && pwd`
+ test -n "$absdir" && dir="$absdir"
+
+ # Now add the directory to shlibpath_var.
+ if eval "test -z \"\$$shlibpath_var\""; then
+ eval "$shlibpath_var=\"\$dir\""
+ else
+ eval "$shlibpath_var=\"\$dir:\$$shlibpath_var\""
+ fi
+ done
+
+ # This variable tells wrapper scripts just to set shlibpath_var
+ # rather than running their programs.
+ libtool_execute_magic="$magic"
+
+ # Check if any of the arguments is a wrapper script.
+ args=
+ for file
+ do
+ case $file in
+ -*) ;;
+ *)
+ # Do a test to see if this is really a libtool program.
+ if (sed -e '4q' $file | egrep "^# Generated by .*$PACKAGE") >/dev/null 2>&1; then
+ # If there is no directory component, then add one.
+ case $file in
+ */* | *\\*) . $file ;;
+ *) . ./$file ;;
+ esac
+
+ # Transform arg to wrapped name.
+ file="$progdir/$program"
+ fi
+ ;;
+ esac
+ # Quote arguments (to preserve shell metacharacters).
+ file=`$echo "X$file" | $Xsed -e "$sed_quote_subst"`
+ args="$args \"$file\""
+ done
+
+ if test -z "$run"; then
+ if test -n "$shlibpath_var"; then
+ # Export the shlibpath_var.
+ eval "export $shlibpath_var"
+ fi
+
+ # Restore saved enviroment variables
+ if test "${save_LC_ALL+set}" = set; then
+ LC_ALL="$save_LC_ALL"; export LC_ALL
+ fi
+ if test "${save_LANG+set}" = set; then
+ LANG="$save_LANG"; export LANG
+ fi
+
+ # Now prepare to actually exec the command.
+ exec_cmd='"$cmd"$args'
+ else
+ # Display what would be done.
+ if test -n "$shlibpath_var"; then
+ eval "\$echo \"\$shlibpath_var=\$$shlibpath_var\""
+ $echo "export $shlibpath_var"
+ fi
+ $echo "$cmd$args"
+ exit 0
+ fi
+ ;;
+
+ # libtool clean and uninstall mode
+ clean | uninstall)
+ modename="$modename: $mode"
+ rm="$nonopt"
+ files=
+ rmforce=
+ exit_status=0
+
+ # This variable tells wrapper scripts just to set variables rather
+ # than running their programs.
+ libtool_install_magic="$magic"
+
+ for arg
+ do
+ case $arg in
+ -f) rm="$rm $arg"; rmforce=yes ;;
+ -*) rm="$rm $arg" ;;
+ *) files="$files $arg" ;;
+ esac
+ done
+
+ if test -z "$rm"; then
+ $echo "$modename: you must specify an RM program" 1>&2
+ $echo "$help" 1>&2
+ exit 1
+ fi
+
+ rmdirs=
+
+ for file in $files; do
+ dir=`$echo "X$file" | $Xsed -e 's%/[^/]*$%%'`
+ if test "X$dir" = "X$file"; then
+ dir=.
+ objdir="$objdir"
+ else
+ objdir="$dir/$objdir"
+ fi
+ name=`$echo "X$file" | $Xsed -e 's%^.*/%%'`
+ test $mode = uninstall && objdir="$dir"
+
+ # Remember objdir for removal later, being careful to avoid duplicates
+ if test $mode = clean; then
+ case " $rmdirs " in
+ *" $objdir "*) ;;
+ *) rmdirs="$rmdirs $objdir" ;;
+ esac
+ fi
+
+ # Don't error if the file doesn't exist and rm -f was used.
+ if (test -L "$file") >/dev/null 2>&1 \
+ || (test -h "$file") >/dev/null 2>&1 \
+ || test -f "$file"; then
+ :
+ elif test -d "$file"; then
+ exit_status=1
+ continue
+ elif test "$rmforce" = yes; then
+ continue
+ fi
+
+ rmfiles="$file"
+
+ case $name in
+ *.la)
+ # Possibly a libtool archive, so verify it.
+ if (sed -e '2q' $file | egrep "^# Generated by .*$PACKAGE") >/dev/null 2>&1; then
+ . $dir/$name
+
+ # Delete the libtool libraries and symlinks.
+ for n in $library_names; do
+ rmfiles="$rmfiles $objdir/$n"
+ done
+ test -n "$old_library" && rmfiles="$rmfiles $objdir/$old_library"
+ test $mode = clean && rmfiles="$rmfiles $objdir/$name $objdir/${name}i"
+
+ if test $mode = uninstall; then
+ if test -n "$library_names"; then
+ # Do each command in the postuninstall commands.
+ eval cmds=\"$postuninstall_cmds\"
+ save_ifs="$IFS"; IFS='~'
+ for cmd in $cmds; do
+ IFS="$save_ifs"
+ $show "$cmd"
+ $run eval "$cmd"
+ if test $? != 0 && test "$rmforce" != yes; then
+ exit_status=1
+ fi
+ done
+ IFS="$save_ifs"
+ fi
+
+ if test -n "$old_library"; then
+ # Do each command in the old_postuninstall commands.
+ eval cmds=\"$old_postuninstall_cmds\"
+ save_ifs="$IFS"; IFS='~'
+ for cmd in $cmds; do
+ IFS="$save_ifs"
+ $show "$cmd"
+ $run eval "$cmd"
+ if test $? != 0 && test "$rmforce" != yes; then
+ exit_status=1
+ fi
+ done
+ IFS="$save_ifs"
+ fi
+ # FIXME: should reinstall the best remaining shared library.
+ fi
+ fi
+ ;;
+
+ *.lo)
+ if test "$build_old_libs" = yes; then
+ oldobj=`$echo "X$name" | $Xsed -e "$lo2o"`
+ rmfiles="$rmfiles $dir/$oldobj"
+ fi
+ ;;
+
+ *)
+ # Do a test to see if this is a libtool program.
+ if test $mode = clean &&
+ (sed -e '4q' $file | egrep "^# Generated by .*$PACKAGE") >/dev/null 2>&1; then
+ relink_command=
+ . $dir/$file
+
+ rmfiles="$rmfiles $objdir/$name $objdir/${name}S.${objext}"
+ if test "$fast_install" = yes && test -n "$relink_command"; then
+ rmfiles="$rmfiles $objdir/lt-$name"
+ fi
+ fi
+ ;;
+ esac
+ $show "$rm $rmfiles"
+ $run $rm $rmfiles || exit_status=1
+ done
+
+ # Try to remove the ${objdir}s in the directories where we deleted files
+ for dir in $rmdirs; do
+ if test -d "$dir"; then
+ $show "rmdir $dir"
+ $run rmdir $dir >/dev/null 2>&1
+ fi
+ done
+
+ exit $exit_status
+ ;;
+
+ "")
+ $echo "$modename: you must specify a MODE" 1>&2
+ $echo "$generic_help" 1>&2
+ exit 1
+ ;;
+ esac
+
+ if test -z "$exec_cmd"; then
+ $echo "$modename: invalid operation mode \`$mode'" 1>&2
+ $echo "$generic_help" 1>&2
+ exit 1
+ fi
+fi # test -z "$show_help"
+
+if test -n "$exec_cmd"; then
+ eval exec $exec_cmd
+ exit 1
+fi
+
+# We need to display help for each of the modes.
+case $mode in
+"") $echo \
+"Usage: $modename [OPTION]... [MODE-ARG]...
+
+Provide generalized library-building support services.
+
+ --config show all configuration variables
+ --debug enable verbose shell tracing
+-n, --dry-run display commands without modifying any files
+ --features display basic configuration information and exit
+ --finish same as \`--mode=finish'
+ --help display this help message and exit
+ --mode=MODE use operation mode MODE [default=inferred from MODE-ARGS]
+ --quiet same as \`--silent'
+ --silent don't print informational messages
+ --version print version information
+
+MODE must be one of the following:
+
+ clean remove files from the build directory
+ compile compile a source file into a libtool object
+ execute automatically set library path, then run a program
+ finish complete the installation of libtool libraries
+ install install libraries or executables
+ link create a library or an executable
+ uninstall remove libraries from an installed directory
+
+MODE-ARGS vary depending on the MODE. Try \`$modename --help --mode=MODE' for
+a more detailed description of MODE."
+ exit 0
+ ;;
+
+clean)
+ $echo \
+"Usage: $modename [OPTION]... --mode=clean RM [RM-OPTION]... FILE...
+
+Remove files from the build directory.
+
+RM is the name of the program to use to delete files associated with each FILE
+(typically \`/bin/rm'). RM-OPTIONS are options (such as \`-f') to be passed
+to RM.
+
+If FILE is a libtool library, object or program, all the files associated
+with it are deleted. Otherwise, only FILE itself is deleted using RM."
+ ;;
+
+compile)
+ $echo \
+"Usage: $modename [OPTION]... --mode=compile COMPILE-COMMAND... SOURCEFILE
+
+Compile a source file into a libtool library object.
+
+This mode accepts the following additional options:
+
+ -o OUTPUT-FILE set the output file name to OUTPUT-FILE
+ -prefer-pic try to building PIC objects only
+ -prefer-non-pic try to building non-PIC objects only
+ -static always build a \`.o' file suitable for static linking
+
+COMPILE-COMMAND is a command to be used in creating a \`standard' object file
+from the given SOURCEFILE.
+
+The output file name is determined by removing the directory component from
+SOURCEFILE, then substituting the C source code suffix \`.c' with the
+library object suffix, \`.lo'."
+ ;;
+
+execute)
+ $echo \
+"Usage: $modename [OPTION]... --mode=execute COMMAND [ARGS]...
+
+Automatically set library path, then run a program.
+
+This mode accepts the following additional options:
+
+ -dlopen FILE add the directory containing FILE to the library path
+
+This mode sets the library path environment variable according to \`-dlopen'
+flags.
+
+If any of the ARGS are libtool executable wrappers, then they are translated
+into their corresponding uninstalled binary, and any of their required library
+directories are added to the library path.
+
+Then, COMMAND is executed, with ARGS as arguments."
+ ;;
+
+finish)
+ $echo \
+"Usage: $modename [OPTION]... --mode=finish [LIBDIR]...
+
+Complete the installation of libtool libraries.
+
+Each LIBDIR is a directory that contains libtool libraries.
+
+The commands that this mode executes may require superuser privileges. Use
+the \`--dry-run' option if you just want to see what would be executed."
+ ;;
+
+install)
+ $echo \
+"Usage: $modename [OPTION]... --mode=install INSTALL-COMMAND...
+
+Install executables or libraries.
+
+INSTALL-COMMAND is the installation command. The first component should be
+either the \`install' or \`cp' program.
+
+The rest of the components are interpreted as arguments to that command (only
+BSD-compatible install options are recognized)."
+ ;;
+
+link)
+ $echo \
+"Usage: $modename [OPTION]... --mode=link LINK-COMMAND...
+
+Link object files or libraries together to form another library, or to
+create an executable program.
+
+LINK-COMMAND is a command using the C compiler that you would use to create
+a program from several object files.
+
+The following components of LINK-COMMAND are treated specially:
+
+ -all-static do not do any dynamic linking at all
+ -avoid-version do not add a version suffix if possible
+ -dlopen FILE \`-dlpreopen' FILE if it cannot be dlopened at runtime
+ -dlpreopen FILE link in FILE and add its symbols to lt_preloaded_symbols
+ -export-dynamic allow symbols from OUTPUT-FILE to be resolved with dlsym(3)
+ -export-symbols SYMFILE
+ try to export only the symbols listed in SYMFILE
+ -export-symbols-regex REGEX
+ try to export only the symbols matching REGEX
+ -LLIBDIR search LIBDIR for required installed libraries
+ -lNAME OUTPUT-FILE requires the installed library libNAME
+ -module build a library that can dlopened
+ -no-fast-install disable the fast-install mode
+ -no-install link a not-installable executable
+ -no-undefined declare that a library does not refer to external symbols
+ -o OUTPUT-FILE create OUTPUT-FILE from the specified objects
+ -release RELEASE specify package release information
+ -rpath LIBDIR the created library will eventually be installed in LIBDIR
+ -R[ ]LIBDIR add LIBDIR to the runtime path of programs and libraries
+ -static do not do any dynamic linking of libtool libraries
+ -version-info CURRENT[:REVISION[:AGE]]
+ specify library version info [each variable defaults to 0]
+
+All other options (arguments beginning with \`-') are ignored.
+
+Every other argument is treated as a filename. Files ending in \`.la' are
+treated as uninstalled libtool libraries, other files are standard or library
+object files.
+
+If the OUTPUT-FILE ends in \`.la', then a libtool library is created,
+only library objects (\`.lo' files) may be specified, and \`-rpath' is
+required, except when creating a convenience library.
+
+If OUTPUT-FILE ends in \`.a' or \`.lib', then a standard library is created
+using \`ar' and \`ranlib', or on Windows using \`lib'.
+
+If OUTPUT-FILE ends in \`.lo' or \`.${objext}', then a reloadable object file
+is created, otherwise an executable program is created."
+ ;;
+
+uninstall)
+ $echo \
+"Usage: $modename [OPTION]... --mode=uninstall RM [RM-OPTION]... FILE...
+
+Remove libraries from an installation directory.
+
+RM is the name of the program to use to delete files associated with each FILE
+(typically \`/bin/rm'). RM-OPTIONS are options (such as \`-f') to be passed
+to RM.
+
+If FILE is a libtool library, all the files associated with it are deleted.
+Otherwise, only FILE itself is deleted using RM."
+ ;;
+
+*)
+ $echo "$modename: invalid operation mode \`$mode'" 1>&2
+ $echo "$help" 1>&2
+ exit 1
+ ;;
+esac
+
+echo
+$echo "Try \`$modename --help' for more information about other modes."
+
+exit 0
+
+# Local Variables:
+# mode:shell-script
+# sh-indentation:2
+# End:
--- /dev/null
+#! /bin/sh
+
+## $Id: makedepend 5787 2002-09-29 23:32:52Z rra $
+##
+## Generate dependencies for INN makefiles
+##
+## This shell script automates the process of updating the dependencies in
+## INN's Makefiles. It uses gcc -MM to do this, since only the maintainers
+## should normally have to do this and using a compiler to parse include
+## directives is more reliable than more ad hoc methods. It takes compiler
+## flags as its first argument and then a list of all source files to
+## process.
+##
+## The Makefile is updated in place, and everything after "DO NOT DELETE
+## THIS LINE" is removed and replaced by the dependencies.
+
+flags="$1"
+shift
+sed '1,/DO NOT DELETE THIS LINE/!d' < Makefile > .makefile.tmp
+for source in "$@" ; do
+ case $source in
+ */*)
+ base=`echo "$source" | sed 's/\..*//'`
+ gcc -MM $flags "$source" | sed "s%^[^.: ][^.: ]*%$base%" \
+ >> .makefile.tmp
+ ;;
+ *)
+ gcc -MM $flags "$source" >> .makefile.tmp
+ ;;
+ esac
+ if [ $? != 0 ] ; then
+ rm .makefile.tmp
+ exit
+ fi
+done
+mv -f .makefile.tmp Makefile
--- /dev/null
+#! /usr/bin/perl -w
+
+## $Id: mkchangelog 7473 2005-12-24 21:29:22Z eagle $
+##
+## Generate a ChangeLog from svn log using svn2cl.
+##
+## This script prompts the user for a date from which to pull log entries
+## and the prefix to strip from file names and generates a ChangeLog file
+## by running svn2cl.
+
+$| = 1;
+
+print "Enter prefix to strip from file names: ";
+my $prefix = <STDIN>;
+chomp $prefix;
+
+print "Enter date to start log at (YYYY-MM-DD): ";
+my $date = <STDIN>;
+chomp $date;
+
+print "\nRunning svn2cl....\n";
+system ("svn2cl --strip-prefix=$prefix --group-by-day -r 'HEAD:{$date}'") == 0
+ or die "svn2cl exited with status " . ($? >> 8) . "\n";
--- /dev/null
+#! /usr/bin/perl -w
+
+## $Id: mkmanifest 7307 2005-06-11 08:38:15Z eagle $
+##
+## Generate a filename-only manifest from an INN tree.
+##
+## This script generates a filename-only manifest from an INN tree, excluding
+## certain files according to .cvsignore files and several built-in rules.
+## It is intended to be used to support make check-manifest from the top
+## level of the INN tree.
+
+require 5.005;
+
+use strict;
+use vars qw(%CVSIGNORE @FILES @IGNORE);
+
+use File::Find qw(find);
+
+# The following regex patterns match files to be ignored wherever they are
+# in the tree. This is intended to handle files that CVS ignores by default
+# or files that are present in the tree and in CVS but which are not included
+# in releases.
+@IGNORE = (qr%(\A|/)\.cvsignore\Z%, qr/\.[ao]\Z/, qr%(\A|/)CVS(/|\Z)%,
+ qr%(\A|/)\.?\#%, qr/\.(old|bak|orig|rej)$/, qr%(\A|/)core\Z%,
+ qr/~$/, qr%(\A|/)\.pure%, qr%(\A|/)\.svn(/|\Z)%);
+
+# Build a list of all the files ignored by rules in .cvsignore files. Meant
+# to be run as the wanted sub of a call to File::Find. Stuff in .cvsignore
+# that contains wildcards needs to be lifted into the list of @IGNORE regexes.
+sub find_cvsignore {
+ return unless $_ eq '.cvsignore';
+ return unless -f;
+ my $file = $_;
+ $file =~ s%^\./%%;
+ my @ignored;
+ my $dir = $File::Find::dir;
+ $dir =~ s%^\./?%%;
+ if ($dir) {
+ $dir .= '/';
+ }
+ open (CVSIGNORE, $_) or die "Cannot open $File::Find::name: $!\n";
+ @ignored = map { $dir . $_ } map { split (' ', $_) } <CVSIGNORE>;
+ close CVSIGNORE;
+ for (@ignored) {
+ if (/\*/) {
+ my $pattern = $_;
+ $pattern =~ s/\./\\./g;
+ $pattern =~ s/\*/.*/g;
+ push (@IGNORE, qr/\A$pattern\Z/);
+ } else {
+ $CVSIGNORE{$_}++;
+ }
+ }
+}
+
+# Build a list of all files in the tree that aren't ignored by .cvsignore
+# files or listed in ignore regexes.
+sub find_files {
+ return if $_ eq '.';
+ my $name = $File::Find::name;
+ $name =~ s%^./%%;
+ if ($CVSIGNORE{$name}) {
+ $File::Find::prune = 1;
+ return;
+ }
+ for my $pattern (@IGNORE) {
+ return if $name =~ /$pattern/;
+ }
+ push (@FILES, $name);
+}
+
+find (\&find_cvsignore, '.');
+find (\&find_files, '.');
+print join ("\n", (sort @FILES), '');
--- /dev/null
+#! /bin/sh
+
+## $Id: mksnapshot 7308 2005-06-11 08:39:54Z eagle $
+##
+## Build a snapshot of the current tree.
+##
+## Meant to be run on a fresh Subversion checkout, this script does the
+## necessary work to generate a snapshot. It expects to be invoked from the
+## top level of the source tree and leaves the generated snapshot in that
+## same directory as a .tar.gz file.
+##
+## Snapshot generation will fail if the tree will not compile or if make test
+## fails. In either case, the output is left in snapshot.log.
+##
+## This script takes one argument, a string representing what tree the
+## snapshot is being taken from. Generally this string is either CURRENT or
+## STABLE.
+
+set -e
+
+date=`date -u +%Y%m%d`
+tree="$1"
+if [ -z "$tree" ] ; then
+ echo "$0: no tree name specified" >&2
+ exit 1
+fi
+
+exec > snapshot.log 2>&1
+
+./configure
+make warnings
+make test
+make check-manifest
+
+cat > README.snapshot <<EOF
+This is a snapshot of the current development version of INN, pulled
+automatically from the Subversion repository. It was made on:
+
+ `date -u +"%B %e, %Y @ %I:%M %p %Z"`
+
+This code should be considered experimental. Only a default compile and
+automated testing is done before it is made available. If it breaks, we'd
+like to know at inn-bugs@isc.org, but if it causes your system to explode,
+don't blame us.
+
+If you are using this code, it's highly recommended that you be on the
+inn-workers@isc.org mailing list. See README for more information.
+EOF
+
+make snapshot SNAPSHOT="$tree" SNAPDATE="$date"
--- /dev/null
+#! /bin/sh
+
+## $Id: mksystem 6397 2003-07-12 19:14:58Z rra $
+##
+## Create include/inn/system.h from include/config.h.
+##
+## include/config.h is generated by autoconf and contains all of the test
+## results for a platform. Most of these are only used when building INN,
+## but some of them are needed for various definitions in the header files
+## for INN's libraries. We want to be able to install those header files
+## and their prerequisites, but we don't want to define the normal symbols
+## defined by autoconf since they're too likely to conflict with other
+## packages.
+##
+## This script takes the path to awk as its first argument and the path to
+## include/config.h as its second argument and generates a file suitable
+## for being included as <inn/system.h>. It contains only the autoconf
+## results needed for INN's API, and the symbols that might conflict with
+## autoconf results in other packages have INN_ prepended.
+
+cat <<EOF
+/* Automatically generated by mksystem from config.h; do not edit. */
+
+/* This header contains information obtained by INN at configure time that
+ is needed by INN headers. Autoconf results that may conflict with the
+ autoconf results of another package have INN_ prepended to the
+ preprocessor symbols. */
+
+#ifndef INN_SYSTEM_H
+#define INN_SYSTEM_H 1
+
+EOF
+
+$1 '
+
+/^#define HAVE_C99_VAMACROS/ { print save $1 " INN_" $2 " " $3 "\n" }
+/^#define HAVE_GNU_VAMACROS/ { print save $1 " INN_" $2 " " $3 "\n" }
+/^#define HAVE_INTTYPES_H/ { print save $1 " INN_" $2 " " $3 "\n" }
+/^#define HAVE_MSYNC_3_ARG/ { print save $1 " INN_" $2 " " $3 "\n" }
+/^#define HAVE_STDBOOL_H/ { print save $1 " INN_" $2 " " $3 "\n" }
+/^#define HAVE_SYS_BITTYPES_H/ { print save $1 " INN_" $2 " " $3 "\n" }
+
+{ save = $0 "\n" }' $2
+
+cat <<EOF
+#endif /* INN_SYSTEM_H */
+EOF
--- /dev/null
+#! /bin/sh
+
+## $Id: mkversion 7345 2005-07-02 05:04:05Z eagle $
+##
+## Create version.h from INN version information.
+##
+## Makefile.global contains the INN version number and extra version
+## information (if any). This is passed to use by lib/Makefile as our
+## first and second arguments. Print a header file suitable for use
+## as inn/version.h on stdout.
+
+version="$1"
+extra="$2"
+string="INN $version"
+if [ x"$extra" = x"prerelease" ] ; then
+ date=`date +%Y%m%d`
+ extra="$date $extra"
+fi
+if [ -n "$extra" ] ; then
+ string="$string ($extra)"
+fi
+major=`echo "$version" | cut -d. -f1`
+minor=`echo "$version" | cut -d. -f2`
+patch=`echo "$version" | cut -d. -f3`
+
+cat <<EOF
+/* Automatically generated by mkversion, edit Makefile.global to change. */
+
+#ifndef INN_VERSION_H
+#define INN_VERSION_H 1
+
+#define INN_VERSION_MAJOR $major
+#define INN_VERSION_MINOR $minor
+#define INN_VERSION_PATCH $patch
+#define INN_VERSION_EXTRA "$extra"
+#define INN_VERSION_STRING "$string"
+
+#endif /* INN_VERSION_H */
+EOF
--- /dev/null
+## $Id: Makefile 7494 2006-03-19 23:19:30Z eagle $
+
+include ../Makefile.global
+
+top = ..
+CFLAGS = $(GCFLAGS) -I.
+
+## The tests that need to be built. Tests in the form of shell scripts
+## or some other form that doesn't require compiling shouldn't be in this
+## list. If they need other things compiled, those other things should be
+## added to EXTRA.
+
+TESTS = lib/buffer.t lib/concat.t lib/confparse.t lib/date.t lib/hash.t \
+ lib/hashtab.t lib/hstrerror.t lib/inet_aton.t lib/inet_ntoa.t \
+ lib/innconf.t lib/list.t lib/md5.t lib/memcmp.t lib/messages.t \
+ lib/mkstemp.t lib/pread.t lib/pwrite.t lib/qio.t lib/snprintf.t \
+ lib/strerror.t lib/strlcat.t lib/strlcpy.t lib/tst.t lib/uwildmat.t \
+ lib/vector.t lib/wire.t lib/xwrite.t overview/tradindexed.t
+
+## Extra stuff that needs to be built before tests can be run.
+
+EXTRA = runtests lib/setenv.tr lib/xmalloc
+
+all check test: $(TESTS) $(EXTRA)
+ ./runtests TESTS
+
+build: $(TESTS) $(EXTRA)
+
+warnings:
+ $(MAKE) COPT='$(WARNINGS)' build
+
+clean clobber distclean:
+ rm -f *.o *.lo */*.o */*.lo .pure */.pure $(TESTS) $(EXTRA)
+ rm -rf .libs */.libs
+
+.c.o: $*.c
+ $(CC) $(CFLAGS) -c -o $@ $*.c
+
+LINK = $(LIBTOOL) $(CC) $(LDFLAGS) -o $@
+STORAGEDEPS = $(LIBSTORAGE) $(LIBHIST) $(LIBINN)
+STORAGELIBS = $(STORAGEDEPS) $(EXTSTORAGELIBS)
+
+runtests: runtests.o
+ $(LINK) runtests.o
+
+lib/buffer.t: lib/buffer-t.o libtest.o $(LIBINN)
+ $(LINK) lib/buffer-t.o libtest.o $(LIBINN)
+
+lib/concat.t: lib/concat-t.o libtest.o $(LIBINN)
+ $(LINK) lib/concat-t.o libtest.o $(LIBINN)
+
+lib/confparse.t: lib/confparse-t.o libtest.o $(LIBINN)
+ $(LINK) lib/confparse-t.o libtest.o $(LIBINN)
+
+lib/date.t: lib/date-t.o libtest.o $(LIBINN)
+ $(LINK) lib/date-t.o libtest.o $(LIBINN)
+
+lib/hash.t: lib/hash-t.o libtest.o $(LIBINN)
+ $(LINK) lib/hash-t.o libtest.o $(LIBINN)
+
+lib/hashtab.t: lib/hashtab-t.o libtest.o $(LIBINN)
+ $(LINK) lib/hashtab-t.o libtest.o $(LIBINN)
+
+lib/hstrerror.o: ../lib/hstrerror.c
+ $(CC) $(CFLAGS) -DTESTING -c -o $@ ../lib/hstrerror.c
+
+lib/hstrerror.t: lib/hstrerror.o lib/hstrerror-t.o libtest.o
+ $(LINK) lib/hstrerror.o lib/hstrerror-t.o libtest.o $(LIBINN)
+
+lib/inet_aton.o: ../lib/inet_aton.c
+ $(CC) $(CFLAGS) -DTESTING -c -o $@ ../lib/inet_aton.c
+
+lib/inet_aton.t: lib/inet_aton.o lib/inet_aton-t.o
+ $(LINK) lib/inet_aton.o lib/inet_aton-t.o
+
+lib/inet_ntoa.o: ../lib/inet_ntoa.c
+ $(CC) $(CFLAGS) -DTESTING -c -o $@ ../lib/inet_ntoa.c
+
+lib/inet_ntoa.t: lib/inet_ntoa.o lib/inet_ntoa-t.o libtest.o
+ $(LINK) lib/inet_ntoa.o lib/inet_ntoa-t.o libtest.o $(LIBINN)
+
+lib/innconf.t: lib/innconf-t.o libtest.o $(LIBINN)
+ $(LINK) lib/innconf-t.o libtest.o $(LIBINN) $(LIBS)
+
+lib/list.t: lib/list-t.o libtest.o $(LIBINN)
+ $(LINK) lib/list-t.o libtest.o $(LIBINN) $(LIBS)
+
+lib/md5.t: lib/md5-t.o libtest.o $(LIBINN)
+ $(LINK) lib/md5-t.o libtest.o $(LIBINN)
+
+lib/memcmp.o: ../lib/memcmp.c
+ $(CC) $(CFLAGS) -DTESTING -c -o $@ ../lib/memcmp.c
+
+lib/memcmp.t: lib/memcmp.o lib/memcmp-t.o libtest.o
+ $(LINK) lib/memcmp.o lib/memcmp-t.o libtest.o $(LIBINN)
+
+lib/messages.o: ../lib/messages.c
+ $(CC) $(CFLAGS) -DDEBUG -c -o $@ ../lib/messages.c
+
+lib/messages-t.o: lib/messages-t.c
+ $(CC) $(CFLAGS) -DDEBUG -c -o $@ lib/messages-t.c
+
+lib/messages.t: lib/messages.o lib/messages-t.o $(LIBINN)
+ $(LINK) lib/messages-t.o lib/messages.o $(LIBINN)
+
+lib/mkstemp.o: ../lib/mkstemp.c
+ $(CC) $(CFLAGS) -DTESTING -c -o $@ ../lib/mkstemp.c
+
+lib/mkstemp.t: lib/mkstemp.o lib/mkstemp-t.o libtest.o
+ $(LINK) lib/mkstemp.o lib/mkstemp-t.o libtest.o $(LIBINN)
+
+lib/pread.o: ../lib/pread.c
+ $(CC) $(CFLAGS) -DTESTING -c -o $@ ../lib/pread.c
+
+lib/pread.t: lib/pread.o lib/pread-t.o libtest.o $(LIBINN)
+ $(LINK) lib/pread.o lib/pread-t.o libtest.o $(LIBINN)
+
+lib/pwrite.o: ../lib/pwrite.c
+ $(CC) $(CFLAGS) -DTESTING -c -o $@ ../lib/pwrite.c
+
+lib/pwrite.t: lib/pwrite.o lib/pwrite-t.o libtest.o $(LIBINN)
+ $(LINK) lib/pwrite.o lib/pwrite-t.o libtest.o $(LIBINN)
+
+lib/qio.t: lib/qio-t.o libtest.o $(LIBINN)
+ $(LINK) lib/qio-t.o libtest.o $(LIBINN)
+
+lib/setenv.o: ../lib/setenv.c
+ $(CC) $(CFLAGS) -DTESTING -c -o $@ ../lib/setenv.c
+
+lib/setenv.tr: lib/setenv.o lib/setenv-t.o libtest.o $(LIBINN)
+ $(LINK) lib/setenv.o lib/setenv-t.o libtest.o $(LIBINN)
+
+lib/snprintf.o: ../lib/snprintf.c
+ $(CC) $(CFLAGS) -DTESTING -c -o $@ ../lib/snprintf.c
+
+lib/snprintf.t: lib/snprintf.o lib/snprintf-t.o libtest.o
+ $(LINK) lib/snprintf.o lib/snprintf-t.o libtest.o $(LIBINN)
+
+lib/strerror.o: ../lib/strerror.c
+ $(CC) $(CFLAGS) -DTESTING -c -o $@ ../lib/strerror.c
+
+lib/strerror.t: lib/strerror.o lib/strerror-t.o libtest.o
+ $(LINK) lib/strerror.o lib/strerror-t.o libtest.o $(LIBINN)
+
+lib/strlcat.o: ../lib/strlcat.c
+ $(CC) $(CFLAGS) -DTESTING -c -o $@ ../lib/strlcat.c
+
+lib/strlcat.t: lib/strlcat.o lib/strlcat-t.o libtest.o
+ $(LINK) lib/strlcat.o lib/strlcat-t.o libtest.o $(LIBINN)
+
+lib/strlcpy.o: ../lib/strlcpy.c
+ $(CC) $(CFLAGS) -DTESTING -c -o $@ ../lib/strlcpy.c
+
+lib/strlcpy.t: lib/strlcpy.o lib/strlcpy-t.o libtest.o
+ $(LINK) lib/strlcpy.o lib/strlcpy-t.o libtest.o $(LIBINN)
+
+lib/tst.t: lib/tst-t.o $(LIBINN)
+ $(LINK) lib/tst-t.o libtest.o $(LIBINN)
+
+lib/uwildmat.t: lib/uwildmat-t.o $(LIBINN)
+ $(LINK) lib/uwildmat-t.o $(LIBINN)
+
+lib/vector.t: lib/vector-t.o libtest.o $(LIBINN)
+ $(LINK) lib/vector-t.o libtest.o $(LIBINN)
+
+lib/wire.t: lib/wire-t.o libtest.o $(LIBINN)
+ $(LINK) lib/wire-t.o libtest.o $(LIBINN)
+
+lib/xmalloc: lib/xmalloc.o $(LIBINN)
+ $(LINK) lib/xmalloc.o $(LIBINN)
+
+lib/xwrite.o: ../lib/xwrite.c
+ $(CC) $(CFLAGS) -DTESTING -c -o $@ ../lib/xwrite.c
+
+lib/xwrite.t: lib/xwrite-t.o lib/xwrite.o lib/fakewrite.o $(LIBINN)
+ $(LINK) lib/xwrite-t.o lib/xwrite.o lib/fakewrite.o $(LIBINN)
+
+overview/tradindexed.t: overview/tradindexed-t.o libtest.o $(STORAGEDEPS)
+ $(LINK) overview/tradindexed-t.o libtest.o $(STORAGELIBS) $(LIBS)
--- /dev/null
+authprogs/ckpasswd
+authprogs/domain
+lib/buffer
+lib/concat
+lib/confparse
+lib/date
+lib/hash
+lib/hashtab
+lib/inet_aton
+lib/inet_ntoa
+lib/innconf
+lib/list
+lib/md5
+lib/memcmp
+lib/messages
+lib/mkstemp
+lib/pread
+lib/pwrite
+lib/qio
+lib/setenv
+lib/snprintf
+lib/strerror
+lib/strlcat
+lib/strlcpy
+lib/tst
+lib/uwildmat
+lib/vector
+lib/wire
+lib/xmalloc
+lib/xwrite
+overview/tradindexed
+util/convdate
--- /dev/null
+#! /bin/sh
+# $Id: ckpasswd.t 5572 2002-08-12 06:01:09Z rra $
+#
+# Test suite for ckpasswd.
+
+# The count starts at 1 and is updated each time ok is printed. printcount
+# takes "ok" or "not ok".
+count=1
+printcount () {
+ echo "$1 $count $2"
+ count=`expr $count + 1`
+}
+
+# Run ckpasswd, expecting it to succeed. Takes a username, a password, any
+# additional options, and the expected output string.
+runsuccess () {
+ output=`$ckpasswd -u "$1" -p "$2" $3 2>&1`
+ status=$?
+ if test $status = 0 && test x"$output" = x"$4" ; then
+ printcount "ok"
+ else
+ printcount "not ok"
+ echo " saw: $output"
+ echo " not: $4"
+ fi
+}
+
+# Run ckpasswd, feeding it the username and password on stdin in the same way
+# that nnrpd would. Takes a username, a password, any additional options, and
+# the expected output string.
+runpipe () {
+ output=`( echo ClientAuthname: $1 ; echo ClientPassword: $2 ) \
+ | $ckpasswd $3 2>&1`
+ status=$?
+ if test $status = 0 && test x"$output" = x"$4" ; then
+ printcount "ok"
+ else
+ printcount "not ok"
+ echo " saw: $output"
+ echo " not: $4"
+ fi
+}
+
+# Run ckpasswd, expecting it to fail, and make sure it fails with status 1 and
+# prints out the right error message. Takes a username, a password, any
+# additional options, and the expected output string.
+runfailure () {
+ output=`$ckpasswd -u "$1" -p "$2" $3 2>&1`
+ status=$?
+ if test $status = 1 && test x"$output" = x"$4" ; then
+ printcount "ok"
+ else
+ printcount "not ok"
+ echo " saw: $output"
+ echo " not: $4"
+ fi
+}
+
+# Make sure we're in the right directory.
+for dir in . authprogs tests/authprogs ; do
+ test -f "$dir/passwd" && cd $dir
+done
+ckpasswd=../../authprogs/ckpasswd
+
+# Print the test count.
+echo 7
+
+# First, run the tests that we expect to succeed.
+runsuccess "foo" "foopass" "-f passwd" "User:foo"
+runsuccess "bar" "barpass" "-f passwd" "User:bar"
+runsuccess "baz" "" "-f passwd" "User:baz"
+runpipe "foo" "foopass" "-f passwd" "User:foo"
+
+# Now, run the tests that we expect to fail.
+runfailure "foo" "barpass" "-f passwd" \
+ "ckpasswd: invalid password for user foo"
+runfailure "who" "foopass" "-f passwd" \
+ "ckpasswd: user who unknown"
+runfailure "" "foopass" "-f passwd" \
+ "ckpasswd: null username"
--- /dev/null
+#! /bin/sh
+# $Id: domain.t 5948 2002-12-08 04:20:46Z rra $
+#
+# Test suite for domain.
+
+# The count starts at 1 and is updated each time ok is printed. printcount
+# takes "ok" or "not ok".
+count=1
+printcount () {
+ echo "$1 $count $2"
+ count=`expr $count + 1`
+}
+
+# Run domain, expecting it to succeed. Feed it the client host the way that
+# nnrpd would. Takes the client host, the domain to check it against, and the
+# user expected.
+runsuccess () {
+ output=`( echo ClientHost: $1 ; echo ClientIP: 127.0.0.1 ; \
+ echo ClientPort: 0 ; echo LocalIP: 127.0.0.1 ; \
+ echo LocalPort: 119) | $domain $2 2>&1`
+ status=$?
+ if test $status = 0 && test x"$output" = x"$3" ; then
+ printcount "ok"
+ else
+ printcount "not ok"
+ echo " saw: $output"
+ echo " not: $3"
+ fi
+}
+
+# Run domain, expecting it to fail, and make sure it fails with status 1 and
+# prints out the right error message. Takes the client host, the domain to
+# check it against, and the expected output string.
+runfailure () {
+ output=`( echo ClientHost: $1 ; echo ClientIP: 127.0.0.1 ; \
+ echo ClientPort: 0 ; echo LocalIP: 127.0.0.1 ; \
+ echo LocalPort: 119) | $domain $2 2>&1`
+ status=$?
+ if test $status = 1 && test x"$output" = x"$3" ; then
+ printcount "ok"
+ else
+ printcount "not ok"
+ echo " saw: $output"
+ echo " not: $3"
+ fi
+}
+
+# Make sure we're in the right directory.
+domain=domain
+for dir in authprogs ../authprogs ../../authprogs ; do
+ test -x "$dir/domain" && domain="$dir/domain"
+done
+
+# Print the test count.
+echo 8
+
+# First, run the tests that we expect to succeed.
+runsuccess "foo.example.com" ".example.com" "User:foo"
+runsuccess "foo.example.com" "example.com" "User:foo"
+runsuccess "foo.bar.example.com" ".example.com" "User:foo.bar"
+runsuccess "foo.bar.example.com" "example.com" "User:foo.bar"
+runsuccess "foo.example.com" "com" "User:foo.example"
+
+# Now, run the tests that we expect to fail.
+runfailure "example.com" "example.com" \
+ "domain: host example.com matches the domain exactly"
+runfailure "foo.example.com" "example.net" \
+ "domain: host foo.example.com didn't match domain example.net"
+runfailure "fooexample.com" "example.com" \
+ "domain: host fooexample.com didn't match domain example.com"
--- /dev/null
+# This is a comment.
+foo:bZukvB.43WoX.:5:5:Foo Bar:/home/foo:/bin/sh
+
+# This is another comment.
+
+This is just some random text that someone put in this file for no good
+reason.
+
+bar:9oV.zhEh2nexE
+baz:qq2C4zterbO2k::::::::::::::::::::::
--- /dev/null
+Path: foo!bar\r
+From: example@example.com\r
+Subject: Test\r
+Date: Mon, 23 Dec 2002 16:00:04 -0700\r
+Message-ID: <id1@example.com>\r
--- /dev/null
+Path: foo!bar\r
+From: example@example.com\r
+Subject: Test\r
+Message-ID: <id1@example.com>\r
+Date: Mon, 23 Dec 2002 16:00:04
\ No newline at end of file
--- /dev/null
+/* $Id: buffer-t.c 5469 2002-05-20 12:50:57Z alexk $ */
+/* buffer test suite. */
+
+#include "config.h"
+#include "clibrary.h"
+
+#include "inn/buffer.h"
+#include "libtest.h"
+
+static const char test_string1[] = "This is a test";
+static const char test_string2[] = " of the buffer system";
+static const char test_string3[] = "This is a test\0 of the buffer system";
+
+int
+main(void)
+{
+ struct buffer one = { 0, 0, 0, NULL };
+ struct buffer two = { 0, 0, 0, NULL };
+ struct buffer *three;
+
+ puts("26");
+
+ buffer_set(&one, test_string1, sizeof(test_string1));
+ ok_int(1, 1024, one.size);
+ ok_int(2, 0, one.used);
+ ok_int(3, sizeof(test_string1), one.left);
+ ok_string(4, test_string1, one.data);
+ buffer_append(&one, test_string2, sizeof(test_string2));
+ ok_int(5, 1024, one.size);
+ ok_int(6, 0, one.used);
+ ok_int(7, sizeof(test_string3), one.left);
+ ok(8, memcmp(one.data, test_string3, sizeof(test_string3)) == 0);
+ one.left -= sizeof(test_string1);
+ one.used += sizeof(test_string1);
+ buffer_append(&one, test_string1, sizeof(test_string1));
+ ok_int(9, 1024, one.size);
+ ok_int(10, sizeof(test_string1), one.used);
+ ok_int(11, sizeof(test_string3), one.left);
+ ok(12,
+ memcmp(one.data + one.used, test_string2, sizeof(test_string2)) == 0);
+ ok(13,
+ memcmp(one.data + one.used + sizeof(test_string2), test_string1,
+ sizeof(test_string1)) == 0);
+ buffer_set(&one, test_string1, sizeof(test_string1));
+ buffer_set(&two, test_string2, sizeof(test_string2));
+ buffer_swap(&one, &two);
+ ok_int(14, 1024, one.size);
+ ok_int(15, 0, one.used);
+ ok_int(16, sizeof(test_string2), one.left);
+ ok_string(17, test_string2, one.data);
+ ok_int(18, 1024, two.size);
+ ok_int(19, 0, two.used);
+ ok_int(20, sizeof(test_string1), two.left);
+ ok_string(21, test_string1, two.data);
+
+ three = buffer_new();
+ ok(22, three != NULL);
+ ok_int(23, 0, three->size);
+ buffer_set(three, test_string1, sizeof(test_string1));
+ ok_int(24, 1024, three->size);
+ buffer_resize(three, 512);
+ ok_int(25, 1024, three->size);
+ buffer_resize(three, 1025);
+ ok_int(26, 2048, three->size);
+ free(three->data);
+ free(three);
+
+ return 0;
+}
--- /dev/null
+/* $Id: concat-t.c 5054 2001-12-12 09:15:24Z rra $ */
+/* concat test suite. */
+
+#include <stdio.h>
+#include <string.h>
+
+#include "libinn.h"
+#include "libtest.h"
+
+#define END (char *) 0
+
+int
+main(void)
+{
+ printf("11\n");
+
+ ok_string( 1, "a", concat("a", END));
+ ok_string( 2, "ab", concat("a", "b", END));
+ ok_string( 3, "ab", concat("ab", "", END));
+ ok_string( 4, "ab", concat("", "ab", END));
+ ok_string( 5, "", concat("", END));
+ ok_string( 6, "abcde", concat("ab", "c", "", "de", END));
+ ok_string( 7, "abcde", concat("abc", "de", END, "f", END));
+
+ ok_string( 8, "/foo", concatpath("/bar", "/foo"));
+ ok_string( 9, "/foo/bar", concatpath("/foo", "bar"));
+ ok_string(10, "./bar", concatpath("/foo", "./bar"));
+ ok_string(11, "/bar/baz/foo/bar", concatpath("/bar/baz", "foo/bar"));
+
+ return 0;
+}
--- /dev/null
+foo bar
+===
+config/tmp:1: parse error: saw string, expecting parameter
+===
+parameter: value with spaces
+===
+config/tmp:1: parse error: saw string, expecting semicolon or newline
+===
+parameter: escape\tvalue
+===
+config/tmp:1: invalid character '\' in unquoted string
+===
+parameter:: value
+===
+config/tmp:1: invalid character ':' in unquoted string
+===
+parameter: "value
+===
+config/tmp:1: no close quote seen for quoted string
+===
+parameter: value ; value
+===
+config/tmp:1: parse error: saw string, expecting parameter
+===
+parameter: # value
+value
+===
+config/tmp:1: parse error: saw string, expecting semicolon or newline
+===
+"foo bar"
+===
+config/tmp:1: parse error: saw quoted string, expecting parameter
+===
+first: second
+third: fourth
+parameter: "value \
+===
+config/tmp:4: end of file encountered while parsing quoted string
+===
+parameter:value
+===
+config/tmp:1: invalid character ':' in unquoted string
+===
+parameter: value # this is a comment
+===
+config/tmp:1: parse error: saw string, expecting semicolon or newline
+===
+parameter: "value
+value"
+===
+config/tmp:1: no close quote seen for quoted string
+===
--- /dev/null
+# This is a leading comment.
+param1: on
+param2: true
+param3: yes
+# this is a comment \
+param4: off
+ # this is another
+int1: 0
+
+int2: -3
--- /dev/null
+# Test a lack of newline at the end of the configuration file.
+parameter: value
\ No newline at end of file
--- /dev/null
+foo: baz
+bar: baz
--- /dev/null
+# This is a leading comment.
+param1: on
+
+ param2: true
+
+
+param3: yes
+# this is a comment \
+param4: off
+ # this is another
+# comment
+ # on several lines
+ param5: false ; param6: no
+
+int1: 0 ; int2: -3
+ # int3: 5000
+ int4: 5000
+int5: 2147483647
+int6: -2147483648
+
+string1: foo; string2: bar
+string3: "this is a test"
+string4: "this is \
+a test"
+string5: "this is \a\b\f\n\r\t\v a test \' of \" escapes \?\\"
+string6: "\
+# this is not a comment\
+"; string7: "lost \
+\nyet?"
--- /dev/null
+parameter: "yes"
+===
+config/tmp:1: parameter is not a boolean
+===
+parameter: False
+===
+config/tmp:1: parameter is not a boolean
+===
+foo: bar
+parameter: 0
+===
+config/tmp:2: parameter is not a boolean
+===
--- /dev/null
+parameter: "foo"
+===
+config/tmp:1: parameter is not an integer
+===
+# Check that line numbers are right.
+key: value; parameter: foobar
+===
+config/tmp:2: parameter is not an integer
+===
+parameter: 999999999999999999999999999999999999999999999999999999999999
+===
+config/tmp:1: parameter doesn't convert to an integer
+===
--- /dev/null
+# Make sure line numbering copes with a leading comment.
+parameter: value
+parameter: value
+===
+config/tmp:3: duplicate parameter parameter
+===
+# Line numbering and Macs.
+parameter: value
+parameter: value
+===
+config/tmp:3: duplicate parameter parameter
+===
+# Line numbering and Windows.
+parameter: value
+parameter: value
+===
+config/tmp:3: duplicate parameter parameter
+===
--- /dev/null
+/* $Id: confparse-t.c 5955 2002-12-08 09:28:32Z rra $ */
+/* confparse test suite. */
+
+#include "config.h"
+#include "clibrary.h"
+#include <unistd.h>
+
+#include "inn/confparse.h"
+#include "inn/messages.h"
+#include "inn/vector.h"
+#include "libinn.h"
+#include "libtest.h"
+
+/* Given a FILE *, read from that file, putting the results into a newly
+ allocated buffer, until encountering a line consisting solely of "===".
+ Returns the buffer, NULL on end of file, dies on error. */
+static char *
+read_section(FILE *file)
+{
+ char buf[1024] = "";
+ char *data = NULL;
+ char *status;
+
+ status = fgets(buf, sizeof(buf), file);
+ if (status == NULL)
+ return false;
+ while (1) {
+ if (status == NULL)
+ die("Unexpected end of file while reading tests");
+ if (strcmp(buf, "===\n") == 0)
+ break;
+ if (data == NULL) {
+ data = xstrdup(buf);
+ } else {
+ char *new_data;
+
+ new_data = concat(data, buf, (char *) 0);
+ free(data);
+ data = new_data;
+ }
+ status = fgets(buf, sizeof(buf), file);
+ }
+ return data;
+}
+
+/* Read from the given file a configuration file and write it out to
+ config/tmp. Returns true on success, false on end of file, and dies on
+ any error. */
+static bool
+write_test_config(FILE *file)
+{
+ FILE *tmp;
+ char *config;
+
+ config = read_section(file);
+ if (config == NULL)
+ return false;
+ tmp = fopen("config/tmp", "w");
+ if (tmp == NULL)
+ sysdie("Cannot create config/tmp");
+ if (fputs(config, tmp) == EOF)
+ sysdie("Write error while writing to config/tmp");
+ fclose(tmp);
+ free(config);
+ return true;
+}
+
+/* Parse a given config file with errors, setting the appropriate error
+ handler for the duration of the parse to save errors into the errors
+ global. Returns the resulting config_group. */
+static struct config_group *
+parse_error_config(const char *filename)
+{
+ struct config_group *group;
+
+ errors_capture();
+ group = config_parse_file(filename);
+ errors_uncapture();
+ return group;
+}
+
+/* Read in a configuration file from the provided FILE *, write it to disk,
+ parse the temporary config file, and return the resulting config_group in
+ the pointer passed as the second parameter. Returns true on success,
+ false on end of file. */
+static bool
+parse_test_config(FILE *file, struct config_group **group)
+{
+ if (!write_test_config(file))
+ return false;
+ *group = parse_error_config("config/tmp");
+ unlink("config/tmp");
+ return true;
+}
+
+/* Test the error test cases in config/errors, ensuring that they all fail
+ to parse and match the expected error messages. Takes the current test
+ count and returns the new test count. */
+static int
+test_errors(int n)
+{
+ FILE *errfile;
+ char *expected;
+ struct config_group *group;
+
+ errfile = fopen("config/errors", "r");
+ if (errfile == NULL)
+ sysdie("Cannot open config/errors");
+ while (parse_test_config(errfile, &group)) {
+ expected = read_section(errfile);
+ if (expected == NULL)
+ die("Unexpected end of file while reading error tests");
+ ok(n++, group == NULL);
+ ok_string(n++, expected, errors);
+ free(expected);
+ }
+ fclose(errfile);
+ return n;
+}
+
+/* Test the warning test cases in config/warningss, ensuring that they all
+ parse successfully and match the expected error messages. Takes the
+ current test count and returns the new test count. */
+static int
+test_warnings(int n)
+{
+ FILE *warnfile;
+ char *expected;
+ struct config_group *group;
+
+ warnfile = fopen("config/warnings", "r");
+ if (warnfile == NULL)
+ sysdie("Cannot open config/warnings");
+ while (parse_test_config(warnfile, &group)) {
+ expected = read_section(warnfile);
+ if (expected == NULL)
+ die("Unexpected end of file while reading error tests");
+ ok(n++, group != NULL);
+ ok_string(n++, expected, errors);
+ free(expected);
+ }
+ fclose(warnfile);
+ return n;
+}
+
+/* Test the warning test cases in config/warn-bool, ensuring that they all
+ parse successfully and produce the expected error messages when retrieved
+ as bools. Takes the current test count and returns the new test count. */
+static int
+test_warnings_bool(int n)
+{
+ FILE *warnfile;
+ char *expected;
+ struct config_group *group;
+ bool b_value = false;
+
+ warnfile = fopen("config/warn-bool", "r");
+ if (warnfile == NULL)
+ sysdie("Cannot open config/warn-bool");
+ while (parse_test_config(warnfile, &group)) {
+ expected = read_section(warnfile);
+ if (expected == NULL)
+ die("Unexpected end of file while reading error tests");
+ ok(n++, group != NULL);
+ ok(n++, errors == NULL);
+ errors_capture();
+ ok(n++, !config_param_boolean(group, "parameter", &b_value));
+ ok_string(n++, expected, errors);
+ errors_uncapture();
+ free(expected);
+ }
+ fclose(warnfile);
+ return n;
+}
+
+/* Test the warning test cases in config/warn-int, ensuring that they all
+ parse successfully and produce the expected error messages when retrieved
+ as bools. Takes the current test count and returns the new test count. */
+static int
+test_warnings_int(int n)
+{
+ FILE *warnfile;
+ char *expected;
+ struct config_group *group;
+ long l_value = 1;
+
+ warnfile = fopen("config/warn-int", "r");
+ if (warnfile == NULL)
+ sysdie("Cannot open config/warn-int");
+ while (parse_test_config(warnfile, &group)) {
+ expected = read_section(warnfile);
+ if (expected == NULL)
+ die("Unexpected end of file while reading error tests");
+ ok(n++, group != NULL);
+ ok(n++, errors == NULL);
+ errors_capture();
+ ok(n++, !config_param_integer(group, "parameter", &l_value));
+ ok_string(n++, expected, errors);
+ errors_uncapture();
+ free(expected);
+ }
+ fclose(warnfile);
+ return n;
+}
+
+int
+main(void)
+{
+ struct config_group *group;
+ bool b_value = false;
+ long l_value = 1;
+ const char *s_value;
+ struct vector *v_value;
+ char *long_param, *long_value;
+ size_t length;
+ int n;
+ FILE *tmpconfig;
+
+ puts("125");
+
+ if (access("config/valid", F_OK) < 0)
+ if (access("lib/config/valid", F_OK) == 0)
+ chdir("lib");
+ group = config_parse_file("config/valid");
+ ok(1, group != NULL);
+ if (group == NULL)
+ exit(1);
+
+ /* Booleans. */
+ ok(2, config_param_boolean(group, "param1", &b_value));
+ ok(3, b_value);
+ b_value = false;
+ ok(4, config_param_boolean(group, "param2", &b_value));
+ ok(5, b_value);
+ b_value = false;
+ ok(6, config_param_boolean(group, "param3", &b_value));
+ ok(7, b_value);
+ ok(8, config_param_boolean(group, "param4", &b_value));
+ ok(9, !b_value);
+ b_value = true;
+ ok(10, config_param_boolean(group, "param5", &b_value));
+ ok(11, !b_value);
+ b_value = true;
+ ok(12, config_param_boolean(group, "param6", &b_value));
+ ok(13, !b_value);
+
+ /* Integers. */
+ ok(14, config_param_integer(group, "int1", &l_value));
+ ok(15, l_value == 0);
+ ok(16, config_param_integer(group, "int2", &l_value));
+ ok(17, l_value == -3);
+ ok(18, !config_param_integer(group, "int3", &l_value));
+ ok(19, l_value == -3);
+ ok(20, config_param_integer(group, "int4", &l_value));
+ ok(21, l_value == 5000);
+ ok(22, config_param_integer(group, "int5", &l_value));
+ ok(23, l_value == 2147483647L);
+ ok(24, config_param_integer(group, "int6", &l_value));
+ ok(25, l_value == (-2147483647L - 1));
+
+ /* Strings. */
+ ok(26, config_param_string(group, "string1", &s_value));
+ ok_string(27, "foo", s_value);
+ ok(28, config_param_string(group, "string2", &s_value));
+ ok_string(29, "bar", s_value);
+ ok(30, config_param_string(group, "string3", &s_value));
+ ok_string(31, "this is a test", s_value);
+ ok(32, config_param_string(group, "string4", &s_value));
+ ok_string(33, "this is a test", s_value);
+ ok(34, config_param_string(group, "string5", &s_value));
+ ok_string(35, "this is \a\b\f\n\r\t\v a test \' of \" escapes \?\\",
+ s_value);
+ ok(36, config_param_string(group, "string6", &s_value));
+ ok_string(37, "# this is not a comment", s_value);
+ ok(38, config_param_string(group, "string7", &s_value));
+ ok_string(39, "lost \nyet?", s_value);
+
+ config_free(group);
+
+ /* Missing newline. */
+ group = config_parse_file("config/no-newline");
+ ok(40, group != NULL);
+ if (group == NULL) {
+ ok(41, false);
+ ok(42, false);
+ } else {
+ ok(41, config_param_string(group, "parameter", &s_value));
+ ok_string(42, "value", s_value);
+ config_free(group);
+ }
+
+ /* Extremely long parameter and value. */
+ tmpconfig = fopen("config/tmp", "w");
+ if (tmpconfig == NULL)
+ sysdie("cannot create config/tmp");
+ long_param = xcalloc(20001, 1);
+ memset(long_param, 'a', 20000);
+ long_value = xcalloc(64 * 1024 + 1, 1);
+ memset(long_value, 'b', 64 * 1024);
+ fprintf(tmpconfig, "%s: \"%s\"; two: %s", long_param, long_value,
+ long_value);
+ fclose(tmpconfig);
+ group = config_parse_file("config/tmp");
+ ok(43, group != NULL);
+ if (group == NULL) {
+ ok(44, false);
+ ok(45, false);
+ ok(46, false);
+ ok(47, false);
+ } else {
+ ok(44, config_param_string(group, long_param, &s_value));
+ ok_string(45, long_value, s_value);
+ ok(46, config_param_string(group, "two", &s_value));
+ ok_string(47, long_value, s_value);
+ config_free(group);
+ }
+ unlink("config/tmp");
+ free(long_param);
+ free(long_value);
+
+ /* Parsing problems exactly on the boundary of a buffer. This test
+ catches a bug in the parser that caused it to miss the colon at the end
+ of a parameter because the colon was the first character read in a new
+ read of the file buffer. */
+ tmpconfig = fopen("config/tmp", "w");
+ if (tmpconfig == NULL)
+ sysdie("cannot create config/tmp");
+ length = 16 * 1024 - strlen(": baz\nfoo:");
+ long_param = xcalloc(length + 1, 1);
+ memset(long_param, 'c', length);
+ fprintf(tmpconfig, "%s: baz\nfoo: bar\n", long_param);
+ fclose(tmpconfig);
+ group = config_parse_file("config/tmp");
+ ok(48, group != NULL);
+ if (group == NULL) {
+ ok(49, false);
+ ok(50, false);
+ ok(51, false);
+ ok(52, false);
+ } else {
+ ok(49, config_param_string(group, long_param, &s_value));
+ ok_string(50, "baz", s_value);
+ ok(51, config_param_string(group, "foo", &s_value));
+ ok_string(52, "bar", s_value);
+ config_free(group);
+ }
+ unlink("config/tmp");
+ free(long_param);
+
+ /* Alternate line endings. */
+ group = config_parse_file("config/line-endings");
+ ok(53, group != NULL);
+ if (group == NULL)
+ exit(1);
+ ok(54, config_param_boolean(group, "param1", &b_value));
+ ok(55, b_value);
+ b_value = false;
+ ok(56, config_param_boolean(group, "param2", &b_value));
+ ok(57, b_value);
+ b_value = false;
+ ok(58, config_param_boolean(group, "param3", &b_value));
+ ok(59, b_value);
+ ok(60, config_param_boolean(group, "param4", &b_value));
+ ok(61, !b_value);
+ ok(62, config_param_integer(group, "int1", &l_value));
+ ok(63, l_value == 0);
+ ok(64, config_param_integer(group, "int2", &l_value));
+ ok(65, l_value == -3);
+ config_free(group);
+
+ /* Listing parameters. */
+ group = config_parse_file("config/simple");
+ ok(66, group != NULL);
+ if (group == NULL)
+ exit(1);
+ v_value = config_params(group);
+ ok_int(67, 2, v_value->count);
+ ok_int(68, 2, v_value->allocated);
+ if (strcmp(v_value->strings[0], "foo") == 0)
+ ok_string(69, "bar", v_value->strings[1]);
+ else if (strcmp(v_value->strings[0], "bar") == 0)
+ ok_string(69, "foo", v_value->strings[1]);
+ else
+ ok(69, false);
+ vector_free(v_value);
+ config_free(group);
+
+ /* Errors. */
+ group = parse_error_config("config/null");
+ ok(70, group == NULL);
+ ok_string(71, "config/null: invalid NUL character found in file\n",
+ errors);
+ n = test_errors(72);
+ n = test_warnings(n);
+ n = test_warnings_bool(n);
+ n = test_warnings_int(n);
+
+ return 0;
+}
--- /dev/null
+/* $Id: date-t.c 7495 2006-03-19 23:21:38Z eagle $ */
+/* makedate test suite */
+
+#include "config.h"
+#include "clibrary.h"
+#include <time.h>
+
+#include "libinn.h"
+#include "libtest.h"
+
+static const time_t test_times[] = {
+ 28800UL, /* Thu, 1 Jan 1970 00:00:00 -0800 (PST) */
+ 362762400UL, /* Tue, 30 Jun 1981 15:20:00 +0000 (UTC) */
+ 396977449UL, /* Sat, 31 Jul 1982 15:30:49 +0000 (UTC) */
+ 825597049UL, /* Thu, 29 Feb 1996 12:30:49 +0000 (UTC) */
+ 850435199UL, /* Thu, 12 Dec 1996 23:59:59 +0000 (UTC) */
+ 852101999UL, /* Wed, 1 Jan 1997 06:59:59 +0000 (UTC) */
+ 934288249UL, /* Tue, 10 Aug 1999 12:30:49 +0000 (UTC) */
+ 946684800UL, /* Sat, 1 Jan 2000 00:00:00 +0000 (UTC) */
+ 946713599UL, /* Fri, 31 Dec 1999 23:59:59 -0800 (PST) */
+ 946713600UL, /* Sat, 1 Jan 2000 00:00:00 -0800 (PST) */
+ 951827449UL, /* Tue, 29 Feb 2000 12:30:49 +0000 (UTC) */
+ 954669599UL, /* Sun, 2 Apr 2000 01:59:59 -0800 (PST) */
+ 954669600UL, /* Sun, 2 Apr 2000 03:00:00 -0700 (PDT) */
+ 967707668UL, /* Thu, 31 Aug 2000 07:41:08 +0000 (UTC) */
+ 972813600UL /* Sun, 29 Oct 2000 02:00:00 -0800 (PST) */
+};
+
+static void
+ok_time(int n, time_t right, const char *date, const char *hour, bool local)
+{
+ time_t seen;
+
+ seen = parsedate_nntp(date, hour, local);
+ if (right == seen)
+ printf("ok %d\n", n);
+ else
+ printf("not ok %d\n wanted %lu seen %lu\n %s %s %d\n", n,
+ (unsigned long) right, (unsigned long) seen, date, hour,
+ local);
+}
+
+static void
+check_nntp(int *n, time_t timestamp)
+{
+ char date[9], hour[7];
+ struct tm *tmp_tm, tm;
+
+ tmp_tm = localtime(×tamp);
+ tm = *tmp_tm;
+ sprintf(date, "%02d%02d%02d", tm.tm_year % 100, tm.tm_mon + 1,
+ tm.tm_mday);
+ sprintf(hour, "%02d%02d%02d", tm.tm_hour, tm.tm_min, tm.tm_sec);
+ ok_time((*n)++, timestamp, date, hour, true);
+ sprintf(date, "%04d%02d%02d", tm.tm_year + 1900, tm.tm_mon + 1,
+ tm.tm_mday);
+ ok_time((*n)++, timestamp, date, hour, true);
+ tmp_tm = gmtime(×tamp);
+ tm = *tmp_tm;
+ sprintf(date, "%04d%02d%02d", tm.tm_year + 1900, tm.tm_mon + 1,
+ tm.tm_mday);
+ sprintf(hour, "%02d%02d%02d", tm.tm_hour, tm.tm_min, tm.tm_sec);
+ ok_time((*n)++, timestamp, date, hour, false);
+}
+
+int
+main(void)
+{
+ char buff[64] = "";
+ bool status;
+ time_t now, result;
+ double diff = 0;
+ int n;
+ unsigned int i;
+
+ char PST8PDT[] = "TZ=PST8PDT";
+ char Newfoundland[] = "TZ=Canada/Newfoundland";
+
+ printf("%d\n", 44 + ARRAY_SIZE(test_times) * 3 + 3);
+
+ now = time(NULL);
+ status = makedate(-1, false, buff, sizeof(buff));
+ if (status) {
+ result = parsedate(buff, NULL);
+ diff = difftime(result, now);
+ }
+ ok(1, status && diff >= 0 && diff < 10);
+ now = time(NULL);
+ status = makedate(-1, true, buff, sizeof(buff));
+ if (status) {
+ result = parsedate(buff, NULL);
+ diff = difftime(result, now);
+ }
+ ok(2, status && diff >= 0 && diff < 10);
+
+ putenv(PST8PDT);
+ tzset();
+
+ status = makedate(100000000UL, false, buff, sizeof(buff));
+ ok(3, status);
+ ok_string(4, "Sat, 3 Mar 1973 09:46:40 +0000 (UTC)", buff);
+ status = makedate(100000000UL, true, buff, sizeof(buff));
+ ok(5, status);
+ ok_string(6, "Sat, 3 Mar 1973 01:46:40 -0800 (PST)", buff);
+ status = makedate(300000000UL, false, buff, sizeof(buff));
+ ok(7, status);
+ ok_string(8, "Thu, 5 Jul 1979 05:20:00 +0000 (UTC)", buff);
+ status = makedate(300000000UL, true, buff, sizeof(buff));
+ ok(9, status);
+ ok_string(10, "Wed, 4 Jul 1979 22:20:00 -0700 (PDT)", buff);
+
+ status = makedate(300000000UL, false, buff, 31);
+ ok(11, !status);
+ status = makedate(300000000UL, false, buff, 32);
+ ok(12, status);
+ ok_string(13, "Thu, 5 Jul 1979 05:20:00 +0000", buff);
+ status = makedate(300000000UL, true, buff, 32);
+ ok(14, status);
+ ok_string(15, "Wed, 4 Jul 1979 22:20:00 -0700", buff);
+
+ putenv(Newfoundland);
+ tzset();
+
+ status = makedate(900000045UL, true, buff, sizeof(buff));
+ ok(16, status);
+ if (memcmp(buff, "Thu, 9 Jul 1998 16:00:45 +0000", 30) == 0)
+ printf("ok 17 # skip - Newfoundland time zone not installed\n");
+ else
+ ok_string(17, "Thu, 9 Jul 1998 13:30:45 -0230 (NDT)", buff);
+
+ putenv(PST8PDT);
+ tzset();
+
+ ok_time(18, (time_t) -1, "20000132", "000000", false);
+ ok_time(19, (time_t) -1, "20000132", "000000", true);
+ ok_time(20, (time_t) -1, "20000230", "000000", false);
+ ok_time(21, (time_t) -1, "20000230", "000000", true);
+ ok_time(22, (time_t) -1, "19990229", "000000", false);
+ ok_time(23, (time_t) -1, "19990229", "000000", true);
+ ok_time(24, (time_t) -1, "19990020", "000000", false);
+ ok_time(25, (time_t) -1, "19990120", "240000", false);
+ ok_time(26, (time_t) -1, "19990120", "146000", false);
+ ok_time(27, (time_t) -1, "19990120", "145961", false);
+ ok_time(28, (time_t) -1, "691231", "235959", false);
+ ok_time(29, (time_t) -1, "19691231", "235959", false);
+ ok_time(30, (time_t) -1, "19700100", "000000", false);
+ ok_time(31, 0, "19700101", "000000", false);
+ ok_time(32, 0, "700101", "000000", false);
+ ok_time(33, (time_t) -1, "2000010101", "000000", false);
+ ok_time(34, (time_t) -1, "00101", "000000", false);
+ ok_time(35, (time_t) -1, "20000101", "11111", false);
+ ok_time(36, (time_t) -1, "20000101", "1111111", false);
+ ok_time(37, (time_t) -1, "200001a1", "000000", false);
+ ok_time(38, (time_t) -1, "20000101", "00a000", false);
+
+ /* Times around the fall daylight savings change are ambiguous; accept
+ either of the possible interpretations, but make sure we get one or
+ the other. */
+ result = parsedate_nntp("20001029", "010000", true);
+ ok(39, result == 972806400UL || result == 972810000UL);
+ result = parsedate_nntp("001029", "013000", true);
+ ok(40, result == 972808200UL || result == 972811800UL);
+ result = parsedate_nntp("20001029", "013000", true);
+ ok(41, result == 972808200UL || result == 972811800UL);
+ result = parsedate_nntp("001029", "013000", true);
+ ok(42, result == 972808200UL || result == 972811800UL);
+ result = parsedate_nntp("20001029", "015959", true);
+ ok(43, result == 972809999UL || result == 972813599UL);
+ result = parsedate_nntp("001029", "015959", true);
+ ok(44, result == 972809999UL || result == 972813599UL);
+
+ n = 45;
+ for (i = 0; i < ARRAY_SIZE(test_times); i++)
+ check_nntp(&n, test_times[i]);
+ check_nntp(&n, time(NULL));
+
+ return 0;
+}
--- /dev/null
+/* $Id: fakewrite.c 5417 2002-04-15 08:40:20Z rra $ */
+/* Fake write and writev functions for testing xwrite and xwritev. */
+
+#include "config.h"
+
+#include <errno.h>
+#include <sys/types.h>
+#include <sys/uio.h>
+
+#include "libinn.h"
+
+ssize_t fake_write(int, const void *, size_t);
+ssize_t fake_pwrite(int, const void *, size_t, off_t);
+ssize_t fake_writev(int, const struct iovec *, int);
+
+/* All the data is actually written into this buffer. We use write_offset
+ to track how far we've written. */
+char write_buffer[256];
+size_t write_offset = 0;
+
+/* If write_interrupt is non-zero, then half of the calls to write or writev
+ will fail, returning -1 with errno set to EINTR. */
+int write_interrupt = 0;
+
+/* If write_fail is non-zero, all writes or writevs will return 0,
+ indicating no progress in writing out the buffer. */
+int write_fail = 0;
+
+/* Accept a write request and write only the first 32 bytes of it into
+ write_buffer (or as much as will fit), returning the amount written. */
+ssize_t
+fake_write(int fd UNUSED, const void *data, size_t n)
+{
+ size_t total;
+
+ if (write_fail)
+ return 0;
+ if (write_interrupt && (write_interrupt++ % 2) == 0) {
+ errno = EINTR;
+ return -1;
+ }
+ total = (n < 32) ? n : 32;
+ if (256 - write_offset < total)
+ total = 256 - write_offset;
+ memcpy(write_buffer + write_offset, data, total);
+ write_offset += total;
+ return total;
+}
+
+/* Accept a pwrite request and write only the first 32 bytes of it into
+ write_buffer at the specified offset (or as much as will fit), returning
+ the amount written. */
+ssize_t
+fake_pwrite(int fd UNUSED, const void *data, size_t n, off_t offset)
+{
+ size_t total;
+
+ if (write_fail)
+ return 0;
+ if (write_interrupt && (write_interrupt++ % 2) == 0) {
+ errno = EINTR;
+ return -1;
+ }
+ total = (n < 32) ? n : 32;
+ if (offset > 256) {
+ errno = ENOSPC;
+ return -1;
+ }
+ if ((size_t) (256 - offset) < total)
+ total = 256 - offset;
+ memcpy(write_buffer + offset, data, total);
+ return total;
+}
+
+/* Accept an xwrite request and write only the first 32 bytes of it into
+ write_buffer (or as much as will fit), returning the amount written. */
+ssize_t
+fake_writev(int fd UNUSED, const struct iovec *iov, int iovcnt)
+{
+ int total, i;
+ size_t left, n;
+
+ if (write_fail)
+ return 0;
+ if (write_interrupt && (write_interrupt++ % 2) == 0) {
+ errno = EINTR;
+ return -1;
+ }
+ left = 256 - write_offset;
+ if (left > 32)
+ left = 32;
+ total = 0;
+ for (i = 0; i < iovcnt && left != 0; i++) {
+ n = ((size_t) iov[i].iov_len < left) ? iov[i].iov_len : left;
+ memcpy(write_buffer + write_offset, iov[i].iov_base, n);
+ write_offset += n;
+ total += n;
+ left -= n;
+ }
+ return total;
+}
--- /dev/null
+/* $Id: hash-t.c 5623 2002-08-21 19:35:37Z alexk $ */
+/* hash test suite. */
+
+#include "config.h"
+#include "clibrary.h"
+
+#include "libinn.h"
+#include "libtest.h"
+
+int
+main(void)
+{
+ HASH h1, h2;
+
+ puts("12");
+
+ h1 = HashMessageID("<lhs@test.invalid>");
+ h2 = HashMessageID("<lhs@TEST.invalid>");
+ ok(1, HashCompare(&h1, &h2) == 0);
+ h2 = HashMessageID("<lhs@test.INVALID>");
+ ok(2, HashCompare(&h1, &h2) == 0);
+ h2 = HashMessageID("<Lhs@test.invalid>");
+ ok(3, HashCompare(&h1, &h2) != 0);
+ h2 = HashMessageID("<lhS@test.invalid>");
+ ok(4, HashCompare(&h1, &h2) != 0);
+ h1 = HashMessageID("<test.invalid>");
+ h2 = HashMessageID("<TEST.invalid>");
+ ok(5, HashCompare(&h1, &h2) != 0);
+ h2 = HashMessageID("<test.INVALID>");
+ ok(6, HashCompare(&h1, &h2) != 0);
+ h1 = HashMessageID("<postmaster@test.invalid>");
+ h2 = HashMessageID("<POSTMASTER@test.invalid>");
+ ok(7, HashCompare(&h1, &h2) == 0);
+ h2 = HashMessageID("<PostMaster@test.invalid>");
+ ok(8, HashCompare(&h1, &h2) == 0);
+ h2 = HashMessageID("<postmasteR@test.invalid>");
+ ok(9, HashCompare(&h1, &h2) == 0);
+ h2 = HashMessageID("<postmaster@TEST.invalid>");
+ ok(10, HashCompare(&h1, &h2) == 0);
+ h2 = HashMessageID("<postmaster@test.INVALID>");
+ ok(11, HashCompare(&h1, &h2) == 0);
+ h1 = HashMessageID("<postmaster.test.invalid>");
+ h2 = HashMessageID("<POSTMASTER.test.invalid>");
+ ok(12, HashCompare(&h1, &h2) != 0);
+
+ return 0;
+}
--- /dev/null
+/* $Id: hashtab-t.c 6026 2002-12-24 05:02:51Z rra $ */
+/* Test suite for lib/hashtab.c. */
+
+#include "config.h"
+#include "clibrary.h"
+#include <sys/stat.h>
+
+#include "inn/hashtab.h"
+#include "inn/messages.h"
+#include "libinn.h"
+#include "libtest.h"
+
+struct wordref {
+ const char *word;
+ int count;
+};
+
+static const void *
+string_key(const void *entry)
+{
+ return entry;
+}
+
+static bool
+string_equal(const void *key, const void *entry)
+{
+ const char *p, *q;
+
+ p = key;
+ q = entry;
+ return !strcmp(p, q);
+}
+
+static void
+string_delete(void *entry)
+{
+ free(entry);
+}
+
+static void
+string_traverse(void *entry, void *data)
+{
+ int i;
+ struct wordref *wordrefs = data;
+
+ for (i = 0; wordrefs[i].word != NULL; i++)
+ if (!strcmp(entry, wordrefs[i].word)) {
+ wordrefs[i].count++;
+ return;
+ }
+ wordrefs[3].count++;
+}
+
+int
+main(void)
+{
+ struct hash *hash;
+ FILE *words;
+ int reported, i;
+ char buffer[1024];
+ char *word;
+ char *test, *testing, *strange, *change, *foo, *bar;
+
+ struct wordref wordrefs[4] = {
+ { "test", 0 }, { "testing", 0 }, { "change", 0 }, { NULL, 0 }
+ };
+
+ test = xstrdup("test");
+ testing = xstrdup("testing");
+ strange = xstrdup("strange");
+ change = xstrdup("change");
+
+ puts("38");
+ hash = hash_create(4, hash_string, string_key, string_equal,
+ string_delete);
+ ok(1, hash != NULL);
+ if (hash == NULL)
+ die("Unable to create hash, cannot continue");
+
+ ok(2, hash_insert(hash, "test", test));
+ ok(3, hash_collisions(hash) == 0);
+ ok(4, hash_expansions(hash) == 0);
+ ok(5, hash_searches(hash) == 1);
+ ok(6, hash_count(hash) == 1);
+ word = hash_lookup(hash, "test");
+ ok(7, word != NULL && !strcmp("test", word));
+ ok(8, hash_delete(hash, "test"));
+ test = xstrdup("test");
+ ok(9, hash_lookup(hash, "test") == NULL);
+ ok(10, !hash_delete(hash, "test"));
+ ok(11, !hash_replace(hash, "test", testing));
+ ok(12, hash_count(hash) == 0);
+ ok(13, hash_insert(hash, "test", test));
+ ok(14, hash_insert(hash, "testing", testing));
+ ok(15, hash_insert(hash, "strange", strange));
+ ok(16, hash_expansions(hash) == 0);
+ ok(17, hash_insert(hash, "change", change));
+ ok(18, hash_expansions(hash) == 1);
+ ok(19, hash_count(hash) == 4);
+ word = hash_lookup(hash, "testing");
+ ok(20, word != NULL && !strcmp("testing", word));
+ word = hash_lookup(hash, "strange");
+ ok(21, word != NULL && !strcmp("strange", word));
+ ok(22, hash_lookup(hash, "thingie") == NULL);
+ ok(23, !hash_delete(hash, "thingie"));
+ ok(24, hash_delete(hash, "strange"));
+ ok(25, hash_lookup(hash, "strange") == NULL);
+ ok(26, hash_count(hash) == 3);
+
+ hash_traverse(hash, string_traverse, &wordrefs[0]);
+ reported = 0;
+ for (i = 0; wordrefs[i].word != NULL; i++)
+ if (wordrefs[i].count != 1 && !reported) {
+ printf("not ");
+ reported = 1;
+ }
+ puts("ok 27");
+ ok(28, wordrefs[3].count == 0);
+
+ hash_free(hash);
+
+ /* Test hash creation with an odd size. This previously could result
+ in the wrong table size being allocated. */
+ test = xstrdup("test");
+ testing = xstrdup("testing");
+ strange = xstrdup("strange");
+ change = xstrdup("change");
+ foo = xstrdup("foo");
+ bar = xstrdup("bar");
+ hash = hash_create(5, hash_string, string_key, string_equal,
+ string_delete);
+ ok(29, hash != NULL);
+ if (hash == NULL)
+ die("Unable to create hash, cannot continue");
+ ok(30, hash_insert(hash, "test", test));
+ ok(31, hash_insert(hash, "testing", testing));
+ ok(32, hash_insert(hash, "strange", strange));
+ ok(33, hash_insert(hash, "change", change));
+ ok(34, hash_insert(hash, "foo", foo));
+ ok(35, hash_insert(hash, "bar", bar));
+ ok(36, hash_count(hash) == 6);
+ hash_free(hash);
+
+ words = fopen("/usr/dict/words", "r");
+ if (words == NULL)
+ words = fopen("/usr/share/dict/words", "r");
+ if (words == NULL) {
+ puts("ok 37 # skip\nok 38 # skip");
+ exit(0);
+ }
+
+ hash = hash_create(4, hash_string, string_key, string_equal,
+ string_delete);
+ reported = 0;
+ if (hash == NULL)
+ printf("not ");
+ else {
+ while (fgets(buffer, sizeof(buffer), words)) {
+ buffer[strlen(buffer) - 1] = '\0';
+ word = xstrdup(buffer);
+ if (!hash_insert(hash, word, word)) {
+ if (!reported)
+ printf("not ");
+ reported = 1;
+ }
+ }
+ }
+ puts("ok 37");
+
+ if (fseek(words, 0, SEEK_SET) < 0)
+ sysdie("Unable to rewind words file");
+ reported = 0;
+ if (hash == NULL)
+ printf("not ");
+ else {
+ while (fgets(buffer, sizeof(buffer), words)) {
+ buffer[strlen(buffer) - 1] = '\0';
+ word = hash_lookup(hash, buffer);
+ if (!word || strcmp(word, buffer) != 0) {
+ if (!reported)
+ printf("not ");
+ reported = 1;
+ }
+ }
+ }
+ puts("ok 38");
+
+ hash_free(hash);
+
+ return 0;
+}
--- /dev/null
+/* $Id: hstrerror-t.c 5060 2001-12-12 09:20:10Z rra $ */
+/* hstrerror test suite. */
+
+#include "config.h"
+#include <netdb.h>
+#include <stdio.h>
+
+#include "libtest.h"
+
+const char *test_hstrerror(int);
+
+static void
+test_error(int n, const char *expected, int error)
+{
+ ok_string(n, expected, test_hstrerror(error));
+}
+
+int
+main(void)
+{
+ puts("7");
+
+ test_error(1, "Internal resolver error", -1);
+ test_error(2, "No resolver error", 0);
+ test_error(3, "No address associated with name", NO_ADDRESS);
+ test_error(4, "Resolver error 777777", 777777);
+ test_error(5, "Resolver error -99999", -99999);
+ test_error(6, "", 1000000);
+ test_error(7, "", -100000);
+
+ return 0;
+}
--- /dev/null
+/* $Id: inet_aton-t.c 5061 2001-12-12 09:21:17Z rra $ */
+/* inet_aton test suite. */
+
+#include "config.h"
+#include "clibrary.h"
+#include <netinet/in.h>
+
+int test_inet_aton(const char *, struct in_addr *);
+
+static void
+test_addr(int n, const char *string, unsigned long addr)
+{
+ bool success, okay;
+ struct in_addr in;
+
+ success = test_inet_aton(string, &in);
+ okay = (success && in.s_addr == htonl(addr));
+
+ printf("%sok %d\n", okay ? "" : "not ", n);
+ if (!okay && !success) printf(" success: %d\n", success);
+ if (!okay && in.s_addr != htonl(addr))
+ printf(" want: %lx\n saw: %lx\n", (unsigned long) htonl(addr),
+ (unsigned long) in.s_addr);
+}
+
+static void
+test_fail(int n, const char *string)
+{
+ struct in_addr in;
+ int success;
+
+ in.s_addr = htonl(0x01020304UL);
+ success = test_inet_aton(string, &in);
+ success = (success == 0 && in.s_addr == htonl(0x01020304UL));
+ printf("%sok %d\n", success ? "" : "not ", n);
+}
+
+int
+main(void)
+{
+ puts("46");
+
+ test_addr( 1, "0.0.0.0", 0);
+ test_addr( 2, "127.0.0.000000", 0x7f000000UL);
+ test_addr( 3, "255.255.255.255", 0xffffffffUL);
+ test_addr( 4, "172.200.232.199", 0xacc8e8c7UL);
+ test_addr( 5, "1.2.3.4", 0x01020304UL);
+
+ test_addr( 6, "0x0.0x0.0x0.0x0", 0);
+ test_addr( 7, "0x7f.0x000.0x0.0x00", 0x7f000000UL);
+ test_addr( 8, "0xff.0xFf.0xFF.0xff", 0xffffffffUL);
+ test_addr( 9, "0xAC.0xc8.0xe8.0xC7", 0xacc8e8c7UL);
+ test_addr(10, "0xAa.0xbB.0xCc.0xdD", 0xaabbccddUL);
+ test_addr(11, "0xEe.0xfF.0.0x00000", 0xeeff0000UL);
+ test_addr(12, "0x1.0x2.0x00003.0x4", 0x01020304UL);
+
+ test_addr(13, "000000.00.000.00", 0);
+ test_addr(14, "0177.0", 0x7f000000UL);
+ test_addr(15, "0377.0377.0377.0377", 0xffffffffUL);
+ test_addr(16, "0254.0310.0350.0307", 0xacc8e8c7UL);
+ test_addr(17, "00001.02.3.00000004", 0x01020304UL);
+
+ test_addr(18, "16909060", 0x01020304UL);
+ test_addr(19, "172.062164307", 0xacc8e8c7UL);
+ test_addr(20, "172.0xc8.0xe8c7", 0xacc8e8c7UL);
+ test_addr(21, "127.1", 0x7f000001UL);
+ test_addr(22, "0xffffffff", 0xffffffffUL);
+ test_addr(23, "127.0xffffff", 0x7fffffffUL);
+ test_addr(24, "127.127.0xffff", 0x7f7fffffUL);
+
+ test_fail(25, "");
+ test_fail(26, "Donald Duck!");
+ test_fail(27, "a127.0.0.1");
+ test_fail(28, "aaaabbbb");
+ test_fail(29, "0x100000000");
+ test_fail(30, "0xfffffffff");
+ test_fail(31, "127.0xfffffff");
+ test_fail(32, "127.376926742");
+ test_fail(33, "127.127.01452466");
+ test_fail(34, "127.127.127.0x100");
+ test_fail(35, "256.0");
+ test_fail(36, "127.0378.127.127");
+ test_fail(37, "127.127.0x100.127");
+ test_fail(38, "127.0.o.1");
+ test_fail(39, "127.127.127.127v");
+ test_fail(40, "ef.127.127.127");
+ test_fail(41, "0128.127.127.127");
+ test_fail(42, "0xeg.127");
+ test_fail(43, ".127.127");
+ test_fail(44, "127.127.");
+ test_fail(45, "127..127");
+ test_fail(46, "de.ad.be.ef");
+
+ return 0;
+}
--- /dev/null
+/* $Id: inet_ntoa-t.c 5061 2001-12-12 09:21:17Z rra $ */
+/* inet_ntoa test suite. */
+
+#include "config.h"
+#include "clibrary.h"
+#include <netinet/in.h>
+
+#include "libtest.h"
+
+const char *test_inet_ntoa(const struct in_addr);
+
+static void
+test_addr(int n, const char *expected, unsigned long addr)
+{
+ struct in_addr in;
+
+ in.s_addr = htonl(addr);
+ ok_string(n, expected, test_inet_ntoa(in));
+}
+
+int
+main(void)
+{
+ puts("5");
+
+ test_addr(1, "0.0.0.0", 0x0);
+ test_addr(2, "127.0.0.0", 0x7f000000UL);
+ test_addr(3, "255.255.255.255", 0xffffffffUL);
+ test_addr(4, "172.200.232.199", 0xacc8e8c7UL);
+ test_addr(5, "1.2.3.4", 0x01020304UL);
+
+ return 0;
+}
--- /dev/null
+/* $Id: innconf-t.c 7748 2008-04-06 13:49:56Z iulius $ */
+/* innconf test suite. */
+
+#include "config.h"
+#include "clibrary.h"
+
+#include "inn/innconf.h"
+#include "inn/messages.h"
+#include "libtest.h"
+
+static const char grep[] =
+"egrep 'mta|organization|ovmethod|hismethod|path|pgpverify'\
+ ../../samples/inn.conf > config/tmp";
+
+int
+main(void)
+{
+ struct innconf *standard;
+ FILE *config;
+
+ if (access("config/valid", F_OK) < 0)
+ if (access("lib/config/valid", F_OK) == 0)
+ chdir("lib");
+
+ puts("9");
+
+ ok(1, innconf_read("../../samples/inn.conf"));
+ standard = innconf;
+ innconf = NULL;
+ if (system(grep) != 0)
+ die("Unable to create stripped configuration file");
+ ok(2, innconf_read("config/tmp"));
+ unlink("config/tmp");
+ ok(3, innconf_compare(standard, innconf));
+ innconf_free(standard);
+ innconf_free(innconf);
+ innconf = NULL;
+ ok(4, true);
+
+ /* Checking inn.conf. */
+ errors_capture();
+ if (system(grep) != 0)
+ die("Unable to create stripped configuration file");
+ ok(5, innconf_check("config/tmp"));
+ ok(6, errors == NULL);
+ innconf_free(innconf);
+ innconf = NULL;
+ config = fopen("config/tmp", "a");
+ if (config == NULL)
+ sysdie("Unable to open stripped configuration file for append");
+ fputs("foo: bar\n", config);
+ fclose(config);
+ ok(7, !innconf_check("config/tmp"));
+ unlink("config/tmp");
+ ok_string(8, "config/tmp:26: unknown parameter foo\n", errors);
+ errors_uncapture();
+ free(errors);
+ errors = NULL;
+ innconf_free(innconf);
+ innconf = NULL;
+ ok(9, true);
+
+ return 0;
+}
--- /dev/null
+/* $Id: list-t.c 6294 2003-04-15 03:43:45Z rra $ */
+/* Test suite for list routines. */
+
+#include "config.h"
+#include "clibrary.h"
+
+#include "inn/messages.h"
+#include "inn/list.h"
+#include "libinn.h"
+#include "libtest.h"
+
+int
+main(void)
+{
+ struct list list;
+ struct node a, b, c;
+
+ puts("28");
+
+ list_new(&list);
+ ok(1, list_isempty(&list));
+
+ ok(2, list_addhead(&list, &a) == &a);
+ ok(3, !list_isempty(&list));
+ ok(4, list_head(&list) == &a);
+ ok(5, list_tail(&list) == &a);
+ ok(6, list_remhead(&list) == &a);
+ ok(7, list_isempty(&list));
+
+ ok(8, list_addhead(&list, &a) == &a);
+ ok(9, list_remtail(&list) == &a);
+ ok(10, list_isempty(&list));
+
+ ok(11, list_addtail(&list, &a) == &a);
+ ok(12, !list_isempty(&list));
+ ok(13, list_head(&list) == &a);
+ ok(14, list_tail(&list) == &a);
+ ok(15, list_remhead(&list) == &a);
+ ok(16, list_isempty(&list));
+
+ list_addtail(&list, &a);
+ ok(17, list_remtail(&list) == &a);
+ ok(18, list_isempty(&list));
+
+ list_addhead(&list, &a);
+ ok(19, list_remove(&a) == &a);
+ ok(20, list_isempty(&list));
+
+ list_addtail(&list, &a);
+ list_addtail(&list, &b);
+ list_insert(&list, &c, &a);
+ ok(21, list_succ(&c) == &b);
+ ok(22, list_pred(&c) == &a);
+ list_remove(&c);
+ list_insert(&list, &c, &b);
+ ok(23, list_succ(&c) == NULL);
+ ok(24, list_pred(&c) == &b);
+ list_remove(&c);
+ list_insert(&list, &c, NULL);
+ ok(25, list_succ(&c) == &a);
+ ok(26, list_pred(&c) == NULL);
+ list_remove(&c);
+ ok(27, list_head(&list) == &a);
+ ok(28, list_tail(&list) == &b);
+
+ return 0;
+}
--- /dev/null
+/* $Id: md5-t.c 6128 2003-01-18 22:26:49Z rra $ */
+/* MD5 hashing test suite. */
+
+#include "config.h"
+#include "clibrary.h"
+#include "inn/md5.h"
+#include "libinn.h"
+#include "libtest.h"
+
+/* Used to initialize strings of unsigned characters. */
+#define U (const unsigned char *)
+
+/* An unsigned char version of strlen. */
+#define ustrlen(s) strlen((const char *) s)
+
+/* Used for converting digests to hex to make them easier to deal with. */
+static const char hex[] = "0123456789abcdef";
+
+/* A set of data strings and resulting digests to check. It's not easy to
+ get nulls into this data structure, so data containing nulls should be
+ checked separately. */
+static const unsigned char * const testdata[] = {
+ /* First five tests of the MD5 test suite from RFC 1321. */
+ U"",
+ U"a",
+ U"abc",
+ U"message digest",
+ U"abcdefghijklmnopqrstuvwxyz",
+
+ /* Three real message IDs to ensure compatibility with old INN versions;
+ the corresponding MD5 hashes were taken directly out of the history
+ file of a server running INN 2.3. */
+ U"<J3Ds5.931$Vg6.7556@news01.chello.no>",
+ U"<sr5v7ooea6e17@corp.supernews.com>",
+ U"<cancel.Y2Ds5.26391$oH5.540535@news-east.usenetserver.com>",
+
+ /* Other random stuff, including high-bit characters. */
+ U"example.test",
+ U"||",
+ U"|||",
+ U"\375\277\277\277\277\276",
+ U"\377\277\277\277\277\277"
+};
+
+/* The hashes corresonding to the above data. */
+static const char * const testhash[] = {
+ "d41d8cd98f00b204e9800998ecf8427e",
+ "0cc175b9c0f1b6a831c399e269772661",
+ "900150983cd24fb0d6963f7d28e17f72",
+ "f96b697d7cb7938d525a2f31aaf161d0",
+ "c3fcd3d76192e4007dfb496cca67e13b",
+ "c4a70fb19af37bed6b7c77f1e1187f00",
+ "7f70531c7027c20b0ddba0a649cf8691",
+ "9d9f0423f38b731c9bf69607cea6be76",
+ "09952be409a7d6464cd7661beeeb966e",
+ "7d010443693eec253a121e2aa2ba177c",
+ "2edf2958166561c5c08cd228e53bbcdc",
+ "c18293a6fe0a09720e841c8ebc697b97",
+ "ce23eb027c63215b999b9f86d6a4f9cb"
+};
+
+static void
+digest2hex(const unsigned char *digest, char *result)
+{
+ const unsigned char *p;
+ unsigned int i;
+
+ for (p = digest, i = 0; i < 32; i += 2, p++) {
+ result[i] = hex[(*p & 0xf0) >> 4];
+ result[i + 1] = hex[*p & 0x0f];
+ }
+ result[32] = '\0';
+}
+
+static void
+test_md5(int n, const char *expected, const unsigned char *data,
+ size_t length)
+{
+ unsigned char digest[16];
+ char hexdigest[33];
+
+ md5_hash(data, length, digest);
+ digest2hex(digest, hexdigest);
+ ok_string(n, expected, hexdigest);
+}
+
+int
+main(void)
+{
+ unsigned int i;
+ int j, n;
+ unsigned char *data;
+ struct md5_context context;
+ char hexdigest[33];
+
+ printf("%d\n", 12 + ARRAY_SIZE(testdata));
+
+ test_md5(1, "93b885adfe0da089cdf634904fd59f71", U"\0", 1);
+ test_md5(2, "e94a053c3fbfcfb22b4debaa11af7718", U"\0ab\n", 4);
+
+ data = xmalloc(64 * 1024);
+ memset(data, 0, 64 * 1024);
+ test_md5(3, "fcd6bcb56c1689fcef28b57c22475bad", data, 64 * 1024);
+ memset(data, 1, 32 * 1024);
+ test_md5(4, "3d8897b14254c9f86fbad3fe22f62edd", data, 64 * 1024);
+ test_md5(5, "25364962aa23b187942a24ae736c4e8c", data, 65000);
+ test_md5(6, "f9816b5d5363d15f14bb98d548309dcc", data, 55);
+ test_md5(7, "5e99dfddfb51c18cfc55911dee24ae7b", data, 56);
+ test_md5(8, "0871ffa021e2bc4da87eb93ac22d293c", data, 63);
+ test_md5(9, "784d68ba9112308689114a6816c628ce", data, 64);
+
+ /* Check the individual functions. */
+ md5_init(&context);
+ md5_update(&context, data, 32 * 1024);
+ md5_update(&context, data + 32 * 1024, 32 * 1024 - 42);
+ md5_update(&context, data + 64 * 1024 - 42, 42);
+ md5_final(&context);
+ digest2hex(context.digest, hexdigest);
+ ok_string(10, "3d8897b14254c9f86fbad3fe22f62edd", hexdigest);
+
+ /* Part of the MD5 test suite from RFC 1321. */
+ for (i = 0, n = 'A'; n <= 'Z'; i++, n++)
+ data[i] = n;
+ for (i = 26, n = 'a'; n <= 'z'; i++, n++)
+ data[i] = n;
+ for (i = 52, n = '0'; n <= '9'; i++, n++)
+ data[i] = n;
+ test_md5(11, "d174ab98d277d9f5a5611c2c9f419d9f", data, 62);
+ for (i = 0, j = 0; j < 8; j++) {
+ for (n = '1'; n <= '9'; i++, n++)
+ data[i] = n;
+ data[i++] = '0';
+ }
+ test_md5(12, "57edf4a22be3c955ac49da2e2107b67a", data, 80);
+
+ n = 13;
+ for (i = 0; i < ARRAY_SIZE(testdata); i++)
+ test_md5(n++, testhash[i], testdata[i], ustrlen(testdata[i]));
+
+ return 0;
+}
--- /dev/null
+/* $Id: memcmp-t.c 5054 2001-12-12 09:15:24Z rra $ */
+/* memcmp test suite. */
+
+#include "config.h"
+#include <stdio.h>
+#include <sys/types.h>
+
+#include "libtest.h"
+
+int test_memcmp(const void *, const void *, size_t);
+
+int
+main(void)
+{
+ puts("15");
+
+ ok( 1, test_memcmp("", "", 0) == 0);
+ ok( 2, test_memcmp("", "", 1) == 0);
+ ok( 3, test_memcmp("alpha", "alpha", 6) == 0);
+ ok( 4, test_memcmp("alpha", "beta", 5) < 0);
+ ok( 5, test_memcmp("beta", "alpha", 5) > 0);
+ ok( 6, test_memcmp("alpha", "apple", 1) == 0);
+ ok( 7, test_memcmp("alpha", "apple", 2) < 0);
+ ok( 8, test_memcmp("\0v", "\0w", 2) < 0);
+ ok( 9, test_memcmp("\200\201\202", "\200\201\202", 4) == 0);
+ ok(10, test_memcmp("\200\201\202", "\200\201\203", 4) < 0);
+ ok(11, test_memcmp("\200\201\203", "\200\201\202", 4) > 0);
+ ok(12, test_memcmp("al\0po", "al\0pha", 6) > 0);
+ ok(13, test_memcmp("\100", "\201", 1) < 0);
+ ok(14, test_memcmp("\200", "\201", 1) < 0);
+ ok(15, test_memcmp("a", "b", 0) == 0);
+
+ return 0;
+}
--- /dev/null
+/* $Id: messages-t.c 5638 2002-08-23 22:52:53Z rra $ */
+/* Test suite for error handling routines. */
+
+#include "config.h"
+#include "clibrary.h"
+#include <errno.h>
+#include <fcntl.h>
+#include <sys/stat.h>
+#include <sys/wait.h>
+
+#include "inn/messages.h"
+#include "libinn.h"
+
+#define END (char *) 0
+
+/* Test function type. */
+typedef void (*test_function_t)(void);
+
+/* Fork and execute the provided function, connecting stdout and stderr to a
+ pipe. Captures the output into the provided buffer and returns the exit
+ status as a waitpid status value. */
+static int
+run_test(test_function_t function, char *buf, size_t buflen)
+{
+ int fds[2];
+ pid_t child;
+ ssize_t count, status;
+
+ /* Flush stdout before we start to avoid odd forking issues. */
+ fflush(stdout);
+
+ /* Set up the pipe and call the function, collecting its output. */
+ if (pipe(fds) == -1)
+ sysdie("can't create pipe");
+ child = fork();
+ if (child == (pid_t) -1) {
+ sysdie("can't fork");
+ } else if (child == 0) {
+ /* In child. Set up our stdout and stderr. */
+ close(fds[0]);
+ if (dup2(fds[1], 1) == -1)
+ _exit(255);
+ if (dup2(fds[1], 2) == -1)
+ _exit(255);
+
+ /* Now, run the function and exit successfully if it returns. */
+ (*function)();
+ fflush(stdout);
+ _exit(0);
+ } else {
+ /* In the parent; close the extra file descriptor, read the output
+ if any, and then collect the exit status. */
+ close(fds[1]);
+ count = 0;
+ do {
+ status = read(fds[0], buf + count, buflen - count - 1);
+ if (status > 0)
+ count += status;
+ } while (status > 0);
+ buf[count < 0 ? 0 : count] = '\0';
+ if (waitpid(child, &status, 0) == (pid_t) -1)
+ sysdie("waitpid failed");
+ }
+ return status;
+}
+
+/* Test functions. */
+static void test1(void) { warn("warning"); }
+static void test2(void) { die("fatal"); }
+static void test3(void) { errno = EPERM; syswarn("permissions"); }
+static void test4(void) { errno = EACCES; sysdie("fatal access"); }
+static void test5(void) {
+ message_program_name = "test5";
+ warn("warning");
+}
+static void test6(void) {
+ message_program_name = "test6";
+ die("fatal");
+}
+static void test7(void) {
+ message_program_name = "test7";
+ errno = EPERM;
+ syswarn("perms %d", 7);
+}
+static void test8(void) {
+ message_program_name = "test8";
+ errno = EACCES;
+ sysdie("%st%s", "fa", "al");
+}
+
+static int return10(void) { return 10; }
+
+static void test9(void) {
+ message_fatal_cleanup = return10;
+ die("fatal");
+}
+static void test10(void) {
+ message_program_name = 0;
+ message_fatal_cleanup = return10;
+ errno = EPERM;
+ sysdie("fatal perm");
+}
+static void test11(void) {
+ message_program_name = "test11";
+ message_fatal_cleanup = return10;
+ errno = EPERM;
+ fputs("1st ", stdout);
+ sysdie("fatal");
+}
+
+static void log(int len, const char *format, va_list args, int error) {
+ fprintf(stderr, "%d %d ", len, error);
+ vfprintf(stderr, format, args);
+ fprintf(stderr, "\n");
+}
+
+static void test12(void) {
+ message_handlers_warn(1, log);
+ warn("warning");
+}
+static void test13(void) {
+ message_handlers_die(1, log);
+ die("fatal");
+}
+static void test14(void) {
+ message_handlers_warn(2, log, log);
+ errno = EPERM;
+ syswarn("warning");
+}
+static void test15(void) {
+ message_handlers_die(2, log, log);
+ message_fatal_cleanup = return10;
+ errno = EPERM;
+ sysdie("fatal");
+}
+static void test16(void) {
+ message_handlers_warn(2, message_log_stderr, log);
+ message_program_name = "test16";
+ errno = EPERM;
+ syswarn("warning");
+}
+static void test17(void) { notice("notice"); }
+static void test18(void) {
+ message_program_name = "test18";
+ notice("notice");
+}
+static void test19(void) { trace(TRACE_PROGRAM, "tracing"); }
+static void test20(void) { debug("debug"); }
+static void test21(void) {
+ message_handlers_notice(1, log);
+ notice("foo");
+}
+static void test22(void) {
+ message_handlers_trace(1, log);
+ message_trace_enable(TRACE_PROGRAM, true);
+ trace(TRACE_PROGRAM, "foo");
+ trace(TRACE_NETWORK, "bar");
+}
+static void test23(void) {
+ message_handlers_debug(1, message_log_stdout);
+ message_program_name = "test23";
+ debug("baz");
+}
+static void test24(void) {
+ message_handlers_die(0);
+ die("hi mom!");
+}
+static void test25(void) {
+ message_handlers_warn(0);
+ warn("this is a test");
+}
+static void test26(void) {
+ notice("first");
+ message_handlers_notice(0);
+ notice("second");
+ message_handlers_notice(1, message_log_stdout);
+ notice("third");
+}
+
+/* Given the test number, intended exit status and message, and the function
+ to run, print ok or not ok. */
+static void
+test_error(int n, int status, const char *output, test_function_t function)
+{
+ int real_status;
+ char buf[256];
+ int succeeded = 1;
+
+ real_status = run_test(function, buf, sizeof(buf));
+ if (!WIFEXITED(real_status) || status != WEXITSTATUS(real_status)) {
+ printf(" unexpected exit status %d\n", real_status);
+ succeeded = 0;
+ }
+ if (strcmp(output, buf)) {
+ printf(" unexpected output: %s", buf);
+ printf(" expected output: %s", output);
+ succeeded = 0;
+ }
+ printf("%sok %d\n", succeeded ? "" : "not ", n);
+}
+
+/* Given the test number, intended status, intended message sans the
+ appended strerror output, errno, and the function to run, print ok or not
+ ok. */
+static void
+test_strerror(int n, int status, const char *output, int error,
+ test_function_t function)
+{
+ char *full_output;
+
+ full_output = concat(output, ": ", strerror(error), "\n", END);
+ test_error(n, status, full_output, function);
+ free(full_output);
+}
+
+/* Run the tests. */
+int
+main(void)
+{
+ char buff[32];
+
+ puts("26");
+
+ test_error(1, 0, "warning\n", test1);
+ test_error(2, 1, "fatal\n", test2);
+ test_strerror(3, 0, "permissions", EPERM, test3);
+ test_strerror(4, 1, "fatal access", EACCES, test4);
+ test_error(5, 0, "test5: warning\n", test5);
+ test_error(6, 1, "test6: fatal\n", test6);
+ test_strerror(7, 0, "test7: perms 7", EPERM, test7);
+ test_strerror(8, 1, "test8: fatal", EACCES, test8);
+ test_error(9, 10, "fatal\n", test9);
+ test_strerror(10, 10, "fatal perm", EPERM, test10);
+ test_strerror(11, 10, "1st test11: fatal", EPERM, test11);
+ test_error(12, 0, "7 0 warning\n", test12);
+ test_error(13, 1, "5 0 fatal\n", test13);
+
+ sprintf(buff, "%d", EPERM);
+
+ test_error(14, 0,
+ concat("7 ", buff, " warning\n7 ", buff, " warning\n", END),
+ test14);
+ test_error(15, 10,
+ concat("5 ", buff, " fatal\n5 ", buff, " fatal\n", END),
+ test15);
+ test_error(16, 0,
+ concat("test16: warning: ", strerror(EPERM), "\n7 ", buff,
+ " warning\n", END),
+ test16);
+
+ test_error(17, 0, "notice\n", test17);
+ test_error(18, 0, "test18: notice\n", test18);
+ test_error(19, 0, "", test19);
+ test_error(20, 0, "", test20);
+ test_error(21, 0, "3 0 foo\n", test21);
+ test_error(22, 0, "3 0 foo\n", test22);
+ test_error(23, 0, "test23: baz\n", test23);
+
+ /* Make sure that it's possible to turn off a message type entirely. */
+ test_error(24, 1, "", test24);
+ test_error(25, 0, "", test25);
+ test_error(26, 0, "first\nthird\n", test26);
+
+ return 0;
+}
--- /dev/null
+/* $Id: mkstemp-t.c 5329 2002-03-17 07:39:14Z rra $ */
+/* mkstemp test suite */
+
+#include "config.h"
+#include "clibrary.h"
+#include <errno.h>
+#include <sys/stat.h>
+
+#include "libtest.h"
+
+int test_mkstemp(char *template);
+
+int
+main(void)
+{
+ int fd;
+ char template[] = "tsXXXXXXX";
+ char tooshort[] = "XXXXX";
+ char bad1[] = "/foo/barXXXXX";
+ char bad2[] = "/foo/barXXXXXX.out";
+ char buffer[256];
+ struct stat st1, st2;
+ ssize_t length;
+
+ puts("20");
+
+ /* First, test a few error messages. */
+ errno = 0;
+ ok_int(1, -1, test_mkstemp(tooshort));
+ ok(2, errno == EINVAL);
+ ok_string(3, "XXXXX", tooshort);
+ errno = 0;
+ ok_int(4, -1, test_mkstemp(bad1));
+ ok(5, errno == EINVAL);
+ ok_string(6, "/foo/barXXXXX", bad1);
+ errno = 0;
+ ok_int(7, -1, test_mkstemp(bad2));
+ ok(8, errno == EINVAL);
+ ok_string(9, "/foo/barXXXXXX.out", bad2);
+ errno = 0;
+
+ /* Now try creating a real file. */
+ fd = test_mkstemp(template);
+ ok(10, fd >= 0);
+ ok(11, strcmp(template, "tsXXXXXXX") != 0);
+ ok(12, strncmp(template, "tsX", 3) == 0);
+ ok(13, access(template, F_OK) == 0);
+
+ /* Make sure that it's the same file as template refers to now. */
+ ok(14, stat(template, &st1) == 0);
+ ok(15, fstat(fd, &st2) == 0);
+ ok(16, st1.st_ino == st2.st_ino);
+ unlink(template);
+
+ /* Make sure the open mode is correct. */
+ length = strlen(template);
+ ok(17, write(fd, template, length) == length);
+ ok(18, lseek(fd, 0, SEEK_SET) == 0);
+ ok(19, read(fd, buffer, length) == length);
+ buffer[length] = '\0';
+ ok_string(20, template, buffer);
+ close(fd);
+
+ return 0;
+}
--- /dev/null
+/* $Id: pread-t.c 5379 2002-03-31 21:45:12Z rra $ */
+/* pread test suite. */
+
+#include "config.h"
+#include "clibrary.h"
+#include <errno.h>
+#include <fcntl.h>
+#include <sys/stat.h>
+
+#include "inn/messages.h"
+#include "libinn.h"
+#include "libtest.h"
+
+ssize_t test_pread(int fd, void *buf, size_t nbyte, off_t offset);
+
+int
+main(void)
+{
+ unsigned char buf[256], result[256];
+ unsigned char c;
+ int i, fd;
+ ssize_t status;
+ off_t position;
+
+ for (c = 0, i = 0; i < 256; i++, c++)
+ buf[i] = c;
+ fd = open(".testout", O_RDWR | O_CREAT | O_TRUNC, 0644);
+ if (fd < 0)
+ sysdie("Can't create .testout");
+ if (unlink(".testout") < 0)
+ sysdie("Can't unlink .testout");
+ if (xwrite(fd, buf, 256) < 0)
+ sysdie("Can't write to .testout");
+ if (lseek(fd, 0, SEEK_SET) == (off_t) -1)
+ sysdie("Can't rewind .testout");
+ memset(result, 0, sizeof(result));
+
+ puts("6");
+
+ status = test_pread(fd, result, 128, 128);
+ ok(1, (status == 128) && !memcmp(result, buf + 128, 128));
+ status = read(fd, result, 64);
+ ok(2, (status == 64) && !memcmp(result, buf, 64));
+ status = test_pread(fd, result, 1, 256);
+ ok(3, status == 0);
+ status = test_pread(fd, result, 256, 0);
+ ok(4, (status == 256) && !memcmp(result, buf, 256));
+ position = lseek(fd, 0, SEEK_CUR);
+ ok(5, position == 64);
+
+ close(20);
+ errno = 0;
+ status = test_pread(20, result, 1, 0);
+ ok(6, (status == -1) && (errno == EBADF));
+
+ return 0;
+}
--- /dev/null
+/* $Id: pwrite-t.c 5379 2002-03-31 21:45:12Z rra $ */
+/* pwrite test suite. */
+
+#include "config.h"
+#include "clibrary.h"
+#include <errno.h>
+#include <fcntl.h>
+#include <sys/stat.h>
+
+#include "inn/messages.h"
+#include "libtest.h"
+
+ssize_t test_pwrite(int fd, const void *buf, size_t nbyte, off_t offset);
+
+int
+main(void)
+{
+ unsigned char buf[256], result[256];
+ unsigned char c;
+ int i, fd;
+ ssize_t status;
+
+ for (c = 0, i = 0; i < 256; i++, c++)
+ buf[i] = c;
+ fd = open(".testout", O_RDWR | O_CREAT | O_TRUNC, 0644);
+ if (fd < 0)
+ sysdie("Can't create .testout");
+ if (unlink(".testout") < 0)
+ sysdie("Can't unlink .testout");
+ memset(result, 0, sizeof(result));
+
+ puts("6");
+
+ ok(1, test_pwrite(fd, buf + 129, 127, 129) == 127);
+ ok(2, write(fd, buf, 64) == 64);
+ ok(3, test_pwrite(fd, buf + 64, 65, 64) == 65);
+ status = read(fd, result, 64);
+ ok(4, (status == 64) && !memcmp(result, buf + 64, 64));
+
+ if (lseek(fd, 0, SEEK_SET) == (off_t) -1)
+ sysdie("Can't rewind .testout");
+ status = read(fd, result, 256);
+ ok(5, (status == 256) && !memcmp(result, buf, 256));
+
+ close(20);
+ errno = 0;
+ status = test_pwrite(20, result, 1, 0);
+ ok(6, (status == -1) && (errno == EBADF));
+
+ return 0;
+}
--- /dev/null
+/* $Id: qio-t.c 6939 2004-06-10 22:04:58Z hkehoe $ */
+/* Test suite for the Quick I/O library */
+
+#include "config.h"
+#include "clibrary.h"
+#include <errno.h>
+#include <fcntl.h>
+#include <sys/stat.h>
+
+#include "inn/messages.h"
+#include "inn/qio.h"
+#include "libinn.h"
+#include "libtest.h"
+
+static void
+output(int fd, const void *data, size_t size)
+{
+ if (xwrite(fd, data, size) < 0)
+ sysdie("Can't write to .testout");
+}
+
+int
+main(void)
+{
+ unsigned char data[256], line[256], out[256];
+ unsigned char c;
+ char *result;
+ int i, count, fd;
+ size_t size = 8192;
+ QIOSTATE *qio;
+ bool success;
+
+#if HAVE_ST_BLKSIZE
+ struct stat st;
+#endif
+
+ for (c = 1, i = 0; i < 255; i++, c++)
+ data[i] = c;
+ data[9] = ' ';
+ data[255] = '\255';
+ memcpy(line, data, 255);
+ line[255] = '\n';
+ memcpy(out, data, 255);
+ out[255] = '\0';
+ fd = open(".testout", O_RDWR | O_CREAT | O_TRUNC, 0644);
+ if (fd < 0) sysdie("Can't create .testout");
+
+#if HAVE_ST_BLKSIZE
+ /* Mostly duplicate the code from qio.c so that we can test with lines
+ exactly as large as the buffer. */
+ if (fstat(fd, &st) == 0 && S_ISREG(st.st_mode)) {
+ size = st.st_blksize;
+ if (size > 4 * 8192)
+ size = 8192;
+ else
+ while(size < 8192)
+ size += st.st_blksize;
+ }
+#endif /* HAVE_ST_BLKSIZE */
+
+ /* Start with small, equally sized lines exactly equal to the buffer.
+ Then a line equal in size to the buffer, then a short line and
+ another line equal in size to the buffer, then a half line and lines
+ repeated to fill another buffer, then a line that's one character too
+ long. */
+ count = size / 256;
+ for (i = 0; i < count; i++)
+ output(fd, line, 256);
+ for (i = 0; i < count - 1; i++)
+ output(fd, data, 256);
+ output(fd, line, 256);
+ output(fd, "\n", 1);
+ for (i = 0; i < count - 1; i++)
+ output(fd, data, 256);
+ output(fd, line, 256);
+ output(fd, data, 127);
+ output(fd, "\n", 1);
+ for (i = 0; i < count; i++)
+ output(fd, line, 256);
+ for (i = 0; i < count; i++)
+ output(fd, data, 256);
+ output(fd, "\n", 1);
+ close(fd);
+
+ puts("30");
+
+ /* Now make sure we can read all that back correctly. */
+ qio = QIOopen(".testout");
+ ok(1, qio != NULL);
+ ok(2, !QIOerror(qio));
+ ok(3, QIOfileno(qio) > 0);
+ if (unlink(".testout") < 0)
+ sysdie("Can't unlink .testout");
+ for (success = true, i = 0; i < count; i++) {
+ result = QIOread(qio);
+ success = (success && !QIOerror(qio) && (QIOlength(qio) == 255)
+ && !strcmp(result, (char *) out));
+ }
+ ok(4, success);
+ ok(5, QIOtell(qio) == (off_t) size);
+ result = QIOread(qio);
+ if (strlen(result) < size - 1) {
+ ok(6, false);
+ } else {
+ for (success = true, i = 0; i < count - 1; i++)
+ success = success && !memcmp(result + i * 256, data, 256);
+ success = success && !memcmp(result + i * 256, data, 255);
+ ok(6, success);
+ }
+ ok(7, QIOtell(qio) == (off_t) (2 * size));
+ result = QIOread(qio);
+ ok(8, !QIOerror(qio));
+ ok(9, QIOlength(qio) == 0);
+ ok(10, *result == 0);
+ result = QIOread(qio);
+ if (strlen(result) < size - 1) {
+ ok(11, false);
+ } else {
+ for (success = true, i = 0; i < count - 1; i++)
+ success = success && !memcmp(result + i * 256, data, 256);
+ success = success && !memcmp(result + i * 256, data, 255);
+ ok(11, success);
+ }
+ ok(12, QIOtell(qio) == (off_t) (3 * size + 1));
+ result = QIOread(qio);
+ ok(13, !QIOerror(qio));
+ ok(14, QIOlength(qio) == 127);
+ ok(15, strlen(result) == 127);
+ ok(16, !memcmp(result, data, 127));
+ for (success = true, i = 0; i < count; i++) {
+ result = QIOread(qio);
+ success = (success && !QIOerror(qio) && (QIOlength(qio) == 255)
+ && !strcmp(result, (char *) out));
+ }
+ ok(17, success);
+ ok(18, QIOtell(qio) == (off_t) (4 * size + 129));
+ result = QIOread(qio);
+ ok(19, !result);
+ ok(20, QIOerror(qio));
+ ok(21, QIOtoolong(qio));
+ ok(22, QIOrewind(qio) == 0);
+ ok(23, QIOtell(qio) == 0);
+ result = QIOread(qio);
+ ok(24, !QIOerror(qio));
+ ok(25, QIOlength(qio) == 255);
+ ok(26, strlen(result) == 255);
+ ok(27, !strcmp(result, (char *) out));
+ ok(28, QIOtell(qio) == 256);
+ fd = QIOfileno(qio);
+ QIOclose(qio);
+ ok(29, close(fd) < 0);
+ ok(30, errno == EBADF);
+
+ return 0;
+}
--- /dev/null
+/* $Id: setenv-t.c 7492 2006-03-19 23:07:34Z eagle $ */
+/* setenv test suite. */
+
+#include "config.h"
+
+#include <errno.h>
+#include <stdio.h>
+#include <stdlib.h>
+
+#include "inn/messages.h"
+#include "libinn.h"
+#include "libtest.h"
+
+int test_setenv(const char *name, const char *value, int overwrite);
+
+static const char test_var[] = "SETENV_TEST";
+static const char test_value1[] = "Do not taunt Happy Fun Ball.";
+static const char test_value2[] = "Do not use Happy Fun Ball on concrete.";
+
+int
+main(void)
+{
+ char *value;
+ int status;
+
+ if (getenv(test_var))
+ die("%s already in the environment!", test_var);
+
+ puts("12");
+
+ ok(1, test_setenv(test_var, test_value1, 0) == 0);
+ ok_string(2, test_value1, getenv(test_var));
+ ok(3, test_setenv(test_var, test_value2, 0) == 0);
+ ok_string(4, test_value1, getenv(test_var));
+ ok(5, test_setenv(test_var, test_value2, 1) == 0);
+ ok_string(6, test_value2, getenv(test_var));
+ ok(7, test_setenv(test_var, "", 1) == 0);
+ ok_string(8, "", getenv(test_var));
+
+ /* We're run by a shell script wrapper that sets resource limits such
+ that we can allocate one string of this size but not two. Note that
+ Linux doesn't support data limits, so skip if we get an unexpected
+ success here. */
+ value = xmalloc(100 * 1024);
+ memset(value, 'A', 100 * 1024 - 1);
+ value[100 * 1024 - 1] = 0;
+ ok(9, test_setenv(test_var, value, 0) == 0);
+ ok_string(10, "", getenv(test_var));
+ status = test_setenv(test_var, value, 1);
+ if (status == 0) {
+ puts("ok 11 # skip - no data limit support");
+ puts("ok 12 # skip - no data limit support");
+ } else {
+ ok(11, (status == -1) && (errno == ENOMEM));
+ ok_string(12, "", getenv(test_var));
+ }
+
+ return 0;
+}
--- /dev/null
+#! /bin/sh
+# $Id: setenv.t 7492 2006-03-19 23:07:34Z eagle $
+#
+# Wrapper around the setenv test suite to set a resource limit low enough
+# that two strings over 100KB can't both be allocated, allowing the memory
+# allocation failure code in setenv to be exercised. Done with this
+# wrapper because ulimit is more easily portable than the corresponding C
+# code.
+
+# Find where the test suite is.
+setenv=setenv.tr
+for file in ./setenv.tr lib/setenv.tr tests/lib/setenv.tr ; do
+ [ -x $file ] && setenv=$file
+done
+
+ulimit -d 150
+exec $setenv
--- /dev/null
+/* $Id: snprintf-t.c 7510 2006-04-02 18:31:51Z eagle $ */
+/* snprintf test suite. */
+
+#include "config.h"
+#include "clibrary.h"
+
+#include "libtest.h"
+
+int test_snprintf(char *str, size_t count, const char *fmt, ...);
+int test_vsnprintf(char *str, size_t count, const char *fmt, va_list args);
+
+static const char string[] = "abcdefghijklmnopqrstuvwxyz0123456789";
+
+static const char *const fp_formats[] = {
+ "%-1.5f", "%1.5f", "%31.9f", "%10.5f", "% 10.5f", "%+22.9f",
+ "%+4.9f", "%01.3f", "%3.1f", "%3.2f", "%.0f", "%.1f",
+ "%f", NULL
+};
+static const char *const int_formats[] = {
+ "%-1.5d", "%1.5d", "%31.9d", "%5.5d", "%10.5d", "% 10.5d",
+ "%+22.30d", "%01.3d", "%4d", "%d", "%ld", NULL
+};
+static const char *const uint_formats[] = {
+ "%-1.5lu", "%1.5lu", "%31.9lu", "%5.5lu", "%10.5lu", "% 10.5lu",
+ "%+6.30lu", "%01.3lu", "%4lu", "%lu", "%4lx", "%4lX",
+ "%01.3lx", "%1lo", NULL
+};
+static const char *const llong_formats[] = {
+ "%lld", "%-1.5lld", "%1.5lld", "%123.9lld", "%5.5lld",
+ "%10.5lld", "% 10.5lld", "%+22.33lld", "%01.3lld", "%4lld",
+ NULL
+};
+static const char *const ullong_formats[] = {
+ "%llu", "%-1.5llu", "%1.5llu", "%123.9llu", "%5.5llu",
+ "%10.5llu", "% 10.5llu", "%+22.33llu", "%01.3llu", "%4llu",
+ "%llx", "%llo", NULL
+};
+
+static const double fp_nums[] = {
+ -1.5, 134.21, 91340.2, 341.1234, 0203.9, 0.96, 0.996, 0.9996, 1.996,
+ 4.136, 0
+};
+static long int_nums[] = {
+ -1, 134, 91340, 341, 0203, 0
+};
+static unsigned long uint_nums[] = {
+ (unsigned long) -1, 134, 91340, 341, 0203, 0
+};
+static long long llong_nums[] = {
+ ~(long long) 0, /* All-1 bit pattern. */
+ (~(unsigned long long) 0) >> 1, /* Largest signed long long. */
+ -150, 134, 91340, 341,
+ 0
+};
+static unsigned long long ullong_nums[] = {
+ ~(unsigned long long) 0, /* All-1 bit pattern. */
+ (~(unsigned long long) 0) >> 1, /* Largest signed long long. */
+ 134, 91340, 341,
+ 0
+};
+
+static void
+test_format(int n, bool truncate, const char *expected, int count,
+ const char *format, ...)
+{
+ char buf[128];
+ int result;
+ va_list args;
+
+ va_start(args, format);
+ result = test_vsnprintf(buf, truncate ? 32 : sizeof(buf), format, args);
+ va_end(args);
+ if (!strcmp(buf, expected) && result == count) {
+ printf("ok %d\n", n);
+ } else {
+ printf("not ok %d\n", n);
+ printf(" format: %s\n", format);
+ if (strcmp(buf, expected))
+ printf(" saw: %s\n want: %s\n", buf, expected);
+ if (result != count) printf(" %d != %d\n", result, count);
+ }
+}
+
+int
+main(void)
+{
+ int n, i, count;
+ unsigned int j;
+ long lcount;
+ char lgbuf[128];
+
+ printf("%d\n",
+ (25 + (ARRAY_SIZE(fp_formats) - 1) * ARRAY_SIZE(fp_nums)
+ + (ARRAY_SIZE(int_formats) - 1) * ARRAY_SIZE(int_nums)
+ + (ARRAY_SIZE(uint_formats) - 1) * ARRAY_SIZE(uint_nums)
+ + (ARRAY_SIZE(llong_formats) - 1) * ARRAY_SIZE(llong_nums)
+ + (ARRAY_SIZE(ullong_formats) - 1) * ARRAY_SIZE(ullong_nums)));
+
+ ok(1, test_snprintf(NULL, 0, "%s", "abcd") == 4);
+ ok(2, test_snprintf(NULL, 0, "%d", 20) == 2);
+ ok(3, test_snprintf(NULL, 0, "Test %.2s", "abcd") == 7);
+ ok(4, test_snprintf(NULL, 0, "%c", 'a') == 1);
+ ok(5, test_snprintf(NULL, 0, "") == 0);
+
+ test_format(6, true, "abcd", 4, "%s", "abcd");
+ test_format(7, true, "20", 2, "%d", 20);
+ test_format(8, true, "Test ab", 7, "Test %.2s", "abcd");
+ test_format(9, true, "a", 1, "%c", 'a');
+ test_format(10, true, "", 0, "");
+ test_format(11, true, "abcdefghijklmnopqrstuvwxyz01234", 36, "%s",
+ string);
+ test_format(12, true, "abcdefghij", 10, "%.10s", string);
+ test_format(13, true, " abcdefghij", 12, "%12.10s", string);
+ test_format(14, true, " abcdefghijklmnopqrstuvwxyz0", 40, "%40s",
+ string);
+ test_format(15, true, "abcdefghij ", 14, "%-14.10s", string);
+ test_format(16, true, " abcdefghijklmnopq", 50, "%50s",
+ string);
+ test_format(17, true, "%abcd%", 6, "%%%0s%%", "abcd");
+ test_format(18, true, "", 0, "%.0s", string);
+ test_format(19, true, "abcdefghijklmnopqrstuvwxyz 444", 32, "%.26s %d",
+ string, 4444);
+ test_format(20, true, "abcdefghijklmnopqrstuvwxyz -2.", 32,
+ "%.26s %.1f", string, -2.5);
+ test_format(21, true, "abcdefghij4444", 14, "%.10s%n%d", string, &count,
+ 4444);
+ ok(22, count == 10);
+ test_format(23, true, "abcdefghijklmnopqrstuvwxyz01234", 36, "%n%s%ln",
+ &count, string, &lcount);
+ ok(24, count == 0);
+ ok(25, lcount == 31);
+
+ n = 25;
+ for (i = 0; fp_formats[i] != NULL; i++)
+ for (j = 0; j < ARRAY_SIZE(fp_nums); j++) {
+ count = sprintf(lgbuf, fp_formats[i], fp_nums[j]);
+ test_format(++n, false, lgbuf, count, fp_formats[i], fp_nums[j]);
+ }
+ for (i = 0; int_formats[i] != NULL; i++)
+ for (j = 0; j < ARRAY_SIZE(int_nums); j++) {
+ count = sprintf(lgbuf, int_formats[i], int_nums[j]);
+ test_format(++n, false, lgbuf, count, int_formats[i],
+ int_nums[j]);
+ }
+ for (i = 0; uint_formats[i] != NULL; i++)
+ for (j = 0; j < ARRAY_SIZE(uint_nums); j++) {
+ count = sprintf(lgbuf, uint_formats[i], uint_nums[j]);
+ test_format(++n, false, lgbuf, count, uint_formats[i],
+ uint_nums[j]);
+ }
+ for (i = 0; llong_formats[i] != NULL; i++)
+ for (j = 0; j < ARRAY_SIZE(llong_nums); j++) {
+ count = sprintf(lgbuf, llong_formats[i], llong_nums[j]);
+ test_format(++n, false, lgbuf, count, llong_formats[i],
+ llong_nums[j]);
+ }
+ for (i = 0; ullong_formats[i] != NULL; i++)
+ for (j = 0; j < ARRAY_SIZE(ullong_nums); j++) {
+ count = sprintf(lgbuf, ullong_formats[i], ullong_nums[j]);
+ test_format(++n, false, lgbuf, count, ullong_formats[i],
+ ullong_nums[j]);
+ }
+
+ return 0;
+}
--- /dev/null
+/* $Id: strerror-t.c 5559 2002-08-11 23:43:48Z rra $ */
+/* strerror test suite. */
+
+#include "config.h"
+#include "clibrary.h"
+#include <errno.h>
+
+#include "libtest.h"
+
+const char *test_strerror(int);
+
+int
+main(void)
+{
+ puts("5");
+
+#if HAVE_STRERROR
+ ok_string(1, strerror(EACCES), test_strerror(EACCES));
+ ok_string(2, strerror(0), test_strerror(0));
+#else
+ ok(1, strerror(EACCES) != NULL);
+ ok(2, strerror(0) != NULL);
+#endif
+ ok_string(3, "Error code 77777", test_strerror(77777));
+ ok_string(4, "Error code -4000", test_strerror(-4000));
+ ok_string(5, "Error code -100000", test_strerror(-100000));
+
+ return 0;
+}
--- /dev/null
+/* $Id: strlcat-t.c 5656 2002-08-25 21:54:57Z rra $ */
+/* strlcat test suite. */
+
+#include "config.h"
+#include "clibrary.h"
+
+#include "libtest.h"
+
+size_t test_strlcat(char *, const char *, size_t);
+
+int
+main(void)
+{
+ char buffer[10] = "";
+
+ puts("27");
+
+ ok_int(1, 3, test_strlcat(buffer, "foo", sizeof(buffer)));
+ ok_string(2, "foo", buffer);
+ ok_int(3, 7, test_strlcat(buffer, " bar", sizeof(buffer)));
+ ok_string(4, "foo bar", buffer);
+ ok_int(5, 9, test_strlcat(buffer, "!!", sizeof(buffer)));
+ ok_string(6, "foo bar!!", buffer);
+ ok_int(7, 10, test_strlcat(buffer, "!", sizeof(buffer)));
+ ok_string(8, "foo bar!!", buffer);
+ ok(9, buffer[9] == '\0');
+ buffer[0] = '\0';
+ ok_int(10, 11, test_strlcat(buffer, "hello world", sizeof(buffer)));
+ ok_string(11, "hello wor", buffer);
+ ok(12, buffer[9] == '\0');
+ buffer[0] = '\0';
+ ok_int(13, 7, test_strlcat(buffer, "sausage", 5));
+ ok_string(14, "saus", buffer);
+ ok_int(15, 14, test_strlcat(buffer, "bacon eggs", sizeof(buffer)));
+ ok_string(16, "sausbacon", buffer);
+
+ /* Make sure that with a size of 0, the destination isn't changed. */
+ ok_int(17, 11, test_strlcat(buffer, "!!", 0));
+ ok_string(18, "sausbacon", buffer);
+
+ /* Now play with empty strings. */
+ ok_int(19, 9, test_strlcat(buffer, "", 0));
+ ok_string(20, "sausbacon", buffer);
+ buffer[0] = '\0';
+ ok_int(21, 0, test_strlcat(buffer, "", sizeof(buffer)));
+ ok_string(22, "", buffer);
+ ok_int(23, 3, test_strlcat(buffer, "foo", 2));
+ ok_string(24, "f", buffer);
+ ok(25, buffer[1] == '\0');
+ ok_int(26, 1, test_strlcat(buffer, "", sizeof(buffer)));
+ ok(27, buffer[1] == '\0');
+
+ return 0;
+}
--- /dev/null
+/* $Id: strlcpy-t.c 5568 2002-08-12 02:06:44Z rra $ */
+/* strlcpy test suite. */
+
+#include "config.h"
+#include "clibrary.h"
+
+#include "libtest.h"
+
+size_t test_strlcpy(char *, const char *, size_t);
+
+int
+main(void)
+{
+ char buffer[10];
+
+ puts("23");
+
+ ok_int(1, 3, test_strlcpy(buffer, "foo", sizeof(buffer)));
+ ok_string(2, "foo", buffer);
+ ok_int(3, 9, test_strlcpy(buffer, "hello wor", sizeof(buffer)));
+ ok_string(4, "hello wor", buffer);
+ ok_int(5, 10, test_strlcpy(buffer, "world hell", sizeof(buffer)));
+ ok_string(6, "world hel", buffer);
+ ok(7, buffer[9] == '\0');
+ ok_int(8, 11, test_strlcpy(buffer, "hello world", sizeof(buffer)));
+ ok_string(9, "hello wor", buffer);
+ ok(10, buffer[9] == '\0');
+
+ /* Make sure that with a size of 0, the destination isn't changed. */
+ ok_int(11, 3, test_strlcpy(buffer, "foo", 0));
+ ok_string(12, "hello wor", buffer);
+
+ /* Now play with empty strings. */
+ ok_int(13, 0, test_strlcpy(buffer, "", 0));
+ ok_string(14, "hello wor", buffer);
+ ok_int(15, 0, test_strlcpy(buffer, "", sizeof(buffer)));
+ ok_string(16, "", buffer);
+ ok_int(17, 3, test_strlcpy(buffer, "foo", 2));
+ ok_string(18, "f", buffer);
+ ok(19, buffer[1] == '\0');
+ ok_int(20, 0, test_strlcpy(buffer, "", 1));
+ ok(21, buffer[0] == '\0');
+
+ /* Finally, check using strlcpy as strlen. */
+ ok_int(22, 3, test_strlcpy(NULL, "foo", 0));
+ ok_int(23, 11, test_strlcpy(NULL, "hello world", 0));
+
+ return 0;
+}
--- /dev/null
+/* $Id: tst-t.c 7262 2005-06-06 04:45:48Z eagle $ */
+/* Test suite for ternary search tries. */
+
+#include "config.h"
+#include "clibrary.h"
+
+#include "inn/messages.h"
+#include "inn/tst.h"
+#include "libinn.h"
+#include "libtest.h"
+
+/* Used for strings of unsigned characters. */
+#define U (const unsigned char *)
+
+/* An unsigned char version of strlen. */
+#define ustrlen(s) strlen((const char *) s)
+
+int
+main(void)
+{
+ struct tst *tst;
+ FILE *words;
+ unsigned char buffer[1024];
+ bool reported;
+ void *existing;
+ unsigned char *word;
+
+ char test[] = "test";
+ char t[] = "t";
+ char foo[] = "foo";
+ char testing[] = "testing";
+ char Strange[] = "Strange";
+ char change[] = "çhange";
+
+ puts("38");
+
+ tst = tst_init(2);
+ ok(1, tst != NULL);
+ ok(2, tst_insert(tst, U"test", test, 0, NULL) == TST_OK);
+ ok_string(3, "test", tst_search(tst, U"test"));
+ ok(4, tst_insert(tst, U"test", foo, 0, &existing) == TST_DUPLICATE_KEY);
+ ok_string(5, "test", existing);
+ ok(6, tst_insert(tst, U"test", foo, TST_REPLACE, &existing) == TST_OK);
+ ok_string(7, "test", existing);
+ ok_string(8, "foo", tst_search(tst, U"test"));
+ ok(9, tst_insert(tst, U"testing", testing, 0, NULL) == TST_OK);
+ ok(10, tst_insert(tst, U"t", t, 0, NULL) == TST_OK);
+ ok(11, tst_insert(tst, U"Strange", Strange, 0, NULL) == TST_OK);
+ ok(12, tst_insert(tst, U"çhange", change, 0, NULL) == TST_OK);
+ ok(13, tst_insert(tst, U"", foo, 0, NULL) == TST_NULL_KEY);
+ ok(14, tst_insert(tst, NULL, foo, 0, NULL) == TST_NULL_KEY);
+ ok_string(15, "testing", tst_search(tst, U"testing"));
+ ok_string(16, "t", tst_search(tst, U"t"));
+ ok_string(17, "Strange", tst_search(tst, U"Strange"));
+ ok_string(18, "çhange", tst_search(tst, U"çhange"));
+ ok_string(19, "foo", tst_search(tst, U"test"));
+ ok(20, tst_search(tst, U"") == NULL);
+ ok(21, tst_search(tst, U"Peter") == NULL);
+ ok(22, tst_search(tst, U"foo") == NULL);
+ ok(23, tst_search(tst, U"te") == NULL);
+ ok_string(24, "Strange", tst_delete(tst, U"Strange"));
+ ok(25, tst_search(tst, U"Strange") == NULL);
+ ok_string(26, "t", tst_delete(tst, U"t"));
+ ok(27, tst_search(tst, U"t") == NULL);
+ ok_string(28, "testing", tst_search(tst, U"testing"));
+ ok_string(29, "foo", tst_search(tst, U"test"));
+ ok_string(30, "testing", tst_delete(tst, U"testing"));
+ ok_string(31, "foo", tst_search(tst, U"test"));
+ ok_string(32, "çhange", tst_delete(tst, U"çhange"));
+ ok_string(33, "foo", tst_delete(tst, U"test"));
+ ok(34, tst_search(tst, NULL) == NULL);
+ ok(35, tst_delete(tst, NULL) == NULL);
+ tst_cleanup(tst);
+ ok(36, true);
+
+ words = fopen("/usr/dict/words", "r");
+ if (words == NULL)
+ words = fopen("/usr/share/dict/words", "r");
+ if (words == NULL) {
+ puts("ok 37 # skip\nok 38 # skip");
+ exit(0);
+ }
+
+ tst = tst_init(1000);
+ reported = false;
+ if (tst == NULL)
+ printf("not ");
+ else {
+ while (fgets((char *) buffer, sizeof(buffer), words)) {
+ buffer[ustrlen(buffer) - 1] = '\0';
+ if (buffer[0] == '\0')
+ continue;
+ word = (unsigned char *) xstrdup((char *) buffer);
+ if (tst_insert(tst, buffer, word, 0, NULL) != TST_OK) {
+ if (!reported)
+ printf("not ");
+ reported = true;
+ }
+ }
+ }
+ puts("ok 37");
+
+ if (fseek(words, 0, SEEK_SET) < 0)
+ sysdie("Unable to rewind words file");
+ reported = false;
+ if (tst == NULL)
+ printf("not ");
+ else {
+ while (fgets((char *) buffer, sizeof(buffer), words)) {
+ buffer[ustrlen(buffer) - 1] = '\0';
+ if (buffer[0] == '\0')
+ continue;
+ word = tst_search(tst, buffer);
+ if (word == NULL || strcmp((char *) word, buffer) != 0) {
+ if (!reported)
+ printf("not ");
+ reported = true;
+ }
+ word = tst_delete(tst, buffer);
+ if (word == NULL || strcmp((char *) word, buffer) != 0) {
+ if (!reported)
+ printf("not ");
+ reported = true;
+ }
+ free(word);
+ }
+ }
+ tst_cleanup(tst);
+ puts("ok 38");
+
+ return 0;
+}
--- /dev/null
+/* $Id: uwildmat-t.c 7262 2005-06-06 04:45:48Z eagle $
+**
+** wildmat test suite.
+**
+** As of March 11, 2001, this test suite achieves 100% coverage of the
+** wildmat source code at that time.
+*/
+
+#include "clibrary.h"
+#include "libinn.h"
+
+static void
+test_r(int n, const char *text, const char *pattern, bool matches)
+{
+ bool matched;
+
+ matched = uwildmat(text, pattern);
+ printf("%sok %d\n", matched == matches ? "" : "not ", n);
+ if (matched != matches)
+ printf(" %s\n %s\n expected %d\n", text, pattern, matches);
+}
+
+static void
+test_p(int n, const char *text, const char *pattern, enum uwildmat matches)
+{
+ enum uwildmat matched;
+
+ matched = uwildmat_poison(text, pattern);
+ printf("%sok %d\n", matched == matches ? "" : "not ", n);
+ if (matched != matches)
+ printf(" %s\n %s\n expected %d got %d\n", text, pattern,
+ (int) matches, (int) matched);
+}
+
+static void
+test_s(int n, const char *text, const char *pattern, bool matches)
+{
+ bool matched;
+
+ matched = uwildmat_simple(text, pattern);
+ printf("%sok %d\n", matched == matches ? "" : "not ", n);
+ if (matched != matches)
+ printf(" %s\n %s\n expected %d\n", text, pattern, matches);
+}
+
+int
+main(void)
+{
+ puts("166");
+
+ /* Basic wildmat features. */
+ test_r( 1, "foo", "foo", true);
+ test_r( 2, "foo", "bar", false);
+ test_r( 3, "", "", true);
+ test_r( 4, "foo", "???", true);
+ test_r( 5, "foo", "??", false);
+ test_r( 6, "foo", "*", true);
+ test_r( 7, "foo", "f*", true);
+ test_r( 8, "foo", "*f", false);
+ test_r( 9, "foo", "*foo*", true);
+ test_r( 10, "foobar", "*ob*a*r*", true);
+ test_r( 11, "aaaaaaabababab", "*ab", true);
+ test_r( 12, "foo*", "foo\\*", true);
+ test_r( 13, "foobar", "foo\\*bar", false);
+ test_r( 14, "\\", "\\\\", true);
+ test_r( 15, "ball", "*[al]?", true);
+ test_r( 16, "ten", "[ten]", false);
+ test_r( 17, "ten", "**[^te]", true);
+ test_r( 18, "ten", "**[^ten]", false);
+ test_r( 19, "ten", "t[a-g]n", true);
+ test_r( 20, "ten", "t[^a-g]n", false);
+ test_r( 21, "ton", "t[^a-g]n", true);
+ test_r( 22, "]", "]", true);
+ test_r( 23, "a]b", "a[]]b", true);
+ test_r( 24, "a-b", "a[]-]b", true);
+ test_r( 25, "a]b", "a[]-]b", true);
+ test_r( 26, "aab", "a[]-]b", false);
+ test_r( 27, "aab", "a[]a-]b", true);
+
+ /* Multiple and negation. */
+ test_r( 28, "foo", "!foo", false);
+ test_r( 29, "foo", "!bar", false);
+ test_r( 30, "foo", "*,!foo", false);
+ test_r( 31, "foo", "*,!bar", true);
+ test_r( 32, "foo", "foo,bar", true);
+ test_r( 33, "bar", "foo,bar", true);
+ test_r( 34, "baz", "foo,bar", false);
+ test_r( 35, "baz", "foo,ba?", true);
+ test_r( 36, "", "!", false);
+ test_r( 37, "foo", "!", false);
+ test_r( 38, "a", "a,!b,c", true);
+ test_r( 39, "b", "a,!b,c", false);
+ test_r( 40, "c", "a,!b,c", true);
+ test_r( 41, "ab", "a*,!ab", false);
+ test_r( 42, "abc", "a*,!ab", true);
+ test_r( 43, "dabc", "a*,!ab", false);
+ test_r( 44, "abc", "a*,!ab*,abc", true);
+ test_r( 45, "", ",", true);
+ test_r( 46, "a", ",a", true);
+ test_r( 47, "a", "a,,,", true);
+ test_r( 48, "b", ",a", false);
+ test_r( 49, "b", "a,,,", false);
+ test_r( 50, "a,b", "a\\,b", true);
+ test_r( 51, "a,b", "a\\\\,b", false);
+ test_r( 52, "a\\", "a\\\\,b", true);
+ test_r( 53, "a\\,b", "a\\\\,b", false);
+ test_r( 54, "a\\,b", "a\\\\\\,b", true);
+ test_r( 55, ",", "\\,", true);
+ test_r( 56, ",\\", "\\,", false);
+ test_r( 57, ",\\", "\\,\\\\,", true);
+ test_r( 58, "", "\\,\\\\,", true);
+ test_r( 59, "", "\\,,!", false);
+ test_r( 60, "", "\\,!,", true);
+
+ /* Various additional tests. */
+ test_r( 61, "acrt", "a[c-c]st", false);
+ test_r( 62, "]", "[^]-]", false);
+ test_r( 63, "a", "[^]-]", true);
+ test_r( 64, "", "\\", false);
+ test_r( 65, "\\", "\\", false);
+ test_r( 66, "foo", "*,@foo", true);
+ test_r( 67, "@foo", "@foo", true);
+ test_r( 68, "foo", "@foo", false);
+ test_r( 69, "[ab]", "\\[ab]", true);
+ test_r( 70, "?a?b", "\\??\\?b", true);
+ test_r( 71, "abc", "\\a\\b\\c", true);
+
+ /* Poison negation. */
+ test_p( 72, "abc", "*", UWILDMAT_MATCH);
+ test_p( 73, "abc", "def", UWILDMAT_FAIL);
+ test_p( 74, "abc", "*,!abc", UWILDMAT_FAIL);
+ test_p( 75, "a", "*,@a", UWILDMAT_POISON);
+ test_p( 76, "ab", "*,@a*,ab", UWILDMAT_MATCH);
+ test_p( 77, "ab", "*,@a**,!ab", UWILDMAT_FAIL);
+ test_p( 78, "@ab", "\\@ab", UWILDMAT_MATCH);
+ test_p( 79, "@ab", "@\\@ab", UWILDMAT_POISON);
+
+ /* UTF-8 characters. */
+ test_r( 80, "S\303\256ne", "S\303\256ne", true);
+ test_r( 81, "S\303\256ne", "S\303\257ne", false);
+ test_r( 82, "S\303\256ne", "S?ne", true);
+ test_r( 83, "S\303\256ne", "S*e", true);
+ test_r( 84, "S\303\256ne", "S[a-\330\200]ne", true);
+ test_r( 85, "S\303\256ne", "S[a-\300\256]ne", false);
+ test_r( 86, "S\303\256ne", "S[^\1-\177]ne", true);
+ test_r( 87, "S\303\256ne", "S[0\303\256$]ne", true);
+ test_r( 88, "\2", "[\1-\3]", true);
+ test_r( 89, "\330\277", "[\330\276-\331\200]", true);
+ test_r( 90, "\337\277", "[\337\276-\350\200\200]", true);
+ test_r( 91, "\357\277\277", "[\357\277\276-\364\200\200\200]", true);
+ test_r( 92, "\357\276\277", "[\357\277\276-\364\200\200\200]", false);
+ test_r( 93, "\367\277\277\277",
+ "[\310\231-\372\200\200\200\200]", true);
+ test_r( 94, "\373\277\277\277\277",
+ "[\1-\375\200\200\200\200\200]", true);
+ test_r( 95, "\375\200\200\200\200\200",
+ "[\5-\375\200\200\200\200\200]", true);
+ test_r( 96, "\375\277\277\277\277\276",
+ "[\375\277\277\277\277\275-\375\277\277\277\277\277]", true);
+ test_r( 97, "b\357\277\277a", "b?a", true);
+ test_r( 98, "b\367\277\277\277a", "b?a", true);
+ test_r( 99, "b\373\277\277\277\277a", "b?a", true);
+ test_r(100, "b\375\277\277\277\277\276a", "b?a", true);
+ test_r(101, "\357\240\275S\313\212\375\206\203\245\260\211",
+ "????", true);
+ test_r(102, "S\303\256ne", "S\\\303\256ne", true);
+ test_r(103, "s", "[^\330\277-\375\277\277\277\277\277]", true);
+ test_r(104, "\367\277\277\277",
+ "[^\330\277-\375\277\277\277\277\277]", false);
+
+ /* Malformed UTF-8. */
+ test_r(105, "S\303\256ne", "S?\256ne", false);
+ test_r(106, "\303\303", "?", false);
+ test_r(107, "\303\303", "??", true);
+ test_r(108, "\200", "[\177-\201]", true);
+ test_r(109, "abc\206d", "*\206d", true);
+ test_r(110, "\303\206", "*\206", false);
+ test_r(111, "\40", "\240", false);
+ test_r(112, "\323", "[a-\377]", true);
+ test_r(113, "\376\277\277\277\277\277", "?", false);
+ test_r(114, "\376\277\277\277\277\277", "??????", true);
+ test_r(115, "\377\277\277\277\277\277", "?", false);
+ test_r(116, "\377\277\277\277\277\277", "??????", true);
+ test_r(117, "\303\323\206", "??", true);
+ test_r(118, "\206", "[\341\206f]", true);
+ test_r(119, "f", "[\341\206f]", true);
+ test_r(120, "\207", "[\341\206-\277]", true);
+ test_r(121, "\207", "[\341\206\206-\277]", false);
+ test_r(122, "\300", "[\277-\341\206]", true);
+ test_r(123, "\206", "[\277-\341\206]", true);
+ test_r(124, "\341\206", "[\341\206-\277]?", true);
+
+ /* Additional tests, including some malformed wildmats. */
+ test_r(125, "ab", "a[]b", false);
+ test_r(126, "a[]b", "a[]b", false);
+ test_r(127, "ab[", "ab[", false);
+ test_r(128, "ab", "[^", false);
+ test_r(129, "ab", "[-", false);
+ test_r(130, "-", "[-]", true);
+ test_r(131, "-", "[a-", false);
+ test_r(132, "-", "[^a-", false);
+ test_r(133, "-", "[--A]", true);
+ test_r(134, "5", "[--A]", true);
+ test_r(135, "\303\206", "[--A]", false);
+ test_r(136, " ", "[ --]", true);
+ test_r(137, "$", "[ --]", true);
+ test_r(138, "-", "[ --]", true);
+ test_r(139, "0", "[ --]", false);
+ test_r(140, "-", "[---]", true);
+ test_r(141, "-", "[------]", true);
+ test_r(142, "j", "[a-e-n]", false);
+ test_r(143, "a", "[^------]", true);
+ test_r(144, "[", "[]-a]", false);
+ test_r(145, "^", "[]-a]", true);
+ test_r(146, "^", "[^]-a]", false);
+ test_r(147, "[", "[^]-a]", true);
+ test_r(148, "^", "[a^bc]", true);
+ test_r(149, "-b]", "[a-]b]", true);
+ test_r(150, "\\]", "[\\]]", true);
+ test_r(151, "]", "[\\-^]", true);
+ test_r(152, "[", "[\\-^]", false);
+ test_r(153, "G", "[A-\\]", true);
+ test_r(154, "aaabbb", "b*a", false);
+ test_r(155, "aabcaa", "*ba*", false);
+ test_r(156, ",", "[,]", true);
+ test_r(157, ",", "[\\,]", true);
+ test_r(158, "\\", "[\\,]", true);
+ test_r(159, "-", "[,-.]", true);
+ test_r(160, "+", "[,-.]", false);
+ test_r(161, "-.]", "[,-.]", false);
+
+ /* Tests for the wildmat_simple interface. */
+ test_s(162, "ab,cd", "ab,cd", true);
+ test_s(163, "ab", "ab,cd", false);
+ test_s(164, "!aaabbb", "!a*b*", true);
+ test_s(165, "ccc", "*,!a*", false);
+ test_s(166, "foo", "*", true);
+
+ return 0;
+}
--- /dev/null
+/* $Id: vector-t.c 5678 2002-08-28 23:21:32Z rra $ */
+/* vector test suite. */
+
+#include "config.h"
+#include "clibrary.h"
+
+#include "inn/vector.h"
+#include "libinn.h"
+#include "libtest.h"
+
+int
+main(void)
+{
+ struct vector *vector;
+ struct cvector *cvector;
+ const char cstring[] = "This is a\ttest. ";
+ const char tabs[] = "test\t\ting\t";
+ static const char nulls1[] = "This\0is\0a\0test.";
+ static const char nulls2[] = "This is a\t\0es\0. ";
+ char empty[] = "";
+ char *string;
+ char *p;
+
+ puts("87");
+
+ vector = vector_new();
+ ok(1, vector != NULL);
+ vector_add(vector, cstring);
+ ok_int(2, 1, vector->count);
+ ok(3, vector->strings[0] != cstring);
+ vector_resize(vector, 4);
+ ok_int(4, 4, vector->allocated);
+ vector_add(vector, cstring);
+ vector_add(vector, cstring);
+ vector_add(vector, cstring);
+ ok_int(5, 4, vector->allocated);
+ ok_int(6, 4, vector->count);
+ ok(7, vector->strings[1] != vector->strings[2]);
+ ok(8, vector->strings[2] != vector->strings[3]);
+ ok(9, vector->strings[3] != vector->strings[0]);
+ ok(10, vector->strings[0] != cstring);
+ vector_clear(vector);
+ ok_int(11, 0, vector->count);
+ ok_int(12, 4, vector->allocated);
+ string = xstrdup(cstring);
+ vector_add(vector, cstring);
+ vector_add(vector, string);
+ ok_int(13, 2, vector->count);
+ ok(14, vector->strings[1] != string);
+ vector_resize(vector, 1);
+ ok_int(15, 1, vector->count);
+ ok(16, vector->strings[0] != cstring);
+ vector_free(vector);
+ free(string);
+
+ cvector = cvector_new();
+ ok(17, cvector != NULL);
+ cvector_add(cvector, cstring);
+ ok_int(18, 1, cvector->count);
+ ok(19, cvector->strings[0] == cstring);
+ cvector_resize(cvector, 4);
+ ok_int(20, 4, cvector->allocated);
+ cvector_add(cvector, cstring);
+ cvector_add(cvector, cstring);
+ cvector_add(cvector, cstring);
+ ok_int(21, 4, cvector->allocated);
+ ok_int(22, 4, cvector->count);
+ ok(23, cvector->strings[1] == cvector->strings[2]);
+ ok(24, cvector->strings[2] == cvector->strings[3]);
+ ok(25, cvector->strings[3] == cvector->strings[0]);
+ ok(26, cvector->strings[0] == cstring);
+ cvector_clear(cvector);
+ ok_int(27, 0, cvector->count);
+ ok_int(28, 4, cvector->allocated);
+ string = xstrdup(cstring);
+ cvector_add(cvector, cstring);
+ cvector_add(cvector, string);
+ ok_int(29, 2, cvector->count);
+ ok(30, cvector->strings[1] == string);
+ cvector_resize(cvector, 1);
+ ok_int(31, 1, cvector->count);
+ ok(32, cvector->strings[0] == cstring);
+ cvector_free(cvector);
+ free(string);
+
+ vector = vector_split_space("This is a\ttest. ", NULL);
+ ok_int(33, 4, vector->count);
+ ok_int(34, 4, vector->allocated);
+ ok_string(35, "This", vector->strings[0]);
+ ok_string(36, "is", vector->strings[1]);
+ ok_string(37, "a", vector->strings[2]);
+ ok_string(38, "test.", vector->strings[3]);
+ vector_add(vector, cstring);
+ ok_string(39, cstring, vector->strings[4]);
+ ok(40, vector->strings[4] != cstring);
+ ok_int(41, 5, vector->allocated);
+ vector = vector_split(cstring, 't', vector);
+ ok_int(42, 3, vector->count);
+ ok_int(43, 5, vector->allocated);
+ ok_string(44, "This is a\t", vector->strings[0]);
+ ok_string(45, "es", vector->strings[1]);
+ ok_string(46, ". ", vector->strings[2]);
+ ok(47, vector->strings[0] != cstring);
+ p = vector_join(vector, "fe");
+ ok_string(48, "This is a\tfeesfe. ", p);
+ free(p);
+ vector_free(vector);
+
+ string = xstrdup(cstring);
+ cvector = cvector_split_space(string, NULL);
+ ok_int(49, 4, cvector->count);
+ ok_int(50, 4, cvector->allocated);
+ ok_string(51, "This", cvector->strings[0]);
+ ok_string(52, "is", cvector->strings[1]);
+ ok_string(53, "a", cvector->strings[2]);
+ ok_string(54, "test.", cvector->strings[3]);
+ ok(55, memcmp(string, nulls1, 16) == 0);
+ cvector_add(cvector, cstring);
+ ok(56, cvector->strings[4] == cstring);
+ ok_int(57, 5, cvector->allocated);
+ free(string);
+ string = xstrdup(cstring);
+ cvector = cvector_split(string, 't', cvector);
+ ok_int(58, 3, cvector->count);
+ ok_int(59, 5, cvector->allocated);
+ ok_string(60, "This is a\t", cvector->strings[0]);
+ ok_string(61, "es", cvector->strings[1]);
+ ok_string(62, ". ", cvector->strings[2]);
+ ok(63, cvector->strings[0] == string);
+ ok(64, memcmp(string, nulls2, 18) == 0);
+ p = cvector_join(cvector, "oo");
+ ok_string(65, "This is a\tooesoo. ", p);
+ free(p);
+ cvector_free(cvector);
+ free(string);
+
+ vector = vector_split("", ' ', NULL);
+ ok_int(66, 1, vector->count);
+ ok_string(67, "", vector->strings[0]);
+ vector_free(vector);
+ cvector = cvector_split(empty, ' ', NULL);
+ ok_int(68, 1, cvector->count);
+ ok_string(69, "", vector->strings[0]);
+ cvector_free(cvector);
+
+ vector = vector_split_space("", NULL);
+ ok_int(70, 0, vector->count);
+ vector_free(vector);
+ cvector = cvector_split_space(empty, NULL);
+ ok_int(71, 0, cvector->count);
+ cvector_free(cvector);
+
+ vector = vector_split(tabs, '\t', NULL);
+ ok_int(72, 4, vector->count);
+ ok_string(73, "test", vector->strings[0]);
+ ok_string(74, "", vector->strings[1]);
+ ok_string(75, "ing", vector->strings[2]);
+ ok_string(76, "", vector->strings[3]);
+ p = vector_join(vector, "");
+ ok_string(77, "testing", p);
+ free(p);
+ vector_free(vector);
+
+ string = xstrdup(tabs);
+ cvector = cvector_split(string, '\t', NULL);
+ ok_int(78, 4, cvector->count);
+ ok_string(79, "test", cvector->strings[0]);
+ ok_string(80, "", cvector->strings[1]);
+ ok_string(81, "ing", cvector->strings[2]);
+ ok_string(82, "", cvector->strings[3]);
+ p = cvector_join(cvector, "");
+ ok_string(83, "testing", p);
+ free(p);
+ cvector_free(cvector);
+ free(string);
+
+ vector = vector_split_space("foo\nbar", NULL);
+ ok_int(84, 1, vector->count);
+ ok_string(85, "foo\nbar", vector->strings[0]);
+ vector_free(vector);
+
+ string = xstrdup("foo\nbar");
+ cvector = cvector_split_space(string, NULL);
+ ok_int(86, 1, cvector->count);
+ ok_string(87, "foo\nbar", cvector->strings[0]);
+ cvector_free(cvector);
+
+ return 0;
+}
--- /dev/null
+/* $Id: wire-t.c 6084 2002-12-27 07:24:55Z rra $ */
+/* wire test suite. */
+
+#include "config.h"
+#include "clibrary.h"
+#include <fcntl.h>
+#include <sys/stat.h>
+
+#include "inn/messages.h"
+#include "inn/wire.h"
+#include "libinn.h"
+#include "libtest.h"
+
+/* Read in a file and return the contents in newly allocated memory. Fills in
+ the provided stat buffer. */
+static char *
+read_file(const char *name, struct stat *st)
+{
+ int fd;
+ char *article;
+ ssize_t count;
+
+ if (stat(name, st) < 0)
+ sysdie("cannot stat %s", name);
+ article = xmalloc(st->st_size);
+ fd = open(name, O_RDONLY);
+ if (fd < 0)
+ sysdie("cannot open %s", name);
+ count = read(fd, article, st->st_size);
+ if (count < st->st_size)
+ die("unable to read all of %s", name);
+ close(fd);
+ return article;
+}
+
+
+/* Test article for wire_findbody. */
+const char ta[] = "Path: \r\nFrom: \r\n\r\n";
+
+int
+main(void)
+{
+ const char *p, *end;
+ char *article;
+ struct stat st;
+
+ puts("34");
+
+ end = ta + sizeof(ta) - 1;
+ p = end - 4;
+ ok(1, wire_findbody(ta, sizeof(ta) - 1) == end);
+ ok(2, wire_findbody(ta, sizeof(ta) - 2) == NULL);
+ ok(3, wire_findbody(ta, sizeof(ta) - 3) == NULL);
+ ok(4, wire_findbody(ta, sizeof(ta) - 4) == NULL);
+ ok(5, wire_findbody(ta, sizeof(ta) - 5) == NULL);
+ ok(6, wire_findbody(p, 4) == end);
+ ok(7, wire_findbody(p, 3) == NULL);
+ ok(8, wire_findbody(p, 2) == NULL);
+ ok(9, wire_findbody(p, 1) == NULL);
+ ok(10, wire_findbody(p, 0) == NULL);
+
+ if (access("articles/strange", F_OK) < 0)
+ if (access("lib/articles/strange", F_OK) == 0)
+ chdir("lib");
+ article = read_file("articles/strange", &st);
+
+ p = wire_findbody(article, st.st_size);
+ ok(11, strncmp(p, "Path: This is", strlen("Path: This is")) == 0);
+ p = wire_nextline(p, article + st.st_size - 1);
+ ok(12, strncmp(p, "Second: Not", strlen("Second: Not")) == 0);
+ p = wire_nextline(p, article + st.st_size - 1);
+ ok(13, p == NULL);
+ p = wire_findheader(article, st.st_size, "Path");
+ ok(14, p == article + 6);
+ p = wire_findheader(article, st.st_size, "From");
+ ok(15, strncmp(p, "This is the real", strlen("This is the real")) == 0);
+ p = wire_findheader(article, st.st_size, "SUMMARY");
+ ok(16, strncmp(p, "First text", strlen("First text")) == 0);
+ p = wire_findheader(article, st.st_size, "Header");
+ ok(17, strncmp(p, "This one is real", strlen("This one is real")) == 0);
+ p = wire_findheader(article, st.st_size, "message-id");
+ ok(18, strncmp(p, "<foo@example.com>", strlen("<foo@example.com>")) == 0);
+ p = wire_findheader(article, st.st_size, "Second");
+ ok(19, p == NULL);
+ p = wire_findheader(article, st.st_size, "suBJect");
+ ok(20, strncmp(p, "This is\rnot", strlen("This is\rnot")) == 0);
+ end = wire_endheader(p, article + st.st_size - 1);
+ ok(21, strncmp(end, "\nFrom: This is", strlen("\nFrom: This is")) == 0);
+ p = wire_findheader(article, st.st_size, "keywordS");
+ ok(22, strncmp(p, "this is --", strlen("this is --")) == 0);
+ end = wire_endheader(p, article + st.st_size - 1);
+ ok(23, strncmp(end, "\nSummary: ", strlen("\nSummary: ")) == 0);
+ p = wire_findheader(article, st.st_size, "strange");
+ ok(24, strncmp(p, "This is\n\nnot", strlen("This is\n\nnot")) == 0);
+ end = wire_endheader(p, article + st.st_size - 1);
+ ok(25, strncmp(end, "\nMessage-ID: ", strlen("\nMessage-ID: ")) == 0);
+ p = wire_findheader(article, st.st_size, "Message");
+ ok(26, p == NULL);
+
+ free(article);
+ article = read_file("articles/no-body", &st);
+
+ ok(27, wire_findbody(article, st.st_size) == NULL);
+ p = wire_findheader(article, st.st_size, "message-id");
+ ok(28, strncmp(p, "<id1@example.com>\r\n",
+ strlen("<id1@example.com>\r\n")) == 0);
+ end = wire_endheader(p, article + st.st_size - 1);
+ ok(29, end == article + st.st_size - 1);
+ ok(30, wire_nextline(p, article + st.st_size - 1) == NULL);
+
+ free(article);
+ article = read_file("articles/truncated", &st);
+
+ ok(31, wire_findbody(article, st.st_size) == NULL);
+ p = wire_findheader(article, st.st_size, "date");
+ ok(32, strncmp(p, "Mon, 23 Dec", strlen("Mon, 23 Dec")) == 0);
+ ok(33, wire_endheader(p, article + st.st_size - 1) == NULL);
+ ok(34, wire_nextline(p, article + st.st_size - 1) == NULL);
+
+ free(article);
+
+ return 0;
+}
--- /dev/null
+/* $Id: xmalloc.c 5379 2002-03-31 21:45:12Z rra $ */
+/* Test suite for xmalloc and family. */
+
+#include "config.h"
+#include "clibrary.h"
+#include <ctype.h>
+#include <errno.h>
+#include <unistd.h>
+
+/* Linux requires sys/time.h be included before sys/resource.h. */
+#if HAVE_SYS_TIME_H
+# include <sys/time.h>
+#endif
+#include <sys/resource.h>
+
+#include "inn/messages.h"
+#include "libinn.h"
+
+/* A customized error handler for checking xmalloc's support of them.
+ Prints out the error message and exits with status 1. */
+static void
+test_handler(const char *function, size_t size, const char *file, int line)
+{
+ die("%s %lu %s %d", function, (unsigned long) size, file, line);
+}
+
+/* Allocate the amount of memory given and write to all of it to make sure
+ we can, returning true if that succeeded and false on any sort of
+ detectable error. */
+static int
+test_malloc(size_t size)
+{
+ char *buffer;
+ size_t i;
+
+ buffer = xmalloc(size);
+ if (buffer == NULL)
+ return 0;
+ if (size > 0)
+ memset(buffer, 1, size);
+ for (i = 0; i < size; i++)
+ if (buffer[i] != 1)
+ return 0;
+ free(buffer);
+ return 1;
+}
+
+/* Allocate half the memory given, write to it, then reallocate to the
+ desired size, writing to the rest and then checking it all. Returns true
+ on success, false on any failure. */
+static int
+test_realloc(size_t size)
+{
+ char *buffer;
+ size_t i;
+
+ buffer = xmalloc(size / 2);
+ if (buffer == NULL)
+ return 0;
+ if (size / 2 > 0)
+ memset(buffer, 1, size / 2);
+ buffer = xrealloc(buffer, size);
+ if (buffer == NULL)
+ return 0;
+ if (size > 0)
+ memset(buffer + size / 2, 2, size - size / 2);
+ for (i = 0; i < size / 2; i++)
+ if (buffer[i] != 1)
+ return 0;
+ for (i = size / 2; i < size; i++)
+ if (buffer[i] != 2)
+ return 0;
+ free(buffer);
+ return 1;
+}
+
+/* Generate a string of the size indicated, call xstrdup on it, and then
+ ensure the result matches. Returns true on success, false on any
+ failure. */
+static int
+test_strdup(size_t size)
+{
+ char *string, *copy;
+ int match;
+
+ string = xmalloc(size);
+ if (string == NULL)
+ return 0;
+ memset(string, 1, size - 1);
+ string[size - 1] = '\0';
+ copy = xstrdup(string);
+ if (copy == NULL)
+ return 0;
+ match = strcmp(string, copy);
+ free(string);
+ free(copy);
+ return (match == 0);
+}
+
+/* Generate a string of the size indicated plus some, call xstrndup on it, and
+ then ensure the result matches. Returns true on success, false on any
+ failure. */
+static int
+test_strndup(size_t size)
+{
+ char *string, *copy;
+ int match, toomuch;
+
+ string = xmalloc(size + 1);
+ if (string == NULL)
+ return 0;
+ memset(string, 1, size - 1);
+ string[size - 1] = 2;
+ string[size] = '\0';
+ copy = xstrndup(string, size - 1);
+ if (copy == NULL)
+ return 0;
+ match = strncmp(string, copy, size - 1);
+ toomuch = strcmp(string, copy);
+ free(string);
+ free(copy);
+ return (match == 0 && toomuch != 0);
+}
+
+/* Allocate the amount of memory given and check that it's all zeroed,
+ returning true if that succeeded and false on any sort of detectable
+ error. */
+static int
+test_calloc(size_t size)
+{
+ char *buffer;
+ size_t i, nelems;
+
+ nelems = size / 4;
+ if (nelems * 4 != size)
+ return 0;
+ buffer = xcalloc(nelems, 4);
+ if (buffer == NULL)
+ return 0;
+ for (i = 0; i < size; i++)
+ if (buffer[i] != 0)
+ return 0;
+ free(buffer);
+ return 1;
+}
+
+/* Take the amount of memory to allocate in bytes as a command-line argument
+ and call test_malloc with that amount of memory. */
+int
+main(int argc, char *argv[])
+{
+ size_t size, max;
+ size_t limit = 0;
+ int willfail = 0;
+ unsigned char code;
+ struct rlimit rl;
+
+ if (argc < 3)
+ die("Usage error. Type, size, and limit must be given.");
+ errno = 0;
+ size = strtol(argv[2], 0, 10);
+ if (size == 0 && errno != 0)
+ sysdie("Invalid size");
+ errno = 0;
+ limit = strtol(argv[3], 0, 10);
+ if (limit == 0 && errno != 0)
+ sysdie("Invalid limit");
+
+ /* If a memory limit was given and we can set memory limits, set it.
+ Otherwise, exit 2, signalling to the driver that the test should be
+ skipped. We do this here rather than in the driver due to some
+ pathological problems with Linux (setting ulimit in the shell caused
+ the shell to die). */
+ if (limit > 0) {
+#if HAVE_SETRLIMIT && defined(RLIMIT_DATA)
+ rl.rlim_cur = limit;
+ rl.rlim_max = limit;
+ if (setrlimit(RLIMIT_DATA, &rl) < 0) {
+ syswarn("Can't set data limit to %lu", (unsigned long) limit);
+ exit(2);
+ }
+#else
+ warn("Data limits aren't supported.");
+ exit(2);
+#endif
+ }
+
+ /* If the code is capitalized, install our customized error handler. */
+ code = argv[1][0];
+ if (isupper(code)) {
+ xmalloc_error_handler = test_handler;
+ code = tolower(code);
+ }
+
+ /* Decide if the allocation should fail. If it should, set willfail to
+ 2, so that if it unexpectedly succeeds, we exit with a status
+ indicating that the test should be skipped. */
+ max = size;
+ if (code == 's' || code == 'n')
+ max *= 2;
+ if (limit > 0 && max > limit)
+ willfail = 2;
+
+ switch (code) {
+ case 'c': exit(test_calloc(size) ? willfail : 1);
+ case 'm': exit(test_malloc(size) ? willfail : 1);
+ case 'r': exit(test_realloc(size) ? willfail : 1);
+ case 's': exit(test_strdup(size) ? willfail : 1);
+ case 'n': exit(test_strndup(size) ? willfail : 1);
+ default:
+ die("Unknown mode %c", argv[1][0]);
+ break;
+ }
+ exit(1);
+}
--- /dev/null
+#! /bin/sh
+# $Id: xmalloc.t 5379 2002-03-31 21:45:12Z rra $
+#
+# Test suite for xmalloc and friends.
+
+# The count starts at 1 and is updated each time ok is printed. printcount
+# takes "ok" or "not ok".
+count=1
+printcount () {
+ echo "$1 $count $2"
+ count=`expr $count + 1`
+}
+
+# Run a program expected to succeed, and print ok if it does.
+runsuccess () {
+ output=`$xmalloc "$1" "$2" "$3" 2>&1 >/dev/null`
+ status=$?
+ if test $status = 0 && test -z "$output" ; then
+ printcount "ok"
+ else
+ if test $status = 2 ; then
+ printcount "ok" "# skip - no data limit support"
+ else
+ printcount "not ok"
+ echo " $output"
+ fi
+ fi
+}
+
+# Run a program expected to fail and make sure it fails with an exit status
+# of 2 and the right failure message. Strip the colon and everything after
+# it off the error message since it's system-specific.
+runfailure () {
+ output=`$xmalloc "$1" "$2" "$3" 2>&1 >/dev/null`
+ status=$?
+ output=`echo "$output" | sed 's/:.*//'`
+ if test $status = 1 && test x"$output" = x"$4" ; then
+ printcount "ok"
+ else
+ if test $status = 2 ; then
+ printcount "ok" "# skip - no data limit support"
+ else
+ printcount "not ok"
+ echo " saw: $output"
+ echo " not: $4"
+ fi
+ fi
+}
+
+# Find where the helper program is.
+xmalloc=xmalloc
+for file in ./xmalloc lib/xmalloc ../xmalloc ; do
+ [ -x $file ] && xmalloc=$file
+done
+
+# Total tests.
+echo 26
+
+# First run the tests expected to succeed.
+runsuccess "m" "21" "0"
+runsuccess "m" "128000" "0"
+runsuccess "m" "0" "0"
+runsuccess "r" "21" "0"
+runsuccess "r" "128000" "0"
+runsuccess "s" "21" "0"
+runsuccess "s" "128000" "0"
+runsuccess "n" "21" "0"
+runsuccess "n" "128000" "0"
+runsuccess "c" "24" "0"
+runsuccess "c" "128000" "0"
+
+# Now limit our memory to 96KB and then try the large ones again, all of
+# which should fail.
+runfailure "m" "128000" "96000" \
+ "failed to malloc 128000 bytes at lib/xmalloc.c line 36"
+runfailure "r" "128000" "96000" \
+ "failed to realloc 128000 bytes at lib/xmalloc.c line 62"
+runfailure "s" "64000" "96000" \
+ "failed to strdup 64000 bytes at lib/xmalloc.c line 91"
+runfailure "n" "64000" "96000" \
+ "failed to strndup 64000 bytes at lib/xmalloc.c line 115"
+runfailure "c" "128000" "96000" \
+ "failed to calloc 128000 bytes at lib/xmalloc.c line 137"
+
+# Check our custom error handler.
+runfailure "M" "128000" "96000" "malloc 128000 lib/xmalloc.c 36"
+runfailure "R" "128000" "96000" "realloc 128000 lib/xmalloc.c 62"
+runfailure "S" "64000" "96000" "strdup 64000 lib/xmalloc.c 91"
+runfailure "N" "64000" "96000" "strndup 64000 lib/xmalloc.c 115"
+runfailure "C" "128000" "96000" "calloc 128000 lib/xmalloc.c 137"
+
+# Check the smaller ones again just for grins.
+runsuccess "m" "21" "96000"
+runsuccess "r" "32" "96000"
+runsuccess "s" "64" "96000"
+runsuccess "n" "20" "96000"
+runsuccess "c" "24" "96000"
--- /dev/null
+/* $Id: xwrite-t.c 5790 2002-09-30 01:05:09Z rra $ */
+/* Test suite for xwrite and xwritev. */
+
+#include "config.h"
+#include "clibrary.h"
+#include <sys/uio.h>
+
+#include "libinn.h"
+
+/* The data array we'll use to do testing. */
+char data[256];
+
+/* These come from fakewrite. */
+extern char write_buffer[];
+extern size_t write_offset;
+extern int write_interrupt;
+extern int write_fail;
+
+static void
+test_write(int n, int status, int total)
+{
+ int success;
+
+ success = (status == total && memcmp(data, write_buffer, 256) == 0);
+ printf("%sok %d\n", success ? "" : "not ", n);
+ if (!success && status != total)
+ printf(" status %d, total %d\n", status, total);
+}
+
+int
+main(void)
+{
+ int i;
+ struct iovec iov[4];
+
+ puts("19");
+
+ /* Test xwrite. */
+ for (i = 0; i < 256; i++)
+ data[i] = i;
+ test_write(1, xwrite(0, data, 256), 256);
+ write_offset = 0;
+ write_interrupt = 1;
+ memset(data, 0, 256);
+ test_write(2, xwrite(0, data, 256), 256);
+ write_offset = 0;
+ for (i = 0; i < 32; i++)
+ data[i] = i * 2;
+ test_write(3, xwrite(0, data, 32), 32);
+ for (i = 32; i < 65; i++)
+ data[i] = i * 2;
+ test_write(4, xwrite(0, data + 32, 33), 33);
+ write_offset = 0;
+ write_interrupt = 0;
+
+ /* Test xwritev. */
+ memset(data, 0, 256);
+ iov[0].iov_base = data;
+ iov[0].iov_len = 256;
+ test_write(5, xwritev(0, iov, 1), 256);
+ write_offset = 0;
+ for (i = 0; i < 256; i++)
+ data[i] = i;
+ iov[0].iov_len = 128;
+ iov[1].iov_base = &data[128];
+ iov[1].iov_len = 16;
+ iov[2].iov_base = &data[144];
+ iov[2].iov_len = 112;
+ test_write(6, xwritev(0, iov, 3), 256);
+ write_offset = 0;
+ write_interrupt = 1;
+ memset(data, 0, 256);
+ iov[0].iov_len = 32;
+ iov[1].iov_base = &data[32];
+ iov[1].iov_len = 224;
+ test_write(7, xwritev(0, iov, 2), 256);
+ for (i = 0; i < 32; i++)
+ data[i] = i * 2;
+ write_offset = 0;
+ test_write(8, xwritev(0, iov, 1), 32);
+ for (i = 32; i < 65; i++)
+ data[i] = i * 2;
+ iov[0].iov_base = &data[32];
+ iov[0].iov_len = 16;
+ iov[1].iov_base = &data[48];
+ iov[1].iov_len = 1;
+ iov[2].iov_base = &data[49];
+ iov[2].iov_len = 8;
+ iov[3].iov_base = &data[57];
+ iov[3].iov_len = 8;
+ test_write(9, xwritev(0, iov, 4), 33);
+ write_offset = 0;
+ write_interrupt = 0;
+
+ /* Test xpwrite. */
+ for (i = 0; i < 256; i++)
+ data[i] = i;
+ test_write(10, xpwrite(0, data, 256, 0), 256);
+ write_interrupt = 1;
+ memset(data + 1, 0, 255);
+ test_write(11, xpwrite(0, data + 1, 255, 1), 255);
+ for (i = 0; i < 32; i++)
+ data[i + 32] = i * 2;
+ test_write(12, xpwrite(0, data + 32, 32, 32), 32);
+ for (i = 32; i < 65; i++)
+ data[i + 32] = i * 2;
+ test_write(13, xpwrite(0, data + 64, 33, 64), 33);
+ write_interrupt = 0;
+
+ /* Test failures. */
+ write_fail = 1;
+ test_write(14, xwrite(0, data + 1, 255), -1);
+ iov[0].iov_base = data + 1;
+ iov[0].iov_len = 255;
+ test_write(15, xwritev(0, iov, 1), -1);
+ test_write(16, xpwrite(0, data + 1, 255, 0), -1);
+
+ /* Test zero-length writes. */
+ test_write(17, xwrite(0, " ", 0), 0);
+ test_write(18, xpwrite(0, " ", 0, 2), 0);
+ iov[0].iov_base = data + 1;
+ iov[0].iov_len = 2;
+ test_write(19, xwritev(0, iov, 0), 0);
+
+ return 0;
+}
--- /dev/null
+/* $Id: libtest.c 5955 2002-12-08 09:28:32Z rra $
+**
+** Some utility routines for writing tests.
+**
+** Herein are a variety of utility routines for writing tests. All
+** routines of the form ok*() take a test number and some number of
+** appropriate arguments, check to be sure the results match the expected
+** output using the arguments, and print out something appropriate for that
+** test number. Other utility routines help in constructing more complex
+** tests.
+*/
+
+#include "config.h"
+#include "clibrary.h"
+
+#include "inn/messages.h"
+#include "libinn.h"
+#include "libtest.h"
+
+/* A global buffer into which message_log_buffer stores error messages. */
+char *errors = NULL;
+
+
+/*
+** Takes a boolean success value and assumes the test passes if that value
+** is true and fails if that value is false.
+*/
+void
+ok(int n, int success)
+{
+ printf("%sok %d\n", success ? "" : "not ", n);
+}
+
+
+/*
+** Takes an expected integer and a seen integer and assumes the test passes
+** if those two numbers match.
+*/
+void
+ok_int(int n, int wanted, int seen)
+{
+ if (wanted == seen)
+ printf("ok %d\n", n);
+ else
+ printf("not ok %d\n wanted: %d\n seen: %d\n", n, wanted, seen);
+}
+
+
+/*
+** Takes a string and what the string should be, and assumes the test
+** passes if those strings match (using strcmp).
+*/
+void
+ok_string(int n, const char *wanted, const char *seen)
+{
+ if (wanted == NULL)
+ wanted = "(null)";
+ if (seen == NULL)
+ seen = "(null)";
+ if (strcmp(wanted, seen) != 0)
+ printf("not ok %d\n wanted: %s\n seen: %s\n", n, wanted, seen);
+ else
+ printf("ok %d\n", n);
+}
+
+
+/*
+** An error handler that appends all errors to the errors global. Used by
+** error_capture.
+*/
+static void
+message_log_buffer(int len, const char *fmt, va_list args, int error UNUSED)
+{
+ char *message;
+
+ message = xmalloc(len + 1);
+ vsnprintf(message, len + 1, fmt, args);
+ if (errors == NULL) {
+ errors = concat(message, "\n", (char *) 0);
+ } else {
+ char *new_errors;
+
+ new_errors = concat(errors, message, "\n", (char *) 0);
+ free(errors);
+ errors = new_errors;
+ }
+ free(message);
+}
+
+
+/*
+** Turn on the capturing of errors. Errors will be stored in the global
+** errors variable where they can be checked by the test suite. Capturing is
+** turned off with errors_uncapture.
+*/
+void
+errors_capture(void)
+{
+ if (errors != NULL) {
+ free(errors);
+ errors = NULL;
+ }
+ message_handlers_warn(1, message_log_buffer);
+}
+
+
+/*
+** Turn off the capturing of errors again.
+*/
+void
+errors_uncapture(void)
+{
+ message_handlers_warn(1, message_log_stderr);
+}
--- /dev/null
+/* $Id: libtest.h 5955 2002-12-08 09:28:32Z rra $
+**
+** Some utility routines for writing tests.
+*/
+
+#ifndef TESTLIB_H
+#define TESTLIB_H 1
+
+#include "config.h"
+#include <inn/defines.h>
+
+/* A global buffer into which errors_capture stores errors. */
+extern char *errors;
+
+BEGIN_DECLS
+
+void ok(int n, int success);
+void ok_int(int n, int wanted, int seen);
+void ok_string(int n, const char *wanted, const char *seen);
+
+/* Turn on capturing of errors with errors_capture. Errors reported by warn
+ will be stored in the global errors variable. Turn this off again with
+ errors_uncapture. Caller is responsible for freeing errors when done. */
+void errors_capture(void);
+void errors_uncapture(void);
+
+END_DECLS
+
+#endif /* TESTLIB_H */
--- /dev/null
+news.groups:1 Re: Pre-RFD - Exec Board Newsgroup Creation Russ Allbery <rra@stanford.edu> Fri, 01 Feb 2002 15:30:16 -0800 <ylvgdgye47.fsf@windlord.stanford.edu> <20020131191005$5818@babylon.ks.uiuc.edu> <a3cdbm$dt3$1@samba.rahul.net> <a3coga$jiv$1@news.orst.edu> <3C5AE4E5.B0A79442@elepar.com> <a3f1o2$nvo$1@news.orst.edu> <3b7m5ug5lmovmhotuegmkq13jpmcubrh0n@4ax.com> 3124 58 Xref: inn.example.com news.groups:1
+news.groups:2 Re: Pre-RFD - Exec Board Newsgroup Creation Russ Allbery <rra@stanford.edu> Fri, 01 Feb 2002 15:32:03 -0800 <ylofj8ye18.fsf@windlord.stanford.edu> <20020131191005$5818@babylon.ks.uiuc.edu> <a3eep7$q6r$1@samba.rahul.net> <a3epeq$rt$1@gw.retro.com> <a3f201$n8$1@samba.rahul.net> <a3f7v9$3kg$1@gw.retro.com> 448 11 Xref: inn.example.com news.groups:2
+news.groups:7 Re: Pre-RFD - Exec Board Newsgroup Creation Russ Allbery <rra@stanford.edu> Fri, 01 Feb 2002 16:34:02 -0800 <ylhep0vi11.fsf@windlord.stanford.edu> <20020131191005$5818@babylon.ks.uiuc.edu> <a3cdbm$dt3$1@samba.rahul.net> <a3coga$jiv$1@news.orst.edu> <3C5AE4E5.B0A79442@elepar.com> <a3f1o2$nvo$1@news.orst.edu> <3b7m5ug5lmovmhotuegmkq13jpmcubrh0n@4ax.com> <ylvgdgye47.fsf@windlord.stanford.edu> <tuam5ug6iomvjscick3a2fftfs7d20rrfk@4ax.com> 2834 55 Xref: inn.example.com news.groups:7
+news.groups:8 Re: Pre-RFD - Exec Board Newsgroup Creation Russ Allbery <rra@stanford.edu> Fri, 01 Feb 2002 17:10:47 -0800 <yladusu1rc.fsf@windlord.stanford.edu> <20020131191005$5818@babylon.ks.uiuc.edu> <a3cdbm$dt3$1@samba.rahul.net> <a3coga$jiv$1@news.orst.edu> <3C5AE4E5.B0A79442@elepar.com> <a3f1o2$nvo$1@news.orst.edu> <3b7m5ug5lmovmhotuegmkq13jpmcubrh0n@4ax.com> <ylvgdgye47.fsf@windlord.stanford.edu> <tuam5ug6iomvjscick3a2fftfs7d20rrfk@4ax.com> <ylhep0vi11.fsf@windlord.stanford.edu> <3ndm5u8fgomord7vmh164ce7pcteh496sp@4ax.com> 2363 47 Xref: inn.example.com news.groups:8
+news.groups:12 Re: Pre-RFD - Exec Board Newsgroup Creation Russ Allbery <rra@stanford.edu> Fri, 01 Feb 2002 17:20:57 -0800 <yl4rl0u1ae.fsf@windlord.stanford.edu> <20020131191005$5818@babylon.ks.uiuc.edu> <a3cdbm$dt3$1@samba.rahul.net> <a3coga$jiv$1@news.orst.edu> <3C5AE4E5.B0A79442@elepar.com> <a3f1o2$nvo$1@news.orst.edu> <3b7m5ug5lmovmhotuegmkq13jpmcubrh0n@4ax.com> <ylvgdgye47.fsf@windlord.stanford.edu> <tuam5ug6iomvjscick3a2fftfs7d20rrfk@4ax.com> <ylhep0vi11.fsf@windlord.stanford.edu> <1hem5uc8efskp0efdahvnpphg2qtrlt9pc@4ax.com> 1651 36 Xref: inn.example.com news.groups:12
+news.groups:13 Re: Pre-RFD - Exec Board Newsgroup Creation Russ Allbery <rra@stanford.edu> Fri, 01 Feb 2002 18:10:18 -0800 <ylu1t03a7p.fsf@windlord.stanford.edu> <20020131191005$5818@babylon.ks.uiuc.edu> <a3cdbm$dt3$1@samba.rahul.net> <a3coga$jiv$1@news.orst.edu> <3C5AE4E5.B0A79442@elepar.com> <a3f1o2$nvo$1@news.orst.edu> <3b7m5ug5lmovmhotuegmkq13jpmcubrh0n@4ax.com> <ylvgdgye47.fsf@windlord.stanford.edu> <tuam5ug6iomvjscick3a2fftfs7d20rrfk@4ax.com> <ylhep0vi11.fsf@windlord.stanford.edu> <1hem5uc8efskp0efdahvnpphg2qtrlt9pc@4ax.com> <yl4rl0u1ae.fsf@windlord.stanford.edu> <v1hm5u4mrhllm98ng1jhp5gco7sflbm4jq@4ax.com> 902 27 Xref: inn.example.com news.groups:13
+news.groups:14 Re: Pre-RFD - Exec Board Newsgroup Creation Russ Allbery <rra@stanford.edu> Fri, 01 Feb 2002 19:08:02 -0800 <ylg04k37jh.fsf@windlord.stanford.edu> <20020131191005$5818@babylon.ks.uiuc.edu> <a3cdbm$dt3$1@samba.rahul.net> <a3coga$jiv$1@news.orst.edu> <3C5AE4E5.B0A79442@elepar.com> <a3f1o2$nvo$1@news.orst.edu> <3b7m5ug5lmovmhotuegmkq13jpmcubrh0n@4ax.com> <ylvgdgye47.fsf@windlord.stanford.edu> <tuam5ug6iomvjscick3a2fftfs7d20rrfk@4ax.com> <ylhep0vi11.fsf@windlord.stanford.edu> <a3fgk302sdn@enews1.newsguy.com> 808 18 Xref: inn.example.com news.groups:14
+news.groups:15 Re: Pre-RFD - Exec Board Newsgroup Creation Russ Allbery <rra@stanford.edu> Fri, 01 Feb 2002 19:12:32 -0800 <yladus37bz.fsf@windlord.stanford.edu> <20020131191005$5818@babylon.ks.uiuc.edu> <a3cdbm$dt3$1@samba.rahul.net> <a3coga$jiv$1@news.orst.edu> <3C5AE4E5.B0A79442@elepar.com> <a3f1o2$nvo$1@news.orst.edu> <3b7m5ug5lmovmhotuegmkq13jpmcubrh0n@4ax.com> <ylvgdgye47.fsf@windlord.stanford.edu> <tuam5ug6iomvjscick3a2fftfs7d20rrfk@4ax.com> <ylhep0vi11.fsf@windlord.stanford.edu> <1hem5uc8efskp0efdahvnpphg2qtrlt9pc@4ax.com> <yl4rl0u1ae.fsf@windlord.stanford.edu> <i8jm5u4l8bt7lk93mramtmbouhtg58tpnb@4ax.com> 643 14 Xref: inn.example.com news.groups:15
+news.groups:16 Re: Pre-RFD - Exec Board Newsgroup Creation Russ Allbery <rra@stanford.edu> Fri, 01 Feb 2002 19:35:38 -0800 <yl4rl0369h.fsf@windlord.stanford.edu> <20020131191005$5818@babylon.ks.uiuc.edu> <a3cdbm$dt3$1@samba.rahul.net> <a3coga$jiv$1@news.orst.edu> <3C5AE4E5.B0A79442@elepar.com> <a3f1o2$nvo$1@news.orst.edu> <3b7m5ug5lmovmhotuegmkq13jpmcubrh0n@4ax.com> <ylvgdgye47.fsf@windlord.stanford.edu> <tuam5ug6iomvjscick3a2fftfs7d20rrfk@4ax.com> <ylhep0vi11.fsf@windlord.stanford.edu> <1hem5uc8efskp0efdahvnpphg2qtrlt9pc@4ax.com> <yl4rl0u1ae.fsf@windlord.stanford.edu> <Xns91A8BC08432FEpeskyirritantcom@209.155.56.83> 326 12 Xref: inn.example.com news.groups:16
+news.groups:17 Re: Pre-RFD - Exec Board Newsgroup Creation Russ Allbery <rra@stanford.edu> Sat, 02 Feb 2002 10:20:43 -0800 <ylg04j7nk4.fsf@windlord.stanford.edu> <20020131191005$5818@babylon.ks.uiuc.edu> <ylvgdgye47.fsf@windlord.stanford.edu> <tuam5ug6iomvjscick3a2fftfs7d20rrfk@4ax.com> <ylhep0vi11.fsf@windlord.stanford.edu> <a3g0i9$6ch$1@samba.rahul.net> 680 16 Xref: inn.example.com news.groups:17
+news.groups:18 Re: The beer truck (was Re: Pre-RFD - Exec Board Newsgroup Creation) Russ Allbery <rra@stanford.edu> Sat, 02 Feb 2002 10:23:15 -0800 <yladur7nfw.fsf@windlord.stanford.edu> <20020131191005$5818@babylon.ks.uiuc.edu> <ylvgdgye47.fsf@windlord.stanford.edu> <tuam5ug6iomvjscick3a2fftfs7d20rrfk@4ax.com> <ylhep0vi11.fsf@windlord.stanford.edu> <a3h7be$dec$1@panix2.panix.com> 375 11 Xref: inn.example.com news.groups:18
+news.groups:19 Re: The beer truck (was Re: Pre-RFD - Exec Board Newsgroup Creation) Russ Allbery <rra@stanford.edu> Sat, 02 Feb 2002 12:51:44 -0800 <yl8zab38v3.fsf@windlord.stanford.edu> <20020131191005$5818@babylon.ks.uiuc.edu> <ylhep0vi11.fsf@windlord.stanford.edu> <a3h7be$dec$1@panix2.panix.com> <yladur7nfw.fsf@windlord.stanford.edu> <a3hd8t$sin$1@panix2.panix.com> 481 16 Xref: inn.example.com news.groups:19
+news.groups:21 Re: Pre-RFD - Exec Board Newsgroup Creation Russ Allbery <rra@stanford.edu> Sat, 02 Feb 2002 17:38:39 -0800 <ylwuxvwdi8.fsf@windlord.stanford.edu> <20020131191005$5818@babylon.ks.uiuc.edu> <PE2$9TMo7+W8Ew43@merlyn.demon.co.uk> <20020202150653$4dbb@babylon.ks.uiuc.edu> <fo1o5uoo90uh1qreh20u6n1ci70jt28mmg@4ax.com> <1f6z4v6.1fbjkdq18w29f4N%kmorgan@aptalaska.net> <lqto5u0p5sblo0ui9hal0nil52e3nlbssm@4ax.com> 316 9 Xref: inn.example.com news.groups:21
+news.groups:22 Re: Pre-RFD - Exec Board Newsgroup Creation Russ Allbery <rra@stanford.edu> Sat, 02 Feb 2002 17:42:27 -0800 <ylr8o3wdbw.fsf@windlord.stanford.edu> <20020131191005$5818@babylon.ks.uiuc.edu> <PE2$9TMo7+W8Ew43@merlyn.demon.co.uk> <08_68.11847$gW4.9091001@news1.rdc1.mi.home.com> 1405 29 Xref: inn.example.com news.groups:22
+news.groups:23 Re: [History] Re: A Chronology of Usenet Newsgroups: Start Post Russ Allbery <rra@stanford.edu> Sun, 03 Feb 2002 09:34:04 -0800 <yl665e4ghf.fsf@windlord.stanford.edu> <3c4a31e3$0$95685$892e7fe2@authen.puce.readfreenews.net> <a301ve02s1r@enews2.newsguy.com> <dbc8daca.0201280231.791aeb03@posting.google.com> <8I07QDHHw-B@khms.westfalen.de> <3c5d3981$0$36784$892e7fe2@authen.puce.readfreenews.net> 617 15 Xref: inn.example.com news.groups:23
+news.groups:24 Re: Guidelines for Big Eight Newsgroup Creation Russ Allbery <rra@stanford.edu> Sun, 03 Feb 2002 09:38:27 -0800 <ylzo2q31po.fsf@windlord.stanford.edu> <big-eight-faq-1012550404$22697@windlord.stanford.edu> <vvjq5u8l1dnrdankh4q7l2d043ud8kt11o@4ax.com> 971 22 Xref: inn.example.com news.groups:24
+news.admin.net-abuse.policy:1 Re: [ALT hierarchy] Anyone can newgroup and anyone can rmgroup ? Russ Allbery <rra@stanford.edu> Sun, 03 Feb 2002 10:22:36 -0800 <ylelk21l3n.fsf@windlord.stanford.edu> <5Re78.4895$XS6.585609@news2-win.server.ntlworld.com> 1448 31 Xref: inn.example.com news.admin.net-abuse.policy:1
+news.groups:25 Re: Pre-RFD - Exec Board Newsgroup Creation Russ Allbery <rra@stanford.edu> Sun, 03 Feb 2002 13:21:34 -0800 <ylwuxuz2g1.fsf@windlord.stanford.edu> <20020131191005$5818@babylon.ks.uiuc.edu> <PE2$9TMo7+W8Ew43@merlyn.demon.co.uk> <20020202150653$4dbb@babylon.ks.uiuc.edu> <FRRtIeAzyYX8Ewxl@merlyn.demon.co.uk> 668 15 Xref: inn.example.com news.groups:25
+news.admin.net-abuse.policy:4 Re: [ALT hierarchy] Anyone can newgroup and anyone can rmgroup ? Russ Allbery <rra@stanford.edu> Sun, 03 Feb 2002 13:23:54 -0800 <ylr8o2z2c5.fsf@windlord.stanford.edu> <5Re78.4895$XS6.585609@news2-win.server.ntlworld.com> <ylelk21l3n.fsf@windlord.stanford.edu> <Gqz2AI.JH2@world.std.com> 533 12 Xref: inn.example.com news.admin.net-abuse.policy:4
+gnu.misc.discuss:1 Re: want to learn perl from free sources Russ Allbery <rra@stanford.edu> Mon, 04 Feb 2002 09:51:12 -0800 <yln0yp87an.fsf@windlord.stanford.edu> <m2r8o1iu11.fsf@dan.jacobson.tw> 641 15 Xref: inn.example.com gnu.misc.discuss:1
+comp.lang.perl.misc:1 Re: want to learn perl from free sources Russ Allbery <rra@stanford.edu> Mon, 04 Feb 2002 09:51:12 -0800 <yln0yp87an.fsf@windlord.stanford.edu> <m2r8o1iu11.fsf@dan.jacobson.tw> 641 15 Xref: inn.example.com comp.lang.perl.misc:1
+csd.bboard:1 comp.doc.techreports submissions from Stanford Russ Allbery <rra@stanford.edu> Mon, 04 Feb 2002 10:28:11 -0800 <yl4rkx3xvo.fsf@windlord.stanford.edu> 672 18 Xref: inn.example.com csd.bboard:1
+news.admin.net-abuse.policy:5 Re: [ALT hierarchy] Anyone can newgroup and anyone can rmgroup ? Russ Allbery <rra@stanford.edu> Mon, 04 Feb 2002 12:09:24 -0800 <ylr8o1xb4b.fsf@windlord.stanford.edu> <5Re78.4895$XS6.585609@news2-win.server.ntlworld.com> <ylelk21l3n.fsf@windlord.stanford.edu> <Gqz2AI.JH2@world.std.com> <ylr8o2z2c5.fsf@windlord.stanford.edu> <Gr0r5x.M8y@world.std.com> 1751 38 Xref: inn.example.com news.admin.net-abuse.policy:5
+csd.bboard:2 Re: comp.doc.techreports submissions from Stanford Russ Allbery <rra@stanford.edu> Mon, 04 Feb 2002 12:31:16 -0800 <yl3d0hxa3v.fsf@windlord.stanford.edu> <yl4rkx3xvo.fsf@windlord.stanford.edu> 794 16 Xref: inn.example.com csd.bboard:2
+gnu.utils.bug:1 Re: let's beef up the tsort info page Russ Allbery <rra@stanford.edu> Mon, 04 Feb 2002 14:03:41 -0800 <yl4rkwx5tu.fsf@windlord.stanford.edu> <m2wuxtj3gh.fsf@dan.jacobson.tw> 2409 73 Xref: inn.example.com gnu.utils.bug:1
+news.groups:26 Re: [META RFD] Removal of low traffic groups. Russ Allbery <rra@stanford.edu> Mon, 04 Feb 2002 15:31:18 -0800 <yl4rkwu8mx.fsf@windlord.stanford.edu> <Xns91A5EF6DA9BCEgrahamdrabblelineone@ID-77355.user.dfncis.de> <3c5f0eb7$0$36784$892e7fe2@authen.puce.readfreenews.net> <3c5u5u08g2u5tva8i6gd1087ug4uma6051@4ax.com> 578 15 Xref: inn.example.com news.groups:26
+news.groups:27 Re: [META RFD] Removal of low traffic groups. Russ Allbery <rra@stanford.edu> Mon, 04 Feb 2002 16:37:55 -0800 <ylofj4rcf0.fsf@windlord.stanford.edu> <Xns91A5EF6DA9BCEgrahamdrabblelineone@ID-77355.user.dfncis.de> <3c5f0eb7$0$36784$892e7fe2@authen.puce.readfreenews.net> <3c5u5u08g2u5tva8i6gd1087ug4uma6051@4ax.com> <yl4rkwu8mx.fsf@windlord.stanford.edu> <a3n74c026se@enews4.newsguy.com> 899 22 Xref: inn.example.com news.groups:27
+news.admin.net-abuse.policy:6 Re: [ALT hierarchy] Anyone can newgroup and anyone can rmgroup ? Russ Allbery <rra@stanford.edu> Mon, 04 Feb 2002 17:56:53 -0800 <ylk7tsofmi.fsf@windlord.stanford.edu> <5Re78.4895$XS6.585609@news2-win.server.ntlworld.com> <ylelk21l3n.fsf@windlord.stanford.edu> <Gqz2AI.JH2@world.std.com> <ylr8o2z2c5.fsf@windlord.stanford.edu> <Gr0r5x.M8y@world.std.com> <ylr8o1xb4b.fsf@windlord.stanford.edu> <Gr1CnE.Iny@world.std.com> 961 18 Xref: inn.example.com news.admin.net-abuse.policy:6
+news.groups:32 Re: [META RFD] Removal of low traffic groups. Russ Allbery <rra@stanford.edu> Mon, 04 Feb 2002 19:02:56 -0800 <yln0yobpgf.fsf@windlord.stanford.edu> <Xns91A5EF6DA9BCEgrahamdrabblelineone@ID-77355.user.dfncis.de> <3c5f0eb7$0$36784$892e7fe2@authen.puce.readfreenews.net> <3c5u5u08g2u5tva8i6gd1087ug4uma6051@4ax.com> <yl4rkwu8mx.fsf@windlord.stanford.edu> <a3n74c026se@enews4.newsguy.com> <ylofj4rcf0.fsf@windlord.stanford.edu> <a3nbva02f0r@enews4.newsguy.com> 1280 26 Xref: inn.example.com news.groups:32
+news.groups:33 Re: CFV: sci.geo.cartography Russ Allbery <rra@stanford.edu> Mon, 04 Feb 2002 19:04:16 -0800 <ylheowbpe7.fsf@windlord.stanford.edu> <1010622320.26971@isc.org> <1012843693.45036@isc.org> <3c5f4805$0$36783$892e7fe2@authen.puce.readfreenews.net> 851 17 Xref: inn.example.com news.groups:33
+news.admin.net-abuse.policy:7 Re: [ALT hierarchy] Anyone can newgroup and anyone can rmgroup ? Russ Allbery <rra@stanford.edu> Tue, 05 Feb 2002 08:52:18 -0800 <yl4rkvx459.fsf@windlord.stanford.edu> <5Re78.4895$XS6.585609@news2-win.server.ntlworld.com> <ylelk21l3n.fsf@windlord.stanford.edu> <Gqz2AI.JH2@world.std.com> <ylr8o2z2c5.fsf@windlord.stanford.edu> <Gr0r5x.M8y@world.std.com> <ylr8o1xb4b.fsf@windlord.stanford.edu> <Gr1CnE.Iny@world.std.com> <ylk7tsofmi.fsf@windlord.stanford.edu> <Gr1LLx.6xF@world.std.com> 3618 69 Xref: inn.example.com news.admin.net-abuse.policy:7
+news.software.nntp:1 Re: peering under the storage API Russ Allbery <rra@stanford.edu> Tue, 05 Feb 2002 17:04:13 -0800 <ylit9bl8tu.fsf@windlord.stanford.edu> <slrna60q6l.3fo.br@panix2.panix.com> 546 14 Xref: inn.example.com news.software.nntp:1
+news.groups:34 Re: CFV: sci.geo.cartography Russ Allbery <rra@stanford.edu> Tue, 05 Feb 2002 17:46:28 -0800 <ylofj3jsaz.fsf@windlord.stanford.edu> <1010622320.26971@isc.org> <1012843693.45036@isc.org> <3c5f4805$0$36783$892e7fe2@authen.puce.readfreenews.net> <m2k7trcxes.fsf@dan.jacobson.tw> <3C6077F5.AE1C5ED8@sfo.com> 882 22 Xref: inn.example.com news.groups:34
+news.groups:36 Re: [META RFD] Removal of low traffic groups. Russ Allbery <rra@stanford.edu> Tue, 05 Feb 2002 17:51:33 -0800 <ylit9bjs2i.fsf@windlord.stanford.edu> <Xns91A5EF6DA9BCEgrahamdrabblelineone@ID-77355.user.dfncis.de> <3c5f0eb7$0$36784$892e7fe2@authen.puce.readfreenews.net> <Xns91AD188E6226grahamdrabblelineone@ID-77355.user.dfncis.de> 1067 21 Xref: inn.example.com news.groups:36
+news.admin.technical:1 Re: nnrpd Russ Allbery <rra@stanford.edu> Tue, 05 Feb 2002 17:53:18 -0800 <yld6zjjrzl.fsf@windlord.stanford.edu> <a3psen$7p4$1@quasar.ctc.edu> 543 13 Xref: inn.example.com news.admin.technical:1
+news.admin.hierarchies:1 Re: Are the active files at ftp.isc.org maintained? Russ Allbery <rra@stanford.edu> Tue, 05 Feb 2002 17:54:47 -0800 <yl7kprjrx4.fsf@windlord.stanford.edu> <m3lme7zhep.fsf@defun.localdomain> 877 21 Xref: inn.example.com news.admin.hierarchies:1
+news.groups:37 Re: CFV: sci.geo.cartography Russ Allbery <rra@stanford.edu> Tue, 05 Feb 2002 18:07:55 -0800 <ylvgdbicqs.fsf@windlord.stanford.edu> <1010622320.26971@isc.org> <1012843693.45036@isc.org> <3c5f4805$0$36783$892e7fe2@authen.puce.readfreenews.net> <3C608D3D.C87E3610@diogenes.sacramento.ca.us> 623 13 Xref: inn.example.com news.groups:37
+news.software.nntp:2 Re: INN on another user/group Russ Allbery <rra@stanford.edu> Tue, 05 Feb 2002 19:14:03 -0800 <ylg04fs3no.fsf@windlord.stanford.edu> <3c609757$0$20968$afc38c87@news.optusnet.com.au> 593 15 Xref: inn.example.com news.software.nntp:2
+news.admin.hierarchies:2 Re: Are the active files at ftp.isc.org maintained? Russ Allbery <rra@stanford.edu> Tue, 05 Feb 2002 21:56:17 -0800 <yl665bqhku.fsf@windlord.stanford.edu> <m3lme7zhep.fsf@defun.localdomain> <yl7kprjrx4.fsf@windlord.stanford.edu> <m3y9i7xluu.fsf@defun.localdomain> 828 19 Xref: inn.example.com news.admin.hierarchies:2
+news.admin.net-abuse.policy:8 Re: [RETROMODERATION] teenfem terrorists Russ Allbery <rra@stanford.edu> Wed, 06 Feb 2002 08:42:16 -0800 <ylr8ny7eaf.fsf@windlord.stanford.edu> <6f826u0dag3c5gosbps2elao0scnu38k0p@news-01.easynews.com> 740 18 Xref: inn.example.com news.admin.net-abuse.policy:8
+news.software.nntp:3 Re: Trailing spaces in body on INN 2.3.3 ? Russ Allbery <rra@stanford.edu> Wed, 06 Feb 2002 08:45:04 -0800 <yllme67e5r.fsf@windlord.stanford.edu> <52419.622207@archiver.winews.net> 852 21 Xref: inn.example.com news.software.nntp:3
+news.software.nntp:4 Re: first > last Russ Allbery <rra@stanford.edu> Wed, 06 Feb 2002 13:50:15 -0800 <yly9i6uvoo.fsf@windlord.stanford.edu> <3C61951F.8080606@gemal.dk> 850 19 Xref: inn.example.com news.software.nntp:4
+news.admin.net-abuse.policy:9 Re: [RETROMODERATION] teenfem terrorists Russ Allbery <rra@stanford.edu> Wed, 06 Feb 2002 13:57:38 -0800 <ylk7tqtgrx.fsf@windlord.stanford.edu> <6f826u0dag3c5gosbps2elao0scnu38k0p@news-01.easynews.com> <ylr8ny7eaf.fsf@windlord.stanford.edu> <878za6o8h5.fsf@erlenstar.demon.co.uk> 578 16 Xref: inn.example.com news.admin.net-abuse.policy:9
+news.admin.net-abuse.policy:10 Re: [RETROMODERATION] teenfem terrorists Russ Allbery <rra@stanford.edu> Wed, 06 Feb 2002 13:58:09 -0800 <yleljytgr2.fsf@windlord.stanford.edu> <6f826u0dag3c5gosbps2elao0scnu38k0p@news-01.easynews.com> <ylr8ny7eaf.fsf@windlord.stanford.edu> <1013015167.535798@ok-corral.gunslinger.net> 548 13 Xref: inn.example.com news.admin.net-abuse.policy:10
+news.admin.hierarchies:3 Re: Are the active files at ftp.isc.org maintained? Russ Allbery <rra@stanford.edu> Wed, 06 Feb 2002 14:00:23 -0800 <yl8za6tgnc.fsf@windlord.stanford.edu> <m3lme7zhep.fsf@defun.localdomain> <yl7kprjrx4.fsf@windlord.stanford.edu> <m3y9i7xluu.fsf@defun.localdomain> <yl665bqhku.fsf@windlord.stanford.edu> <m3lme68jz6.fsf@defun.localdomain> 478 17 Xref: inn.example.com news.admin.hierarchies:3
+news.admin.hierarchies:4 Re: [INFO] bc.* and van.* Russ Allbery <rra@stanford.edu> Wed, 06 Feb 2002 14:09:30 -0800 <yllme6s1np.fsf@windlord.stanford.edu> <a36mup$160fpd$1@ID-80930.news.dfncis.de> <a3ptk7.3vuai51.1@mid.glglgl.de> <a3qmdq$1akiad$1@ID-80930.news.dfncis.de> <a3qseq.3vue8up.1@mid.glglgl.de> <a3qvtt$19gllq$1@ID-80930.news.dfncis.de> 972 20 Xref: inn.example.com news.admin.hierarchies:4
+news.admin.hierarchies:7 Re: [INFO] bc.* and van.* Russ Allbery <rra@stanford.edu> Wed, 06 Feb 2002 16:19:10 -0800 <ylk7tqp2ip.fsf@windlord.stanford.edu> <a36mup$160fpd$1@ID-80930.news.dfncis.de> <a3ptk7.3vuai51.1@mid.glglgl.de> <a3qmdq$1akiad$1@ID-80930.news.dfncis.de> <a3qseq.3vue8up.1@mid.glglgl.de> <a3qvtt$19gllq$1@ID-80930.news.dfncis.de> <yllme6s1np.fsf@windlord.stanford.edu> <a3sbmu$1ao4a2$1@ID-80930.news.dfncis.de> 421 11 Xref: inn.example.com news.admin.hierarchies:7
+news.admin.hierarchies:11 Re: control.ctl maintenance (was: [INFO] bc.* and van.*) Russ Allbery <rra@stanford.edu> Wed, 06 Feb 2002 16:20:13 -0800 <yleljyp2gy.fsf@windlord.stanford.edu> <a36mup$160fpd$1@ID-80930.news.dfncis.de> <a3qseq.3vue8up.1@mid.glglgl.de> <a3qvtt$19gllq$1@ID-80930.news.dfncis.de> <yllme6s1np.fsf@windlord.stanford.edu> <EKi88.17124$gW4.11422684@news1.rdc1.mi.home.com> 688 18 Xref: inn.example.com news.admin.hierarchies:11
+news.groups:38 Re: RESULT: sci.military.nuclear-bio-chem fails 73:25 Russ Allbery <rra@stanford.edu> Wed, 06 Feb 2002 22:21:34 -0800 <yl8za5n769.fsf@windlord.stanford.edu> <1012432821.79903@isc.org> <1j6l5u499l6ru00rn6ibje0cgsv32jkrk1@4ax.com> <a3f1oq02gd2@enews3.newsguy.com> <a3obho02a17@enews2.newsguy.com> <a3pcp9$3d8$1@gw.retro.com> 1093 26 Xref: inn.example.com news.groups:38
+news.groups:39 Re: RESULT: sci.military.nuclear-bio-chem fails 73:25 Russ Allbery <rra@stanford.edu> Thu, 07 Feb 2002 00:46:46 -0800 <yl3d0dn0g9.fsf@windlord.stanford.edu> <1012432821.79903@isc.org> <a3obho02a17@enews2.newsguy.com> <a3pcp9$3d8$1@gw.retro.com> <yl8za5n769.fsf@windlord.stanford.edu> <a3tb6q$i69$1@gw.retro.com> 5108 96 Xref: inn.example.com news.groups:39
+news.software.nntp:5 Re: innd and Microsoft Exchange Russ Allbery <rra@stanford.edu> Thu, 07 Feb 2002 10:01:40 -0800 <yleljx41dn.fsf@windlord.stanford.edu> <623f5703.0202070541.2b943211@posting.google.com> 1013 21 Xref: inn.example.com news.software.nntp:5
+news.software.nntp:6 Re: innd and Microsoft Exchange Russ Allbery <rra@stanford.edu> Fri, 08 Feb 2002 11:04:25 -0800 <ylofiz23t2.fsf@windlord.stanford.edu> <623f5703.0202070541.2b943211@posting.google.com> <yleljx41dn.fsf@windlord.stanford.edu> <3C63AD90.492F3C00@eproduction.ch> 541 14 Xref: inn.example.com news.software.nntp:6
+news.software.nntp:9 Re: Trailing spaces in body on INN 2.3.3 ? Russ Allbery <rra@stanford.edu> Fri, 08 Feb 2002 11:05:28 -0800 <ylit9723rb.fsf@windlord.stanford.edu> <52419.622207@archiver.winews.net> <a405vd$huq$1@newsread4.arcor-online.net> 556 16 Xref: inn.example.com news.software.nntp:9
+news.software.nntp:10 Re: Trailing spaces in body on INN 2.3.3 ? Russ Allbery <rra@stanford.edu> Fri, 08 Feb 2002 12:15:43 -0800 <ylsn8bwx00.fsf@windlord.stanford.edu> <52419.622207@archiver.winews.net> <a405vd$huq$1@newsread4.arcor-online.net> <ylit9723rb.fsf@windlord.stanford.edu> <C2W88.119862$i3.977629@news.easynews.com> 749 19 Xref: inn.example.com news.software.nntp:10
+news.groups:40 Re: RESULT: sci.military.nuclear-bio-chem fails 73:25 Russ Allbery <rra@stanford.edu> Fri, 08 Feb 2002 18:11:54 -0800 <ylsn8b76ad.fsf@windlord.stanford.edu> <1012432821.79903@isc.org> <a3tb6q$i69$1@gw.retro.com> <a3uq5f$n81$1@news.orst.edu> <a3v87u$ihf$1@panix2.panix.com> <a409ek$qc8$1@gw.retro.com> 1995 40 Xref: inn.example.com news.groups:40
+news.admin.hierarchies:12 Re: Are the active files at ftp.isc.org maintained? Russ Allbery <rra@stanford.edu> Fri, 08 Feb 2002 20:42:25 -0800 <ylu1srclla.fsf@windlord.stanford.edu> <m3lme7zhep.fsf@defun.localdomain> <yl7kprjrx4.fsf@windlord.stanford.edu> <m3y9i7xluu.fsf@defun.localdomain> 505 14 Xref: inn.example.com news.admin.hierarchies:12
+news.groups:41 Re: RESULT: sci.military.nuclear-bio-chem fails 73:25 Russ Allbery <rra@stanford.edu> Fri, 08 Feb 2002 23:24:31 -0800 <yllme3azio.fsf@windlord.stanford.edu> <1012432821.79903@isc.org> <a3v87u$ihf$1@panix2.panix.com> <a409ek$qc8$1@gw.retro.com> <ylsn8b76ad.fsf@windlord.stanford.edu> <a42f8b$rfj$1@samba.rahul.net> 1282 26 Xref: inn.example.com news.groups:41
+news.admin.hierarchies:13 Re: [INFO] bc.* and van.* Russ Allbery <rra@stanford.edu> Sat, 09 Feb 2002 12:00:56 -0800 <yl8za2a0hz.fsf@windlord.stanford.edu> <a36mup$160fpd$1@ID-80930.news.dfncis.de> 905 23 Xref: inn.example.com news.admin.hierarchies:13
+news.groups:42 Re: RESULT: sci.military.nuclear-bio-chem fails 73:25 Russ Allbery <rra@stanford.edu> Sat, 09 Feb 2002 12:20:01 -0800 <yl1yfu9zm6.fsf@windlord.stanford.edu> <1012432821.79903@isc.org> <a3uq5f$n81$1@news.orst.edu> <a3v87u$ihf$1@panix2.panix.com> <a409ek$qc8$1@gw.retro.com> <3c651972$0$67480$892e7fe2@authen.puce.readfreenews.net> 990 19 Xref: inn.example.com news.groups:42
+news.admin.hierarchies:18 Re: Are the active files at ftp.isc.org maintained? Russ Allbery <rra@stanford.edu> Sat, 09 Feb 2002 12:25:07 -0800 <ylvgd68kt8.fsf@windlord.stanford.edu> <m3lme7zhep.fsf@defun.localdomain> <yl7kprjrx4.fsf@windlord.stanford.edu> <m3y9i7xluu.fsf@defun.localdomain> <ylu1srclla.fsf@windlord.stanford.edu> <m37kpmdayt.fsf@defun.localdomain> 580 18 Xref: inn.example.com news.admin.hierarchies:18
+comp.lang.perl.moderated:1 Re: perldoc vs man why so much slower Russ Allbery <rra@stanford.edu> Sat, 09 Feb 2002 12:47:26 -0800 <yl8za28js1.fsf@windlord.stanford.edu> <m1d6zfnl9h.fsf@reader.newsguy.com> <20020209180911.23756.qmail@plover.com> 799 20 Xref: inn.example.com comp.lang.perl.moderated:1
+news.software.nntp:11 Re: Trailing spaces in body on INN 2.3.3 ? Russ Allbery <rra@stanford.edu> Sat, 09 Feb 2002 13:18:51 -0800 <yl3d0a8ibo.fsf@windlord.stanford.edu> <52419.622207@archiver.winews.net> <a405vd$huq$1@newsread4.arcor-online.net> <ylit9723rb.fsf@windlord.stanford.edu> <2504c.922202@archiver.winews.net> 1075 27 Xref: inn.example.com news.software.nntp:11
+news.groups:43 Re: RESULT: sci.military.nuclear-bio-chem fails 73:25 Russ Allbery <rra@stanford.edu> Sat, 09 Feb 2002 23:47:28 -0800 <yln0yh22y7.fsf@windlord.stanford.edu> <1012432821.79903@isc.org> <ylsn8b76ad.fsf@windlord.stanford.edu> <a42f8b$rfj$1@samba.rahul.net> <yllme3azio.fsf@windlord.stanford.edu> <GrAr0y.AsA@world.std.com> 2306 45 Xref: inn.example.com news.groups:43
+news.groups:44 Re: RESULT: sci.military.nuclear-bio-chem fails 73:25 Russ Allbery <rra@stanford.edu> Sat, 09 Feb 2002 23:49:43 -0800 <ylheop22ug.fsf@windlord.stanford.edu> <1012432821.79903@isc.org> <a3v87u$ihf$1@panix2.panix.com> <a409ek$qc8$1@gw.retro.com> <ylsn8b76ad.fsf@windlord.stanford.edu> <a42f8b$rfj$1@samba.rahul.net> <yllme3azio.fsf@windlord.stanford.edu> <a44mu80urn@enews2.newsguy.com> 810 16 Xref: inn.example.com news.groups:44
+news.software.nntp:12 Re: Trailing spaces in body on INN 2.3.3 ? Russ Allbery <rra@stanford.edu> Sun, 10 Feb 2002 09:32:55 -0800 <yl8za1kzso.fsf@windlord.stanford.edu> <52419.622207@archiver.winews.net> <a405vd$huq$1@newsread4.arcor-online.net> <yl3d0a8ibo.fsf@windlord.stanford.edu> <21049.a22200@archiver.winews.net> 1915 40 Xref: inn.example.com news.software.nntp:12
+news.groups:46 Re: RESULT: sci.military.nuclear-bio-chem fails 73:25 Russ Allbery <rra@stanford.edu> Sun, 10 Feb 2002 10:47:13 -0800 <ylpu3dgoni.fsf@windlord.stanford.edu> <1012432821.79903@isc.org> <yllme3azio.fsf@windlord.stanford.edu> <a44mu80urn@enews2.newsguy.com> <ylheop22ug.fsf@windlord.stanford.edu> <a46ec6$lee$1@samba.rahul.net> 1052 28 Xref: inn.example.com news.groups:46
+news.groups:47 Re: 2nd RFD: sci.space.moderated moderated Russ Allbery <rra@stanford.edu> Sun, 10 Feb 2002 10:54:24 -0800 <ylk7tlgobj.fsf@windlord.stanford.edu> <1010713186.32070@isc.org> <3N898.13340$Zu6.55955@news-server.bigpond.net.au> <u6argep8r8l2c2@corp.supernews.com> <Xns91B0C720E8590grahamdrabblelineone@ID-77355.user.dfncis.de> <a440uv$r73$2@dent.deepthot.org> <u6b36tthfaecfb@corp.supernews.com> 1235 23 Xref: inn.example.com news.groups:47
+news.groups:48 Re: RESULT: sci.military.nuclear-bio-chem fails 73:25 Russ Allbery <rra@stanford.edu> Sun, 10 Feb 2002 11:48:18 -0800 <yly9i1f799.fsf@windlord.stanford.edu> <1012432821.79903@isc.org> <ylheop22ug.fsf@windlord.stanford.edu> <a46ec6$lee$1@samba.rahul.net> <ylpu3dgoni.fsf@windlord.stanford.edu> <a46i88$m4m$1@samba.rahul.net> 1563 32 Xref: inn.example.com news.groups:48
+news.groups:49 Re: 2nd RFD: sci.space.moderated moderated Russ Allbery <rra@stanford.edu> Sun, 10 Feb 2002 12:12:17 -0800 <ylsn89f65a.fsf@windlord.stanford.edu> <1010713186.32070@isc.org> <a440uv$r73$2@dent.deepthot.org> <u6b36tthfaecfb@corp.supernews.com> <a448lr$il$1@dent.deepthot.org> <3c65d1dc$0$67479$892e7fe2@authen.puce.readfreenews.net> 3599 70 Xref: inn.example.com news.groups:49
+news.groups:50 Re: RESULT: sci.military.nuclear-bio-chem fails 73:25 Russ Allbery <rra@stanford.edu> Sun, 10 Feb 2002 14:31:08 -0800 <yly9i1as0j.fsf@windlord.stanford.edu> <1012432821.79903@isc.org> <ylsn8b76ad.fsf@windlord.stanford.edu> <a42f8b$rfj$1@samba.rahul.net> <yllme3azio.fsf@windlord.stanford.edu> <GrAr0y.AsA@world.std.com> <yln0yh22y7.fsf@windlord.stanford.edu> <3c68f0c2.3904908@supernews.seanet.com> 1723 38 Xref: inn.example.com news.groups:50
+gnu.emacs.gnus:1 Re: Gnus, Postings, and Anti-spam? Russ Allbery <rra@stanford.edu> Sun, 10 Feb 2002 14:37:51 -0800 <ylheoparpc.fsf@windlord.stanford.edu> <ubsezbqfg.fsf@synopsys.com> <geg.y1ysn8apwb0.fsf@fly.verified.de> <eljuvgy6.fsf@ichimusai.org> <geg.y1y3d0apg8z.fsf@fly.verified.de> <vgd52d4a.fsf@ichimusai.org> 862 23 Xref: inn.example.com gnu.emacs.gnus:1
+news.groups:53 Re: Professional practice (was Re: RESULT: sci.military.nuclear-bio-chem fails 73:25) Russ Allbery <rra@stanford.edu> Sun, 10 Feb 2002 18:37:01 -0800 <yl8za0agmq.fsf@windlord.stanford.edu> <1012432821.79903@isc.org> <yln0yh22y7.fsf@windlord.stanford.edu> <3c68f0c2.3904908@supernews.seanet.com> <yly9i1as0j.fsf@windlord.stanford.edu> <a474cg$qou$1@panix2.panix.com> 1274 30 Xref: inn.example.com news.groups:53
+news.groups:56 Re: RESULT: sci.military.nuclear-bio-chem fails 73:25 Russ Allbery <rra@stanford.edu> Sun, 10 Feb 2002 20:01:15 -0800 <yleljs8y5w.fsf@windlord.stanford.edu> <1012432821.79903@isc.org> <ylsn8b76ad.fsf@windlord.stanford.edu> <a42f8b$rfj$1@samba.rahul.net> <yllme3azio.fsf@windlord.stanford.edu> <GrAr0y.AsA@world.std.com> <yln0yh22y7.fsf@windlord.stanford.edu> <3c68f0c2.3904908@supernews.seanet.com> <yly9i1as0j.fsf@windlord.stanford.edu> <a47aeg01e39@enews3.newsguy.com> 1482 35 Xref: inn.example.com news.groups:56
+news.admin.technical:2 Re: Maximum Length of Newsgroup Description field Russ Allbery <rra@stanford.edu> Mon, 11 Feb 2002 13:05:47 -0800 <ylit9391as.fsf@windlord.stanford.edu> <Xns91B278CC1107Djaydejaydenet@157.54.3.22> 526 12 Xref: inn.example.com news.admin.technical:2
+gnu.emacs.gnus:2 Re: modifying Sender: to (valid) From: of gnus-posting-styles Russ Allbery <rra@stanford.edu> Mon, 11 Feb 2002 15:55:01 -0800 <ylheon7ewa.fsf@windlord.stanford.edu> <m0it93yajy.fsf@k2.onsight.com> <vxkbsevzlcc.fsf@cinnamon.vanillaknot.com> <m0it9339nj.fsf@k2.onsight.com> 175 8 Xref: inn.example.com gnu.emacs.gnus:2
+gnu.emacs.gnus:6 Re: modifying Sender: to (valid) From: of gnus-posting-styles Russ Allbery <rra@stanford.edu> Mon, 11 Feb 2002 15:56:50 -0800 <ylbsev7et9.fsf@windlord.stanford.edu> <m0it93yajy.fsf@k2.onsight.com> <vxkbsevzlcc.fsf@cinnamon.vanillaknot.com> <uy9hzy4yp.fsf@synopsys.com> 1021 23 Xref: inn.example.com gnu.emacs.gnus:6
+news.software.nntp:13 Re: rebuilding overview with makehistory Russ Allbery <rra@stanford.edu> Mon, 11 Feb 2002 18:14:40 -0800 <ylofiv5tv3.fsf@windlord.stanford.edu> <slrna6gcn5.nmf.br@panix2.panix.com> 687 20 Xref: inn.example.com news.software.nntp:13
+news.software.nntp:14 Re: rebuilding overview with makehistory Russ Allbery <rra@stanford.edu> Tue, 12 Feb 2002 11:15:20 -0800 <yladuefr5j.fsf@windlord.stanford.edu> <slrna6gcn5.nmf.br@panix2.panix.com> <ylofiv5tv3.fsf@windlord.stanford.edu> <slrna6ig0n.qig.br@panix2.panix.com> 505 13 Xref: inn.example.com news.software.nntp:14
+gnu.emacs.gnus:7 Re: modifying Sender: to (valid) From: of gnus-posting-styles Russ Allbery <rra@stanford.edu> Tue, 12 Feb 2002 12:07:06 -0800 <ylwuxia2hh.fsf@windlord.stanford.edu> <m0it93yajy.fsf@k2.onsight.com> <vxkbsevzlcc.fsf@cinnamon.vanillaknot.com> <uy9hzy4yp.fsf@synopsys.com> <ylbsev7et9.fsf@windlord.stanford.edu> <ilu665235iy.fsf@extundo.com> 426 11 Xref: inn.example.com gnu.emacs.gnus:7
+gnu.emacs.gnus:8 Re: modifying Sender: to (valid) From: of gnus-posting-styles Russ Allbery <rra@stanford.edu> Wed, 13 Feb 2002 13:46:55 -0800 <yl3d05awc0.fsf@windlord.stanford.edu> <m0it93yajy.fsf@k2.onsight.com> <vxkbsevzlcc.fsf@cinnamon.vanillaknot.com> <uy9hzy4yp.fsf@synopsys.com> <ylbsev7et9.fsf@windlord.stanford.edu> <ilu665235iy.fsf@extundo.com> <ylwuxia2hh.fsf@windlord.stanford.edu> <1yfpzc36.fsf@hschmi22.userfqdn.rz-online.de> 326 11 Xref: inn.example.com gnu.emacs.gnus:8
+news.software.nntp:15 Re: chan.c problem with INN 2.3.[012] on AIX 4.2 Russ Allbery <rra@stanford.edu> Thu, 14 Feb 2002 11:49:28 -0800 <yl7kpfq1x3.fsf@windlord.stanford.edu> <fa8727e8.0202141109.7875ddc@posting.google.com> 1449 42 Xref: inn.example.com news.software.nntp:15
+news.groups:57 Re: comp.distributed again? Russ Allbery <rra@stanford.edu> Thu, 14 Feb 2002 13:43:52 -0800 <ylu1sjkacn.fsf@windlord.stanford.edu> <1012432821.79903@isc.org> <a4co04$d94$1@slb2.atl.mindspring.net> <a4edc2$uds$5@dent.deepthot.org> <a4frn0$71f$1@slb3.atl.mindspring.net> <a4frot$inj$8@dent.deepthot.org> 360 10 Xref: inn.example.com news.groups:57
+news.groups:58 Re: RESULT: sci.military.nuclear-bio-chem fails 73:25 Russ Allbery <rra@stanford.edu> Thu, 14 Feb 2002 21:40:08 -0800 <ylvgczi9qf.fsf@windlord.stanford.edu> <1012432821.79903@isc.org> <a3obho02a17@enews2.newsguy.com> <a3pcp9$3d8$1@gw.retro.com> <3c6038d6.774936@news.storm.ca> <a3pg75$3vk$1@gw.retro.com> <3c604f11.3004891@news.storm.ca> <3n516u0ahojobsdiknc99l00f0euoujb60@4ax.com> <3C60A31C.A5D832F7@elepar.com> <slrna6lpis.6k8.dbt@pianosa.catch22.org> <3C6B5EA3.4A10@mcimail.com> <slrna6p6q1.4c8.dbt@pianosa.catch22.org> 718 17 Xref: inn.example.com news.groups:58
+news.groups:59 Re: Professional practice (was Re: RESULT: sci.military.nuclear-bio-chem fails 73:25) Russ Allbery <rra@stanford.edu> Fri, 15 Feb 2002 19:59:06 -0800 <ylofiq6prp.fsf@windlord.stanford.edu> <1012432821.79903@isc.org> <yly9i1as0j.fsf@windlord.stanford.edu> <a474cg$qou$1@panix2.panix.com> <yl8za0agmq.fsf@windlord.stanford.edu> <a4jcua$nqo$1@panix2.panix.com> 500 12 Xref: inn.example.com news.groups:59
+news.groups:260 Re: [History] The Guidelines: a preliminary revision history Russ Allbery <rra@stanford.edu> Sat, 16 Feb 2002 12:47:46 -0800 <yleljlnogd.fsf@windlord.stanford.edu> <dbc8daca.0202161159.4c0a1eee@posting.google.com> 7082 173 Xref: inn.example.com news.groups:60
+news.groups:464 Re: [History] The Guidelines: a preliminary revision history Russ Allbery <rra@stanford.edu> Sun, 17 Feb 2002 00:05:02 -0800 <ylbseoh6tt.fsf@windlord.stanford.edu> <dbc8daca.0202161159.4c0a1eee@posting.google.com> <yleljlnogd.fsf@windlord.stanford.edu> <dbc8daca.0202162324.1ce3b411@posting.google.com> 845 22 Xref: inn.example.com news.groups:64
+news.admin.net-abuse.policy:11 Re: [ALT hierarchy] Anyone can newgroup and anyone can rmgroup ? Russ Allbery <rra@stanford.edu> Sun, 17 Feb 2002 13:58:51 -0800 <yllmdrdb38.fsf@windlord.stanford.edu> <5Re78.4895$XS6.585609@news2-win.server.ntlworld.com> <ylelk21l3n.fsf@windlord.stanford.edu> <87pu3mj826.fsf@erlenstar.demon.co.uk> <3mqs5us2puq2ov9gpqn7cqn8hhstojsesj@4ax.com> <b2059324.0202170829.47a7abce@posting.google.com> <3C7022DC.2F2E87DF@trin.ity> 546 11 Xref: inn.example.com news.admin.net-abuse.policy:11
+news.software.nntp:16 Re: [yEnc] some newbie questions Russ Allbery <rra@stanford.edu> Sun, 17 Feb 2002 15:43:07 -0800 <ylpu33brp0.fsf@windlord.stanford.edu> <3c6e6798.9093644@news> <a4kc41$ou1$2@pegasus.csx.cam.ac.uk> <3c702e8c.7007252@news> 1000 24 Xref: inn.example.com news.software.nntp:16
+news.software.nntp:17 Re: RFC 977 extensions? Russ Allbery <rra@stanford.edu> Mon, 18 Feb 2002 13:46:21 -0800 <ylvgcu31le.fsf@windlord.stanford.edu> <Xns91B9E610EE161M8@62.2.16.82> 786 21 Xref: inn.example.com news.software.nntp:17
+gnu.misc.discuss:2 Re: Money matters Russ Allbery <rra@stanford.edu> Mon, 18 Feb 2002 18:27:36 -0800 <yl3czyyzmv.fsf@windlord.stanford.edu> <1629dcd9.0202160842.36e8bdab@posting.google.com> <C4cc8.17$X5.109616@burlma1-snr2> <87heoek2h5.fsf@toncho.dhh.gt.org> <Rvhc8.29$X5.122317@burlma1-snr2> 1014 21 Xref: inn.example.com gnu.misc.discuss:2
+gnu.misc.discuss:3 Re: Money matters Russ Allbery <rra@stanford.edu> Mon, 18 Feb 2002 20:12:07 -0800 <ylr8nixg88.fsf@windlord.stanford.edu> <1629dcd9.0202160842.36e8bdab@posting.google.com> <C4cc8.17$X5.109616@burlma1-snr2> <87heoek2h5.fsf@toncho.dhh.gt.org> <Rvhc8.29$X5.122317@burlma1-snr2> <yl3czyyzmv.fsf@windlord.stanford.edu> <slrna73glj.9cp.jmaynard@thebrain.conmicro.cx> 1896 35 Xref: inn.example.com gnu.misc.discuss:3
+news.software.nntp:18 Re: [yEnc] some newbie questions Russ Allbery <rra@stanford.edu> Tue, 19 Feb 2002 10:34:36 -0800 <yleljh722r.fsf@windlord.stanford.edu> <3c6e6798.9093644@news> <a4kc41$ou1$2@pegasus.csx.cam.ac.uk> <3c702e8c.7007252@news> <a4pknt$4pg$2@pegasus.csx.cam.ac.uk> <53488.j22209@archiver.winews.net> 872 21 Xref: inn.example.com news.software.nntp:18
+news.software.nntp:19 Re: Usefor: Message/external-body - any experiences ? Russ Allbery <rra@stanford.edu> Tue, 19 Feb 2002 10:36:56 -0800 <yl8z9p71yv.fsf@windlord.stanford.edu> <5955a.j22200@archiver.winews.net> 921 22 Xref: inn.example.com news.software.nntp:19
+news.software.nntp:20 Re: "Recomended Format of Usenet Articles with 8-bit Attachements" (yEnc) Russ Allbery <rra@stanford.edu> Wed, 20 Feb 2002 13:49:54 -0800 <ylwux7su0t.fsf@windlord.stanford.edu> <3c7615d0.11232095@news> 1280 30 Xref: inn.example.com news.software.nntp:20
+news.software.nntp:21 Re: [yEnc] Survey time! (Compression or not?) Russ Allbery <rra@stanford.edu> Thu, 21 Feb 2002 16:40:59 -0800 <yleljecpr8.fsf@windlord.stanford.edu> <a504os$736$6@pegasus.csx.cam.ac.uk> <200220022141483852%planb@newsreaders.com> <20020220230116.405$a3@newsreader.com> <1014270020.729921@ok-corral.gunslinger.net> <20020221152617.647$FV@newsreader.com> <a53ut8$7i4$4@pegasus.csx.cam.ac.uk> <20020221183904.819$lz@newsreader.com> 1560 35 Xref: inn.example.com news.software.nntp:21
+news.software.nntp:22 Re: [yEnc] Survey time! (Compression or not?) Russ Allbery <rra@stanford.edu> Thu, 21 Feb 2002 21:35:01 -0800 <ylr8ne6pve.fsf@windlord.stanford.edu> <a504os$736$6@pegasus.csx.cam.ac.uk> <200220022141483852%planb@newsreaders.com> <20020220230116.405$a3@newsreader.com> <1014270020.729921@ok-corral.gunslinger.net> <20020221152617.647$FV@newsreader.com> <a53ut8$7i4$4@pegasus.csx.cam.ac.uk> <20020221183904.819$lz@newsreader.com> <yleljecpr8.fsf@windlord.stanford.edu> <20020221224023.453$bg@newsreader.com> 1560 38 Xref: inn.example.com news.software.nntp:22
+news.software.nntp:23 Re: [yEnc] Survey time! (Compression or not?) Russ Allbery <rra@stanford.edu> Thu, 21 Feb 2002 23:57:04 -0800 <ylvgcq3q5r.fsf@windlord.stanford.edu> <a504os$736$6@pegasus.csx.cam.ac.uk> <200220022141483852%planb@newsreaders.com> <20020220230116.405$a3@newsreader.com> <1014270020.729921@ok-corral.gunslinger.net> <20020221152617.647$FV@newsreader.com> <a53ut8$7i4$4@pegasus.csx.cam.ac.uk> <20020221183904.819$lz@newsreader.com> <yleljecpr8.fsf@windlord.stanford.edu> <20020221224023.453$bg@newsreader.com> <ylr8ne6pve.fsf@windlord.stanford.edu> <20020222021334.828$pl@newsreader.com> 2024 48 Xref: inn.example.com news.software.nntp:23
+news.software.nntp:24 Re: [yEnc] Survey time! (Compression or not?) Russ Allbery <rra@stanford.edu> Fri, 22 Feb 2002 10:24:51 -0800 <ylu1s9nzm4.fsf@windlord.stanford.edu> <a504os$736$6@pegasus.csx.cam.ac.uk> <200220022141483852%planb@newsreaders.com> <20020220230116.405$a3@newsreader.com> <1014270020.729921@ok-corral.gunslinger.net> <20020221152617.647$FV@newsreader.com> <a53ut8$7i4$4@pegasus.csx.cam.ac.uk> <20020221183904.819$lz@newsreader.com> <yleljecpr8.fsf@windlord.stanford.edu> <20020221224023.453$bg@newsreader.com> <ylr8ne6pve.fsf@windlord.stanford.edu> <20020222021334.828$pl@newsreader.com> <ylvgcq3q5r.fsf@windlord.stanford.edu> <20020222120943.799$AG@newsreader.com> 1353 29 Xref: inn.example.com news.software.nntp:24
+news.admin.hierarchies:19 Re: Draft hierarchies intro for news.announce.newusers Russ Allbery <rra@stanford.edu> Fri, 22 Feb 2002 12:35:43 -0800 <ylbsehjluo.fsf@windlord.stanford.edu> <Grp0uH.Gop@presby.edu> <3ik45a.06k.ln@erik.selwerd.nl> <wVpd8.37121$gW4.19914136@news1.rdc1.mi.home.com> <duac7uk00n416m5vn6lo21qupuo9il6mtk@4ax.com> 888 23 Xref: inn.example.com news.admin.hierarchies:19
--- /dev/null
+..foo..:7498 Bogus newsgroup name Russ Allbery <rra@stanford.edu> Sat, 02 Feb 2002 12:51:44 -0800 <test-1@example.com> 481 16 Xref: inn.example.com ..foo..:7498
+../..:7498 Bogus newsgroup name Russ Allbery <rra@stanford.edu> Sat, 02 Feb 2002 12:51:44 -0800 <test-2@example.com> 481 16 Xref: inn.example.com ../..:7498
+/:7498 Bogus newsgroup name Russ Allbery <rra@stanford.edu> Sat, 02 Feb 2002 12:51:44 -0800 <test-3@example.com> 481 16 Xref: inn.example.com /:7498
+bar..:7498 Bogus newsgroup name Russ Allbery <rra@stanford.edu> Sat, 02 Feb 2002 12:51:44 -0800 <test-4@example.com> 481 16 Xref: inn.example.com bar..:7498
+bar../:7498 Bogus newsgroup name Russ Allbery <rra@stanford.edu> Sat, 02 Feb 2002 12:51:44 -0800 <test-5@example.com> 481 16 Xref: inn.example.com bar../:7498
+/../foo:1 Bogus newsgroup name Russ Allbery <rra@stanford.edu> Sat, 02 Feb 2002 12:51:44 -0800 <test-6@example.com> 481 16 Xref: inn.example.com /../foo:1
+.:1 Bogus newsgroup name Russ Allbery <rra@stanford.edu> Sat, 02 Feb 2002 12:51:44 -0800 <test-7@example.com> 481 16 Xref: inn.example.com .:1
+////:7498 Bogus newsgroup name Russ Allbery <rra@stanford.edu> Sat, 02 Feb 2002 12:51:44 -0800 <test-8@example.com> 481 16 Xref: inn.example.com ////:7498
+foo..bar:1 Bogus newsgroup name Russ Allbery <rra@stanford.edu> Sat, 02 Feb 2002 12:51:44 -0800 <test-9@example.com> 481 16 Xref: inn.example.com foo..bar:1
--- /dev/null
+example.test:7498 High-numbered test article Russ Allbery <rra@stanford.edu> Sat, 02 Feb 2002 12:51:44 -0800 <test-1@example.com> 481 16 Xref: inn.example.com example.test:7498
+example.test:7499 High-numbered test article Russ Allbery <rra@stanford.edu> Sat, 02 Feb 2002 12:51:44 -0800 <test-2@example.com> <test-1@example.com> 481 16 Xref: inn.example.com example.test:7499
--- /dev/null
+news.admin.hierarchies:19 Re: Draft hierarchies intro for news.announce.newusers Russ Allbery <rra@stanford.edu> Fri, 22 Feb 2002 12:35:43 -0800 <ylbsehjluo.fsf@windlord.stanford.edu> <Grp0uH.Gop@presby.edu> <3ik45a.06k.ln@erik.selwerd.nl> <wVpd8.37121$gW4.19914136@news1.rdc1.mi.home.com> <duac7uk00n416m5vn6lo21qupuo9il6mtk@4ax.com> 888 23 Xref: inn.example.com news.admin.hierarchies:19
+news.software.nntp:24 Re: [yEnc] Survey time! (Compression or not?) Russ Allbery <rra@stanford.edu> Fri, 22 Feb 2002 10:24:51 -0800 <ylu1s9nzm4.fsf@windlord.stanford.edu> <a504os$736$6@pegasus.csx.cam.ac.uk> <200220022141483852%planb@newsreaders.com> <20020220230116.405$a3@newsreader.com> <1014270020.729921@ok-corral.gunslinger.net> <20020221152617.647$FV@newsreader.com> <a53ut8$7i4$4@pegasus.csx.cam.ac.uk> <20020221183904.819$lz@newsreader.com> <yleljecpr8.fsf@windlord.stanford.edu> <20020221224023.453$bg@newsreader.com> <ylr8ne6pve.fsf@windlord.stanford.edu> <20020222021334.828$pl@newsreader.com> <ylvgcq3q5r.fsf@windlord.stanford.edu> <20020222120943.799$AG@newsreader.com> 1353 29 Xref: inn.example.com news.software.nntp:24
+news.software.nntp:23 Re: [yEnc] Survey time! (Compression or not?) Russ Allbery <rra@stanford.edu> Thu, 21 Feb 2002 23:57:04 -0800 <ylvgcq3q5r.fsf@windlord.stanford.edu> <a504os$736$6@pegasus.csx.cam.ac.uk> <200220022141483852%planb@newsreaders.com> <20020220230116.405$a3@newsreader.com> <1014270020.729921@ok-corral.gunslinger.net> <20020221152617.647$FV@newsreader.com> <a53ut8$7i4$4@pegasus.csx.cam.ac.uk> <20020221183904.819$lz@newsreader.com> <yleljecpr8.fsf@windlord.stanford.edu> <20020221224023.453$bg@newsreader.com> <ylr8ne6pve.fsf@windlord.stanford.edu> <20020222021334.828$pl@newsreader.com> 2024 48 Xref: inn.example.com news.software.nntp:23
+news.software.nntp:22 Re: [yEnc] Survey time! (Compression or not?) Russ Allbery <rra@stanford.edu> Thu, 21 Feb 2002 21:35:01 -0800 <ylr8ne6pve.fsf@windlord.stanford.edu> <a504os$736$6@pegasus.csx.cam.ac.uk> <200220022141483852%planb@newsreaders.com> <20020220230116.405$a3@newsreader.com> <1014270020.729921@ok-corral.gunslinger.net> <20020221152617.647$FV@newsreader.com> <a53ut8$7i4$4@pegasus.csx.cam.ac.uk> <20020221183904.819$lz@newsreader.com> <yleljecpr8.fsf@windlord.stanford.edu> <20020221224023.453$bg@newsreader.com> 1560 38 Xref: inn.example.com news.software.nntp:22
+news.software.nntp:21 Re: [yEnc] Survey time! (Compression or not?) Russ Allbery <rra@stanford.edu> Thu, 21 Feb 2002 16:40:59 -0800 <yleljecpr8.fsf@windlord.stanford.edu> <a504os$736$6@pegasus.csx.cam.ac.uk> <200220022141483852%planb@newsreaders.com> <20020220230116.405$a3@newsreader.com> <1014270020.729921@ok-corral.gunslinger.net> <20020221152617.647$FV@newsreader.com> <a53ut8$7i4$4@pegasus.csx.cam.ac.uk> <20020221183904.819$lz@newsreader.com> 1560 35 Xref: inn.example.com news.software.nntp:21
+news.software.nntp:20 Re: "Recomended Format of Usenet Articles with 8-bit Attachements" (yEnc) Russ Allbery <rra@stanford.edu> Wed, 20 Feb 2002 13:49:54 -0800 <ylwux7su0t.fsf@windlord.stanford.edu> <3c7615d0.11232095@news> 1280 30 Xref: inn.example.com news.software.nntp:20
+news.software.nntp:19 Re: Usefor: Message/external-body - any experiences ? Russ Allbery <rra@stanford.edu> Tue, 19 Feb 2002 10:36:56 -0800 <yl8z9p71yv.fsf@windlord.stanford.edu> <5955a.j22200@archiver.winews.net> 921 22 Xref: inn.example.com news.software.nntp:19
+news.software.nntp:18 Re: [yEnc] some newbie questions Russ Allbery <rra@stanford.edu> Tue, 19 Feb 2002 10:34:36 -0800 <yleljh722r.fsf@windlord.stanford.edu> <3c6e6798.9093644@news> <a4kc41$ou1$2@pegasus.csx.cam.ac.uk> <3c702e8c.7007252@news> <a4pknt$4pg$2@pegasus.csx.cam.ac.uk> <53488.j22209@archiver.winews.net> 872 21 Xref: inn.example.com news.software.nntp:18
+gnu.misc.discuss:3 Re: Money matters Russ Allbery <rra@stanford.edu> Mon, 18 Feb 2002 20:12:07 -0800 <ylr8nixg88.fsf@windlord.stanford.edu> <1629dcd9.0202160842.36e8bdab@posting.google.com> <C4cc8.17$X5.109616@burlma1-snr2> <87heoek2h5.fsf@toncho.dhh.gt.org> <Rvhc8.29$X5.122317@burlma1-snr2> <yl3czyyzmv.fsf@windlord.stanford.edu> <slrna73glj.9cp.jmaynard@thebrain.conmicro.cx> 1896 35 Xref: inn.example.com gnu.misc.discuss:3
+gnu.misc.discuss:2 Re: Money matters Russ Allbery <rra@stanford.edu> Mon, 18 Feb 2002 18:27:36 -0800 <yl3czyyzmv.fsf@windlord.stanford.edu> <1629dcd9.0202160842.36e8bdab@posting.google.com> <C4cc8.17$X5.109616@burlma1-snr2> <87heoek2h5.fsf@toncho.dhh.gt.org> <Rvhc8.29$X5.122317@burlma1-snr2> 1014 21 Xref: inn.example.com gnu.misc.discuss:2
+news.software.nntp:17 Re: RFC 977 extensions? Russ Allbery <rra@stanford.edu> Mon, 18 Feb 2002 13:46:21 -0800 <ylvgcu31le.fsf@windlord.stanford.edu> <Xns91B9E610EE161M8@62.2.16.82> 786 21 Xref: inn.example.com news.software.nntp:17
+news.software.nntp:16 Re: [yEnc] some newbie questions Russ Allbery <rra@stanford.edu> Sun, 17 Feb 2002 15:43:07 -0800 <ylpu33brp0.fsf@windlord.stanford.edu> <3c6e6798.9093644@news> <a4kc41$ou1$2@pegasus.csx.cam.ac.uk> <3c702e8c.7007252@news> 1000 24 Xref: inn.example.com news.software.nntp:16
+news.admin.net-abuse.policy:11 Re: [ALT hierarchy] Anyone can newgroup and anyone can rmgroup ? Russ Allbery <rra@stanford.edu> Sun, 17 Feb 2002 13:58:51 -0800 <yllmdrdb38.fsf@windlord.stanford.edu> <5Re78.4895$XS6.585609@news2-win.server.ntlworld.com> <ylelk21l3n.fsf@windlord.stanford.edu> <87pu3mj826.fsf@erlenstar.demon.co.uk> <3mqs5us2puq2ov9gpqn7cqn8hhstojsesj@4ax.com> <b2059324.0202170829.47a7abce@posting.google.com> <3C7022DC.2F2E87DF@trin.ity> 546 11 Xref: inn.example.com news.admin.net-abuse.policy:11
+news.groups:464 Re: [History] The Guidelines: a preliminary revision history Russ Allbery <rra@stanford.edu> Sun, 17 Feb 2002 00:05:02 -0800 <ylbseoh6tt.fsf@windlord.stanford.edu> <dbc8daca.0202161159.4c0a1eee@posting.google.com> <yleljlnogd.fsf@windlord.stanford.edu> <dbc8daca.0202162324.1ce3b411@posting.google.com> 845 22 Xref: inn.example.com news.groups:64
+news.groups:260 Re: [History] The Guidelines: a preliminary revision history Russ Allbery <rra@stanford.edu> Sat, 16 Feb 2002 12:47:46 -0800 <yleljlnogd.fsf@windlord.stanford.edu> <dbc8daca.0202161159.4c0a1eee@posting.google.com> 7082 173 Xref: inn.example.com news.groups:60
+news.groups:59 Re: Professional practice (was Re: RESULT: sci.military.nuclear-bio-chem fails 73:25) Russ Allbery <rra@stanford.edu> Fri, 15 Feb 2002 19:59:06 -0800 <ylofiq6prp.fsf@windlord.stanford.edu> <1012432821.79903@isc.org> <yly9i1as0j.fsf@windlord.stanford.edu> <a474cg$qou$1@panix2.panix.com> <yl8za0agmq.fsf@windlord.stanford.edu> <a4jcua$nqo$1@panix2.panix.com> 500 12 Xref: inn.example.com news.groups:59
+news.groups:58 Re: RESULT: sci.military.nuclear-bio-chem fails 73:25 Russ Allbery <rra@stanford.edu> Thu, 14 Feb 2002 21:40:08 -0800 <ylvgczi9qf.fsf@windlord.stanford.edu> <1012432821.79903@isc.org> <a3obho02a17@enews2.newsguy.com> <a3pcp9$3d8$1@gw.retro.com> <3c6038d6.774936@news.storm.ca> <a3pg75$3vk$1@gw.retro.com> <3c604f11.3004891@news.storm.ca> <3n516u0ahojobsdiknc99l00f0euoujb60@4ax.com> <3C60A31C.A5D832F7@elepar.com> <slrna6lpis.6k8.dbt@pianosa.catch22.org> <3C6B5EA3.4A10@mcimail.com> <slrna6p6q1.4c8.dbt@pianosa.catch22.org> 718 17 Xref: inn.example.com news.groups:58
+news.groups:57 Re: comp.distributed again? Russ Allbery <rra@stanford.edu> Thu, 14 Feb 2002 13:43:52 -0800 <ylu1sjkacn.fsf@windlord.stanford.edu> <1012432821.79903@isc.org> <a4co04$d94$1@slb2.atl.mindspring.net> <a4edc2$uds$5@dent.deepthot.org> <a4frn0$71f$1@slb3.atl.mindspring.net> <a4frot$inj$8@dent.deepthot.org> 360 10 Xref: inn.example.com news.groups:57
+news.software.nntp:15 Re: chan.c problem with INN 2.3.[012] on AIX 4.2 Russ Allbery <rra@stanford.edu> Thu, 14 Feb 2002 11:49:28 -0800 <yl7kpfq1x3.fsf@windlord.stanford.edu> <fa8727e8.0202141109.7875ddc@posting.google.com> 1449 42 Xref: inn.example.com news.software.nntp:15
+gnu.emacs.gnus:8 Re: modifying Sender: to (valid) From: of gnus-posting-styles Russ Allbery <rra@stanford.edu> Wed, 13 Feb 2002 13:46:55 -0800 <yl3d05awc0.fsf@windlord.stanford.edu> <m0it93yajy.fsf@k2.onsight.com> <vxkbsevzlcc.fsf@cinnamon.vanillaknot.com> <uy9hzy4yp.fsf@synopsys.com> <ylbsev7et9.fsf@windlord.stanford.edu> <ilu665235iy.fsf@extundo.com> <ylwuxia2hh.fsf@windlord.stanford.edu> <1yfpzc36.fsf@hschmi22.userfqdn.rz-online.de> 326 11 Xref: inn.example.com gnu.emacs.gnus:8
+gnu.emacs.gnus:7 Re: modifying Sender: to (valid) From: of gnus-posting-styles Russ Allbery <rra@stanford.edu> Tue, 12 Feb 2002 12:07:06 -0800 <ylwuxia2hh.fsf@windlord.stanford.edu> <m0it93yajy.fsf@k2.onsight.com> <vxkbsevzlcc.fsf@cinnamon.vanillaknot.com> <uy9hzy4yp.fsf@synopsys.com> <ylbsev7et9.fsf@windlord.stanford.edu> <ilu665235iy.fsf@extundo.com> 426 11 Xref: inn.example.com gnu.emacs.gnus:7
+news.software.nntp:14 Re: rebuilding overview with makehistory Russ Allbery <rra@stanford.edu> Tue, 12 Feb 2002 11:15:20 -0800 <yladuefr5j.fsf@windlord.stanford.edu> <slrna6gcn5.nmf.br@panix2.panix.com> <ylofiv5tv3.fsf@windlord.stanford.edu> <slrna6ig0n.qig.br@panix2.panix.com> 505 13 Xref: inn.example.com news.software.nntp:14
+news.software.nntp:13 Re: rebuilding overview with makehistory Russ Allbery <rra@stanford.edu> Mon, 11 Feb 2002 18:14:40 -0800 <ylofiv5tv3.fsf@windlord.stanford.edu> <slrna6gcn5.nmf.br@panix2.panix.com> 687 20 Xref: inn.example.com news.software.nntp:13
+gnu.emacs.gnus:6 Re: modifying Sender: to (valid) From: of gnus-posting-styles Russ Allbery <rra@stanford.edu> Mon, 11 Feb 2002 15:56:50 -0800 <ylbsev7et9.fsf@windlord.stanford.edu> <m0it93yajy.fsf@k2.onsight.com> <vxkbsevzlcc.fsf@cinnamon.vanillaknot.com> <uy9hzy4yp.fsf@synopsys.com> 1021 23 Xref: inn.example.com gnu.emacs.gnus:6
+gnu.emacs.gnus:2 Re: modifying Sender: to (valid) From: of gnus-posting-styles Russ Allbery <rra@stanford.edu> Mon, 11 Feb 2002 15:55:01 -0800 <ylheon7ewa.fsf@windlord.stanford.edu> <m0it93yajy.fsf@k2.onsight.com> <vxkbsevzlcc.fsf@cinnamon.vanillaknot.com> <m0it9339nj.fsf@k2.onsight.com> 175 8 Xref: inn.example.com gnu.emacs.gnus:2
+news.admin.technical:2 Re: Maximum Length of Newsgroup Description field Russ Allbery <rra@stanford.edu> Mon, 11 Feb 2002 13:05:47 -0800 <ylit9391as.fsf@windlord.stanford.edu> <Xns91B278CC1107Djaydejaydenet@157.54.3.22> 526 12 Xref: inn.example.com news.admin.technical:2
+news.groups:56 Re: RESULT: sci.military.nuclear-bio-chem fails 73:25 Russ Allbery <rra@stanford.edu> Sun, 10 Feb 2002 20:01:15 -0800 <yleljs8y5w.fsf@windlord.stanford.edu> <1012432821.79903@isc.org> <ylsn8b76ad.fsf@windlord.stanford.edu> <a42f8b$rfj$1@samba.rahul.net> <yllme3azio.fsf@windlord.stanford.edu> <GrAr0y.AsA@world.std.com> <yln0yh22y7.fsf@windlord.stanford.edu> <3c68f0c2.3904908@supernews.seanet.com> <yly9i1as0j.fsf@windlord.stanford.edu> <a47aeg01e39@enews3.newsguy.com> 1482 35 Xref: inn.example.com news.groups:56
+news.groups:53 Re: Professional practice (was Re: RESULT: sci.military.nuclear-bio-chem fails 73:25) Russ Allbery <rra@stanford.edu> Sun, 10 Feb 2002 18:37:01 -0800 <yl8za0agmq.fsf@windlord.stanford.edu> <1012432821.79903@isc.org> <yln0yh22y7.fsf@windlord.stanford.edu> <3c68f0c2.3904908@supernews.seanet.com> <yly9i1as0j.fsf@windlord.stanford.edu> <a474cg$qou$1@panix2.panix.com> 1274 30 Xref: inn.example.com news.groups:53
+gnu.emacs.gnus:1 Re: Gnus, Postings, and Anti-spam? Russ Allbery <rra@stanford.edu> Sun, 10 Feb 2002 14:37:51 -0800 <ylheoparpc.fsf@windlord.stanford.edu> <ubsezbqfg.fsf@synopsys.com> <geg.y1ysn8apwb0.fsf@fly.verified.de> <eljuvgy6.fsf@ichimusai.org> <geg.y1y3d0apg8z.fsf@fly.verified.de> <vgd52d4a.fsf@ichimusai.org> 862 23 Xref: inn.example.com gnu.emacs.gnus:1
+news.groups:50 Re: RESULT: sci.military.nuclear-bio-chem fails 73:25 Russ Allbery <rra@stanford.edu> Sun, 10 Feb 2002 14:31:08 -0800 <yly9i1as0j.fsf@windlord.stanford.edu> <1012432821.79903@isc.org> <ylsn8b76ad.fsf@windlord.stanford.edu> <a42f8b$rfj$1@samba.rahul.net> <yllme3azio.fsf@windlord.stanford.edu> <GrAr0y.AsA@world.std.com> <yln0yh22y7.fsf@windlord.stanford.edu> <3c68f0c2.3904908@supernews.seanet.com> 1723 38 Xref: inn.example.com news.groups:50
+news.groups:49 Re: 2nd RFD: sci.space.moderated moderated Russ Allbery <rra@stanford.edu> Sun, 10 Feb 2002 12:12:17 -0800 <ylsn89f65a.fsf@windlord.stanford.edu> <1010713186.32070@isc.org> <a440uv$r73$2@dent.deepthot.org> <u6b36tthfaecfb@corp.supernews.com> <a448lr$il$1@dent.deepthot.org> <3c65d1dc$0$67479$892e7fe2@authen.puce.readfreenews.net> 3599 70 Xref: inn.example.com news.groups:49
+news.groups:48 Re: RESULT: sci.military.nuclear-bio-chem fails 73:25 Russ Allbery <rra@stanford.edu> Sun, 10 Feb 2002 11:48:18 -0800 <yly9i1f799.fsf@windlord.stanford.edu> <1012432821.79903@isc.org> <ylheop22ug.fsf@windlord.stanford.edu> <a46ec6$lee$1@samba.rahul.net> <ylpu3dgoni.fsf@windlord.stanford.edu> <a46i88$m4m$1@samba.rahul.net> 1563 32 Xref: inn.example.com news.groups:48
+news.groups:47 Re: 2nd RFD: sci.space.moderated moderated Russ Allbery <rra@stanford.edu> Sun, 10 Feb 2002 10:54:24 -0800 <ylk7tlgobj.fsf@windlord.stanford.edu> <1010713186.32070@isc.org> <3N898.13340$Zu6.55955@news-server.bigpond.net.au> <u6argep8r8l2c2@corp.supernews.com> <Xns91B0C720E8590grahamdrabblelineone@ID-77355.user.dfncis.de> <a440uv$r73$2@dent.deepthot.org> <u6b36tthfaecfb@corp.supernews.com> 1235 23 Xref: inn.example.com news.groups:47
+news.groups:46 Re: RESULT: sci.military.nuclear-bio-chem fails 73:25 Russ Allbery <rra@stanford.edu> Sun, 10 Feb 2002 10:47:13 -0800 <ylpu3dgoni.fsf@windlord.stanford.edu> <1012432821.79903@isc.org> <yllme3azio.fsf@windlord.stanford.edu> <a44mu80urn@enews2.newsguy.com> <ylheop22ug.fsf@windlord.stanford.edu> <a46ec6$lee$1@samba.rahul.net> 1052 28 Xref: inn.example.com news.groups:46
+news.software.nntp:12 Re: Trailing spaces in body on INN 2.3.3 ? Russ Allbery <rra@stanford.edu> Sun, 10 Feb 2002 09:32:55 -0800 <yl8za1kzso.fsf@windlord.stanford.edu> <52419.622207@archiver.winews.net> <a405vd$huq$1@newsread4.arcor-online.net> <yl3d0a8ibo.fsf@windlord.stanford.edu> <21049.a22200@archiver.winews.net> 1915 40 Xref: inn.example.com news.software.nntp:12
+news.groups:44 Re: RESULT: sci.military.nuclear-bio-chem fails 73:25 Russ Allbery <rra@stanford.edu> Sat, 09 Feb 2002 23:49:43 -0800 <ylheop22ug.fsf@windlord.stanford.edu> <1012432821.79903@isc.org> <a3v87u$ihf$1@panix2.panix.com> <a409ek$qc8$1@gw.retro.com> <ylsn8b76ad.fsf@windlord.stanford.edu> <a42f8b$rfj$1@samba.rahul.net> <yllme3azio.fsf@windlord.stanford.edu> <a44mu80urn@enews2.newsguy.com> 810 16 Xref: inn.example.com news.groups:44
+news.groups:43 Re: RESULT: sci.military.nuclear-bio-chem fails 73:25 Russ Allbery <rra@stanford.edu> Sat, 09 Feb 2002 23:47:28 -0800 <yln0yh22y7.fsf@windlord.stanford.edu> <1012432821.79903@isc.org> <ylsn8b76ad.fsf@windlord.stanford.edu> <a42f8b$rfj$1@samba.rahul.net> <yllme3azio.fsf@windlord.stanford.edu> <GrAr0y.AsA@world.std.com> 2306 45 Xref: inn.example.com news.groups:43
+news.software.nntp:11 Re: Trailing spaces in body on INN 2.3.3 ? Russ Allbery <rra@stanford.edu> Sat, 09 Feb 2002 13:18:51 -0800 <yl3d0a8ibo.fsf@windlord.stanford.edu> <52419.622207@archiver.winews.net> <a405vd$huq$1@newsread4.arcor-online.net> <ylit9723rb.fsf@windlord.stanford.edu> <2504c.922202@archiver.winews.net> 1075 27 Xref: inn.example.com news.software.nntp:11
+comp.lang.perl.moderated:1 Re: perldoc vs man why so much slower Russ Allbery <rra@stanford.edu> Sat, 09 Feb 2002 12:47:26 -0800 <yl8za28js1.fsf@windlord.stanford.edu> <m1d6zfnl9h.fsf@reader.newsguy.com> <20020209180911.23756.qmail@plover.com> 799 20 Xref: inn.example.com comp.lang.perl.moderated:1
+news.admin.hierarchies:18 Re: Are the active files at ftp.isc.org maintained? Russ Allbery <rra@stanford.edu> Sat, 09 Feb 2002 12:25:07 -0800 <ylvgd68kt8.fsf@windlord.stanford.edu> <m3lme7zhep.fsf@defun.localdomain> <yl7kprjrx4.fsf@windlord.stanford.edu> <m3y9i7xluu.fsf@defun.localdomain> <ylu1srclla.fsf@windlord.stanford.edu> <m37kpmdayt.fsf@defun.localdomain> 580 18 Xref: inn.example.com news.admin.hierarchies:18
+news.groups:42 Re: RESULT: sci.military.nuclear-bio-chem fails 73:25 Russ Allbery <rra@stanford.edu> Sat, 09 Feb 2002 12:20:01 -0800 <yl1yfu9zm6.fsf@windlord.stanford.edu> <1012432821.79903@isc.org> <a3uq5f$n81$1@news.orst.edu> <a3v87u$ihf$1@panix2.panix.com> <a409ek$qc8$1@gw.retro.com> <3c651972$0$67480$892e7fe2@authen.puce.readfreenews.net> 990 19 Xref: inn.example.com news.groups:42
+news.admin.hierarchies:13 Re: [INFO] bc.* and van.* Russ Allbery <rra@stanford.edu> Sat, 09 Feb 2002 12:00:56 -0800 <yl8za2a0hz.fsf@windlord.stanford.edu> <a36mup$160fpd$1@ID-80930.news.dfncis.de> 905 23 Xref: inn.example.com news.admin.hierarchies:13
+news.groups:41 Re: RESULT: sci.military.nuclear-bio-chem fails 73:25 Russ Allbery <rra@stanford.edu> Fri, 08 Feb 2002 23:24:31 -0800 <yllme3azio.fsf@windlord.stanford.edu> <1012432821.79903@isc.org> <a3v87u$ihf$1@panix2.panix.com> <a409ek$qc8$1@gw.retro.com> <ylsn8b76ad.fsf@windlord.stanford.edu> <a42f8b$rfj$1@samba.rahul.net> 1282 26 Xref: inn.example.com news.groups:41
+news.admin.hierarchies:12 Re: Are the active files at ftp.isc.org maintained? Russ Allbery <rra@stanford.edu> Fri, 08 Feb 2002 20:42:25 -0800 <ylu1srclla.fsf@windlord.stanford.edu> <m3lme7zhep.fsf@defun.localdomain> <yl7kprjrx4.fsf@windlord.stanford.edu> <m3y9i7xluu.fsf@defun.localdomain> 505 14 Xref: inn.example.com news.admin.hierarchies:12
+news.groups:40 Re: RESULT: sci.military.nuclear-bio-chem fails 73:25 Russ Allbery <rra@stanford.edu> Fri, 08 Feb 2002 18:11:54 -0800 <ylsn8b76ad.fsf@windlord.stanford.edu> <1012432821.79903@isc.org> <a3tb6q$i69$1@gw.retro.com> <a3uq5f$n81$1@news.orst.edu> <a3v87u$ihf$1@panix2.panix.com> <a409ek$qc8$1@gw.retro.com> 1995 40 Xref: inn.example.com news.groups:40
+news.software.nntp:10 Re: Trailing spaces in body on INN 2.3.3 ? Russ Allbery <rra@stanford.edu> Fri, 08 Feb 2002 12:15:43 -0800 <ylsn8bwx00.fsf@windlord.stanford.edu> <52419.622207@archiver.winews.net> <a405vd$huq$1@newsread4.arcor-online.net> <ylit9723rb.fsf@windlord.stanford.edu> <C2W88.119862$i3.977629@news.easynews.com> 749 19 Xref: inn.example.com news.software.nntp:10
+news.software.nntp:9 Re: Trailing spaces in body on INN 2.3.3 ? Russ Allbery <rra@stanford.edu> Fri, 08 Feb 2002 11:05:28 -0800 <ylit9723rb.fsf@windlord.stanford.edu> <52419.622207@archiver.winews.net> <a405vd$huq$1@newsread4.arcor-online.net> 556 16 Xref: inn.example.com news.software.nntp:9
+news.software.nntp:6 Re: innd and Microsoft Exchange Russ Allbery <rra@stanford.edu> Fri, 08 Feb 2002 11:04:25 -0800 <ylofiz23t2.fsf@windlord.stanford.edu> <623f5703.0202070541.2b943211@posting.google.com> <yleljx41dn.fsf@windlord.stanford.edu> <3C63AD90.492F3C00@eproduction.ch> 541 14 Xref: inn.example.com news.software.nntp:6
+news.software.nntp:5 Re: innd and Microsoft Exchange Russ Allbery <rra@stanford.edu> Thu, 07 Feb 2002 10:01:40 -0800 <yleljx41dn.fsf@windlord.stanford.edu> <623f5703.0202070541.2b943211@posting.google.com> 1013 21 Xref: inn.example.com news.software.nntp:5
+news.groups:39 Re: RESULT: sci.military.nuclear-bio-chem fails 73:25 Russ Allbery <rra@stanford.edu> Thu, 07 Feb 2002 00:46:46 -0800 <yl3d0dn0g9.fsf@windlord.stanford.edu> <1012432821.79903@isc.org> <a3obho02a17@enews2.newsguy.com> <a3pcp9$3d8$1@gw.retro.com> <yl8za5n769.fsf@windlord.stanford.edu> <a3tb6q$i69$1@gw.retro.com> 5108 96 Xref: inn.example.com news.groups:39
+news.groups:38 Re: RESULT: sci.military.nuclear-bio-chem fails 73:25 Russ Allbery <rra@stanford.edu> Wed, 06 Feb 2002 22:21:34 -0800 <yl8za5n769.fsf@windlord.stanford.edu> <1012432821.79903@isc.org> <1j6l5u499l6ru00rn6ibje0cgsv32jkrk1@4ax.com> <a3f1oq02gd2@enews3.newsguy.com> <a3obho02a17@enews2.newsguy.com> <a3pcp9$3d8$1@gw.retro.com> 1093 26 Xref: inn.example.com news.groups:38
+news.admin.hierarchies:11 Re: control.ctl maintenance (was: [INFO] bc.* and van.*) Russ Allbery <rra@stanford.edu> Wed, 06 Feb 2002 16:20:13 -0800 <yleljyp2gy.fsf@windlord.stanford.edu> <a36mup$160fpd$1@ID-80930.news.dfncis.de> <a3qseq.3vue8up.1@mid.glglgl.de> <a3qvtt$19gllq$1@ID-80930.news.dfncis.de> <yllme6s1np.fsf@windlord.stanford.edu> <EKi88.17124$gW4.11422684@news1.rdc1.mi.home.com> 688 18 Xref: inn.example.com news.admin.hierarchies:11
+news.admin.hierarchies:7 Re: [INFO] bc.* and van.* Russ Allbery <rra@stanford.edu> Wed, 06 Feb 2002 16:19:10 -0800 <ylk7tqp2ip.fsf@windlord.stanford.edu> <a36mup$160fpd$1@ID-80930.news.dfncis.de> <a3ptk7.3vuai51.1@mid.glglgl.de> <a3qmdq$1akiad$1@ID-80930.news.dfncis.de> <a3qseq.3vue8up.1@mid.glglgl.de> <a3qvtt$19gllq$1@ID-80930.news.dfncis.de> <yllme6s1np.fsf@windlord.stanford.edu> <a3sbmu$1ao4a2$1@ID-80930.news.dfncis.de> 421 11 Xref: inn.example.com news.admin.hierarchies:7
+news.admin.hierarchies:4 Re: [INFO] bc.* and van.* Russ Allbery <rra@stanford.edu> Wed, 06 Feb 2002 14:09:30 -0800 <yllme6s1np.fsf@windlord.stanford.edu> <a36mup$160fpd$1@ID-80930.news.dfncis.de> <a3ptk7.3vuai51.1@mid.glglgl.de> <a3qmdq$1akiad$1@ID-80930.news.dfncis.de> <a3qseq.3vue8up.1@mid.glglgl.de> <a3qvtt$19gllq$1@ID-80930.news.dfncis.de> 972 20 Xref: inn.example.com news.admin.hierarchies:4
+news.admin.hierarchies:3 Re: Are the active files at ftp.isc.org maintained? Russ Allbery <rra@stanford.edu> Wed, 06 Feb 2002 14:00:23 -0800 <yl8za6tgnc.fsf@windlord.stanford.edu> <m3lme7zhep.fsf@defun.localdomain> <yl7kprjrx4.fsf@windlord.stanford.edu> <m3y9i7xluu.fsf@defun.localdomain> <yl665bqhku.fsf@windlord.stanford.edu> <m3lme68jz6.fsf@defun.localdomain> 478 17 Xref: inn.example.com news.admin.hierarchies:3
+news.admin.net-abuse.policy:10 Re: [RETROMODERATION] teenfem terrorists Russ Allbery <rra@stanford.edu> Wed, 06 Feb 2002 13:58:09 -0800 <yleljytgr2.fsf@windlord.stanford.edu> <6f826u0dag3c5gosbps2elao0scnu38k0p@news-01.easynews.com> <ylr8ny7eaf.fsf@windlord.stanford.edu> <1013015167.535798@ok-corral.gunslinger.net> 548 13 Xref: inn.example.com news.admin.net-abuse.policy:10
+news.admin.net-abuse.policy:9 Re: [RETROMODERATION] teenfem terrorists Russ Allbery <rra@stanford.edu> Wed, 06 Feb 2002 13:57:38 -0800 <ylk7tqtgrx.fsf@windlord.stanford.edu> <6f826u0dag3c5gosbps2elao0scnu38k0p@news-01.easynews.com> <ylr8ny7eaf.fsf@windlord.stanford.edu> <878za6o8h5.fsf@erlenstar.demon.co.uk> 578 16 Xref: inn.example.com news.admin.net-abuse.policy:9
+news.software.nntp:4 Re: first > last Russ Allbery <rra@stanford.edu> Wed, 06 Feb 2002 13:50:15 -0800 <yly9i6uvoo.fsf@windlord.stanford.edu> <3C61951F.8080606@gemal.dk> 850 19 Xref: inn.example.com news.software.nntp:4
+news.software.nntp:3 Re: Trailing spaces in body on INN 2.3.3 ? Russ Allbery <rra@stanford.edu> Wed, 06 Feb 2002 08:45:04 -0800 <yllme67e5r.fsf@windlord.stanford.edu> <52419.622207@archiver.winews.net> 852 21 Xref: inn.example.com news.software.nntp:3
+news.admin.net-abuse.policy:8 Re: [RETROMODERATION] teenfem terrorists Russ Allbery <rra@stanford.edu> Wed, 06 Feb 2002 08:42:16 -0800 <ylr8ny7eaf.fsf@windlord.stanford.edu> <6f826u0dag3c5gosbps2elao0scnu38k0p@news-01.easynews.com> 740 18 Xref: inn.example.com news.admin.net-abuse.policy:8
+news.admin.hierarchies:2 Re: Are the active files at ftp.isc.org maintained? Russ Allbery <rra@stanford.edu> Tue, 05 Feb 2002 21:56:17 -0800 <yl665bqhku.fsf@windlord.stanford.edu> <m3lme7zhep.fsf@defun.localdomain> <yl7kprjrx4.fsf@windlord.stanford.edu> <m3y9i7xluu.fsf@defun.localdomain> 828 19 Xref: inn.example.com news.admin.hierarchies:2
+news.software.nntp:2 Re: INN on another user/group Russ Allbery <rra@stanford.edu> Tue, 05 Feb 2002 19:14:03 -0800 <ylg04fs3no.fsf@windlord.stanford.edu> <3c609757$0$20968$afc38c87@news.optusnet.com.au> 593 15 Xref: inn.example.com news.software.nntp:2
+news.groups:37 Re: CFV: sci.geo.cartography Russ Allbery <rra@stanford.edu> Tue, 05 Feb 2002 18:07:55 -0800 <ylvgdbicqs.fsf@windlord.stanford.edu> <1010622320.26971@isc.org> <1012843693.45036@isc.org> <3c5f4805$0$36783$892e7fe2@authen.puce.readfreenews.net> <3C608D3D.C87E3610@diogenes.sacramento.ca.us> 623 13 Xref: inn.example.com news.groups:37
+news.admin.hierarchies:1 Re: Are the active files at ftp.isc.org maintained? Russ Allbery <rra@stanford.edu> Tue, 05 Feb 2002 17:54:47 -0800 <yl7kprjrx4.fsf@windlord.stanford.edu> <m3lme7zhep.fsf@defun.localdomain> 877 21 Xref: inn.example.com news.admin.hierarchies:1
+news.admin.technical:1 Re: nnrpd Russ Allbery <rra@stanford.edu> Tue, 05 Feb 2002 17:53:18 -0800 <yld6zjjrzl.fsf@windlord.stanford.edu> <a3psen$7p4$1@quasar.ctc.edu> 543 13 Xref: inn.example.com news.admin.technical:1
+news.groups:36 Re: [META RFD] Removal of low traffic groups. Russ Allbery <rra@stanford.edu> Tue, 05 Feb 2002 17:51:33 -0800 <ylit9bjs2i.fsf@windlord.stanford.edu> <Xns91A5EF6DA9BCEgrahamdrabblelineone@ID-77355.user.dfncis.de> <3c5f0eb7$0$36784$892e7fe2@authen.puce.readfreenews.net> <Xns91AD188E6226grahamdrabblelineone@ID-77355.user.dfncis.de> 1067 21 Xref: inn.example.com news.groups:36
+news.groups:34 Re: CFV: sci.geo.cartography Russ Allbery <rra@stanford.edu> Tue, 05 Feb 2002 17:46:28 -0800 <ylofj3jsaz.fsf@windlord.stanford.edu> <1010622320.26971@isc.org> <1012843693.45036@isc.org> <3c5f4805$0$36783$892e7fe2@authen.puce.readfreenews.net> <m2k7trcxes.fsf@dan.jacobson.tw> <3C6077F5.AE1C5ED8@sfo.com> 882 22 Xref: inn.example.com news.groups:34
+news.software.nntp:1 Re: peering under the storage API Russ Allbery <rra@stanford.edu> Tue, 05 Feb 2002 17:04:13 -0800 <ylit9bl8tu.fsf@windlord.stanford.edu> <slrna60q6l.3fo.br@panix2.panix.com> 546 14 Xref: inn.example.com news.software.nntp:1
+news.admin.net-abuse.policy:7 Re: [ALT hierarchy] Anyone can newgroup and anyone can rmgroup ? Russ Allbery <rra@stanford.edu> Tue, 05 Feb 2002 08:52:18 -0800 <yl4rkvx459.fsf@windlord.stanford.edu> <5Re78.4895$XS6.585609@news2-win.server.ntlworld.com> <ylelk21l3n.fsf@windlord.stanford.edu> <Gqz2AI.JH2@world.std.com> <ylr8o2z2c5.fsf@windlord.stanford.edu> <Gr0r5x.M8y@world.std.com> <ylr8o1xb4b.fsf@windlord.stanford.edu> <Gr1CnE.Iny@world.std.com> <ylk7tsofmi.fsf@windlord.stanford.edu> <Gr1LLx.6xF@world.std.com> 3618 69 Xref: inn.example.com news.admin.net-abuse.policy:7
+news.groups:33 Re: CFV: sci.geo.cartography Russ Allbery <rra@stanford.edu> Mon, 04 Feb 2002 19:04:16 -0800 <ylheowbpe7.fsf@windlord.stanford.edu> <1010622320.26971@isc.org> <1012843693.45036@isc.org> <3c5f4805$0$36783$892e7fe2@authen.puce.readfreenews.net> 851 17 Xref: inn.example.com news.groups:33
+news.groups:32 Re: [META RFD] Removal of low traffic groups. Russ Allbery <rra@stanford.edu> Mon, 04 Feb 2002 19:02:56 -0800 <yln0yobpgf.fsf@windlord.stanford.edu> <Xns91A5EF6DA9BCEgrahamdrabblelineone@ID-77355.user.dfncis.de> <3c5f0eb7$0$36784$892e7fe2@authen.puce.readfreenews.net> <3c5u5u08g2u5tva8i6gd1087ug4uma6051@4ax.com> <yl4rkwu8mx.fsf@windlord.stanford.edu> <a3n74c026se@enews4.newsguy.com> <ylofj4rcf0.fsf@windlord.stanford.edu> <a3nbva02f0r@enews4.newsguy.com> 1280 26 Xref: inn.example.com news.groups:32
+news.admin.net-abuse.policy:6 Re: [ALT hierarchy] Anyone can newgroup and anyone can rmgroup ? Russ Allbery <rra@stanford.edu> Mon, 04 Feb 2002 17:56:53 -0800 <ylk7tsofmi.fsf@windlord.stanford.edu> <5Re78.4895$XS6.585609@news2-win.server.ntlworld.com> <ylelk21l3n.fsf@windlord.stanford.edu> <Gqz2AI.JH2@world.std.com> <ylr8o2z2c5.fsf@windlord.stanford.edu> <Gr0r5x.M8y@world.std.com> <ylr8o1xb4b.fsf@windlord.stanford.edu> <Gr1CnE.Iny@world.std.com> 961 18 Xref: inn.example.com news.admin.net-abuse.policy:6
+news.groups:27 Re: [META RFD] Removal of low traffic groups. Russ Allbery <rra@stanford.edu> Mon, 04 Feb 2002 16:37:55 -0800 <ylofj4rcf0.fsf@windlord.stanford.edu> <Xns91A5EF6DA9BCEgrahamdrabblelineone@ID-77355.user.dfncis.de> <3c5f0eb7$0$36784$892e7fe2@authen.puce.readfreenews.net> <3c5u5u08g2u5tva8i6gd1087ug4uma6051@4ax.com> <yl4rkwu8mx.fsf@windlord.stanford.edu> <a3n74c026se@enews4.newsguy.com> 899 22 Xref: inn.example.com news.groups:27
+news.groups:26 Re: [META RFD] Removal of low traffic groups. Russ Allbery <rra@stanford.edu> Mon, 04 Feb 2002 15:31:18 -0800 <yl4rkwu8mx.fsf@windlord.stanford.edu> <Xns91A5EF6DA9BCEgrahamdrabblelineone@ID-77355.user.dfncis.de> <3c5f0eb7$0$36784$892e7fe2@authen.puce.readfreenews.net> <3c5u5u08g2u5tva8i6gd1087ug4uma6051@4ax.com> 578 15 Xref: inn.example.com news.groups:26
+gnu.utils.bug:1 Re: let's beef up the tsort info page Russ Allbery <rra@stanford.edu> Mon, 04 Feb 2002 14:03:41 -0800 <yl4rkwx5tu.fsf@windlord.stanford.edu> <m2wuxtj3gh.fsf@dan.jacobson.tw> 2409 73 Xref: inn.example.com gnu.utils.bug:1
+csd.bboard:2 Re: comp.doc.techreports submissions from Stanford Russ Allbery <rra@stanford.edu> Mon, 04 Feb 2002 12:31:16 -0800 <yl3d0hxa3v.fsf@windlord.stanford.edu> <yl4rkx3xvo.fsf@windlord.stanford.edu> 794 16 Xref: inn.example.com csd.bboard:2
+news.admin.net-abuse.policy:5 Re: [ALT hierarchy] Anyone can newgroup and anyone can rmgroup ? Russ Allbery <rra@stanford.edu> Mon, 04 Feb 2002 12:09:24 -0800 <ylr8o1xb4b.fsf@windlord.stanford.edu> <5Re78.4895$XS6.585609@news2-win.server.ntlworld.com> <ylelk21l3n.fsf@windlord.stanford.edu> <Gqz2AI.JH2@world.std.com> <ylr8o2z2c5.fsf@windlord.stanford.edu> <Gr0r5x.M8y@world.std.com> 1751 38 Xref: inn.example.com news.admin.net-abuse.policy:5
+csd.bboard:1 comp.doc.techreports submissions from Stanford Russ Allbery <rra@stanford.edu> Mon, 04 Feb 2002 10:28:11 -0800 <yl4rkx3xvo.fsf@windlord.stanford.edu> 672 18 Xref: inn.example.com csd.bboard:1
+comp.lang.perl.misc:1 Re: want to learn perl from free sources Russ Allbery <rra@stanford.edu> Mon, 04 Feb 2002 09:51:12 -0800 <yln0yp87an.fsf@windlord.stanford.edu> <m2r8o1iu11.fsf@dan.jacobson.tw> 641 15 Xref: inn.example.com comp.lang.perl.misc:1
+gnu.misc.discuss:1 Re: want to learn perl from free sources Russ Allbery <rra@stanford.edu> Mon, 04 Feb 2002 09:51:12 -0800 <yln0yp87an.fsf@windlord.stanford.edu> <m2r8o1iu11.fsf@dan.jacobson.tw> 641 15 Xref: inn.example.com gnu.misc.discuss:1
+news.admin.net-abuse.policy:4 Re: [ALT hierarchy] Anyone can newgroup and anyone can rmgroup ? Russ Allbery <rra@stanford.edu> Sun, 03 Feb 2002 13:23:54 -0800 <ylr8o2z2c5.fsf@windlord.stanford.edu> <5Re78.4895$XS6.585609@news2-win.server.ntlworld.com> <ylelk21l3n.fsf@windlord.stanford.edu> <Gqz2AI.JH2@world.std.com> 533 12 Xref: inn.example.com news.admin.net-abuse.policy:4
+news.groups:25 Re: Pre-RFD - Exec Board Newsgroup Creation Russ Allbery <rra@stanford.edu> Sun, 03 Feb 2002 13:21:34 -0800 <ylwuxuz2g1.fsf@windlord.stanford.edu> <20020131191005$5818@babylon.ks.uiuc.edu> <PE2$9TMo7+W8Ew43@merlyn.demon.co.uk> <20020202150653$4dbb@babylon.ks.uiuc.edu> <FRRtIeAzyYX8Ewxl@merlyn.demon.co.uk> 668 15 Xref: inn.example.com news.groups:25
+news.admin.net-abuse.policy:1 Re: [ALT hierarchy] Anyone can newgroup and anyone can rmgroup ? Russ Allbery <rra@stanford.edu> Sun, 03 Feb 2002 10:22:36 -0800 <ylelk21l3n.fsf@windlord.stanford.edu> <5Re78.4895$XS6.585609@news2-win.server.ntlworld.com> 1448 31 Xref: inn.example.com news.admin.net-abuse.policy:1
+news.groups:24 Re: Guidelines for Big Eight Newsgroup Creation Russ Allbery <rra@stanford.edu> Sun, 03 Feb 2002 09:38:27 -0800 <ylzo2q31po.fsf@windlord.stanford.edu> <big-eight-faq-1012550404$22697@windlord.stanford.edu> <vvjq5u8l1dnrdankh4q7l2d043ud8kt11o@4ax.com> 971 22 Xref: inn.example.com news.groups:24
+news.groups:23 Re: [History] Re: A Chronology of Usenet Newsgroups: Start Post Russ Allbery <rra@stanford.edu> Sun, 03 Feb 2002 09:34:04 -0800 <yl665e4ghf.fsf@windlord.stanford.edu> <3c4a31e3$0$95685$892e7fe2@authen.puce.readfreenews.net> <a301ve02s1r@enews2.newsguy.com> <dbc8daca.0201280231.791aeb03@posting.google.com> <8I07QDHHw-B@khms.westfalen.de> <3c5d3981$0$36784$892e7fe2@authen.puce.readfreenews.net> 617 15 Xref: inn.example.com news.groups:23
+news.groups:22 Re: Pre-RFD - Exec Board Newsgroup Creation Russ Allbery <rra@stanford.edu> Sat, 02 Feb 2002 17:42:27 -0800 <ylr8o3wdbw.fsf@windlord.stanford.edu> <20020131191005$5818@babylon.ks.uiuc.edu> <PE2$9TMo7+W8Ew43@merlyn.demon.co.uk> <08_68.11847$gW4.9091001@news1.rdc1.mi.home.com> 1405 29 Xref: inn.example.com news.groups:22
+news.groups:21 Re: Pre-RFD - Exec Board Newsgroup Creation Russ Allbery <rra@stanford.edu> Sat, 02 Feb 2002 17:38:39 -0800 <ylwuxvwdi8.fsf@windlord.stanford.edu> <20020131191005$5818@babylon.ks.uiuc.edu> <PE2$9TMo7+W8Ew43@merlyn.demon.co.uk> <20020202150653$4dbb@babylon.ks.uiuc.edu> <fo1o5uoo90uh1qreh20u6n1ci70jt28mmg@4ax.com> <1f6z4v6.1fbjkdq18w29f4N%kmorgan@aptalaska.net> <lqto5u0p5sblo0ui9hal0nil52e3nlbssm@4ax.com> 316 9 Xref: inn.example.com news.groups:21
+news.groups:19 Re: The beer truck (was Re: Pre-RFD - Exec Board Newsgroup Creation) Russ Allbery <rra@stanford.edu> Sat, 02 Feb 2002 12:51:44 -0800 <yl8zab38v3.fsf@windlord.stanford.edu> <20020131191005$5818@babylon.ks.uiuc.edu> <ylhep0vi11.fsf@windlord.stanford.edu> <a3h7be$dec$1@panix2.panix.com> <yladur7nfw.fsf@windlord.stanford.edu> <a3hd8t$sin$1@panix2.panix.com> 481 16 Xref: inn.example.com news.groups:19
+news.groups:18 Re: The beer truck (was Re: Pre-RFD - Exec Board Newsgroup Creation) Russ Allbery <rra@stanford.edu> Sat, 02 Feb 2002 10:23:15 -0800 <yladur7nfw.fsf@windlord.stanford.edu> <20020131191005$5818@babylon.ks.uiuc.edu> <ylvgdgye47.fsf@windlord.stanford.edu> <tuam5ug6iomvjscick3a2fftfs7d20rrfk@4ax.com> <ylhep0vi11.fsf@windlord.stanford.edu> <a3h7be$dec$1@panix2.panix.com> 375 11 Xref: inn.example.com news.groups:18
+news.groups:17 Re: Pre-RFD - Exec Board Newsgroup Creation Russ Allbery <rra@stanford.edu> Sat, 02 Feb 2002 10:20:43 -0800 <ylg04j7nk4.fsf@windlord.stanford.edu> <20020131191005$5818@babylon.ks.uiuc.edu> <ylvgdgye47.fsf@windlord.stanford.edu> <tuam5ug6iomvjscick3a2fftfs7d20rrfk@4ax.com> <ylhep0vi11.fsf@windlord.stanford.edu> <a3g0i9$6ch$1@samba.rahul.net> 680 16 Xref: inn.example.com news.groups:17
+news.groups:16 Re: Pre-RFD - Exec Board Newsgroup Creation Russ Allbery <rra@stanford.edu> Fri, 01 Feb 2002 19:35:38 -0800 <yl4rl0369h.fsf@windlord.stanford.edu> <20020131191005$5818@babylon.ks.uiuc.edu> <a3cdbm$dt3$1@samba.rahul.net> <a3coga$jiv$1@news.orst.edu> <3C5AE4E5.B0A79442@elepar.com> <a3f1o2$nvo$1@news.orst.edu> <3b7m5ug5lmovmhotuegmkq13jpmcubrh0n@4ax.com> <ylvgdgye47.fsf@windlord.stanford.edu> <tuam5ug6iomvjscick3a2fftfs7d20rrfk@4ax.com> <ylhep0vi11.fsf@windlord.stanford.edu> <1hem5uc8efskp0efdahvnpphg2qtrlt9pc@4ax.com> <yl4rl0u1ae.fsf@windlord.stanford.edu> <Xns91A8BC08432FEpeskyirritantcom@209.155.56.83> 326 12 Xref: inn.example.com news.groups:16
+news.groups:15 Re: Pre-RFD - Exec Board Newsgroup Creation Russ Allbery <rra@stanford.edu> Fri, 01 Feb 2002 19:12:32 -0800 <yladus37bz.fsf@windlord.stanford.edu> <20020131191005$5818@babylon.ks.uiuc.edu> <a3cdbm$dt3$1@samba.rahul.net> <a3coga$jiv$1@news.orst.edu> <3C5AE4E5.B0A79442@elepar.com> <a3f1o2$nvo$1@news.orst.edu> <3b7m5ug5lmovmhotuegmkq13jpmcubrh0n@4ax.com> <ylvgdgye47.fsf@windlord.stanford.edu> <tuam5ug6iomvjscick3a2fftfs7d20rrfk@4ax.com> <ylhep0vi11.fsf@windlord.stanford.edu> <1hem5uc8efskp0efdahvnpphg2qtrlt9pc@4ax.com> <yl4rl0u1ae.fsf@windlord.stanford.edu> <i8jm5u4l8bt7lk93mramtmbouhtg58tpnb@4ax.com> 643 14 Xref: inn.example.com news.groups:15
+news.groups:14 Re: Pre-RFD - Exec Board Newsgroup Creation Russ Allbery <rra@stanford.edu> Fri, 01 Feb 2002 19:08:02 -0800 <ylg04k37jh.fsf@windlord.stanford.edu> <20020131191005$5818@babylon.ks.uiuc.edu> <a3cdbm$dt3$1@samba.rahul.net> <a3coga$jiv$1@news.orst.edu> <3C5AE4E5.B0A79442@elepar.com> <a3f1o2$nvo$1@news.orst.edu> <3b7m5ug5lmovmhotuegmkq13jpmcubrh0n@4ax.com> <ylvgdgye47.fsf@windlord.stanford.edu> <tuam5ug6iomvjscick3a2fftfs7d20rrfk@4ax.com> <ylhep0vi11.fsf@windlord.stanford.edu> <a3fgk302sdn@enews1.newsguy.com> 808 18 Xref: inn.example.com news.groups:14
+news.groups:13 Re: Pre-RFD - Exec Board Newsgroup Creation Russ Allbery <rra@stanford.edu> Fri, 01 Feb 2002 18:10:18 -0800 <ylu1t03a7p.fsf@windlord.stanford.edu> <20020131191005$5818@babylon.ks.uiuc.edu> <a3cdbm$dt3$1@samba.rahul.net> <a3coga$jiv$1@news.orst.edu> <3C5AE4E5.B0A79442@elepar.com> <a3f1o2$nvo$1@news.orst.edu> <3b7m5ug5lmovmhotuegmkq13jpmcubrh0n@4ax.com> <ylvgdgye47.fsf@windlord.stanford.edu> <tuam5ug6iomvjscick3a2fftfs7d20rrfk@4ax.com> <ylhep0vi11.fsf@windlord.stanford.edu> <1hem5uc8efskp0efdahvnpphg2qtrlt9pc@4ax.com> <yl4rl0u1ae.fsf@windlord.stanford.edu> <v1hm5u4mrhllm98ng1jhp5gco7sflbm4jq@4ax.com> 902 27 Xref: inn.example.com news.groups:13
+news.groups:12 Re: Pre-RFD - Exec Board Newsgroup Creation Russ Allbery <rra@stanford.edu> Fri, 01 Feb 2002 17:20:57 -0800 <yl4rl0u1ae.fsf@windlord.stanford.edu> <20020131191005$5818@babylon.ks.uiuc.edu> <a3cdbm$dt3$1@samba.rahul.net> <a3coga$jiv$1@news.orst.edu> <3C5AE4E5.B0A79442@elepar.com> <a3f1o2$nvo$1@news.orst.edu> <3b7m5ug5lmovmhotuegmkq13jpmcubrh0n@4ax.com> <ylvgdgye47.fsf@windlord.stanford.edu> <tuam5ug6iomvjscick3a2fftfs7d20rrfk@4ax.com> <ylhep0vi11.fsf@windlord.stanford.edu> <1hem5uc8efskp0efdahvnpphg2qtrlt9pc@4ax.com> 1651 36 Xref: inn.example.com news.groups:12
+news.groups:8 Re: Pre-RFD - Exec Board Newsgroup Creation Russ Allbery <rra@stanford.edu> Fri, 01 Feb 2002 17:10:47 -0800 <yladusu1rc.fsf@windlord.stanford.edu> <20020131191005$5818@babylon.ks.uiuc.edu> <a3cdbm$dt3$1@samba.rahul.net> <a3coga$jiv$1@news.orst.edu> <3C5AE4E5.B0A79442@elepar.com> <a3f1o2$nvo$1@news.orst.edu> <3b7m5ug5lmovmhotuegmkq13jpmcubrh0n@4ax.com> <ylvgdgye47.fsf@windlord.stanford.edu> <tuam5ug6iomvjscick3a2fftfs7d20rrfk@4ax.com> <ylhep0vi11.fsf@windlord.stanford.edu> <3ndm5u8fgomord7vmh164ce7pcteh496sp@4ax.com> 2363 47 Xref: inn.example.com news.groups:8
+news.groups:7 Re: Pre-RFD - Exec Board Newsgroup Creation Russ Allbery <rra@stanford.edu> Fri, 01 Feb 2002 16:34:02 -0800 <ylhep0vi11.fsf@windlord.stanford.edu> <20020131191005$5818@babylon.ks.uiuc.edu> <a3cdbm$dt3$1@samba.rahul.net> <a3coga$jiv$1@news.orst.edu> <3C5AE4E5.B0A79442@elepar.com> <a3f1o2$nvo$1@news.orst.edu> <3b7m5ug5lmovmhotuegmkq13jpmcubrh0n@4ax.com> <ylvgdgye47.fsf@windlord.stanford.edu> <tuam5ug6iomvjscick3a2fftfs7d20rrfk@4ax.com> 2834 55 Xref: inn.example.com news.groups:7
+news.groups:2 Re: Pre-RFD - Exec Board Newsgroup Creation Russ Allbery <rra@stanford.edu> Fri, 01 Feb 2002 15:32:03 -0800 <ylofj8ye18.fsf@windlord.stanford.edu> <20020131191005$5818@babylon.ks.uiuc.edu> <a3eep7$q6r$1@samba.rahul.net> <a3epeq$rt$1@gw.retro.com> <a3f201$n8$1@samba.rahul.net> <a3f7v9$3kg$1@gw.retro.com> 448 11 Xref: inn.example.com news.groups:2
+news.groups:1 Re: Pre-RFD - Exec Board Newsgroup Creation Russ Allbery <rra@stanford.edu> Fri, 01 Feb 2002 15:30:16 -0800 <ylvgdgye47.fsf@windlord.stanford.edu> <20020131191005$5818@babylon.ks.uiuc.edu> <a3cdbm$dt3$1@samba.rahul.net> <a3coga$jiv$1@news.orst.edu> <3C5AE4E5.B0A79442@elepar.com> <a3f1o2$nvo$1@news.orst.edu> <3b7m5ug5lmovmhotuegmkq13jpmcubrh0n@4ax.com> 3124 58 Xref: inn.example.com news.groups:1
--- /dev/null
+#!/usr/bin/perl
+
+## $Id: munge-data 5144 2002-02-24 06:24:55Z rra $
+##
+## Munge .overview data into something suitable for test data.
+##
+## This script isn't used regularly and is here only for the use of INN
+## developers and other people needing to generate more overview test data.
+## It expects overview data but possibly with extra fields at the end, snips
+## off To: data (to avoid putting people's e-mail addresses into INN test
+## data), and if Newsgroups: data is present, rewrites the Xref header to use
+## it instead of keeping the Xref data (this is so that I can use .overview
+## files from Gnus as test data). It generates overview data but with the
+## newsgroup name and a colon prepended to the article number so that it can
+## be split apart into overview data for multiple groups.
+##
+## Please don't include overview information for people's articles into INN's
+## test suite without their permission.
+
+my %number;
+while (<>) {
+ s/\s+$//;
+ my @data = split /\t/;
+ @data = grep { !/^To:/ } @data;
+ my $group = pop @data;
+ my $xref = pop @data;
+ if ($group =~ s/^Newsgroups: //) {
+ my @groups = split (/\s*,\s*/, $group);
+ for (@groups) {
+ $number{$_} = 1 unless $number{$_};
+ $data[0] = $_ . ':' . $number{$_}++;
+ $number{$_} += int (rand 5) if rand (10) > 8;
+ print join ("\t", @data, "Xref: inn.example.com $data[0]"), "\n";
+ }
+ } else {
+ $xref =~ s/Xref:\s*\S+\s+//;
+ my @xref = split (' ', $xref);
+ for (@xref) {
+ $data[0] = $_;
+ print join ("\t", @data, "Xref: inn.example.com $xref"), "\n";
+ }
+ }
+}
--- /dev/null
+/* $Id: tradindexed-t.c 6388 2003-07-12 19:07:56Z rra $ */
+/* tradindexed test suite. */
+
+#include "config.h"
+#include "clibrary.h"
+#include <errno.h>
+#include <sys/stat.h>
+
+#include "inn/innconf.h"
+#include "inn/hashtab.h"
+#include "inn/messages.h"
+#include "inn/vector.h"
+#include "libinn.h"
+#include "libtest.h"
+#include "ov.h"
+#include "storage.h"
+
+#include "../storage/tradindexed/tradindexed.h"
+
+/* Used as the artificial token for all articles inserted into overview. */
+static const TOKEN faketoken = { 1, 1, "" };
+
+struct group {
+ char *group;
+ unsigned long count;
+ unsigned long low;
+ unsigned long high;
+};
+
+static const void *
+group_key(const void *entry)
+{
+ const struct group *group = (const struct group *) entry;
+ return group->group;
+}
+
+static bool
+group_eq(const void *key, const void *entry)
+{
+ const char *first = key;
+ const char *second;
+
+ second = ((const struct group *) entry)->group;
+ return !strcmp(first, second);
+}
+
+static void
+group_del(void *entry)
+{
+ struct group *group = (struct group *) entry;
+
+ free(group->group);
+ free(group);
+}
+
+/* Build a stripped-down innconf struct that contains only those settings that
+ the tradindexed overview method cares about. */
+static void
+fake_innconf(void)
+{
+ innconf = xmalloc(sizeof(*innconf));
+ innconf->pathoverview = xstrdup("tdx-tmp");
+ innconf->overcachesize = 20;
+ innconf->groupbaseexpiry = true;
+ innconf->tradindexedmmap = true;
+}
+
+/* Initialize the overview database. */
+static bool
+overview_init(void)
+{
+ fake_innconf();
+ if (access("data/basic", F_OK) < 0)
+ if (access("overview/data/basic", F_OK) == 0)
+ if (chdir("overview") != 0)
+ sysdie("Cannot cd to overview");
+ if (mkdir("tdx-tmp", 0755) != 0 && errno != EEXIST)
+ sysdie("Cannot mkdir tdx-tmp");
+ return tradindexed_open(OV_READ | OV_WRITE);
+}
+
+/* Check to be sure that the line wasn't too long, and then parse the
+ beginning of the line from one of our data files, setting the article
+ number (via the passed pointer) and returning a pointer to the beginning of
+ the real overview data. This function nul-terminates the group name and
+ leaves it at the beginning of the buffer. (Ugly interface, but it's just a
+ test suite.) */
+static char *
+overview_data_parse(char *data, unsigned long *artnum)
+{
+ char *start;
+
+ if (data[strlen(data) - 1] != '\n')
+ die("Line too long in input data");
+
+ start = strchr(data, ':');
+ if (start == NULL)
+ die("No colon found in input data");
+ *start = '\0';
+ start++;
+ *artnum = strtoul(start, NULL, 10);
+ if (artnum == 0)
+ die("Cannot parse article number in input data");
+ return start;
+}
+
+/* Load an empty overview database from a file, in the process populating a
+ hash table with each group, the high water mark, and the count of messages
+ that should be in the group. Returns the hash table on success and dies on
+ failure. Takes the name of the data file to load. */
+static struct hash *
+overview_load(const char *data)
+{
+ struct hash *groups;
+ struct group *group;
+ FILE *overview;
+ char buffer[4096];
+ char flag[] = "y";
+ char *start;
+ unsigned long artnum;
+
+ /* Run through the overview data. Each time we see a group, we update our
+ stored information about that group, which we'll use for verification
+ later. We store that in a local hash table. */
+ groups = hash_create(32, hash_string, group_key, group_eq, group_del);
+ if (groups == NULL)
+ die("Cannot create a hash table");
+ overview = fopen(data, "r");
+ if (overview == NULL)
+ sysdie("Cannot open %s for reading", data);
+ while (fgets(buffer, sizeof(buffer), overview) != NULL) {
+ start = overview_data_parse(buffer, &artnum);
+
+ /* See if we've already seen this group. If not, create it in the
+ overview and the hash table; otherwise, update our local hash table
+ entry. */
+ group = hash_lookup(groups, buffer);
+ if (group == NULL) {
+ group = xmalloc(sizeof(struct group));
+ group->group = xstrdup(buffer);
+ group->count = 1;
+ group->low = artnum;
+ group->high = artnum;
+ if (!hash_insert(groups, group->group, group))
+ die("Cannot insert %s into hash table", group->group);
+ if (!tradindexed_groupadd(group->group, 0, 0, flag))
+ die("Cannot insert group %s into overview", group->group);
+ } else {
+ group->count++;
+ group->low = (artnum < group->low) ? artnum : group->low;
+ group->high = (artnum > group->high) ? artnum : group->high;
+ }
+
+ /* Do the actual insert of the data. Note that we set the arrival
+ time and expires time in a deterministic fashion so that we can
+ check later if that data is being stored properly. */
+ if (!tradindexed_add(group->group, artnum, faketoken, start,
+ strlen(start), artnum * 10,
+ (artnum % 5 == 0) ? artnum * 100 : artnum))
+ die("Cannot insert %s:%lu into overview", group->group, artnum);
+ }
+ fclose(overview);
+ return groups;
+}
+
+/* Verify that all of the group data looks correct; this is low mark, high
+ mark, and article count. Returns true if all the data is right, false
+ otherwise. This function is meant to be called as a hash traversal
+ function, which means that it will be called for each element in our local
+ hash table of groups with the group struct as the first argument and a
+ pointer to a status as the second argument. */
+static void
+overview_verify_groups(void *data, void *cookie)
+{
+ struct group *group = (struct group *) data;
+ bool *status = (bool *) cookie;
+ int low, high, count, flag;
+
+ if (!tradindexed_groupstats(group->group, &low, &high, &count, &flag)) {
+ warn("Unable to get data for %s", group->group);
+ *status = false;
+ return;
+ }
+ if ((unsigned long) low != group->low) {
+ warn("Low article wrong for %s: %lu != %lu", group->group,
+ (unsigned long) low, group->low);
+ *status = false;
+ }
+ if ((unsigned long) high != group->high) {
+ warn("High article wrong for %s: %lu != %lu", group->group,
+ (unsigned long) high, group->high);
+ *status = false;
+ }
+ if ((unsigned long) count != group->count) {
+ warn("Article count wrong for %s: %lu != %lu", group->group,
+ (unsigned long) count, group->count);
+ *status = false;
+ }
+ if (flag != 'y') {
+ warn("Flag wrong for %s: %c != y", group->group, (char) flag);
+ *status = false;
+ }
+}
+
+/* Verify the components of the overview data for a particular entry. */
+static bool
+check_data(const char *group, unsigned long artnum, const char *expected,
+ const char *seen, int length, TOKEN token)
+{
+ bool status = true;
+
+ if (strlen(expected) != (size_t) length) {
+ warn("Length wrong for %s:%lu: %d != %lu", group, artnum, length,
+ (unsigned long) strlen(expected));
+ status = false;
+ }
+ if (memcmp(&token, &faketoken, sizeof(token)) != 0) {
+ warn("Token wrong for %s:%lu", group, artnum);
+ status = false;
+ }
+ if (memcmp(expected, seen, length) != 0) {
+ warn("Data mismatch for %s:%lu", group, artnum);
+ warn("====\n%s\n====\n%s\n====", expected, seen);
+ status = false;
+ }
+ return status;
+}
+
+/* Read through the data again, looking up each article as we go and verifying
+ that the data stored in overview is the same as the data we put there. Do
+ this two ways each time, once via getartinfo and once via opensearch.
+ Return true if everything checks out, false otherwise. Takes the path to
+ the data file. */
+static bool
+overview_verify_data(const char *data)
+{
+ FILE *overdata;
+ char buffer[4096];
+ char *start;
+ unsigned long artnum, overnum;
+ char *overview;
+ int length;
+ TOKEN token;
+ bool status = true;
+ void *search;
+ time_t arrived;
+
+ overdata = fopen(data, "r");
+ if (overdata == NULL)
+ sysdie("Cannot open %s for reading", data);
+ while (fgets(buffer, sizeof(buffer), overdata) != NULL) {
+ start = overview_data_parse(buffer, &artnum);
+
+ /* Now check that the overview data is correct for that group. */
+ if (!tradindexed_getartinfo(buffer, artnum, &token)) {
+ warn("No overview data found for %s:%lu", buffer, artnum);
+ status = false;
+ continue;
+ }
+ if (memcmp(&token, &faketoken, sizeof(token)) != 0) {
+ warn("Token wrong for %s:%lu", buffer, artnum);
+ status = false;
+ }
+
+ /* Do the same thing, except use search. */
+ search = tradindexed_opensearch(buffer, artnum, artnum);
+ if (search == NULL) {
+ warn("Unable to open search for %s:%lu", buffer, artnum);
+ status = false;
+ continue;
+ }
+ if (!tradindexed_search(search, &overnum, &overview, &length, &token,
+ &arrived)) {
+ warn("No overview data found for %s:%lu", buffer, artnum);
+ status = false;
+ continue;
+ }
+ if (overnum != artnum) {
+ warn("Incorrect article number in search for %s:%lu: %lu != %lu",
+ buffer, artnum, overnum, artnum);
+ status = false;
+ }
+ if (!check_data(buffer, artnum, start, overview, length, token))
+ status = false;
+ if ((unsigned long) arrived != artnum * 10) {
+ warn("Arrival time wrong for %s:%lu: %lu != %lu", buffer, artnum,
+ (unsigned long) arrived, artnum * 10);
+ status = false;
+ }
+ if (tradindexed_search(search, &overnum, &overview, &length, &token,
+ &arrived)) {
+ warn("Unexpected article found for %s:%lu", buffer, artnum);
+ status = false;
+ }
+ tradindexed_closesearch(search);
+ }
+ fclose(overdata);
+ return status;
+}
+
+/* Try an overview search and verify that all of the data is returned in the
+ right order. The first group mentioned in the provided data file will be
+ the group the search is done in, and the search will cover all articles
+ from the second article to the second-to-the-last article in the group.
+ Returns true if everything checks out, false otherwise. */
+static bool
+overview_verify_search(const char *data)
+{
+ unsigned long artnum, overnum, i;
+ unsigned long start = 0;
+ unsigned long end = 0;
+ unsigned long last = 0;
+ struct vector *expected;
+ char *line, *group;
+ FILE *overview;
+ char buffer[4096];
+ int length;
+ TOKEN token;
+ void *search;
+ time_t arrived;
+ bool status = true;
+
+ overview = fopen(data, "r");
+ if (overview == NULL)
+ sysdie("Cannot open %s for reading", data);
+ expected = vector_new();
+ if (fgets(buffer, sizeof(buffer), overview) == NULL)
+ die("Unexpected end of file in %s", data);
+ overview_data_parse(buffer, &artnum);
+ group = xstrdup(buffer);
+ while (fgets(buffer, sizeof(buffer), overview) != NULL) {
+ line = overview_data_parse(buffer, &artnum);
+ if (strcmp(group, buffer) != 0)
+ continue;
+ vector_add(expected, line);
+ if (start == 0)
+ start = artnum;
+ end = last;
+ last = artnum;
+ }
+ search = tradindexed_opensearch(group, start, end);
+ if (search == NULL) {
+ warn("Unable to open search for %s:%lu", buffer, start);
+ free(group);
+ vector_free(expected);
+ return false;
+ }
+ i = 0;
+ while (tradindexed_search(search, &overnum, &line, &length, &token,
+ &arrived)) {
+ if (!check_data(group, overnum, expected->strings[i], line, length,
+ token))
+ status = false;
+ if ((unsigned long) arrived != overnum * 10) {
+ warn("Arrival time wrong for %s:%lu: %lu != %lu", group, overnum,
+ (unsigned long) arrived, overnum * 10);
+ status = false;
+ }
+ i++;
+ }
+ tradindexed_closesearch(search);
+ if (overnum != end) {
+ warn("End of search in %s wrong: %lu != %lu", group, overnum, end);
+ status = false;
+ }
+ if (i != expected->count - 1) {
+ warn("Didn't see all expected entries in %s", group);
+ status = false;
+ }
+ free(group);
+ vector_free(expected);
+ return status;
+}
+
+/* Try an overview search and verify that all of the data is returned in the
+ right order. The search will cover everything from article 1 to the
+ highest numbered article plus one. There were some problems with a search
+ low-water mark lower than the base of the group. Returns true if
+ everything checks out, false otherwise. */
+static bool
+overview_verify_full_search(const char *data)
+{
+ unsigned long artnum, overnum, i;
+ unsigned long end = 0;
+ struct vector *expected;
+ char *line;
+ char *group = NULL;
+ FILE *overview;
+ char buffer[4096];
+ int length;
+ TOKEN token;
+ void *search;
+ time_t arrived;
+ bool status = true;
+
+ overview = fopen(data, "r");
+ if (overview == NULL)
+ sysdie("Cannot open %s for reading", data);
+ expected = vector_new();
+ while (fgets(buffer, sizeof(buffer), overview) != NULL) {
+ line = overview_data_parse(buffer, &artnum);
+ if (group == NULL)
+ group = xstrdup(buffer);
+ vector_add(expected, line);
+ end = artnum;
+ }
+ search = tradindexed_opensearch(group, 1, end + 1);
+ if (search == NULL) {
+ warn("Unable to open full search for %s", group);
+ free(group);
+ vector_free(expected);
+ return false;
+ }
+ i = 0;
+ while (tradindexed_search(search, &overnum, &line, &length, &token,
+ &arrived)) {
+ if (!check_data(group, overnum, expected->strings[i], line, length,
+ token))
+ status = false;
+ if ((unsigned long) arrived != overnum * 10) {
+ warn("Arrival time wrong for %s:%lu: %lu != %lu", group, overnum,
+ (unsigned long) arrived, overnum * 10);
+ status = false;
+ }
+ i++;
+ }
+ tradindexed_closesearch(search);
+ if (overnum != end) {
+ warn("End of search in %s wrong: %lu != %lu", group, overnum, end);
+ status = false;
+ }
+ if (i != expected->count) {
+ warn("Didn't see all expected entries in %s", group);
+ status = false;
+ }
+ free(group);
+ vector_free(expected);
+ return status;
+}
+
+int
+main(void)
+{
+ struct hash *groups;
+ bool status;
+
+ puts("21");
+
+ if (!overview_init())
+ die("Opening the overview database failed, cannot continue");
+ ok(1, true);
+
+ groups = overview_load("data/basic");
+ ok(2, true);
+ status = true;
+ hash_traverse(groups, overview_verify_groups, &status);
+ ok(3, status);
+ ok(4, overview_verify_data("data/basic"));
+ ok(5, overview_verify_search("data/basic"));
+ hash_free(groups);
+ tradindexed_close();
+ system("/bin/rm -r tdx-tmp");
+ ok(6, true);
+
+ if (!overview_init())
+ die("Opening the overview database failed, cannot continue");
+ ok(7, true);
+
+ groups = overview_load("data/reversed");
+ ok(8, true);
+ status = true;
+ hash_traverse(groups, overview_verify_groups, &status);
+ ok(9, status);
+ ok(10, overview_verify_data("data/basic"));
+ ok(11, overview_verify_search("data/basic"));
+ hash_free(groups);
+ tradindexed_close();
+ system("/bin/rm -r tdx-tmp");
+ ok(12, true);
+
+ if (!overview_init())
+ die("Opening the overview database failed, cannot continue");
+ ok(13, true);
+
+ groups = overview_load("data/high-numbered");
+ ok(14, true);
+ ok(15, overview_verify_data("data/high-numbered"));
+ ok(16, overview_verify_full_search("data/high-numbered"));
+ hash_free(groups);
+ tradindexed_close();
+ system("/bin/rm -r tdx-tmp");
+ ok(17, true);
+
+ if (!overview_init())
+ die("Opening the overview database failed, cannot continue");
+ ok(18, true);
+
+ groups = overview_load("data/bogus");
+ ok(19, true);
+ ok(20, overview_verify_data("data/bogus"));
+ hash_free(groups);
+ tradindexed_close();
+ system("/bin/rm -r tdx-tmp");
+ ok(21, true);
+
+ return 0;
+}
--- /dev/null
+/* $Id: runtests.c 7578 2006-09-11 23:03:12Z eagle $
+
+ Run a set of tests, reporting results.
+
+ Copyright 2000, 2001 Russ Allbery <rra@stanford.edu>
+
+ Please note that this file is maintained separately from INN by the above
+ author (which is why the coding style is slightly different). Any fixes
+ added to the INN tree should also be reported to the above author if
+ necessary.
+
+ Permission is hereby granted, free of charge, to any person obtaining a
+ copy of this software and associated documentation files (the
+ "Software"), to deal in the Software without restriction, including
+ without limitation the rights to use, copy, modify, merge, publish,
+ distribute, sublicense, and/or sell copies of the Software, and to
+ permit persons to whom the Software is furnished to do so, subject to
+ the following conditions:
+
+ The above copyright notice and this permission notice shall be included
+ in all copies or substantial portions of the Software.
+
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
+ OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
+ IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
+ CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
+ TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
+ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+
+ Usage:
+
+ runtests <test-list>
+
+ Expects a list of executables located in the given file, one line per
+ executable. For each one, runs it as part of a test suite, reporting
+ results. Test output should start with a line containing the number of
+ tests (numbered from 1 to this number), and then each line should be in
+ the following format:
+
+ ok <number>
+ not ok <number>
+ ok <number> # skip
+
+ where <number> is the number of the test. ok indicates success, not ok
+ indicates failure, and "# skip" indicates the test was skipped for some
+ reason (maybe because it doesn't apply to this platform).
+
+ This file is completely stand-alone by intention. As stated more
+ formally in the license above, you are welcome to include it in your
+ packages as a test suite driver. It requires ANSI C (__FILE__, __LINE__,
+ void, const, stdarg.h, string.h) and POSIX (fcntl.h, unistd.h, pid_t) and
+ won't compile out of the box on SunOS without adjustments to include
+ strings.h instead. This is intentionally not fixed using autoconf so
+ that this file will not have a dependency on autoconf (although you're
+ welcome to fix it for your project if you want). Since it doesn't matter
+ as much that the test suite for the software package be utterly portable
+ to older systems, this file should be portable enough for most purposes.
+
+ Any bug reports, bug fixes, and improvements are very much welcome and
+ should be sent to the e-mail address above. */
+
+#include "config.h"
+#include "clibrary.h"
+#include "portable/wait.h"
+#include "portable/time.h"
+#include <ctype.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <stdarg.h>
+#include <sys/stat.h>
+
+/* sys/time.h must be included before sys/resource.h on some platforms. */
+#include <sys/resource.h>
+
+/* Test status codes. */
+enum test_status {
+ TEST_FAIL,
+ TEST_PASS,
+ TEST_SKIP,
+ TEST_INVALID
+};
+
+/* Error exit statuses for test processes. */
+#define CHILDERR_DUP 100 /* Couldn't redirect stderr or stdout. */
+#define CHILDERR_EXEC 101 /* Couldn't exec child process. */
+#define CHILDERR_STDERR 102 /* Couldn't open stderr file. */
+
+/* Structure to hold data for a set of tests. */
+struct testset {
+ const char *file; /* The file name of the test. */
+ int count; /* Expected count of tests. */
+ int current; /* The last seen test number. */
+ int passed; /* Count of passing tests. */
+ int failed; /* Count of failing lists. */
+ int skipped; /* Count of skipped tests (passed). */
+ enum test_status *results; /* Table of results by test number. */
+ int aborted; /* Whether the set as aborted. */
+ int reported; /* Whether the results were reported. */
+ int status; /* The exit status of the test. */
+};
+
+/* Structure to hold a linked list of test sets. */
+struct testlist {
+ struct testset *ts;
+ struct testlist *next;
+};
+
+/* Header used for test output. %s is replaced by the file name of the list
+ of tests. */
+static const char banner[] = "\n\
+Running all tests listed in %s. If any tests fail, run the failing\n\
+test program by hand to see more details. The test program will have the\n\
+same name as the test set but with \".t\" appended.\n\n";
+
+/* Header for reports of failed tests. */
+static const char header[] = "\n\
+Failed Set Fail/Total (%) Skip Stat Failing Tests\n\
+-------------------------- -------------- ---- ---- ------------------------";
+
+/* Include the file name and line number in malloc failures. */
+#define xmalloc(size) x_malloc((size), __FILE__, __LINE__)
+#define xstrdup(p) x_strdup((p), __FILE__, __LINE__)
+
+/* Internal prototypes. */
+static void sysdie(const char *format, ...);
+static void *x_malloc(size_t, const char *file, int line);
+static char *x_strdup(const char *, const char *file, int line);
+static int test_analyze(struct testset *);
+static int test_batch(const char *testlist);
+static void test_checkline(const char *line, struct testset *);
+static void test_fail_summary(const struct testlist *);
+static int test_init(const char *line, struct testset *);
+static int test_print_range(int first, int last, int chars, int limit);
+static void test_summarize(struct testset *, int status);
+static pid_t test_start(const char *path, int *fd);
+static double tv_diff(const struct timeval *, const struct timeval *);
+static double tv_seconds(const struct timeval *);
+static double tv_sum(const struct timeval *, const struct timeval *);
+
+
+/* Report a fatal error, including the results of strerror, and exit. */
+static void
+sysdie(const char *format, ...)
+{
+ int oerrno;
+ va_list args;
+
+ oerrno = errno;
+ fflush(stdout);
+ fprintf(stderr, "runtests: ");
+ va_start(args, format);
+ vfprintf(stderr, format, args);
+ va_end(args);
+ fprintf(stderr, ": %s\n", strerror(oerrno));
+ exit(1);
+}
+
+
+/* Allocate memory, reporting a fatal error and exiting on failure. */
+static void *
+x_malloc(size_t size, const char *file, int line)
+{
+ void *p;
+
+ p = malloc(size);
+ if (!p)
+ sysdie("failed to malloc %lu bytes at %s line %d",
+ (unsigned long) size, file, line);
+ return p;
+}
+
+
+/* Copy a string, reporting a fatal error and exiting on failure. */
+static char *
+x_strdup(const char *s, const char *file, int line)
+{
+ char *p;
+ size_t len;
+
+ len = strlen(s) + 1;
+ p = malloc(len);
+ if (!p)
+ sysdie("failed to strdup %lu bytes at %s line %d",
+ (unsigned long) len, file, line);
+ memcpy(p, s, len);
+ return p;
+}
+
+
+/* Given a struct timeval, return the number of seconds it represents as a
+ double. Use difftime() to convert a time_t to a double. */
+static double
+tv_seconds(const struct timeval *tv)
+{
+ return difftime(tv->tv_sec, 0) + tv->tv_usec * 1e-6;
+}
+
+/* Given two struct timevals, return the difference in seconds. */
+static double
+tv_diff(const struct timeval *tv1, const struct timeval *tv0)
+{
+ return tv_seconds(tv1) - tv_seconds(tv0);
+}
+
+/* Given two struct timevals, return the sum in seconds as a double. */
+static double
+tv_sum(const struct timeval *tv1, const struct timeval *tv2)
+{
+ return tv_seconds(tv1) + tv_seconds(tv2);
+}
+
+
+/* Read the first line of test output, which should contain the range of
+ test numbers, and initialize the testset structure. Assume it was zeroed
+ before being passed in. Return true if initialization succeeds, false
+ otherwise. */
+static int
+test_init(const char *line, struct testset *ts)
+{
+ int i;
+
+ /* Prefer a simple number of tests, but if the count is given as a range
+ such as 1..10, accept that too for compatibility with Perl's
+ Test::Harness. */
+ while (isspace((unsigned char)(*line))) line++;
+ if (!strncmp(line, "1..", 3)) line += 3;
+
+ /* Get the count, check it for validity, and initialize the struct. */
+ i = atoi(line);
+ if (i <= 0) {
+ puts("invalid test count");
+ ts->aborted = 1;
+ ts->reported = 1;
+ return 0;
+ }
+ ts->count = i;
+ ts->results = xmalloc(ts->count * sizeof(enum test_status));
+ for (i = 0; i < ts->count; i++) ts->results[i] = TEST_INVALID;
+ return 1;
+}
+
+
+/* Start a program, connecting its stdout to a pipe on our end and its
+ stderr to /dev/null, and storing the file descriptor to read from in the
+ two argument. Returns the PID of the new process. Errors are fatal. */
+static pid_t
+test_start(const char *path, int *fd)
+{
+ int fds[2], errfd;
+ pid_t child;
+
+ if (pipe(fds) == -1) sysdie("can't create pipe");
+ child = fork();
+ if (child == (pid_t) -1) {
+ sysdie("can't fork");
+ } else if (child == 0) {
+ /* In child. Set up our stdout and stderr. */
+ errfd = open("/dev/null", O_WRONLY);
+ if (errfd < 0) _exit(CHILDERR_STDERR);
+ if (dup2(errfd, 2) == -1) _exit(CHILDERR_DUP);
+ close(fds[0]);
+ if (dup2(fds[1], 1) == -1) _exit(CHILDERR_DUP);
+
+ /* Now, exec our process. */
+ if (execl(path, path, (char *) 0) == -1) _exit(CHILDERR_EXEC);
+ } else {
+ /* In parent. Close the extra file descriptor. */
+ close(fds[1]);
+ }
+ *fd = fds[0];
+ return child;
+}
+
+
+/* Given a single line of output from a test, parse it and return the
+ success status of that test. Anything printed to stdout not matching the
+ form /^(not )?ok \d+/ is ignored. Sets ts->current to the test number
+ that just reported status. */
+static void
+test_checkline(const char *line, struct testset *ts)
+{
+ enum test_status status = TEST_PASS;
+ int current;
+
+ /* If the given line isn't newline-terminated, it was too big for an
+ fgets(), which means ignore it. */
+ if (line[strlen(line) - 1] != '\n') return;
+
+ /* Parse the line, ignoring something we can't parse. */
+ if (!strncmp(line, "not ", 4)) {
+ status = TEST_FAIL;
+ line += 4;
+ }
+ if (strncmp(line, "ok ", 3)) return;
+ line += 3;
+ current = atoi(line);
+ if (current == 0) return;
+ if (current < 0 || current > ts->count) {
+ printf("invalid test number %d\n", current);
+ ts->aborted = 1;
+ ts->reported = 1;
+ return;
+ }
+ while (isspace((unsigned char)(*line))) line++;
+ while (isdigit((unsigned char)(*line))) line++;
+ while (isspace((unsigned char)(*line))) line++;
+ if (*line == '#') {
+ line++;
+ while (isspace((unsigned char)(*line))) line++;
+ if (!strncmp(line, "skip", 4)) status = TEST_SKIP;
+ }
+
+ /* Make sure that the test number is in range and not a duplicate. */
+ if (ts->results[current - 1] != TEST_INVALID) {
+ printf("duplicate test number %d\n", current);
+ ts->aborted = 1;
+ ts->reported = 1;
+ return;
+ }
+
+ /* Good results. Increment our various counters. */
+ switch (status) {
+ case TEST_PASS: ts->passed++; break;
+ case TEST_FAIL: ts->failed++; break;
+ case TEST_SKIP: ts->skipped++; break;
+ default: break;
+ }
+ ts->current = current;
+ ts->results[current - 1] = status;
+}
+
+
+/* Print out a range of test numbers, returning the number of characters it
+ took up. Add a comma and a space before the range if chars indicates
+ that something has already been printed on the line, and print
+ ... instead if chars plus the space needed would go over the limit (use a
+ limit of 0 to disable this. */
+static int
+test_print_range(int first, int last, int chars, int limit)
+{
+ int needed = 0;
+ int out = 0;
+ int n;
+
+ if (chars > 0) {
+ needed += 2;
+ if (!limit || chars <= limit) out += printf(", ");
+ }
+ for (n = first; n > 0; n /= 10)
+ needed++;
+ if (last > first) {
+ for (n = last; n > 0; n /= 10)
+ needed++;
+ needed++;
+ }
+ if (limit && chars + needed > limit) {
+ if (chars <= limit) out += printf("...");
+ } else {
+ if (last > first) out += printf("%d-", first);
+ out += printf("%d", last);
+ }
+ return out;
+}
+
+
+/* Summarize a single test set. The second argument is 0 if the set exited
+ cleanly, a positive integer representing the exit status if it exited
+ with a non-zero status, and a negative integer representing the signal
+ that terminated it if it was killed by a signal. */
+static void
+test_summarize(struct testset *ts, int status)
+{
+ int i;
+ int missing = 0;
+ int failed = 0;
+ int first = 0;
+ int last = 0;
+
+ if (ts->aborted) {
+ fputs("aborted", stdout);
+ if (ts->count > 0)
+ printf(", passed %d/%d", ts->passed, ts->count - ts->skipped);
+ } else {
+ for (i = 0; i < ts->count; i++) {
+ if (ts->results[i] == TEST_INVALID) {
+ if (missing == 0) fputs("MISSED ", stdout);
+ if (first && i == last) {
+ last = i + 1;
+ } else {
+ if (first) {
+ test_print_range(first, last, missing - 1, 0);
+ }
+ missing++;
+ first = i + 1;
+ last = i + 1;
+ }
+ }
+ }
+ if (first) test_print_range(first, last, missing - 1, 0);
+ first = 0;
+ last = 0;
+ for (i = 0; i < ts->count; i++) {
+ if (ts->results[i] == TEST_FAIL) {
+ if (missing && !failed) fputs("; ", stdout);
+ if (failed == 0) fputs("FAILED ", stdout);
+ if (first && i == last) {
+ last = i + 1;
+ } else {
+ if (first) {
+ test_print_range(first, last, failed - 1, 0);
+ }
+ failed++;
+ first = i + 1;
+ last = i + 1;
+ }
+ }
+ }
+ if (first) test_print_range(first, last, failed - 1, 0);
+ if (!missing && !failed) {
+ fputs(!status ? "ok" : "dubious", stdout);
+ if (ts->skipped > 0) printf(" (skipped %d tests)", ts->skipped);
+ }
+ }
+ if (status > 0) {
+ printf(" (exit status %d)", status);
+ } else if (status < 0) {
+ printf(" (killed by signal %d%s)", -status,
+ WCOREDUMP(ts->status) ? ", core dumped" : "");
+ }
+ putchar('\n');
+}
+
+
+/* Given a test set, analyze the results, classify the exit status, handle a
+ few special error messages, and then pass it along to test_summarize()
+ for the regular output. */
+static int
+test_analyze(struct testset *ts)
+{
+ if (ts->reported) return 0;
+ if (WIFEXITED(ts->status) && WEXITSTATUS(ts->status) != 0) {
+ switch (WEXITSTATUS(ts->status)) {
+ case CHILDERR_DUP:
+ if (!ts->reported) puts("can't dup file descriptors");
+ break;
+ case CHILDERR_EXEC:
+ if (!ts->reported) puts("execution failed (not found?)");
+ break;
+ case CHILDERR_STDERR:
+ if (!ts->reported) puts("can't open /dev/null");
+ break;
+ default:
+ test_summarize(ts, WEXITSTATUS(ts->status));
+ break;
+ }
+ return 0;
+ } else if (WIFSIGNALED(ts->status)) {
+ test_summarize(ts, -WTERMSIG(ts->status));
+ return 0;
+ } else {
+ test_summarize(ts, 0);
+ return (ts->failed == 0);
+ }
+}
+
+
+/* Runs a single test set, accumulating and then reporting the results.
+ Returns true if the test set was successfully run and all tests passed,
+ false otherwise. */
+static int
+test_run(struct testset *ts)
+{
+ pid_t testpid, child;
+ int outfd, i, status;
+ FILE *output;
+ char buffer[BUFSIZ];
+ char *file;
+
+ /* Initialize the test and our data structures, flagging this set in
+ error if the initialization fails. */
+ file = xmalloc(strlen(ts->file) + 3);
+ strcpy(file, ts->file);
+ strcat(file, ".t");
+ testpid = test_start(file, &outfd);
+ free(file);
+ output = fdopen(outfd, "r");
+ if (!output) sysdie("fdopen failed");
+ if (!fgets(buffer, sizeof(buffer), output)) ts->aborted = 1;
+ if (!ts->aborted && !test_init(buffer, ts)) {
+ while (fgets(buffer, sizeof(buffer), output))
+ ;
+ ts->aborted = 1;
+ }
+
+ /* Pass each line of output to test_checkline(). */
+ while (!ts->aborted && fgets(buffer, sizeof(buffer), output))
+ test_checkline(buffer, ts);
+ if (ferror(output)) ts->aborted = 1;
+
+ /* Close the output descriptor, retrieve the exit status, and pass that
+ information to test_analyze() for eventual output. */
+ fclose(output);
+ child = waitpid(testpid, &ts->status, 0);
+ if (child == (pid_t) -1)
+ sysdie("waitpid for %u failed", (unsigned int) testpid);
+ status = test_analyze(ts);
+
+ /* Convert missing tests to failed tests. */
+ for (i = 0; i < ts->count; i++) {
+ if (ts->results[i] == TEST_INVALID) {
+ ts->failed++;
+ ts->results[i] = TEST_FAIL;
+ status = 0;
+ }
+ }
+ return status;
+}
+
+
+/* Summarize a list of test failures. */
+static void
+test_fail_summary(const struct testlist *fails)
+{
+ struct testset *ts;
+ int i, chars, total, first, last;
+
+ puts(header);
+
+ /* Failed Set Fail/Total (%) Skip Stat Failing (25)
+ -------------------------- -------------- ---- ---- -------------- */
+ for (; fails; fails = fails->next) {
+ ts = fails->ts;
+ total = ts->count - ts->skipped;
+ printf("%-26.26s %4d/%-4d %3.0f%% %4d ", ts->file, ts->failed,
+ total, total ? (ts->failed * 100.0) / total : 0,
+ ts->skipped);
+ if (WIFEXITED(ts->status)) {
+ printf("%4d ", WEXITSTATUS(ts->status));
+ } else {
+ printf(" -- ");
+ }
+ if (ts->aborted) {
+ puts("aborted");
+ continue;
+ }
+ chars = 0;
+ first = 0;
+ last = 0;
+ for (i = 0; i < ts->count; i++) {
+ if (ts->results[i] == TEST_FAIL) {
+ if (first && i == last) {
+ last = i + 1;
+ } else {
+ if (first)
+ chars += test_print_range(first, last, chars, 20);
+ first = i + 1;
+ last = i + 1;
+ }
+ }
+ }
+ if (first) test_print_range(first, last, chars, 20);
+ putchar('\n');
+ }
+}
+
+
+/* Run a batch of tests from a given file listing each test on a line by
+ itself. The file must be rewindable. Returns true iff all tests
+ passed. */
+static int
+test_batch(const char *testlist)
+{
+ FILE *tests;
+ size_t length, i;
+ size_t longest = 0;
+ char buffer[BUFSIZ];
+ int line;
+ struct testset ts, *tmp;
+ struct timeval start, end;
+ struct rusage stats;
+ struct testlist *failhead = 0;
+ struct testlist *failtail = 0;
+ int total = 0;
+ int passed = 0;
+ int skipped = 0;
+ int failed = 0;
+ int aborted = 0;
+
+ /* Open our file of tests to run and scan it, checking for lines that
+ are too long and searching for the longest line. */
+ tests = fopen(testlist, "r");
+ if (!tests) sysdie("can't open %s", testlist);
+ line = 0;
+ while (fgets(buffer, sizeof(buffer), tests)) {
+ line++;
+ length = strlen(buffer) - 1;
+ if (buffer[length] != '\n') {
+ fprintf(stderr, "%s:%d: line too long\n", testlist, line);
+ exit(1);
+ }
+ if (length > longest) longest = length;
+ }
+ if (fseek(tests, 0, SEEK_SET) == -1)
+ sysdie("can't rewind %s", testlist);
+
+ /* Add two to longest and round up to the nearest tab stop. This is how
+ wide the column for printing the current test name will be. */
+ longest += 2;
+ if (longest % 8) longest += 8 - (longest % 8);
+
+ /* Start the wall clock timer. */
+ gettimeofday(&start, NULL);
+
+ /* Now, plow through our tests again, running each one. Check line
+ length again out of paranoia. */
+ line = 0;
+ while (fgets(buffer, sizeof(buffer), tests)) {
+ line++;
+ length = strlen(buffer) - 1;
+ if (buffer[length] != '\n') {
+ fprintf(stderr, "%s:%d: line too long\n", testlist, line);
+ exit(1);
+ }
+ buffer[length] = '\0';
+ fputs(buffer, stdout);
+ for (i = length; i < longest; i++) putchar('.');
+ memset(&ts, 0, sizeof(ts));
+ ts.file = xstrdup(buffer);
+ if (!test_run(&ts)) {
+ tmp = xmalloc(sizeof(struct testset));
+ memcpy(tmp, &ts, sizeof(struct testset));
+ if (!failhead) {
+ failhead = xmalloc(sizeof(struct testset));
+ failhead->ts = tmp;
+ failhead->next = 0;
+ failtail = failhead;
+ } else {
+ failtail->next = xmalloc(sizeof(struct testset));
+ failtail = failtail->next;
+ failtail->ts = tmp;
+ failtail->next = 0;
+ }
+ }
+ aborted += ts.aborted;
+ total += ts.count;
+ passed += ts.passed;
+ skipped += ts.skipped;
+ failed += ts.failed;
+ }
+ total -= skipped;
+
+ /* Stop the timer and get our child resource statistics. */
+ gettimeofday(&end, NULL);
+ getrusage(RUSAGE_CHILDREN, &stats);
+
+ /* Print out our final results. */
+ if (failhead) test_fail_summary(failhead);
+ putchar('\n');
+ if (aborted) {
+ printf("Aborted %d test sets, passed %d/%d tests.\n", aborted,
+ passed, total);
+ } else if (failed == 0) {
+ fputs("All tests successful", stdout);
+ if (skipped) printf(", %d tests skipped", skipped);
+ puts(".");
+ } else {
+ printf("Failed %d/%d tests, %.2f%% okay.\n", failed, total,
+ (total - failed) * 100.0 / total);
+ }
+ printf("Files=%d, Tests=%d", line, total);
+ printf(", %.2f seconds", tv_diff(&end, &start));
+ printf(" (%.2f usr + %.2f sys = %.2f CPU)\n",
+ tv_seconds(&stats.ru_utime), tv_seconds(&stats.ru_stime),
+ tv_sum(&stats.ru_utime, &stats.ru_stime));
+ return !(failed || aborted);
+}
+
+
+/* Main routine. Given a file listing tests, run each test listed. */
+int
+main(int argc, char *argv[])
+{
+ if (argc != 2) {
+ fprintf(stderr, "Usage: runtests <test-list>\n");
+ exit(1);
+ }
+ printf(banner, argv[1]);
+ exit(test_batch(argv[1]) ? 0 : 1);
+}
--- /dev/null
+#! /bin/sh
+# $Id: convdate.t 5754 2002-09-09 00:48:21Z rra $
+#
+# Test suite for convdate.
+
+# The count starts at 1 and is updated each time ok is printed. printcount
+# takes "ok" or "not ok".
+count=1
+printcount () {
+ echo "$1 $count $2"
+ count=`expr $count + 1`
+}
+
+# Given the output from convdate and the expected output, compare them.
+compare () {
+ status=$?
+ if [ $status = 0 ] && [ "$1" = "$2" ] ; then
+ printcount "ok"
+ else
+ echo " $1"
+ echo " $2"
+ printcount "not ok"
+ fi
+}
+
+# Find convdate.
+convdate=false
+for file in ../expire/convdate ../../expire/convdate expire/convdate ; do
+ [ -x $file ] && convdate=$file
+done
+if [ $convdate = "false" ] ; then
+ echo "Could not find convdate" >&2
+ exit 1
+fi
+
+# Print out the count of tests.
+echo 7
+
+# Run our tests. These are all from the man page, but with time zones
+# added.
+TZ=EST5EDT; export TZ
+compare "`$convdate 'feb 10, 1991 10am EST'`" 'Sun Feb 10 10:00:00 1991'
+compare "`$convdate '12pm EST 12/13/91' '12am EDT 5/4/90'`" \
+ 'Fri Dec 13 12:00:00 1991
+Fri May 4 00:00:00 1990'
+compare "`$convdate -n 'feb 10, 1991 10am-0500' '12am-0400 5/5/90'`" \
+ '666198000
+641880000'
+compare "`$convdate -c 666198000`" 'Sun Feb 10 10:00:00 1991'
+compare "`$convdate -dc 666198000`" 'Sun, 10 Feb 1991 15:00:00 +0000 (UTC)'
+compare "`env TZ=PST8PDT $convdate -dlc 666198000`" \
+ 'Sun, 10 Feb 1991 07:00:00 -0800 (PST)'
+compare "`env TZ=EST5EDT $convdate -dlc 666198000`" \
+ 'Sun, 10 Feb 1991 10:00:00 -0500 (EST)'